{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Snap.Internal.Http.Types where
import Control.Monad (unless)
import Data.ByteString (ByteString)
import Data.ByteString.Builder (Builder, byteString, toLazyByteString)
import qualified Data.ByteString.Char8 as S
import qualified Data.ByteString.Lazy.Char8 as L
import Data.CaseInsensitive (CI)
import qualified Data.CaseInsensitive as CI
import qualified Data.IntMap as IM
import Data.List hiding (take)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (Maybe (..), fromMaybe, maybe)
import Data.Monoid (mconcat)
import Data.Time.Clock (UTCTime)
import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
import Data.Word (Word64)
import Foreign.C.Types (CTime (..))
import Prelude (Bool (..), Eq (..), FilePath, IO, Int, Integral (..), Monad (..), Num ((-)), Ord (..), Ordering (..), Read (..), Show (..), String, fmap, fromInteger, fromIntegral, id, not, otherwise, truncate, ($), (.))
#ifdef PORTABLE
import Prelude (realToFrac, ($!))
#endif
import System.IO (IOMode (ReadMode), SeekMode (AbsoluteSeek), hSeek, withBinaryFile)
import System.IO.Streams (InputStream, OutputStream)
import qualified System.IO.Streams as Streams
import System.IO.Unsafe (unsafePerformIO)
#ifdef PORTABLE
import Data.Time.Clock.POSIX
import Data.Time.Clock.POSIX
import Data.Time.Format as Time
import Data.Time.Locale.Compat (defaultTimeLocale)
import Data.Time.LocalTime
#else
import qualified Data.ByteString.Unsafe as S
import Data.Time.Format ()
import Foreign.C.String (CString)
import Foreign.Marshal.Alloc (mallocBytes)
#endif
import Snap.Types.Headers (Headers)
import qualified Snap.Types.Headers as H
#ifndef PORTABLE
foreign import ccall unsafe "set_c_locale"
set_c_locale :: IO ()
foreign import ccall unsafe "c_parse_http_time"
c_parse_http_time :: CString -> IO CTime
foreign import ccall unsafe "c_format_http_time"
c_format_http_time :: CTime -> CString -> IO ()
foreign import ccall unsafe "c_format_log_time"
c_format_log_time :: CTime -> CString -> IO ()
#endif
class a where
:: (Headers -> Headers) -> a -> a
:: a -> Headers
addHeader :: (HasHeaders a) => CI ByteString -> ByteString -> a -> a
CI ByteString
k ByteString
v = forall a. HasHeaders a => (Headers -> Headers) -> a -> a
updateHeaders forall a b. (a -> b) -> a -> b
$ CI ByteString -> ByteString -> Headers -> Headers
H.insert CI ByteString
k ByteString
v
setHeader :: (HasHeaders a) => CI ByteString -> ByteString -> a -> a
CI ByteString
k ByteString
v = forall a. HasHeaders a => (Headers -> Headers) -> a -> a
updateHeaders forall a b. (a -> b) -> a -> b
$ CI ByteString -> ByteString -> Headers -> Headers
H.set CI ByteString
k ByteString
v
getHeader :: (HasHeaders a) => CI ByteString -> a -> Maybe ByteString
CI ByteString
k a
a = CI ByteString -> Headers -> Maybe ByteString
H.lookup CI ByteString
k forall a b. (a -> b) -> a -> b
$ forall a. HasHeaders a => a -> Headers
headers a
a
listHeaders :: (HasHeaders a) => a -> [(CI ByteString, ByteString)]
= Headers -> [(CI ByteString, ByteString)]
H.toList forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. HasHeaders a => a -> Headers
headers
deleteHeader :: (HasHeaders a) => CI ByteString -> a -> a
CI ByteString
k = forall a. HasHeaders a => (Headers -> Headers) -> a -> a
updateHeaders forall a b. (a -> b) -> a -> b
$ CI ByteString -> Headers -> Headers
H.delete CI ByteString
k
data Method = GET | HEAD | POST | PUT | DELETE | TRACE | OPTIONS | CONNECT |
PATCH | Method ByteString
deriving(Int -> Method -> ShowS
[Method] -> ShowS
Method -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Method] -> ShowS
$cshowList :: [Method] -> ShowS
show :: Method -> String
$cshow :: Method -> String
showsPrec :: Int -> Method -> ShowS
$cshowsPrec :: Int -> Method -> ShowS
Show, ReadPrec [Method]
ReadPrec Method
Int -> ReadS Method
ReadS [Method]
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [Method]
$creadListPrec :: ReadPrec [Method]
readPrec :: ReadPrec Method
$creadPrec :: ReadPrec Method
readList :: ReadS [Method]
$creadList :: ReadS [Method]
readsPrec :: Int -> ReadS Method
$creadsPrec :: Int -> ReadS Method
Read)
instance Eq Method where
Method
a == :: Method -> Method -> Bool
== Method
b =
Method -> Method
normalizeMethod Method
a Method -> Method -> Bool
`eq` Method -> Method
normalizeMethod Method
b
where
Method
GET eq :: Method -> Method -> Bool
`eq` Method
GET = Bool
True
Method
HEAD `eq` Method
HEAD = Bool
True
Method
POST `eq` Method
POST = Bool
True
Method
PUT `eq` Method
PUT = Bool
True
Method
DELETE `eq` Method
DELETE = Bool
True
Method
TRACE `eq` Method
TRACE = Bool
True
Method
OPTIONS `eq` Method
OPTIONS = Bool
True
Method
CONNECT `eq` Method
CONNECT = Bool
True
Method
PATCH `eq` Method
PATCH = Bool
True
Method ByteString
x1 `eq` Method ByteString
y1 = ByteString
x1 forall a. Eq a => a -> a -> Bool
== ByteString
y1
Method
_ `eq` Method
_ = Bool
False
instance Ord Method where
compare :: Method -> Method -> Ordering
compare Method
a Method
b =
Method -> Method -> Ordering
check (Method -> Method
normalizeMethod Method
a) (Method -> Method
normalizeMethod Method
b)
where
check :: Method -> Method -> Ordering
check Method
GET Method
GET = Ordering
EQ
check Method
HEAD Method
HEAD = Ordering
EQ
check Method
POST Method
POST = Ordering
EQ
check Method
PUT Method
PUT = Ordering
EQ
check Method
DELETE Method
DELETE = Ordering
EQ
check Method
TRACE Method
TRACE = Ordering
EQ
check Method
OPTIONS Method
OPTIONS = Ordering
EQ
check Method
CONNECT Method
CONNECT = Ordering
EQ
check Method
PATCH Method
PATCH = Ordering
EQ
check (Method ByteString
x1) (Method ByteString
y1) = forall a. Ord a => a -> a -> Ordering
compare ByteString
x1 ByteString
y1
check Method
x Method
y = forall a. Ord a => a -> a -> Ordering
compare (Method -> Int
tag Method
x) (Method -> Int
tag Method
y)
tag :: Method -> Int
tag :: Method -> Int
tag (GET{}) = Int
0
tag (HEAD{}) = Int
1
tag (POST{}) = Int
2
tag (PUT{}) = Int
3
tag (DELETE{}) = Int
4
tag (TRACE{}) = Int
5
tag (OPTIONS{}) = Int
6
tag (CONNECT{}) = Int
7
tag (PATCH{}) = Int
8
tag (Method{}) = Int
9
{-# INLINE normalizeMethod #-}
normalizeMethod :: Method -> Method
normalizeMethod :: Method -> Method
normalizeMethod m :: Method
m@(Method ByteString
name) = case ByteString
name of
ByteString
"GET" -> Method
GET
ByteString
"HEAD" -> Method
HEAD
ByteString
"POST" -> Method
POST
ByteString
"PUT" -> Method
PUT
ByteString
"DELETE" -> Method
DELETE
ByteString
"TRACE" -> Method
TRACE
ByteString
"OPTIONS" -> Method
OPTIONS
ByteString
"CONNECT" -> Method
CONNECT
ByteString
"PATCH" -> Method
PATCH
ByteString
_ -> Method
m
normalizeMethod Method
m = Method
m
type HttpVersion = (Int,Int)
data Cookie = Cookie {
Cookie -> ByteString
cookieName :: !ByteString
, Cookie -> ByteString
cookieValue :: !ByteString
, Cookie -> Maybe UTCTime
cookieExpires :: !(Maybe UTCTime)
, Cookie -> Maybe ByteString
cookieDomain :: !(Maybe ByteString)
, Cookie -> Maybe ByteString
cookiePath :: !(Maybe ByteString)
, Cookie -> Bool
cookieSecure :: !Bool
, Cookie -> Bool
cookieHttpOnly :: !Bool
} deriving (Cookie -> Cookie -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Cookie -> Cookie -> Bool
$c/= :: Cookie -> Cookie -> Bool
== :: Cookie -> Cookie -> Bool
$c== :: Cookie -> Cookie -> Bool
Eq, Int -> Cookie -> ShowS
[Cookie] -> ShowS
Cookie -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Cookie] -> ShowS
$cshowList :: [Cookie] -> ShowS
show :: Cookie -> String
$cshow :: Cookie -> String
showsPrec :: Int -> Cookie -> ShowS
$cshowsPrec :: Int -> Cookie -> ShowS
Show)
type Params = Map ByteString [ByteString]
data Request = Request
{
Request -> ByteString
rqHostName :: ByteString
, Request -> ByteString
rqClientAddr :: ByteString
, Request -> Int
rqClientPort :: {-# UNPACK #-} !Int
, Request -> ByteString
rqServerAddr :: ByteString
, Request -> Int
rqServerPort :: {-# UNPACK #-} !Int
, Request -> ByteString
rqLocalHostname :: ByteString
, Request -> Bool
rqIsSecure :: !Bool
, :: Headers
, Request -> InputStream ByteString
rqBody :: InputStream ByteString
, Request -> Maybe Word64
rqContentLength :: !(Maybe Word64)
, Request -> Method
rqMethod :: !Method
, Request -> HttpVersion
rqVersion :: {-# UNPACK #-} !HttpVersion
, Request -> [Cookie]
rqCookies :: [Cookie]
, Request -> ByteString
rqPathInfo :: ByteString
, Request -> ByteString
rqContextPath :: ByteString
, Request -> ByteString
rqURI :: ByteString
, Request -> ByteString
rqQueryString :: ByteString
, Request -> Params
rqParams :: Params
, Request -> Params
rqQueryParams :: Params
, Request -> Params
rqPostParams :: Params
}
instance Show Request where
show :: Request -> String
show Request
r = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [ String
method, String
" ", String
uri, String
" HTTP/", String
version, String
"\n"
, String
hdrs, String
"\n\n"
, String
"sn=\"", String
sname, String
"\" c=", String
clntAddr, String
" s=", String
srvAddr
, String
" ctx=", String
contextpath, String
" clen=", String
contentlength, String
secure
, String
params, String
cookies
]
where
method :: String
method = forall a. Show a => a -> String
show forall a b. (a -> b) -> a -> b
$ Request -> Method
rqMethod Request
r
uri :: String
uri = ByteString -> String
S.unpack forall a b. (a -> b) -> a -> b
$ Request -> ByteString
rqURI Request
r
version :: String
version = let (Int
mj, Int
mn) = Request -> HttpVersion
rqVersion Request
r in forall a. Show a => a -> String
show Int
mj forall a. [a] -> [a] -> [a]
++ String
"." forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Int
mn
hdrs :: String
hdrs = forall a. [a] -> [[a]] -> [a]
intercalate String
"\n" forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (CI ByteString, ByteString) -> String
showHdr (Headers -> [(CI ByteString, ByteString)]
H.toList forall a b. (a -> b) -> a -> b
$ Request -> Headers
rqHeaders Request
r)
showHdr :: (CI ByteString, ByteString) -> String
showHdr (CI ByteString
a,ByteString
b) = (ByteString -> String
S.unpack forall a b. (a -> b) -> a -> b
$ forall s. CI s -> s
CI.original CI ByteString
a) forall a. [a] -> [a] -> [a]
++ String
": " forall a. [a] -> [a] -> [a]
++ ByteString -> String
S.unpack ByteString
b
sname :: String
sname = ByteString -> String
S.unpack forall a b. (a -> b) -> a -> b
$ Request -> ByteString
rqLocalHostname Request
r
clntAddr :: String
clntAddr = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [ByteString -> String
S.unpack forall a b. (a -> b) -> a -> b
$ Request -> ByteString
rqClientAddr Request
r, String
":", forall a. Show a => a -> String
show forall a b. (a -> b) -> a -> b
$ Request -> Int
rqClientPort Request
r]
srvAddr :: String
srvAddr = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [ByteString -> String
S.unpack forall a b. (a -> b) -> a -> b
$ Request -> ByteString
rqServerAddr Request
r, String
":", forall a. Show a => a -> String
show forall a b. (a -> b) -> a -> b
$ Request -> Int
rqServerPort Request
r]
contextpath :: String
contextpath = ByteString -> String
S.unpack forall a b. (a -> b) -> a -> b
$ Request -> ByteString
rqContextPath Request
r
contentlength :: String
contentlength = forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"n/a" forall a. Show a => a -> String
show (Request -> Maybe Word64
rqContentLength Request
r)
secure :: String
secure = if Request -> Bool
rqIsSecure Request
r then String
" secure" else String
""
params :: String
params = String -> String -> [String] -> String
showFlds String
"\nparams: " String
", " forall a b. (a -> b) -> a -> b
$
forall a b. (a -> b) -> [a] -> [b]
map (\ (ByteString
a,[ByteString]
b) -> ByteString -> String
S.unpack ByteString
a forall a. [a] -> [a] -> [a]
++ String
": " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show [ByteString]
b)
(forall k a. Map k a -> [(k, a)]
Map.toAscList forall a b. (a -> b) -> a -> b
$ Request -> Params
rqParams Request
r)
cookies :: String
cookies = String -> String -> [String] -> String
showFlds String
"\ncookies: " String
"\n " forall a b. (a -> b) -> a -> b
$
forall a b. (a -> b) -> [a] -> [b]
map forall a. Show a => a -> String
show (Request -> [Cookie]
rqCookies Request
r)
showFlds :: String -> String -> [String] -> String
showFlds String
header String
delim [String]
lst
= if Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t a -> Bool
null forall a b. (a -> b) -> a -> b
$ [String]
lst then String
header forall a. [a] -> [a] -> [a]
++ (forall a. [a] -> [[a]] -> [a]
intercalate String
delim [String]
lst)
else String
"" :: String
instance HasHeaders Request where
headers :: Request -> Headers
headers = Request -> Headers
rqHeaders
updateHeaders :: (Headers -> Headers) -> Request -> Request
updateHeaders Headers -> Headers
f Request
r = Request
r { rqHeaders :: Headers
rqHeaders = Headers -> Headers
f (Request -> Headers
rqHeaders Request
r) }
instance HasHeaders Headers where
headers :: Headers -> Headers
headers = forall a. a -> a
id
updateHeaders :: (Headers -> Headers) -> Headers -> Headers
updateHeaders = forall a. a -> a
id
type StreamProc = OutputStream Builder -> IO (OutputStream Builder)
data ResponseBody = Stream (StreamProc)
| SendFile FilePath (Maybe (Word64, Word64))
rspBodyMap :: (StreamProc -> StreamProc) -> ResponseBody -> ResponseBody
rspBodyMap :: (StreamProc -> StreamProc) -> ResponseBody -> ResponseBody
rspBodyMap StreamProc -> StreamProc
f ResponseBody
b = StreamProc -> ResponseBody
Stream forall a b. (a -> b) -> a -> b
$ StreamProc -> StreamProc
f forall a b. (a -> b) -> a -> b
$ ResponseBody -> StreamProc
rspBodyToEnum ResponseBody
b
rspBodyToEnum :: ResponseBody -> StreamProc
rspBodyToEnum :: ResponseBody -> StreamProc
rspBodyToEnum (Stream StreamProc
e) = StreamProc
e
rspBodyToEnum (SendFile String
fp Maybe (Word64, Word64)
Nothing) = \OutputStream Builder
out ->
forall a. String -> (InputStream ByteString -> IO a) -> IO a
Streams.withFileAsInput String
fp forall a b. (a -> b) -> a -> b
$ \InputStream ByteString
is -> do
InputStream Builder
is' <- forall a b. (a -> IO b) -> InputStream a -> IO (InputStream b)
Streams.mapM (forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Builder
byteString) InputStream ByteString
is
forall a. InputStream a -> OutputStream a -> IO ()
Streams.connect InputStream Builder
is' OutputStream Builder
out
forall (m :: * -> *) a. Monad m => a -> m a
return OutputStream Builder
out
rspBodyToEnum (SendFile String
fp (Just (Word64
start, Word64
end))) = \OutputStream Builder
out ->
forall r. String -> IOMode -> (Handle -> IO r) -> IO r
withBinaryFile String
fp IOMode
ReadMode forall a b. (a -> b) -> a -> b
$ \Handle
handle -> do
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Word64
start forall a. Eq a => a -> a -> Bool
== Word64
0) forall a b. (a -> b) -> a -> b
$ Handle -> SeekMode -> Integer -> IO ()
hSeek Handle
handle SeekMode
AbsoluteSeek forall a b. (a -> b) -> a -> b
$ forall a. Integral a => a -> Integer
toInteger Word64
start
InputStream ByteString
is <- Handle -> IO (InputStream ByteString)
Streams.handleToInputStream Handle
handle
InputStream Builder
is' <- Int64 -> InputStream ByteString -> IO (InputStream ByteString)
Streams.takeBytes (forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ Word64
end forall a. Num a => a -> a -> a
- Word64
start) InputStream ByteString
is forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>=
forall a b. (a -> IO b) -> InputStream a -> IO (InputStream b)
Streams.mapM (forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Builder
byteString)
forall a. InputStream a -> OutputStream a -> IO ()
Streams.connect InputStream Builder
is' OutputStream Builder
out
forall (m :: * -> *) a. Monad m => a -> m a
return OutputStream Builder
out
data Response = Response
{ :: Headers
, Response -> Map ByteString Cookie
rspCookies :: Map ByteString Cookie
, Response -> Maybe Word64
rspContentLength :: !(Maybe Word64)
, Response -> ResponseBody
rspBody :: ResponseBody
, Response -> Int
rspStatus :: !Int
, Response -> ByteString
rspStatusReason :: !ByteString
, Response -> Bool
rspTransformingRqBody :: !Bool
}
instance Show Response where
show :: Response -> String
show Response
r = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [ String
statusline
, String
hdrs
, String
contentLength
, String
"\r\n"
, String
body
]
where
statusline :: String
statusline = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [ String
"HTTP/1.1 "
, forall a. Show a => a -> String
show forall a b. (a -> b) -> a -> b
$ Response -> Int
rspStatus Response
r
, String
" "
, ByteString -> String
S.unpack forall a b. (a -> b) -> a -> b
$ Response -> ByteString
rspStatusReason Response
r
, String
"\r\n" ]
hdrs :: String
hdrs = forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (CI ByteString, ByteString) -> String
showHdr forall a b. (a -> b) -> a -> b
$ Headers -> [(CI ByteString, ByteString)]
H.toList forall a b. (a -> b) -> a -> b
$ Response -> Headers -> Headers
renderCookies Response
r
forall a b. (a -> b) -> a -> b
$ Response -> Headers
rspHeaders forall a b. (a -> b) -> a -> b
$ Response -> Response
clearContentLength Response
r
contentLength :: String
contentLength = forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"" (\Word64
l -> forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [String
"Content-Length: ", forall a. Show a => a -> String
show Word64
l, String
"\r\n"] ) (Response -> Maybe Word64
rspContentLength Response
r)
showHdr :: (CI ByteString, ByteString) -> String
showHdr (CI ByteString
k,ByteString
v) = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [ ByteString -> String
S.unpack (forall s. CI s -> s
CI.original CI ByteString
k), String
": ", ByteString -> String
S.unpack ByteString
v, String
"\r\n" ]
body :: String
body = forall a. IO a -> a
unsafePerformIO forall a b. (a -> b) -> a -> b
$ do
(OutputStream Builder
os, IO [Builder]
grab) <- forall c. IO (OutputStream c, IO [c])
Streams.listOutputStream
let f :: StreamProc
f = ResponseBody -> StreamProc
rspBodyToEnum forall a b. (a -> b) -> a -> b
$ Response -> ResponseBody
rspBody Response
r
OutputStream Builder
_ <- StreamProc
f OutputStream Builder
os
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (ByteString -> String
L.unpack forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> ByteString
toLazyByteString forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Monoid a => [a] -> a
mconcat) IO [Builder]
grab
instance HasHeaders Response where
headers :: Response -> Headers
headers = Response -> Headers
rspHeaders
updateHeaders :: (Headers -> Headers) -> Response -> Response
updateHeaders Headers -> Headers
f Response
r = Response
r { rspHeaders :: Headers
rspHeaders = Headers -> Headers
f (Response -> Headers
rspHeaders Response
r) }
rqParam :: ByteString
-> Request
-> Maybe [ByteString]
rqParam :: ByteString -> Request -> Maybe [ByteString]
rqParam ByteString
k Request
rq = forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup ByteString
k forall a b. (a -> b) -> a -> b
$ Request -> Params
rqParams Request
rq
{-# INLINE rqParam #-}
rqPostParam :: ByteString
-> Request
-> Maybe [ByteString]
rqPostParam :: ByteString -> Request -> Maybe [ByteString]
rqPostParam ByteString
k Request
rq = forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup ByteString
k forall a b. (a -> b) -> a -> b
$ Request -> Params
rqPostParams Request
rq
{-# INLINE rqPostParam #-}
rqQueryParam :: ByteString
-> Request
-> Maybe [ByteString]
rqQueryParam :: ByteString -> Request -> Maybe [ByteString]
rqQueryParam ByteString
k Request
rq = forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup ByteString
k forall a b. (a -> b) -> a -> b
$ Request -> Params
rqQueryParams Request
rq
{-# INLINE rqQueryParam #-}
rqModifyParams :: (Params -> Params) -> Request -> Request
rqModifyParams :: (Params -> Params) -> Request -> Request
rqModifyParams Params -> Params
f Request
r = Request
r { rqParams :: Params
rqParams = Params
p }
where
p :: Params
p = Params -> Params
f forall a b. (a -> b) -> a -> b
$ Request -> Params
rqParams Request
r
{-# INLINE rqModifyParams #-}
rqSetParam :: ByteString
-> [ByteString]
-> Request
-> Request
rqSetParam :: ByteString -> [ByteString] -> Request -> Request
rqSetParam ByteString
k [ByteString]
v = (Params -> Params) -> Request -> Request
rqModifyParams forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert ByteString
k [ByteString]
v
{-# INLINE rqSetParam #-}
emptyResponse :: Response
emptyResponse :: Response
emptyResponse = Headers
-> Map ByteString Cookie
-> Maybe Word64
-> ResponseBody
-> Int
-> ByteString
-> Bool
-> Response
Response Headers
H.empty forall k a. Map k a
Map.empty forall a. Maybe a
Nothing
(StreamProc -> ResponseBody
Stream (forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. a -> a
id))
Int
200 ByteString
"OK" Bool
False
setResponseBody :: (OutputStream Builder -> IO (OutputStream Builder))
-> Response
-> Response
setResponseBody :: StreamProc -> Response -> Response
setResponseBody StreamProc
e Response
r = Response
r { rspBody :: ResponseBody
rspBody = StreamProc -> ResponseBody
Stream StreamProc
e }
{-# INLINE setResponseBody #-}
setResponseStatus :: Int
-> ByteString
-> Response
-> Response
setResponseStatus :: Int -> ByteString -> Response -> Response
setResponseStatus Int
s ByteString
reason Response
r = Response
r { rspStatus :: Int
rspStatus=Int
s, rspStatusReason :: ByteString
rspStatusReason=ByteString
reason }
{-# INLINE setResponseStatus #-}
setResponseCode :: Int
-> Response
-> Response
setResponseCode :: Int -> Response -> Response
setResponseCode Int
s Response
r = Int -> ByteString -> Response -> Response
setResponseStatus Int
s ByteString
reason Response
r
where
reason :: ByteString
reason = forall a. a -> Maybe a -> a
fromMaybe ByteString
"Unknown" (forall a. Int -> IntMap a -> Maybe a
IM.lookup Int
s IntMap ByteString
statusReasonMap)
{-# INLINE setResponseCode #-}
modifyResponseBody :: ((OutputStream Builder -> IO (OutputStream Builder)) ->
(OutputStream Builder -> IO (OutputStream Builder)))
-> Response
-> Response
modifyResponseBody :: (StreamProc -> StreamProc) -> Response -> Response
modifyResponseBody StreamProc -> StreamProc
f Response
r = Response
r { rspBody :: ResponseBody
rspBody = (StreamProc -> StreamProc) -> ResponseBody -> ResponseBody
rspBodyMap StreamProc -> StreamProc
f (Response -> ResponseBody
rspBody Response
r) }
{-# INLINE modifyResponseBody #-}
setContentType :: ByteString -> Response -> Response
setContentType :: ByteString -> Response -> Response
setContentType = forall a. HasHeaders a => CI ByteString -> ByteString -> a -> a
setHeader CI ByteString
"Content-Type"
{-# INLINE setContentType #-}
cookieToBS :: Cookie -> ByteString
cookieToBS :: Cookie -> ByteString
cookieToBS (Cookie ByteString
k ByteString
v Maybe UTCTime
mbExpTime Maybe ByteString
mbDomain Maybe ByteString
mbPath Bool
isSec Bool
isHOnly) = ByteString
cookie
where
cookie :: ByteString
cookie = [ByteString] -> ByteString
S.concat [ByteString
k, ByteString
"=", ByteString
v, ByteString
path, ByteString
exptime, ByteString
domain, ByteString
secure, ByteString
hOnly]
path :: ByteString
path = forall b a. b -> (a -> b) -> Maybe a -> b
maybe ByteString
"" (ByteString -> ByteString -> ByteString
S.append ByteString
"; path=") Maybe ByteString
mbPath
domain :: ByteString
domain = forall b a. b -> (a -> b) -> Maybe a -> b
maybe ByteString
"" (ByteString -> ByteString -> ByteString
S.append ByteString
"; domain=") Maybe ByteString
mbDomain
exptime :: ByteString
exptime = forall b a. b -> (a -> b) -> Maybe a -> b
maybe ByteString
"" (ByteString -> ByteString -> ByteString
S.append ByteString
"; expires=" forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> ByteString
fmt) Maybe UTCTime
mbExpTime
secure :: ByteString
secure = if Bool
isSec then ByteString
"; Secure" else ByteString
""
hOnly :: ByteString
hOnly = if Bool
isHOnly then ByteString
"; HttpOnly" else ByteString
""
fmt :: UTCTime -> ByteString
fmt = forall a. IO a -> a
unsafePerformIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. CTime -> IO ByteString
formatHttpTime forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> CTime
toCTime
toCTime :: UTCTime -> CTime
toCTime :: UTCTime -> CTime
toCTime = forall a. Num a => Integer -> a
fromInteger forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (RealFrac a, Integral b) => a -> b
truncate forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> POSIXTime
utcTimeToPOSIXSeconds
renderCookies :: Response -> Headers -> Headers
renderCookies :: Response -> Headers -> Headers
renderCookies Response
r Headers
hdrs
| forall (t :: * -> *) a. Foldable t => t a -> Bool
null [ByteString]
cookies = Headers
hdrs
| Bool
otherwise = forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' (\Headers
m ByteString
v -> ByteString -> ByteString -> Headers -> Headers
H.unsafeInsert ByteString
"set-cookie" ByteString
v Headers
m) Headers
hdrs [ByteString]
cookies
where
cookies :: [ByteString]
cookies = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Cookie -> ByteString
cookieToBS forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall k a. Map k a -> [a]
Map.elems forall a b. (a -> b) -> a -> b
$ Response -> Map ByteString Cookie
rspCookies Response
r
addResponseCookie :: Cookie
-> Response
-> Response
addResponseCookie :: Cookie -> Response -> Response
addResponseCookie ck :: Cookie
ck@(Cookie ByteString
k ByteString
_ Maybe UTCTime
_ Maybe ByteString
_ Maybe ByteString
_ Bool
_ Bool
_) Response
r = Response
r { rspCookies :: Map ByteString Cookie
rspCookies = Map ByteString Cookie
cks' }
where
cks' :: Map ByteString Cookie
cks'= forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert ByteString
k Cookie
ck forall a b. (a -> b) -> a -> b
$ Response -> Map ByteString Cookie
rspCookies Response
r
{-# INLINE addResponseCookie #-}
getResponseCookie :: ByteString
-> Response
-> Maybe Cookie
getResponseCookie :: ByteString -> Response -> Maybe Cookie
getResponseCookie ByteString
cn Response
r = forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup ByteString
cn forall a b. (a -> b) -> a -> b
$ Response -> Map ByteString Cookie
rspCookies Response
r
{-# INLINE getResponseCookie #-}
getResponseCookies :: Response
-> [Cookie]
getResponseCookies :: Response -> [Cookie]
getResponseCookies = forall k a. Map k a -> [a]
Map.elems forall b c a. (b -> c) -> (a -> b) -> a -> c
. Response -> Map ByteString Cookie
rspCookies
{-# INLINE getResponseCookies #-}
deleteResponseCookie :: ByteString
-> Response
-> Response
deleteResponseCookie :: ByteString -> Response -> Response
deleteResponseCookie ByteString
cn Response
r = Response
r { rspCookies :: Map ByteString Cookie
rspCookies = Map ByteString Cookie
cks' }
where
cks' :: Map ByteString Cookie
cks'= forall k a. Ord k => k -> Map k a -> Map k a
Map.delete ByteString
cn forall a b. (a -> b) -> a -> b
$ Response -> Map ByteString Cookie
rspCookies Response
r
{-# INLINE deleteResponseCookie #-}
modifyResponseCookie :: ByteString
-> (Cookie -> Cookie)
-> Response
-> Response
modifyResponseCookie :: ByteString -> (Cookie -> Cookie) -> Response -> Response
modifyResponseCookie ByteString
cn Cookie -> Cookie
f Response
r = forall b a. b -> (a -> b) -> Maybe a -> b
maybe Response
r Cookie -> Response
modify forall a b. (a -> b) -> a -> b
$ ByteString -> Response -> Maybe Cookie
getResponseCookie ByteString
cn Response
r
where
modify :: Cookie -> Response
modify Cookie
ck = Cookie -> Response -> Response
addResponseCookie (Cookie -> Cookie
f Cookie
ck) Response
r
{-# INLINE modifyResponseCookie #-}
setContentLength :: Word64 -> Response -> Response
setContentLength :: Word64 -> Response -> Response
setContentLength !Word64
l Response
r = Response
r { rspContentLength :: Maybe Word64
rspContentLength = forall a. a -> Maybe a
Just Word64
l }
{-# INLINE setContentLength #-}
clearContentLength :: Response -> Response
clearContentLength :: Response -> Response
clearContentLength Response
r = Response
r { rspContentLength :: Maybe Word64
rspContentLength = forall a. Maybe a
Nothing }
{-# INLINE clearContentLength #-}
formatHttpTime :: CTime -> IO ByteString
formatLogTime :: CTime -> IO ByteString
parseHttpTime :: ByteString -> IO CTime
#ifdef PORTABLE
fromStr :: String -> ByteString
fromStr = S.pack
{-# INLINE fromStr #-}
formatHttpTime = return . format . toUTCTime
where
format :: UTCTime -> ByteString
format = fromStr . formatTime defaultTimeLocale "%a, %d %b %Y %X GMT"
toUTCTime :: CTime -> UTCTime
toUTCTime = posixSecondsToUTCTime . realToFrac
formatLogTime ctime = do
t <- utcToLocalZonedTime $ toUTCTime ctime
return $! format t
where
format :: ZonedTime -> ByteString
format = fromStr . formatTime defaultTimeLocale "%d/%b/%Y:%H:%M:%S %z"
toUTCTime :: CTime -> UTCTime
toUTCTime = posixSecondsToUTCTime . realToFrac
parseHttpTime = return . toCTime . prs . S.unpack
where
parseTime =
#if MIN_VERSION_time(1,10,0)
parseTimeM True
#else
Time.parseTime
#endif
prs :: String -> Maybe UTCTime
prs = parseTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S GMT"
toCTime :: Maybe UTCTime -> CTime
toCTime (Just t) = fromInteger $ truncate $ utcTimeToPOSIXSeconds t
toCTime Nothing = fromInteger 0
#else
formatLogTime :: CTime -> IO ByteString
formatLogTime CTime
t = do
Ptr CChar
ptr <- forall a. Int -> IO (Ptr a)
mallocBytes Int
40
CTime -> Ptr CChar -> IO ()
c_format_log_time CTime
t Ptr CChar
ptr
Ptr CChar -> IO ByteString
S.unsafePackMallocCString Ptr CChar
ptr
formatHttpTime :: CTime -> IO ByteString
formatHttpTime CTime
t = do
Ptr CChar
ptr <- forall a. Int -> IO (Ptr a)
mallocBytes Int
40
CTime -> Ptr CChar -> IO ()
c_format_http_time CTime
t Ptr CChar
ptr
Ptr CChar -> IO ByteString
S.unsafePackMallocCString Ptr CChar
ptr
parseHttpTime :: ByteString -> IO CTime
parseHttpTime ByteString
s = forall a. ByteString -> (Ptr CChar -> IO a) -> IO a
S.unsafeUseAsCString ByteString
s forall a b. (a -> b) -> a -> b
$ \Ptr CChar
ptr ->
Ptr CChar -> IO CTime
c_parse_http_time Ptr CChar
ptr
#endif
statusReasonMap :: IM.IntMap ByteString
statusReasonMap :: IntMap ByteString
statusReasonMap = forall a. [(Int, a)] -> IntMap a
IM.fromList [
(Int
100, ByteString
"Continue"),
(Int
101, ByteString
"Switching Protocols"),
(Int
102, ByteString
"Processing"),
(Int
103, ByteString
"Early Hints"),
(Int
200, ByteString
"OK"),
(Int
201, ByteString
"Created"),
(Int
202, ByteString
"Accepted"),
(Int
203, ByteString
"Non-Authoritative Information"),
(Int
204, ByteString
"No Content"),
(Int
205, ByteString
"Reset Content"),
(Int
206, ByteString
"Partial Content"),
(Int
207, ByteString
"Multi-Status"),
(Int
208, ByteString
"Already Reported"),
(Int
226, ByteString
"IM Used"),
(Int
300, ByteString
"Multiple Choices"),
(Int
301, ByteString
"Moved Permanently"),
(Int
302, ByteString
"Found"),
(Int
303, ByteString
"See Other"),
(Int
304, ByteString
"Not Modified"),
(Int
305, ByteString
"Use Proxy"),
(Int
306, ByteString
"(Unused)"),
(Int
307, ByteString
"Temporary Redirect"),
(Int
308, ByteString
"Permanent Redirect"),
(Int
400, ByteString
"Bad Request"),
(Int
401, ByteString
"Unauthorized"),
(Int
402, ByteString
"Payment Required"),
(Int
403, ByteString
"Forbidden"),
(Int
404, ByteString
"Not Found"),
(Int
405, ByteString
"Method Not Allowed"),
(Int
406, ByteString
"Not Acceptable"),
(Int
407, ByteString
"Proxy Authentication Required"),
(Int
408, ByteString
"Request Timeout"),
(Int
409, ByteString
"Conflict"),
(Int
410, ByteString
"Gone"),
(Int
411, ByteString
"Length Required"),
(Int
412, ByteString
"Precondition Failed"),
(Int
413, ByteString
"Payload Too Large"),
(Int
414, ByteString
"URI Too Long"),
(Int
415, ByteString
"Unsupported Media Type"),
(Int
416, ByteString
"Range Not Satisfiable"),
(Int
417, ByteString
"Expectation Failed"),
(Int
421, ByteString
"Misdirected Request"),
(Int
422, ByteString
"Unprocessable Entity"),
(Int
423, ByteString
"Locked"),
(Int
424, ByteString
"Failed Dependency"),
(Int
425, ByteString
"Too Early"),
(Int
426, ByteString
"Upgrade Required"),
(Int
428, ByteString
"Precondition Required"),
(Int
429, ByteString
"Too Many Requests"),
(Int
431, ByteString
"Request Header Fields Too Large"),
(Int
451, ByteString
"Unavailable For Legal Reasons"),
(Int
500, ByteString
"Internal Server Error"),
(Int
501, ByteString
"Not Implemented"),
(Int
502, ByteString
"Bad Gateway"),
(Int
503, ByteString
"Service Unavailable"),
(Int
504, ByteString
"Gateway Timeout"),
(Int
505, ByteString
"HTTP Version Not Supported"),
(Int
506, ByteString
"Variant Also Negotiates"),
(Int
507, ByteString
"Insufficient Storage"),
(Int
508, ByteString
"Loop Detected"),
(Int
510, ByteString
"Not Extended"),
(Int
511, ByteString
"Network Authentication Required")
]
rqRemoteAddr :: Request -> ByteString
rqRemoteAddr :: Request -> ByteString
rqRemoteAddr = Request -> ByteString
rqClientAddr
{-# DEPRECATED rqRemoteAddr "(snap-core >= 1.0.0.0) please use 'rqClientAddr', this will be removed in 1.1.*" #-}
rqRemotePort :: Request -> Int
rqRemotePort :: Request -> Int
rqRemotePort = Request -> Int
rqClientPort
{-# DEPRECATED rqRemotePort "(snap-core >= 1.0.0.0) please use 'rqClientPort', this will be removed in 1.1.*" #-}