You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang HTTP响应反序列化报错:unexpected end of JSON input 问题排查与解决

解决Golang HTTP客户端JSON反序列化"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内容不完整,可能的原因有两个:

  1. 服务器返回的JSON本身存在截断(比如网络中断、服务器内部错误导致未发送完整响应);
  2. 客户端流式读取响应体时发生中断(比如未设置超时,导致读取过程卡住或仅读取部分内容)。

解决方案:先读取完整响应体再反序列化

替换原来的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 10:13:11