{"record":{"id":"4e2d8d7e7e1424ab","repo":"AlexxIT/go2rtc","slug":"err-error-4e2d8d","errorCode":null,"errorMessage":"err.Error()","messagePattern":"err\\.Error\\(\\)","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/webrtc/server.go","lineNumber":86,"sourceCode":"\tif stream == nil {\n\t\thttp.Error(w, api.StreamNotFound, http.StatusNotFound)\n\t\treturn\n\t}\n\n\tmediaType := r.Header.Get(\"Content-Type\")\n\tif mediaType != \"\" {\n\t\tmediaType, _, _ = strings.Cut(mediaType, \";\")\n\t\tmediaType = strings.ToLower(strings.TrimSpace(mediaType))\n\t}\n\n\tvar offer string\n\n\tswitch mediaType {\n\tcase \"application/json\":\n\t\tvar desc pion.SessionDescription\n\t\tif err := json.NewDecoder(r.Body).Decode(&desc); err != nil {\n\t\t\tlog.Error().Err(err).Caller().Send()\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\toffer = desc.SDP\n\n\tcase \"application/x-www-form-urlencoded\":\n\t\tif err := r.ParseForm(); err != nil {\n\t\t\tlog.Error().Err(err).Caller().Send()\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tofferB64 := r.Form.Get(\"data\")\n\t\tb, err := base64.StdEncoding.DecodeString(offerB64)\n\t\tif err != nil {\n\t\t\tlog.Error().Err(err).Caller().Send()\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\toffer = string(b)","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/AlexxIT/go2rtc/blob/c245815e75e2a5fd60b4290f12bfc04e55a984d3/internal/webrtc/server.go#L68-L104","documentation":"When Content-Type is application/json, outputWebRTC decodes the request body into a pion.SessionDescription. If the body is not valid JSON (or not shaped like a session description), json.Decode fails and the handler logs the error and returns HTTP 400 with the decoder's error text. This guards the JSON SDP-exchange path against malformed payloads.","triggerScenarios":"POST with Content-Type: application/json but body is raw SDP text instead of {\"type\":\"offer\",\"sdp\":\"v=0\\r\\n...\"}; truncated body; sending SDP with the JSON content type; invalid JSON syntax.","commonSituations":"Clients that read the SDP from a file and forget to wrap it in the JSON envelope; copy-paste errors adding trailing text; a fetch() call whose body was serialized twice or not at all.","solutions":["Send the body as {\"type\":\"offer\",\"sdp\":\"<full SDP>\"} when using Content-Type: application/json","Or drop the JSON Content-Type and send raw SDP (application/sdp) so the default branch reads the body","Validate the JSON body client-side before sending"],"exampleFix":"// before\nfetch(url, {method:'POST', headers:{'Content-Type':'application/json'}, body: pc.localDescription.sdp})\n// after\nfetch(url, {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(pc.localDescription)})","handlingStrategy":"validation","validationCode":"const payload = {type: pc.localDescription.type, sdp: pc.localDescription.sdp};\nJSON.parse(JSON.stringify(payload)); // throws early if not serializable\nif (!payload.sdp?.startsWith('v=0')) throw new Error('invalid SDP');","typeGuard":"const isSessionDescription = (d) => d && typeof d.sdp === 'string' && d.sdp.startsWith('v=0');","tryCatchPattern":"try { ... } catch (e) { if (e.response?.status === 400) { console.error('Body must be valid JSON SessionDescription'); } }","preventionTips":["Always JSON.stringify the full localDescription, not just the sdp string","Match Content-Type to the actual payload format","Add a client-side sanity check that the SDP starts with 'v=0'"],"tags":["http-400","json","webrtc","request-body"],"backgroundTag":"json-decode-failed","analyzedSha":"c245815e75e2a5fd60b4290f12bfc04e55a984d3","analyzedAt":"2026-09-07T11:47:02.965Z","contentChangedAt":"2026-09-07T11:47:02.965Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}