Backend Development 11 min read

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.

360 Tech Engineering
360 Tech Engineering
360 Tech Engineering
Implementing a Simple HTTP/2 Header-Only Server in Go

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 --http2

The -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.

GoServerTLSHTTP/2headerFramer
360 Tech Engineering
Written by

360 Tech Engineering

Official tech channel of 360, building the most professional technology aggregation platform for the brand.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.