or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

bpf.mdcontext-ctxhttp.mdcontext.mddict.mddns-dnsmessage.mdhtml-atom.mdhtml-charset.mdhtml.mdhttp-httpguts.mdhttp-httpproxy.mdhttp2-h2c.mdhttp2-hpack.mdhttp2.mdicmp.mdidna.mdindex.mdipv4.mdipv6.mdnettest.mdnetutil.mdproxy.mdpublicsuffix.mdquic-qlog.mdquic.mdtrace.mdwebdav.mdwebsocket.mdxsrftoken.md
tile.json

http2-h2c.mddocs/

HTTP/2 Cleartext (h2c)

Package h2c implements the unencrypted "h2c" form of HTTP/2.

The h2c protocol is the non-TLS version of HTTP/2 which is not available from net/http or golang.org/x/net/http2.

Import

import "golang.org/x/net/http2/h2c"

Functions

// NewHandler returns an http.Handler that wraps h, intercepting any h2c traffic
func NewHandler(h http.Handler, s *http2.Server) http.Handler

NewHandler

NewHandler returns an http.Handler that wraps h, intercepting any h2c traffic. If a request is an h2c connection, it's hijacked and redirected to s.ServeConn. Otherwise the returned Handler just forwards requests to h.

This works because h2c is designed to be parseable as valid HTTP/1, but ignored by any HTTP server that does not handle h2c. Therefore we leverage the HTTP/1 compatible parts of the Go http library to parse and recognize h2c requests. Once a request is recognized as h2c, we hijack the connection and convert it to an HTTP/2 connection which is understandable to s.ServeConn.

The first request on an h2c connection is read entirely into memory before the Handler is called. To limit the memory consumed by this request, wrap the result of NewHandler in an http.MaxBytesHandler.

Usage Examples

Basic h2c Server

import (
    "golang.org/x/net/http2"
    "golang.org/x/net/http2/h2c"
    "net/http"
)

func main() {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, h2c!"))
    })

    // Create HTTP/2 server configuration
    h2s := &http2.Server{}

    // Wrap handler with h2c support
    h2cHandler := h2c.NewHandler(handler, h2s)

    // Start server
    server := &http.Server{
        Addr:    ":8080",
        Handler: h2cHandler,
    }

    server.ListenAndServe()
}

h2c with Custom HTTP/2 Settings

func setupH2CServer() *http.Server {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "text/plain")
        w.Write([]byte("h2c response"))
    })

    // Configure HTTP/2 server
    h2s := &http2.Server{
        MaxConcurrentStreams: 100,
        IdleTimeout:          5 * time.Minute,
    }

    // Create h2c handler
    h2cHandler := h2c.NewHandler(mux, h2s)

    return &http.Server{
        Addr:    ":8080",
        Handler: h2cHandler,
    }
}

h2c with Memory Limits

func setupLimitedH2CServer() *http.Server {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Handle request
        w.Write([]byte("Response"))
    })

    h2s := &http2.Server{}
    h2cHandler := h2c.NewHandler(handler, h2s)

    // Limit initial request size to 1MB
    limitedHandler := http.MaxBytesHandler(h2cHandler, 1<<20)

    return &http.Server{
        Addr:    ":8080",
        Handler: limitedHandler,
    }
}

Testing h2c Client

import (
    "net/http"
    "golang.org/x/net/http2"
)

func makeH2CRequest() error {
    // Configure HTTP/2 client for h2c
    client := &http.Client{
        Transport: &http2.Transport{
            AllowHTTP: true, // Allow h2c
            DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
                // Use plain TCP connection for h2c
                return net.Dial(network, addr)
            },
        },
    }

    resp, err := client.Get("http://localhost:8080/")
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    // Process response...
    return nil
}