{"record":{"id":"83a05650353533c3","repo":"snail007/goproxy","slug":"http-decoder-data-line-err-s","errorCode":null,"errorMessage":"http decoder data line err:%s","messagePattern":"http decoder data line err:(.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"utils/structs.go","lineNumber":257,"sourceCode":"\nfunc NewHTTPRequest(inConn *net.Conn, bufSize int, isBasicAuth bool, basicAuth *BasicAuth) (req HTTPRequest, err error) {\n\tbuf := make([]byte, bufSize)\n\tlen := 0\n\treq = HTTPRequest{\n\t\tconn: inConn,\n\t}\n\tlen, err = (*inConn).Read(buf[:])\n\tif err != nil {\n\t\tif err != io.EOF {\n\t\t\terr = fmt.Errorf(\"http decoder read err:%s\", err)\n\t\t}\n\t\tCloseConn(inConn)\n\t\treturn\n\t}\n\treq.HeadBuf = buf[:len]\n\tindex := bytes.IndexByte(req.HeadBuf, '\\n')\n\tif index == -1 {\n\t\terr = fmt.Errorf(\"http decoder data line err:%s\", string(req.HeadBuf)[:50])\n\t\tCloseConn(inConn)\n\t\treturn\n\t}\n\tfmt.Sscanf(string(req.HeadBuf[:index]), \"%s%s\", &req.Method, &req.hostOrURL)\n\tif req.Method == \"\" || req.hostOrURL == \"\" {\n\t\terr = fmt.Errorf(\"http decoder data err:%s\", string(req.HeadBuf)[:50])\n\t\tCloseConn(inConn)\n\t\treturn\n\t}\n\treq.Method = strings.ToUpper(req.Method)\n\treq.isBasicAuth = isBasicAuth\n\treq.basicAuth = basicAuth\n\tlog.Printf(\"%s:%s\", req.Method, req.hostOrURL)\n\n\tif req.IsHTTPS() {\n\t\terr = req.HTTPS()\n\t} else {\n\t\terr = req.HTTP()","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/snail007/goproxy/blob/e6d6a821db80e7f47ee6e981a144984e1d4ddb3d/utils/structs.go#L239-L275","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","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.","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.","Ensure the client actually sends a complete request line terminated by CRLF (check for custom clients or modified HTTP stacks)."],"exampleFix":"// before\nlen, err = (*inConn).Read(buf[:])\n...\nindex := bytes.IndexByte(req.HeadBuf, '\\n')\n\n// after\nreader := bufio.NewReader(*inConn)\nheadBuf, err := reader.ReadBytes('\\n') // accumulates until line end\nif err != nil { ... }\nreq.HeadBuf = headBuf","handlingStrategy":"validation","validationCode":"func looksLikeHTTP(firstBytes []byte) bool {\n\tmethods := []string{\"GET\", \"POST\", \"PUT\", \"DELETE\", \"HEAD\", \"OPTIONS\", \"PATCH\", \"CONNECT\", \"TRACE\"}\n\ts := string(firstBytes)\n\tfor _, m := range methods {\n\t\tif strings.HasPrefix(s, m+\" \") {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n// call before proxying: if the sniffed bytes start with 0x16 0x03, the client\n// is speaking TLS to a plain port — reject with a clear message instead.","typeGuard":"func isTLSClientHello(b []byte) bool {\n\treturn len(b) >= 3 && b[0] == 0x16 && b[1] == 0x03\n}","tryCatchPattern":"_, err := utils.NewHTTPRequest(conn, bufSize, isAuth, auth)\nif err != nil && strings.HasPrefix(err.Error(), \"http decoder data line err\") {\n\tlog.Printf(\"non-HTTP data on proxy port (TLS to plain port?): %.20s\", err.Error())\n}","preventionTips":["Use http:// (not https://) in http_proxy/https_proxy env vars unless the proxy itself terminates TLS.","Sniff the first bytes on accept and reject TLS handshakes with a clear log message.","Point port scanners/health checks away from the proxy port or expect this error from them.","Prefer a buffered read-until-CRLF parser over a single Read() if clients may fragment requests."],"tags":["http","parsing","protocol-mismatch","go"],"backgroundTag":"invalid-http-request","analyzedSha":"e6d6a821db80e7f47ee6e981a144984e1d4ddb3d","analyzedAt":"2026-09-03T15:32:42.750Z","contentChangedAt":"2026-09-03T15:32:42.750Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}