Content-length with Play stream API

2,089 views
Skip to first unread message

Alex Jarvis

unread,
Jul 18, 2012, 5:02:02 AM7/18/12
to play-fr...@googlegroups.com
The disadvantage of the Play chunked response API is that you should not set the Content-Length header.

The advantage of setting the Content-Length header is that the client knows what % of the file has downloaded. The HTTP spec states that you should not set the Content-Length header when using Transfer-Encoding is chunked, and that clients should ignore the length if it is.

So, this leads me to ask, is it possible to use the Play streaming API (so not loading all of the file into memory) without setting the transfer encoding to chunked and also setting the content-length header?

I am using MongoDB with GridFS which allows me to get the length of the file in bytes and also an input stream which I can pass to the ok() Result.

C. Mundi

unread,
Jul 18, 2012, 9:20:06 PM7/18/12
to play-fr...@googlegroups.com

This is a great question.  I want to know the answer, too.

For example, when I use FireFox to download the latest Ubuntu ISO image via HTTP (yes, I know this is abusive) the FireFox download manager knows exactly how many bytes it is expecting.  And surely the data are being chunked for streaming.  Could an HTTP guru explain this?  Maybe I should fire up wireshark and capture some headers and response codes.

--
You received this message because you are subscribed to the Google Groups "play-framework" group.
To view this discussion on the web visit https://groups.google.com/d/msg/play-framework/-/7FlhyAezwwcJ.
To post to this group, send email to play-fr...@googlegroups.com.
To unsubscribe from this group, send email to play-framewor...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/play-framework?hl=en.

Guillaume Bort

unread,
Jul 18, 2012, 10:32:54 PM7/18/12
to play-fr...@googlegroups.com
Yes it is theoretically possible. Now the current Java API misses this
method. You can either report the problem, or submit a pull request to
speed up things.
> --
> You received this message because you are subscribed to the Google Groups
> "play-framework" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/play-framework/-/7FlhyAezwwcJ.
> To post to this group, send email to play-fr...@googlegroups.com.
> To unsubscribe from this group, send email to
> play-framewor...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/play-framework?hl=en.



--
Guillaume Bort, http://guillaume.bort.fr

Sadache Aldrobi

unread,
Jul 19, 2012, 4:28:07 AM7/19/12
to play-fr...@googlegroups.com
It exists since 2.0 in the scala API.
Result case class already takes a stream (Enumerator) and if you add a CONTENT_LENGTH header then it will stream that body. There is also an API for doing this:

def downLoad() = Action {
    // 100000 times "123456789", one each 100ms
    val slowlyStreamedFile = Enumerator.repeatM(Promise.timeout("123456789",100)) &> Enumeratee.take(100000)
    Ok.feed(slowlyStreamedFile).withHeaders(CONTENT_LENGTH -> "1800000", CONTENT_DISPOSITION -> "attachment;filename=filename.ext")
  }

on chrome:

Inline image 1
www.sadekdrobi.com
ʎdoɹʇuǝ
image.png

Alex Jarvis

unread,
Jul 19, 2012, 5:20:35 AM7/19/12
to play-fr...@googlegroups.com
Thanks for your replies on this issue! I've just been looking at the Java API and can see where it is missing the feed method/function.

I'm a bit new to Scala Enumator/Iteratee stuff and have briefly been over the docs, but I'm not confident to write a pull request yet, so created an issue instead:


Cheers,
Alex
image.png

Alex Jarvis

unread,
Aug 14, 2012, 8:35:22 AM8/14/12
to play-fr...@googlegroups.com
Hi David,

Thanks a lot for your example! I guess I'll have to write the controller code for this in Scala like you have done then - it doesn't look too difficult!

Cheers!
Alex

On Wednesday, 8 August 2012 17:34:38 UTC+1, David M. wrote:
Hello,

I am using the Java API and have been facing the same problem as you: 
  - I have an InputStream 
  - I know the total length of data that will be streamed
  - I wanted the size of the downloaded file to be displayed in the web client (so that a progress bar is displayed)  
  - I don't know much of Scala ;-)

Here is how I made it to work:

1) I created a Java Helper class containing the following methods:

public class DownloadFileHelper {
   String fileId;

    public DownloadFileHelper(String fid) {
        this.fileId = fid;
     }

    public int getContentLength() {
       // retrieve the length of the requested file (via DB query or any other mean that suit your needs)
    }

    public InputStream getInputStream() {
      //Build an input stream that will be send us the content of requested file
    }
}

2) I created a really simple Controller in Scala that uses this helper

package controllers

import play.api._
import play.api.mvc._
import java.io.InputStream
import controllers.worker.DownloadFileHelper
import play.api.libs.iteratee.Enumerator

/**
 * Implements fileDownload in Scala because the withHeaders method is 
 * not available in the current version of the Java API (and content-length
 * cannot be set).
 * 
 * This property is required to allow web browsers to display a progress bar
 * during file download.
 * 
 */
object FilesDownload extends Controller{

  def downloadFile(fileId:String) = Action {
    
    //All the backend calls are inside the worker threads to keep scala part as simple as possible
    val worker:DownloadFileHelpder = new DownloadFileHelper(fileId);
    val is:InputStream  = worker.getInputStream();    
    var length = ""+worker.getContentLength();
    
    Ok.stream(Enumerator.fromStream(is)).withHeaders(CONTENT_LENGTH -> length);
    
  } 
  
}

3) Say hello to the progress bar in your browser ;-)

 
Hope it helps!
Regards,
David
Reply all
Reply to author
Forward
0 new messages