mirror of https://github.com/voidlizard/hbs2
195 lines
6.2 KiB
Haskell
195 lines
6.2 KiB
Haskell
{-# LANGUAGE OverloadedLabels #-}
|
||
{-# LANGUAGE StrictData #-}
|
||
{-# LANGUAGE ImpredicativeTypes #-}
|
||
module HBS2.Net.Dialog.Client where
|
||
|
||
-- import System.Clock
|
||
-- import System.Timeout
|
||
import Codec.Serialise
|
||
import Control.Arrow
|
||
import Control.Exception qualified as Exception
|
||
import Control.Monad
|
||
import Control.Monad.Cont
|
||
import Control.Monad.Error.Class
|
||
import Control.Monad.Except (ExceptT(..), runExcept, runExceptT)
|
||
import Control.Monad.IO.Unlift
|
||
import Control.Monad.State.Class as State
|
||
import Control.Monad.State.Strict (evalState, evalStateT)
|
||
import Data.ByteString (ByteString)
|
||
import Data.ByteString qualified as BS
|
||
import Data.ByteString.Builder
|
||
import Data.ByteString.Lazy qualified as BSL
|
||
import Data.Default
|
||
import Data.Generics.Labels
|
||
import Data.Generics.Product.Fields
|
||
import Data.List qualified as List
|
||
import Data.Map.Strict as Map
|
||
import Data.String.Conversions (cs, ConvertibleStrings)
|
||
import Data.Time
|
||
import GHC.Generics(Generic)
|
||
import Lens.Micro.Platform
|
||
import Streaming as S
|
||
import Streaming.Prelude qualified as S
|
||
import UnliftIO.Exception
|
||
import UnliftIO.STM
|
||
import UnliftIO.Timeout
|
||
|
||
import HBS2.Net.Dialog.Core
|
||
import HBS2.Net.Dialog.Helpers.Streaming
|
||
|
||
---
|
||
|
||
dQuery_ :: MonadUnliftIO m
|
||
=> RequestParams
|
||
-> DialogClient m peer
|
||
-> peer
|
||
-> Frames
|
||
-> m ()
|
||
dQuery_ _par dcli peer rq =
|
||
withClientQuery dcli & \dialf ->
|
||
dialf peer rq' \_flow -> pure ()
|
||
where
|
||
rq' = rq & #unFrames %~ ([serialiseS routerSignature] <>)
|
||
|
||
--
|
||
dQuery1 :: (MonadUnliftIO m)
|
||
=> RequestParams
|
||
-> DialogClient m peer
|
||
-> peer
|
||
-> Frames
|
||
-> m Frames
|
||
|
||
dQuery1 par dcli peer rq = dQuery' par dcli peer rq \flow ->
|
||
either (throwIO . DQuery1Error) (pure . view _2) =<< headEither flow
|
||
|
||
data DQuery1Error = DQuery1Error RequestResult
|
||
deriving (Show)
|
||
|
||
instance Exception DQuery1Error
|
||
|
||
--
|
||
dQuery' :: MonadUnliftIO m
|
||
=> RequestParams
|
||
-> DialogClient m peer
|
||
-> peer
|
||
-> Frames
|
||
-> (Stream (Of (ResponseHeader, Frames)) m RequestResult -> m r)
|
||
-> m r
|
||
|
||
dQuery' par dcli peer rq go =
|
||
withClientQuery dcli & \dialf -> do
|
||
dialf peer rq' \flow -> go $
|
||
flow
|
||
& withEffectsMay RequestTimeout (timeout' (requestParamsTimeout par))
|
||
& S.map decodeHeader
|
||
& stopAfterLeftMay (either
|
||
(\(merr, xs) -> Left (Nothing, RequestErrorBadResponse merr xs))
|
||
processResponseHeader
|
||
)
|
||
|
||
where
|
||
|
||
processResponseHeader :: (ResponseHeader, Frames) ->
|
||
Either
|
||
(Maybe (ResponseHeader, Frames), RequestResult)
|
||
(ResponseHeader, Frames)
|
||
|
||
processResponseHeader rhxs@(rh, xs) = case ((responseStatusCode . respStatus) rh) of
|
||
Success200 -> Left (Just rhxs, RequestDone)
|
||
SuccessNoContent204 -> Left (Just rhxs, RequestDone)
|
||
SuccessMore -> Right rhxs
|
||
BadRequest400 -> Left (Nothing, (RequestFailure (respStatus rh) xs))
|
||
Forbidden403 -> Left (Nothing, (RequestFailure (respStatus rh) xs))
|
||
NotFound404 -> Left (Nothing, (RequestFailure (respStatus rh) xs))
|
||
|
||
rq' = rq & #unFrames %~ ([serialiseS routerSignature] <>)
|
||
|
||
timeout' :: MonadUnliftIO m => NominalDiffTime -> m a -> m (Maybe a)
|
||
timeout' = timeout . round . (* 10^6) . nominalDiffTimeToSeconds
|
||
|
||
--
|
||
decodeHeader :: Frames -> Either (BadResponse, Frames) (ResponseHeader, Frames)
|
||
decodeHeader = evalState do
|
||
ex <- runExceptT cutFrameDecode'
|
||
xs <- State.get
|
||
pure $ ex
|
||
& left ((, xs) . maybe ResponseInsufficientFrames ResponseParseError)
|
||
& right (, xs)
|
||
|
||
data RequestParams = RequestParams
|
||
{ requestParamsTimeout :: NominalDiffTime
|
||
}
|
||
deriving (Generic)
|
||
|
||
instance Default RequestParams where
|
||
def = RequestParams
|
||
{ requestParamsTimeout = 5
|
||
}
|
||
|
||
data DialogClient m p = DialogClient
|
||
{ withClientQuery :: ClientQuery m p
|
||
}
|
||
|
||
type ClientQuery m p = forall r .
|
||
p
|
||
-> Frames
|
||
-> (Stream (Of Frames) m RequestResult -> m r)
|
||
-> m r
|
||
|
||
withClient :: forall m p i r . MonadUnliftIO m
|
||
=> DClient m p i -> (DialogClient m p -> m r) -> m r
|
||
withClient dclient go = do
|
||
callerID <- newCallerID
|
||
|
||
requestIDtvar <- newTVarIO 1
|
||
|
||
-- У обработчика получателя - своё окружение, в которое мы добавляем
|
||
-- обработчики ответов на запросы по requestID
|
||
requestResponseEnv <- newRequestResponseEnv
|
||
|
||
let withClientQuery' :: ClientQuery m p
|
||
withClientQuery' = \pid xs handleStream -> do
|
||
requestID <- atomically $ stateTVar requestIDtvar (id &&& succ)
|
||
|
||
ch <- newTQueueIO
|
||
let useResponse = RequestResponseHandler @m do
|
||
atomically . writeTQueue ch
|
||
let
|
||
-- flow :: Stream (Of Frames) m RequestResult
|
||
flow = S.repeatM (atomically (readTQueue ch))
|
||
|
||
bracket_
|
||
(setupRepHandler requestResponseEnv requestID useResponse)
|
||
(clearRepHandler requestResponseEnv requestID)
|
||
(do
|
||
|
||
clientSendProtoRequest dclient pid do
|
||
xs & addEnvelope
|
||
[ (BSL.toStrict . serialise) callerID
|
||
, (BSL.toStrict . serialise) requestID
|
||
]
|
||
|
||
handleStream flow
|
||
)
|
||
|
||
-- Установить в окружении обработчик получателя с callerID
|
||
let callerHandler = CallerHandler $ unFrames >>> \case
|
||
|
||
requestIDRaw:xs -> do
|
||
case deserialiseOrFail (BSL.fromStrict requestIDRaw) of
|
||
Left _ ->
|
||
-- Если не нашли, ничего не предпринимать
|
||
-- На этот вопрос уже не ждут ответа
|
||
pure ()
|
||
Right requestID -> do
|
||
mh <- findRepHandler requestResponseEnv requestID
|
||
forM_ mh \(RequestResponseHandler h) -> h (Frames xs)
|
||
|
||
_ -> pure ()
|
||
|
||
bracket_
|
||
(setupCallerEnv (clientCallerEnv dclient) callerID callerHandler)
|
||
(clearCallerEnv (clientCallerEnv dclient) callerID)
|
||
(go (DialogClient {withClientQuery = withClientQuery'}))
|
||
|