Search code examples
scalarestakkaakka-http

How can i handle multipart post request with akka http?


I wont to handle multipart request. If I accept a request using such a route

   val routePutData = path("api" / "putFile" / Segment) {
      subDir => {
        entity(as[String]) { (str) => {
          complete(str)
        }
      }
    }}

I get the following text(i try to send log4j config):

Content-Disposition: form-data; name="file"; filename="log4j.properties"
Content-Type: application/binary

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd hh:mm:ss} %t %-5p %c{1} - %m%n
----gc0pMUlT1B0uNdArYc0p--

How can i get array of bytes from file i send and file name? I try to use entity(as[Multipart.FormData]), and formFields directive, but it didn't help.


Solution

  • You should keep up with the akka docs, but I think that there were not enought examples in the file uploading section. Anyway, you don't need to extract entity as a string or byte arrays, akka already has a directive, called fileUpload. This takes a parameter called fieldName which is the key to look for in the multipart request, and expects a function to know what to do given the metadata and the content of the file. Something like this:

    post {
      extractRequestContext { ctx =>
        implicit val mat = ctx.materializer
    
        fileUpload(fieldName = "myfile") {
          case (metadata, byteSource) =>
    
            val fileName = metadata.fileName
            val futureBytes = byteSource
              .mapConcat[Byte] { byteString =>
                collection.immutable.Iterable.from(
                  byteString.iterator
                )
              }
              .toMat(Sink.fold(Array.emptyByteArray) {
                case (arr, newLine) => arr :+ newLine
              }
              )(Keep.right)
              .run()
    
            val filePath = Files.createFile(Paths.get(s"/DIR/TO/SAVE/FILE/$fileName"))
            onSuccess(futureBytes.map(bytes => Files.write(filePath, bytes))) { _ =>
              complete(s"wrote file to: ${filePath.toUri.toString}")
            }
    
        }
      }
    }