For Process Street’s Yammer integration, I needed to post file attachments to their API using the Play Scala WS library.

However, it turns out that WS does not yet support uploading files using multipart/form-data and POST. There are several workarounds on the ‘net, but they all involve getting the underlying Ning Java client and doing thing manually.

Why not harness the power of Play and Scala and write our own Writeable to do that job? Heck, we already have a MultipartFormData object for handling multipart form requests, why not use that?

Behold, the fruit of my labour:

package utilities

import java.io.{ByteArrayOutputStream, File}

import com.ning.http.client.FluentCaseInsensitiveStringsMap
import com.ning.http.multipart.{MultipartRequestEntity, FilePart, StringPart}
import play.api.http.HeaderNames._
import play.api.http.{ContentTypeOf, Writeable}
import play.api.mvc.{Codec, MultipartFormData}

object MultipartFormDataWriteable {

    implicit def contentTypeOf_MultipartFormData[A](implicit codec: Codec): ContentTypeOf[MultipartFormData[A]] = {
        ContentTypeOf[MultipartFormData[A]](Some("multipart/form-data; boundary=__X_PROCESS_STREET_BOUNDARY__"))
    }

    implicit def writeableOf_MultipartFormData(implicit contentType: ContentTypeOf[MultipartFormData[File]]): Writeable[MultipartFormData[File]] = {
        Writeable[MultipartFormData[File]]((formData: MultipartFormData[File]) => {

            val stringParts = formData.dataParts flatMap {
                case (key, values) => values map (new StringPart(key, _))
            }

            val fileParts = formData.files map { filePart =>
                new FilePart(filePart.key, filePart.ref, filePart.contentType getOrElse "application/octet-stream", null)
            }

            val parts = stringParts ++ fileParts

            val headers = new FluentCaseInsensitiveStringsMap().add(CONTENT_TYPE, contentType.mimeType.get)
            val entity = new MultipartRequestEntity(parts.toArray, headers)
            val outputStream = new ByteArrayOutputStream
            entity.writeRequest(outputStream)

            outputStream.toByteArray

        })(contentType)
    }

}

Now, how do you use it? Like so:

import play.api.libs.ws.WS
import play.api.mvc.MultipartFormData.FilePart
import play.api.mvc.MultipartFormData
import utilities.MultipartFormDataWriteable._

...

val url = "https://example.com"

val dataParts = Map(
    "foo" -> Seq("bar"),
    "alice" -> Seq("bob")
)

val file = new jave.io.File(... path to a jpg ...)
val fileParts = Seq(new FilePart("attachment", "foo.jpg", Some("image/jpeg"), file)

val multipartFormData = MultipartFormData(dataParts, fileParts, Seq(), Seq())

WS.url(url).post(multipartFormData)