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

使用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()的错误检查顺序颠倒,逻辑混乱。

具体修复步骤

  1. 重置文件指针位置:每次读取文件前,将指针重置到文件开头
  2. 修正错误处理逻辑:完善所有函数的错误捕获与处理
  3. 规范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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 17:43:13