使用Gin框架向多服务器发文件:仅首请求成功问题排查与修复
问题:多次POST传文件仅首次成功,后续返回200但文件为空
我尝试通过POST请求向不同服务器发送文件,共发起3次请求:1次发往http://192.168.42.129:8090/receive,另外2次发往http://localhost:8090/receive。但只有第一个请求成功,其余请求返回200状态码,但接收端拿到的文件是空的。附上相关代码,求修正。
接收文件的Gin处理函数
func ReceivePublicKey() *gin.Engine { router := gin.Default() router.MaxMultipartMemory = 8 << 20 //router.Static("/", "./static") router.POST("/receive", func(context *gin.Context) { file, _ := context.FormFile("file") log.Println(file.Filename) dst := "./" + file.Filename context.SaveUploadedFile(file, dst) context.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) context.String(200, "server: receive publicKey successfully!") }) return router }
创建和发送请求的函数
func CreateSendFileReq(file *os.File, fileName string, url string) *http.Request { bodyBuf := &bytes.Buffer{} bodyWrite := multipart.NewWriter(bodyBuf) fileWrite, err := bodyWrite.CreateFormFile("file", fileName) _, err = io.Copy(fileWrite, file) if err != nil { log.Println("err") } bodyWrite.Close() if err != nil { log.Println("err") } req, _ := http.NewRequest("POST", url, bodyBuf) req.Header.Set("Content-Type", bodyWrite.FormDataContentType()) return req } func SendRequest(r *http.Request) *http.Response { client := &http.Client{} resp, err := client.Do(r) if err == nil { return resp } else { log.Fatal(err) return nil } }
出现问题的初始化函数
func IotInit() { privateFile, _ := os.Open("private.pem") publicFile, _ := os.Open("public.pem") userId := GenerateIotId(publicFile) fmt.Println(userId) sendPrivateToServer := Controller.CreateSendFileReq(privateFile, userId+".pem", "http://192.168.42.129:8090/receive") sendPublicToUser := Controller.CreateSendFileReq(publicFile, "public.pem", "http://localhost:8090/receive") resp := Controller.SendRequest(sendPublicToUser) if resp.StatusCode != 200 { log.Fatal(resp.StatusCode) } resp.Body.Close() sendPrivateToUser := Controller.CreateSendFileReq(privateFile, "private.pem", "http://localhost:8090/receive") resp = Controller.SendRequest(sendPrivateToServer) if resp.StatusCode != 200 { log.Fatal(resp.StatusCode) } resp.Body.Close() resp = Controller.SendRequest(sendPrivateToUser) if resp.StatusCode != 200 { log.Fatal(resp.StatusCode) } resp.Body.Close() publicFile.Close() privateFile.Close() }
问题原因及修复方案
核心问题
文件句柄在第一次io.Copy后,指针已经移动到文件末尾,后续再用同一个文件句柄调用io.Copy时,会从末尾开始读取,自然读不到内容,导致上传空文件。
另外代码还存在以下潜在问题:
- 忽略了
os.Open、http.NewRequest的错误,可能隐藏底层异常; ReceivePublicKey里连续调用两次context.String,不符合HTTP响应规范;CreateSendFileReq里bodyWrite.Close()的错误检查顺序颠倒,逻辑混乱。
具体修复步骤
- 重置文件指针位置:每次读取文件前,将指针重置到文件开头
- 修正错误处理逻辑:完善所有函数的错误捕获与处理
- 规范HTTP响应输出:确保接收端只返回一次响应
修复后的CreateSendFileReq
func CreateSendFileReq(file *os.File, fileName string, url string) (*http.Request, error) { // 重置文件指针到开头 if _, err := file.Seek(0, io.SeekStart); err != nil { return nil, err } bodyBuf := &bytes.Buffer{} bodyWrite := multipart.NewWriter(bodyBuf) fileWrite, err := bodyWrite.CreateFormFile("file", fileName) if err != nil { bodyWrite.Close() return nil, err } _, err = io.Copy(fileWrite, file) if err != nil { bodyWrite.Close() return nil, err } if err := bodyWrite.Close(); err != nil { return nil, err } req, err := http.NewRequest("POST", url, bodyBuf) if err != nil { return nil, err } req.Header.Set("Content-Type", bodyWrite.FormDataContentType()) return req, nil }
修复后的SendRequest
func SendRequest(r *http.Request) (*http.Response, error) { client := &http.Client{} return client.Do(r) }
修复后的IotInit
func IotInit() { privateFile, err := os.Open("private.pem") if err != nil { log.Fatal(err) } defer privateFile.Close() publicFile, err := os.Open("public.pem") if err != nil { log.Fatal(err) } defer publicFile.Close() userId := GenerateIotId(publicFile) fmt.Println(userId) // 发送public到localhost sendPublicToUser, err := Controller.CreateSendFileReq(publicFile, "public.pem", "http://localhost:8090/receive") if err != nil { log.Fatal(err) } resp, err := Controller.SendRequest(sendPublicToUser) if err != nil { log.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != 200 { log.Fatalf("request failed with status: %d", resp.StatusCode) } // 发送private到远程服务器 sendPrivateToServer, err := Controller.CreateSendFileReq(privateFile, userId+".pem", "http://192.168.42.129:8090/receive") if err != nil { log.Fatal(err) } resp, err = Controller.SendRequest(sendPrivateToServer) if err != nil { log.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != 200 { log.Fatalf("request failed with status: %d", resp.StatusCode) } // 发送private到localhost sendPrivateToUser, err := Controller.CreateSendFileReq(privateFile, "private.pem", "http://localhost:8090/receive") if err != nil { log.Fatal(err) } resp, err = Controller.SendRequest(sendPrivateToUser) if err != nil { log.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != 200 { log.Fatalf("request failed with status: %d", resp.StatusCode) } }
修复后的ReceivePublicKey
func ReceivePublicKey() *gin.Engine { router := gin.Default() router.MaxMultipartMemory = 8 << 20 router.POST("/receive", func(context *gin.Context) { file, err := context.FormFile("file") if err != nil { context.String(http.StatusBadRequest, "failed to get file: %v", err) return } log.Println(file.Filename) dst := "./" + file.Filename if err := context.SaveUploadedFile(file, dst); err != nil { context.String(http.StatusInternalServerError, "failed to save file: %v", err) return } context.String(http.StatusOK, fmt.Sprintf("'%s' uploaded! server: receive publicKey successfully!", file.Filename)) }) return router }
内容的提问来源于stack exchange,提问作者xzy-bit
相关产品推荐
相关产品推荐

