Scala 我的http请求在Akka future中变为空
我的服务器应用程序使用Scalatra、json4s和Akka 它收到的大多数请求都是POST,它们会立即返回到客户端,并给出固定的响应。实际响应以异步方式发送到客户端的服务器套接字。为此,我需要从http请求中Scala 我的http请求在Akka future中变为空,scala,akka,scalatra,Scala,Akka,Scalatra,我的服务器应用程序使用Scalatra、json4s和Akka 它收到的大多数请求都是POST,它们会立即返回到客户端,并给出固定的响应。实际响应以异步方式发送到客户端的服务器套接字。为此,我需要从http请求中getRemoteAddr。我正在使用以下代码进行尝试: case class MyJsonParams(foo:String, bar:Int) class MyServices extends ScalatraServlet { implicit val formats = D
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
为空,这让我感到非常困惑。我怀疑我将来一定会“关闭”请求。但是,当没有其他请求进行时,受控测试场景也会发生错误。我会想象,请求
是不可变的(也许我错了?)
为了解决此问题,我将代码更改为:
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"}""")
}
}
这似乎奏效了。但是,我真的不知道它是否还包括任何与并发相关的糟糕编程实践,这些实践可能会在将来导致错误(“未来”在其最常见的意义上是指未来:)。我不知道scalatra,但乍一看,withJsonFuture
函数返回一个OK
,但也通过future{closure(addr,params)}
调用创建一个线程
如果后一个线程在处理OK
后运行,则响应已发送,请求已关闭/GCed
为什么要创建一个Future
来运行您的closure
如果withJsonFuture
需要返回Future
(再次抱歉,我不知道scalatra),您应该将该函数的整个主体包装在Future
中,我不知道scalatra,但是您正在访问一个名为request
的值,而您没有定义自己,这是可疑的。我猜它是扩展ScalatraServlet
的一部分。如果是这种情况,那么可能是可变状态,即在请求开始时(由Scalatra)设置,然后在请求结束时取消。如果发生了这种情况,那么您的解决方法就可以了,就像在未来
块之前将请求
分配给另一个val,比如val myRequest=request
,然后在未来和闭包中作为myRequest
访问它一样。
我会尽力解释的
在您的示例中,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。尝试在您的类声明中添加带有FutureSupport的
类MyServices使用FutureSupport{}扩展ScalatrServlet
我想这也可能与尝试做一些与框架建议不同的事情有关。它们有自己的json和futures组件,但文档记录有点不足。我想这是Scala web框架的一个普遍问题。然而,尽管有点肤浅,我还是要说Scalatra的文档是本课程中最完整、对初学者最友好的文档之一。也许电梯有更好的文件,但它太重了,我的需要。我急切地等待着Scalatra MEAP的书,但到目前为止,我没有看到在编写章节方面有太多进展。他们确实有一个AsyncResult
类,但这并不能解决使用本地线程的问题。遗憾的是,您仍然必须在处理程序开始之前捕获请求。我的直觉告诉我,scalatra添加了异步支持,这当然是正确的,这也是scalatra 2.2拥有非常方便的AsyncResult
类的原因。@RandallSchulz:我不使用AsyncResult
,因为我想立即发送响应。真正的响应以异步方式发送到客户端的服务器套接字(它不是浏览器,而是Adobe Air应用程序)。未来发生的事情是火和遗忘。我使用的是未来,而不是参与者,因为参与者序列化请求,我希望它们并行运行。我知道我可以培养出一个短命的演员,但未来似乎不那么冗长。