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

使用http4s客户端发送multipart/form-data时内容为空的问题

问题

我尝试使用http4s客户端发送以Array[Byte]作为文件负载的multipart表单请求,但文档未提及该功能。我编写了如下代码:

def main():IO[Unit] = {
        filename           = "example.txt"
        fieldName          = "payload"
        fileEntityBody     = fs2.Stream.emits(extract).covary[IO] //extract是字节数组


        multipart = Multipart[IO](
                      Vector(
                        Part.fileData(
                          fieldName,
                          "",
                          fileEntityBody,
                          Headers.of(Header("Content-Disposition", s"""form-data; name="$fieldName"; filename="$filename""""))
                        )
                      )
                    )

        makeRequest(parsedUri, headers, multipart, Method.PUT)

}
def makeRequest(
        parsedUri: Either[ParseFailure, Uri],
        headers: Headers,
        requestPayload: Multipart[IO],
        method: Method
    ): IO[Unit] = {
      for {
        uri     <- IO.fromEither(parsedUri)
        request <- IO(
                     Request[IO](method, uri)
                       .withHeaders(headers)
                       .withEntity(requestPayload)
                   )
        _       <- client.run(request).use(response_ => IO(responseHandler(response_)))

      } yield IO.unit
    }

查看http4s发送的原始请求时,发现如下内容:

Content-Type: multipart/form-data
content-length: 0
host: 10.10.10.10:8000
accept: */*
user-agent: AHC/2.

这表明http4s未正确处理multipart负载(content-length为0),请问问题出在哪?

问题分析与解决
  • Part.fileData参数误用:你调用Part.fileData时,第二个参数传入了空字符串,这个参数本身就是用来指定文件名的,而你又手动在Headers里重复设置Content-Disposition头。http4s的fileData方法会自动生成符合规范的Content-Disposition头,手动传入的自定义头会和自动生成的内容冲突,导致multipart部件无法被正确识别,最终出现空负载。
  • 流的构造需规范:虽然fs2.Stream.emits(extract).covary[IO]能生成流,但建议显式指定流的类型,或者用更稳妥的方式构造:
    val fileStream = fs2.Stream.fromIterator[IO](extract.iterator, chunkSize = 1024)
    
  • 修正后的代码示例:
    def main(): IO[Unit] = {
      val filename = "example.txt"
      val fieldName = "payload"
      val fileBytes = extract // Array[Byte]
      val fileStream = fs2.Stream.emits(fileBytes).covary[IO]
    
      val multipart = Multipart[IO](
        Vector(
          Part.fileData[IO](
            name = fieldName,
            filename = filename,
            content = fileStream,
            headers = Headers.empty // 无需手动设置Content-Disposition
          )
        )
      )
    
      makeRequest(parsedUri, headers, multipart, Method.PUT)
    }
    
  • 额外注意事项:
    • 不要手动设置Content-Type: multipart/form-data头,http4s在处理Multipart实体时会自动生成包含boundary的正确Content-Type,手动覆盖会导致服务器无法解析multipart内容。
    • 确保项目中引入了http4s的multipart相关依赖,比如http4s-core(版本需匹配),否则编码器可能无法正常工作。

内容的提问来源于stack exchange,提问作者Mukhayyo Tashpulatova

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 11:32:43