Как я могу безопасно обрабатывать запросы маршрутизации одновременно в spray.routing?

Кажется, что примеры использования HTTP-сервера Spray упрощают процесс обработки сервером запросов последовательно, а не одновременно. Это верно, потому что в примерах показан объект маршрутизации, реализованный как актор, обрабатывающий один запрос за раз (facepalm?**). Это кажется быть общим проблема.

Например, ниже при доступе к /work1 запрос обрабатывается асинхронно, но для /work2 мы, к сожалению, блокируем ВСЕ другие запросы (предположим, например, что /work2 должен быть занят аутентификацией маркера из файла cookie в базе данных).

Есть ли способ использовать spray.routing, где выполнение разветвляется до перехода к маршрутизации?

import akka.actor.ActorSystem
import spray.http.{MediaTypes, HttpEntity}
import spray.routing.SimpleRoutingApp
import scala.concurrent.Future

class MySimpleServer(val system: ActorSystem, val HOST: String, val PORT: Int) extends SimpleRoutingApp {

  implicit val _system: ActorSystem = system
  import _system.dispatcher

  def main(args: Array[String]): Unit = {
    startServer(interface = HOST, port = PORT) {
      get {
        path("work1") {
          complete {
            // Asynchronously process some work
            Future.apply {
              Thread.sleep(1000)
              HttpEntity(
                MediaTypes.`text/html`,
                "OK"
              )
            }
          }
        } ~
          path("work2") {
            complete {
              // Synchronously process some work and block all routing for this Actor.
              // Oh sh*t!
              Thread.sleep(1000)
              HttpEntity(
                MediaTypes.`text/html`,
                "OK"
              )
            }
          } 
      }
    }
  }
}

** поскольку маршрутизация, как правило, является операцией без сохранения состояния, кажется, что нет смысла создавать маршрутизатор и актера, верно?

Для каждого другого веб-сервера, который я использовал, разветвление управления соединением с процессом-обработчиком или потоком более разумно (IMO) происходит почти сразу после принятия TCP-соединения. (Я думаю) это максимизирует скорость, с которой могут быть получены соединения, и минимизирует риск непреднамеренной блокировки - по крайней мере, полностью предотвращает непреднамеренную блокировку при маршрутизации.


Обновлять:

Как предложил @rahilb

  detach() {
    get {...} ..
 }

и вызов как:

 val responses = (0 until 10)
    .map { _ => (IO(Http) ? HttpRequest(GET, s"${TEST_BASE_URL}work1")).mapTo[HttpResponse] }
    .map { response => Await.result(response, 5 seconds) }

... по-прежнему занимает около> 3 секунд для work1 или work2.


person user48956    schedule 10.11.2015    source источник


Ответы (1)


На самом деле, даже ваш маршрут work2 может привести к истощению HTTP-актора, поскольку ExecutionContext, используемый в Future.apply, является system.dispatcher, то есть контекстом спрея HttpServiceActor. Мы можем предоставить другой ExecutionContext для долгосрочных фьючерсов, чтобы не рисковать голоданием спрея.

Однако, чтобы ответить на ваш вопрос, существует директива под названием detach , который будет запускать остальную часть маршрута в некотором ExecutionContext, потенциально оставляя больше ресурсов свободными для входящих запросов... но, поскольку это директива, разветвление происходит только после попадания по маршруту.

person rahilb    schedule 10.11.2015
comment
Казалось, это помогло, но тем не менее, если я выполняю 10 запросов, общее количество занимает 3 секунды (при вызове work1 или work2). Обновил вопрос с моей работой. - person user48956; 10.11.2015