Search code examples
scalafacebook-graph-apiakkaakka-http

Akka HTTP: ByteString as a file payload in a form-data request


In one of my previous questions, I asked how to represent a form-data request using Akka HTTP? According to answers I created a working sample, but faced a "scaleability" issue - when the number of form-data requests is high, I need to handle a lot of files in the file system.

I'm curious, is it possible to send ByteString as a file payload in a form-data request?

case class FBSingleChunkUpload(accessToken: String, 
        sessionId: String,
        from: Long, 
        to: Long, 
        file: ByteString) //this property is received from S3 as array of bytes

I created a following sample:

def defaultEntity(content: String) =
  HttpEntity.Default(
    ContentTypes.`text/plain(UTF-8)`,
    content.length, Source(ByteString(content) :: Nil)
  )

def chunkEntity(chunk: ByteString) =
  HttpEntity.Strict(
    ContentType(MediaTypes.`application/octet-stream`),
    chunk
  )

val formData = Multipart.FormData(
  Source(
    Multipart.FormData.BodyPart("access_token", defaultEntity(upload.fbUploadSession.fbId.accessToken)) ::
    Multipart.FormData.BodyPart("upload_phase", defaultEntity("transfer")) ::
    Multipart.FormData.BodyPart("start_offset", defaultEntity(upload.fbUploadSession.from.toString)) ::
    Multipart.FormData.BodyPart("upload_session_id", defaultEntity(upload.fbUploadSession.uploadSessionId)) ::
    Multipart.FormData.BodyPart("video_file_chunk", chunkEntity(upload.chunk)) :: Nil
  )
)
val req = HttpRequest(
  HttpMethods.POST,
  s"/v2.3/${upload.fbUploadSession.fbId.pageId}/videos",
  Nil,
  formData.toEntity()
)

In this case Facebook sends me back a message:

Your video upload timed out before it could be completed. This is probably because of a slow network connection or because the video you're trying to upload is too large

But if I send the same ByteString as a File it works fine.

What could be the reason of this? I already tried to use MediaTypes.multipart/form-data in chunkEntity but it behaves in the same way.


Solution

  • In order to send a ByteString as a form-data file you need to use a following BodyPart:

    def fileEntity(chunk: ByteString) = Multipart.FormData.BodyPart.Strict("video_file_chunk",
        HttpEntity(ContentType(MediaTypes.`application/octet-stream`), chunk), Map("fileName" -> "video_chunk"))
    

    Pay extra attention to Map("fileName" -> "video_chunk") this parameters is mandatory in order to construct a form-data HTTP request correctly.

    So instead of chunkEntity from the question, use fileEntity from this answer :)