Skip to content

feat: add AcceptOptions.ErrorHandler - #588

Draft
mafredri wants to merge 2 commits into
masterfrom
mafredri/accept-error-handler
Draft

mafredri wants to merge 2 commits into
masterfrom
mafredri/accept-error-handler

Conversation

@mafredri

Copy link
Copy Markdown
Member

Accept writes a plain text http.Error response when it rejects a handshake request. A server that needs a structured error body, such as RFC 9457 JSON (#545), cannot replace that response. A second write after Accept returns only appends to the plain text body: the status and text/plain content type stay Accept's, and net/http logs a superfluous WriteHeader call.

AcceptOptions.ErrorHandler func(w http.ResponseWriter, r *http.Request, status int, err error) writes that response instead. It receives the status Accept chose and the error Accept returns, before wrapping. w.Header() already holds the headers Accept set, such as Sec-WebSocket-Version: 13 for an unsupported version. It is called for every rejection before the 101 response: request validation, origin, and a ResponseWriter without http.Hijacker. It is not called when hijacking fails, because Accept has already written 101 Switching Protocols. A handler that writes nothing leaves net/http's default 200 OK with an empty body.

A nil ErrorHandler keeps the previous response. The Accept doc now says not to write to w after Accept returns an error.

Tests cover the handler for all 11 rejections. A separate test pins the default status, headers and body for all 11 and passes on master. For four rejections, the raw responses match master byte for byte, excluding Date.

Fixes #545

Decision log
  • Handler field instead of a typed error plus an opt-out flag: with the flag, callers must write after a rejection but not after a hijack failure, and a wrong branch gives 200 OK or a double write.
  • The handler receives r, so one handler shared across requests can use it, for example r.Context(). Allow customize how HTTP errors are rendered when websocket.Accept fails to upgrade #545 discussed capturing the request in a closure instead, which does not work for shared options.
  • Documentation alone was rejected because callers still could not change the body (Allow customize how HTTP errors are rendered when websocket.Accept fails to upgrade #545, Support disabling error writing via AcceptOptions #152).
  • No exported per-reason errors; status and error text are enough to render a response, and sentinels can be added later.
  • For an invalid OriginPatterns entry the handler receives "Forbidden", as Accept returns today; the pattern error is still only logged.
  • Without http.Hijacker, the handler receives the descriptive error, while the nil default body stays "Not Implemented".
  • No recover around the handler: a panic behaves like any other handler panic in net/http.
  • The hijack failure after the 101 is unchanged; whether to hijack before writing 101 is a separate decision.
  • The field is also added to the js/wasm AcceptOptions stub so shared code compiles.

🤖 This PR was created with the help of Coder Agents.

Accept's rejection responses had no test that checked the status,
headers and body together. Pinning them for every rejection before the
101 lets a change to how the response is written prove that the default
output stays the same.
Accept always wrote a plain text http.Error response when it rejected a
handshake, so servers with a structured error format had to write a
second response, which net/http appends to Accept's body and logs as a
superfluous WriteHeader call. ErrorHandler lets the caller write that
response instead, with the same signature as gorilla's Upgrader.Error;
nil keeps the previous output.

Refs #545
@mafredri
mafredri force-pushed the mafredri/accept-error-handler branch from ff05d69 to f154711 Compare October 30, 2026 15:18

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow customize how HTTP errors are rendered when websocket.Accept fails to upgrade

1 participant