gofr-dev/gofr · error
: cannot hijack connection
Error message
%w: cannot hijack connection
What it means
This wrapped error is produced by StatusResponseWriter.Hijack in GoFr's logging middleware: when the inner http.ResponseWriter cannot be cast to http.Hijacker, it returns fmt.Errorf("%w: cannot hijack connection", errHijackNotSupported). Callers can match it with errors.Is against the sentinel. It means the connection upgrade requested by the handler cannot proceed through this writer.
Solutions
- Make every wrapper in the writer chain implement http.Hijacker by delegating to the inner writer
- Exclude upgrade-style routes from wrapping by non-hijackable middleware
- Handle the error in the handler (close/flush normally or return 500) using errors.Is
- Test upgrades with a full httptest.Server, which provides a hijackable connection
Example fix
// before
conn, rw, err := w.Hijack() // "response writer does not support hijacking: cannot hijack connection"
// after
conn, rw, err := w.Hijack()
if err != nil {
if errors.Is(err, middleware.ErrHijackNotSupported) {
http.Error(w, "upgrade not supported", http.StatusInternalServerError)
return
}
} Defensive patterns
Strategy: type-guard
Validate before calling
if h, ok := w.(http.Hijacker); !ok {
return fmt.Errorf("writer %T cannot hijack; needed for connection upgrade", w)
} else { _ = h } Type guard
func hijacker(w http.ResponseWriter) (http.Hijacker, bool) {
h, ok := w.(http.Hijacker)
return h, ok
} Try / catch
conn, rw, err := srw.Hijack()
if errors.Is(err, middleware.ErrHijackNotSupported) {
log.Warn("connection upgrade not possible through this writer chain")
http.Error(srw, "upgrade unsupported", http.StatusInternalServerError)
return
} Prevention
- Forward the Hijacker interface through all custom middleware writers
- Route upgrade-only handlers outside wrapping middleware or unwrap before hijacking
- Test upgrades end-to-end with httptest.NewServer
- Check errors.Is(err, ErrHijackNotSupported) to give clear upgrade-failure responses
When it happens
Trigger: A handler (typically a WebSocket/upgrade request) calls Hijack on the logging middleware's StatusResponseWriter while the response writer beneath it does not implement http.Hijacker.
Common situations: WebSocket endpoints routed through logging-only writer chains, third-party middleware that wraps the writer without forwarding Hijack, or test doubles lacking Hijack support.
Related errors
- response writer does not support hijacking
- Could not open WebSocket connection
- api keys list is empty
- bucket id must not be empty
- bucket name must not be empty
AI-assisted analysis of gofr-dev/gofr@187eb24962 (2026-09-01).
Data as JSON: /api/errors/8c6d08caa596eb11.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/gofr/http/middleware/logger.go:90
}
return w.status
}
// Unwrap returns the wrapped ResponseWriter so http.NewResponseController can reach the underlying
// connection for Flush and SetWriteDeadline — needed for streaming responses.
func (w *StatusResponseWriter) Unwrap() http.ResponseWriter {
return w.ResponseWriter
}
// Hijack implements the http.Hijacker interface. So that we are able to upgrade to a websocket
// connection that requires the responseWriter implementation to implement this method.
func (w *StatusResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hijacker, ok := w.ResponseWriter.(http.Hijacker); ok {
return hijacker.Hijack()
}
return nil, nil, fmt.Errorf("%w: cannot hijack connection", errHijackNotSupported)
}
// RequestLog represents a log entry for HTTP requests.
type RequestLog struct {
TraceID string `json:"trace_id,omitempty"`
SpanID string `json:"span_id,omitempty"`
StartTime string `json:"start_time,omitempty"`
ResponseTime int64 `json:"response_time,omitempty"`
Method string `json:"method,omitempty"`
UserAgent string `json:"user_agent,omitempty"`
IP string `json:"ip,omitempty"`
URI string `json:"uri,omitempty"`
Response int `json:"response,omitempty"`
}
// zeroTraceID is the canonical 32-zero string the W3C trace-context
// invalid TraceID prints to. We use it for the X-Correlation-ID
// response header AND for the request-log field when no SpanContextView on GitHub (pinned to 187eb24962)