tests: add HTTP server unit tests and fix several identified bugs #1471
Labels
No labels
UX
active development
backlog
blocker
bootstrap
bounty
bug
dependencies
discussion
documentation
duplicate
enhancement
flaky test
help wanted
invalid
javascript
question
release
tendentious
wontfix
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
mighty-gerbils/gerbil!1471
Loading…
Reference in a new issue
No description provided.
Delete branch "hmarcien/net-http-tests"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
@ -65,1 +68,4 @@(catch (e)(void)))));; read request head (executes concurrently with the writer thread)(let* (((values status status-line)Don't we want to thread-join! or otherwise catch any subthread error in the main thread (after maybe using ##thread-end-with-uncaught-exception! in the internal thread)?
My initial intuition, was that we could just let the Gambit runtime silently swallow the EPIPE without needing to join.
However, if we do a synchronous thread-join!, I think we will recreate the TCP deadlock: if the server stops reading (e.g. for a 413) but keeps the socket open, the writer thread will block on I/O and the main thread will deadlock on the join.
Because of this, I will go with your second suggestion and use ##thread-end-with-uncaught-exception! to surface real bugs without blocking the main thread.
is this really necessary? We do want to join this thread somewhere indeed but i am not convinced the write should happen asynchronously at all.
Why do we need it? We have to write the request anyway before we read the response.
The dangling thread can become problematic too when we upgrade in things like websockets as well.
@ -12,2 +15,4 @@(make-temporary-file-name "log."))(def tmp-file(string-append (make-temporary-file-name "file") ".js"))make-temporary-file-name now supports a suffix: argument.
@ -50,8 +51,13 @@(serve-dir self.path)I'm not sure what serve-dir does, but shouldn't that only be attempted after the path has been found safe?
serve-dir simply looks for an index.html inside the base directory. It is only called when the requested URL exactly matches the root path, meaning there is no user-provided subpath to sanitize here. The directory traversal check (safe-target?) is applied in the other branch, where a subpath is actually requested.
@ -55,0 +57,4 @@(let ((root (path-normalize self.path)))(let ((root/ (if (string-suffix? "/" root) root (string-append root "/"))))(string-prefix? root/ target))))))(if (and safe-target? (file-exists? target))Wait, is the root a path in the URL or in the filesystem?
Is target absolute or relative? If relative, to what?
The root (from self.path) is a path in the filesystem. target is an absolute filesystem path because path-normalize resolves the URL subpath relative to the physical root. Then, safe-target? simply checks that this final absolute path still starts with the root directory to prevent any ../ escapes.
@ -35,64 +36,75 @@=> :pair(cons "Content-Type" (mime-file-type path)))(defsyntax-case with-requested-file-info ()You might be better off using the new with-identifiers or maybe even the new with-id (in a syntax-rules)
left some comments to address.
@ -65,0 +66,4 @@(write-newline! writer)(__request-write-body! writer body)(writer.flush)(catch (OSError? e)i think the pipe errors should propagate and not cause a timeout.
@ -12,3 +13,3 @@new-directory-listing-handler)(defstruct directory-handler(defstruct (directory-handler HttpMethodHandler)we have a funky case of kebab eating camels here.
@ -55,0 +55,4 @@(safe-target?(and target(let ((root (path-normalize self.path)))(let ((root/ (if (string-suffix? "/" root) root (string-append root "/"))))lets precompute this and store it on the handler, no need to do it for every request.
@ -15,3 +16,3 @@(def cache-threshold 32768)(defstruct file-handler(defstruct (file-handler HttpMethodHandler)camel kebab :)
@ -0,0 +7,4 @@(export #t)(defstruct HttpMethodHandler ())i think we need to kebab case this.
@ -0,0 +34,4 @@(implement RequestHandler HttpMethodHandler(handle-request!(lambda (self (req : Request) (res : ResponseHandler))(using (self : MethodHandler)this will cast and allocate a new instance for every request.
it is preferable to have a constructor that precasts self to this and stores it in the object.
@ -74,0 +73,4 @@;; MethodHandler extends RequestHandler to provide HTTP verb-specific routing.;; Concrete handlers should inherit from HttpMethodHandler (in http-method.ss);; to automatically dispatch requests and fallback to 405 Method Not Allowed.(interface (MethodHandler RequestHandler)nice
@ -198,2 +198,3 @@(addr :- sockaddr))"bind(___arg1, ___arg2, (socklen_t)___U8VECTORSIZE(___ARG2))")"bind(___arg1, ___arg2,(___arg2->sa_family == AF_INET) ? sizeof(struct sockaddr_in) :is this actually necessary? caller preallocates. no?
also i don't think bind cares at all if you give it bigger.
@ -202,2 +206,3 @@(addr :- sockaddr))"connect(___arg1, ___arg2, (socklen_t)___U8VECTORSIZE(___ARG2))")"connect(___arg1, ___arg2,(___arg2->sa_family == AF_INET) ? sizeof(struct sockaddr_in) :same here
@ -239,2 +247,3 @@(dest :- sockaddr))"sendto(___arg1, (void*)(___arg2 + ___arg3), ___arg4, ___arg5, ___arg6, (socklen_t)___U8VECTORSIZE(___ARG6))")"sendto(___arg1, (void*)(___arg2 + ___arg3), ___arg4, ___arg5, ___arg6,(___arg6->sa_family == AF_INET) ? sizeof(struct sockaddr_in) :and here.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.