grpc/grpc-go · error

gRPC requires a ResponseWriter supporting http.Flusher

Error message

gRPC requires a ResponseWriter supporting http.Flusher

What it means

Returned by NewServerHandlerTransport when the http.ResponseWriter does not implement the http.Flusher interface. gRPC streaming requires flushing partial responses to the client as they are produced (for real-time streaming), so the ResponseWriter must support Flusher. The transport checks this with a type assertion at handler_server.go:73 and rejects with HTTP 500 if it fails.

Solutions

  1. Ensure any custom ResponseWriter wrapper embeds or delegates Flush() to the underlying writer.
  2. Implement http.Flusher on your wrapper: func (w *myWriter) Flush() { w.w.(http.Flusher).Flush() }.
  3. Check that middleware (logging, gzip, etc.) preserves the Flusher interface.
  4. In tests, use real http.Server or ensure your ResponseRecorder implements Flusher.

Example fix

// before: wrapper drops Flusher
type logWriter struct { http.ResponseWriter }
func (lw *logWriter) Write(b []byte) (int, error) { return lw.ResponseWriter.Write(b) }

// after: forward Flush()
type logWriter struct { http.ResponseWriter }
func (lw *logWriter) Write(b []byte) (int, error) { return lw.ResponseWriter.Write(b) }
func (lw *logWriter) Flush() {
    if f, ok := lw.ResponseWriter.(http.Flusher); ok { f.Flush() }
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Verify the ResponseWriter implements http.Flusher before passing to gRPC.
if _, ok := w.(http.Flusher); !ok {
    log.Fatal("ResponseWriter must support http.Flusher for gRPC streaming")
}

Type guard

// Type guard to ensure a ResponseWriter supports Flusher.
func supportsFlusher(w http.ResponseWriter) bool {
    _, ok := w.(http.Flusher)
    return ok
}

Try / catch

st, err := transport.NewServerHandlerTransport(w, r, stats, pool)
if err != nil {
    if strings.Contains(err.Error(), "http.Flusher") {
        log.Fatal("wrap ResponseWriter with Flush() support for gRPC")
    }
}

Prevention

When it happens

Trigger: Wrapping or replacing the http.ResponseWriter passed to the gRPC handler with a custom type that does not embed or implement http.Flusher. Common with custom middleware, logging wrappers, or response recorders (e.g., httptest.ResponseRecorder in older Go versions did not implement Flusher, though modern ones do).

Common situations: Custom HTTP middleware wrapping the ResponseWriter for logging/metrics without forwarding Flush(); using httptest.ResponseRecorder that lacks Flusher (Go < 1.8); a response-buffering proxy that wraps the writer; test doubles that don't implement the full interface.

Related errors


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

Appendix: source

Thrown at internal/transport/handler_server.go:76

		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,
		req:            r,

View on GitHub (pinned to 0c51461d27)