我的http请求在Akka未来变为null

时间:2013-07-03 14:09:36

标签: scala akka scalatra

我的服务器应用程序使用Scalatra,json4s和Akka。

它收到的大多数请求都是POST,它们会立即返回到客户端并返回固定响应。实际响应以异步方式发送到客户端的服务器套接字。为此,我需要从http请求getRemoteAddr。我正在尝试使用以下代码:

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"}""")
  }    
}

withJsonFuture函数的目的是将一些样板文件移出路径处理。

这有时可行(打印request的非空值),有时request为空,我觉得很令人费解。我怀疑在未来我必须“关闭”request。但是,当没有其他请求发生时,在受控测试方案中也会发生错误。我想request是不可变的(也许我错了?)

为了解决这个问题,我已将代码更改为以下内容:

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"}""")
  }    
}

这似乎有效。但是,我真的不知道它是否仍然包含任何可能导致未来错误的与并发相关的错误编程实践(“未来”意味着最常见的意义=未来的意义:)。

4 个答案:

答案 0 :(得分:5)

Scalatra不太适合异步代码。我最近偶然发现了和你一样的问题。 问题是scalatra试图通过暴露尽可能消除尽可能多的烦恼的dsl来使代码尽可能具有声明性,特别是不要求你明确地传递数据。

我会尝试解释。

在您的示例中,post("/test")中的代码是匿名函数。请注意,它不接受任何参数,甚至不接受当前请求对象。 相反,scalatra会在调用您自己的处理程序之前将当前请求对象存储在线程本地值中,然后您可以通过ScalatraServlet.request将其返回。

这是经典的动态范围模式。它的优点是您可以编写许多实用程序方法来访问当前请求并从处理程序中调用它们,而无需显式传递请求。

现在,当你使用异步代码时会出现问题。 在您的情况下,withJsonFuture内的代码在最初调用处理程序的原始线程之外的另一个线程上执行(它将在ExecutionContext的线程池的线程上执行)。 因此,当访问本地线程时,您正在访问线程局部变量的完全不同的实例。 简而言之,经典的动态范围模式不适合异步上下文。

这里的解决方案是在处理程序的最开始捕获请求,然后专门引用:

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)
  }
}

坦率地说,这很容易出错恕我直言,所以如果您处于同步环境中,我个人会完全避免使用Scalatra。

答案 1 :(得分:4)

我不知道Scalatra,但是你正在访问一个你自己没有定义的名为request的值,这很可疑。我的猜测是,它是扩展ScalatraServlet的一部分。如果是这种情况,则可能是可变状态,它在请求开始时设置(由Scalatra设置),然后在结束时无效。如果发生这种情况,那么您的解决方法是可以的,因为将request分配给val myRequest = request块之前的另一个val future,然后在未来内部将其作为myRequest访问闭合。

答案 2 :(得分:2)

我不知道scalatra,但乍一看,withJsonFuture函数返回OK,但也通过future { closure(addr, params) }调用创建了一个帖子。

如果在之后运行后一个线程处理了OK,则会发送响应并关闭/ GCed请求。

为什么要创建Future来运行closure

如果withJsonFuture需要返回Future(再次,抱歉,我不知道scalatra),您应该将该函数的整个部分包装在Future中。

答案 3 :(得分:0)

尝试将with FutureSupport放在这样的类声明中

class MyServices extends ScalatraServlet with FutureSupport {}