snail007/goproxy · error

http decoder data line err:%s

Error message

http decoder data line err:%s

What it means

After reading the request head, NewHTTPRequest looks for the first newline ('\n') to delimit the HTTP request line. If no newline exists in the received bytes, it throws "http decoder data line err:%s" with the first 50 bytes of the buffer, closes the connection, and returns. This means the data received does not look like an HTTP request head at all.

Source

Thrown at utils/structs.go:257

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)
	req.isBasicAuth = isBasicAuth
	req.basicAuth = basicAuth
	log.Printf("%s:%s", req.Method, req.hostOrURL)

	if req.IsHTTPS() {
		err = req.HTTPS()
	} else {
		err = req.HTTP()

View on GitHub (pinned to e6d6a821db)

Solutions

  1. Check the client proxy configuration: a plain-HTTP proxy port must receive plain HTTP; remove https:// from the proxy URL if the proxy is not TLS-enabled (use http:// for http_proxy/https_proxy unless the proxy itself speaks TLS).
  2. Inspect the 50-byte hexdump in the error: bytes starting 0x16 0x03 indicate a TLS ClientHello hitting a plain port — reconfigure the client or wrap the proxy in TLS.
  3. If requests are merely fragmented, this simple single-Read parser is the limitation; buffer with a bufio.Reader and read until \r\n\r\n instead of one Read() call.
  4. Ensure the client actually sends a complete request line terminated by CRLF (check for custom clients or modified HTTP stacks).

Example fix

// before
len, err = (*inConn).Read(buf[:])
...
index := bytes.IndexByte(req.HeadBuf, '\n')

// after
reader := bufio.NewReader(*inConn)
headBuf, err := reader.ReadBytes('\n') // accumulates until line end
if err != nil { ... }
req.HeadBuf = headBuf
Defensive patterns

Strategy: validation

Validate before calling

func looksLikeHTTP(firstBytes []byte) bool {
	methods := []string{"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH", "CONNECT", "TRACE"}
	s := string(firstBytes)
	for _, m := range methods {
		if strings.HasPrefix(s, m+" ") {
			return true
		}
	}
	return false
}
// call before proxying: if the sniffed bytes start with 0x16 0x03, the client
// is speaking TLS to a plain port — reject with a clear message instead.

Type guard

func isTLSClientHello(b []byte) bool {
	return len(b) >= 3 && b[0] == 0x16 && b[1] == 0x03
}

Try / catch

_, err := utils.NewHTTPRequest(conn, bufSize, isAuth, auth)
if err != nil && strings.HasPrefix(err.Error(), "http decoder data line err") {
	log.Printf("non-HTTP data on proxy port (TLS to plain port?): %.20s", err.Error())
}

Prevention

When it happens

Trigger: Calling NewHTTPRequest when the client sends a partial request line without CRLF, sends raw TLS handshake bytes (\x16\x03...) to a plaintext HTTP proxy port, sends binary/garbage data, or the read returns only part of the first line in one packet.

Common situations: Client configured to use HTTPS on the proxy port of a plain-HTTP proxy (https_proxy pointing at a non-TLS proxy); tools like curl with `-k` sending TLS to the wrong port; a client that crashed mid-write; non-HTTP protocols pointed at the proxy.

Related errors


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