gofr-dev/gofr · error

%w: 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.

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 SpanContext

View on GitHub (pinned to 187eb24962)

Solutions

  1. Make every wrapper in the writer chain implement http.Hijacker by delegating to the inner writer
  2. Exclude upgrade-style routes from wrapping by non-hijackable middleware
  3. Handle the error in the handler (close/flush normally or return 500) using errors.Is
  4. 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

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


AI-assisted analysis of gofr-dev/gofr@187eb24962 (2026-09-01). Data as JSON: /api/errors/8c6d08caa596eb11. Report an issue: GitHub.