{"record":{"id":"0730d2b6bf53eb96","repo":"XTLS/Xray-core","slug":"connection-ends-0730d2","errorCode":null,"errorMessage":"connection ends","messagePattern":"connection ends","errorType":"exception","errorClass":"errors.Error","httpStatus":null,"severity":"warning","filePath":"proxy/http/client.go","lineNumber":160,"sourceCode":"\t}, p.Timeouts.ConnectionIdle)\n\n\trequestFunc := func() error {\n\t\tdefer timer.SetTimeout(p.Timeouts.DownlinkOnly)\n\t\treturn buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer))\n\t}\n\tresponseFunc := func() error {\n\t\tob.CanSpliceCopy = 1\n\t\tdefer timer.SetTimeout(p.Timeouts.UplinkOnly)\n\t\treturn buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer))\n\t}\n\n\tif newCtx != nil {\n\t\tctx = newCtx\n\t}\n\n\tresponseDonePost := task.OnSuccess(responseFunc, task.Close(link.Writer))\n\tif err := task.Run(ctx, requestFunc, responseDonePost); err != nil {\n\t\treturn errors.New(\"connection ends\").Base(err)\n\t}\n\n\treturn nil\n}\n\n// fillRequestHeader will fill out the template of the headers\nfunc fillRequestHeader(ctx context.Context, header []*Header) ([]*Header, error) {\n\tif len(header) == 0 {\n\t\treturn header, nil\n\t}\n\n\tinbound := session.InboundFromContext(ctx)\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\n\tif inbound == nil || ob == nil {\n\t\treturn nil, errors.New(\"missing inbound or outbound metadata from context\")\n\t}","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/proxy/http/client.go#L142-L178","documentation":"Outer wrapper for failures from the http outbound's bidirectional task group (requestFunc uplink / responseFunc downlink). Any mid-connection error — client disconnect, proxy closing the tunnel, policy timeout in timer.SetTimeout — is reported as \"connection ends\" with the true cause chained underneath.","triggerScenarios":"Established CONNECT tunnel where either copy direction fails: proxy RSTs the tunnel, client closes, or uplinkOnly/downlinkOnly timeouts expire while the tunnel is idle.","commonSituations":"Idle keepalive-heavy traffic through an aggressive proxy, mobile clients dropping, timeouts inherited from policy level 0 (default) being too short.","solutions":["Unwrap to find the underlying copy error before deciding severity","Treat io.EOF/context.Canceled bases as normal teardown","Raise policy timeouts (uplinkOnly/downlinkOnly) for idle-prone traffic through HTTP proxies","If the proxy kills tunnels early, enable periodic traffic or shorter app keepalives"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"```go\nfunc isBenignTeardown(err error) bool {\n    return errors.Is(err, io.EOF) || errors.Is(err, context.Canceled)\n}\n```","tryCatchPattern":"```go\nif err := c.Process(ctx, link, dialer); err != nil && !isBenignTeardown(err) {\n    log.Warn().Err(errors.Unwrap(err)).Msg(\"http outbound connection failed\")\n}\n```","preventionTips":["Unwrap before classifying","Tune timeouts for idle tunnels","Expect proxies to close idle tunnels; keep app keepalives shorter"],"tags":["http-outbound","lifecycle","wrapper","xray"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}