Golang服务器转发表单上传文件至Flask服务器时request.files为空的问题及优化方案
问题背景
我正在开发一个文件上传转发应用:页面表单将文件上传至Golang服务器,再由Golang服务器转发给Flask服务器处理。但遇到了一个棘手的问题:Golang能正常接收文件,可Flask端始终无法获取到文件,request.files一直为空。
初始代码
Golang服务器代码
func api_upload_model(w http.ResponseWriter, r *http.Request) { r.ParseMultipartForm(50 << 20) file, handler, err := r.FormFile("Model") if err != nil { fmt.Println("Error Retrieving the File") fmt.Println(err) return } fmt.Printf("Uploaded File: %+v\n", handler.Filename) fmt.Printf("File Size: %+v\n", handler.Size) resp, err := http.Post(*URL here*, "multipart/form-data", file) if err != nil { fmt.Println("Error Sending the File") fmt.Println(err) return } w.Header().Set("Content-Type", "text/html") buffer, _ := ioutil.ReadAll(resp.Body) _, err = w.Write(buffer) if err != nil { fmt.Println("Error Sending the File") fmt.Println(err) return } } func main() { router := chi.NewRouter() router.Post("/api/upload_model", api_upload_model) http.ListenAndServe(":8080", router) }
Flask处理代码
@app.route('/upload_model', methods=['POST']) def upload_model(): log("Inference server", "123") name = request.args.get('name', None) if name is None: return "Error: bad request parameters." print(str(request)) if 'file' not in request.files: # 我知道未将文件名设为'file',但request.files始终为空 return "No file" file = request.files['file'] if file: filename = secure_filename(name) file.save(os.path.join("data/models", name)) return "200"
问题现象
Golang控制台能正常输出文件信息:Uploaded File: 1.jpg和File Size: 114940,说明文件接收环节没有问题。但Flask端打印的请求为<Request 'http://*URL here*/upload_model?name=1.jpg' [POST]>,且request.files始终为空,完全无法获取到上传的文件内容。
问题根源
你最初的Golang代码犯了一个典型错误:当用r.FormFile("Model")取出单个文件流后,直接将这个流作为http.Post的请求体发送给Flask。但multipart/form-data类型的请求并非单纯的文件内容,它需要包含边界分隔符、字段名、文件名等元数据信息。仅传递文件内容的话,Flask无法识别这是一个合法的文件上传请求,自然解析不出request.files中的内容。
正确解决方案
最简洁高效的办法是不解析原请求的multipart内容,直接将整个请求原封不动转发给Flask,这样能完整保留原始请求的multipart/form-data结构,让Flask正常解析文件。修改后的Golang代码如下:
func api_upload_model(w http.ResponseWriter, r *http.Request) { // 基于原始请求创建转发请求,不解析multipart表单 proxyReq, err := http.NewRequest(r.Method, *URL here*, r.Body) if err != nil { fmt.Println("创建转发请求失败:", err) http.Error(w, "服务器内部错误", http.StatusInternalServerError) return } // 复制原始请求的所有请求头,尤其是Content-Type(包含multipart边界) proxyReq.Header = make(http.Header) for key, values := range r.Header { proxyReq.Header[key] = append([]string{}, values...) } // 发送请求到Flask服务器 client := &http.Client{} resp, err := client.Do(proxyReq) if err != nil { fmt.Println("转发请求到Flask失败:", err) http.Error(w, "转发请求失败", http.StatusBadGateway) return } defer resp.Body.Close() // 将Flask的响应原样返回给客户端 w.Header().Set("Content-Type", resp.Header.Get("Content-Type")) w.WriteHeader(resp.StatusCode) _, err = io.Copy(w, resp.Body) if err != nil { fmt.Println("返回响应失败:", err) } } func main() { router := chi.NewRouter() router.Post("/api/upload_model", api_upload_model) http.ListenAndServe(":8080", router) }
额外提示
这种转发方式不仅解决了文件解析问题,还避免了多余的multipart解析和重新构建工作,效率更高。另外需要注意:
- 务必复制原始请求的所有头部,尤其是
Content-Type,它包含的multipart边界是Flask正确识别文件的关键。 - 如果需要给Flask请求添加额外参数(比如代码中的
name),可以在创建proxyReq时修改URL的查询参数部分。
内容的提问来源于stack exchange,提问作者Dmitry

