Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] How can I set mutipartBody to receive Chinese filename in form part ? #1714

Closed
counter2015 opened this issue Dec 28, 2021 · 8 comments
Milestone

Comments

@counter2015
Copy link

counter2015 commented Dec 28, 2021

Tapir version: 0.19.3

Scala version: 2.13.7

Describe the Problem

In rfc6266

The parameters "filename" and "filename*" differ only in that
"filename*" uses the encoding defined in [RFC5987], allowing the use
of characters not present in the ISO-8859-1 character set
([ISO-8859-1]).

I wrote an endpoint to hanle file upload , like this

val uploadEndpoint =
    endpoint
      .post
      .in("file")
      .in(path[String]("id"))
      .in(multipartBody[FileForm])
      .out(stringBody)
      .tag("File")

  def uploadLogic(id: String, form: FileForm): ZIO[Logging, Nothing, String] = {
    for {
      _ <- Logging.info(s"got upload file request, id is $id")
    } yield form.file.fileName.getOrElse("file name not found")
  }

It works well in old version of tapir 0.17.12, but not work in 0.19.3

since softwaremill/sttp#877 add encoding in client side mutipart body, does tapir provide something like this in server side ? So I can use default UTF-8 Codec ?

How to reproduce?

$  sbt new counter2015/tapir-zio-http4s.g8
a template of tapir-zio-http4s project. 

name [tapir-exmple]: 

Template applied in /private/tmp/./tapir-exmple

$ cd tapir-exmple

$ sbt run

In another window, or in swagger page

$ curl -X 'POST' \
  'http://localhost:8080/file/1' \
  -H 'accept: text/plain' \
  -H 'Content-Type: multipart/form-data' \
  -F 'uploader=me' \
  -F 'file=@中文文件名.json;type=application/json'

image

response:
Bad Request
Invalid value for: body

Additional information

0.17.12 version can be found here, it works well
https://github.com/counter2015/tapir-zio-http4s.g8/tree/0.17/src/main/g8

Raw log from postman

POST /file/2222 HTTP/1.1
accept: text/plain
Content-Type: multipart/form-data
User-Agent: PostmanRuntime/7.26.5
Postman-Token: cf00388a-a15a-4463-98ba-a48eea00de14
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 416
----------------------------postman
Content-Disposition: form-data; name="uploader"

counter
----------------------------postman
Content-Disposition: form-data; name="file"; filename="中文文件名.json"; filename*="UTF-8''%E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6%E5%90%8D.json"

<中文文件名.json>
----------------------------postman--
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=UTF-8
Date: Tue, 28 Dec 2021 13:22:30 GMT
Content-Length: 23
Invalid value for: body
@adamw
Copy link
Member

adamw commented Jan 3, 2022

Hm I think it's not about encoding of the body (the linked sttp issue sets the body encoding, as far as I know), but rather about using filename/filename* appropriately as you write.

Which interpreter are you using, http4s? I wonder what changed since 0.17 🤔

@counter2015
Copy link
Author

counter2015 commented Jan 3, 2022

yes, http4s. the full depenience can be found here

0.17 version

import sbt._

object Dependencies {

  val tapirVersion = "0.17.12"

  val tapir = Seq(
    "com.softwaremill.sttp.tapir" %% "tapir-zio"                % tapirVersion,
    "com.softwaremill.sttp.tapir" %% "tapir-zio-http4s-server"  % tapirVersion,
    "com.softwaremill.sttp.tapir" %% "tapir-json-circe"         % tapirVersion,
    "com.softwaremill.sttp.tapir" %% "tapir-openapi-docs"       % tapirVersion,
    "com.softwaremill.sttp.tapir" %% "tapir-openapi-circe-yaml" % tapirVersion,
    "com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-http4s"  % tapirVersion,
  )

  val zioLoggingVersion = "0.5.14"
  val log =  Seq(
    "ch.qos.logback" % "logback-classic" % "1.2.10",
    "dev.zio" %% "zio-logging" % zioLoggingVersion,
    "dev.zio" %% "zio-logging-slf4j" % zioLoggingVersion,
  )

  val coreDependency = tapir ++ log
}

0.19 version

import sbt._

object Dependencies {

  val tapirVersion = "0.19.3"

  val tapir = Seq(
    "com.softwaremill.sttp.tapir" %% "tapir-zio"                % tapirVersion,
    "com.softwaremill.sttp.tapir" %% "tapir-zio-http4s-server"  % tapirVersion,
    "com.softwaremill.sttp.tapir" %% "tapir-json-circe"         % tapirVersion,
    "com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle"  % tapirVersion,
  )

  val zioLoggingVersion = "0.5.14"
  val log =  Seq(
    "ch.qos.logback" % "logback-classic" % "1.2.10",
    "dev.zio" %% "zio-logging" % zioLoggingVersion,
    "dev.zio" %% "zio-logging-slf4j" % zioLoggingVersion,
  )

  val coreDependency = tapir ++ log
}

@counter2015 counter2015 changed the title [Question] How can I set mutipartBody use UTF-8 encoder to receive Chinese filename in form part ? [Question] How can I set mutipartBody to receive Chinese filename in form part ? Jan 3, 2022
@adamw
Copy link
Member

adamw commented Jan 4, 2022

I think this is a problem with http4s, see: http4s/http4s#5809

@adamw
Copy link
Member

adamw commented Jan 4, 2022

For future testing, here's my test code reproducing the problem:

import cats.effect._
import org.http4s.HttpRoutes
import org.http4s.server.Router
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.syntax.kleisli._
import sttp.model.Part
import sttp.tapir._
import sttp.tapir.server.http4s.Http4sServerInterpreter

import java.io.File
import scala.concurrent.ExecutionContext
import scala.io.Source
import sttp.tapir.generic.auto._

/*
curl -X 'POST' 'http://localhost:8080/file/1' -H 'accept: text/plain' -H 'Content-Type: multipart/form-data' -F 'uploader=me' -F 'file=@中文文件名.json;type=application/json'
 */

object Test extends IOApp {

  case class FileForm(file: Part[File])

  val uploadEndpoint =
    endpoint.post
      .in("file")
      .in(path[String]("id"))
      .in(multipartBody[FileForm])
      .tag("File")

  def uploadLogic(id: String, form: FileForm): IO[Unit] = IO {
    println(s"\n\n\nGot: $id ${form.file}\n${Source.fromFile(form.file.body).mkString("\n")}")
  }

  val uploadRoutes: HttpRoutes[IO] =
    Http4sServerInterpreter[IO]().toRoutes(uploadEndpoint.serverLogicSuccess { case (id, form) => uploadLogic(id, form) })

  implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global

  override def run(args: List[String]): IO[ExitCode] = {
    BlazeServerBuilder[IO]
      .withExecutionContext(ec)
      .bindHttp(8080, "localhost")
      .withHttpApp(Router("/" -> uploadRoutes).orNotFound)
      .resource
      .use { _ => IO.never }
      .as(ExitCode.Success)
  }
}

@counter2015
Copy link
Author

Thanks!

@adamw adamw added this to the Blocked milestone Mar 16, 2022
@weili96
Copy link

weili96 commented Jan 5, 2024

so , this question can be solve?

@adamw
Copy link
Member

adamw commented Jan 5, 2024

@weili96 well, we don't really have a solution to this problem. So I'd keep this open.

@counter2015
Copy link
Author

close via http4s/http4s#5809

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants