cooper

package module
v0.0.0-...-53f8988 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 7, 2026 License: MIT Imports: 9 Imported by: 0

README

cooper

A Go package for handling the HTTP/1.1 101 Switching Protocols handshake on both ends of a connection returning a net.Conn.

Features

  • Server-side hijack: Handle upgrade requests with cooper.Hijack, an http.Handler that negotiates the handshake and passes the raw connection to your code.
  • Client-side upgrade: Perform the client half of the handshake over any net.Conn with cooper.Upgrade.
  • Protocol negotiation: Restrict which protocols the server accepts; unrecognised clients receive 426 Upgrade Required.
  • Response validation: Verify custom handshake headers (e.g. Sec-WebSocket-Accept) before the connection is handed over.
  • Buffered data safety: Leftover HTTP buffer bytes are transparently prepended to the returned connection — always safe to read from directly.
  • Zero dependencies: No external imports. Every type in the public API is from the standard library.

Installation

go get lowbit.dev/cooper

Usage

// Server
http.Handle("/raw", cooper.Hijack(func(conn net.Conn, proto string) {
    defer conn.Close()
    io.Copy(conn, conn)
}))

// Client
conn, _ := net.Dial("tcp", "host:8080")
req, _ := http.NewRequest("GET", "http://host:8080/raw", nil)
req.Header.Set("Upgrade", "myproto/1")

upgraded, err := cooper.Upgrade(conn, req)
if err != nil {
    // err wraps one of the Err* sentinels
}
defer upgraded.Close()

Server options

  • Protocols(names ...string): Restrict accepted upgrade values. Case-insensitive. Unrecognised protocols receive 426.
  • ResponseHeaders(fn): Inject additional headers into the 101 response (e.g. Sec-WebSocket-Accept).
  • OnError(fn): Receive errors that occur after the response writer is gone — write/flush failures and recovered handler panics.
cooper.Hijack(handler,
    cooper.Protocols("myproto/1", "myproto/2"),
    cooper.ResponseHeaders(func(r *http.Request, proto string) http.Header {
        h := http.Header{}
        h.Set("Sec-WebSocket-Accept", deriveAccept(r.Header.Get("Sec-WebSocket-Key")))
        return h
    }),
    cooper.OnError(func(err error) {
        slog.Error("upgrade error", "err", err)
    }),
)

TLS

cooper.Upgrade works over any net.Conn, including TLS. Dial with tls.Dial before passing the connection in.

tlsConn, err := tls.Dial("tcp", "host:8443", &tls.Config{
    ServerName: "host",
})
if err != nil {
    // handle
}

req, _ := http.NewRequest("GET", "https://host:8443/raw", nil)
req.Header.Set("Upgrade", "myproto/1")

upgraded, err := cooper.Upgrade(tlsConn, req)

Client options

  • ResponseValidator(fn): Called after Cooper's own checks pass. Return an error to reject the response; it is wrapped with ErrResponseValidator.
cooper.Upgrade(conn, req,
    cooper.ResponseValidator(func(req *http.Request, resp *http.Response) error {
        if resp.Header.Get("Sec-WebSocket-Accept") != deriveAccept(req.Header.Get("Sec-WebSocket-Key")) {
            return errors.New("accept header mismatch")
        }
        return nil
    }),
)

Error sentinels

All errors wrap a sentinel value detectable with errors.Is.

  • ErrHijackFailed: Connection could not be hijacked from the HTTP server.
  • ErrWriteHandshake: Failed to write the 101 response.
  • ErrFlushHandshake: Failed to flush the 101 response.
  • ErrHandlerPanic: Recovered panic in the handler goroutine.
  • ErrDrainBuffer: Could not drain the HTTP read buffer before handing off the connection.
  • ErrMissingUpgradeHeader: Request carries no Upgrade header.
  • ErrSetDeadline: Handshake deadline could not be set.
  • ErrSendRequest: Upgrade request could not be written.
  • ErrReadResponse: Server response could not be read or parsed.
  • ErrUnexpectedStatus: Server responded with a status other than 101.
  • ErrProtocolMismatch: Server's Upgrade response header doesn't match what was requested.
  • ErrMissingConnectionHeader: Server's response is missing Connection: Upgrade.
  • ErrResponseValidator: A ResponseValidator rejected the response.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrHijackFailed is delivered to the OnError callback when the server
	// cannot take ownership of the connection from the HTTP server.
	ErrHijackFailed = errors.New("hijack failed")

	// ErrWriteHandshake is delivered when the 101 response cannot be written.
	ErrWriteHandshake = errors.New("write handshake response failed")

	// ErrFlushHandshake is delivered when the 101 response cannot be flushed.
	ErrFlushHandshake = errors.New("flush handshake response failed")

	// ErrHandlerPanic is delivered when a HijackHandler panics.
	// The recovered value is appended to the error message.
	ErrHandlerPanic = errors.New("handler panic")
)
View Source
var (
	// ErrMissingUpgradeHeader is returned by Upgrade when the request has no
	// Upgrade header set.
	ErrMissingUpgradeHeader = errors.New("missing Upgrade header")

	// ErrSetDeadline is returned by Upgrade when the handshake deadline cannot
	// be set on the connection.
	ErrSetDeadline = errors.New("set handshake deadline failed")

	// ErrSendRequest is returned by Upgrade when the HTTP request cannot be
	// written to the connection.
	ErrSendRequest = errors.New("send upgrade request failed")

	// ErrReadResponse is returned by Upgrade when the server response cannot
	// be read or parsed.
	ErrReadResponse = errors.New("read upgrade response failed")

	// ErrUnexpectedStatus is returned by Upgrade when the server responds with
	// a status other than 101 Switching Protocols.
	ErrUnexpectedStatus = errors.New("unexpected response status")

	// ErrProtocolMismatch is returned by Upgrade when the Upgrade header in
	// the server's response does not match the requested protocol.
	ErrProtocolMismatch = errors.New("protocol mismatch in response")

	// ErrMissingConnectionHeader is returned by Upgrade when the server's
	// response is missing the required Connection: Upgrade header.
	ErrMissingConnectionHeader = errors.New("response missing Connection: Upgrade header")

	// ErrResponseValidator is returned by Upgrade when a ResponseValidator
	// option rejects the server's response.
	ErrResponseValidator = errors.New("response validation failed")
)
View Source
var ErrDrainBuffer = errors.New("drain read buffer failed")

ErrDrainBuffer is returned or delivered when leftover buffered bytes from the HTTP layer cannot be read before the connection is handed to the caller.

Functions

func Hijack

func Hijack(handler HijackHandler, opts ...Option) http.Handler

Hijack returns an http.Handler that performs a full HTTP/1.1 protocol upgrade handshake and hands the raw connection to handler.

Use Protocols to restrict accepted upgrade values. Use OnError to receive errors that occur after the response writer is no longer available.

Ownership of the connection transfers to handler on success; Hijack closes conn only when the handshake itself fails.

func Upgrade

func Upgrade(conn net.Conn, req *http.Request, opts ...UpgradeOption) (net.Conn, error)

Upgrade performs an HTTP/1.1 protocol upgrade on conn using req. On success it returns a net.Conn.

The caller is fully responsible for constructing req: method, URL, headers, and an optional body may all be set. The only hard requirement is that req.Header must contain "Upgrade: <proto>" (used for response validation).

Types

type HijackHandler

type HijackHandler func(conn net.Conn, proto string)

HijackHandler is called after a successful HTTP/1.1 upgrade handshake. conn is the raw connection ready for protocol use; proto is the negotiated protocol value from the Upgrade header.

Ownership of conn transfers to the handler — it is responsible for closing it.

type Option

type Option func(*config)

Option configures the behaviour of Hijack.

func OnError

func OnError(fn func(error)) Option

OnError sets a callback that receives errors occurring during or after the handshake. This includes write failures and recovered handler panics. If not set, these errors are silently discarded.

func Protocols

func Protocols(protos ...string) Option

Protocols restricts the server to the given protocol names. Matching is case-insensitive. Clients requesting a protocol not in the list receive a 426 Upgrade Required response listing the accepted protocols. When Protocols is not set, any non-empty Upgrade value is accepted.

func ResponseHeaders

func ResponseHeaders(fn func(*http.Request, string) http.Header) Option

ResponseHeaders sets a function that produces additional headers to include in the 101 Switching Protocols response. It receives the incoming request and the negotiated protocol name so challenge headers (e.g. Sec-WebSocket-Accept) can be derived from them. Headers returned here are merged into the response after the mandatory Connection and Upgrade headers.

type UpgradeOption

type UpgradeOption func(*upgradeConfig)

UpgradeOption configures the behaviour of Upgrade.

func ResponseValidator

func ResponseValidator(fn func(req *http.Request, resp *http.Response) error) UpgradeOption

ResponseValidator sets a function that is called after Cooper's own header checks pass (101 status, Upgrade match, Connection: Upgrade). The validator receives the original request and the server response, and may inspect any additional headers — for example to verify Sec-WebSocket-Accept. If it returns an error, Upgrade returns that error wrapped with ErrResponseValidator.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL