Вместо одного длительного GET-запроса, я бы, возможно, настроил конечную точку, принимающую POST-запросы. POST будет немедленно возвращен с двумя ссылками в теле ответа:
одна ссылка на новый ресурс, представляющий результат задачи, который не будет немедленно доступен. До этого GET-запросы к результату могли возвращать 409 (Конфликт) .
одну ссылку на связанный, немедленно доступный ресурс, представляющий уведомления, испускаемые при выполнении задачи.
Как только клиент успешно выполнит GET ресурса результата задачи, он может УДАЛИТЬ его. Это должно удалить как ресурс результата задачи, так и связанный ресурс уведомления.
Для каждого запроса POST вам потребуется порождать фоновый рабочий поток . Вам также потребуется фоновый поток для удаления устаревших результатов задачи (поскольку клиенты могут быть ленивыми и не вызывать DELETE). Эти потоки будут взаимодействовать с MVar
s , TVar
s , каналами или аналогичными методами .
Теперь вопрос: как лучше всего обрабатывать уведомления, отправляемые сервером? Есть несколько опций :
- Просто периодически опрашивайте ресурс уведомления от клиента. Недостатки: потенциально много HTTP-запросов, уведомления не принимаются быстро.
- длинный опрос . последовательность запросов GET, которые остаются открытыми до тех пор, пока сервер не захочет отправить какое-либо уведомление, или до истечения времени ожидания.
- события, отправленные сервером . wai-extra имеет поддержку для этого, но я не знаю, как подключить raw wai
Application
обратно к Скотти. - WebSockets . Не уверен, как интегрироваться со Скотти.
Вот серверный скелет длинного механизма опроса. Некоторые предварительные импорта:
{-# LANGUAGE NumDecimals #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (concurrently_) -- from async
import Control.Concurrent.STM -- from stm
import Control.Concurrent.STM.TMChan -- from stm-chans
import Control.Monad.IO.Class (liftIO)
import Data.Aeson (ToJSON) -- from aeson
import Data.Foldable (for_)
import Data.Text (Text)
import Web.Scotty
А вот и основной код.
main :: IO ()
main =
do
chan <- atomically $ newTMChan @Text
concurrently_
( do
for_
["starting", "working on it", "finishing"]
( \msg -> do
threadDelay 10e6
atomically $ writeTMChan chan msg
)
atomically $ closeTMChan chan
)
( scotty 3000
$ get "/notifications"
$ do
mmsg <- liftIO $ atomically $ readTMChan chan
json $
case mmsg of
Nothing -> ["closed!"]
Just msg -> [msg]
)
Существует два одновременных потока. Один передает сообщения в закрываемый канал с 10-секундными интервалами , другой запускает сервер Scotty, где каждый вызов GET зависает, пока новое сообщение не поступит в канал.
Тестируя его с bash, используя curl , мы должны увидеть последовательность сообщений:
bash$ for run in {1..4}; do curl -s localhost:3000/notifications ; done
["starting"]["working on it"]["finishing"]["closed!"]