Skip to content

Rewrite std/net/http into a usable HTTP/1.1 server and client#1156

Open
marcauberer wants to merge 5 commits into
mainfrom
feature/http-server-client
Open

Rewrite std/net/http into a usable HTTP/1.1 server and client#1156
marcauberer wants to merge 5 commits into
mainfrom
feature/http-server-client

Conversation

@marcauberer

Copy link
Copy Markdown
Member

What

Rewrites the std/net/http module from a non-functional stub into a usable, platform-independent HTTP/1.1 implementation, and adds tests.

The previous http.spice imported socket_linux directly (broken on other platforms) and had empty start()/serve() bodies. It is replaced with an implementation layered on the cross-platform std/net/socket abstraction.

Module

  • HttpHeaders — insertion-ordered, case-insensitive header collection, so serialization is deterministic.
  • HttpRequest / HttpResponse — value types you can build, serialize and parse. setBody keeps Content-Length in sync. Public data fields plus setHeader/getHeader/hasHeader helpers.
  • parseHttpRequest / parseHttpResponse — socket-free parsers returning Result<...> (split on \r\n\r\n, tolerant of header whitespace/case).
  • reasonPhrase(int) — canonical status text for common codes.
  • HttpServeraddRoute(method, path, handler), handle() dispatch with a 404 fallback, and serveOnce / serveForever / stop over real sockets.
  • HttpClientsend / get / post, auto-adding the Host header.

Drive-by fix

socket_windows.spice referenced SockLenT but never defined it (the Linux/Darwin variants do), which would block importing the socket layer — and now HTTP — on Windows. Added type SockLenT alias int;. Also updated std/README.md, which previously described HTTP as a libcurl-based client.

Tests

Test Runs on CI? Covers
std/net/http-messages yes request/response build + serialize (exact-byte asserts), parse, case-insensitive headers, header overwrite, reason phrases, round-trip
std/net/http-server-routing yes HttpServer.handle route dispatch + 404 fallback (socket-free)
std/net/http-server-client disabled end-to-end server+client over real TCP via a thread pool (mirrors existing sockets-basic)

The two runnable cases assert throughout and print one deterministic line, so their cout.out is just All assertions passed!.

Notes for reviewers

The message (de)serialization is intentionally socket-free and is what the runnable tests exercise. Two things worth a careful look since they lean on less-attested language usage:

  • f<HttpResponse>(const HttpRequest&) used as a struct field (HttpRoute.handler) — procedure-pointer fields (p()) and f<...>(...) pointer params/locals are both attested separately in the tree, but the function-returning variant as a field is new here.
  • The exact-byte serialization asserts in http-messages.

I was unable to compile locally in my environment, so this has been written/reviewed against existing std patterns but not executed. CI (or a local spicetest run) is the real check.

The previous `std/net/http` was a stub: it imported `socket_linux` directly
(breaking other platforms) and had empty `start()`/`serve()` bodies. This
replaces it with a real, platform-independent implementation built on the
cross-platform `std/net/socket` abstraction:

- HttpHeaders: insertion-ordered, case-insensitive header collection so that
  serialization is deterministic.
- HttpRequest / HttpResponse: value types that can be built, serialized and
  parsed. setBody keeps the Content-Length header in sync.
- parseHttpRequest / parseHttpResponse: socket-free parsers returning Result.
- reasonPhrase: canonical status text for common codes.
- HttpServer: route table with addRoute, handle() dispatch with a 404 fallback,
  and serveOnce / serveForever / stop over real sockets.
- HttpClient: send / get / post helpers that auto-add the Host header.

Also defines the previously-undefined `SockLenT` in socket_windows.spice (the
Linux/Darwin variants already define it), which otherwise blocks importing the
socket layer on Windows, and updates the std README to describe the new module.

Tests:
- std/net/http-messages: build/serialize/parse round-trips (socket-free).
- std/net/http-server-routing: HttpServer dispatch + 404 fallback (socket-free).
- std/net/http-server-client: end-to-end server+client over TCP (disabled,
  mirrors the existing sockets-basic convention).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@marcauberer marcauberer requested review from a team as code owners June 2, 2026 14:24
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions github-actions Bot added tests Contains changes to the test cases std Prs / issues regarding the standard lib labels Jun 2, 2026
marcauberer and others added 4 commits June 2, 2026 16:36
Three fixes found via review and CI on the new std/net/http module:

- Client socket I/O went to fd 0: openClientSocket constructed the Socket with
  connFd = 0, but Socket.read/write operate on connFd, so the HTTP client wrote
  the request to stdin and read the response from stdin. connFd now mirrors
  sockFd for client sockets (socket_linux/darwin/windows).

- Accepted connections were leaked: acceptConnection overwrote connFd without
  closing the previous one and only sockFd was ever closed. Added
  Socket.closeConnection() (a no-op for client/unconnected sockets) and call it
  in HttpServer.acceptAndRespond after responding and on the parse-error path.

- socket_windows was missing the entire client path; added connect/inet_addr/
  recv/send/closesocket externs plus Socket.read/write/closeConnection and
  openClientSocket, and switched Socket.close to closesocket.

- Spice's parser does not allow a method call on the result of another call
  (a.b().c()); rewrote the chained calls in http.spice's parse helpers into
  intermediate locals, which is what caused the CI parser error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spice's semantic analysis rejects the binary `+` operator on a char and an
int (`cast<char>(c + 32)`), which failed CI in asciiToLower. Switch to the
compound-assignment idiom already used by std/text/format (`c += <uint>`),
which is supported on char operands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI surfaced: `Cannot apply '=' operator on types String& and const String&`
at splitHeadAndBody's `headSection = raw`. Spice's operator resolution does
not handle a reference-typed variable (a `String&` out-parameter) on the
left-hand side of `=`/`+=`; value variables and struct fields work fine.

- splitHeadAndBody now returns a Pair<String, String> (head, body) instead of
  writing into two `String&` out-parameters; callers read it via copy-init.
- HttpHeaders.appendTo(String& out) -> HttpHeaders.serialize() returning a
  String built on its own value-typed `result`; HttpRequest/HttpResponse
  serialize() concatenate that via a local.

These are the only two spots that mutated a reference-typed local through an
operator; the rest of the parse/serialize paths only assign to value vars and
fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI: a struct-literal initialization of the function-pointer field
(`HttpRoute{ ..., handler }`) was rejected with a field-value type mismatch
even though the expected and actual types print identically -- a Spice
type-identity quirk for function pointers in struct literals.

Add an HttpRoute constructor that assigns the handler via `this.handler =
handler`, the same way std/io/cli-option populates its `p(const T&) callback`
field, and build routes with `HttpRoute(method, path, handler)`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@marcauberer marcauberer added this to the 0.26.0 milestone Jun 2, 2026
@marcauberer marcauberer modified the milestones: 0.26.0, 0.27.0 Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XXL std Prs / issues regarding the standard lib tests Contains changes to the test cases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant