snail007/goproxy · warning

http decoder read err:%s

Error message

http decoder read err:%s

What it means

NewHTTPRequest reads the first chunk of the client connection into a buffer to parse the HTTP request head. If the Read() call fails with any error other than io.EOF, it wraps it as "http decoder read err:%s", closes the connection, and returns. This is a transport-level read failure before any HTTP parsing occurs.

Source

Thrown at utils/structs.go:249

	conn        *net.Conn
	Host        string
	Method      string
	URL         string
	hostOrURL   string
	isBasicAuth bool
	basicAuth   *BasicAuth
}

func NewHTTPRequest(inConn *net.Conn, bufSize int, isBasicAuth bool, basicAuth *BasicAuth) (req HTTPRequest, err error) {
	buf := make([]byte, bufSize)
	len := 0
	req = HTTPRequest{
		conn: inConn,
	}
	len, err = (*inConn).Read(buf[:])
	if err != nil {
		if err != io.EOF {
			err = fmt.Errorf("http decoder read err:%s", err)
		}
		CloseConn(inConn)
		return
	}
	req.HeadBuf = buf[:len]
	index := bytes.IndexByte(req.HeadBuf, '\n')
	if index == -1 {
		err = fmt.Errorf("http decoder data line err:%s", string(req.HeadBuf)[:50])
		CloseConn(inConn)
		return
	}
	fmt.Sscanf(string(req.HeadBuf[:index]), "%s%s", &req.Method, &req.hostOrURL)
	if req.Method == "" || req.hostOrURL == "" {
		err = fmt.Errorf("http decoder data err:%s", string(req.HeadBuf)[:50])
		CloseConn(inConn)
		return
	}
	req.Method = strings.ToUpper(req.Method)

View on GitHub (pinned to e6d6a821db)

Solutions

  1. Treat this as an expected client-side disconnect: log it at debug level and skip — the connection is already closed by the library, just return without retrying on the same conn.
  2. If it happens for every request, check what the client is actually sending: a client speaking HTTPS directly to the HTTP proxy port will cause handshake bytes to break reads — point the client at the correct proxy port or enable the CONNECT/TLS path.
  3. Set/extend socket read deadlines if timeouts are the cause (review any SetReadDeadline on the accepted conn).
  4. Check for intermediary devices (NAT, LB) dropping idle connections and add TCP keepalives on the listener.

Example fix

len, err = (*inConn).Read(buf[:])
if err != nil {
	if err != io.EOF {
		// caller-side handling
		var ne net.Error
		if errors.As(err, &ne) && ne.Timeout() {
			log.Println("client read timeout, closing conn")
		} else {
			log.Printf("client dropped connection: %v", err)
		}
	}
}
Defensive patterns

Strategy: try-catch

Try / catch

_, err := utils.NewHTTPRequest(conn, bufSize, isAuth, auth)
if err != nil {
	var ne net.Error
	if errors.As(err, &ne) && ne.Timeout() {
		log.Printf("client read timed out: %v", err)
	} else {
		log.Printf("client aborted before sending request (normal for connection pooling): %v", err)
	}
	// connection already closed by NewHTTPRequest; do not reuse
	return
}

Prevention

When it happens

Trigger: Calling NewHTTPRequest (via the callback path) when the underlying TCP conn.Read fails: the client abruptly closed/resets the connection (ECONNRESET), a read deadline expired, or the socket was torn down while the server was waiting for the request bytes.

Common situations: Browsers pre-opening connections and closing them before sending a request (very common with Chrome/keep-alive pooling); clients timing out behind slow proxies; the client sending a TLS handshake or other binary data that the raw TCP reader chokes on; idle keep-alive connections reaped by an intermediate NAT/firewall sending RST.

Related errors


AI-assisted analysis of snail007/goproxy@e6d6a821db (2026-09-03). Data as JSON: /api/errors/008666e48b104336. Report an issue: GitHub.