btc-lsp-0.1.0.0: src/BtcLsp/Grpc/Server/LowLevel.hs
--
-- NOTE : requestBody is deprecated
-- but we need it.
--
{-# OPTIONS_GHC -Wno-deprecations #-}
module BtcLsp.Grpc.Server.LowLevel
( GSEnv (..),
runServer,
serverApp,
)
where
import BtcLsp.Grpc.Data
import qualified BtcLsp.Grpc.Sig as Sig
import BtcLsp.Import.External
import Control.Concurrent (modifyMVar)
import Data.Aeson (withObject, (.:), (.:?))
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Lazy as BSL
import qualified Data.CaseInsensitive as CI
import qualified Data.Text.Encoding as TE
import Network.GRPC.HTTP2.Encoding (gzip)
import Network.GRPC.Server
import Network.HTTP2.Server hiding (Request)
import Network.Wai
import Network.Wai.Handler.Warp as Warp
import Network.Wai.Handler.WarpTLS (runTLS, tlsSettingsMemory)
data GSEnv = GSEnv
{ gsEnvPort :: Int,
gsEnvSigVerify :: Bool,
gsEnvSigHeaderName :: SigHeaderName,
gsEnvEncryption :: Encryption,
gsEnvTls :: Maybe (TlsData 'Server),
gsEnvLogger :: Text -> IO (),
gsEnvSigner :: Sig.MsgToSign -> IO (Maybe Sig.LndSig)
}
deriving stock (Generic)
instance FromJSON GSEnv where
parseJSON =
withObject
"GSEnv"
( \x ->
GSEnv
<$> x .: "port"
<*> x .: "sig_verify"
<*> x .: "sig_header_name"
<*> x .: "encryption"
<*> x .:? "tls"
<*> pure (const $ pure ())
<*> pure (const $ pure Nothing)
)
runServer ::
GSEnv ->
(GSEnv -> RawRequestBytes -> [ServiceHandler]) ->
IO ()
runServer env handlers =
case (gsEnvEncryption env, gsEnvTls env) of
(Encrypted, Just tls) ->
runTLS
( tlsSettingsMemory
(TE.encodeUtf8 . coerce $ tlsCert tls)
(TE.encodeUtf8 . coerce $ tlsKey tls)
)
(setPort port defaultSettings)
(Encrypted, Nothing) ->
error $
"Fatal error - can not run LSP gRPC endpoint"
<> " over TLS unless TlsData is provided!"
(UnEncrypted, _) ->
Warp.run port
$ if gsEnvSigVerify env
then extractBodyBytesMiddleware env $ serverApp handlers
else serverApp handlers env (RawRequestBytes mempty)
where
port = gsEnvPort env
serverApp ::
(GSEnv -> RawRequestBytes -> [ServiceHandler]) ->
GSEnv ->
RawRequestBytes ->
Application
serverApp handlers env body req rep = do
let app = grpcApp [gzip] $ handlers env body
app req middleware
where
sigHeaderName =
from $ gsEnvSigHeaderName env
middleware res = do
modifyHTTP2Data req $ \http2data0 ->
let http2data = fromMaybe defaultHTTP2Data http2data0
in Just $
http2data
{ http2dataTrailers =
trailersMaker
mempty
(http2dataTrailers http2data)
}
rep $
mapResponseHeaders
(\hs -> ("trailer", sigHeaderName) : hs)
res
trailersMaker acc oldMaker Nothing = do
ts <- oldMaker Nothing
case ts of
Trailers ss -> do
mSig <- gsEnvSigner env $ Sig.MsgToSign acc
pure $ case mSig of
Nothing ->
ts
Just sig ->
Trailers $
( CI.mk sigHeaderName,
B64.encode $ Sig.unLndSig sig
) :
ss
NextTrailersMaker {} ->
throwIO $
GRPCStatus
INTERNAL
"UNEXPECTED_NEW_TRAILERS_MAKER"
trailersMaker acc oldMaker (Just bs) = do
pure
. NextTrailersMaker
$ trailersMaker (acc <> bs) oldMaker
extractBodyBytesMiddleware ::
GSEnv ->
(GSEnv -> RawRequestBytes -> Application) ->
Application
extractBodyBytesMiddleware env app req resp = do
body <- BSL.toStrict <$> strictRequestBody req
gsEnvLogger env $
"Server ==> extracted raw request body"
<> inspect body
body' <- newMVar body
app env (RawRequestBytes body) (req' body') resp
where
requestBody' mvar =
modifyMVar
mvar
( \b ->
pure $
if b == mempty
then (mempty, mempty)
else (mempty, b)
)
req' b =
req
{ requestBody = requestBody' b
}