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
- Ensure any custom ResponseWriter wrapper embeds or delegates Flush() to the underlying writer.
- Implement http.Flusher on your wrapper: func (w *myWriter) Flush() { w.w.(http.Flusher).Flush() }.
- Check that middleware (logging, gzip, etc.) preserves the Flusher interface.
- 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
- Ensure any custom ResponseWriter wrapper delegates Flush() to the underlying writer.
- Test middleware wrappers to confirm they implement http.Flusher.
- Avoid response-buffering middleware in front of gRPC streaming endpoints.
- Use standard net/http server which provides Flusher-capable writers.
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
- invalid gRPC request content-type
- invalid gRPC request method
- gRPC requires HTTP/2
- transport: set send compressor called after headers sent or…
- buffer size is not an exponent of two
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)