grpc/grpc-go · error

gRPC requires a ResponseWriter supporting http.Flusher

Error message

gRPC requires a ResponseWriter supporting http.Flusher

What it means

NewServerHandlerTransport (handler_server.go:53) lets you run a gRPC server inside a standard net/http.Handler. It requires the http.ResponseWriter to implement http.Flusher because gRPC streaming relies on flushing frames to the client as they are produced. If w.(http.Flusher) fails, it writes an HTTP 500 and returns this error (it is a returned error, not a panic).

Source

Thrown at internal/transport/handler_server.go:75

		http.Error(w, msg, http.StatusMethodNotAllowed)
		return nil, errors.New(msg)
	}
	contentType := r.Header.Get("Content-Type")
	// TODO: do we assume contentType is lowercase? we did before
	contentSubtype, validContentType := grpcutil.ContentSubtype(contentType)
	if !validContentType {
		msg := fmt.Sprintf("invalid gRPC request content-type %q", contentType)
		http.Error(w, msg, http.StatusUnsupportedMediaType)
		return nil, errors.New(msg)
	}
	if r.ProtoMajor != 2 {
		msg := "gRPC requires HTTP/2"
		http.Error(w, msg, http.StatusHTTPVersionNotSupported)
		return nil, errors.New(msg)
	}
	if _, ok := w.(http.Flusher); !ok {
		msg := "gRPC requires a ResponseWriter supporting http.Flusher"
		http.Error(w, msg, http.StatusInternalServerError)
		return nil, errors.New(msg)
	}

	var localAddr net.Addr
	if la := r.Context().Value(http.LocalAddrContextKey); la != nil {
		localAddr, _ = la.(net.Addr)
	}
	var authInfo credentials.AuthInfo
	if r.TLS != nil {
		authInfo = credentials.TLSInfo{State: *r.TLS, CommonAuthInfo: credentials.CommonAuthInfo{SecurityLevel: credentials.PrivacyAndIntegrity}}
	}
	p := peer.Peer{
		Addr:      strAddr(r.RemoteAddr),
		LocalAddr: localAddr,
		AuthInfo:  authInfo,
	}
	st := &serverHandlerTransport{
		rw:             w,

View on GitHub (pinned to 03255a9237)

Solutions

  1. Ensure the ResponseWriter passed to the gRPC http handler is the original or a wrapper that embeds/promotes http.Flusher.
  2. If you wrap the writer, implement Flush() on your wrapper that delegates to the underlying Flusher.
  3. Do not insert a buffering layer between the HTTP server and the gRPC handler.

Example fix

// before: a buffering wrapper hides Flush()
type bufWriter struct{ http.ResponseWriter }
func (b *bufWriter) Write(p []byte) (int, error) { /* buffer */ ... }
// passing &bufWriter{w} to the gRPC handler -> error

// after: promote Flusher
type bufWriter struct{ http.ResponseWriter }
func (b *bufWriter) Flush() {
    if f, ok := b.ResponseWriter.(http.Flusher); ok { f.Flush() }
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Verify Flusher support before dispatching to the gRPC handler
func handler(w http.ResponseWriter, r *http.Request) {
    if _, ok := w.(http.Flusher); !ok {
        http.Error(w, "streaming requires http.Flusher", http.StatusInternalServerError)
        return
    }
    // ...delegate to gRPC...
}

Type guard

func supportsFlush(w http.ResponseWriter) bool {
    _, ok := w.(http.Flusher)
    return ok
}

Prevention

When it happens

Trigger: Calling NewServerHandlerTransport with a ResponseWriter that does not satisfy http.Flusher. The standard net/http server's ResponseWriter DOES implement Flusher (when Flush is available), but a custom/ wrapped ResponseWriter (e.g. a buffering middleware, a gzip writer, a test httptest mock missing Flusher) may not.

Common situations: Wrapping the http.ResponseWriter in a buffering or compressing middleware before delegating to the gRPC handler; an httptest.ResponseRecorder (which does implement Flusher, but a custom test double may not); running behind a proxy that decorates the writer.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/23e758ece59dd7ae. Report an issue: GitHub.