ginuerzh/gost · error · net.OpError

write not supported

Error message

write not supported

What it means

http2ServerConn.Write is a stub returning a *net.OpError with "write not supported", because writes must go through the wrapped http.ResponseWriter rather than a raw byte sink. The dummy conn only exposes Close/addr/deadline behavior for interface compatibility.

Source

Thrown at http2.go:894

}

func (c *http2Conn) SetWriteDeadline(t time.Time) error {
	return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}

// a dummy HTTP2 server conn used by HTTP2 handler
type http2ServerConn struct {
	r      *http.Request
	w      http.ResponseWriter
	closed chan struct{}
}

func (c *http2ServerConn) Read(b []byte) (n int, err error) {
	return 0, &net.OpError{Op: "read", Net: "http2", Source: nil, Addr: nil, Err: errors.New("read not supported")}
}

func (c *http2ServerConn) Write(b []byte) (n int, err error) {
	return 0, &net.OpError{Op: "write", Net: "http2", Source: nil, Addr: nil, Err: errors.New("write not supported")}
}

func (c *http2ServerConn) Close() error {
	select {
	case <-c.closed:
	default:
		close(c.closed)
	}
	return nil
}

func (c *http2ServerConn) LocalAddr() net.Addr {
	addr, _ := net.ResolveTCPAddr("tcp", c.r.Host)
	return addr
}

func (c *http2ServerConn) RemoteAddr() net.Addr {
	addr, _ := net.ResolveTCPAddr("tcp", c.r.RemoteAddr)

View on GitHub (pinned to a33fdbf4c9)

Solutions

  1. Write response data via c.w (http.ResponseWriter), after flushing headers with the http.Flusher.
  2. Use http2Conn from the listener/dialer path for raw conn writes (it backs onto an HTTP/2 stream pipe).
  3. Replace conn-based relay writes with a pipe: write into an io.Pipe handed to the ResponseWriter.
  4. Type-check before writing so the dummy server conn is never passed to byte-oriented code.

Example fix

// before
serverConn.Write(buf) // write not supported
// after
serverConn.w.Write(buf) // write via the underlying ResponseWriter
if fw, ok := serverConn.w.(http.Flusher); ok { fw.Flush() }
Defensive patterns

Strategy: type-guard

Type guard

func isUnsupportedWrite(err error) bool {
    var oe *net.OpError
    return errors.As(err, &oe) && oe.Net == "http2" && strings.Contains(err.Error(), "write not supported")
}

Try / catch

if _, err := conn.Write(b); err != nil {
    if isUnsupportedWrite(err) {
        return fmt.Errorf("write via http.ResponseWriter, not the dummy server conn")
    }
    return err
}

Prevention

When it happens

Trigger: Calling Write() on an http2ServerConn, e.g. relaying bytes with io.Copy(serverConn, src) or writing handshake payloads directly to the conn.

Common situations: Treating the handler-side conn as a bidirectional tunnel; porting code from a TCP-based handler where conn.Write worked; forgetting that HTTP/2 response data must be written to the ResponseWriter with proper headers flushed.

Related errors


AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02). Data as JSON: /api/errors/27faff3043a63a03. Report an issue: GitHub.