Implementing a Simple HTTP/2 Header-Only Server in Go
This article demonstrates how to build a minimal HTTP/2 server in Go that responds only with header frames, covering certificate generation, server code, frame structures, the http2Framer API, and how to test the implementation using curl, providing both conceptual explanations and full source listings.
The previous chapters introduced many conceptual aspects of HTTP/2, which can feel abstract; this part shifts to a concrete, practical example by implementing a tiny HTTP/2 server that only returns a header and keeps the connection open.
First, a self‑signed TLS certificate is generated with openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt . The resulting server.key and server.crt files are used by the Go server; because the certificate is self‑signed, clients must ignore verification (e.g., using -k with curl).
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/header", func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-custom-header", "custom header")
w.WriteHeader(http.StatusNoContent)
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
select {}
})
log.Println("start listen on 8080...")
log.Fatal(http.ListenAndServeTLS(":8080", "server.crt", "server.key", nil))
}Running the server and testing it with a recent curl that supports HTTP/2:
curl "https://localhost:8080/header" -k -i --http2The -k flag disables certificate verification, -i shows response headers, and --http2 forces the use of HTTP/2 (the client will fall back to HTTP/1.1 if the server does not support it).
In HTTP/2 the smallest transmission unit is a Frame . The article shows the Go definition of the generic frame interface and the concrete http2HeadersFrame structure, including its header, priority parameters, and payload buffer.
type http2HeadersFrame struct {
http2FrameHeader
Priority http2PriorityParam
headerFragBuf []byte // not owned
}
type http2PriorityParam struct {
StreamDep uint32 // stream this one depends on (0 = no dependency)
Exclusive bool
Weight uint8 // 0‑255, actual weight = Weight+1
}The http2Framer type is responsible for low‑level reading and writing of frames. Its key fields include the underlying io.Reader and io.Writer , buffers for the frame header, and methods such as ReadFrame , WriteHeaders , and WriteData .
type http2Framer struct {
r io.Reader
w io.Writer
headerBuf [http2frameHeaderLen]byte
// ... other fields omitted for brevity
}
func http2NewFramer(w io.Writer, r io.Reader) *http2Framer { /* ... */ }
func (f *http2Framer) ReadFrame() (http2Frame, error) { /* ... */ }
func (f *http2Framer) WriteHeaders(p http2HeadersFrameParam) error { /* ... */ }During request handling, after adding a custom header with w.Header().Add() , the server flushes the response using the Flush() method of the underlying http2responseWriter . Because no body is written, the flush triggers only a Headers frame without the END_STREAM flag, keeping the stream open for future server‑push or other interactions.
Overall, the article ties together TLS setup, Go’s HTTP/2 server internals, frame definitions, and the framer API to illustrate how a minimal header‑only HTTP/2 service can be built and verified.
360 Tech Engineering
Official tech channel of 360, building the most professional technology aggregation platform for the brand.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.