{"record":{"id":"947b426408a69308","repo":"openimsdk/open-im-server","slug":"error-dynamic-message-passed-to-http-error","errorCode":null,"errorMessage":"error (dynamic message passed to http.Error)","messagePattern":"error \\(dynamic message passed to http\\.Error\\)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/msggateway/context.go","lineNumber":193,"sourceCode":"\t\tinfo.SDKType = GoSDK\n\tcase GoSDK, JsSDK:\n\tdefault:\n\t\treturn servererrs.ErrConnArgsErr.WrapMsg(\"sdkType is invalid\")\n\t}\n\tc.info = info\n\treturn nil\n}\n\nfunc (c *UserConnContext) GetRemoteAddr() string {\n\treturn c.RemoteAddr\n}\n\nfunc (c *UserConnContext) SetHeader(key, value string) {\n\tc.RespWriter.Header().Set(key, value)\n}\n\nfunc (c *UserConnContext) ErrReturn(error string, code int) {\n\thttp.Error(c.RespWriter, error, code)\n}\n\nfunc (c *UserConnContext) GetConnID() string {\n\treturn c.ConnID\n}\n\nfunc (c *UserConnContext) GetUserID() string {\n\tif c == nil || c.info == nil {\n\t\treturn \"\"\n\t}\n\treturn c.info.UserID\n}\n\nfunc (c *UserConnContext) GetPlatformID() int {\n\tif c == nil || c.info == nil {\n\t\treturn 0\n\t}\n\treturn c.info.PlatformID","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/openimsdk/open-im-server/blob/175a7bb0673eca18e9d1b10bff4f728da6b1b513/internal/msggateway/context.go#L175-L211","documentation":"ErrReturn is a thin wrapper around net/http's http.Error, writing an error string and HTTP status code to the WebSocket upgrade response writer. Because the message is a dynamic string passed straight through, any caller-supplied text is emitted as the HTTP response body. This error entry flags that dynamic message: the client sees whatever string the gateway hands to ErrReturn (e.g. invalid token, bad upgrade request, rate limit).","triggerScenarios":"Any code path in the message gateway that calls UserConnContext.ErrReturn(msg, code) before or instead of upgrading the WebSocket connection — e.g. failed authentication/token validation, missing query parameters, rejected origin, or internal errors during the HTTP handshake phase.","commonSituations":"A client hits the WebSocket endpoint with an expired/invalid token and receives a plain-text error body instead of a WS connection; proxy or CORS misconfig causes the handshake to be rejected; gateway version changes the error strings so client-side parsers break; developers accidentally send sensitive internals via this dynamic message.","solutions":["Inspect the HTTP response body/status returned on the WS handshake — the dynamic string is the gateway's rejection reason; fix the underlying condition it reports (usually token or params).","In the gateway, replace free-form strings with typed, constant error messages so clients can match them reliably.","Ensure ErrReturn is only called with non-sensitive, user-safe messages; log full details server-side instead.","Client-side: handle non-101 handshake responses explicitly (check response status/code) rather than treating any failure as a network error."],"exampleFix":"// before\nc.ErrReturn(fmt.Sprintf(\"token invalid: %v\", err), http.StatusUnauthorized)\n\n// after\nc.ErrReturn(\"token invalid\", http.StatusUnauthorized) // details logged server-side","handlingStrategy":"try-catch","validationCode":"// client-side, before treating handshake as success\nresp, err := dialer.DialContext(ctx, wsURL, hdr)\nif err != nil {\n\tif resp != nil && resp.StatusCode != http.StatusSwitchingProtocols {\n\t\tbody, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\treturn fmt.Errorf(\"gateway rejected handshake (%d): %s\", resp.StatusCode, body)\n\t}\n\treturn err\n}","typeGuard":"func isGatewayRejection(err error) bool {\n\tvar he *websocket.HTTPError // or check resp status from your WS lib\n\treturn errors.As(err, &he) && he.StatusCode >= 400\n}","tryCatchPattern":"conn, resp, err := dialer.Dial(wsURL, hdr)\nif err != nil {\n\tif resp != nil {\n\t\tswitch resp.StatusCode {\n\t\tcase http.StatusUnauthorized:\n\t\t\t// refresh token and retry once\n\t\tcase http.StatusForbidden:\n\t\t\t// origin/permission problem — do not retry\n\t\tdefault:\n\t\t\tlog.Printf(\"gateway error: %s\", resp.Status)\n\t\t}\n\t}\n\treturn err\n}","preventionTips":["Always check the HTTP status of a failed WS handshake before retrying; the dynamic message explains the rejection.","Refresh credentials proactively so token-expiry rejections (the most common ErrReturn path) are rare.","Match on status code, not on the error string — gateway messages are dynamic and may change between versions.","Server-side: restrict ErrReturn inputs to a fixed set of constant messages to keep client handling stable."],"tags":["http","websocket","error-handling","msggateway"],"backgroundTag":"dynamic-http-error-message","analyzedSha":"175a7bb0673eca18e9d1b10bff4f728da6b1b513","analyzedAt":"2026-09-04T16:52:56.821Z","contentChangedAt":"2026-09-04T16:52:56.821Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}