📜 ⬆️ ⬇️

Asynchronous HTTP in the Play Framework

When writing your own web applications, situations often arise in which you have to choose between synchronous and asynchronous execution of requests. On the one hand, the idea of ​​synchronous work looks quite logical: we start some process, wait for its completion, and then continue working. But in fact, this approach is good only in the case of simple calculations. Imagine that in the process of execution you need, for example, to make a complex query to the database - and even better, send the request to another server and wait for the results from it. In this case, the further work of the flow will be stopped for quite a long time - and this, of course, is not always acceptable. In such cases asynchronous requests come to the rescue. Let's see how work with them is implemented in the Play framework.

Future [Result]


The situation with costly calculations described above in the Play framework is solved with the help of Future [Result]. The idea behind it is simple: why wait for the result of the calculations, if instead you can switch to other tasks? The difference for the user, of course, will not be - his browser will still wait for a response from the server. But the server itself will be able, instead of waiting for the result, to transfer resources to handle requests from other users. Well, as soon as we get the result, it can be quickly processed and sent to the user.

All that is needed to execute a block of code asynchronously with the receipt of Future [Result] is to wrap it in a simple structure of the form:

import play.api.libs.concurrent.Execution.Implicits.defaultContext val futureInt: Future[Int] = scala.concurrent.Future { intensiveComputation()} 

')
And to return the resulting value - use the Action.async method:

 import play.api.libs.concurrent.Execution.Implicits.defaultContext def index = Action.async { val futureInt = scala.concurrent.Future { intensiveComputation() } futureInt.map(i => Ok("Got result: " + i))} 


And this option works well as long as the code runs correctly. But no one is immune from errors during execution, and if the error happens in a block of code that is executed asynchronously, it can lead to the result that the result will not be generated. This means that the user will not receive a response and his browser will remain on hold until he finally closes the tab. To avoid this, however, is quite simple: each promise (that is, Future [Result]) can be timed out: if during this time we do not get a real result, an error will be generated. This is also done quite simply:

 import play.api.libs.concurrent.Execution.Implicits.defaultContext import scala.concurrent.duration._ def index = Action.async { val futureInt = scala.concurrent.Future { intensiveComputation() } val timeoutFuture = play.api.libs.concurrent.Promise.timeout("Oops", 1.second) Future.firstCompletedOf(Seq(futureInt, timeoutFuture)).map { case i: Int => Ok("Got result: " + i) case t: String => InternalServerError(t) }} 


But It should be noted that with all the advantages, Future [Result] should be used wisely: after all, when using it, one of the threads will still be blocked, just in this case - not the main one. And this can lead to a variety of problems, especially with a large number of competitive requests.

HTTP streaming responses


In HTTP 1.1, one connection can be used to send multiple requests ( http://en.wikipedia.org/wiki/HTTP_persistent_connection ). With one condition: the server must include the Content-Length header in the response. Usually, when sending replies, this condition is not so important, because Play is quite capable of calculating the size of the content on its own. But in fact, not always everything is so simple: because in the simplest case (using SimpleResult), the framework uses the Enumerator (play.api.libs.iteratee.Enumerator) to form the response body, to calculate the length it needs to load the entire content in memory. And in the case of large amounts of data (imagine that you want to return the video file to the user) this can lead to some problems.

The concept of Enumerator in Play is closely related to the concept Iteratee. A bunch of Iteratee-Enumerator serves in this framework for the non-blocking implementation of the customer-producer pattern. In other words, Iteratee is a consumer that can generate some result based on absorbed data fragments. Enumerator, on the other hand, is a manufacturer who supplies these fragments.

Note : for standard cases of working with Enumerator, Play provides a set of factory functions for creating it: Enumerator.fromFile, for example, creates it based on the contents of the file; Enumerator.fromStream - based on the data stream. In general, however, an Enumerator will have to be created manually.

But back to streaming responses. To avoid performance problems when calculating the size of data, it is better to calculate it in advance and substitute the resulting result in the request header. This eliminates the need to load into memory the entire contents of the Enumerator, which means that resources are saved and productivity increases. For the example described above, the video file can be accessed as follows:

 def index = Action { val file = new java.io.File("/tmp/fileToServe.mov") val fileContent: Enumerator[Array[Byte]] = Enumerator.fromFile(file) SimpleResult( header = ResponseHeader(200, Map(CONTENT_LENGTH -> file.length.toString)), body = fileContent )} 


Or just use the Play framework function, designed to transfer the file to the user:

 def index = Action { Ok.sendFile(new java.io.File("/tmp/fileToServe.mov"))} 


In addition to the obviously much simpler approach, it has several other advantages: for example, it allows you to change the file name or display its contents directly in the browser window - this can be very convenient if you need to load an HTML template or image.

However, all the methods described above are good only in the case of static content, that is, such that we know in advance. But what if by the time of the beginning of the transfer all the content is not ready yet - and therefore it will be impossible to calculate its length with all the desire? In this case, fragmented replies that use the chunked transfer encoding mechanism ( http://en.wikipedia.org/wiki/Chunked_transfer_encoding ) come to the rescue. This approach eliminates the need to use Content-Length, which means that you can start transferring data as you generate them, without knowing their final size. Can play with him Play? You still ask:

 def index = Action { val data = getDataStream val dataContent: Enumerator[Array[Byte]] = Enumerator.fromStream(data) ChunkedResult( header = ResponseHeader(200), chunks = dataContent )} 


Or:

 def index = Action { val data = getDataStream val dataContent: Enumerator[Array[Byte]] = Enumerator.fromStream(data) Ok.chunked(dataContent)} 


You can pass and Enumerator:

 def index = Action { Ok.chunked( Enumerator("kiki", "foo", "bar").andThen(Enumerator.eof) )} 


The andThen method is used to create a sequence of several Enumerator. In this case, for example, after the data, Enumerator.eof is transmitted - a zero-length fragment that signals that the transfer is complete.

Note : instead of the andThen method, you can use the >>> operator - functionally there is no difference between them, but using the latter can increase the readability of the code, saving it from an excessive number of brackets.

But this approach also has its drawbacks: for example, due to the fact that until the last moment the final size of the transmitted data is unknown to the browser, it cannot show the percentage of completion. But this, in fact, is not such a big minus compared with the need to wait until the end of the generation of data before starting their transfer.

Comet sockets


One application of the fragmented response method described above is to create Comet sockets. What it is? If simple, these are answers consisting of
  .  ,      ,    -           ,     . 

, Ok.chunked Ok.stream:

def comet = Action { val events = Enumerator( """<script>console.log('kiki')</script>""", """<script>console.log('foo')</script>""", """<script>console.log('bar')</script>""" ) Ok.stream(events >>> Enumerator.eof).as(HTML)}


: Play framework:

def comet = Action { val events = Enumerator("kiki", "foo", "bar") Ok.stream((events &> Comet(callback = "console.log")) >>> Enumerator.eof)}


WebSockets
Play , Comet sockets . : , , , , Ajax-. , WebSockets ( http://en.wikipedia.org/wiki/WebSocket ), .

, WebSockets, Action: - . WebSocket, Action , , , , HTTP .

WebSocket :

def index = WebSocket.using[String] { request => // Just consume and ignore the input val in = Iteratee.consume[String]() // Send a single 'Hello!' message and close val out = Enumerator("Hello!").andThen(Enumerator.eof) (in, out)}

In - - Iteratee, , ; out - - Enumerator, .


, , , Play - , . : , .
. , , - , .

, Ok.chunked Ok.stream:

def comet = Action { val events = Enumerator( """<script>console.log('kiki')</script>""", """<script>console.log('foo')</script>""", """<script>console.log('bar')</script>""" ) Ok.stream(events >>> Enumerator.eof).as(HTML)}


: Play framework:

def comet = Action { val events = Enumerator("kiki", "foo", "bar") Ok.stream((events &> Comet(callback = "console.log")) >>> Enumerator.eof)}


WebSockets
Play , Comet sockets . : , , , , Ajax-. , WebSockets ( http://en.wikipedia.org/wiki/WebSocket ), .

, WebSockets, Action: - . WebSocket, Action , , , , HTTP .

WebSocket :

def index = WebSocket.using[String] { request => // Just consume and ignore the input val in = Iteratee.consume[String]() // Send a single 'Hello!' message and close val out = Enumerator("Hello!").andThen(Enumerator.eof) (in, out)}

In - - Iteratee, , ; out - - Enumerator, .


, , , Play - , . : , .
  .  ,      ,    -           ,     . 

, Ok.chunked Ok.stream:

def comet = Action { val events = Enumerator( """<script>console.log('kiki')</script>""", """<script>console.log('foo')</script>""", """<script>console.log('bar')</script>""" ) Ok.stream(events >>> Enumerator.eof).as(HTML)}


: Play framework:

def comet = Action { val events = Enumerator("kiki", "foo", "bar") Ok.stream((events &> Comet(callback = "console.log")) >>> Enumerator.eof)}


WebSockets
Play , Comet sockets . : , , , , Ajax-. , WebSockets ( http://en.wikipedia.org/wiki/WebSocket ), .

, WebSockets, Action: - . WebSocket, Action , , , , HTTP .

WebSocket :

def index = WebSocket.using[String] { request => // Just consume and ignore the input val in = Iteratee.consume[String]() // Send a single 'Hello!' message and close val out = Enumerator("Hello!").andThen(Enumerator.eof) (in, out)}

In - - Iteratee, , ; out - - Enumerator, .


, , , Play - , . : , .
. , , - , .

, Ok.chunked Ok.stream:

def comet = Action { val events = Enumerator( """<script>console.log('kiki')</script>""", """<script>console.log('foo')</script>""", """<script>console.log('bar')</script>""" ) Ok.stream(events >>> Enumerator.eof).as(HTML)}


: Play framework:

def comet = Action { val events = Enumerator("kiki", "foo", "bar") Ok.stream((events &> Comet(callback = "console.log")) >>> Enumerator.eof)}


WebSockets
Play , Comet sockets . : , , , , Ajax-. , WebSockets ( http://en.wikipedia.org/wiki/WebSocket ), .

, WebSockets, Action: - . WebSocket, Action , , , , HTTP .

WebSocket :

def index = WebSocket.using[String] { request => // Just consume and ignore the input val in = Iteratee.consume[String]() // Send a single 'Hello!' message and close val out = Enumerator("Hello!").andThen(Enumerator.eof) (in, out)}

In - - Iteratee, , ; out - - Enumerator, .


, , , Play - , . : , .

. , , - , .

, Ok.chunked Ok.stream:

def comet = Action { val events = Enumerator( """<script>console.log('kiki')</script>""", """<script>console.log('foo')</script>""", """<script>console.log('bar')</script>""" ) Ok.stream(events >>> Enumerator.eof).as(HTML)}


: Play framework:

def comet = Action { val events = Enumerator("kiki", "foo", "bar") Ok.stream((events &> Comet(callback = "console.log")) >>> Enumerator.eof)}


WebSockets
Play , Comet sockets . : , , , , Ajax-. , WebSockets ( http://en.wikipedia.org/wiki/WebSocket ), .

, WebSockets, Action: - . WebSocket, Action , , , , HTTP .

WebSocket :

def index = WebSocket.using[String] { request => // Just consume and ignore the input val in = Iteratee.consume[String]() // Send a single 'Hello!' message and close val out = Enumerator("Hello!").andThen(Enumerator.eof) (in, out)}

In - - Iteratee, , ; out - - Enumerator, .


, , , Play - , . : , .

. , , - , .

, Ok.chunked Ok.stream:

def comet = Action { val events = Enumerator( """<script>console.log('kiki')</script>""", """<script>console.log('foo')</script>""", """<script>console.log('bar')</script>""" ) Ok.stream(events >>> Enumerator.eof).as(HTML)}


: Play framework:

def comet = Action { val events = Enumerator("kiki", "foo", "bar") Ok.stream((events &> Comet(callback = "console.log")) >>> Enumerator.eof)}


WebSockets
Play , Comet sockets . : , , , , Ajax-. , WebSockets ( http://en.wikipedia.org/wiki/WebSocket ), .

, WebSockets, Action: - . WebSocket, Action , , , , HTTP .

WebSocket :

def index = WebSocket.using[String] { request => // Just consume and ignore the input val in = Iteratee.consume[String]() // Send a single 'Hello!' message and close val out = Enumerator("Hello!").andThen(Enumerator.eof) (in, out)}

In - - Iteratee, , ; out - - Enumerator, .


, , , Play - , . : , .
  .  ,      ,    -           ,     . 

, Ok.chunked Ok.stream:

def comet = Action { val events = Enumerator( """<script>console.log('kiki')</script>""", """<script>console.log('foo')</script>""", """<script>console.log('bar')</script>""" ) Ok.stream(events >>> Enumerator.eof).as(HTML)}


: Play framework:

def comet = Action { val events = Enumerator("kiki", "foo", "bar") Ok.stream((events &> Comet(callback = "console.log")) >>> Enumerator.eof)}


WebSockets
Play , Comet sockets . : , , , , Ajax-. , WebSockets ( http://en.wikipedia.org/wiki/WebSocket ), .

, WebSockets, Action: - . WebSocket, Action , , , , HTTP .

WebSocket :

def index = WebSocket.using[String] { request => // Just consume and ignore the input val in = Iteratee.consume[String]() // Send a single 'Hello!' message and close val out = Enumerator("Hello!").andThen(Enumerator.eof) (in, out)}

In - - Iteratee, , ; out - - Enumerator, .


, , , Play - , . : , .
. , , - , .

, Ok.chunked Ok.stream:

def comet = Action { val events = Enumerator( """<script>console.log('kiki')</script>""", """<script>console.log('foo')</script>""", """<script>console.log('bar')</script>""" ) Ok.stream(events >>> Enumerator.eof).as(HTML)}


: Play framework:

def comet = Action { val events = Enumerator("kiki", "foo", "bar") Ok.stream((events &> Comet(callback = "console.log")) >>> Enumerator.eof)}


WebSockets
Play , Comet sockets . : , , , , Ajax-. , WebSockets ( http://en.wikipedia.org/wiki/WebSocket ), .

, WebSockets, Action: - . WebSocket, Action , , , , HTTP .

WebSocket :

def index = WebSocket.using[String] { request => // Just consume and ignore the input val in = Iteratee.consume[String]() // Send a single 'Hello!' message and close val out = Enumerator("Hello!").andThen(Enumerator.eof) (in, out)}

In - - Iteratee, , ; out - - Enumerator, .


, , , Play - , . : , .

. , , - , .

, Ok.chunked Ok.stream:

def comet = Action { val events = Enumerator( """<script>console.log('kiki')</script>""", """<script>console.log('foo')</script>""", """<script>console.log('bar')</script>""" ) Ok.stream(events >>> Enumerator.eof).as(HTML)}


: Play framework:

def comet = Action { val events = Enumerator("kiki", "foo", "bar") Ok.stream((events &> Comet(callback = "console.log")) >>> Enumerator.eof)}


WebSockets
Play , Comet sockets . : , , , , Ajax-. , WebSockets ( http://en.wikipedia.org/wiki/WebSocket ), .

, WebSockets, Action: - . WebSocket, Action , , , , HTTP .

WebSocket :

def index = WebSocket.using[String] { request => // Just consume and ignore the input val in = Iteratee.consume[String]() // Send a single 'Hello!' message and close val out = Enumerator("Hello!").andThen(Enumerator.eof) (in, out)}

In - - Iteratee, , ; out - - Enumerator, .


, , , Play - , . : , .

. , , - , .

, Ok.chunked Ok.stream:

def comet = Action { val events = Enumerator( """<script>console.log('kiki')</script>""", """<script>console.log('foo')</script>""", """<script>console.log('bar')</script>""" ) Ok.stream(events >>> Enumerator.eof).as(HTML)}


: Play framework:

def comet = Action { val events = Enumerator("kiki", "foo", "bar") Ok.stream((events &> Comet(callback = "console.log")) >>> Enumerator.eof)}


WebSockets
Play , Comet sockets . : , , , , Ajax-. , WebSockets ( http://en.wikipedia.org/wiki/WebSocket ), .

, WebSockets, Action: - . WebSocket, Action , , , , HTTP .

WebSocket :

def index = WebSocket.using[String] { request => // Just consume and ignore the input val in = Iteratee.consume[String]() // Send a single 'Hello!' message and close val out = Enumerator("Hello!").andThen(Enumerator.eof) (in, out)}

In - - Iteratee, , ; out - - Enumerator, .


, , , Play - , . : , .

Source: https://habr.com/ru/post/219471/


All Articles