在Gin框架中处理异步请求时,需要特别注意请求体的生命周期。你遇到的错误是因为goroutine尝试读取已经关闭的请求体。以下是正确的实现方式:
package main
import (
"bytes"
"io"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.POST("/async", func(c *gin.Context) {
// 1. 在goroutine启动前读取并复制请求体
bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read request body"})
return
}
// 恢复原始请求体(可选,但推荐)
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// 2. 立即响应客户端
c.JSON(http.StatusAccepted, gin.H{
"message": "Request accepted for processing",
"task_id": "12345",
})
// 3. 启动异步处理goroutine
go func(body []byte) {
// 使用复制的请求体数据
processRequest(body)
}(bodyBytes)
})
r.Run(":8080")
}
func processRequest(body []byte) {
// 模拟耗时处理
time.Sleep(5 * time.Second)
// 处理请求体数据
// 例如:解析JSON、数据库操作等
// fmt.Printf("Processing request body: %s\n", string(body))
}
更完整的示例,包含请求上下文传递和错误处理:
package main
import (
"bytes"
"context"
"encoding/json"
"io"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
type AsyncRequest struct {
Data string `json:"data"`
}
func main() {
r := gin.Default()
r.POST("/async-process", asyncHandler)
r.Run(":8080")
}
func asyncHandler(c *gin.Context) {
// 复制请求上下文(去除取消功能)
ctx := context.Background()
// 读取并复制请求体
var buf bytes.Buffer
tee := io.TeeReader(c.Request.Body, &buf)
// 先解析请求验证格式
var req AsyncRequest
if err := json.NewDecoder(tee).Decode(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request format"})
return
}
// 恢复请求体供后续使用
c.Request.Body = io.NopCloser(&buf)
// 立即响应
c.JSON(http.StatusAccepted, gin.H{
"status": "processing",
"message": "Your request is being processed asynchronously",
})
// 异步处理
go processAsync(ctx, req)
}
func processAsync(ctx context.Context, req AsyncRequest) {
// 模拟异步处理
time.Sleep(2 * time.Second)
// 这里可以执行数据库操作、调用其他服务等
log.Printf("Processing async request with data: %s", req.Data)
// 处理完成后可以:
// 1. 更新数据库状态
// 2. 发送Webhook通知
// 3. 写入消息队列等
}
如果需要跟踪异步任务状态,可以结合任务队列:
package main
import (
"crypto/rand"
"encoding/hex"
"io"
"net/http"
"sync"
"github.com/gin-gonic/gin"
)
type TaskManager struct {
tasks sync.Map
}
func main() {
tm := &TaskManager{}
r := gin.Default()
r.POST("/task", func(c *gin.Context) {
taskID := generateTaskID()
// 读取请求体
body, _ := io.ReadAll(c.Request.Body)
// 存储任务
tm.tasks.Store(taskID, "pending")
// 立即响应
c.JSON(http.StatusAccepted, gin.H{
"task_id": taskID,
"status": "accepted",
})
// 异步处理
go func(id string, data []byte) {
// 处理任务
processTask(data)
// 更新状态
tm.tasks.Store(id, "completed")
}(taskID, body)
})
r.GET("/task/:id", func(c *gin.Context) {
taskID := c.Param("id")
if status, ok := tm.tasks.Load(taskID); ok {
c.JSON(http.StatusOK, gin.H{
"task_id": taskID,
"status": status,
})
} else {
c.JSON(http.StatusNotFound, gin.H{"error": "task not found"})
}
})
r.Run(":8080")
}
func generateTaskID() string {
b := make([]byte, 8)
rand.Read(b)
return hex.EncodeToString(b)
}
func processTask(data []byte) {
// 异步任务处理逻辑
}
关键点:
- 在启动goroutine之前完整读取请求体
- 使用
io.NopCloser恢复请求体(如果需要)
- 立即返回202 Accepted状态码
- 避免在goroutine中引用原始请求对象
- 考虑使用context.Background()避免上下文取消