1 {-# LANGUAGE TypeSynonymInstances, DeriveDataTypeable, OverloadedStrings #-} 2 module Action.Response 3 ( Response 4 , ResponseData(..) 5 , emptyResponse 6 , okResponse 7 , result 8 , unsafeResult 9 , runResult 10 , proxyResponse 11 ) where 12 13 import Conduit (Source, (.|)) 14 import Control.Exception (Exception, throwIO, throw, handle) 15 import Control.Monad (join) 16 import Control.Monad.IO.Class (MonadIO, liftIO) 17 import Data.Typeable (Typeable) 18 import Network.HTTP.Types (ResponseHeaders, Status, ok200, hContentType) 19 import Network.Wai 20 ( Response 21 , responseBuilder 22 , responseLBS 23 , StreamingBody 24 , responseStream 25 , FilePart(..) 26 , responseFile 27 , responseStatus 28 ) 29 import System.Posix.Types (FileOffset) 30 import qualified Conduit as CND 31 import qualified Data.Binary.Builder as DBB 32 import qualified Data.ByteString as BS 33 import qualified Data.ByteString.Builder as BSB 34 import qualified Data.ByteString.Lazy as BSL 35 import qualified Data.Text as T 36 import qualified Data.Text.Encoding as TE 37 import qualified Data.Text.Lazy as TL 38 import qualified Data.Text.Lazy.Encoding as TLE 39 import qualified Network.HTTP.Client as HC 40 import qualified Text.Blaze.Html as Html 41 import qualified Text.Blaze.Html.Renderer.Utf8 as Html 42 43 import qualified JSON 44 45 -- | This class captures Databrary's mechanism for creating 'Response's from the 46 -- values actually returned by handlers. 47 -- 48 -- It is rather general. A value can be turned into a response either by 49 -- modifying headers or by using one of Wai's Response composers (e.g. 50 -- 'responseBuilder'). There are some rather esoteric instances. 51 -- 52 -- Similar mechanisms in Servant or Yesod generally restrict themselves to 53 -- building response *data* out of return values, without explicit mention of 54 -- 'Response'. See e.g. 55 -- <http://hackage.haskell.org/package/http-api-data-0.3.8.1/docs/Web-HttpApiData.html> 56 -- 57 -- Servant also has 58 -- <http://hackage.haskell.org/package/servant-0.13.0.1/docs/Servant-API-ContentTypes.html#t:MimeRender MimeRender>, 59 -- which describes how to create a ByteString for use in creating Responses. 60 class ResponseData r where 61 response :: Status -> ResponseHeaders -> r -> Response 62 63 instance ResponseData BSB.Builder where 64 response = responseBuilder 65 66 instance ResponseData BSL.ByteString where 67 response = responseLBS 68 69 instance ResponseData BS.ByteString where 70 response s h = responseBuilder s h . BSB.byteString 71 72 instance ResponseData (Source (CND.ResourceT IO) BS.ByteString) where 73 response s h src = 74 responseStream s h 75 (\send _ -> 76 CND.runConduitRes 77 (src 78 .| CND.mapM_C 79 (CND.lift . send . DBB.fromByteString))) 80 81 instance ResponseData StreamingBody where 82 response = responseStream 83 84 instance ResponseData ((BS.ByteString -> IO ()) -> IO ()) where 85 response s h f = responseStream s h (\w l -> f (\b -> if BS.null b then l else w (BSB.byteString b))) 86 87 instance ResponseData (FilePath, Maybe FilePart) where 88 response s h (f, p) = responseFile s h f p 89 90 instance ResponseData (FilePath, FilePart) where 91 response s h (f, p) = response s h (f, Just p) 92 93 instance ResponseData (FilePath, Maybe FileOffset) where 94 response s h (f, z) = response s h (f, join (FilePart 0) . toInteger <$> z) 95 96 instance ResponseData String where 97 response s h = 98 response s ((hContentType, "text/plain;charset=utf-8") : h) . BSB.stringUtf8 99 100 instance ResponseData T.Text where 101 response s h = 102 response s ((hContentType, "text/plain;charset=utf-8") : h) . TE.encodeUtf8Builder 103 104 instance ResponseData TL.Text where 105 response s h = 106 response s ((hContentType, "text/plain;charset=utf-8") : h) . TLE.encodeUtf8Builder 107 108 instance ResponseData JSON.Value where 109 response s h = 110 response s ((hContentType, "application/json") : h) . JSON.encode 111 112 instance ResponseData JSON.Encoding where 113 response s h = 114 response s ((hContentType, "application/json") : h) . JSON.fromEncoding 115 116 instance ResponseData JSON.Series where 117 response s h = 118 response s h . JSON.pairs 119 120 instance (JSON.ToJSON k, JSON.ToObject o, ResponseData o) => ResponseData (JSON.Record k o) where 121 response s h = 122 response s h . JSON.recordObject 123 124 instance ResponseData Html.Html where 125 response s h = 126 response s ((hContentType, "text/html;charset=utf-8") : h) . Html.renderHtmlBuilder 127 128 emptyResponse :: Status -> ResponseHeaders -> Response 129 emptyResponse s h = response s h BS.empty 130 131 okResponse :: ResponseData r => ResponseHeaders -> r -> Response 132 okResponse = response ok200 133 134 -- | A wrapper for the short-circuiting machinery (see 'result'). 135 newtype Result = Result { resultResponse :: Response } deriving (Typeable) 136 instance Show Result where 137 showsPrec p (Result r) = showParen (p > 10) 138 $ showString "Result " . showsPrec 11 (responseStatus r) 139 instance Exception Result 140 141 -- | Short circuit immediately, returning the given Reponse. 142 -- 143 -- FIXME: Rather than implementing this using exceptions, could we either use a 144 -- left-biased Alternative or simply use a better procedural style? 145 result :: MonadIO m => Response -> m a 146 result = liftIO . throwIO . Result 147 148 -- | Short circuit from within non-monadic code. Not recommended, and hardly 149 -- ever used. 150 unsafeResult :: Response -> a 151 unsafeResult = throw . Result 152 153 -- | Run some action that may short circut using 'result' or 'unsafeResult'. 154 runResult :: IO Response -> IO Response 155 runResult = handle (return . resultResponse) 156 157 proxyResponse :: HC.Response BSL.ByteString -> Response 158 proxyResponse r = responseLBS 159 (HC.responseStatus r) 160 (filter ((/= "transfer-encoding") . fst) $ HC.responseHeaders r) 161 (HC.responseBody r)