Karol Szustakowski
Karol Szustakowski

Reputation: 149

Play Framework - build a TemporaryFile from a file stored in RAM

I am using Scala Play Framework to send images to the client in a multipart form data as a response to a get request.

I have the images stored in the BufferedImage format. Currently, I send the images back to the client using this getResult method:

@Singleton
class ResultController @Inject()(val controllerComponents: ControllerComponents) extends BaseController with DefaultWriteables{

    class ResultForm(seq: Seq[BufferedImage]){
        val (leftRotation, rightRotation, backRotation) = seq.zip(
            Seq("left", "right", "back")
        ).map{ case (bufferedImage: BufferedImage, partName: String) =>
            val bos = new ByteArrayOutputStream()
            ImageIO.write(bufferedImage, "png", bos)
            MultipartFormData.FilePart(
                partName,
                s"${partName}_result.png",
                Some("image/png"),
                ByteString.fromArray(bos.toByteArray())
            ) 
        } match {
            case Seq(l, r, b) => (l, r, b)
        }

        def getResponse() = {
            MultipartFormData(
                dataParts = Map[String, Seq[String]](), 
                files = Seq(leftRotation, rightRotation, backRotation),
                badParts =Seq()
            )
        }
    }

    implicit def writeableOf_MultipartFormDataWithBs(
        codec: Codec,
        contentType: Option[String]
    ):play.api.http.Writeable[MultipartFormData[akka.util.ByteString]] = {
        writeableOf_MultipartFormData(
            codec,
            Writeable[MultipartFormData.FilePart[akka.util.ByteString]](
                (bs: MultipartFormData.FilePart[akka.util.ByteString]) => bs.ref,
                contentType
            )
        )
    }


    def getResult(accessKey: String) = Action{request => 
        val jobResult = GeneratorData.getJobResult(accessKey)


        jobResult match {
            case Some(files) => {
                val rf = new ResultForm(files)
                Ok(
                    rf.getResponse()
                )(
                    writeableOf_MultipartFormDataWithBs(
                        Codec.iso_8859_1,
                        contentType = Some("multipart/form-data")
                    )
                )
            }
            case None => BadRequest(Json.toJson(
                new ErrorResponse("The result was not found on the server")
            ))
        }
    }    
}

However, in client-side javascript, Response.formData() only works in chrome and opera, but firefox throws a TypeError: Could not parse content as FormData. exception. This is possibly due to some wild conversions of the image formats that are going on, and the definition of a custom writer.

The whole thing would be way easier, if instead of an akka ByteString I just passed a TemporaryFile for which the writer is well-defined by the framework, but I can't seem to find a way to create a TemporaryFile, documentation doesn't list any methods like "create". There is a TemporaryFileCreator trait, but none of it's subclasses seem to have methods that convert from any type of in-memory file to a TemporaryFile. They do, however, have methods to specify a path to the file, but I need to serve the files using RAM only.

So, is there a way to create a TemporaryFile from an in-memory file data? Or maybe a way to send three images as multipart so that firefox, not just opera and chromium can parse the result? Base64 is also a solution, but I would prefer to do this the "right" way.

Upvotes: 0

Views: 242

Answers (1)

Karol Szustakowski
Karol Szustakowski

Reputation: 149

So I have implemented a simple example in python and it turns out this is a Firefox bug. You can trace the bug report here: https://bugzilla.mozilla.org/show_bug.cgi?id=1752761

Upvotes: 0

Related Questions