gin中文件上传需先解析multipart/form-data,再序列化为protobuf bytes字段;必须限制文件大小与类型、增大grpc消息上限、使用带超时的context并复用连接。

不能直接“转发”文件——gRPC 不支持 HTTP multipart/form-data 的原始二进制流透传,必须在 Gin 中先解析上传的文件,再按 protobuf 定义的 message 结构序列化为 bytes 字段,通过 gRPC client 发送给后端服务。
gin.FormFile 读取文件后必须显式限制大小和类型
HTTP 文件上传走的是 multipart/form-data,Gin 的 c.FormFile("img") 只是拿到文件元信息(如 FilenName、Size),真正读取内容需调用 file.Open() + ioutil.ReadAll()。不加限制容易被恶意大文件打爆内存:
-
c.Request.MultipartForm.MaxMemory默认是 32MB,但建议在路由前用中间件设为更小值(如 10MB) - 检查
file.Header.Get("Content-Type"),拒绝application/x-executable等危险 MIME - 用
file.Size > 10*1024*1024提前返回413 Payload Too Large
protobuf message 的 bytes 字段要能承载完整文件内容
你定义的 PhotoRuquest 中 bytes databytes = 2 看似简单,但实际有隐含约束:
- gRPC 默认最大接收消息为 4MB(
grpc.MaxCallRecvMsgSize),超限会报rpc error: code = ResourceExhausted desc = grpc: received message larger than max... - 必须在
grpc.Dial()时显式增大限制:grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(20*1024*1024), grpc.MaxCallRecvMsgSize(20*1024*1024)) - Protobuf 编码会使原始二进制膨胀约 33%(Base64 不参与,这是 proto binary wire format 自身开销),20MB 原图最终可能接近 26MB 传输量
sendImg 调用必须带 context 并处理 timeout 和 cancel
文件上传是长耗时操作,gRPC 调用不能用 context.Background():
- 应从 Gin
c.Request.Context()派生子 context:ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - 务必在 defer 中调用
cancel(),否则可能泄漏 goroutine - 捕获
context.DeadlineExceeded错误并转为504 Gateway Timeout返回给前端 - 不要用
log.Fatal()—— 它会 kill 整个进程,应改用c.AbortWithStatusJSON(http.StatusInternalServerError, ...)
实际调用链中 conn 复用与连接池是性能关键点
每次 grpc.Dial() 建连开销大,且默认不复用底层 TCP 连接:
- 把
*grpc.ClientConn提升为全局变量或依赖注入,避免每次请求都重连 - 启用连接池需手动配置:
grpc.WithTransportCredentials(insecure.NewCredentials())(开发)或grpc.WithTransportCredentials(credentials.NewTLS(...))(生产) - 若并发高,考虑用
sync.Pool管理pb.PhotoerClient实例,但注意 client 本身是线程安全的,通常只需复用 conn
最易被忽略的是:gRPC 服务端收到的 bytes 是原始二进制,不是 base64 字符串;而前端通过 curl 上传时,-F 'img=@xxx.jpg' 发送的是 raw bytes,Gin 解析后仍为 raw bytes,二者格式一致——只要 protobuf 字段类型和 gRPC 传输配置对齐,就不会出现“图片损坏”问题。错就错在没调大 recv msg size 或没做 context 超时控制。











