Note
This is not a production-ready library (yet). It was built to understand how HTTP actually works under the hood, not to be depended on in real projects.
An HTTP/1.1 server implemented from scratch in Go, directly on top of TCP, with no net/http on the server side. It parses raw request bytes off the wire, and writes responses (status lines, headers, chunked encoding, trailers) by hand, byte for byte, according to the HTTP/1.1 spec.
Most Go web work happens through net/http, which hides the wire format entirely. This project builds the protocol layer itself (a streaming request parser, a header parser, and a response writer capable of fixed-length, chunked, and trailer-bearing responses) to understand what net/http is actually doing underneath.
- Streaming request parser: parses the request line, headers, and body incrementally as bytes arrive, without waiting for the full request to be buffered first.
- Case-insensitive header handling: per RFC 9110, with repeated header fields merged into a single comma-separated value.
- Concurrent connections: each accepted connection is handled on its own goroutine.
- Chunked transfer encoding: stream a response body of unknown length,
Transfer-Encoding: chunked, hex-prefixed chunks. - Trailers: send headers after the body (e.g. a content hash computed only once the full body has been read), announced up front via the
Trailerheader. - A small reverse proxy:
/httpbin/<path>forwards requests tohttps://httpbin.org/<path>and streams the response back to the client chunk by chunk, live, as it arrives.
Requires Go 1.26+.
git clone <this-repo>
cd go-http
go build ./...Start the server:
go run ./cmd/httpserverIt listens on :4000. Try the built-in routes:
curl http://localhost:4000/
curl -i http://localhost:4000/yourproblem # 400 Bad Request
curl -i http://localhost:4000/myproblem # 500 Internal Server ErrorProxy a request through to httpbin.org, streamed as chunked encoding:
curl --raw http://localhost:4000/httpbin/htmlcurl --raw (or nc) is important here: plain curl reassembles chunked responses transparently, hiding the hex chunk sizes and trailers this project actually implements. To see the raw wire format:
echo -e "GET /httpbin/stream/5 HTTP/1.1\r\nHost: localhost:4000\r\nConnection: close\r\n\r\n" | nc localhost 4000Serve a local file with a custom content type:
curl -o /dev/null http://localhost:4000/video(expects assets/vim.mp4 to exist locally, see .gitignore; this path is not committed.)
cmd/
httpserver/ entry point: route table and handlers (main.go)
tcplistener/ early scratch program for raw TCP experimentation
internal/
request/ parses raw bytes into a Request (request line, headers, body)
headers/ case-insensitive header map + parser
response/ Writer: status lines, headers, chunked bodies, trailers
server/ TCP listener, connection lifecycle, handler dispatch
sequenceDiagram
participant Client
participant server as internal/server
participant request as internal/request
participant Handler as myHandler (cmd/httpserver)
participant response as internal/response
Client->>server: TCP connection
server->>request: RequestFromReader(conn)
request-->>server: *Request
server->>Handler: handler(response.NewWriter(conn), req)
Handler->>response: WriteStatusLine / WriteHeaders / WriteBody
Handler->>response: (or) WriteChunkedBody... / WriteChunkedBodyDone / WriteTrailers
response-->>Client: bytes written directly to conn
The handler owns the entire response: it writes the status line, headers, and body (or chunks + trailers) directly to the connection; the server does not buffer or inspect the response on the way out.
go test ./...Covers the request-line/header/body parser (including reads split across arbitrary byte-sized chunks, to make sure the parser doesn't assume anything arrives in one piece), the header map, and an end-to-end test that dials the running server over a real TCP socket and asserts on the raw response bytes.