Golang HTTP响应反序列化报错:unexpected end of JSON input 问题排查与解决
从你的描述和代码来看,核心问题是部分200响应的JSON内容被截断,导致反序列化时触发"unexpected end of JSON input"错误。下面一步步分析原因并给出解决方案:
一、先修复最明显的代码笔误
你的代码里有一个严重的类型不匹配错误:在200响应分支的最后,你返回的是response(*http.Response类型),但函数定义的返回值是(*model.Response, error)。这不仅会导致编译错误,还可能掩盖其他问题,首先要修正:
// 错误写法 return response, nil // 正确写法 return responseModel, nil
二、核心问题:响应体未完整读取或服务器返回不完整JSON
错误信息"unexpected end of JSON input"明确说明反序列化时JSON内容不完整,可能的原因有两个:
- 服务器返回的JSON本身存在截断(比如网络中断、服务器内部错误导致未发送完整响应);
- 客户端流式读取响应体时发生中断(比如未设置超时,导致读取过程卡住或仅读取部分内容)。
解决方案:先读取完整响应体再反序列化
替换原来的json.NewDecoder(response.Body).Decode为先读取整个响应体,再反序列化。这样可以确保获取到完整的响应内容,同时方便排查问题:
修改200响应的处理逻辑:
if response.StatusCode == http.StatusOK { // 先读取完整响应体 bodyBytes, readErr := ioutil.ReadAll(response.Body) if readErr != nil { zap.S().Errorf("Failed to read full response body: %v", readErr) return nil, errors.New("Failed to read response content") } // 调试时打印完整响应(生产环境可改为debug级别或关闭) zap.S().Debugf("Received full response: %s", string(bodyBytes)) // 校验Content-Length是否匹配实际读取长度 if response.ContentLength != -1 && int(response.ContentLength) != len(bodyBytes) { zap.S().Warnf("Content-Length mismatch: expected %d, got %d", response.ContentLength, len(bodyBytes)) } // 反序列化 err = json.Unmarshal(bodyBytes, &responseModel) if err != nil { errorMessage := "Request response decode error" zap.S().Errorf("%v, Err: %v, Full Response: %s", errorMessage, err, string(bodyBytes)) return nil, errors.New(errorMessage) } return responseModel, nil }
三、完善客户端配置与错误处理
1. 设置全局请求超时
你的代码里只处理了连接超时,但没有设置整个请求(包括读取响应)的超时时间。如果服务器迟迟不发送完整响应,客户端会一直等待,最终可能读取到不完整的内容。给http.Client添加超时:
client := &http.Client{ Timeout: 15 * time.Second, // 根据业务需求调整超时时间 }
2. 修复错误处理逻辑
原来的超时判断顺序不合理,应该先处理通用错误,再判断是否为超时:
response, err := client.Do(request) if err != nil { // 优先判断超时错误 if timeoutErr, ok := err.(net.Error); ok && timeoutErr.Timeout() { zap.S().Error("Response timeout exceeded") return nil, errors.New("Response timeout exceeded") } // 处理其他请求错误 zap.S().Errorf("Failed to send request: %v", err) return nil, errors.New("Failed to send request") } // 防御性编程:确保response不为空 if response == nil { zap.S().Error("Received nil response") return nil, errors.New("Received nil response") }
3. 不要忽略http.NewRequest的错误
原来的代码直接request, _ := http.NewRequest(...)忽略了错误,这可能导致后续请求异常,应该处理:
request, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(byteData)) if err != nil { zap.S().Errorf("Failed to create HTTP request: %v", err) return nil, err } request.Close = true
四、验证JSON合法性
如果修改后仍然报错,把日志里打印的完整响应内容复制到JSON校验工具中检查,确认是否存在语法错误(比如未转义的特殊字符、缺失的括号/引号等)。从你提供的示例响应来看,arz字段里的<script>内容有正确的转义(\"),但实际返回的响应可能存在转义问题。
总结修改后的完整代码片段
func SendRequest(requestModel *model.Request) (*model.Response, error) { responseModel := &model.Response{} byteData, err := json.Marshal(requestModel) if err != nil { zap.S().Errorf("Error marshalling request. Err: %v", err) return nil, err } url := "xx" request, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(byteData)) if err != nil { zap.S().Errorf("Failed to create HTTP request: %v", err) return nil, err } request.Close = true // 设置全局超时 client := &http.Client{ Timeout: 15 * time.Second, } response, err := client.Do(request) if err != nil { if timeoutErr, ok := err.(net.Error); ok && timeoutErr.Timeout() { zap.S().Error("Response timeout exceeded") return nil, errors.New("Response timeout exceeded") } zap.S().Errorf("Failed to send request: %v", err) return nil, errors.New("Failed to send request") } if response == nil { zap.S().Error("Received nil response") return nil, errors.New("Received nil response") } defer response.Body.Close() if response.StatusCode == http.StatusOK { bodyBytes, readErr := ioutil.ReadAll(response.Body) if readErr != nil { zap.S().Errorf("Failed to read full response body: %v", readErr) return nil, errors.New("Failed to read response content") } zap.S().Debugf("Received full response: %s", string(bodyBytes)) if response.ContentLength != -1 && int(response.ContentLength) != len(bodyBytes) { zap.S().Warnf("Content-Length mismatch: expected %d, got %d", response.ContentLength, len(bodyBytes)) } err = json.Unmarshal(bodyBytes, &responseModel) if err != nil { errorMessage := "Request response decode error" zap.S().Errorf("%v, Err: %v, Full Response: %s", errorMessage, err, string(bodyBytes)) return nil, errors.New(errorMessage) } return responseModel, nil } else if response.StatusCode == http.StatusNoContent { return nil, nil } else { bodyBytes, _ := ioutil.ReadAll(response.Body) errorMessage := "Unexpected response status" zap.S().Errorf("%v, StatusCode %v, Response: %v Request: %v", errorMessage, response.StatusCode, string(bodyBytes), string(byteData)) return nil, errors.New(errorMessage) } }
按照以上步骤修改后,你应该能定位到问题根源:如果是服务器返回的JSON不完整,需要和服务器端排查;如果是客户端读取问题,超时设置和完整读取逻辑会解决。
内容的提问来源于stack exchange,提问作者Kartal

