{"record":{"id":"7e81e59f4a90b930","repo":"ginuerzh/gost","slug":"bad-request","errorCode":null,"errorMessage":"bad request","messagePattern":"bad request","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"http2.go","lineNumber":792,"sourceCode":"\tselect {\n\tcase l.connChan <- conn:\n\tdefault:\n\t\tconn.Close()\n\t\tlog.Logf(\"[http2] %s - %s: connection queue is full\", conn.RemoteAddr(), conn.LocalAddr())\n\t}\n\n\t<-conn.closed // NOTE: we need to wait for streaming end, or the connection will be closed\n}\n\nfunc (l *h2Listener) upgrade(w http.ResponseWriter, r *http.Request) (*http2Conn, error) {\n\tif l.path == \"\" && r.Method != http.MethodConnect {\n\t\tw.WriteHeader(http.StatusMethodNotAllowed)\n\t\treturn nil, errors.New(\"method not allowed\")\n\t}\n\n\tif l.path != \"\" && r.RequestURI != l.path {\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\treturn nil, errors.New(\"bad request\")\n\t}\n\n\tw.WriteHeader(http.StatusOK)\n\tif fw, ok := w.(http.Flusher); ok {\n\t\tfw.Flush() // write header to client\n\t}\n\n\tremoteAddr, _ := net.ResolveTCPAddr(\"tcp\", r.RemoteAddr)\n\tif remoteAddr == nil {\n\t\tremoteAddr = &net.TCPAddr{\n\t\t\tIP:   net.IPv4zero,\n\t\t\tPort: 0,\n\t\t}\n\t}\n\tconn := &http2Conn{\n\t\tr:          r.Body,\n\t\tw:          flushWriter{w},\n\t\tlocalAddr:  l.Listener.Addr(),","sourceCodeStart":774,"sourceCodeEnd":810,"githubUrl":"https://github.com/ginuerzh/gost/blob/a33fdbf4c98034f4bfeeaea9868909822b9c526d/http2.go#L774-L810","documentation":"h2Listener.upgrade rejects an HTTP/2 request whose RequestURI does not match the listener's configured path (l.path != \"\"). The request reaches the handler but is not on the endpoint the listener was bound to, so it is answered with 400 Bad Request and this error.","triggerScenarios":"Client dials an h2Listener created with a specific path (e.g. \"/tunnel\") but sends the request to a different URI, or sends a CONNECT (empty RequestURI in HTTP/2) to a path-bound listener.","commonSituations":"Client/server path configuration mismatch (one side configured with \"/ws\", other with \"/tunnel\"); base-URL handling on the client stripping or adding path segments; HTTP/2 CONNECT requests which carry :path differently than the literal configured path.","solutions":["Make the client dial URI exactly match the path configured on the h2Listener (including leading slash and no extra segments).","Remove the path restriction from the listener if it should accept any request.","Log r.RequestURI server-side to compare against the configured l.path and correct the mismatch.","If using HTTP/2 CONNECT tunneling, do not bind the listener to a path, since CONNECT requests may not carry the expected URI."],"exampleFix":"// before (mismatched paths)\nserver: NewH2Listener(host, WithPath(\"/tunnel\"))\nclient: dial(\"https://host/ws\")\n// after\nserver: NewH2Listener(host, WithPath(\"/tunnel\"))\nclient: dial(\"https://host/tunnel\")","handlingStrategy":"validation","validationCode":"// client side: URI must match the server listener path exactly\nif serverPath != \"\" && reqURI != serverPath {\n    return fmt.Errorf(\"request URI %q does not match configured h2 path %q\", reqURI, serverPath)\n}","typeGuard":null,"tryCatchPattern":"conn, err := dialer.Dial(ctx, network, addr)\nif err != nil {\n    if strings.Contains(err.Error(), \"bad request\") {\n        return fmt.Errorf(\"check h2 listener path vs dial URI: %w\", err)\n    }\n    return err\n}","preventionTips":["Store the h2 path in shared config used by both client and server.","Watch for base-URL joins that add/drop path segments.","Avoid binding a path-restricted listener if you also accept raw CONNECT traffic."],"tags":["http2","bad-request","path-mismatch","server"],"backgroundTag":"http-400-bad-request","analyzedSha":"a33fdbf4c98034f4bfeeaea9868909822b9c526d","analyzedAt":"2026-09-02T22:15:54.506Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}