背景
有一個需求是這樣的,前端需要通過http請求的form-data上傳圖片文件,后端接收圖片后調用AI接口執行命令,由于命令執行時間較長,需要持續返回當前任務在全局任務列表中的位置,以便前端即時更新排隊信息。
思考
如果直接在gin的請求處理函數中開goroutine,并在goroutine中通過類似c.JSON的方法來返回響應,前端會無法收到響應。原因是當處理函數返回時,Gin 會自動關閉 HTTP 請求的響應,這意味著在處理函數返回后,后臺的協程無法再向響應中寫入數據,從而導致客戶端收不到信息。(不用goroutine也不行,因為持續返回響應涉及到使用for循環,會阻塞通道,阻塞主協程,仍然無法返回響應),使用gin配合websocket解決。
于是解決方案就是,使用gin的處理函數接收圖片后,把當前任務的id作為響應返回給前端。然后前端再用當前任務的id建立websocket請求,在websocket請求中,通常不會自動關閉,除非顯式地由客戶端或服務器關閉,所以就可以在ws的處理函數中開goroutine來持續返回響應。
func WsHandleConnect(s *melody.Session) {TaskId, _ := s.Get("task_id")// 將 TaskId 轉換為字符串并解析為整數taskIdStr, _ := TaskId.(string)taskId, err := strconv.Atoi(taskIdStr)if err != nil {sendJSONResponse(s, Res{Code: 4000,Msg: "Invalid Task ID",Data: nil,})s.Close()return}var task *model.Task// 遍歷 TaskQueue 查找對應的任務for _, t := range global.TaskQueue {if t.ID == taskId {task = tbreak}}if task == nil {sendJSONResponse(s, Res{Code: 4000,Msg: "Task ID not provided",Data: nil,})s.Close()return}logger.Log.Error("task:", task.ID)go func() {for {select {case result := <-task.Response:sendJSONResponse(s, Res{Code: global.SuccessCode,Msg: result,Data: nil,})s.Close()returndefault:time.Sleep(3 * time.Second)if len(global.TaskQueue) == 0 {sendJSONResponse(s, Res{Code: global.SuccessCode,Msg: "Task failed",Data: nil,})s.Close()return}position := service.FindTaskPosition(task.ID)sendJSONResponse(s, Res{Code: global.SuccessCode,Data: position,})}}}()
}