wai-thrift-0.0.1.2: src/Thrift/Transport/Wai.hs
{-# LANGUAGE FlexibleInstances #-}
{-|
Module : Thrift.Transport.Wai
Description : Wai support for thrift transport
License : MIT
Maintainer : Yogesh Sajanikar <yogesh_sajanikar@yahoo.com>
Stability : experimental
Portability : POSIX, WINDOWS
Support thrift transport for Wai Request and Response.
-}
module Thrift.Transport.Wai (
-- * Request Transport
RequestTransport,
fromRequest,
-- * Stream Transport for response
StreamTransport,
toStreamTransport,
-- * Wai compatible application and middleware
thriftStreamingBody,
thriftApp,
thriftMiddleware
) where
import Thrift.Transport
import Thrift.Protocol
import Thrift.Transport.IOBuffer
import Network.Wai as Wai
import Network.Wai.Internal
import Data.IORef
import Blaze.ByteString.Builder
import Data.Monoid
import Network.HTTP.Types (status200)
import Network.HTTP.Types.Method
-- | Transport layer based on 'Request'
-- This is a readonly transport layer. Write operations will fail.
data RequestTransport = RequestTransport Request ReadBuffer
-- | Creates RequestTransport from WAI request
-- Initilizes RequestTransport with a lazy request body from request
fromRequest :: Request -> IO RequestTransport
fromRequest req = RequestTransport
<$> return req
<*> (Wai.lazyRequestBody req >>= newIORef)
instance Transport RequestTransport where
tIsOpen = const $ return True
tClose = const $ return ()
tRead (RequestTransport _ b) n = readBuf b (fromIntegral n)
tPeek (RequestTransport _ b) = peekBuf b
tWrite _ _ = fail "RequestTransport does not support write"
tFlush _ = fail "RequestTransport does not support flush"
-- | StreamTransport is write-only transport layer for thrift 'Transport'
data StreamTransport = StreamTransport { writer :: Builder -> IO () -- ^ to append response to the builder
, flusher :: IO () -- ^ to flush the response to IO
}
-- | Create 'StreamTransport' from two parts, builder creating the chunk
-- and flush, to flush the chunk to response stream. This is very similar
-- to 'StreamingBody'
toStreamTransport :: (Builder -> IO () ) -- ^ Builder for building the chunks
-> IO () -- ^ Flush the content to the response stream
-> StreamTransport -- ^ 'StreamTransport' for Wai
toStreamTransport = StreamTransport
instance Transport StreamTransport where
tIsOpen = const $ return True
tClose = const $ return ()
tRead _ _ = fail "Read operation is not supported for response"
tPeek _ = fail "Peek is not allowed for response buffers"
tWrite st bs = writer st $ fromLazyByteString bs
tFlush = flusher
-- | Creates a streaming body that processes the request, and responds by
-- calling thrift handler. It uses 'RequestTransport' for processing the thrift
-- input, and 'StreamTransport' for responding through 'StreamingBody'
--
-- This is a very useful function, and is in fact used implement 'thriftWaiApp'
-- and 'thriftMiddleware'
--
-- For example one can use 'thriftStreamingBody' with scotty as
--
-- >
-- > import qualified Greeting as G -- Thrift generated code
-- >
-- > thriftStream :: ActionM ()
-- > thriftStream = do
-- > req <- request
-- > stream $ thriftStreamingBody G.GreetData JSONProtocol JSONProtocol G.process
-- >
thriftStreamingBody ::
(Protocol ip, Protocol op)
=> h -- ^ Type supporting thrift generated interface
-> (RequestTransport -> ip RequestTransport) -- ^ Input protocol selector for 'RequestTransport'
-> (StreamTransport -> op StreamTransport) -- ^ Output protocol selector for 'StreamTransport'
-> (h -> (ip RequestTransport, op StreamTransport) -> IO Bool) -- ^ Thrift request handler
-> Request -- ^ Wai request, to be embedded in 'RequestTransport'
-> StreamingBody -- ^ Wai 'StreamingBody'
thriftStreamingBody h isp osp proc_ req write flushstream = do
inp <- isp <$> fromRequest req
let out = osp (StreamTransport write flushstream)
_ <- proc_ h (inp, out)
return ()
-- | Wai compatible application.
-- This does not add the necessary headers.
-- This does not add necessary headers for allowing cross origin requests
thriftApp ::
(Protocol ip, Protocol op)
=> h -- ^ Type supporting thrift generated interface
-> (RequestTransport -> ip RequestTransport) -- ^ Input protocol selector for 'RequestTransport'
-> (StreamTransport -> op StreamTransport) -- ^ Output protocol selector for 'StreamTransport'
-> (h -> (ip RequestTransport, op StreamTransport) -> IO Bool) -- ^ Thrift request handler
-> Application -- ^ Wai application
thriftApp h isp osp proc_ req responder =
responder $ Wai.responseStream status200 [] $ thriftStreamingBody h isp osp proc_ req
-- | Creates Wai middleware for the given handler
-- This does not add necessary headers for allowing cross origin requests
thriftMiddleware :: (Protocol ip, Protocol op)
=> h -- ^ Type supporting thrift generated interface
-> (RequestTransport -> ip RequestTransport) -- ^ Input protocol selector for 'RequestTransport'
-> (StreamTransport -> op StreamTransport) -- ^ Output protocol selector for 'StreamTransport'
-> (h -> (ip RequestTransport, op StreamTransport) -> IO Bool) -- ^ Thrift request handler
-> Middleware -- ^ Wai middleware
thriftMiddleware h isp osp proc_ app req responder = app req $ \res ->
case res of
ResponseStream {} ->
if methodPost == requestMethod req then
responder
$ Wai.responseStream status200 (responseHeaders res)
$ thriftStreamingBody h isp osp proc_ req
else
responder res
_ -> responder res