Table of contents
1.
Introduction
2.
Asynchronous HTTP programming
2.1.
Asynchronous Results
2.1.1.
Make Controllers Asynchronous.
2.1.2.
Creating Non-Blocking Actions
2.1.3.
How to Create a Future[Result]
2.1.4.
Returning futures
2.1.5.
Actions are Asynchronous by default.
2.1.6.
Handling Time-outs
2.2.
Streaming HTTP Responses
2.2.1.
Standard Responses and Content-Length Header
2.2.2.
Sending Large Amounts of Data
2.2.3.
Serving files
2.2.4.
Chunked Responses
2.3.
Comet
2.3.1.
Using Chunked Responses with Comet
2.3.2.
Comet Imports
2.3.3.
Using Comet with String Flow
2.3.4.
Using Comet with JSON Flow
2.3.5.
Using Comet with iframe
2.3.6.
Debugging Comet
2.4.
WebSockets
2.4.1.
Handling WebSockets
2.4.2.
Handling WebSockets with Akka Streams and actors
2.4.3.
Handling WebSockets with Akka streams directly
2.4.4.
Accessing a WebSocket
2.4.5.
Configuring WebSocket Frame Length
3.
Frequently Asked Questions
3.1.
What is asynchronous HTTP?
3.2.
Give an example of asynchronous programming.
3.3.
What is an asynchronous HTTP client?
3.4.
Give some examples of programming languages that are asynchronous.
3.5.
Which method is commonly used to make an asynchronous HTTP request?
4.
Conclusion
Last Updated: Mar 27, 2024
Medium

Scala Developers-Asynchronous HTTP programming

Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

Welcome!

In today's times, we may listen to the Play framework, created by Guillaume Bort, that follows the model–view–controller architectural pattern. If you have not listened to it, don't worry, but I am sure that You are thinking about why I am talking about the Play framework. So, the main reason for talking about the Play framework is that the Play framework is written in Scala, and we will cover Asynchronous HTTP programming in Scala. 

Now, coming to the topic of Asynchronous HTTP programming.

Asynchronous HTTP programming

Asynchronous HTTP Programming Image

Asynchronous HTTP programming is a big topic so let's discuss this in the following parts.

  • Asynchronous Results
  • Streaming HTTP Responses
  • Comet
  • WebSockets

Asynchronous Results

Let's see how we handle asynchronous results.

Make Controllers Asynchronous.

Make Controller Asynchronous Image

Play Framework is asynchronous Internally from the bottom up and handles every request asynchronously.

Also, the default configuration is tuned for asynchronous controllers, i.e., having the controller code wait for an operation or JDBC calls, streaming API, HTTP requests, and long computations are blocking operations.

We can also increase the number of threads in the default execution context to allow more concurrent requests to be processed by blocking controllers.

Creating Non-Blocking Actions

Creating Non-Blocking Actions Image

Because of the Play, the action code must be as fast as possible, i.e., non-blocking. 

So the question is, what should we return as a result if we are not yet able to generate it? 

The response is a future result!

A Future[Result] will be recovered with a value of type Result. Therefore, we can quickly generate the result without blocking by giving a Future[Result] instead of a typical Result.

While waiting for the response, the web client will be blocked, but nothing on the server will be blocked.  

Also, the server resources can be used to serve other clients.

If you call out to a blocking API such as JDBC, you will have to run your ExecutionStage with a different executor to move it off Play's rendering thread pool. You can also create a subclass of Play.api.libs.concurrent.CustomExecutionContext regarding the custom dispatcher.

import play.api.libs.concurrent.CustomExecutionContext
trait MyExecutionContext extends ExecutionContext
class MyExecutionContextImpl @Inject() (system: ActorSystem)
    extends CustomExecutionContext(system, "my.executor")
    with MyExecutionContext
class CodingNinjas @Inject() (myExecutionContext: MyExecutionContext, val controllerComponents: ControllerComponents)
    extends BaseController {
  def index = Action.async {
    Future {
      Ok("result of blocking call")
    }(myExecutionContext)
  }
}
You can also try this code with Online Java Compiler
Run Code


How to Create a Future[Result]

Let's discuss how we create a Future[result].

You need another future first to create a Future[Result], which means that the Future will give us the actual value that you need to compute the result:

val futurePIValue: Future[Double] = computePIAsynchronously()
val futureResult: Future[Result] = futurePIValue.map { pi =>
  Ok("PI value computed: " + pi)
}


All Play's asynchronous API calls give you a Future. You can call the external web service to schedule asynchronous tasks using the Play.api.libs.WS API or to communicate with actors using play.api.libs.Akka.

Returning futures

We can use the action.apply the builder method to build actions to send an asynchronous result. Also, we need to use the action.async builder method:

def index = Action.async{
  val futureInt = scala.concurrent.Future { intensiveComputation() }
  futureInt.map(i => Ok("Got result: " + i))
}

 

Actions are Asynchronous by default.

By default, Play actions are asynchronous. You can check in the code below that the { Ok(...) } part of the code is not the method body of the controller. An anonymous function is being passed to the Action object's apply method, which creates an object of type Action. The unknown function you wrote will be called internally, and its result will be enclosed in the Future.

def echo = Action { request =>
  Ok("Got request [" + request + "]")
}

 

Handling Time-outs

To avoid the web browser block, you can use Play.api.libs.concurrent.Futures to wrap a Future in the non-blocking timeout.

import scala.concurrent.duration._
import play.api.libs.concurrent.Futures._
def index = Action.async {
  intensiveComputation()
    .withTimeout(1.seconds)
    .map { i =>
      Ok("Got result: " + i)
    }
    .recover {
      case e: scala.concurrent.TimeoutException =>
        InternalServerError("timeout")
    }
}


Let's discuss the Streaming HTTP Responses in Asynchronous HTTP programming.

Streaming HTTP Responses

streaming http responses

Standard Responses and Content-Length Header

For keeping a single connection open to serve several HTTP requests and responses in HTTP1.1, the server must send the significant Content-Length HTTP header along with the response.

We are not specifying a Content-Length header by default when you send back a simple result, such as:

def index = Action {
  Ok("Hello World")
}

 

Sending Large Amounts of Data

What about large data sets if it's not a problem to load the whole content into memory? Let's say to return a large file to the web client.

Firstly, let's see how to create a Source[ByteString, _] for the file content:

val file = new java.io.File("/tmp/fileToServe.pdf")
val path: java.nio.file.Path = file.toPath
val source: Source[ByteString, _] = FileIO.fromPath(path)

 

Now it looks simple right? Let's use this streamed HttpEntity to specify the response body:

def streamed = Action{
  val file = new java.io.File("/tmp/fileToServe.pdf")
  val path: java.nio.file.Path = file.toPath
  val source: Source[ByteString, _] = FileIO.fromPath(path)
  Result(header = ResponseHeader(200, Map.empty),body = HttpEntity.Streamed(source, None, Some("application/pdf")))
}

 

Now, we have a problem here as we don't specify the Content-Length in a streamed entity, Play will have to compute it, and the only way to do this is to consume the entire source content and load it into memory, then compute the response size.

Serving files

Of course, Play provides easy-to-use helpers for the common task of serving a local file:

def file = Action {
  Ok.sendFile(new java.io.File("/tmp/fileToServe.pdf"))
}
You can also try this code with Online Java Compiler
Run Code

 

The above helper will also compute the Content-Type header from the file name and add the Content-Disposition header. To show this file inline, add the header Content-Disposition: inline; filename=fileToServe.pdf to the HTTP response.

Chunked Responses

We can compute the content length before streaming it; therefore, it works well with streaming file content. But what about the dynamically computed content when no content size is available?

The benefit is that we can serve the data live, which means that we send chunks of data as soon as they are available, and the drawback is that it cannot display a proper download progress bar since the web browser doesn't know the content size.

Now, let's see the uses of Comet with String flow, JSON flow, and iframe in the Asynchronous HTTP programming.

Comet

Comet Image

Using Chunked Responses with Comet

The common use of chunked responses is to create a Comet socket.

Since A Comet socket is a chunked text/html response that contains only <script> elements, and For each chunk, we can write a <script> tag containing JavaScript that is executed by the web browser immediately. Also, we can send events to live to the web browser from the server this way, and for each message, firstly, we have to wrap it into a <script> tag that calls a JavaScript callback function and then write it to the chunked response.

Comet Imports

To use the Comet helper, we have to import the classes, which are the following-

import akka.stream.Materializer
import akka.stream.scaladsl.Source
import play.api.http.ContentTypes
import play.api.libs.Comet
import play.api.libs.json._
import play.api.mvc._

 

Using Comet with String Flow

For pushing a string of messages through a Flow, do the following:

def cometString = Action {
  implicit val m = materializer
  def stringSource: Source[String, _] = Source(List("kiki", "foo", "bar"))
  Ok.chunked(stringSource.via(Comet.string("parent.cometMessage"))).as(ContentTypes.HTML)
}

 

Using Comet with JSON Flow

Fro pushing JSON messages through a Flow, do the following:

def cometJson = Action {
  implicit val m = materializer
  def jsonSource: Source[JsValue, _] = Source(List(JsString("jsonString")))
  Ok.chunked(jsonSource.via(Comet.json("parent.cometMessage"))).as(ContentTypes.HTML)
}

 

Using Comet with iframe

The comet helper should be used with a forever-iframe technique, with an HTML page like:

<script type="text/javascript">
  var cometMessage = function(event) {
    console.log('Received event: ' + event)
  }
</script>

<iframe src="/comet"></iframe>
You can also try this code with Online Javascript Compiler
Run Code

 

You have to add the following config to your application.conf and ensure that you have route mappings set up to see the above Comet in action.

play.filters.headers {
  frameOptions = "SAMEORIGIN"
  contentSecurityPolicy = "connect-src 'self'"
}
You can also try this code with Online Javascript Compiler
Run Code

 

Debugging Comet

Using the log() operation to show any errors in mapping data through the stream is the easiest or most straightforward way to debug a Comet stream that is not working.

At last, we will discuss the WebSockets in Asynchronous HTTP programming.

WebSockets

WebSockets Image

WebSockets are the type of sockets that are used from a web browser based on a protocol that allows two-way full duplex communication. Since the client can send messages, the server can receive messages as long as there is an active WebSocket connection between them.

The Modern HTML5 compliant web browsers support WebSockets via a JavaScript WebSocket API. However, WebSockets are not limited to just being used by WebBrowsers. Since there are many WebSocket client libraries available that allow servers to talk to each other and native mobile apps to use WebSockets. Also, using the WebSockets in these contexts has the advantage of reusing the existing TCP port used by the Play server.

Handling WebSockets

Until now, we have been using Action instances to handle standard HTTP requests and send back normal HTTP responses. WebSockets are a different beast and cannot be addressed via standard Action.

A WebSocket is modeled as a Flow, incoming WebSocket messages are fed into the Flow, and at last, the messages produced by the Flow are sent out to the client.

A flow is viewed as something that receives messages, does some processing, and then produces the processed messages. The input of the Flow may be disconnected entirely from the output of the Flow. Akka streams provide a constructor, Flow.fromSinkAndSource, precisely for this purpose, and often when handling WebSockets, the input and work will not be connected at all.

Also, The Play provides some factory methods for constructing WebSockets.

Handling WebSockets with Akka Streams and actors

To convert an ActorRef to a flow, we can use a Play utility, ActorFlow, to handle a WebSocket with an actor. This utility takes a function as an input that converts the ActorRef to send messages to an object akka.actor.props.

import play.api.mvc._
import play.api.libs.streams.ActorFlow
import javax.inject.Inject
import akka.actor.ActorSystem
import akka.stream.Materializer
class CodingNinjas @Inject() (cc: ControllerComponents)(implicit system: ActorSystem, mat: Materializer)
    extends AbstractController(cc) {
  def socket = WebSocket.accept[String, String] { request =>
    ActorFlow.actorRef { out =>
      MyWebSocketActor.props(out)
    }
  }
}
You can also try this code with Online Javascript Compiler
Run Code

 

Handling WebSockets with Akka streams directly

If the WebSocket behaves more like a stream, then Actors are not the right abstraction to handle the WebSockets.

import play.api.mvc._
import akka.stream.scaladsl._
def socket = WebSocket.accept[String, String] { request =>
  val in = Sink.foreach[String](println)
  val out = Source.single("Hello!").concat(Source.maybe)
  Flow.fromSinkAndSource(in, out)
}
You can also try this code with Online Javascript Compiler
Run Code

 

If you want to retrieve standard headers and session data, then WebSocket allows you to access the request headers. However, it doesn't access the request body or the HTTP response.

In the above example, we have created a sink that prints each message to the console. We make a simple source to send messages for sending a single Hello! Message. Also, we need to concatenate a source that will never send anything; otherwise, the single source will terminate the Flow and the connection.

Accessing a WebSocket

You need to add a route to send data or access a WebSocketfor your WebSocket in your routes file. 

Configuring WebSocket Frame Length

Now, coming to this topic, You can configure the maximum length for WebSocket data frames using Play.server.websocket.frame.maxLength or by passing -Dwebsocket.frame.maxLength system property when you are running your application.

Let's take an example.

sbt -Dwebsocket.frame.maxLength=64k run
You can also try this code with Online Javascript Compiler
Run Code

 

The above configuration gives you more control of WebSocket frame length. Also, it can be adjusted to your application requirements which may reduce denial of service attacks using extended data frames.

Let's Check out some FAQs related to Asynchronous HTTP programming.

Frequently Asked Questions

What is asynchronous HTTP?

Asynchronous HTTP Request Processing is a relatively new technique that allows us to process a single HTTP request.

Give an example of asynchronous programming.

The data may take a long time to submit to a database. But with asynchronous programming, the user can move or switch to another screen while the function continues to execute. For example, When a user uploads a photo, they don't have to stay on the same screen until the photo finishes loading.

What is an asynchronous HTTP client?

Asynchronous HTTP client (AsyncHttpClient (AHC)) is a library built on top of Netty, and the primary purpose is to execute the HTTP requests and process responses asynchronously efficiently. 

Give some examples of programming languages that are asynchronous.

C++, C#, Node. JS, etc., are some of the asynchronous programming languages.

Which method is commonly used to make an asynchronous HTTP request?

Ajax is the commonly used method to make an asynchronous HTTP request where Data are sent using the HTTP POST method and received using the HTTP GET method.

Conclusion

It's time to conclude this article, and I am sure you have gained knowledge of Asynchronous HTTP programming.

After reading about Asynchronous HTTP programming, are you not feeling excited to read/explore more articles on Data Structures and Algorithms? Don't worry; Coding Ninjas has you covered. See What is ScalaScala Programming LanguageAsynchronous Data Transfer, API ReferenceRest APIBasics of Java, and Web Technologies to learn.

Do upvote our blogs if you find them helpful and engaging!

Happy Learning!

Live masterclass