Streaming large files with play framework and third party API

Viewed 6076

I'm writing a play 2 application and I am struggling with a file streaming problem. I retrieve my files using a third party API with a method having the following signature:

FileMetadata getFile(OutputStream destination, String fileId)

In a traditional Servlet application, if I wanted to send the content to my client I would have done something like:

HttpServletResponse resp;
myService.getFile(resp.getOutpuStream, fileId);

My problem is that in my play 2 Controller class I don't have access to the underlying OuputStream, so the simplest implementation of my controller method would be:

public static downloadFile(String id) {
    ByteArrayOutputStream baos = new BAOS(...);
    myApi.getFile(baos,id); //Load inside temp Array      
    ByteArrayInputStream bais = new BAIS(baos.toByteArray())
    return Ok(bais);
 }

It will work but it requires to load the whole content into memory before serving it so it's not an option (files can be huge).

I was thinking of a solution consisting in:

  • Defining a ByteArrayOutputStream (baos) inside my controller
  • Calling the third party API with this baos in parameter
  • Using the chunk return of the play framework to send the content of the baos as soon as something is written inside by the 3rd party API

Problem is that I don't know if it possible (call to getFile is blocking so it would require multiple threads with a shared OutputStream) nor if it's overkill.

As someone ever faced this kind of problem and found a solution? Could my proposed solution solve my problem?

Any insights will be appreciated.

Thanks

EDIT 1 Based on kheraud suggestion I have managed to have a working, but still not perfect, solution (code below).

Unfortunately if a problem occurs during the call to the getFile method, error is not sent back to the client (because I returned Ok) and the browser waits indefinitely for a file that will never come.

Is there a way to handle this case ?

public static Result downloadFile(String fileId {    
      Thread readerThread = null;
      try {
          PipedOutputStream pos = new PipedOutputStream();
          PipedInputStream pis = new PipedInputStream(pos); 
          //Reading must be done in another thread
          readerThread = new DownloadFileWorker(fileId,pos);
          readerThread.start();

          return ok(pis);
      } catch (Exception ex) {
          ex.printStackTrace();
          return internalServerError(ex.toString());

      }
  }

static class DownloadFileWorker extends Thread{
      String fileId;  
      PipedOutputStream pos;

      public DownloadFileWorker(String fileId, PipedOutputStream pos) {
        super();
        this.fileId = fileId
        this.pos = pos;
    }

    public void run(){
          try {
              myApi.getFile(pos,fileId);
              pos.close();
          } catch (Exception ex) {
              ex.printStackTrace();
          }
      }
}

EDIT 2

I found a way to avoid infinite loading of the page by simply adding a pos.close in the catch() part of the worker thread. Client ends up with a zero KB file but I guess that's better than an infinite waiting.

2 Answers
Related