module Wobsurv.Response where
import BasePrelude hiding (bracket, for, yield, head)
import Pipes
import Pipes.Safe
import Control.Monad.Trans.Reader
import qualified Wobsurv.Util.HTTP.Renderer as ProtocolRenderer
import qualified Wobsurv.Util.HTTP.Model as Protocol
import qualified Wobsurv.Util.HTTP.URLEncoding as URLEncoding
import qualified Wobsurv.Util.Mustache.Renderer as TemplatesRenderer
import qualified Wobsurv.TemplateModels.NotFound as NotFound
import qualified Wobsurv.TemplateModels.Index as Index
import qualified Pipes.ByteString
import qualified Pipes.Text
import qualified Data.ByteString
import qualified Data.ByteString.Builder
import qualified Data.Text
import qualified Data.Text.Encoding
import qualified Data.Text.Lazy
import qualified Data.HashMap.Strict
import qualified Filesystem.Path as Path
import qualified Filesystem.Path.CurrentOS as Path.CurrentOS
import qualified Filesystem.Path.Rules as Path.Rules
import qualified Filesystem
type BS =
Data.ByteString.ByteString
type FilePath =
Path.CurrentOS.FilePath
type Text =
Data.Text.Text
type LazyText =
Data.Text.Lazy.Text
type KeepAliveTimeout =
Word
type MimeMappings =
Data.HashMap.Strict.HashMap Text BS
type Env =
TemplatesRenderer.Renderer
type Response r =
ReaderT Env (Producer BS (SafeT IO)) r
runInProducer :: Response a -> Env -> Producer BS (SafeT IO) a
runInProducer =
runReaderT
serviceUnavailable :: Response ()
serviceUnavailable =
do
statusLine Protocol.serviceUnavailable
connectionHeader False
contentTypeHeader ("text/html", Just Protocol.UTF8)
newLine
template "service-unavailable" ()
badRequest :: Response ()
badRequest =
do
statusLine Protocol.badRequest
connectionHeader False
contentTypeHeader ("text/html", Just Protocol.UTF8)
newLine
template "bad-request" ()
notImplemented :: Response ()
notImplemented =
do
statusLine Protocol.notImplemented
connectionHeader False
contentTypeHeader ("text/html", Just Protocol.UTF8)
newLine
template "not-implemented" ()
entityTooLarge :: Response ()
entityTooLarge =
do
statusLine Protocol.entityTooLarge
connectionHeader False
contentTypeHeader ("text/html", Just Protocol.UTF8)
newLine
template "entity-too-large" ()
notFound :: Protocol.RelativeURI -> Response ()
notFound uri =
do
statusLine Protocol.notFound
connectionHeader False
contentTypeHeader ("text/html", Just Protocol.UTF8)
newLine
template "not-found" $
NotFound.NotFound {
NotFound.uri =
URLEncoding.toText $ ProtocolRenderer.toByteString $ ProtocolRenderer.relativeURI uri
}
okFile :: FilePath -> Maybe KeepAliveTimeout -> MimeMappings -> Response ()
okFile path keepAliveTimeout mimeMappings =
do
statusLine Protocol.ok
case keepAliveTimeout of
Nothing -> do
connectionHeader False
Just v -> do
connectionHeader True
keepAliveHeader (v, Nothing)
forM_ contentType $ \x -> contentTypeHeader (x, Nothing)
newLine
file path
where
contentType =
Path.extension path >>= \e -> Data.HashMap.Strict.lookup e mimeMappings
okIndex :: FilePath -> FilePath -> Maybe KeepAliveTimeout -> Response ()
okIndex uriPath path keepAliveTimeout =
do
statusLine Protocol.ok
case keepAliveTimeout of
Nothing -> do
connectionHeader False
Just v -> do
connectionHeader True
keepAliveHeader (v, Nothing)
contentTypeHeader ("text/html", Just Protocol.UTF8)
newLine
contents <- do
files <- do
paths <- liftIO $ Filesystem.listDirectory path
return $ map Path.filename paths
if publicPath /= "/"
then return $ ".." : files
else return files
template "index" $ Index.Index (pathRepr publicPath) (map pathRepr contents)
where
publicPath =
Path.parent $ "/" <> uriPath <> "./"
pathRepr =
fromString . Path.CurrentOS.encodeString
contentTypeHeader :: Protocol.ContentTypeHeader -> Response ()
contentTypeHeader =
liftBSBuilder . ProtocolRenderer.contentTypeHeader
connectionHeader :: Protocol.ConnectionHeader -> Response ()
connectionHeader =
liftBSBuilder . ProtocolRenderer.connectionHeader
keepAliveHeader :: Protocol.KeepAliveHeader -> Response ()
keepAliveHeader =
liftBSBuilder . ProtocolRenderer.keepAliveHeader
contentLengthHeader :: Protocol.ContentLengthHeader -> Response ()
contentLengthHeader =
liftBSBuilder . ProtocolRenderer.contentLengthHeader
newLine :: Response ()
newLine =
liftBSBuilder ProtocolRenderer.newLine
template :: (Data model) => Text -> model -> Response ()
template name model =
do
templatesRenderer <- ask
traverse_ lazyText $
TemplatesRenderer.render model name templatesRenderer
lazyText :: LazyText -> Response ()
lazyText t =
lift $ for (Pipes.Text.fromLazy t) (yield . Data.Text.Encoding.encodeUtf8)
file :: FilePath -> Response ()
file path =
lift $
bracket
(liftIO $ Filesystem.openFile path Filesystem.ReadMode)
(liftIO . hClose)
Pipes.ByteString.fromHandle
statusLine :: Protocol.Status -> Response ()
statusLine status =
liftBSBuilder $ ProtocolRenderer.statusLine (1, 1) status
liftBSBuilder :: Data.ByteString.Builder.Builder -> Response ()
liftBSBuilder =
lift . Pipes.ByteString.fromLazy . Data.ByteString.Builder.toLazyByteString