My http request becomes null in future Akka

My server application uses Scalatra with json4s and Akka.

Most of the requests he receives are POST, and they are immediately returned to the client with a fixed response. Actual responses are sent asynchronously to the server socket on the client. For this, I need getRemoteAddr from the http request. I am trying to use the following code:

 case class MyJsonParams(foo:String, bar:Int) class MyServices extends ScalatraServlet { implicit val formats = DefaultFormats post("/test") { withJsonFuture[MyJsonParams]{ params => // code that calls request.getRemoteAddr goes here // sometimes request is null and I get an exception println(request) } } def withJsonFuture[A](closure: A => Unit)(implicit mf: Manifest[A]) = { contentType = "text/json" val params:A = parse(request.body).extract[A] future{ closure(params) } Ok("""{"result":"OK"}""") } } 

The purpose of the withJsonFuture function is to move some pattern from my route processing.

This sometimes works (prints a non-zero value for request ), and sometimes request is null, which I find pretty cryptic. I suspect that I should "close" the request in my future. However, the error also occurs in controlled testing scenarios when no other requests occur. I would suggest that request is immutable (maybe I'm wrong?)

In an attempt to solve the problem, I changed my code to the following:

 case class MyJsonParams(foo:String, bar:Int) class MyServices extends ScalatraServlet { implicit val formats = DefaultFormats post("/test") { withJsonFuture[MyJsonParams]{ (addr, params) => println(addr) } } def withJsonFuture[A](closure: (String, A) => Unit)(implicit mf: Manifest[A]) = { contentType = "text/json" val addr = request.getRemoteAddr() val params:A = parse(request.body).extract[A] future{ closure(addr, params) } Ok("""{"result":"OK"}""") } } 

It seems to work. However, I really don’t know if it still contains some bad concurrency-related programming practice that may cause an error in the future (β€œfuture” means the most common meaning = what's ahead :).

+4
source share
3 answers

I do not know Scalatra, but it is suspicious that you are accessing a request value that you yourself do not define. I guess this happens as part of the ScalatraServlet extension. If so, then it probably changes the state that it is installed (according to ScalaTra) at the beginning of the request, and then is canceled at the end. If this happens, then your workaround is fine, as it would assign request different value, like val myRequest = request , in front of the future block, and then get it as myRequest inside the future and close.

+4
source

Scalatra is not so suitable for asynchronous code. I recently came across the same problem as you. The problem is that scalatra tries to make the code as declarative as possible by exposing dsl, which removes as much fuss as possible, and in particular, does not require you to explicitly pass the data.

I will try to explain.

In your example, the code inside post("/test") is an anonymous function. Note that it does not accept any parameters, not even the current request object. Instead, scalatra will store the current request object inside the local value of the stream just before it calls your own handler, and you can get it back through ScalatraServlet.request .

This is a classic dynamic scale pattern. The advantage is that you can write many useful methods that access the current request and call them from your handlers, without explicitly passing the request.

Now the problem arises when you use asynchronous code, just like you do. In your case, the code inside withJsonFuture is executed in a different thread than the original thread originally called by the handler (it will be executed in the thread from the thread pool ExecutionContext ). Thus, when accessing the local stream, you get access to a completely different instance of the local stream variable. Simply put, the classic Dynamic Scope pattern is not suitable in an asynchronous context.

The solution here is to capture the request at the very beginning of your handler, and then only reference it:

 post("/test") { val currentRequest = request withJsonFuture[MyJsonParams]{ params => // code that calls request.getRemoteAddr goes here // sometimes request is null and I get an exception println(currentRequest) } } 

Quite frankly, this is too easy to make a mistake IMHO, so I would personally avoid using Scalatra at all if you are in a synchronous context.

+5
source

I do not know the scalar, but at first glance the withJsonFuture function returns OK , but also creates a stream through a call to future { closure(addr, params) } .

If this last thread is started after OK processed, the response is sent and the request is closed / GCed.

Why create a Future to run closure ?

if withJsonFuture should return Future (again, sorry, I do not know the scatra), you should wrap the entire element of this function in Future .

+2
source

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


All Articles