{"record":{"id":"eeeb76abeb629574","repo":"MHSanaei/3x-ui","slug":"s-s-w-content-length-d-cap-d","errorCode":null,"errorMessage":"%s %s: %w (content-length %d, cap %d)","messagePattern":"(.+?) (.+?): %w \\(content-length (.+?), cap (.+?)\\)","errorType":"exception","errorClass":"errRemoteResponseTooLarge","httpStatus":null,"severity":"error","filePath":"internal/web/runtime/remote.go","lineNumber":273,"sourceCode":"\n\t// Validate status before reading a success payload: a non-OK response's\n\t// body is never used beyond a short diagnostic, so don't let a node force us\n\t// to buffer a large body just to return an HTTP error.\n\tif resp.StatusCode != http.StatusOK {\n\t\tsnippet, _ := io.ReadAll(io.LimitReader(resp.Body, errBodyDiagBytes))\n\t\tif msg := bytes.TrimSpace(snippet); len(msg) > 0 {\n\t\t\t// %q quotes/escapes the untrusted node body so control characters or\n\t\t\t// newlines in it can't garble or inject into the error/log output.\n\t\t\treturn nil, fmt.Errorf(\"%s %s: HTTP %d: %q\", method, path, resp.StatusCode, msg)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"%s %s: HTTP %d\", method, path, resp.StatusCode)\n\t}\n\n\t// Fast-fail on an honestly-declared oversize body; the LimitReader below is\n\t// the real guard since Content-Length is untrusted, may be absent, or is -1\n\t// under transparent decompression.\n\tif resp.ContentLength > maxRemoteResponseBytes {\n\t\treturn nil, fmt.Errorf(\"%s %s: %w (content-length %d, cap %d)\", method, path, errRemoteResponseTooLarge, resp.ContentLength, maxRemoteResponseBytes)\n\t}\n\n\traw, err := readCappedBody(resp.Body, maxRemoteResponseBytes)\n\tif err != nil {\n\t\tif errors.Is(err, errRemoteResponseTooLarge) {\n\t\t\treturn nil, fmt.Errorf(\"%s %s: %w (cap %d bytes)\", method, path, err, maxRemoteResponseBytes)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"read body: %w\", err)\n\t}\n\n\tvar env envelope\n\tif err := json.Unmarshal(raw, &env); err != nil {\n\t\treturn nil, fmt.Errorf(\"decode envelope: %w\", err)\n\t}\n\tif !env.Success {\n\t\treturn &env, &remoteAPIError{msg: env.Msg}\n\t}\n\treturn &env, nil","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/web/runtime/remote.go#L255-L291","documentation":"Returned by Remote.do when the response's declared Content-Length already exceeds maxRemoteResponseBytes, wrapping errRemoteResponseTooLarge. This is the fast-fail path: it refuses to stream a body the node honestly declared as oversized. It protects the master's memory from a node (or something impersonating one) that returns a gigantic payload.","triggerScenarios":"An RPC whose response legitimately grew past the cap — e.g. panel/api/inbounds/list on a node with an enormous number of inbounds/clients — or a node endpoint replying with a huge non-JSON payload (log dump, HTML page) because of a version mismatch or wrong route.","commonSituations":"Node with thousands of inbounds or very large settings JSON; master pointed at a URL that returns something other than the expected API (misconfigured address returning a big static file); a runaway handler on the node serializing internal state.","solutions":["Check the reported content-length vs cap: if it is a legitimate inbound-list response, shrink the payload (fewer inbounds per node, trim client remarks) or raise maxRemoteResponseBytes in remote.go after measuring real sizes.","curl -sI the same endpoint on the node to see what the big body actually is — if it is not JSON, the master is hitting the wrong route.","Verify master and node versions match so list endpoints return the same compact shape.","Do not wrap or gzip-transform the response in a way that inflates Content-Length (proxy double-encoding)."],"exampleFix":"// before: node returns 40MB inbound list\n// err: GET panel/api/inbounds/list: remote response too large (content-length 41943040, cap 8388608)\n\n// after: split clients across more nodes/inbounds, or in remote.go raise the constant after review\n// const maxRemoteResponseBytes = 64 << 20","handlingStrategy":"validation","validationCode":"// Estimate list size before fetching\nif nodeInboundCount > safeInboundBudget { // e.g. thousands of clients per node\n    return errors.New(\"node payload likely exceeds master cap; split inbounds across nodes\")\n}","typeGuard":"func isResponseTooLarge(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"remote response too large\")\n}","tryCatchPattern":"if err := remote.ListInboundOptions(ctx); err != nil {\n    if isResponseTooLarge(err) {\n        // capacity decision, not a retry: shard or raise cap deliberately\n        return shardNodeInbounds(nodeID)\n    }\n    return err\n}","preventionTips":["Keep per-node inbound/client counts within a planned budget instead of one giant node.","Measure real list-payload sizes after major client-count growth.","Never point the master's node address at a non-API URL; a big static file triggers this exact failure."],"tags":["http","resource-limits","node-sync","remote","payload-size"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}