Asynchronous Actions

Suppose that we are at a food court and place an order to eat something at a kiosk, we are given a token and a bill. Later, when the order is ready, the kiosk flashes the token number, and upon noticing it, we collect the order.

This is similar to a request with an asynchronous response cycle, where the kiosk acts like the server, the order acts similar to a request, and the token as a promise, which gets resolved when the order is ready.

Most operations are better handled asynchronously. This is also mostly preferred since it does not block server resources until the operation is completed.

Play Action is a helper object, which extends the ActionBuilder trait. The apply method of the ActionBuilder trait implements the Action trait, which we saw earlier. Let's take a look at the relevant code from the ActionBuilder trait:

trait ActionBuilder[+R[_]] extends ActionFunction[Request, R] { 
  self => 

  final def apply[A](bodyParser: BodyParser[A])(block: R[A] => 
    Result): Action[A] = async(bodyParser) { req: R[A] => 
    Future.successful(block(req)) 
  } 

  final def async[A](bodyParser: BodyParser[A])(block: R[A] => Future[Result]): Action[A] = composeAction(new Action[A] { 
    def parser = composeParser(bodyParser) 
    def apply(request: Request[A]) = try { 
      invokeBlock(request, block) 
    } catch { 
      // NotImplementedError is not caught by NonFatal, wrap it 
      case e: NotImplementedError => throw new RuntimeException(e) 
      // LinkageError is similarly harmless in Play Framework, since automatic reloading could easily trigger it 
      case e: LinkageError => throw new RuntimeException(e) 
    } 
    override def executionContext = ActionBuilder.this.executionContext 
  }) 

... 

}  

Notice that the apply method itself calls the async method internally. The async method expects us to define the Action, which results in Future[Result], thereby aiding us to write non-blocking code.

We will use the same method to define an asynchronous Action. Assume that we need to fetch the requested file from a remote client, consolidate/analyze the data, and then send the results. Since we do not know the size of the file and the status of network connectivity with a remote client, it is better to handle the Action asynchronously. The action will be defined in this way:

def getReport(fileName:String ) = Action.async { 
     Future { 
      val file:File = new File(fileName) 
      if (file.exists()) { 
        val info = file.lastModified() 
        Ok(s"lastModified on ${new Date(info)}") 
      } 
      else 
        NoContent 
    } 
  } 

After fetching the file, if it is empty, we send a response with a status code of 204, else we continue with the processing and send the processed data as a part of the result.

We may come across an instance, as we saw in the previous example, get report, that we do not wish to wait longer than 10 seconds for the remote client to fetch the file. In this case, we'll need to modify the Action definition in this way:

def getReport(fileName: String) = Action.async { 
   
    val mayBeFile = Future { 
      new File(fileName) 
    } 
    val timeout = play.api.libs.concurrent.Promise.timeout("Past max time", 10, TimeUnit.SECONDS) 
    Future.firstCompletedOf(Seq(mayBeFile, timeout)).map { 
      case f: File => 
        if (f.exists()) {
          val info = f.lastModified()
          Ok(s"lastModified on ${new Date(info)}")
        } 
        else
          NoContent
      case t: String => InternalServerError(t)
    }
  }

So, if the remote client doesn't respond with the requested file in 10 seconds, we will get a response with status code 500 and the content as the message we set for the timeout, Past max time.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.12.207