packages feed

thrift-http-0.3.0.0: Thrift/Channel/HTTP.hs

{-
  Copyright (c) Meta Platforms, Inc. and affiliates.
  All rights reserved.

  This source code is licensed under the BSD-style license found in the
  LICENSE file in the root directory of this source tree.
-}

module Thrift.Channel.HTTP (
    HTTPConfig(..),
    withHTTPChannel,
    withHTTPChannelIO,
  ) where

import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as LBS
import Data.Proxy
import qualified Data.Text.Encoding as Text
import Network.HTTP.Client hiding (Proxy)
import Network.HTTP.Client.TLS
import Network.HTTP.Types

import Thrift.Channel
import Thrift.Monad
import Thrift.Protocol
import Thrift.Protocol.Id

data HTTPChannel s = HTTPChannel
  { httpConfig :: HTTPConfig s
  , httpManager :: Manager
  }

data HTTPConfig s = HTTPConfig
  { httpHost :: ByteString
  , httpPort :: Int
  , httpProtocolId :: ProtocolId
  , httpUseHttps :: Bool
  , httpResponseTimeout :: Maybe Int -- ^ microseconds
  }
  deriving Show

withHTTPChannel
    :: HTTPConfig t
    -> (forall p . Protocol p => ThriftM p HTTPChannel t a)
    -> IO a
withHTTPChannel config@HTTPConfig{..} action = do
  manager <- newTlsManagerWith tlsManagerSettings {
    managerResponseTimeout =
      maybe responseTimeoutNone responseTimeoutMicro httpResponseTimeout }
  withProxy httpProtocolId $ \proxy ->
    runAction (HTTPChannel config manager) action proxy
  where
    runAction
      :: Protocol p
      => HTTPChannel t
      -> ThriftM p HTTPChannel t a
      -> Proxy p
      -> IO a
    runAction c a _ = runThrift a c

withHTTPChannelIO
    :: HTTPConfig t
    -> (forall p . Protocol p => HTTPChannel t -> Proxy p -> IO a)
    -> IO a
withHTTPChannelIO config@HTTPConfig{..} action = do
  manager <- newTlsManagerWith tlsManagerSettings
  withProxy httpProtocolId $ \proxy ->
    action (HTTPChannel config manager) proxy

instance ClientChannel HTTPChannel where
  sendRequest = httpRequest
  sendOnewayRequest chan req sendCb = httpRequest chan req sendCb (\_ -> return ())

httpRequest
  :: HTTPChannel t
  -> Thrift.Channel.Request
  -> SendCallback
  -> RecvCallback
  -> IO ()
httpRequest HTTPChannel{..} Request{..} sendCb recvCb = do
    let !prot = httpProtocolId httpConfig
    let request = defaultRequest
          { host = httpHost httpConfig
          , port = httpPort httpConfig
          , method = "POST"
          , secure = httpUseHttps httpConfig
          , requestHeaders =
              [ (hContentType, if
                  | prot == binaryProtocolId ->
                      "application/x-thrift-binary"
                  | prot == compactProtocolId ->
                      "application/x-thrift-compact"
                  | otherwise -> -- later: JSON
                      "application/x-thrift-binary") ]
          , requestBody = RequestBodyBS reqMsg
          }
          -- TODO: rpcOpts
    response <- httpLbs request httpManager
    sendCb Nothing -- TODO?
    let s = responseStatus response
    if s == status200 then
      recvCb $ Right Response
        { respMsg = LBS.toStrict (responseBody response)
        , respHeader = [] }
    else
      recvCb (Left (ChannelException (Text.decodeUtf8 (statusMessage s))))