2019-05-17 04:25:58 +03:00
|
|
|
{-
|
|
|
|
- TODO When making a request, handle the case where the request id is
|
|
|
|
already in use.
|
|
|
|
- TODO When canceling a request, don't send Http.Canceled if the
|
|
|
|
request already finished.
|
|
|
|
-}
|
2019-05-16 03:00:10 +03:00
|
|
|
|
|
|
|
module Vere.Http.Client where
|
|
|
|
|
|
|
|
import ClassyPrelude
|
2019-05-17 03:05:34 +03:00
|
|
|
import Data.Void
|
2019-05-17 04:25:58 +03:00
|
|
|
import Vere.Http as Http
|
2019-05-17 03:05:34 +03:00
|
|
|
import Control.Concurrent hiding (newEmptyMVar, putMVar)
|
|
|
|
|
2019-05-17 04:25:58 +03:00
|
|
|
import qualified Network.HTTP.Client as H
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
type ReqId = Word
|
|
|
|
|
|
|
|
data Ev = Receive ReqId Http.Event -- %receive
|
2019-05-16 03:00:10 +03:00
|
|
|
|
|
|
|
data Eff
|
2019-05-17 04:25:58 +03:00
|
|
|
= NewReq ReqId Request -- %request
|
|
|
|
| CancelReq ReqId -- %cancel-request
|
|
|
|
|
|
|
|
data State = State
|
|
|
|
{ sManager :: H.Manager
|
|
|
|
, sLive :: TVar (Map ReqId ThreadId)
|
|
|
|
, sChan :: MVar Ev
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
cvtReq :: Request -> H.Request
|
|
|
|
cvtReq = undefined
|
|
|
|
|
|
|
|
cvtRespHeaders :: H.Response a -> ResponseHeader
|
|
|
|
cvtRespHeaders resp = undefined
|
2019-05-17 03:05:34 +03:00
|
|
|
|
|
|
|
|
2019-05-17 04:25:58 +03:00
|
|
|
--------------------------------------------------------------------------------
|
2019-05-17 03:05:34 +03:00
|
|
|
|
|
|
|
initState :: IO State
|
2019-05-17 04:25:58 +03:00
|
|
|
initState = State <$> H.newManager H.defaultManagerSettings
|
|
|
|
<*> newTVarIO mempty
|
|
|
|
<*> newEmptyMVar
|
2019-05-17 03:05:34 +03:00
|
|
|
|
2019-05-17 04:25:58 +03:00
|
|
|
emit :: State -> Ev -> IO ()
|
|
|
|
emit (State _ _ chan) event = putMVar chan event
|
2019-05-17 03:05:34 +03:00
|
|
|
|
2019-05-17 04:25:58 +03:00
|
|
|
runEff :: State -> Eff -> IO ()
|
|
|
|
runEff st@(State _ s _) = \case CancelReq id -> cancelReq st id
|
|
|
|
NewReq id req -> newReq st id req
|
2019-05-17 03:05:34 +03:00
|
|
|
|
2019-05-17 04:25:58 +03:00
|
|
|
newReq :: State -> ReqId -> Request -> IO ()
|
|
|
|
newReq st id req = do tid <- runReq st id req
|
|
|
|
atomically $ modifyTVar (sLive st) (insertMap id tid)
|
2019-05-17 03:05:34 +03:00
|
|
|
|
2019-05-17 04:25:58 +03:00
|
|
|
cancelReq :: State -> ReqId -> IO ()
|
|
|
|
cancelReq st id =
|
2019-05-17 03:05:34 +03:00
|
|
|
join $ atomically $ do
|
2019-05-17 04:25:58 +03:00
|
|
|
tbl <- readTVar (sLive st)
|
|
|
|
case lookup id tbl of
|
|
|
|
Nothing -> pure (pure ())
|
|
|
|
Just tid -> do
|
|
|
|
writeTVar (sLive st) (deleteMap id tbl)
|
|
|
|
pure $ do killThread tid
|
|
|
|
emit st (Receive id Canceled)
|
|
|
|
|
|
|
|
runReq :: State -> ReqId -> Request -> IO ThreadId
|
|
|
|
runReq st id request =
|
|
|
|
forkIO $ H.withResponse (cvtReq request) (sManager st) $ \resp -> do
|
|
|
|
let headers = cvtRespHeaders resp
|
|
|
|
let getChunk = recv (H.responseBody resp)
|
|
|
|
let loop = getChunk >>= \case
|
|
|
|
Just bs -> emit st (Receive id $ Received bs) >> loop
|
|
|
|
Nothing -> emit st (Receive id Done)
|
|
|
|
emit st (Receive id $ Started headers)
|
|
|
|
loop
|
2019-05-17 03:05:34 +03:00
|
|
|
where
|
2019-05-17 04:25:58 +03:00
|
|
|
recv :: H.BodyReader -> IO (Maybe ByteString)
|
|
|
|
recv read = read <&> \case chunk | null chunk -> Nothing
|
|
|
|
| otherwise -> Just chunk
|