4

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

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

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

4 回答 4

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。

于 2013-07-03T15:07:41.133 回答
4

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

于 2013-07-03T14:53:28.650 回答
2

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

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

为什么要创建一个Future来运行你closure

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

于 2013-07-03T14:51:26.853 回答
0

试着with FutureSupport像这样写你的班级声明

class MyServices extends ScalatraServlet with FutureSupport {}

于 2018-07-24T05:52:24.617 回答