Как можно безопасно обрабатывать запросы маршрутизации одновременно в 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"
)
}
}
}
}
}
}
** поскольку маршрутизация, как правило, является операцией без сохранения состояния, кажется, не выгодно создавать маршрутизатор и Actor, верно?
Для любого другого веб-сервера, который я использовал, более разумно разветвление управления соединением с процессом-обработчиком или потоком (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 секунд либо для работы1 или работы2.
1 ответ
На самом деле даже ваш work2
Маршрут может привести к истощению актера HTTP, так как ExecutionContext
используется в Future.apply
является system.dispatcher
т. е. спрей HttpServiceActor
контекст. Мы можем предоставить другой ExecutionContext для долгосрочных фьючерсов, чтобы не рисковать голоданием спрея.
Чтобы ответить на ваш вопрос, есть директива под названием detach
он будет выполнять оставшуюся часть маршрута в некотором ExecutionContext, потенциально оставляя больше ресурсов свободным для входящих запросов... но так как это директива, разветвление происходит только после того, как маршрут достигнут.