{"record":{"id":"306f9070bf96dfa0","repo":"siyuan-note/siyuan","slug":"the-request-body-is-empty-or-truncated-eof-decode","errorCode":null,"errorMessage":"the request body is empty or truncated (EOF)","messagePattern":"the request body is empty or truncated \\(EOF\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/decode.go","lineNumber":38,"sourceCode":"\t}\n\tif e.decodeRequest != nil {\n\t\treturn e.decodeRequest(reader)\n\t}\n\tif e.definition.Body == StructJSONBody {\n\t\terr = json.NewDecoder(reader).Decode(&request)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"Parses request [%s] failed: %s\", e.definition.Path, err)\n\t\t}\n\t\treturn\n\t}\n\tvar fields map[string]json.RawMessage\n\terr = json.NewDecoder(reader).Decode(&fields)\n\tif err != nil {\n\t\tif e.definition.Body == LegacyOptionalBody {\n\t\t\treturn request, nil\n\t\t}\n\t\tif errors.Is(err, io.EOF) {\n\t\t\terr = errors.New(\"the request body is empty or truncated (EOF)\")\n\t\t}\n\t\treturn request, fmt.Errorf(\"Parses request [%s] failed: %s\", e.definition.Path, err)\n\t}\n\tvalue := reflect.ValueOf(&request).Elem()\n\tif value.Kind() != reflect.Struct {\n\t\treturn request, fmt.Errorf(\"request contract must be a struct\")\n\t}\n\terr = decodeRequestFields(value, fields)\n\treturn request, err\n}\n\nfunc decodeRequestFields(value reflect.Value, fields map[string]json.RawMessage) error {\n\tfor i := 0; i < value.NumField(); i++ {\n\t\tfield := value.Type().Field(i)\n\t\tname := strings.Split(field.Tag.Get(\"json\"), \",\")[0]\n\t\tif (!field.IsExported() && !field.Anonymous) || name == \"-\" {\n\t\t\tcontinue\n\t\t}","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/decode.go#L20-L56","documentation":"Decode reads the HTTP request body into the contract's field map via json.Decoder.Decode. When the body is completely empty or ends prematurely, Decode returns io.EOF, which this code replaces with an explicit message before wrapping it with the failing contract path. It exists so callers can distinguish 'no/short body' from malformed JSON.","triggerScenarios":"Sending a POST/PUT request to an apicontract endpoint with a zero-length body, a Content-Length larger than the actual bytes sent, or a connection cut mid-body so the JSON is truncated.","commonSituations":"Forgetting to pass a body for endpoints whose contract does not declare LegacyOptionalBody; proxy/gateway buffering stripping the body; streaming clients that close the connection before flushing; tests calling the handler directly with no io.Reader content.","solutions":["Ensure the client sends a valid JSON body with the required fields for this endpoint","Check Content-Length / Transfer-Encoding and that the client actually flushes the payload","If the endpoint legitimately accepts no body, mark its contract field with LegacyOptionalBody","Inspect intermediaries (proxies, load balancers) that may truncate request bodies"],"exampleFix":"// before: client sends request without body\nreq, _ := http.NewRequest(\"POST\", url, nil)\n// after\nbody := []byte(`{\"id\":\"20240101120000-abc\"}`)\nreq, _ := http.NewRequest(\"POST\", url, bytes.NewReader(body))\nreq.Header.Set(\"Content-Type\", \"application/json\")","handlingStrategy":"validation","validationCode":"function ensureBody(body) {\n  if (!body || (typeof body === \"string\" && body.trim() === \"\")) throw new Error(\"Request body is empty; supply a JSON payload\")\n  const text = typeof body === \"string\" ? body : JSON.stringify(body)\n  try { JSON.parse(text) } catch (e) { throw new Error(\"Request body is not complete JSON: \" + e.message) }\n  return text\n}","typeGuard":"function hasBody(req) { return req.body !== undefined && req.body !== null && Object.keys(req.body).length > 0 }","tryCatchPattern":"try { await api.call(payload) } catch (e) { if (/empty or truncated \\(EOF\\)/.test(e.message)) { console.error(\"Body was empty/truncated — check client serialization and proxies\", payload) } else { throw e } }","preventionTips":["Always set Content-Type: application/json and send a serialized body","Verify Content-Length matches bytes actually sent when crafting raw requests","For no-body endpoints, confirm the contract declares LegacyOptionalBody","Test through the same proxy chain used in production"],"tags":["http","json","request-body","api-contract"],"backgroundTag":"empty-response-body","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}