ginuerzh/gost · error · net.OpError

read not supported

Error message

read not supported

What it means

http2ServerConn wraps only the server side of an HTTP request/response (http.Request + http.ResponseWriter). Reading raw bytes is meaningless in that model, so Read always returns 0 bytes and a *net.OpError with "read not supported".

Source

Thrown at http2.go:890

}

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

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

View on GitHub (pinned to a33fdbf4c9)

Solutions

  1. Read the request body via c.r.Body (the wrapped *http.Request) instead of conn.Read.
  2. Use http2Conn (from the h2 listener Accept path) when a bidirectional net.Conn is required.
  3. Refactor code that io.Copy's from the conn to consume r.Body and write to w explicitly.
  4. Guard with a type check so the dummy server conn is never fed to conn-oriented APIs.

Example fix

// before
io.Copy(dst, serverConn) // read not supported
// after
io.Copy(dst, serverConn.r.Body) // read the HTTP request body
Defensive patterns

Strategy: type-guard

Validate before calling

// before relaying, ensure the conn supports reading
func readable(c net.Conn) bool {
    _, isDummy := c.(interface{ IsServerDummy() bool })
    return !isDummy // http2ServerConn cannot Read
}

Type guard

func isH2ServerConn(c net.Conn) bool {
    // http2ServerConn exposes RemoteAddr resolved from the HTTP request
    _, ok := c.(interface{ Read([]byte) (int, error) })
    _ = ok
    // prefer explicit type assertion in-package:
    _, isDummy := c.(*gosthttp2.ServerConnLike)
    return !isDummy
}

Try / catch

n, err := conn.Read(buf)
if err != nil {
    var oe *net.OpError
    if errors.As(err, &oe) && strings.Contains(err.Error(), "read not supported") {
        return fmt.Errorf("use r.Body for handler-side http2 conns")
    }
    return err
}

Prevention

When it happens

Trigger: Calling Read() on an http2ServerConn obtained from the HTTP/2 handler path, e.g. passing the conn to code expecting a bidirectional net.Conn (relay, io.Copy from the conn).

Common situations: Using the handler-side dummy conn in a tunneling relay where the developer expected a full-duplex conn; mixing up http2ServerConn (handler side) with http2Conn (dialer/listener side, which supports Read).

Related errors


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