{-# LANGUAGE FlexibleInstances #-} {-| Module : Thrift.Transport.Wai Description : Wai support for thrift transport License : MIT Maintainer : Yogesh Sajanikar 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