I would like to use Play2 to implement a server that receives a POST of files, process the files as they are received, and send the results to a GET request from another user while the POST is still transferring files. Is it possible to do this with Play2? I already use Play2 to authenticate users, so I would like to use Play2 to handle authentication of this transaction. I use Java, but if needed, I can use Scala. If so, what should I look into? If not, I guess I need to look into some Java NIO frameworks and implement another server.
-
It is definitely possible with Play, but is this really what you want? What if there is nobody to get the file that is being uploaded? Does it just "stay there" until someone comes to get it? What if there's nobody to upload a file if someone is in queue to download? Will the download just wait? – Carsten Jan 28 '13 at 18:07
-
The upload and download will occur between two currently online users. A download notification will be sent to the other user once this user is ready to upload. The whole operation will time out if download/upload doesn't start within certain amount of time. We do not want to keep a saved file on the server for this transfer operation. – coolsuntraveler Jan 28 '13 at 22:18
-
I don't see how it is possible because when I get the file from Play, the whole file has been uploaded from the user onto the server. I don't see a way to get a part of the uploaded file and pass it to a GET request from another user. – coolsuntraveler Jan 28 '13 at 22:27
-
1You have to use the cool new Play 2 features like Iteratees. I don't know much about them myself, but I'll try it this week if I have the time. here are some starting points. You need to write a [custom PartHandler](http://stackoverflow.com/questions/12066993/uploading-file-as-stream-in-play-framework-2-0), and you need a way to get that data to another user. Similar to [this demo](http://blog.greweb.fr/2012/08/zound-a-playframework-2-audio-streaming-experiment-using-iteratees/), where an audio stream is sent to clients. – Carsten Jan 29 '13 at 17:06
1 Answers
I don't think this is possible in Java, but I've managed to put together a simple Scala version. You can still use Java for all other parts of you project.
But be aware that it's just a crude demo, focussing on the "simultaneous up- and download" part and ignoring everything else. I'm not even sure that this is the right way to do it, but it seems to work.
Basically, what you want to do is to use Concurrent.broadcast
to create a input/output stream pair. The output part (Enumeratee
) is streamed to the client. Then, you need a custom PartHandler
which takes the uploaded data as it arrives, and feeds it into the the input part (Concurrent.Channel
).
Please not that, in order for this example to work, you have to go to the download page first, then start uploading a file.
sample Application.scala
package controllers
import play.api._
import libs.iteratee.{Enumerator, Input, Iteratee, Concurrent}
import play.api.mvc._
import scala.collection.concurrent
object Application extends Controller {
val streams = new concurrent.TrieMap[String,(Enumerator[Array[Byte]], Concurrent.Channel[Array[Byte]])]
def index = Action {
Ok(views.html.index())
}
def download(id: String) = Action {
val (fileStream, fileInput) = Concurrent.broadcast[Array[Byte]]
streams += id -> (fileStream, fileInput)
Ok.stream {
fileStream.onDoneEnumerating(streams -= id)
}
}
def upload = Action(parse.multipartFormData(myPartHandler)) {
request => Ok("banana!")
}
def myPartHandler: BodyParsers.parse.Multipart.PartHandler[MultipartFormData.FilePart[Result]] = {
parse.Multipart.handleFilePart {
case parse.Multipart.FileInfo(partName, filename, contentType) =>
val (thisStream, thisInput) = streams(partName)
Iteratee.fold[Array[Byte], Concurrent.Channel[Array[Byte]]](thisInput) { (inp, data) =>
inp.push(data)
inp
}.mapDone { inp =>
inp.push(Input.EOF)
Ok("upload Done")
}
}
}
}
sample index.scala.html
<p>
<a href="@routes.Application.download("123456")">Download</a>
</p>
<form action="@routes.Application.upload" method="post" enctype="multipart/form-data">
<input type="file" name="123456">
<input type="submit">
</form>

- 17,991
- 4
- 48
- 53
-
Thanks for your help. I haven't upgraded to Play 2.1 yet, but I got it to work using the post you shared with PipedInputStream and PipedOutputStream. I will definitely try Concurrent.broadcast once I use Play 2.1. – coolsuntraveler Feb 08 '13 at 01:14
-
in my implementation, Play will start buffering the data if sending occurs before receiving. Does it happen in your implementation? Do you know of anyway to tell Play to block sending? My ideal behavior is for sending to start first, but Play will block it. Once the receiving side starts receiving, sending will start uploading. I tried tweaking chunk size of enumerator and pipe size of PipedInputStream, but it didn't help. – coolsuntraveler Feb 11 '13 at 18:28
-
You can start sending, as long as there's an appropriate entry in the `streams` Map. In my example, it's just created on the downloader's side, but you could as well check on both sides if it already exists, and if it doesn't, create it. But again: my example just focuses on the streaming stuff. You really need to implement checks like this one, and also if there are multiple people uploading to or downloading from the same `id`. – Carsten Feb 11 '13 at 21:11
-
I did implement the mechanism to download using different ids. I think I know the answer to my question. The browsers automatically download a file after I click on the link even though I haven't chosen a place to save it. I was confused by that behavior, thinking the upload starts before I download it. Now I am facing another problem. The download stream usually freezes after 50MB without completing the download. I upgraded to Play 2.1 and use your implementation and still see the same problem. I need to look into that. Any idea? – coolsuntraveler Feb 13 '13 at 23:12
-
I tested the code with a 1 GB file and it worked just fine. I'm not sure how big the file parts are, but if they're small, it could help to re-chunk them into bigger parts (say, 100 kB or 1 MB), [following this example](http://blog.greweb.fr/2012/08/zound-a-playframework-2-audio-streaming-experiment-using-iteratees/). – Carsten Feb 14 '13 at 08:23
-
Thanks. I think it is probably my office internet problem then. I transferred up to 600MB at home without any problem, but I still saw disconnection for 1GB+ files. But that should be acceptable for now. – coolsuntraveler Feb 18 '13 at 21:30