Golang中API返回无效JSON响应的问题如何解决

Golang中API返回无效JSON响应的问题如何解决 我有一个API方法 router.GET("/data/UploadStatus", func(c *gin.Context)。这个方法会调用数据库获取值并返回JSON响应。在绝大多数情况下它都能正常工作,但在极少数场景下,我会收到无效的JSON响应,因为响应中附加了 “true” 值。

c.JSON(http.StatusOK, response)
5 回复

嗨,迈克尔,如果有任何更新请告诉我…

更多关于Golang中API返回无效JSON响应的问题如何解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你能贴出一些你的代码吗?

从你的代码片段来看,逻辑似乎不正确,你可以这样修改:

if response, err := Data.GetUploadStatus(dbClient, c.Query("byGuid"), c.Query("token")); err == nil {
		
		if err == nil && len(response.StatusEntry) > 0 {
			entry := response.StatusEntry[0]
			if strings.EqualFold(entry.State, "Complete") {
				LogUploadCompleteStatus(c.Query("account"), c.Query("byGuid"), "Succeeded", entry.StartTime, entry.CompletionTime)
			}
			if strings.EqualFold(entry.State, "Error") {
				LogUploadCompleteStatus(c.Query("account"), c.Query("byGuid"), "Failed", entry.StartTime, entry.CompletionTime)
			}
		} else {
		c.JSON(http.StatusOK, response)
               }
	} else {
		logger.Debug(c.Query("byGuid"), "Failed to perform uploadStatus"+err.Error())
		c.JSON(http.StatusInternalServerError, err)
	}

好的,GetUploadStatus 会调用存储过程并获取数据库中的值。JSON响应通常符合预期,但在极少数情况下(尤其是在并发调用该函数时),我会收到无效的JSON响应,因为响应中附加了“true”值。

if response, err := Data.GetUploadStatus(dbClient, c.Query("byGuid"), c.Query("token")); err == nil {
					c.JSON(http.StatusOK, response)
					if err == nil && len(response.StatusEntry) > 0 {
						entry := response.StatusEntry[0]
						if strings.EqualFold(entry.State, "Complete") {
							LogUploadCompleteStatus(c.Query("account"), c.Query("byGuid"), "Succeeded", entry.StartTime, entry.CompletionTime)
						}
						if strings.EqualFold(entry.State, "Error") {
							LogUploadCompleteStatus(c.Query("account"), c.Query("byGuid"), "Failed", entry.StartTime, entry.CompletionTime)
						}
					}
					if err != nil {
						logger.Debug(c.Query("byGuid"), "Failed to perform uploadStatus"+err.Error())
						c.JSON(http.StatusInternalServerError, err)
					}
				} else {
					logger.Debug(c.Query("byGuid"), "Failed to perform uploadStatus"+err.Error())
					c.JSON(http.StatusInternalServerError, err)
				}

在Golang中,API返回无效JSON响应通常是由于在调用c.JSON()之前或之后意外写入了其他内容。根据你的描述,响应中附加了"true"值,这很可能是由于在同一个请求处理流程中多次调用了写入响应的方法。

以下是可能的原因和解决方案:

常见原因

  1. c.JSON()之前调用了其他写入方法(如c.String()c.JSON()等)
  2. 中间件或拦截器意外写入了响应
  3. 并发写入响应

解决方案

1. 确保单一响应写入

确保在请求处理中只调用一次响应写入方法:

router.GET("/data/UploadStatus", func(c *gin.Context) {
    // 获取数据
    data, err := getDataFromDB()
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return // 确保返回,避免继续执行
    }
    
    // 构建响应
    response := gin.H{
        "status": "success",
        "data":   data,
    }
    
    // 只调用一次c.JSON
    c.JSON(http.StatusOK, response)
    // 不要在此后调用任何c.Writer.Write()相关方法
})

2. 检查中间件

检查是否有中间件在c.JSON()之后修改了响应:

func CheckMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        
        // 避免在c.Next()后写入响应
        // 下面的代码会导致问题:
        // c.Writer.Write([]byte("true"))
        
        // 正确的做法是只修改header或记录日志
        c.Writer.Header().Set("X-Custom-Header", "value")
    }
}

3. 使用响应包装器确保单一写入

创建一个包装函数来确保响应只被写入一次:

type ResponseWriter struct {
    gin.ResponseWriter
    written bool
}

func (w *ResponseWriter) Write(data []byte) (int, error) {
    if w.written {
        return 0, nil // 忽略后续写入
    }
    w.written = true
    return w.ResponseWriter.Write(data)
}

func (w *ResponseWriter) WriteHeader(statusCode int) {
    if !w.written {
        w.ResponseWriter.WriteHeader(statusCode)
    }
}

func EnsureSingleWrite() gin.HandlerFunc {
    return func(c *gin.Context) {
        writer := &ResponseWriter{ResponseWriter: c.Writer, written: false}
        c.Writer = writer
        c.Next()
    }
}

// 使用
router.Use(EnsureSingleWrite())
router.GET("/data/UploadStatus", func(c *gin.Context) {
    // 你的处理逻辑
    c.JSON(http.StatusOK, response)
})

4. 检查数据库驱动或库的副作用

某些数据库驱动可能在后台写入日志或其他内容:

func getDataFromDB() (interface{}, error) {
    // 确保数据库操作不会写入响应writer
    // 避免这样的代码:
    // fmt.Fprintf(c.Writer, "true")
    
    // 正确的做法
    var result []YourStruct
    err := db.Find(&result).Error
    return result, err
}

5. 添加调试日志

添加日志来追踪响应写入的位置:

func DebugMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path
        
        c.Next()
        
        latency := time.Since(start)
        fmt.Printf("Path: %s | Status: %d | Latency: %v | Size: %d\n", 
            path, c.Writer.Status(), latency, c.Writer.Size())
    }
}

router.Use(DebugMiddleware())

6. 完整的修复示例

router.GET("/data/UploadStatus", func(c *gin.Context) {
    // 使用defer和recover处理panic
    defer func() {
        if r := recover(); r != nil {
            c.JSON(http.StatusInternalServerError, gin.H{
                "error": "internal server error",
            })
        }
    }()
    
    // 获取查询参数
    query := c.Query("id")
    
    // 数据库操作
    var statusData UploadStatus
    err := db.Where("id = ?", query).First(&statusData).Error
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{
            "error": "record not found",
        })
        return // 关键:确保返回
    }
    
    // 构建响应
    response := gin.H{
        "id":     statusData.ID,
        "status": statusData.Status,
        "time":   statusData.CreatedAt,
    }
    
    // 只调用一次JSON写入
    c.JSON(http.StatusOK, response)
})

检查你的代码中是否有以下模式:

  • 多个c.JSON()调用
  • c.Writer.Write()的直接调用
  • 在goroutine中异步写入响应
  • 第三方库或中间件修改响应体

使用c.Writer.Size()方法可以检查响应是否已被写入,这有助于调试:

if c.Writer.Size() > 0 {
    // 响应已被写入,不要再次写入
    return
}
回到顶部