-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Snap: A Haskell Web Framework (core interfaces and types) -- -- Snap is a simple and fast web development framework and server written -- in Haskell. For more information or to download the latest version, -- you can visit the Snap project website at -- http://snapframework.com/. -- -- This library contains the core definitions and types for the Snap -- framework, including: -- --
-- ghci> debug "Some debug message" -- [ 225] Some debug message --debug :: MonadIO m => String -> m () -- | Print out the error message corresponding to the Errno value -- returned by getErrno together with any additional information -- provided by the user (usually the location where the error occurred). -- -- Example: -- --
-- ghci> debugErrno "pathtoSource.hs:34" -- [ 323] pathtoSource.hs:34: failed (Success) --debugErrno :: MonadIO m => String -> m () -- | An opaque data type for HTTP headers. Intended to be imported -- qualified, i.e: -- --
-- import Snap.Types.Headers (Headers) -- import qualified Snap.Types.Headers as H -- -- foo :: Headers -- foo = H.empty --module Snap.Types.Headers -- | A key-value map that represents a collection of HTTP header fields. -- Keys are case-insensitive. data Headers -- | An empty collection of HTTP header fields. -- -- Example: -- --
-- ghci> H.empty
-- H {unH = []}
--
empty :: Headers
-- | Is a given collection of HTTP header fields empty?
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> H.null H.empty
-- True
-- ghci> H.null $ H.fromList [("Host", "localhost")]
-- False
--
null :: Headers -> Bool
-- | Does this collection of HTTP header fields contain a given field?
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> H.member "host" $ H.fromList [("Host", "localhost")]
-- True
-- ghci> H.member "Accept" $ H.fromList [("Host", "localhost")]
-- False
--
member :: CI ByteString -> Headers -> Bool
-- | Look up the value of a given HTTP header field.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> H.lookup "host" $ H.fromList [("Host", "localhost")]
-- Just "localhost"
-- ghci> H.lookup "Accept" $ H.fromList [("Host", "localhost")]
-- Nothing
--
lookup :: CI ByteString -> Headers -> Maybe ByteString
-- | Look up the value of a given HTTP header field or return the provided
-- default value when that header field is not present.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> let hdrs = H.fromList [("Host", "localhost")]
-- ghci> H.lookupWithDefault "host" "127.0.0.1" $ hdrs
-- "localhost"
-- ghci> H.lookupWithDefault "Accept" "text/plain" $ hdrs
-- "text/plain"
--
lookupWithDefault :: ByteString -> CI ByteString -> Headers -> ByteString
-- | Insert a key-value pair into the headers map. If the key already
-- exists in the map, the values are catenated with ", ".
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> let hdrs = H.insert "Accept" "text/plain" $ H.empty
-- ghci> hdrs
-- H {unH = [("accept","text/plain")]}
-- ghci> H.insert "Accept" "text/html" $ hdrs
-- H {unH = [("accept","text/plain,text/html")]}
--
insert :: CI ByteString -> ByteString -> Headers -> Headers
-- | Insert a key-value pair into the headers map, without checking whether
-- the header already exists. The key must be already case-folded,
-- or none of the lookups will work!
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> let hdrs = H.unsafeInsert "accept" "text/plain" $ H.empty
-- ghci> hdrs
-- H {unH = [("accept","text/plain")]}
-- ghci> let hdrs' = H.unsafeInsert "accept" "text/html" $ hdrs
-- ghci> hdrs'
-- H {unH = [("accept","text/html"), ("accept","text/plain")]}
-- ghci> H.lookup "accept" hdrs'
-- Just "text/html"
--
unsafeInsert :: ByteString -> ByteString -> Headers -> Headers
-- | Set the value of a HTTP header field to a given value, replacing the
-- old value.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> H.set "accept" "text/plain" $ H.empty
-- H {unH = [("accept","text/plain")]}
-- ghci> H.set "accept" "text/html" $ H.fromList [("Accept", "text/plain")]
-- H {unH = [("accept","text/html")]}
--
set :: CI ByteString -> ByteString -> Headers -> Headers
-- | Delete all key-value pairs associated with the given key from the
-- headers map.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> H.delete "accept" $ H.fromList [("Accept", "text/plain")]
-- H {unH = []}
--
delete :: CI ByteString -> Headers -> Headers
-- | Strict left fold over all key-value pairs in the headers map.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import Data.Monoid
-- ghci> let hdrs = H.fromList [("Accept", "text/plain"), ("Accept", "text/html")]
-- ghci> let f (cntr, acc) _ val = (cntr+1, val <> ";" <> acc)
-- ghci> H.foldl' f (0, "") hdrs
-- (2,"text/html;text/plain;")
--
foldl' :: (a -> CI ByteString -> ByteString -> a) -> a -> Headers -> a
-- | Right fold over all key-value pairs in the headers map.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import Data.Monoid
-- ghci> let hdrs = H.fromList [("Accept", "text/plain"), ("Accept", "text/html")]
-- ghci> let f _ val (cntr, acc) = (cntr+1, val <> ";" <> acc)
-- ghci> H.foldr f (0, "") hdrs
-- (2,"text/plain;text/html;")
--
foldr :: (CI ByteString -> ByteString -> a -> a) -> a -> Headers -> a
-- | Same as foldl', but the key parameter is of type
-- ByteString instead of CI ByteString. The key is
-- case-folded (lowercase).
foldedFoldl' :: (a -> ByteString -> ByteString -> a) -> a -> Headers -> a
-- | Same as foldr, but the key parameter is of type
-- ByteString instead of CI ByteString. The key is
-- case-folded (lowercase).
foldedFoldr :: (ByteString -> ByteString -> a -> a) -> a -> Headers -> a
-- | Convert a Headers value to a list of key-value pairs.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> let l = [("Accept", "text/plain"), ("Accept", "text/html")]
-- ghci> H.toList . H.fromList $ l
-- [("accept","text/plain"),("accept","text/html")]
--
toList :: Headers -> [(CI ByteString, ByteString)]
-- | Build a Headers value from a list of key-value pairs.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> H.fromList [("Accept", "text/plain"), ("Accept", "text/html")]
-- H {unH = [("accept","text/plain"),("accept","text/html")]}
--
fromList :: [(CI ByteString, ByteString)] -> Headers
-- | Like fromList, but the keys are assumed to be already
-- case-folded (in lowercase).
unsafeFromCaseFoldedList :: [(ByteString, ByteString)] -> Headers
-- | Like toList, but does not convert the keys to CI
-- ByteString, so key comparisons will be case-sensitive.
unsafeToCaseFoldedList :: Headers -> [(ByteString, ByteString)]
instance GHC.Show.Show Snap.Types.Headers.Headers
-- | An internal Snap module containing HTTP types.
--
-- N.B. this is an internal interface, please don't write user
-- code that depends on it. Most of these declarations (except for the
-- unsafe/encapsulation-breaking ones) are re-exported from
-- Snap.Core.
module Snap.Internal.Http.Types
set_c_locale :: IO ()
c_parse_http_time :: CString -> IO CTime
c_format_http_time :: CTime -> CString -> IO ()
c_format_log_time :: CTime -> CString -> IO ()
-- | A typeclass for datatypes which contain HTTP headers.
class HasHeaders a
-- | Modify the datatype's headers.
updateHeaders :: HasHeaders a => (Headers -> Headers) -> a -> a
-- | Retrieve the headers from a datatype that has headers.
headers :: HasHeaders a => a -> Headers
-- | Adds a header key-value-pair to the HasHeaders datatype. If a
-- header with the same name already exists, the new value is appended to
-- the headers list.
--
-- Example:
--
--
-- ghci> import qualified Snap.Types.Headers as H
-- ghci> addHeader "Host" "localhost" H.empty
-- H {unH = [("host","localhost")]}
-- ghci> addHeader "Host" "127.0.0.1" it
-- H {unH = [("host","localhost,127.0.0.1")]}
--
addHeader :: HasHeaders a => CI ByteString -> ByteString -> a -> a
-- | Sets a header key-value-pair in a HasHeaders datatype. If a
-- header with the same name already exists, it is overwritten with the
-- new value.
--
-- Example:
--
--
-- ghci> import qualified Snap.Types.Headers as H
-- ghci> setHeader "Host" "localhost" H.empty
-- H {unH = [("host","localhost")]}
-- ghci> setHeader "Host" "127.0.0.1" it
-- H {unH = [("host","127.0.0.1")]}
--
setHeader :: HasHeaders a => CI ByteString -> ByteString -> a -> a
-- | Gets a header value out of a HasHeaders datatype.
--
-- Example:
--
-- -- ghci> import qualified Snap.Types.Headers as H -- ghci> getHeader "Host" $ setHeader "Host" "localhost" H.empty -- Just "localhost" --getHeader :: HasHeaders a => CI ByteString -> a -> Maybe ByteString -- | Lists all the headers out of a HasHeaders datatype. If many -- headers came in with the same name, they will be catenated together. -- -- Example: -- --
-- ghci> import qualified Snap.Types.Headers as H
-- ghci> listHeaders $ setHeader "Host" "localhost" H.empty
-- [("host","localhost")]
--
listHeaders :: HasHeaders a => a -> [(CI ByteString, ByteString)]
-- | Clears a header value from a HasHeaders datatype.
--
-- Example:
--
--
-- ghci> import qualified Snap.Types.Headers as H
-- ghci> deleteHeader "Host" $ setHeader "Host" "localhost" H.empty
-- H {unH = []}
--
deleteHeader :: HasHeaders a => CI ByteString -> a -> a
-- | Enumerates the HTTP method values (see
-- http://tools.ietf.org/html/rfc2068.html#section-5.1.1).
data Method
GET :: Method
HEAD :: Method
POST :: Method
PUT :: Method
DELETE :: Method
TRACE :: Method
OPTIONS :: Method
CONNECT :: Method
PATCH :: Method
Method :: ByteString -> Method
-- | Equate the special case constructors with their corresponding
-- Method name variant.
normalizeMethod :: Method -> Method
-- | Represents a (major, minor) version of the HTTP protocol.
type HttpVersion = (Int, Int)
-- | A datatype representing an HTTP cookie.
data Cookie
Cookie :: !ByteString -> !ByteString -> !Maybe UTCTime -> !Maybe ByteString -> !Maybe ByteString -> !Bool -> !Bool -> Cookie
-- | The name of the cookie.
[cookieName] :: Cookie -> !ByteString
-- | The cookie's string value.
[cookieValue] :: Cookie -> !ByteString
-- | The cookie's expiration value, if it has one.
[cookieExpires] :: Cookie -> !Maybe UTCTime
-- | The cookie's "domain" value, if it has one.
[cookieDomain] :: Cookie -> !Maybe ByteString
-- | The cookie path.
[cookiePath] :: Cookie -> !Maybe ByteString
-- | Tag as secure cookie?
[cookieSecure] :: Cookie -> !Bool
-- | HTTP only?
[cookieHttpOnly] :: Cookie -> !Bool
-- | A type alias for the HTTP parameters mapping. Each parameter key maps
-- to a list of ByteString values; if a parameter is specified
-- multiple times (e.g.: "GET /foo?param=bar1¶m=bar2"),
-- looking up "param" in the mapping will give you ["bar1",
-- "bar2"].
type Params = Map ByteString [ByteString]
-- | Contains all of the information about an incoming HTTP request.
data Request
Request :: ByteString -> ByteString -> {-# UNPACK #-} !Int -> ByteString -> {-# UNPACK #-} !Int -> ByteString -> !Bool -> Headers -> InputStream ByteString -> !Maybe Word64 -> !Method -> {-# UNPACK #-} !HttpVersion -> [Cookie] -> ByteString -> ByteString -> ByteString -> ByteString -> Params -> Params -> Params -> Request
-- | The server name of the request, as it came in from the request's
-- Host: header.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.get "/foo/bar" M.empty
-- ghci| T.setHeader "host" "example.com"
-- ghci| :}
-- ghci> rqHostName rq
-- "example.com"
--
[rqHostName] :: Request -> ByteString
-- | The remote IP address.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqClientAddr `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "127.0.0.1" --[rqClientAddr] :: Request -> ByteString -- | The remote TCP port number. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqClientPort `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "60000" --[rqClientPort] :: Request -> {-# UNPACK #-} !Int -- | The local IP address for this request. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqServerAddr `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "127.0.0.1" --[rqServerAddr] :: Request -> ByteString -- | Returns the port number the HTTP server is listening on. This may be -- useless from the perspective of external requests, e.g. if the server -- is running behind a proxy. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqServerPort `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- 8080 --[rqServerPort] :: Request -> {-# UNPACK #-} !Int -- | Returns the HTTP server's idea of its local hostname, including port. -- This is as configured with the Config object at startup. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqLocalHostname `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "localhost" --[rqLocalHostname] :: Request -> ByteString -- | Returns True if this is an HTTPS session. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqIsSecure `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- False --[rqIsSecure] :: Request -> !Bool -- | Contains all HTTP Headers associated with this request. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> rqHeaders `fmap` T.buildRequest (T.get "/foo/bar" M.empty)
-- H {unH = [("host","localhost")]}
--
[rqHeaders] :: Request -> Headers
-- | Actual body of the request.
[rqBody] :: Request -> InputStream ByteString
-- | Returns the Content-Length of the HTTP request body.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqContentLength `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- Nothing --[rqContentLength] :: Request -> !Maybe Word64 -- | Returns the HTTP request method. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqMethod `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- GET --[rqMethod] :: Request -> !Method -- | Returns the HTTP version used by the client. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqVersion `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- (1,1) --[rqVersion] :: Request -> {-# UNPACK #-} !HttpVersion -- | Returns a list of the cookies that came in from the HTTP request -- headers. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqCookies `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- [] --[rqCookies] :: Request -> [Cookie] -- | Handlers can be hung on a URI "entry point"; this is called -- the "context path". If a handler is hung on the context path -- "/foo/", and you request "/foo/bar", the value of -- rqPathInfo will be "bar". -- -- The following identity holds: -- --
-- rqURI r == S.concat [ rqContextPath r -- , rqPathInfo r -- , let q = rqQueryString r -- in if S.null q -- then "" -- else S.append "?" q -- ] ---- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqPathInfo `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "foo/bar" --[rqPathInfo] :: Request -> ByteString -- | The "context path" of the request; catenating rqContextPath, -- and rqPathInfo should get you back to the original rqURI -- (ignoring query strings). The rqContextPath always begins and -- ends with a slash ("/") character, and represents the path -- (relative to your component/snaplet) you took to get to your handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqContextPath `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "/" --[rqContextPath] :: Request -> ByteString -- | Returns the URI requested by the client. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqURI `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "foo/bar" --[rqURI] :: Request -> ByteString -- | Returns the HTTP query string for this Request. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> rq <- T.buildRequest (T.get "/foo/bar" (M.fromList [("name", ["value"])]))
-- ghci> rqQueryString rq
-- "name=value"
--
[rqQueryString] :: Request -> ByteString
-- | Returns the parameters mapping for this Request. "Parameters"
-- are automatically decoded from the URI's query string and
-- POST body and entered into this mapping. The rqParams
-- value is thus a union of rqQueryParams and rqPostParams.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqParams rq
-- fromList [("baz",["qux","quux"])]
--
[rqParams] :: Request -> Params
-- | The parameter mapping decoded from the URI's query string.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqQueryParams rq
-- fromList [("baz",["quux"])]
--
[rqQueryParams] :: Request -> Params
-- | The parameter mapping decoded from the POST body. Note that Snap only
-- auto-decodes POST request bodies when the request's
-- Content-Type is application/x-www-form-urlencoded.
-- For multipart/form-data use handleFileUploads to
-- decode the POST request and fill this mapping.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqPostParams rq
-- fromList [("baz",["qux"])]
--
[rqPostParams] :: Request -> Params
type StreamProc = OutputStream Builder -> IO (OutputStream Builder)
data ResponseBody
-- | output body is a function that writes to a Builder stream
Stream :: StreamProc -> ResponseBody
-- | output body is sendfile(), optional second argument is a byte range to
-- send
SendFile :: FilePath -> Maybe (Word64, Word64) -> ResponseBody
rspBodyMap :: (StreamProc -> StreamProc) -> ResponseBody -> ResponseBody
rspBodyToEnum :: ResponseBody -> StreamProc
-- | Represents an HTTP response.
data Response
Response :: Headers -> Map ByteString Cookie -> !Maybe Word64 -> ResponseBody -> !Int -> !ByteString -> !Bool -> Response
[rspHeaders] :: Response -> Headers
[rspCookies] :: Response -> Map ByteString Cookie
-- | We will need to inspect the content length no matter what, and looking
-- up "content-length" in the headers and parsing the number out of the
-- text will be too expensive.
[rspContentLength] :: Response -> !Maybe Word64
[rspBody] :: Response -> ResponseBody
-- | Returns the HTTP status code.
--
-- Example:
--
-- -- ghci> rspStatus emptyResponse -- 200 --[rspStatus] :: Response -> !Int -- | Returns the HTTP status explanation string. -- -- Example: -- --
-- ghci> rspStatusReason emptyResponse -- OK --[rspStatusReason] :: Response -> !ByteString -- | If true, we are transforming the request body with -- transformRequestBody [rspTransformingRqBody] :: Response -> !Bool -- | Looks up the value(s) for the given named parameter. Parameters -- initially come from the request's query string and any decoded POST -- body (if the request's Content-Type is -- application/x-www-form-urlencoded). Parameter values can be -- modified within handlers using "rqModifyParams". -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqParam "baz" rq
-- Just ["qux","quux"]
--
rqParam :: ByteString -> Request -> Maybe [ByteString]
-- | Looks up the value(s) for the given named parameter in the POST
-- parameters mapping.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqPostParam "baz" rq
-- Just ["qux"]
--
rqPostParam :: ByteString -> Request -> Maybe [ByteString]
-- | Looks up the value(s) for the given named parameter in the query
-- parameters mapping.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqQueryParam "baz" rq
-- Just ["quux"]
--
rqQueryParam :: ByteString -> Request -> Maybe [ByteString]
-- | Modifies the parameters mapping (which is a Map ByteString
-- ByteString) in a Request using the given function.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqParams rq
-- fromList [("baz",["qux","quux"])]
-- ghci> rqParams $ rqModifyParams (M.delete "baz") rq
-- fromList []
--
rqModifyParams :: (Params -> Params) -> Request -> Request
-- | Writes a key-value pair to the parameters mapping within the given
-- request.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqParams rq
-- fromList [("baz",["qux","quux"])]
-- ghci> rqParams $ rqSetParam "baz" ["corge"] rq
-- fromList [("baz", ["corge"])]
--
rqSetParam :: ByteString -> [ByteString] -> Request -> Request
-- | An empty Response.
--
-- Example:
--
-- -- ghci> emptyResponse -- HTTP/1.1 200 OK --emptyResponse :: Response -- | Sets an HTTP response body to the given stream procedure. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified System.IO.Streams as Streams
-- ghci> import qualified Data.ByteString.Builder as Builder
-- ghci> :{
-- ghci| let r = setResponseBody
-- ghci| (out -> do
-- ghci| Streams.write (Just $ Builder.byteString "Hello, world!") out
-- ghci| return out)
-- ghci| emptyResponse
-- ghci| :}
-- ghci> r
-- HTTP/1.1 200 OK
--
-- Hello, world!
--
setResponseBody :: (OutputStream Builder -> IO (OutputStream Builder)) -> Response -> Response
-- | Sets the HTTP response status. Note: normally you would use
-- setResponseCode unless you needed a custom response
-- explanation.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> setResponseStatus 500 "Internal Server Error" emptyResponse -- HTTP/1.1 500 Internal Server Error --setResponseStatus :: Int -> ByteString -> Response -> Response -- | Sets the HTTP response code. -- -- Example: -- --
-- ghci> setResponseCode 404 emptyResponse -- HTTP/1.1 404 Not Found --setResponseCode :: Int -> Response -> Response -- | Modifies a response body. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified System.IO.Streams as Streams
-- ghci> import qualified Data.ByteString.Builder as Builder
-- ghci> :{
-- ghci| let r = setResponseBody
-- ghci| (out -> do
-- ghci| Streams.write (Just $ Builder.byteString "Hello, world!") out
-- ghci| return out)
-- ghci| emptyResponse
-- ghci| :}
-- ghci> r
-- HTTP/1.1 200 OK
--
-- Hello, world!
-- ghci> :{
-- ghci| let r' = modifyResponseBody
-- ghci| (f out -> do
-- ghci| out' <- f out
-- ghci| Streams.write (Just $ Builder.byteString "\nBye, world!") out'
-- ghci| return out') r
-- ghci| :}
-- ghci> r'
-- HTTP/1.1 200 OK
--
-- Hello, world!
-- Bye, world!
--
modifyResponseBody :: ((OutputStream Builder -> IO (OutputStream Builder)) -> OutputStream Builder -> IO (OutputStream Builder)) -> Response -> Response
-- | Sets the Content-Type in the Response headers.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> setContentType "text/html" emptyResponse -- HTTP/1.1 200 OK -- content-type: text/html --setContentType :: ByteString -> Response -> Response -- | Convert Cookie into ByteString for output. -- -- TODO: Remove duplication. This function is copied from -- snap-server/Snap.Internal.Http.Server.Session. cookieToBS :: Cookie -> ByteString -- | Render cookies from a given Response to Headers. -- -- TODO: Remove duplication. This function is copied from -- snap-server/Snap.Internal.Http.Server.Session. renderCookies :: Response -> Headers -> Headers -- | Adds an HTTP Cookie to Response headers. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
-- ghci> getResponseCookie "name" $ addResponseCookie cookie emptyResponse
-- Just (Cookie {cookieName = "name", cookieValue = "value", ...})
--
addResponseCookie :: Cookie -> Response -> Response
-- | Gets an HTTP Cookie with the given name from Response
-- headers.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> getResponseCookie "cookie-name" emptyResponse -- Nothing --getResponseCookie :: ByteString -> Response -> Maybe Cookie -- | Returns a list of Cookies present in Response -- -- Example: -- --
-- ghci> getResponseCookies emptyResponse -- [] --getResponseCookies :: Response -> [Cookie] -- | Deletes an HTTP Cookie from the Response headers. Please -- note this does not necessarily erase the cookie from the client -- browser. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
-- ghci> let rsp = addResponseCookie cookie emptyResponse
-- ghci> getResponseCookie "name" rsp
-- Just (Cookie {cookieName = "name", cookieValue = "value", ...})
-- ghci> getResponseCookie "name" $ deleteResponseCookie "name" rsp
-- Nothing
--
deleteResponseCookie :: ByteString -> Response -> Response
-- | Modifies an HTTP Cookie with given name in Response
-- headers. Nothing will happen if a matching Cookie can not be
-- found in Response.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import Data.Monoid
-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
-- ghci> let rsp = addResponseCookie cookie emptyResponse
-- ghci> getResponseCookie "name" rsp
-- Just (Cookie {cookieName = "name", cookieValue = "value", ...})
-- ghci> let f ck@(Cookie { cookieName = name }) = ck { cookieName = name <> "'"}
-- ghci> let rsp' = modifyResponseCookie "name" f rsp
-- ghci> getResponseCookie "name'" rsp'
-- Just (Cookie {cookieName = "name'", ...})
-- ghci> getResponseCookie "name" rsp'
-- Just (Cookie {cookieName = "name", ...})
--
modifyResponseCookie :: ByteString -> (Cookie -> Cookie) -> Response -> Response
-- | A note here: if you want to set the Content-Length for the
-- response, Snap forces you to do it with this function rather than by
-- setting it in the headers; the Content-Length in the headers
-- will be ignored.
--
-- The reason for this is that Snap needs to look up the value of
-- Content-Length for each request, and looking the string value
-- up in the headers and parsing the number out of the text will be too
-- expensive.
--
-- If you don't set a content length in your response, HTTP keep-alive
-- will be disabled for HTTP/1.0 clients, forcing a Connection:
-- close. For HTTP/1.1 clients, Snap will switch to the chunked
-- transfer encoding if Content-Length is not specified.
--
-- Example:
--
-- -- ghci> setContentLength 400 emptyResponse -- HTTP/1.1 200 OK -- Content-Length: 400 --setContentLength :: Word64 -> Response -> Response -- | Removes any Content-Length set in the Response. -- -- Example: -- --
-- ghci> clearContentLength $ setContentLength 400 emptyResponse -- HTTP/1.1 200 OK --clearContentLength :: Response -> Response -- | Convert a CTime into an HTTP timestamp. -- -- Example: -- --
-- ghci> formatHttpTime . fromIntegral $ 10 -- "Thu, 01 Jan 1970 00:00:10 GMT" --formatHttpTime :: CTime -> IO ByteString -- | Convert a CTime into common log entry format. formatLogTime :: CTime -> IO ByteString -- | Converts an HTTP timestamp into a CTime. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> parseHttpTime "Thu, 01 Jan 1970 00:00:10 GMT" -- 10 --parseHttpTime :: ByteString -> IO CTime -- | Adapted from: -- -- -- https://www.iana.org/assignments/http-status-codes/http-status-codes.txt statusReasonMap :: IntMap ByteString -- | See rqClientAddr. -- | Deprecated: (snap-core >= 1.0.0.0) please use -- rqClientAddr, this will be removed in 1.1.* rqRemoteAddr :: Request -> ByteString -- | See rqClientPort. -- | Deprecated: (snap-core >= 1.0.0.0) please use -- rqClientPort, this will be removed in 1.1.* rqRemotePort :: Request -> Int instance GHC.Show.Show Snap.Internal.Http.Types.Cookie instance GHC.Classes.Eq Snap.Internal.Http.Types.Cookie instance GHC.Read.Read Snap.Internal.Http.Types.Method instance GHC.Show.Show Snap.Internal.Http.Types.Method instance GHC.Show.Show Snap.Internal.Http.Types.Response instance Snap.Internal.Http.Types.HasHeaders Snap.Internal.Http.Types.Response instance GHC.Show.Show Snap.Internal.Http.Types.Request instance Snap.Internal.Http.Types.HasHeaders Snap.Internal.Http.Types.Request instance GHC.Classes.Eq Snap.Internal.Http.Types.Method instance GHC.Classes.Ord Snap.Internal.Http.Types.Method instance Snap.Internal.Http.Types.HasHeaders Snap.Types.Headers.Headers module Snap.Internal.Parsing fullyParse :: ByteString -> Parser a -> Either String a (>) :: Parser a -> String -> Parser a infix 0 > fullyParse' :: (Parser a -> ByteString -> Result a) -> (Result a -> ByteString -> Result a) -> ByteString -> Parser a -> Either String a parseNum :: Parser Int64 untilEOL :: Parser ByteString crlf :: Parser ByteString toTableList :: (Char -> Bool) -> [Char] toTable :: (Char -> Bool) -> Char -> Bool skipFieldChars :: Parser () isFieldChar :: Char -> Bool -- | Parser for request headers. pHeaders :: Parser [(ByteString, ByteString)] pWord :: Parser ByteString pWord' :: (Char -> Bool) -> Parser ByteString pQuotedString :: Parser ByteString pQuotedString' :: (Char -> Bool) -> Parser ByteString isRFCText :: Char -> Bool matchAll :: [Char -> Bool] -> Char -> Bool pAvPairs :: Parser [(ByteString, ByteString)] pAvPair :: Parser (ByteString, ByteString) pParameter :: Parser (ByteString, ByteString) pParameter' :: (Char -> Bool) -> Parser (ByteString, ByteString) trim :: ByteString -> ByteString pValueWithParameters :: Parser (ByteString, [(CI ByteString, ByteString)]) pValueWithParameters' :: (Char -> Bool) -> Parser (ByteString, [(CI ByteString, ByteString)]) pContentTypeWithParameters :: Parser (ByteString, [(CI ByteString, ByteString)]) pToken :: Parser ByteString isToken :: Char -> Bool -- | Used for "token": comma-separated tokens/field-names, like a header -- field list. pTokens :: Parser [ByteString] parseToCompletion :: Parser a -> ByteString -> Maybe a type DList a = [a] -> [a] pUrlEscaped :: Parser ByteString -- | Decode an URL-escaped string (see -- http://tools.ietf.org/html/rfc2396.html#section-2.4) -- -- Example: -- --
-- ghci> urlDecode "1+attoparsec+%7e%3d+3+*+10%5e-2+meters" -- Just "1 attoparsec ~= 3 * 10^-2 meters" --urlDecode :: ByteString -> Maybe ByteString -- | URL-escape a string (see -- http://tools.ietf.org/html/rfc2396.html#section-2.4) -- -- Example: -- --
-- ghci> urlEncode "1 attoparsec ~= 3 * 10^-2 meters" -- "1+attoparsec+%7e%3d+3+*+10%5e-2+meters" --urlEncode :: ByteString -> ByteString -- | URL-escape a string (see -- http://tools.ietf.org/html/rfc2396.html#section-2.4) into a -- Builder. -- -- Example: -- --
-- ghci> import Data.ByteString.Builder -- ghci> toLazyByteString . urlEncodeBuilder $ "1 attoparsec ~= 3 * 10^-2 meters" -- "1+attoparsec+%7e%3d+3+*+10%5e-2+meters" --urlEncodeBuilder :: ByteString -> Builder urlEncodeClean :: Char -> Bool hexd :: Char -> Builder finish :: Result a -> Result a -- | Parse a string encoded in application/x-www-form-urlencoded -- format. -- -- Example: -- --
-- ghci> parseUrlEncoded "Name=John+Doe&Name=Jane+Doe&Age=23&Formula=a+%2B+b+%3D%3D+13%25%21" -- fromList [(Age,["23"]),(Formula,["a + b == 13%!"]),(Name,["John Doe","Jane Doe"])] --parseUrlEncoded :: ByteString -> Map ByteString [ByteString] -- | Like printUrlEncoded, but produces a Builder instead of -- a ByteString. Useful for constructing a large string -- efficiently in a single step. -- -- Example: -- --
-- ghci> import Data.Map -- ghci> import Data.Monoid -- ghci> import Data.ByteString.Builder -- ghci> let bldr = buildUrlEncoded (fromList [(Name, ["John Doe"]), (Age, ["23"])]) -- ghci> toLazyByteString $ byteString "http://example.com/script?" <> bldr -- "http://example.com/script?Age=23&Name=John+Doe" --buildUrlEncoded :: Map ByteString [ByteString] -> Builder -- | Given a collection of key-value pairs with possibly duplicate keys -- (represented as a Map), construct a string in -- application/x-www-form-urlencoded format. -- -- Example: -- --
-- ghci> printUrlEncoded (fromList [(Name, ["John Doe"]), (Age, ["23"])]) -- "Age=23&Name=John+Doe" --printUrlEncoded :: Map ByteString [ByteString] -> ByteString pCookies :: Parser [Cookie] parseCookie :: ByteString -> Maybe [Cookie] unsafeFromHex :: (Enum a, Num a, Bits a) => ByteString -> a unsafeFromNat :: (Enum a, Num a, Bits a) => ByteString -> a module Snap.Internal.Core -- | MonadSnap is a type class, analogous to MonadIO for -- IO, that makes it easy to wrap Snap inside monad -- transformers. class (Monad m, MonadIO m, MonadBaseControl IO m, MonadPlus m, Functor m, Applicative m, Alternative m) => MonadSnap m -- | Lift a computation from the Snap monad. liftSnap :: MonadSnap m => Snap a -> m a data SnapResult a SnapValue :: a -> SnapResult a Zero :: Zero -> SnapResult a -- | Type of external handler passed to escapeHttp. type EscapeHttpHandler = ((Int -> Int) -> IO ()) " timeout modifier" -> InputStream ByteString " socket read end" -> OutputStream Builder " socket write end" -> IO () -- | Used internally to implement escapeHttp. data EscapeSnap TerminateConnection :: SomeException -> EscapeSnap EscapeHttp :: EscapeHttpHandler -> EscapeSnap data Zero PassOnProcessing :: Zero EarlyTermination :: Response -> Zero EscapeSnap :: EscapeSnap -> Zero -- | Snap is the Monad that user web handlers run in. -- Snap gives you: -- --
printRqContextPath :: Snap () printRqContextPath = -- writeBS . rqContextPath =<< getRequest --
printRspStatusReason :: Snap () -- printRspStatusReason = writeBS . rspStatusReason -- =<< getResponse
a :: Snap String a = -- pass b :: Snap String b = return "foo" c :: Snap String c = a -- <|> b -- try running a, if it fails then try b --
example :: -- (OutputStream Builder -> IO (OutputStream -- Builder)) -> Snap () example streamProc = do writeBS -- "I'm a strict bytestring" writeLBS "I'm a lazy bytestring" -- writeText "I'm strict text" addToOutput streamProc --
a :: Snap () -- a = do modifyResponse $ setResponseStatus 500 "Internal -- Server Error" writeBS "500 error" r <- getResponse -- finishWith rthen any subsequent processing will be -- skipped and the supplied Response value will be returned from -- runSnap as-is.
a :: Snap () a = liftIO fireTheMissiles --
a :: Snap () a = setTimeout 30
import Control.Exception.Lifted -- (SomeException, throwIO, catch) foo :: Snap () -- foo = bar `catch` (e::SomeException) -> baz where bar = -- throwIO FooException
foo :: Snap () foo = -- logError "grumble."
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> import qualified Data.ByteString.Lazy as L
-- ghci> import Data.Char (toUpper)
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified System.IO.Streams as Streams
-- ghci> let r = T.put "/foo" "text/plain" "some text"
-- ghci> :{
-- ghci| let f s = do u <- Streams.map (B8.map toUpper) s
-- ghci| l <- Streams.toList u
-- ghci| return $ L.fromChunks l
-- ghci| :}
-- ghci> T.runHandler r (runRequestBody f >>= writeLBS)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Thu, 07 Aug 2014 20:48:40 GMT
--
-- SOME TEXT
--
runRequestBody :: MonadSnap m => (InputStream ByteString -> IO a) -> m a
-- | Returns the request body as a lazy bytestring. /Note that the request
-- is not actually provided lazily!/
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.put "/foo" "text/plain" "some text" -- ghci> T.runHandler r (readRequestBody 2048 >>= writeLBS) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 20:08:44 GMT -- -- some text ---- -- Since: 0.6 readRequestBody :: MonadSnap m => Word64 -> m ByteString -- | Normally Snap is careful to ensure that the request body is fully -- consumed after your web handler runs, but before the Response -- body is streamed out the socket. If you want to transform the request -- body into some output in O(1) space, you should use this function. -- -- Take care: in order for this to work, the HTTP client must be written -- with input-to-output streaming in mind. -- -- Note that upon calling this function, response processing finishes -- early as if you called finishWith. Make sure you set any -- content types, headers, cookies, etc. before you call this function. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.ByteString.Char8 as B8 -- ghci> import Data.Char (toUpper) -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import qualified System.IO.Streams as Streams -- ghci> let r = T.put "/foo" "text/plain" "some text" -- ghci> let f = Streams.map (B8.map toUpper) -- ghci> T.runHandler r (transformRequestBody f >> readRequestBody 2048 >>= writeLBS) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 20:30:15 GMT -- -- SOME TEXT --transformRequestBody :: (InputStream ByteString -> IO (InputStream ByteString)) -> Snap () -- | Short-circuits a Snap monad action early, storing the given -- Response value in its state. -- -- IMPORTANT: Be vary careful when using this with things like a DB -- library's withTransaction function or any other kind of -- setup/teardown block, as it can prevent the cleanup from being called -- and result in resource leaks. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import Control.Applicative -- ghci> let r = T.get "/" M.empty -- ghci> T.runHandler r ((ifTop $ writeBS "TOP") <|> finishWith emptyResponse) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 16:58:57 GMT -- -- TOP -- ghci> let r' = T.get "/foo/bar" M.empty -- ghci> T.runHandler r' ((ifTop $ writeBS "TOP") <|> finishWith emptyResponse) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 17:50:50 GMT --finishWith :: MonadSnap m => Response -> m a -- | Capture the flow of control in case a handler calls finishWith. -- -- WARNING: in the event of a call to transformRequestBody -- it is possible to violate HTTP protocol safety when using this -- function. If you call catchFinishWith it is suggested that you -- do not modify the body of the Response which was passed to the -- finishWith call. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.ByteString.Char8 as B8 -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import Control.Applicative -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> let h = (ifTop $ writeBS "TOP") <|> finishWith emptyResponse -- ghci> T.runHandler r (catchFinishWith h >>= writeBS . B8.pack . show) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 18:35:42 GMT -- -- Left HTTP/1.1 200 OK --catchFinishWith :: Snap a -> Snap (Either Response a) -- | Fails out of a Snap monad action. This is used to indicate that -- you choose not to handle the given request within the given handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r pass -- HTTP/1.1 404 Not Found -- server: Snap/test -- date: Thu, 07 Aug 2014 13:35:42 GMT -- -- <!DOCTYPE html> -- <html> -- <head> -- <title>Not found</title> -- </head> -- <body> -- <code>No handler accepted "/foo/bar"/code -- </body></html> --pass :: MonadSnap m => m a -- | Runs a Snap monad action only if the request's HTTP method -- matches the given method. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (method GET $ writeBS "OK") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 13:38:48 GMT -- -- OK -- ghci> T.runHandler r (method POST $ writeBS "OK") -- HTTP/1.1 404 Not Found -- ... --method :: MonadSnap m => Method -> m a -> m a -- | Runs a Snap monad action only if the request's HTTP method -- matches one of the given methods. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (methods [GET, POST] $ writeBS "OK") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 13:38:48 GMT -- -- OK -- ghci> T.runHandler r (methods [POST] $ writeBS "OK") -- HTTP/1.1 404 Not Found -- ... --methods :: MonadSnap m => [Method] -> m a -> m a updateContextPath :: Int -> Request -> Request pathWith :: MonadSnap m => (ByteString -> ByteString -> Bool) -> ByteString -> m a -> m a -- | Runs a Snap monad action only when the rqPathInfo of the -- request starts with the given path. For example, -- --
-- dir "foo" handler ---- -- Will fail if rqPathInfo is not "/foo" or -- "/foo/...", and will add "foo/" to the handler's -- local rqContextPath. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (dir "foo" $ writeBS "OK") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 14:52:24 GMT -- -- OK -- ghci> T.runHandler r (dir "baz" $ writeBS "OK") -- HTTP/1.1 404 Not Found -- ... --dir :: MonadSnap m => ByteString -> m a -> m a -- | Runs a Snap monad action only for requests where -- rqPathInfo is exactly equal to the given string. If the path -- matches, locally sets rqContextPath to the old value of -- rqPathInfo, sets rqPathInfo="", and runs the given -- handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> T.runHandler (T.get "/foo" M.empty) (path "foo" $ writeBS "bar") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 14:15:42 GMT -- -- bar -- ghci> T.runHandler (T.get "/foo" M.empty) (path "bar" $ writeBS "baz") -- HTTP/1.1 404 Not Found -- ... --path :: MonadSnap m => ByteString -> m a -> m a -- | Runs a Snap monad action only when the first path component is -- successfully parsed as the argument to the supplied handler function. -- -- Note that the path segment is url-decoded prior to being passed to -- fromBS; this is new as of snap-core 0.10. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/11/foo/bar" M.empty -- ghci> let f = (\i -> if i == 11 then writeBS "11" else writeBS "???") -- ghci> T.runHandler r (pathArg f) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 14:27:10 GMT -- -- 11 -- ghci> let r' = T.get "/foo/11/bar" M.empty -- ghci> T.runHandler r' (pathArg f) -- HTTP/1.1 404 Not Found -- ... --pathArg :: (Readable a, MonadSnap m) => (a -> m b) -> m b -- | Runs a Snap monad action only when rqPathInfo is empty. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/" M.empty -- ghci> T.runHandler r (ifTop $ writeBS OK) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 14:56:39 GMT -- -- OK -- ghci> let r' = T.get "/foo" M.empty -- ghci> T.runHandler r' (ifTop $ writeBS "OK") -- HTTP/1.1 404 Not Found -- ... --ifTop :: MonadSnap m => m a -> m a -- | Local Snap version of get. sget :: Snap SnapState -- | Local Snap monad version of modify. smodify :: (SnapState -> SnapState) -> Snap () -- | Grabs the Request object out of the Snap monad. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeBS . rqURI =<< getRequest) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Sat, 02 Aug 2014 07:51:54 GMT -- -- /foo/bar --getRequest :: MonadSnap m => m Request -- | Grabs the Response object out of the Snap monad. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeBS . rspStatusReason =<< getResponse) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Sat, 02 Aug 2014 15:06:00 GMT -- -- OK --getResponse :: MonadSnap m => m Response -- | Grabs something out of the Request object, using the given -- projection function. See gets. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeBS =<< getsRequest rqURI) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Sat, 02 Aug 2014 07:51:54 GMT -- -- /foo/bar --getsRequest :: MonadSnap m => (Request -> a) -> m a -- | Grabs something out of the Response object, using the given -- projection function. See gets. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeBS =<< getsResponse rspStatusReason) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 13:35:45 GMT -- -- OK --getsResponse :: MonadSnap m => (Response -> a) -> m a -- | Puts a new Request object into the Snap monad. Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> :{
-- ghci| let hndlr = do rq <- T.buildRequest (T.get "/bar/foo" M.empty)
-- ghci| putRequest rq
-- ghci| uri' <- getsRequest rqURI
-- ghci| writeBS uri'
-- ghci| :}
-- ghci> T.runHandler (T.get "/foo/bar" M.empty) hndlr
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Wed, 06 Aug 2014 15:13:46 GMT
--
-- /bar/foo
--
putRequest :: MonadSnap m => Request -> m ()
-- | Puts a new Response object into the Snap monad.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let rsp = setResponseCode 404 emptyResponse -- ghci> let req = T.get "/foo/bar" M.empty -- ghci> T.runHandler req (putResponse rsp) -- HTTP/1.1 404 Not Found -- server: Snap/test -- date: Wed, 06 Aug 2014 13:59:58 GMT --putResponse :: MonadSnap m => Response -> m () -- | Modifies the Request object stored in a Snap monad. -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> r' <- T.buildRequest $ T.get "/bar/foo" M.empty -- ghci> T.runHandler r (modifyRequest (const r') >> getsRequest rqURI >>= writeBS) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 15:24:25 GMT -- -- /bar/foo --modifyRequest :: MonadSnap m => (Request -> Request) -> m () -- | Modifes the Response object stored in a Snap monad. -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (modifyResponse $ setResponseCode 404) -- HTTP/1.1 404 Not Found -- server: Snap/test -- date: Wed, 06 Aug 2014 15:27:11 GMT --modifyResponse :: MonadSnap m => (Response -> Response) -> m () -- | Performs a redirect by setting the Location header to the -- given target URL/path and the status code to 302 in the -- Response object stored in a Snap monad. Note that the -- target URL is not validated in any way. Consider using -- redirect' instead, which allows you to choose the correct -- status code. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (redirect "http://snapframework.com") -- HTTP/1.1 302 Found -- content-length: 0 -- location: http://snapframework.com -- server: Snap/test -- date: Thu, 07 Aug 2014 08:52:11 GMT -- Content-Length: 0 --redirect :: MonadSnap m => ByteString -> m a -- | Performs a redirect by setting the Location header to the -- given target URL/path and the status code (should be one of 301, 302, -- 303 or 307) in the Response object stored in a Snap -- monad. Note that the target URL is not validated in any way. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (redirect' "http://snapframework.com" 301) -- HTTP/1.1 307 Temporary Redirect -- content-length: 0 -- location: http://snapframework.com -- server: Snap/test -- date: Thu, 07 Aug 2014 08:55:51 GMT -- Content-Length: 0 --redirect' :: MonadSnap m => ByteString -> Int -> m a -- | Log an error message in the Snap monad. -- -- Example: -- --
-- ghci> import qualified Data.ByteString.Char8 as B8 -- ghci> runSnap (logError "fatal error!") (error . B8.unpack) undefined undefined -- *** Exception: fatal error! --logError :: MonadSnap m => ByteString -> m () -- | Run the given stream procedure, adding its output to the -- Response stored in the Snap monad state. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Builder as B
-- ghci> import qualified System.IO.Streams as Streams
-- ghci> let r = T.get "/foo/bar" M.empty
-- ghci> :{
-- ghci| let f str = do {
-- ghci| Streams.write (Just $ B.byteString "Hello, streams world") str;
-- ghci| return str }
-- ghci| :}
-- ghci> T.runHandler r (addToOutput f)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Wed, 06 Aug 2014 17:55:47 GMT
--
-- Hello, streams world
--
addToOutput :: MonadSnap m => (OutputStream Builder -> IO (OutputStream Builder)) -> m ()
-- | Adds the given Builder to the body of the Response
-- stored in the | Snap monad state.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.ByteString.Builder as B -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeBuilder $ B.byteString "Hello, world") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 17:33:33 GMT -- -- Hello, world --writeBuilder :: MonadSnap m => Builder -> m () -- | Adds the given strict ByteString to the body of the -- Response stored in the Snap monad state. -- -- Warning: This function is intentionally non-strict. If any pure -- exceptions are raised by the expression creating the -- ByteString, the exception won't actually be raised within the -- Snap handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeBS "Hello, bytestring world") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 17:34:27 GMT -- -- Hello, bytestring world --writeBS :: MonadSnap m => ByteString -> m () -- | Adds the given lazy ByteString to the body of the -- Response stored in the Snap monad state. -- -- Warning: This function is intentionally non-strict. If any pure -- exceptions are raised by the expression creating the -- ByteString, the exception won't actually be raised within the -- Snap handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeLBS "Hello, lazy bytestring world") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 17:35:15 GMT -- -- Hello, lazy bytestring world --writeLBS :: MonadSnap m => ByteString -> m () -- | Adds the given strict Text to the body of the Response -- stored in the Snap monad state. -- -- Warning: This function is intentionally non-strict. If any pure -- exceptions are raised by the expression creating the -- ByteString, the exception won't actually be raised within the -- Snap handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeText "Hello, text world") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 17:36:38 GMT -- -- Hello, text world --writeText :: MonadSnap m => Text -> m () -- | Adds the given lazy Text to the body of the Response -- stored in the Snap monad state. -- -- Warning: This function is intentionally non-strict. If any pure -- exceptions are raised by the expression creating the -- ByteString, the exception won't actually be raised within the -- Snap handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeLazyText "Hello, lazy text world") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 17:37:41 GMT -- -- Hello, lazy text world --writeLazyText :: MonadSnap m => Text -> m () -- | Sets the output to be the contents of the specified file. -- -- Calling sendFile will overwrite any output queued to be sent in -- the Response. If the response body is not modified after the -- call to sendFile, Snap will use the efficient -- sendfile() system call on platforms that support it. -- -- If the response body is modified (using modifyResponseBody), -- the file will be read using mmap(). -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> writeFile "/tmp/snap-file" "Hello, sendFile world" -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (sendFile "/tmp/snap-file") -- HTTP/1.1 200 OK -- content-length: 21 -- server: Snap/test -- date: Wed, 06 Aug 2014 17:45:10 GMT -- Content-Length: 21 -- -- Hello, sendFile world --sendFile :: MonadSnap m => FilePath -> m () -- | Sets the output to be the contents of the specified file, within the -- given (start,end) range. -- -- Calling sendFilePartial will overwrite any output queued to be -- sent in the Response. If the response body is not modified -- after the call to sendFilePartial, Snap will use the efficient -- sendfile() system call on platforms that support it. -- -- If the response body is modified (using modifyResponseBody), -- the file will be read using mmap(). -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> writeFile "/tmp/snap-file" "Hello, sendFilePartial world" -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (sendFilePartial "/tmp/snap-file" (7, 28)) -- HTTP/1.1 200 OK -- content-length: 21 -- server: Snap/test -- date: Wed, 06 Aug 2014 17:47:20 GMT -- Content-Length: 21 -- -- sendFilePartial world --sendFilePartial :: MonadSnap m => FilePath -> (Word64, Word64) -> m () -- | Runs a Snap action with a locally-modified Request state -- object. The Request object in the Snap monad state after the -- call to localRequest will be unchanged. Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> r' <- T.buildRequest $ T.get "/bar/foo" M.empty -- ghci> let printRqURI = getsRequest rqURI >>= writeBS >> writeBS "\n" -- ghci> T.runHandler r (printRqURI >> localRequest (const r') printRqURI) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 15:34:12 GMT -- -- /foo/bar -- /bar/foo --localRequest :: MonadSnap m => (Request -> Request) -> m a -> m a -- | Fetches the Request from state and hands it to the given -- action. Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import Control.Monad.IO.Class -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> let h = withRequest (\rq -> liftIO (T.requestToString rq) >>= writeBS) -- ghci> T.runHandler r h -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 15:44:24 GMT -- -- GET /foo/bar HTTP/1.1 -- host: localhost --withRequest :: MonadSnap m => (Request -> m a) -> m a -- | Fetches the Response from state and hands it to the given -- action. Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (withResponse $ writeBS . rspStatusReason) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 15:48:45 GMT -- -- OK --withResponse :: MonadSnap m => (Response -> m a) -> m a -- | Modifies the Request in the state to set the -- rqRemoteAddr field to the value in the X-Forwarded-For -- header. If the header is not present, this action has no effect. -- -- This action should be used only when working behind a reverse http -- proxy that sets the X-Forwarded-For header. This is the only way to -- ensure the value in the X-Forwarded-For header can be trusted. -- -- This is provided as a filter so actions that require the remote -- address can get it in a uniform manner. It has specifically limited -- functionality to ensure that its transformation can be trusted, when -- used correctly. ipHeaderFilter :: MonadSnap m => m () -- | Modifies the Request in the state to set the -- rqRemoteAddr field to the value from the header specified. If -- the header specified is not present, this action has no effect. -- -- This action should be used only when working behind a reverse http -- proxy that sets the header being looked at. This is the only way to -- ensure the value in the header can be trusted. -- -- This is provided as a filter so actions that require the remote -- address can get it in a uniform manner. It has specifically limited -- functionality to ensure that its transformation can be trusted, when -- used correctly. ipHeaderFilter' :: MonadSnap m => CI ByteString -> m () -- | This function brackets a Snap action in resource acquisition and -- release. This is provided because MonadCatchIO's bracket -- function doesn't work properly in the case of a short-circuit return -- from the action being bracketed. -- -- In order to prevent confusion regarding the effects of the aquisition -- and release actions on the Snap state, this function doesn't accept -- Snap actions for the acquire or release actions. -- -- This function will run the release action in all cases where the -- acquire action succeeded. This includes the following behaviors from -- the bracketed Snap action. -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let br = bracketSnap (putStrLn "before") (const $ putStrLn "after") -- ghci> T.runHandler (T.get "/" M.empty) (br $ const $ writeBS "OK") -- before -- after -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 18:41:50 GMT -- -- OK --bracketSnap :: IO a -> (a -> IO b) -> (a -> Snap c) -> Snap c -- | This exception is thrown if the handler you supply to runSnap -- fails. data NoHandlerException NoHandlerException :: String -> NoHandlerException -- | Terminate the HTTP session with the given exception. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import qualified Control.Exception as E -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (terminateConnection $ E.AssertionFailed "Assertion failed!") -- *** Exception: <terminated: Assertion failed!> --terminateConnection :: (Exception e, MonadSnap m) => e -> m a -- | Terminate the HTTP session and hand control to some external handler, -- escaping all further HTTP traffic. -- -- The external handler takes three arguments: a function to modify the -- thread's timeout, and a read and a write ends to the socket. escapeHttp :: MonadSnap m => EscapeHttpHandler -> m () -- | Runs a Snap monad action. -- -- This function is mostly intended for library writers; instead of -- invoking runSnap directly, use httpServe or -- runHandler (for testing). runSnap :: Snap a -> (ByteString -> IO ()) -> ((Int -> Int) -> IO ()) -> Request -> IO (Request, Response) -- | Post-process a finalized HTTP response: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> let r = T.get "/foo/bar" $ M.fromList [("foo", ["bar"])]
-- ghci> T.runHandler r (getParam "foo" >>= writeBS . B8.pack . show)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Mon, 11 Aug 2014 12:57:20 GMT
--
-- Just "bar"
--
getParam :: MonadSnap m => ByteString -> m (Maybe ByteString)
-- | See rqPostParam. Looks up a value for the given named parameter
-- in the POST form parameters mapping in Request. If more than
-- one value was entered for the given parameter name,
-- getPostParam gloms the values together with:
-- intercalate " ".
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> let r = T.postUrlEncoded "/foo/bar" $ M.fromList [("foo", ["bar"])]
-- ghci> T.runHandler r (getPostParam "foo" >>= writeBS . B8.pack . show)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Mon, 11 Aug 2014 13:01:04 GMT
--
-- Just "bar"
--
getPostParam :: MonadSnap m => ByteString -> m (Maybe ByteString)
-- | See rqQueryParam. Looks up a value for the given named
-- parameter in the query string parameters mapping in Request. If
-- more than one value was entered for the given parameter name,
-- getQueryParam gloms the values together with
-- intercalate " ".
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.ByteString.Char8 as B8 -- ghci> let r = T.postUrlEncoded "/foo/bar" M.empty >> T.setQueryStringRaw "foo=bar&foo=baz" -- ghci> T.runHandler r (getQueryParam "foo" >>= writeBS . B8.pack . show) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Mon, 11 Aug 2014 13:06:50 GMT -- -- Just "bar baz" --getQueryParam :: MonadSnap m => ByteString -> m (Maybe ByteString) -- | See rqParams. Convenience function to return Params from -- the Request inside of a MonadSnap instance. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> let r = T.get "/foo/bar" $ M.fromList [("foo", ["bar"])]
-- ghci> T.runHandler r (getParams >>= writeBS . B8.pack . show)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Mon, 11 Aug 2014 13:02:54 GMT
--
-- fromList [("foo",["bar"])]
--
getParams :: MonadSnap m => m Params
-- | See rqParams. Convenience function to return Params from
-- the Request inside of a MonadSnap instance.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> let r = T.postUrlEncoded "/foo/bar" $ M.fromList [("foo", ["bar"])]
-- ghci> T.runHandler r (getPostParams >>= writeBS . B8.pack . show)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Mon, 11 Aug 2014 13:04:34 GMT
--
-- fromList [("foo",["bar"])]
--
getPostParams :: MonadSnap m => m Params
-- | See rqParams. Convenience function to return Params from
-- the Request inside of a MonadSnap instance.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> let r = T.postUrlEncoded "/foo/bar" M.empty >> T.setQueryStringRaw "foo=bar&foo=baz"
-- ghci> T.runHandler r (getQueryParams >>= writeBS . B8.pack . show)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Mon, 11 Aug 2014 13:10:17 GMT
--
-- fromList [("foo",["bar","baz"])]
--
getQueryParams :: MonadSnap m => m Params
-- | Gets the HTTP Cookie with the specified name.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
-- ghci> let r = T.get "/foo/bar" M.empty >> T.addCookies [cookie]
-- ghci> T.runHandler r (getCookie "name" >>= writeBS . B8.pack . show)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Thu, 07 Aug 2014 12:16:58 GMT
--
-- Just (Cookie {cookieName = "name", cookieValue = "value", ...})
--
getCookie :: MonadSnap m => ByteString -> m (Maybe Cookie)
-- | Gets the HTTP Cookie with the specified name and decodes it. If
-- the decoding fails, the handler calls pass.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False -- ghci> let r = T.get "/foo/bar" M.empty >> T.addCookies [cookie] -- ghci> T.runHandler r (readCookie "name" >>= writeBS) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 12:20:09 GMT -- -- value --readCookie :: (MonadSnap m, Readable a) => ByteString -> m a -- | Expire given Cookie in client's browser. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> let cookie = Cookie "name" "" Nothing (Just "/subsite") Nothing True False -- ghci> T.runHandler r (expireCookie cookie) -- HTTP/1.1 200 OK -- set-cookie: name=; path=/subsite; expires=Sat, 24 Dec 1994 06:28:16 GMT; Secure -- server: Snap/test -- -- date: Thu, 07 Aug 2014 12:21:27 GMT -- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False -- ghci> let r2 = T.get "/foo/bar" M.empty >> T.addCookies [cookie] -- ghci> T.runHandler r (getCookie "name" >>= maybe (return ()) expireCookie) -- HTTP/1.1 200 OK -- set-cookie: name=; expires=Sat, 24 Dec 1994 06:28:16 GMT -- server: Snap/test --expireCookie :: MonadSnap m => Cookie -> m () -- | Causes the handler thread to be killed n seconds from now. setTimeout :: MonadSnap m => Int -> m () -- | Causes the handler thread to be killed at least n seconds -- from now. extendTimeout :: MonadSnap m => Int -> m () -- | Modifies the amount of time remaining before the request times out. modifyTimeout :: MonadSnap m => (Int -> Int) -> m () -- | Returns an IO action which you can use to modify the timeout -- value. getTimeoutModifier :: MonadSnap m => m ((Int -> Int) -> IO ()) -- | Represents an HTTP response. data Response Response :: Headers -> Map ByteString Cookie -> !Maybe Word64 -> ResponseBody -> !Int -> !ByteString -> !Bool -> Response [rspHeaders] :: Response -> Headers [rspCookies] :: Response -> Map ByteString Cookie -- | We will need to inspect the content length no matter what, and looking -- up "content-length" in the headers and parsing the number out of the -- text will be too expensive. [rspContentLength] :: Response -> !Maybe Word64 [rspBody] :: Response -> ResponseBody -- | Returns the HTTP status code. -- -- Example: -- --
-- ghci> rspStatus emptyResponse -- 200 --[rspStatus] :: Response -> !Int -- | Returns the HTTP status explanation string. -- -- Example: -- --
-- ghci> rspStatusReason emptyResponse -- OK --[rspStatusReason] :: Response -> !ByteString -- | If true, we are transforming the request body with -- transformRequestBody [rspTransformingRqBody] :: Response -> !Bool data ResponseBody -- | output body is a function that writes to a Builder stream Stream :: StreamProc -> ResponseBody -- | output body is sendfile(), optional second argument is a byte range to -- send SendFile :: FilePath -> Maybe (Word64, Word64) -> ResponseBody type StreamProc = OutputStream Builder -> IO (OutputStream Builder) -- | Contains all of the information about an incoming HTTP request. data Request Request :: ByteString -> ByteString -> {-# UNPACK #-} !Int -> ByteString -> {-# UNPACK #-} !Int -> ByteString -> !Bool -> Headers -> InputStream ByteString -> !Maybe Word64 -> !Method -> {-# UNPACK #-} !HttpVersion -> [Cookie] -> ByteString -> ByteString -> ByteString -> ByteString -> Params -> Params -> Params -> Request -- | The server name of the request, as it came in from the request's -- Host: header. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.get "/foo/bar" M.empty
-- ghci| T.setHeader "host" "example.com"
-- ghci| :}
-- ghci> rqHostName rq
-- "example.com"
--
[rqHostName] :: Request -> ByteString
-- | The remote IP address.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqClientAddr `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "127.0.0.1" --[rqClientAddr] :: Request -> ByteString -- | The remote TCP port number. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqClientPort `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "60000" --[rqClientPort] :: Request -> {-# UNPACK #-} !Int -- | The local IP address for this request. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqServerAddr `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "127.0.0.1" --[rqServerAddr] :: Request -> ByteString -- | Returns the port number the HTTP server is listening on. This may be -- useless from the perspective of external requests, e.g. if the server -- is running behind a proxy. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqServerPort `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- 8080 --[rqServerPort] :: Request -> {-# UNPACK #-} !Int -- | Returns the HTTP server's idea of its local hostname, including port. -- This is as configured with the Config object at startup. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqLocalHostname `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "localhost" --[rqLocalHostname] :: Request -> ByteString -- | Returns True if this is an HTTPS session. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqIsSecure `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- False --[rqIsSecure] :: Request -> !Bool -- | Contains all HTTP Headers associated with this request. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> rqHeaders `fmap` T.buildRequest (T.get "/foo/bar" M.empty)
-- H {unH = [("host","localhost")]}
--
[rqHeaders] :: Request -> Headers
-- | Actual body of the request.
[rqBody] :: Request -> InputStream ByteString
-- | Returns the Content-Length of the HTTP request body.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqContentLength `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- Nothing --[rqContentLength] :: Request -> !Maybe Word64 -- | Returns the HTTP request method. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqMethod `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- GET --[rqMethod] :: Request -> !Method -- | Returns the HTTP version used by the client. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqVersion `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- (1,1) --[rqVersion] :: Request -> {-# UNPACK #-} !HttpVersion -- | Returns a list of the cookies that came in from the HTTP request -- headers. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqCookies `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- [] --[rqCookies] :: Request -> [Cookie] -- | Handlers can be hung on a URI "entry point"; this is called -- the "context path". If a handler is hung on the context path -- "/foo/", and you request "/foo/bar", the value of -- rqPathInfo will be "bar". -- -- The following identity holds: -- --
-- rqURI r == S.concat [ rqContextPath r -- , rqPathInfo r -- , let q = rqQueryString r -- in if S.null q -- then "" -- else S.append "?" q -- ] ---- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqPathInfo `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "foo/bar" --[rqPathInfo] :: Request -> ByteString -- | The "context path" of the request; catenating rqContextPath, -- and rqPathInfo should get you back to the original rqURI -- (ignoring query strings). The rqContextPath always begins and -- ends with a slash ("/") character, and represents the path -- (relative to your component/snaplet) you took to get to your handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqContextPath `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "/" --[rqContextPath] :: Request -> ByteString -- | Returns the URI requested by the client. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqURI `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "foo/bar" --[rqURI] :: Request -> ByteString -- | Returns the HTTP query string for this Request. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> rq <- T.buildRequest (T.get "/foo/bar" (M.fromList [("name", ["value"])]))
-- ghci> rqQueryString rq
-- "name=value"
--
[rqQueryString] :: Request -> ByteString
-- | Returns the parameters mapping for this Request. "Parameters"
-- are automatically decoded from the URI's query string and
-- POST body and entered into this mapping. The rqParams
-- value is thus a union of rqQueryParams and rqPostParams.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqParams rq
-- fromList [("baz",["qux","quux"])]
--
[rqParams] :: Request -> Params
-- | The parameter mapping decoded from the URI's query string.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqQueryParams rq
-- fromList [("baz",["quux"])]
--
[rqQueryParams] :: Request -> Params
-- | The parameter mapping decoded from the POST body. Note that Snap only
-- auto-decodes POST request bodies when the request's
-- Content-Type is application/x-www-form-urlencoded.
-- For multipart/form-data use handleFileUploads to
-- decode the POST request and fill this mapping.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqPostParams rq
-- fromList [("baz",["qux"])]
--
[rqPostParams] :: Request -> Params
-- | A type alias for the HTTP parameters mapping. Each parameter key maps
-- to a list of ByteString values; if a parameter is specified
-- multiple times (e.g.: "GET /foo?param=bar1¶m=bar2"),
-- looking up "param" in the mapping will give you ["bar1",
-- "bar2"].
type Params = Map ByteString [ByteString]
-- | A datatype representing an HTTP cookie.
data Cookie
Cookie :: !ByteString -> !ByteString -> !Maybe UTCTime -> !Maybe ByteString -> !Maybe ByteString -> !Bool -> !Bool -> Cookie
-- | The name of the cookie.
[cookieName] :: Cookie -> !ByteString
-- | The cookie's string value.
[cookieValue] :: Cookie -> !ByteString
-- | The cookie's expiration value, if it has one.
[cookieExpires] :: Cookie -> !Maybe UTCTime
-- | The cookie's "domain" value, if it has one.
[cookieDomain] :: Cookie -> !Maybe ByteString
-- | The cookie path.
[cookiePath] :: Cookie -> !Maybe ByteString
-- | Tag as secure cookie?
[cookieSecure] :: Cookie -> !Bool
-- | HTTP only?
[cookieHttpOnly] :: Cookie -> !Bool
-- | Represents a (major, minor) version of the HTTP protocol.
type HttpVersion = (Int, Int)
-- | Enumerates the HTTP method values (see
-- http://tools.ietf.org/html/rfc2068.html#section-5.1.1).
data Method
GET :: Method
HEAD :: Method
POST :: Method
PUT :: Method
DELETE :: Method
TRACE :: Method
OPTIONS :: Method
CONNECT :: Method
PATCH :: Method
Method :: ByteString -> Method
-- | A typeclass for datatypes which contain HTTP headers.
class HasHeaders a
-- | Modify the datatype's headers.
updateHeaders :: HasHeaders a => (Headers -> Headers) -> a -> a
-- | Retrieve the headers from a datatype that has headers.
headers :: HasHeaders a => a -> Headers
-- | Adds a header key-value-pair to the HasHeaders datatype. If a
-- header with the same name already exists, the new value is appended to
-- the headers list.
--
-- Example:
--
--
-- ghci> import qualified Snap.Types.Headers as H
-- ghci> addHeader "Host" "localhost" H.empty
-- H {unH = [("host","localhost")]}
-- ghci> addHeader "Host" "127.0.0.1" it
-- H {unH = [("host","localhost,127.0.0.1")]}
--
addHeader :: HasHeaders a => CI ByteString -> ByteString -> a -> a
-- | Sets a header key-value-pair in a HasHeaders datatype. If a
-- header with the same name already exists, it is overwritten with the
-- new value.
--
-- Example:
--
--
-- ghci> import qualified Snap.Types.Headers as H
-- ghci> setHeader "Host" "localhost" H.empty
-- H {unH = [("host","localhost")]}
-- ghci> setHeader "Host" "127.0.0.1" it
-- H {unH = [("host","127.0.0.1")]}
--
setHeader :: HasHeaders a => CI ByteString -> ByteString -> a -> a
-- | Gets a header value out of a HasHeaders datatype.
--
-- Example:
--
-- -- ghci> import qualified Snap.Types.Headers as H -- ghci> getHeader "Host" $ setHeader "Host" "localhost" H.empty -- Just "localhost" --getHeader :: HasHeaders a => CI ByteString -> a -> Maybe ByteString -- | Lists all the headers out of a HasHeaders datatype. If many -- headers came in with the same name, they will be catenated together. -- -- Example: -- --
-- ghci> import qualified Snap.Types.Headers as H
-- ghci> listHeaders $ setHeader "Host" "localhost" H.empty
-- [("host","localhost")]
--
listHeaders :: HasHeaders a => a -> [(CI ByteString, ByteString)]
-- | Clears a header value from a HasHeaders datatype.
--
-- Example:
--
--
-- ghci> import qualified Snap.Types.Headers as H
-- ghci> deleteHeader "Host" $ setHeader "Host" "localhost" H.empty
-- H {unH = []}
--
deleteHeader :: HasHeaders a => CI ByteString -> a -> a
-- | Equate the special case constructors with their corresponding
-- Method name variant.
normalizeMethod :: Method -> Method
rspBodyMap :: (StreamProc -> StreamProc) -> ResponseBody -> ResponseBody
rspBodyToEnum :: ResponseBody -> StreamProc
-- | Looks up the value(s) for the given named parameter. Parameters
-- initially come from the request's query string and any decoded POST
-- body (if the request's Content-Type is
-- application/x-www-form-urlencoded). Parameter values can be
-- modified within handlers using "rqModifyParams".
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqParam "baz" rq
-- Just ["qux","quux"]
--
rqParam :: ByteString -> Request -> Maybe [ByteString]
-- | Looks up the value(s) for the given named parameter in the POST
-- parameters mapping.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqPostParam "baz" rq
-- Just ["qux"]
--
rqPostParam :: ByteString -> Request -> Maybe [ByteString]
-- | Looks up the value(s) for the given named parameter in the query
-- parameters mapping.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqQueryParam "baz" rq
-- Just ["quux"]
--
rqQueryParam :: ByteString -> Request -> Maybe [ByteString]
-- | Modifies the parameters mapping (which is a Map ByteString
-- ByteString) in a Request using the given function.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqParams rq
-- fromList [("baz",["qux","quux"])]
-- ghci> rqParams $ rqModifyParams (M.delete "baz") rq
-- fromList []
--
rqModifyParams :: (Params -> Params) -> Request -> Request
-- | Writes a key-value pair to the parameters mapping within the given
-- request.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqParams rq
-- fromList [("baz",["qux","quux"])]
-- ghci> rqParams $ rqSetParam "baz" ["corge"] rq
-- fromList [("baz", ["corge"])]
--
rqSetParam :: ByteString -> [ByteString] -> Request -> Request
-- | An empty Response.
--
-- Example:
--
-- -- ghci> emptyResponse -- HTTP/1.1 200 OK --emptyResponse :: Response -- | Sets an HTTP response body to the given stream procedure. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified System.IO.Streams as Streams
-- ghci> import qualified Data.ByteString.Builder as Builder
-- ghci> :{
-- ghci| let r = setResponseBody
-- ghci| (out -> do
-- ghci| Streams.write (Just $ Builder.byteString "Hello, world!") out
-- ghci| return out)
-- ghci| emptyResponse
-- ghci| :}
-- ghci> r
-- HTTP/1.1 200 OK
--
-- Hello, world!
--
setResponseBody :: (OutputStream Builder -> IO (OutputStream Builder)) -> Response -> Response
-- | Sets the HTTP response status. Note: normally you would use
-- setResponseCode unless you needed a custom response
-- explanation.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> setResponseStatus 500 "Internal Server Error" emptyResponse -- HTTP/1.1 500 Internal Server Error --setResponseStatus :: Int -> ByteString -> Response -> Response -- | Sets the HTTP response code. -- -- Example: -- --
-- ghci> setResponseCode 404 emptyResponse -- HTTP/1.1 404 Not Found --setResponseCode :: Int -> Response -> Response -- | Modifies a response body. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified System.IO.Streams as Streams
-- ghci> import qualified Data.ByteString.Builder as Builder
-- ghci> :{
-- ghci| let r = setResponseBody
-- ghci| (out -> do
-- ghci| Streams.write (Just $ Builder.byteString "Hello, world!") out
-- ghci| return out)
-- ghci| emptyResponse
-- ghci| :}
-- ghci> r
-- HTTP/1.1 200 OK
--
-- Hello, world!
-- ghci> :{
-- ghci| let r' = modifyResponseBody
-- ghci| (f out -> do
-- ghci| out' <- f out
-- ghci| Streams.write (Just $ Builder.byteString "\nBye, world!") out'
-- ghci| return out') r
-- ghci| :}
-- ghci> r'
-- HTTP/1.1 200 OK
--
-- Hello, world!
-- Bye, world!
--
modifyResponseBody :: ((OutputStream Builder -> IO (OutputStream Builder)) -> OutputStream Builder -> IO (OutputStream Builder)) -> Response -> Response
-- | Sets the Content-Type in the Response headers.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> setContentType "text/html" emptyResponse -- HTTP/1.1 200 OK -- content-type: text/html --setContentType :: ByteString -> Response -> Response -- | Adds an HTTP Cookie to Response headers. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
-- ghci> getResponseCookie "name" $ addResponseCookie cookie emptyResponse
-- Just (Cookie {cookieName = "name", cookieValue = "value", ...})
--
addResponseCookie :: Cookie -> Response -> Response
-- | Gets an HTTP Cookie with the given name from Response
-- headers.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> getResponseCookie "cookie-name" emptyResponse -- Nothing --getResponseCookie :: ByteString -> Response -> Maybe Cookie -- | Returns a list of Cookies present in Response -- -- Example: -- --
-- ghci> getResponseCookies emptyResponse -- [] --getResponseCookies :: Response -> [Cookie] -- | Deletes an HTTP Cookie from the Response headers. Please -- note this does not necessarily erase the cookie from the client -- browser. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
-- ghci> let rsp = addResponseCookie cookie emptyResponse
-- ghci> getResponseCookie "name" rsp
-- Just (Cookie {cookieName = "name", cookieValue = "value", ...})
-- ghci> getResponseCookie "name" $ deleteResponseCookie "name" rsp
-- Nothing
--
deleteResponseCookie :: ByteString -> Response -> Response
-- | Modifies an HTTP Cookie with given name in Response
-- headers. Nothing will happen if a matching Cookie can not be
-- found in Response.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import Data.Monoid
-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
-- ghci> let rsp = addResponseCookie cookie emptyResponse
-- ghci> getResponseCookie "name" rsp
-- Just (Cookie {cookieName = "name", cookieValue = "value", ...})
-- ghci> let f ck@(Cookie { cookieName = name }) = ck { cookieName = name <> "'"}
-- ghci> let rsp' = modifyResponseCookie "name" f rsp
-- ghci> getResponseCookie "name'" rsp'
-- Just (Cookie {cookieName = "name'", ...})
-- ghci> getResponseCookie "name" rsp'
-- Just (Cookie {cookieName = "name", ...})
--
modifyResponseCookie :: ByteString -> (Cookie -> Cookie) -> Response -> Response
-- | A note here: if you want to set the Content-Length for the
-- response, Snap forces you to do it with this function rather than by
-- setting it in the headers; the Content-Length in the headers
-- will be ignored.
--
-- The reason for this is that Snap needs to look up the value of
-- Content-Length for each request, and looking the string value
-- up in the headers and parsing the number out of the text will be too
-- expensive.
--
-- If you don't set a content length in your response, HTTP keep-alive
-- will be disabled for HTTP/1.0 clients, forcing a Connection:
-- close. For HTTP/1.1 clients, Snap will switch to the chunked
-- transfer encoding if Content-Length is not specified.
--
-- Example:
--
-- -- ghci> setContentLength 400 emptyResponse -- HTTP/1.1 200 OK -- Content-Length: 400 --setContentLength :: Word64 -> Response -> Response -- | Removes any Content-Length set in the Response. -- -- Example: -- --
-- ghci> clearContentLength $ setContentLength 400 emptyResponse -- HTTP/1.1 200 OK --clearContentLength :: Response -> Response -- | Convert a CTime into common log entry format. formatLogTime :: CTime -> IO ByteString -- | Convert a CTime into an HTTP timestamp. -- -- Example: -- --
-- ghci> formatHttpTime . fromIntegral $ 10 -- "Thu, 01 Jan 1970 00:00:10 GMT" --formatHttpTime :: CTime -> IO ByteString -- | Converts an HTTP timestamp into a CTime. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> parseHttpTime "Thu, 01 Jan 1970 00:00:10 GMT" -- 10 --parseHttpTime :: ByteString -> IO CTime -- | Adapted from: -- -- -- https://www.iana.org/assignments/http-status-codes/http-status-codes.txt statusReasonMap :: IntMap ByteString instance GHC.Classes.Eq Snap.Internal.Core.NoHandlerException instance GHC.Show.Show Snap.Internal.Core.NoHandlerException instance GHC.Exception.Type.Exception Snap.Internal.Core.NoHandlerException instance Control.Monad.Trans.Control.MonadBaseControl GHC.Types.IO Snap.Internal.Core.Snap instance Snap.Internal.Core.MonadSnap Snap.Internal.Core.Snap instance GHC.Base.Monad Snap.Internal.Core.Snap instance Control.Monad.Fail.MonadFail Snap.Internal.Core.Snap instance Control.Monad.IO.Class.MonadIO Snap.Internal.Core.Snap instance Control.Monad.Base.MonadBase GHC.Types.IO Snap.Internal.Core.Snap instance GHC.Base.MonadPlus Snap.Internal.Core.Snap instance GHC.Base.Functor Snap.Internal.Core.Snap instance GHC.Base.Applicative Snap.Internal.Core.Snap instance GHC.Base.Alternative Snap.Internal.Core.Snap instance GHC.Exception.Type.Exception Snap.Internal.Core.EscapeSnap instance GHC.Show.Show Snap.Internal.Core.EscapeSnap -- | This module contains the core type definitions, class instances, and -- functions for HTTP as well as the Snap monad, which is used for -- web handlers. module Snap.Core -- | Snap is the Monad that user web handlers run in. -- Snap gives you: -- --
printRqContextPath :: Snap () printRqContextPath = -- writeBS . rqContextPath =<< getRequest --
printRspStatusReason :: Snap () -- printRspStatusReason = writeBS . rspStatusReason -- =<< getResponse
a :: Snap String a = -- pass b :: Snap String b = return "foo" c :: Snap String c = a -- <|> b -- try running a, if it fails then try b --
example :: -- (OutputStream Builder -> IO (OutputStream -- Builder)) -> Snap () example streamProc = do writeBS -- "I'm a strict bytestring" writeLBS "I'm a lazy bytestring" -- writeText "I'm strict text" addToOutput streamProc --
a :: Snap () -- a = do modifyResponse $ setResponseStatus 500 "Internal -- Server Error" writeBS "500 error" r <- getResponse -- finishWith rthen any subsequent processing will be -- skipped and the supplied Response value will be returned from -- runSnap as-is.
a :: Snap () a = liftIO fireTheMissiles --
a :: Snap () a = setTimeout 30
import Control.Exception.Lifted -- (SomeException, throwIO, catch) foo :: Snap () -- foo = bar `catch` (e::SomeException) -> baz where bar = -- throwIO FooException
foo :: Snap () foo = -- logError "grumble."
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let br = bracketSnap (putStrLn "before") (const $ putStrLn "after") -- ghci> T.runHandler (T.get "/" M.empty) (br $ const $ writeBS "OK") -- before -- after -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 18:41:50 GMT -- -- OK --bracketSnap :: IO a -> (a -> IO b) -> (a -> Snap c) -> Snap c -- | Short-circuits a Snap monad action early, storing the given -- Response value in its state. -- -- IMPORTANT: Be vary careful when using this with things like a DB -- library's withTransaction function or any other kind of -- setup/teardown block, as it can prevent the cleanup from being called -- and result in resource leaks. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import Control.Applicative -- ghci> let r = T.get "/" M.empty -- ghci> T.runHandler r ((ifTop $ writeBS "TOP") <|> finishWith emptyResponse) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 16:58:57 GMT -- -- TOP -- ghci> let r' = T.get "/foo/bar" M.empty -- ghci> T.runHandler r' ((ifTop $ writeBS "TOP") <|> finishWith emptyResponse) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 17:50:50 GMT --finishWith :: MonadSnap m => Response -> m a -- | Capture the flow of control in case a handler calls finishWith. -- -- WARNING: in the event of a call to transformRequestBody -- it is possible to violate HTTP protocol safety when using this -- function. If you call catchFinishWith it is suggested that you -- do not modify the body of the Response which was passed to the -- finishWith call. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.ByteString.Char8 as B8 -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import Control.Applicative -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> let h = (ifTop $ writeBS "TOP") <|> finishWith emptyResponse -- ghci> T.runHandler r (catchFinishWith h >>= writeBS . B8.pack . show) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 18:35:42 GMT -- -- Left HTTP/1.1 200 OK --catchFinishWith :: Snap a -> Snap (Either Response a) -- | Fails out of a Snap monad action. This is used to indicate that -- you choose not to handle the given request within the given handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r pass -- HTTP/1.1 404 Not Found -- server: Snap/test -- date: Thu, 07 Aug 2014 13:35:42 GMT -- -- <!DOCTYPE html> -- <html> -- <head> -- <title>Not found</title> -- </head> -- <body> -- <code>No handler accepted "/foo/bar"/code -- </body></html> --pass :: MonadSnap m => m a -- | Type of external handler passed to escapeHttp. type EscapeHttpHandler = ((Int -> Int) -> IO ()) " timeout modifier" -> InputStream ByteString " socket read end" -> OutputStream Builder " socket write end" -> IO () -- | Used internally to implement escapeHttp. data EscapeSnap TerminateConnection :: SomeException -> EscapeSnap EscapeHttp :: EscapeHttpHandler -> EscapeSnap -- | Terminate the HTTP session and hand control to some external handler, -- escaping all further HTTP traffic. -- -- The external handler takes three arguments: a function to modify the -- thread's timeout, and a read and a write ends to the socket. escapeHttp :: MonadSnap m => EscapeHttpHandler -> m () -- | Terminate the HTTP session with the given exception. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import qualified Control.Exception as E -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (terminateConnection $ E.AssertionFailed "Assertion failed!") -- *** Exception: <terminated: Assertion failed!> --terminateConnection :: (Exception e, MonadSnap m) => e -> m a -- | Runs a Snap monad action only if the request's HTTP method -- matches the given method. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (method GET $ writeBS "OK") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 13:38:48 GMT -- -- OK -- ghci> T.runHandler r (method POST $ writeBS "OK") -- HTTP/1.1 404 Not Found -- ... --method :: MonadSnap m => Method -> m a -> m a -- | Runs a Snap monad action only if the request's HTTP method -- matches one of the given methods. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (methods [GET, POST] $ writeBS "OK") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 13:38:48 GMT -- -- OK -- ghci> T.runHandler r (methods [POST] $ writeBS "OK") -- HTTP/1.1 404 Not Found -- ... --methods :: MonadSnap m => [Method] -> m a -> m a -- | Runs a Snap monad action only for requests where -- rqPathInfo is exactly equal to the given string. If the path -- matches, locally sets rqContextPath to the old value of -- rqPathInfo, sets rqPathInfo="", and runs the given -- handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> T.runHandler (T.get "/foo" M.empty) (path "foo" $ writeBS "bar") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 14:15:42 GMT -- -- bar -- ghci> T.runHandler (T.get "/foo" M.empty) (path "bar" $ writeBS "baz") -- HTTP/1.1 404 Not Found -- ... --path :: MonadSnap m => ByteString -> m a -> m a -- | Runs a Snap monad action only when the first path component is -- successfully parsed as the argument to the supplied handler function. -- -- Note that the path segment is url-decoded prior to being passed to -- fromBS; this is new as of snap-core 0.10. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/11/foo/bar" M.empty -- ghci> let f = (\i -> if i == 11 then writeBS "11" else writeBS "???") -- ghci> T.runHandler r (pathArg f) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 14:27:10 GMT -- -- 11 -- ghci> let r' = T.get "/foo/11/bar" M.empty -- ghci> T.runHandler r' (pathArg f) -- HTTP/1.1 404 Not Found -- ... --pathArg :: (Readable a, MonadSnap m) => (a -> m b) -> m b -- | Runs a Snap monad action only when the rqPathInfo of the -- request starts with the given path. For example, -- --
-- dir "foo" handler ---- -- Will fail if rqPathInfo is not "/foo" or -- "/foo/...", and will add "foo/" to the handler's -- local rqContextPath. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (dir "foo" $ writeBS "OK") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 14:52:24 GMT -- -- OK -- ghci> T.runHandler r (dir "baz" $ writeBS "OK") -- HTTP/1.1 404 Not Found -- ... --dir :: MonadSnap m => ByteString -> m a -> m a -- | Runs a Snap monad action only when rqPathInfo is empty. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/" M.empty -- ghci> T.runHandler r (ifTop $ writeBS OK) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 14:56:39 GMT -- -- OK -- ghci> let r' = T.get "/foo" M.empty -- ghci> T.runHandler r' (ifTop $ writeBS "OK") -- HTTP/1.1 404 Not Found -- ... --ifTop :: MonadSnap m => m a -> m a -- | A web handler which, given a mapping from URL entry points to web -- handlers, efficiently routes requests to the correct handler. -- -- Usage -- -- The URL entry points are given as relative paths, for example: -- --
-- route [ ("foo/bar/quux", fooBarQuux) ]
--
--
-- If the URI of the incoming request is /foo/bar/quux or
-- /foo/bar/quux/...anything... then the request will be routed
-- to "fooBarQuux", with rqContextPath set to
-- "/foo/bar/quux/" and rqPathInfo set to
-- "...anything...".
--
-- A path component within an URL entry point beginning with a colon
-- (":") is treated as a variable capture; the
-- corresponding path component within the request URI will be entered
-- into the rqParams parameters mapping with the given name. For
-- instance, if the routes were:
--
--
-- route [ ("foo/:bar/baz", fooBazHandler) ]
--
--
-- Then a request for "/foo/saskatchewan/baz" would be routed to
-- fooBazHandler with a mapping for "bar" =>
-- "saskatchewan" in its parameters table.
--
-- Longer paths are matched first, and specific routes are matched before
-- captures. That is, if given routes:
--
--
-- [ ("a", h1), ("a/b", h2), ("a/:x", h3) ]
--
--
-- a request for "/a/b" will go to h2, "/a/s"
-- for any s will go to h3, and "/a" will go to
-- h1.
--
-- The following example matches "/article" to an article index,
-- "/login" to a login, and "/article/..." to an
-- article renderer.
--
--
-- route [ ("article", renderIndex)
-- , ("article/:id", renderArticle)
-- , ("login", method POST doLogin) ]
--
--
-- Note: URL decoding
--
-- A short note about URL decoding: path matching and variable capture
-- are done on decoded URLs, but the contents of
-- rqContextPath and rqPathInfo will contain the original
-- encoded URL, i.e. what the user entered. For example, in the following
-- scenario:
--
--
-- route [ ("a b c d/", foo ) ]
--
--
-- A request for "/a+b+c+d" will be sent to foo with
-- rqContextPath set to "a+b+c+d".
--
-- This behaviour changed as of Snap 0.6.1; previous versions had
-- unspecified (and buggy!) semantics here.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as Map
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> import Snap.Test
-- ghci> :{
-- ghci| let handler = do r <- getRequest
-- ghci| writeBS $ "rqContextPath: " <> rqContextPath r <> "\n"
-- ghci| writeBS $ "rqPathInfo: " <> rqPathInfo r <> "\n"
-- ghci| writeBS $ "rqParams: " <> (B8.pack . show $ rqParams r)
-- ghci| :}
-- ghci> runHandler (get "/foo/bar" "Map.empty") (route [("foo", handler)])
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Sat, 02 Aug 2014 05:16:59 GMT
--
-- rqContextPath: /foo/
-- rqPathInfo: bar
-- rqParams: fromList []
-- ghci> runHandler (get "/foo/bar" "Map.empty") (route [("foo/:bar", handler)])
-- [...]
--
-- rqContextPath: /foo/bar/
-- rqPathInfo:
-- rqParams: fromList [("bar",["bar"])]
--
route :: MonadSnap m => [(ByteString, m a)] -> m a
-- | The routeLocal function is the same as route, except it
-- doesn't change the request's context path. This is useful if you want
-- to route to a particular handler but you want that handler to receive
-- the rqPathInfo as it is.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> import Snap.Test
-- ghci> :{
-- ghci| let handler = do r <- getRequest
-- ghci| writeBS $ "rqContextPath: " <> rqContextPath r <> "\n"
-- ghci| writeBS $ "rqPathInfo: " <> rqPathInfo r <> "\n"
-- ghci| writeBS $ "rqParams: " <> (B8.pack . show $ rqParams r)
-- ghci| :}
-- ghci> runHandler (get "/foo/bar" M.empty) (routeLocal [("foo", handler)])
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Sat, 02 Aug 2014 05:17:28 GMT
--
-- rqContextPath: /
-- rqPathInfo: foo/bar
-- ghci> runHandler (get "/foo/bar" M.empty) (routeLocal [("foo/:bar", handler)])
-- [...]
--
-- rqContextPath: /
-- rqPathInfo: foo/bar
-- rqParams: fromList [("bar",["bar"])]
--
routeLocal :: MonadSnap m => [(ByteString, m a)] -> m a
-- | Grabs the Request object out of the Snap monad.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeBS . rqURI =<< getRequest) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Sat, 02 Aug 2014 07:51:54 GMT -- -- /foo/bar --getRequest :: MonadSnap m => m Request -- | Grabs something out of the Request object, using the given -- projection function. See gets. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeBS =<< getsRequest rqURI) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Sat, 02 Aug 2014 07:51:54 GMT -- -- /foo/bar --getsRequest :: MonadSnap m => (Request -> a) -> m a -- | Grabs the Response object out of the Snap monad. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeBS . rspStatusReason =<< getResponse) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Sat, 02 Aug 2014 15:06:00 GMT -- -- OK --getResponse :: MonadSnap m => m Response -- | Grabs something out of the Response object, using the given -- projection function. See gets. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeBS =<< getsResponse rspStatusReason) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 13:35:45 GMT -- -- OK --getsResponse :: MonadSnap m => (Response -> a) -> m a -- | Puts a new Request object into the Snap monad. Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> :{
-- ghci| let hndlr = do rq <- T.buildRequest (T.get "/bar/foo" M.empty)
-- ghci| putRequest rq
-- ghci| uri' <- getsRequest rqURI
-- ghci| writeBS uri'
-- ghci| :}
-- ghci> T.runHandler (T.get "/foo/bar" M.empty) hndlr
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Wed, 06 Aug 2014 15:13:46 GMT
--
-- /bar/foo
--
putRequest :: MonadSnap m => Request -> m ()
-- | Puts a new Response object into the Snap monad.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let rsp = setResponseCode 404 emptyResponse -- ghci> let req = T.get "/foo/bar" M.empty -- ghci> T.runHandler req (putResponse rsp) -- HTTP/1.1 404 Not Found -- server: Snap/test -- date: Wed, 06 Aug 2014 13:59:58 GMT --putResponse :: MonadSnap m => Response -> m () -- | Modifies the Request object stored in a Snap monad. -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> r' <- T.buildRequest $ T.get "/bar/foo" M.empty -- ghci> T.runHandler r (modifyRequest (const r') >> getsRequest rqURI >>= writeBS) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 15:24:25 GMT -- -- /bar/foo --modifyRequest :: MonadSnap m => (Request -> Request) -> m () -- | Modifes the Response object stored in a Snap monad. -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (modifyResponse $ setResponseCode 404) -- HTTP/1.1 404 Not Found -- server: Snap/test -- date: Wed, 06 Aug 2014 15:27:11 GMT --modifyResponse :: MonadSnap m => (Response -> Response) -> m () -- | Runs a Snap action with a locally-modified Request state -- object. The Request object in the Snap monad state after the -- call to localRequest will be unchanged. Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> r' <- T.buildRequest $ T.get "/bar/foo" M.empty -- ghci> let printRqURI = getsRequest rqURI >>= writeBS >> writeBS "\n" -- ghci> T.runHandler r (printRqURI >> localRequest (const r') printRqURI) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 15:34:12 GMT -- -- /foo/bar -- /bar/foo --localRequest :: MonadSnap m => (Request -> Request) -> m a -> m a -- | Fetches the Request from state and hands it to the given -- action. Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import Control.Monad.IO.Class -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> let h = withRequest (\rq -> liftIO (T.requestToString rq) >>= writeBS) -- ghci> T.runHandler r h -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 15:44:24 GMT -- -- GET /foo/bar HTTP/1.1 -- host: localhost --withRequest :: MonadSnap m => (Request -> m a) -> m a -- | Fetches the Response from state and hands it to the given -- action. Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (withResponse $ writeBS . rspStatusReason) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 15:48:45 GMT -- -- OK --withResponse :: MonadSnap m => (Response -> m a) -> m a -- | Log an error message in the Snap monad. -- -- Example: -- --
-- ghci> import qualified Data.ByteString.Char8 as B8 -- ghci> runSnap (logError "fatal error!") (error . B8.unpack) undefined undefined -- *** Exception: fatal error! --logError :: MonadSnap m => ByteString -> m () -- | Pass the request body stream to a consuming procedure, returning the -- result. -- -- If the consuming procedure you pass in here throws an exception, Snap -- will attempt to clear the rest of the unread request body (using -- skipToEof) before rethrowing the exception. If you used -- terminateConnection, however, Snap will give up and immediately -- close the socket. -- -- To prevent slowloris attacks, the connection will be also terminated -- if the input socket produces data too slowly (500 bytes per second is -- the default limit). -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> import qualified Data.ByteString.Lazy as L
-- ghci> import Data.Char (toUpper)
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified System.IO.Streams as Streams
-- ghci> let r = T.put "/foo" "text/plain" "some text"
-- ghci> :{
-- ghci| let f s = do u <- Streams.map (B8.map toUpper) s
-- ghci| l <- Streams.toList u
-- ghci| return $ L.fromChunks l
-- ghci| :}
-- ghci> T.runHandler r (runRequestBody f >>= writeLBS)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Thu, 07 Aug 2014 20:48:40 GMT
--
-- SOME TEXT
--
runRequestBody :: MonadSnap m => (InputStream ByteString -> IO a) -> m a
-- | Returns the request body as a lazy bytestring. /Note that the request
-- is not actually provided lazily!/
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.put "/foo" "text/plain" "some text" -- ghci> T.runHandler r (readRequestBody 2048 >>= writeLBS) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 20:08:44 GMT -- -- some text ---- -- Since: 0.6 readRequestBody :: MonadSnap m => Word64 -> m ByteString -- | Normally Snap is careful to ensure that the request body is fully -- consumed after your web handler runs, but before the Response -- body is streamed out the socket. If you want to transform the request -- body into some output in O(1) space, you should use this function. -- -- Take care: in order for this to work, the HTTP client must be written -- with input-to-output streaming in mind. -- -- Note that upon calling this function, response processing finishes -- early as if you called finishWith. Make sure you set any -- content types, headers, cookies, etc. before you call this function. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.ByteString.Char8 as B8 -- ghci> import Data.Char (toUpper) -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import qualified System.IO.Streams as Streams -- ghci> let r = T.put "/foo" "text/plain" "some text" -- ghci> let f = Streams.map (B8.map toUpper) -- ghci> T.runHandler r (transformRequestBody f >> readRequestBody 2048 >>= writeLBS) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 20:30:15 GMT -- -- SOME TEXT --transformRequestBody :: (InputStream ByteString -> IO (InputStream ByteString)) -> Snap () -- | Contains all of the information about an incoming HTTP request. data Request -- | Represents an HTTP response. data Response -- | A key-value map that represents a collection of HTTP header fields. -- Keys are case-insensitive. data Headers -- | A typeclass for datatypes which contain HTTP headers. class HasHeaders a -- | Modify the datatype's headers. updateHeaders :: HasHeaders a => (Headers -> Headers) -> a -> a -- | Retrieve the headers from a datatype that has headers. headers :: HasHeaders a => a -> Headers -- | A type alias for the HTTP parameters mapping. Each parameter key maps -- to a list of ByteString values; if a parameter is specified -- multiple times (e.g.: "GET /foo?param=bar1¶m=bar2"), -- looking up "param" in the mapping will give you ["bar1", -- "bar2"]. type Params = Map ByteString [ByteString] -- | Enumerates the HTTP method values (see -- http://tools.ietf.org/html/rfc2068.html#section-5.1.1). data Method GET :: Method HEAD :: Method POST :: Method PUT :: Method DELETE :: Method TRACE :: Method OPTIONS :: Method CONNECT :: Method PATCH :: Method Method :: ByteString -> Method -- | A datatype representing an HTTP cookie. data Cookie Cookie :: !ByteString -> !ByteString -> !Maybe UTCTime -> !Maybe ByteString -> !Maybe ByteString -> !Bool -> !Bool -> Cookie -- | The name of the cookie. [cookieName] :: Cookie -> !ByteString -- | The cookie's string value. [cookieValue] :: Cookie -> !ByteString -- | The cookie's expiration value, if it has one. [cookieExpires] :: Cookie -> !Maybe UTCTime -- | The cookie's "domain" value, if it has one. [cookieDomain] :: Cookie -> !Maybe ByteString -- | The cookie path. [cookiePath] :: Cookie -> !Maybe ByteString -- | Tag as secure cookie? [cookieSecure] :: Cookie -> !Bool -- | HTTP only? [cookieHttpOnly] :: Cookie -> !Bool -- | Represents a (major, minor) version of the HTTP protocol. type HttpVersion = (Int, Int) -- | Adds a header key-value-pair to the HasHeaders datatype. If a -- header with the same name already exists, the new value is appended to -- the headers list. -- -- Example: -- --
-- ghci> import qualified Snap.Types.Headers as H
-- ghci> addHeader "Host" "localhost" H.empty
-- H {unH = [("host","localhost")]}
-- ghci> addHeader "Host" "127.0.0.1" it
-- H {unH = [("host","localhost,127.0.0.1")]}
--
addHeader :: HasHeaders a => CI ByteString -> ByteString -> a -> a
-- | Sets a header key-value-pair in a HasHeaders datatype. If a
-- header with the same name already exists, it is overwritten with the
-- new value.
--
-- Example:
--
--
-- ghci> import qualified Snap.Types.Headers as H
-- ghci> setHeader "Host" "localhost" H.empty
-- H {unH = [("host","localhost")]}
-- ghci> setHeader "Host" "127.0.0.1" it
-- H {unH = [("host","127.0.0.1")]}
--
setHeader :: HasHeaders a => CI ByteString -> ByteString -> a -> a
-- | Gets a header value out of a HasHeaders datatype.
--
-- Example:
--
-- -- ghci> import qualified Snap.Types.Headers as H -- ghci> getHeader "Host" $ setHeader "Host" "localhost" H.empty -- Just "localhost" --getHeader :: HasHeaders a => CI ByteString -> a -> Maybe ByteString -- | Lists all the headers out of a HasHeaders datatype. If many -- headers came in with the same name, they will be catenated together. -- -- Example: -- --
-- ghci> import qualified Snap.Types.Headers as H
-- ghci> listHeaders $ setHeader "Host" "localhost" H.empty
-- [("host","localhost")]
--
listHeaders :: HasHeaders a => a -> [(CI ByteString, ByteString)]
-- | Clears a header value from a HasHeaders datatype.
--
-- Example:
--
--
-- ghci> import qualified Snap.Types.Headers as H
-- ghci> deleteHeader "Host" $ setHeader "Host" "localhost" H.empty
-- H {unH = []}
--
deleteHeader :: HasHeaders a => CI ByteString -> a -> a
-- | Modifies the Request in the state to set the
-- rqRemoteAddr field to the value in the X-Forwarded-For
-- header. If the header is not present, this action has no effect.
--
-- This action should be used only when working behind a reverse http
-- proxy that sets the X-Forwarded-For header. This is the only way to
-- ensure the value in the X-Forwarded-For header can be trusted.
--
-- This is provided as a filter so actions that require the remote
-- address can get it in a uniform manner. It has specifically limited
-- functionality to ensure that its transformation can be trusted, when
-- used correctly.
ipHeaderFilter :: MonadSnap m => m ()
-- | Modifies the Request in the state to set the
-- rqRemoteAddr field to the value from the header specified. If
-- the header specified is not present, this action has no effect.
--
-- This action should be used only when working behind a reverse http
-- proxy that sets the header being looked at. This is the only way to
-- ensure the value in the header can be trusted.
--
-- This is provided as a filter so actions that require the remote
-- address can get it in a uniform manner. It has specifically limited
-- functionality to ensure that its transformation can be trusted, when
-- used correctly.
ipHeaderFilter' :: MonadSnap m => CI ByteString -> m ()
-- | Contains all HTTP Headers associated with this request.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> rqHeaders `fmap` T.buildRequest (T.get "/foo/bar" M.empty)
-- H {unH = [("host","localhost")]}
--
rqHeaders :: Request -> Headers
-- | The server name of the request, as it came in from the request's
-- Host: header.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.get "/foo/bar" M.empty
-- ghci| T.setHeader "host" "example.com"
-- ghci| :}
-- ghci> rqHostName rq
-- "example.com"
--
rqHostName :: Request -> ByteString
-- | The remote IP address.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqClientAddr `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "127.0.0.1" --rqClientAddr :: Request -> ByteString -- | The remote TCP port number. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqClientPort `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "60000" --rqClientPort :: Request -> Int -- | The local IP address for this request. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqServerAddr `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "127.0.0.1" --rqServerAddr :: Request -> ByteString -- | Returns the port number the HTTP server is listening on. This may be -- useless from the perspective of external requests, e.g. if the server -- is running behind a proxy. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqServerPort `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- 8080 --rqServerPort :: Request -> Int -- | Returns the HTTP server's idea of its local hostname, including port. -- This is as configured with the Config object at startup. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqLocalHostname `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "localhost" --rqLocalHostname :: Request -> ByteString -- | Returns True if this is an HTTPS session. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqIsSecure `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- False --rqIsSecure :: Request -> Bool -- | Returns the Content-Length of the HTTP request body. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqContentLength `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- Nothing --rqContentLength :: Request -> Maybe Word64 -- | Returns the HTTP request method. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqMethod `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- GET --rqMethod :: Request -> Method -- | Returns the HTTP version used by the client. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqVersion `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- (1,1) --rqVersion :: Request -> HttpVersion -- | Returns a list of the cookies that came in from the HTTP request -- headers. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqCookies `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- [] --rqCookies :: Request -> [Cookie] -- | Handlers can be hung on a URI "entry point"; this is called -- the "context path". If a handler is hung on the context path -- "/foo/", and you request "/foo/bar", the value of -- rqPathInfo will be "bar". -- -- The following identity holds: -- --
-- rqURI r == S.concat [ rqContextPath r -- , rqPathInfo r -- , let q = rqQueryString r -- in if S.null q -- then "" -- else S.append "?" q -- ] ---- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqPathInfo `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "foo/bar" --rqPathInfo :: Request -> ByteString -- | The "context path" of the request; catenating rqContextPath, -- and rqPathInfo should get you back to the original rqURI -- (ignoring query strings). The rqContextPath always begins and -- ends with a slash ("/") character, and represents the path -- (relative to your component/snaplet) you took to get to your handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqContextPath `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "/" --rqContextPath :: Request -> ByteString -- | Returns the URI requested by the client. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.Map as M -- ghci> rqURI `fmap` T.buildRequest (T.get "/foo/bar" M.empty) -- "foo/bar" --rqURI :: Request -> ByteString -- | Returns the HTTP query string for this Request. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> rq <- T.buildRequest (T.get "/foo/bar" (M.fromList [("name", ["value"])]))
-- ghci> rqQueryString rq
-- "name=value"
--
rqQueryString :: Request -> ByteString
-- | Returns the parameters mapping for this Request. "Parameters"
-- are automatically decoded from the URI's query string and
-- POST body and entered into this mapping. The rqParams
-- value is thus a union of rqQueryParams and rqPostParams.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqParams rq
-- fromList [("baz",["qux","quux"])]
--
rqParams :: Request -> Params
-- | The parameter mapping decoded from the URI's query string.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqQueryParams rq
-- fromList [("baz",["quux"])]
--
rqQueryParams :: Request -> Params
-- | The parameter mapping decoded from the POST body. Note that Snap only
-- auto-decodes POST request bodies when the request's
-- Content-Type is application/x-www-form-urlencoded.
-- For multipart/form-data use handleFileUploads to
-- decode the POST request and fill this mapping.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqPostParams rq
-- fromList [("baz",["qux"])]
--
rqPostParams :: Request -> Params
-- | Looks up the value(s) for the given named parameter. Parameters
-- initially come from the request's query string and any decoded POST
-- body (if the request's Content-Type is
-- application/x-www-form-urlencoded). Parameter values can be
-- modified within handlers using "rqModifyParams".
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqParam "baz" rq
-- Just ["qux","quux"]
--
rqParam :: ByteString -> Request -> Maybe [ByteString]
-- | Looks up the value(s) for the given named parameter in the POST
-- parameters mapping.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqPostParam "baz" rq
-- Just ["qux"]
--
rqPostParam :: ByteString -> Request -> Maybe [ByteString]
-- | Looks up the value(s) for the given named parameter in the query
-- parameters mapping.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqQueryParam "baz" rq
-- Just ["quux"]
--
rqQueryParam :: ByteString -> Request -> Maybe [ByteString]
-- | See rqParam. Looks up a value for the given named parameter in
-- the Request. If more than one value was entered for the given
-- parameter name, getParam gloms the values together with
-- intercalate " ".
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> let r = T.get "/foo/bar" $ M.fromList [("foo", ["bar"])]
-- ghci> T.runHandler r (getParam "foo" >>= writeBS . B8.pack . show)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Mon, 11 Aug 2014 12:57:20 GMT
--
-- Just "bar"
--
getParam :: MonadSnap m => ByteString -> m (Maybe ByteString)
-- | See rqPostParam. Looks up a value for the given named parameter
-- in the POST form parameters mapping in Request. If more than
-- one value was entered for the given parameter name,
-- getPostParam gloms the values together with:
-- intercalate " ".
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> let r = T.postUrlEncoded "/foo/bar" $ M.fromList [("foo", ["bar"])]
-- ghci> T.runHandler r (getPostParam "foo" >>= writeBS . B8.pack . show)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Mon, 11 Aug 2014 13:01:04 GMT
--
-- Just "bar"
--
getPostParam :: MonadSnap m => ByteString -> m (Maybe ByteString)
-- | See rqQueryParam. Looks up a value for the given named
-- parameter in the query string parameters mapping in Request. If
-- more than one value was entered for the given parameter name,
-- getQueryParam gloms the values together with
-- intercalate " ".
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.ByteString.Char8 as B8 -- ghci> let r = T.postUrlEncoded "/foo/bar" M.empty >> T.setQueryStringRaw "foo=bar&foo=baz" -- ghci> T.runHandler r (getQueryParam "foo" >>= writeBS . B8.pack . show) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Mon, 11 Aug 2014 13:06:50 GMT -- -- Just "bar baz" --getQueryParam :: MonadSnap m => ByteString -> m (Maybe ByteString) -- | See rqParams. Convenience function to return Params from -- the Request inside of a MonadSnap instance. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> let r = T.get "/foo/bar" $ M.fromList [("foo", ["bar"])]
-- ghci> T.runHandler r (getParams >>= writeBS . B8.pack . show)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Mon, 11 Aug 2014 13:02:54 GMT
--
-- fromList [("foo",["bar"])]
--
getParams :: MonadSnap m => m Params
-- | See rqParams. Convenience function to return Params from
-- the Request inside of a MonadSnap instance.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> let r = T.postUrlEncoded "/foo/bar" $ M.fromList [("foo", ["bar"])]
-- ghci> T.runHandler r (getPostParams >>= writeBS . B8.pack . show)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Mon, 11 Aug 2014 13:04:34 GMT
--
-- fromList [("foo",["bar"])]
--
getPostParams :: MonadSnap m => m Params
-- | See rqParams. Convenience function to return Params from
-- the Request inside of a MonadSnap instance.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> let r = T.postUrlEncoded "/foo/bar" M.empty >> T.setQueryStringRaw "foo=bar&foo=baz"
-- ghci> T.runHandler r (getQueryParams >>= writeBS . B8.pack . show)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Mon, 11 Aug 2014 13:10:17 GMT
--
-- fromList [("foo",["bar","baz"])]
--
getQueryParams :: MonadSnap m => m Params
-- | Modifies the parameters mapping (which is a Map ByteString
-- ByteString) in a Request using the given function.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqParams rq
-- fromList [("baz",["qux","quux"])]
-- ghci> rqParams $ rqModifyParams (M.delete "baz") rq
-- fromList []
--
rqModifyParams :: (Params -> Params) -> Request -> Request
-- | Writes a key-value pair to the parameters mapping within the given
-- request.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| rq <- T.buildRequest $ do
-- ghci| T.postUrlEncoded "/foo/bar" $ M.fromList [("baz", ["qux"])]
-- ghci| T.setQueryStringRaw "baz=quux"
-- ghci| :}
-- ghci> rqParams rq
-- fromList [("baz",["qux","quux"])]
-- ghci> rqParams $ rqSetParam "baz" ["corge"] rq
-- fromList [("baz", ["corge"])]
--
rqSetParam :: ByteString -> [ByteString] -> Request -> Request
-- | See rqClientAddr.
-- | Deprecated: (snap-core >= 1.0.0.0) please use
-- rqClientAddr, this will be removed in 1.1.*
rqRemoteAddr :: Request -> ByteString
-- | See rqClientPort.
-- | Deprecated: (snap-core >= 1.0.0.0) please use
-- rqClientPort, this will be removed in 1.1.*
rqRemotePort :: Request -> Int
-- | An empty Response.
--
-- Example:
--
-- -- ghci> emptyResponse -- HTTP/1.1 200 OK --emptyResponse :: Response -- | Sets the HTTP response code. -- -- Example: -- --
-- ghci> setResponseCode 404 emptyResponse -- HTTP/1.1 404 Not Found --setResponseCode :: Int -> Response -> Response -- | Sets the HTTP response status. Note: normally you would use -- setResponseCode unless you needed a custom response -- explanation. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> setResponseStatus 500 "Internal Server Error" emptyResponse -- HTTP/1.1 500 Internal Server Error --setResponseStatus :: Int -> ByteString -> Response -> Response -- | Returns the HTTP status code. -- -- Example: -- --
-- ghci> rspStatus emptyResponse -- 200 --rspStatus :: Response -> Int -- | Returns the HTTP status explanation string. -- -- Example: -- --
-- ghci> rspStatusReason emptyResponse -- OK --rspStatusReason :: Response -> ByteString -- | Sets the Content-Type in the Response headers. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> setContentType "text/html" emptyResponse -- HTTP/1.1 200 OK -- content-type: text/html --setContentType :: ByteString -> Response -> Response -- | Adds an HTTP Cookie to Response headers. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
-- ghci> getResponseCookie "name" $ addResponseCookie cookie emptyResponse
-- Just (Cookie {cookieName = "name", cookieValue = "value", ...})
--
addResponseCookie :: Cookie -> Response -> Response
-- | Gets an HTTP Cookie with the given name from Response
-- headers.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> getResponseCookie "cookie-name" emptyResponse -- Nothing --getResponseCookie :: ByteString -> Response -> Maybe Cookie -- | Returns a list of Cookies present in Response -- -- Example: -- --
-- ghci> getResponseCookies emptyResponse -- [] --getResponseCookies :: Response -> [Cookie] -- | Deletes an HTTP Cookie from the Response headers. Please -- note this does not necessarily erase the cookie from the client -- browser. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
-- ghci> let rsp = addResponseCookie cookie emptyResponse
-- ghci> getResponseCookie "name" rsp
-- Just (Cookie {cookieName = "name", cookieValue = "value", ...})
-- ghci> getResponseCookie "name" $ deleteResponseCookie "name" rsp
-- Nothing
--
deleteResponseCookie :: ByteString -> Response -> Response
-- | Modifies an HTTP Cookie with given name in Response
-- headers. Nothing will happen if a matching Cookie can not be
-- found in Response.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import Data.Monoid
-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
-- ghci> let rsp = addResponseCookie cookie emptyResponse
-- ghci> getResponseCookie "name" rsp
-- Just (Cookie {cookieName = "name", cookieValue = "value", ...})
-- ghci> let f ck@(Cookie { cookieName = name }) = ck { cookieName = name <> "'"}
-- ghci> let rsp' = modifyResponseCookie "name" f rsp
-- ghci> getResponseCookie "name'" rsp'
-- Just (Cookie {cookieName = "name'", ...})
-- ghci> getResponseCookie "name" rsp'
-- Just (Cookie {cookieName = "name", ...})
--
modifyResponseCookie :: ByteString -> (Cookie -> Cookie) -> Response -> Response
-- | Expire given Cookie in client's browser.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> let cookie = Cookie "name" "" Nothing (Just "/subsite") Nothing True False -- ghci> T.runHandler r (expireCookie cookie) -- HTTP/1.1 200 OK -- set-cookie: name=; path=/subsite; expires=Sat, 24 Dec 1994 06:28:16 GMT; Secure -- server: Snap/test -- -- date: Thu, 07 Aug 2014 12:21:27 GMT -- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False -- ghci> let r2 = T.get "/foo/bar" M.empty >> T.addCookies [cookie] -- ghci> T.runHandler r (getCookie "name" >>= maybe (return ()) expireCookie) -- HTTP/1.1 200 OK -- set-cookie: name=; expires=Sat, 24 Dec 1994 06:28:16 GMT -- server: Snap/test --expireCookie :: MonadSnap m => Cookie -> m () -- | Gets the HTTP Cookie with the specified name. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Char8 as B8
-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
-- ghci> let r = T.get "/foo/bar" M.empty >> T.addCookies [cookie]
-- ghci> T.runHandler r (getCookie "name" >>= writeBS . B8.pack . show)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Thu, 07 Aug 2014 12:16:58 GMT
--
-- Just (Cookie {cookieName = "name", cookieValue = "value", ...})
--
getCookie :: MonadSnap m => ByteString -> m (Maybe Cookie)
-- | Gets the HTTP Cookie with the specified name and decodes it. If
-- the decoding fails, the handler calls pass.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False -- ghci> let r = T.get "/foo/bar" M.empty >> T.addCookies [cookie] -- ghci> T.runHandler r (readCookie "name" >>= writeBS) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 07 Aug 2014 12:20:09 GMT -- -- value --readCookie :: (MonadSnap m, Readable a) => ByteString -> m a -- | A note here: if you want to set the Content-Length for the -- response, Snap forces you to do it with this function rather than by -- setting it in the headers; the Content-Length in the headers -- will be ignored. -- -- The reason for this is that Snap needs to look up the value of -- Content-Length for each request, and looking the string value -- up in the headers and parsing the number out of the text will be too -- expensive. -- -- If you don't set a content length in your response, HTTP keep-alive -- will be disabled for HTTP/1.0 clients, forcing a Connection: -- close. For HTTP/1.1 clients, Snap will switch to the chunked -- transfer encoding if Content-Length is not specified. -- -- Example: -- --
-- ghci> setContentLength 400 emptyResponse -- HTTP/1.1 200 OK -- Content-Length: 400 --setContentLength :: Word64 -> Response -> Response -- | Removes any Content-Length set in the Response. -- -- Example: -- --
-- ghci> clearContentLength $ setContentLength 400 emptyResponse -- HTTP/1.1 200 OK --clearContentLength :: Response -> Response -- | Performs a redirect by setting the Location header to the -- given target URL/path and the status code to 302 in the -- Response object stored in a Snap monad. Note that the -- target URL is not validated in any way. Consider using -- redirect' instead, which allows you to choose the correct -- status code. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (redirect "http://snapframework.com") -- HTTP/1.1 302 Found -- content-length: 0 -- location: http://snapframework.com -- server: Snap/test -- date: Thu, 07 Aug 2014 08:52:11 GMT -- Content-Length: 0 --redirect :: MonadSnap m => ByteString -> m a -- | Performs a redirect by setting the Location header to the -- given target URL/path and the status code (should be one of 301, 302, -- 303 or 307) in the Response object stored in a Snap -- monad. Note that the target URL is not validated in any way. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (redirect' "http://snapframework.com" 301) -- HTTP/1.1 307 Temporary Redirect -- content-length: 0 -- location: http://snapframework.com -- server: Snap/test -- date: Thu, 07 Aug 2014 08:55:51 GMT -- Content-Length: 0 --redirect' :: MonadSnap m => ByteString -> Int -> m a -- | Sets an HTTP response body to the given stream procedure. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified System.IO.Streams as Streams
-- ghci> import qualified Data.ByteString.Builder as Builder
-- ghci> :{
-- ghci| let r = setResponseBody
-- ghci| (out -> do
-- ghci| Streams.write (Just $ Builder.byteString "Hello, world!") out
-- ghci| return out)
-- ghci| emptyResponse
-- ghci| :}
-- ghci> r
-- HTTP/1.1 200 OK
--
-- Hello, world!
--
setResponseBody :: (OutputStream Builder -> IO (OutputStream Builder)) -> Response -> Response
-- | Modifies a response body.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified System.IO.Streams as Streams
-- ghci> import qualified Data.ByteString.Builder as Builder
-- ghci> :{
-- ghci| let r = setResponseBody
-- ghci| (out -> do
-- ghci| Streams.write (Just $ Builder.byteString "Hello, world!") out
-- ghci| return out)
-- ghci| emptyResponse
-- ghci| :}
-- ghci> r
-- HTTP/1.1 200 OK
--
-- Hello, world!
-- ghci> :{
-- ghci| let r' = modifyResponseBody
-- ghci| (f out -> do
-- ghci| out' <- f out
-- ghci| Streams.write (Just $ Builder.byteString "\nBye, world!") out'
-- ghci| return out') r
-- ghci| :}
-- ghci> r'
-- HTTP/1.1 200 OK
--
-- Hello, world!
-- Bye, world!
--
modifyResponseBody :: ((OutputStream Builder -> IO (OutputStream Builder)) -> OutputStream Builder -> IO (OutputStream Builder)) -> Response -> Response
-- | Run the given stream procedure, adding its output to the
-- Response stored in the Snap monad state.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import qualified Snap.Test as T
-- ghci> import qualified Data.ByteString.Builder as B
-- ghci> import qualified System.IO.Streams as Streams
-- ghci> let r = T.get "/foo/bar" M.empty
-- ghci> :{
-- ghci| let f str = do {
-- ghci| Streams.write (Just $ B.byteString "Hello, streams world") str;
-- ghci| return str }
-- ghci| :}
-- ghci> T.runHandler r (addToOutput f)
-- HTTP/1.1 200 OK
-- server: Snap/test
-- date: Wed, 06 Aug 2014 17:55:47 GMT
--
-- Hello, streams world
--
addToOutput :: MonadSnap m => (OutputStream Builder -> IO (OutputStream Builder)) -> m ()
-- | Adds the given Builder to the body of the Response
-- stored in the | Snap monad state.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.ByteString.Builder as B -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeBuilder $ B.byteString "Hello, world") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 17:33:33 GMT -- -- Hello, world --writeBuilder :: MonadSnap m => Builder -> m () -- | Adds the given strict ByteString to the body of the -- Response stored in the Snap monad state. -- -- Warning: This function is intentionally non-strict. If any pure -- exceptions are raised by the expression creating the -- ByteString, the exception won't actually be raised within the -- Snap handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeBS "Hello, bytestring world") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 17:34:27 GMT -- -- Hello, bytestring world --writeBS :: MonadSnap m => ByteString -> m () -- | Adds the given lazy Text to the body of the Response -- stored in the Snap monad state. -- -- Warning: This function is intentionally non-strict. If any pure -- exceptions are raised by the expression creating the -- ByteString, the exception won't actually be raised within the -- Snap handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeLazyText "Hello, lazy text world") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 17:37:41 GMT -- -- Hello, lazy text world --writeLazyText :: MonadSnap m => Text -> m () -- | Adds the given strict Text to the body of the Response -- stored in the Snap monad state. -- -- Warning: This function is intentionally non-strict. If any pure -- exceptions are raised by the expression creating the -- ByteString, the exception won't actually be raised within the -- Snap handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeText "Hello, text world") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 17:36:38 GMT -- -- Hello, text world --writeText :: MonadSnap m => Text -> m () -- | Adds the given lazy ByteString to the body of the -- Response stored in the Snap monad state. -- -- Warning: This function is intentionally non-strict. If any pure -- exceptions are raised by the expression creating the -- ByteString, the exception won't actually be raised within the -- Snap handler. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (writeLBS "Hello, lazy bytestring world") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Wed, 06 Aug 2014 17:35:15 GMT -- -- Hello, lazy bytestring world --writeLBS :: MonadSnap m => ByteString -> m () -- | Sets the output to be the contents of the specified file. -- -- Calling sendFile will overwrite any output queued to be sent in -- the Response. If the response body is not modified after the -- call to sendFile, Snap will use the efficient -- sendfile() system call on platforms that support it. -- -- If the response body is modified (using modifyResponseBody), -- the file will be read using mmap(). -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> writeFile "/tmp/snap-file" "Hello, sendFile world" -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (sendFile "/tmp/snap-file") -- HTTP/1.1 200 OK -- content-length: 21 -- server: Snap/test -- date: Wed, 06 Aug 2014 17:45:10 GMT -- Content-Length: 21 -- -- Hello, sendFile world --sendFile :: MonadSnap m => FilePath -> m () -- | Sets the output to be the contents of the specified file, within the -- given (start,end) range. -- -- Calling sendFilePartial will overwrite any output queued to be -- sent in the Response. If the response body is not modified -- after the call to sendFilePartial, Snap will use the efficient -- sendfile() system call on platforms that support it. -- -- If the response body is modified (using modifyResponseBody), -- the file will be read using mmap(). -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> writeFile "/tmp/snap-file" "Hello, sendFilePartial world" -- ghci> let r = T.get "/foo/bar" M.empty -- ghci> T.runHandler r (sendFilePartial "/tmp/snap-file" (7, 28)) -- HTTP/1.1 200 OK -- content-length: 21 -- server: Snap/test -- date: Wed, 06 Aug 2014 17:47:20 GMT -- Content-Length: 21 -- -- sendFilePartial world --sendFilePartial :: MonadSnap m => FilePath -> (Word64, Word64) -> m () -- | Causes the handler thread to be killed n seconds from now. setTimeout :: MonadSnap m => Int -> m () -- | Causes the handler thread to be killed at least n seconds -- from now. extendTimeout :: MonadSnap m => Int -> m () -- | Modifies the amount of time remaining before the request times out. modifyTimeout :: MonadSnap m => (Int -> Int) -> m () -- | Returns an IO action which you can use to modify the timeout -- value. getTimeoutModifier :: MonadSnap m => m ((Int -> Int) -> IO ()) -- | Convert a CTime into an HTTP timestamp. -- -- Example: -- --
-- ghci> formatHttpTime . fromIntegral $ 10 -- "Thu, 01 Jan 1970 00:00:10 GMT" --formatHttpTime :: CTime -> IO ByteString -- | Converts an HTTP timestamp into a CTime. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> parseHttpTime "Thu, 01 Jan 1970 00:00:10 GMT" -- 10 --parseHttpTime :: ByteString -> IO CTime -- | Parse a string encoded in application/x-www-form-urlencoded -- format. -- -- Example: -- --
-- ghci> parseUrlEncoded "Name=John+Doe&Name=Jane+Doe&Age=23&Formula=a+%2B+b+%3D%3D+13%25%21" -- fromList [(Age,["23"]),(Formula,["a + b == 13%!"]),(Name,["John Doe","Jane Doe"])] --parseUrlEncoded :: ByteString -> Map ByteString [ByteString] -- | Like printUrlEncoded, but produces a Builder instead of -- a ByteString. Useful for constructing a large string -- efficiently in a single step. -- -- Example: -- --
-- ghci> import Data.Map -- ghci> import Data.Monoid -- ghci> import Data.ByteString.Builder -- ghci> let bldr = buildUrlEncoded (fromList [(Name, ["John Doe"]), (Age, ["23"])]) -- ghci> toLazyByteString $ byteString "http://example.com/script?" <> bldr -- "http://example.com/script?Age=23&Name=John+Doe" --buildUrlEncoded :: Map ByteString [ByteString] -> Builder -- | Given a collection of key-value pairs with possibly duplicate keys -- (represented as a Map), construct a string in -- application/x-www-form-urlencoded format. -- -- Example: -- --
-- ghci> printUrlEncoded (fromList [(Name, ["John Doe"]), (Age, ["23"])]) -- "Age=23&Name=John+Doe" --printUrlEncoded :: Map ByteString [ByteString] -> ByteString -- | URL-escape a string (see -- http://tools.ietf.org/html/rfc2396.html#section-2.4) -- -- Example: -- --
-- ghci> urlEncode "1 attoparsec ~= 3 * 10^-2 meters" -- "1+attoparsec+%7e%3d+3+*+10%5e-2+meters" --urlEncode :: ByteString -> ByteString -- | URL-escape a string (see -- http://tools.ietf.org/html/rfc2396.html#section-2.4) into a -- Builder. -- -- Example: -- --
-- ghci> import Data.ByteString.Builder -- ghci> toLazyByteString . urlEncodeBuilder $ "1 attoparsec ~= 3 * 10^-2 meters" -- "1+attoparsec+%7e%3d+3+*+10%5e-2+meters" --urlEncodeBuilder :: ByteString -> Builder -- | Decode an URL-escaped string (see -- http://tools.ietf.org/html/rfc2396.html#section-2.4) -- -- Example: -- --
-- ghci> urlDecode "1+attoparsec+%7e%3d+3+*+10%5e-2+meters" -- Just "1 attoparsec ~= 3 * 10^-2 meters" --urlDecode :: ByteString -> Maybe ByteString -- | The Snap.Test module contains primitives and combinators for testing -- Snap applications. module Snap.Test -- | RequestBuilder is a monad transformer that allows you to conveniently -- build a snap Request for testing. data RequestBuilder m a -- | A request body of type "multipart/form-data" consists of a -- set of named form parameters, each of which can by either a list of -- regular form values or a set of file uploads. type MultipartParams = [(ByteString, MultipartParam)] -- | A single "multipart/form-data" form parameter: either a list -- of regular form values or a set of file uploads. data MultipartParam -- | a form variable consisting of the given ByteString values. FormData :: [ByteString] -> MultipartParam -- | a file upload consisting of the given FileData values. Files :: [FileData] -> MultipartParam -- | Represents a single file upload for the MultipartParam. data FileData FileData :: ByteString -> ByteString -> ByteString -> FileData -- | the file's name [fdFileName] :: FileData -> ByteString -- | the file's content-type [fdContentType] :: FileData -> ByteString -- | the file contents [fdContents] :: FileData -> ByteString -- | The RequestType datatype enumerates the different kinds of HTTP -- requests you can generate using the testing interface. Most users will -- prefer to use the get, postUrlEncoded, -- postMultipart, put, and delete convenience -- functions. data RequestType GetRequest :: RequestType RequestWithRawBody :: Method -> ByteString -> RequestType MultipartPostRequest :: MultipartParams -> RequestType UrlEncodedPostRequest :: Params -> RequestType DeleteRequest :: RequestType -- | Runs a RequestBuilder, producing the desired Request. -- -- N.B. please don't use the request you get here in a real Snap -- application; things will probably break. Don't say you weren't warned -- :-) -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> buildRequest $ get "/foo/bar" M.empty -- GET /foo/bar HTTP/1.1 -- host: localhost -- -- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a --buildRequest :: MonadIO m => RequestBuilder m () -> m Request -- | Given a web handler in the Snap monad, and a -- RequestBuilder defining a test request, runs the handler, -- producing an HTTP Response. -- -- This function will produce almost exactly the same output as running -- the handler in a real server, except that chunked transfer encoding is -- not applied, and the "Transfer-Encoding" header is not set (this makes -- it easier to test response output). -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import Snap.Core -- ghci> runHandler (get "foo/bar" M.empty) (writeBS "Hello, world!") -- HTTP/1.1 200 OK -- server: Snap/test -- date: Thu, 17 Jul 2014 21:03:23 GMT -- -- Hello, world! --runHandler :: MonadIO m => RequestBuilder m () -> Snap a -> m Response -- | Given a web handler in some arbitrary MonadSnap monad, a -- function specifying how to evaluate it within the context of the test -- monad, and a RequestBuilder defining a test request, runs the -- handler, producing an HTTP Response. runHandlerM :: (MonadIO m, MonadSnap n) => (forall a. Request -> n a -> m Response) -> RequestBuilder m () -> n b -> m Response -- | Given a web handler in the Snap monad, and a -- RequestBuilder defining a test request, runs the handler and -- returns the monadic value it produces. -- -- Throws an exception if the Snap handler early-terminates with -- finishWith or mzero. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import Control.Monad -- ghci> import qualified Data.Map as M -- ghci> import Snap.Core -- ghci> evalHandler (get "foo/bar" M.empty) (writeBS "Hello, world!" >> return 42) -- 42 -- ghci> evalHandler (get "foo/bar" M.empty) mzero -- *** Exception: No handler for request: failure was pass --evalHandler :: MonadIO m => RequestBuilder m () -> Snap a -> m a -- | Given a web handler in some arbitrary MonadSnap monad, a -- function specifying how to evaluate it within the context of the test -- monad, and a RequestBuilder defining a test request, runs the -- handler, returning the monadic value it produces. -- -- Throws an exception if the Snap handler early-terminates with -- finishWith or mzero. evalHandlerM :: (MonadIO m, MonadSnap n) => (forall a. Request -> n a -> m a) -> RequestBuilder m () -> n b -> m b -- | Builds an HTTP "GET" request with the given query parameters. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> buildRequest $ get "/foo/bar" (M.fromList [("param0", ["baz", "quux"])])
-- GET /foo/bar?param0=baz¶m0=quux HTTP/1.1
-- host: localhost
--
-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a
-- params: param0: ["baz","quux"]
--
get :: MonadIO m => ByteString -> Params -> RequestBuilder m ()
-- | Builds an HTTP "POST" request with the given form parameters, using
-- the "application/x-www-form-urlencoded" MIME type.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> buildRequest $ postUrlEncoded "/foo/bar" (M.fromList [("param0", ["baz", "quux"])])
-- POST /foo/bar HTTP/1.1
-- content-type: application/x-www-form-urlencoded
-- content-length: 22
-- host: localhost
--
-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=22
-- params: param0: ["baz","quux"]
--
postUrlEncoded :: MonadIO m => ByteString -> Params -> RequestBuilder m ()
-- | Builds an HTTP "POST" request with the given form parameters, using
-- the "form-data/multipart" MIME type.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> buildRequest $ postMultipart "/foo/bar" [("param0", FormData ["baz", "quux"])]
-- POST /foo/bar HTTP/1.1
-- content-type: multipart/form-data; boundary=snap-boundary-572334111ec0c05ad4812481e8585dfa
-- content-length: 406
-- host: localhost
--
-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=406
--
postMultipart :: MonadIO m => ByteString -> MultipartParams -> RequestBuilder m ()
-- | Builds an HTTP "PUT" request.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> buildRequest $ put "/foo/bar" "text/plain" "some text" -- PUT /foo/bar HTTP/1.1 -- content-type: text/plain -- content-length: 9 -- host: localhost -- -- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=9 --put :: MonadIO m => ByteString -> ByteString -> ByteString -> RequestBuilder m () -- | Builds a "raw" HTTP "POST" request, with the given MIME type and body -- contents. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> buildRequest $ postRaw "/foo/bar" "text/plain" "some text" -- POST /foo/bar HTTP/1.1 -- content-type: text/plain -- content-length: 9 -- host: localhost -- -- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=9 --postRaw :: MonadIO m => ByteString -> ByteString -> ByteString -> RequestBuilder m () -- | Builds an HTTP "DELETE" request with the given query parameters. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> buildRequest $ delete "/foo/bar" M.empty -- DELETE /foo/bar HTTP/1.1 -- host: localhost -- -- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a --delete :: MonadIO m => ByteString -> Params -> RequestBuilder m () -- | Adds the given header to the request being built. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| buildRequest $ do get "/foo/bar" M.empty
-- ghci| addHeader "Accept" "text/html"
-- ghci| addHeader "Accept" "text/plain"
-- ghci| :}
-- GET /foo/bar HTTP/1.1
-- accept: text/html,text/plain
-- host: localhost
--
-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a
--
addHeader :: Monad m => CI ByteString -> ByteString -> RequestBuilder m ()
-- | Sets the request's content-type to the given MIME type.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> buildRequest $ put "/foo/bar" "text/html" "some text" >> setContentType "text/plain" -- PUT /foo/bar HTTP/1.1 -- content-type: text/plain -- content-length: 9 -- host: localhost -- -- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=9 --setContentType :: Monad m => ByteString -> RequestBuilder m () -- | Sets the given header in the request being built, overwriting any -- header with the same name already present. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> :{
-- ghci| buildRequest $ do get "/foo/bar" M.empty
-- ghci| setHeader "Accept" "text/html"
-- ghci| setHeader "Accept" "text/plain"
-- ghci| :}
-- GET /foo/bar HTTP/1.1
-- accept: text/plain
-- host: localhost
--
-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a
--
setHeader :: Monad m => CI ByteString -> ByteString -> RequestBuilder m ()
-- | Adds the given cookies to the request being built.
--
-- Example:
--
--
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> import Snap.Core
-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False
-- ghci> buildRequest $ get "/foo/bar" M.empty >> addCookies [cookie]
-- GET /foo/bar HTTP/1.1
-- cookie: name=value
-- host: localhost
--
-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a
-- cookies: Cookie {cookieName = "name", cookieValue = "value", ...}
--
addCookies :: Monad m => [Cookie] -> RequestBuilder m ()
-- | Sets the test request's http version
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> buildRequest $ delete "/foo/bar" M.empty >> setHttpVersion (1,0) -- DELETE /foo/bar HTTP/1.0 -- host: localhost -- -- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a --setHttpVersion :: Monad m => (Int, Int) -> RequestBuilder m () -- | Escapes the given parameter mapping and sets it as the request's query -- string. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Data.Map as M
-- ghci> buildRequest $ get "/foo/bar" M.empty >> setQueryString (M.fromList [("param0", ["baz"]), ("param1", ["qux"])])
-- GET /foo/bar?param0=baz¶m1=qux HTTP/1.1
-- host: localhost
--
-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a
-- params: param0: ["baz"], param1: ["qux"]
--
setQueryString :: Monad m => Params -> RequestBuilder m ()
-- | Sets the request's query string to be the raw bytestring provided,
-- without any escaping or other interpretation. Most users should
-- instead choose the setQueryString function, which takes a
-- parameter mapping.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> buildRequest $ get "/foo/bar" M.empty >> setQueryStringRaw "param0=baz¶m1=qux" -- GET /foo/bar?param0=baz¶m1=qux HTTP/1.1 -- host: localhost -- -- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a -- params: param0: ["baz"], param1: ["qux"] --setQueryStringRaw :: Monad m => ByteString -> RequestBuilder m () -- | Sets the request's path. The path provided must begin with a -- "/" and must not contain a query string; if you want -- to provide a query string in your test request, you must use -- setQueryString or setQueryStringRaw. Note that -- rqContextPath is never set by any RequestBuilder -- function. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> buildRequest $ get "/foo/bar" M.empty >> setRequestPath "/bar/foo" -- GET /bar/foo HTTP/1.1 -- host: localhost -- -- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a --setRequestPath :: Monad m => ByteString -> RequestBuilder m () -- | Sets the type of the Request being built. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> buildRequest $ delete "/foo/bar" M.empty >> setRequestType GetRequest -- GET /foo/bar HTTP/1.1 -- host: localhost -- -- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a --setRequestType :: MonadIO m => RequestType -> RequestBuilder m () -- | Controls whether the test request being generated appears to be an -- https request or not. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> buildRequest $ delete "/foo/bar" M.empty >> setSecure True -- DELETE /foo/bar HTTP/1.1 -- host: localhost -- -- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=/ clen=n/a secure --setSecure :: Monad m => Bool -> RequestBuilder m () -- | Given a Response, assert that its HTTP status code is 200 -- (success). -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified Test.HUnit as T
-- ghci> let test = T.runTestTT . T.TestCase
-- ghci> test $ assertSuccess emptyResponse
-- Cases: 1 Tried: 1 Errors: 0 Failures: 0
-- Counts {cases = 1, tried = 1, errors = 0, failures = 0}
-- ghci> test $ assertSuccess (setResponseStatus 500 "Internal Server Error" emptyResponse)
-- ### Failure:
-- Expected success (200) but got (500)
-- expected: 200
-- but got: 500
-- Cases: 1 Tried: 1 Errors: 0 Failures: 1
-- Counts {cases = 1, tried = 1, errors = 0, failures = 1}
--
assertSuccess :: Response -> Assertion
-- | Given a Response, assert that its HTTP status code is 404 (Not
-- Found).
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> assert404 $ setResponseStatus 404 "Not Found" emptyResponse -- ghci> assert404 emptyResponse -- *** Exception: HUnitFailure "Expected Not Found (404) but got (200)\nexpected: 404\n but got: 200" --assert404 :: Response -> Assertion -- | Given a Response, assert that its HTTP status code is between -- 300 and 399 (a redirect), and that the Location header of the -- Response points to the specified URI. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> let r' = setResponseStatus 301 "Moved Permanently" emptyResponse -- ghci> let r = setHeader "Location" "www.example.com" r' -- ghci> assertRedirectTo "www.example.com" r -- ghci> assertRedirectTo "www.example.com" emptyResponse -- *** Exception: HUnitFailure "Expected redirect but got status code (200)" --assertRedirectTo :: ByteString -> Response -> Assertion -- | Given a Response, assert that its HTTP status code is between -- 300 and 399 (a redirect). -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> assertRedirect $ setResponseStatus 301 "Moved Permanently" emptyResponse -- ghci> assertRedirect emptyResponse -- *** Exception: HUnitFailure "Expected redirect but got status code (200)" --assertRedirect :: Response -> Assertion -- | Given a Response, assert that its body matches the given -- regular expression. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings
-- ghci> import qualified System.IO.Streams as Streams
-- ghci> import qualified Data.ByteString.Builder as Builder
-- ghci> :{
-- ghci| let r = setResponseBody
-- ghci| (out -> do
-- ghci| Streams.write (Just $ Builder.byteString "Hello, world!") out
-- ghci| return out)
-- ghci| emptyResponse
-- ghci| :}
-- ghci> assertBodyContains "^Hello" r
-- ghci> assertBodyContains "Bye" r
-- *** Exception: HUnitFailure "Expected body to match regexp \"\"Bye\"\", but didn't"
--
assertBodyContains :: ByteString -> Response -> Assertion
-- | Given a Response, return its body as a ByteString.
--
-- Example:
--
-- -- ghci> getResponseBody emptyResponse -- "" --getResponseBody :: Response -> IO ByteString -- | Converts the given Request to a bytestring. -- -- Since: 1.0.0.0 -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> r <- buildRequest $ get "/foo/bar" M.empty -- ghci> requestToString r -- "GET /foo/bar HTTP/1.1\r\nhost: localhost\r\n\r\n" --requestToString :: Request -> IO ByteString -- | Converts the given Response to a bytestring. -- -- Example: -- --
-- ghci> import Snap.Core -- ghci> responseToString emptyResponse -- "HTTP/1.1 200 OK\r\n\r\n" --responseToString :: Response -> IO ByteString -- | Add CORS (cross-origin resource sharing) headers to a Snap -- application. CORS headers can be added either conditionally or -- unconditionally to the entire site, or you can apply CORS headers to a -- single route. -- -- To use in a snaplet, simply use wrapSite: -- --
-- wrapSite $ applyCORS defaultOptions --module Snap.Util.CORS -- | Apply CORS headers to a specific request. This is useful if you only -- have a single action that needs CORS headers, and you don't want to -- pay for conditional checks on every request. -- -- You should note that applyCORS needs to be used before you add -- any method combinators. For example, the following won't do -- what you want: -- --
-- method POST $ applyCORS defaultOptions $ myHandler ---- -- This fails to work as CORS requires an OPTIONS request in the -- preflighting stage, but this would get filtered out. Instead, use -- --
-- applyCORS defaultOptions $ method POST $ myHandler --applyCORS :: MonadSnap m => CORSOptions m -> m () -> m () -- | Specify the options to use when building CORS headers for a response. -- Most of these options are Handler actions to allow you to -- conditionally determine the setting of each header. data CORSOptions m CORSOptions :: m OriginList -> m Bool -> m (HashSet (CI ByteString)) -> m (HashSet HashableMethod) -> (HashSet ByteString -> m (HashSet ByteString)) -> CORSOptions m -- | Which origins are allowed to make cross-origin requests. [corsAllowOrigin] :: CORSOptions m -> m OriginList -- | Whether or not to allow exposing the response when the omit -- credentials flag is unset. [corsAllowCredentials] :: CORSOptions m -> m Bool -- | A list of headers that are exposed to clients. This allows clients to -- read the values of these headers, if the response includes them. [corsExposeHeaders] :: CORSOptions m -> m (HashSet (CI ByteString)) -- | A list of request methods that are allowed. [corsAllowedMethods] :: CORSOptions m -> m (HashSet HashableMethod) -- | An action to determine which of the request headers are allowed. This -- action is supplied the parsed contents of -- Access-Control-Request-Headers. [corsAllowedHeaders] :: CORSOptions m -> HashSet ByteString -> m (HashSet ByteString) -- | Liberal default options. Specifies that: -- --
-- {--}
--
-- module Main where
--
-- import Snap.Core (Snap, route)
-- import Snap.Http.Server (quickHttpServe)
-- import Snap.Util.FileServe
--
-- site :: Snap ()
-- site = route
-- [ ("/files", serveDirectory "static")
-- , ("/simple", serveDirectoryWith simpleDirectoryConfig "static")
-- , ("/default", serveDirectoryWith defaultDirectoryConfig "static")
-- , ("/fancy", serveDirectoryWith fancyDirectoryConfig "static")
-- , ("/paper", serveFile "static/paper.pdf")
-- , ("/thesis", serveFileAs "application/pdf" "static/thesis.pdf")
-- ]
--
-- main :: IO ()
-- main = quickHttpServe site
--
module Snap.Util.FileServe
-- | Gets a path from the Request using rqPathInfo and makes
-- sure it is safe to use for opening files. A path is safe if it is a
-- relative path and has no ".." elements to escape the intended
-- directory structure.
--
-- Example:
--
-- -- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> import qualified Data.ByteString.Char8 as B8 -- ghci> T.runHandler (T.get "/foo/bar" M.empty) (getSafePath >>= writeBS . B8.pack) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Fri, 08 Aug 2014 16:13:20 GMT -- -- foo/bar -- ghci> T.runHandler (T.get "/foo/../bar" M.empty) (getSafePath >>= writeBS . B8.pack) -- HTTP/1.1 404 Not Found -- ... --getSafePath :: MonadSnap m => m FilePath -- | A type alias for MIME type type MimeMap = HashMap FilePath ByteString -- | A type alias for dynamic handlers type HandlerMap m = HashMap FilePath (FilePath -> m ()) -- | A collection of options for serving static files out of a directory. data DirectoryConfig m DirectoryConfig :: [FilePath] -> (FilePath -> m ()) -> HandlerMap m -> MimeMap -> (FilePath -> m ()) -> DirectoryConfig m -- | Files to look for when a directory is requested (e.g., index.html) [indexFiles] :: DirectoryConfig m -> [FilePath] -- | Handler to generate a directory listing if there is no index. [indexGenerator] :: DirectoryConfig m -> FilePath -> m () -- | Map of extensions to pass to dynamic file handlers. This could be -- used, for example, to implement CGI dispatch, pretty printing of -- source code, etc. [dynamicHandlers] :: DirectoryConfig m -> HandlerMap m -- | MIME type map to look up content types. [mimeTypes] :: DirectoryConfig m -> MimeMap -- | Handler that is called before a file is served. It will only be called -- when a file is actually found, not for generated index pages. [preServeHook] :: DirectoryConfig m -> FilePath -> m () -- | A very simple configuration for directory serving. This configuration -- uses built-in MIME types from defaultMimeTypes, and has no -- index files, index generator, dynamic file handlers, or -- preServeHook. simpleDirectoryConfig :: MonadSnap m => DirectoryConfig m -- | A reasonable default configuration for directory serving. This -- configuration uses built-in MIME types from defaultMimeTypes, -- serves common index files index.html and index.htm, -- but does not autogenerate directory indexes, nor have any dynamic file -- handlers. The preServeHook will not do anything. defaultDirectoryConfig :: MonadSnap m => DirectoryConfig m -- | A more elaborate configuration for file serving. This configuration -- uses built-in MIME types from defaultMimeTypes, serves common -- index files index.html and index.htm, and -- autogenerates directory indexes with a Snap-like feel. It still has no -- dynamic file handlers, nor preServeHook, which should be added -- as needed. -- -- Files recognized as indexes include index.html, -- index.htm, default.html, default.htm, -- home.html -- -- Example of how the autogenerated directory index looks like: -- fancyDirectoryConfig :: MonadSnap m => DirectoryConfig m -- | An automatic index generator, which is fairly small and does not rely -- on any external files (which may not be there depending on external -- request routing). -- -- A MimeMap is passed in to display the types of files in the -- directory listing based on their extension. Preferably, this is the -- same as the map in the DirectoryConfig -- -- The styles parameter allows you to apply styles to the directory -- listing. The listing itself consists of a table, containing a header -- row using th elements, and one row per file using td elements, so -- styles for those pieces may be attached to the appropriate tags. defaultIndexGenerator :: MonadSnap m => MimeMap -> ByteString -> FilePath -> m () -- | The default set of mime type mappings we use when serving files. Its -- value: -- --
-- Map.fromList [ -- ( ".asc" , "text/plain" ), -- ( ".asf" , "video/x-ms-asf" ), -- ( ".asx" , "video/x-ms-asf" ), -- ( ".au" , "audio/basic" ), -- ( ".avi" , "video/x-msvideo" ), -- ( ".bmp" , "image/bmp" ), -- ( ".bz2" , "application/x-bzip" ), -- ( ".c" , "text/plain" ), -- ( ".class" , "application/octet-stream" ), -- ( ".conf" , "text/plain" ), -- ( ".cpp" , "text/plain" ), -- ( ".css" , "text/css" ), -- ( ".csv" , "text/csv" ), -- ( ".cxx" , "text/plain" ), -- ( ".doc" , "application/msword" ), -- ( ".docx" , S.append "application/vnd.openxmlformats-officedocument" -- ".wordprocessingml.document" ), -- ( ".dotx" , S.append "application/vnd.openxmlformats-officedocument" -- ".wordprocessingml.template" ), -- ( ".dtd" , "application/xml-dtd" ), -- ( ".dvi" , "application/x-dvi" ), -- ( ".exe" , "application/octet-stream" ), -- ( ".flv" , "video/x-flv" ), -- ( ".gif" , "image/gif" ), -- ( ".gz" , "application/x-gzip" ), -- ( ".hs" , "text/plain" ), -- ( ".htm" , "text/html" ), -- ( ".html" , "text/html" ), -- ( ".ico" , "image/x-icon" ), -- ( ".jar" , "application/x-java-archive" ), -- ( ".jpeg" , "image/jpeg" ), -- ( ".jpg" , "image/jpeg" ), -- ( ".js" , "text/javascript" ), -- ( ".json" , "application/json" ), -- ( ".log" , "text/plain" ), -- ( ".m3u" , "audio/x-mpegurl" ), -- ( ".m3u8" , "application/x-mpegURL" ), -- ( ".mka" , "audio/x-matroska" ), -- ( ".mk3d" , "video/x-matroska" ), -- ( ".mkv" , "video/x-matroska" ), -- ( ".mov" , "video/quicktime" ), -- ( ".mp3" , "audio/mpeg" ), -- ( ".mp4" , "video/mp4" ), -- ( ".mpeg" , "video/mpeg" ), -- ( ".mpg" , "video/mpeg" ), -- ( ".ogg" , "application/ogg" ), -- ( ".pac" , "application/x-ns-proxy-autoconfig" ), -- ( ".pdf" , "application/pdf" ), -- ( ".png" , "image/png" ), -- ( ".potx" , S.append "application/vnd.openxmlformats-officedocument" -- ".presentationml.template" ), -- ( ".ppsx" , S.append "application/vnd.openxmlformats-officedocument" -- ".presentationml.slideshow" ), -- ( ".ppt" , "application/vnd.ms-powerpoint" ), -- ( ".pptx" , S.append "application/vnd.openxmlformats-officedocument" -- ".presentationml.presentation" ), -- ( ".ps" , "application/postscript" ), -- ( ".qt" , "video/quicktime" ), -- ( ".rtf" , "text/rtf" ), -- ( ".sig" , "application/pgp-signature" ), -- ( ".sldx" , S.append "application/vnd.openxmlformats-officedocument" -- ".presentationml.slide" ), -- ( ".spl" , "application/futuresplash" ), -- ( ".svg" , "image/svg+xml" ), -- ( ".swf" , "application/x-shockwave-flash" ), -- ( ".tar" , "application/x-tar" ), -- ( ".tar.bz2" , "application/x-bzip-compressed-tar" ), -- ( ".tar.gz" , "application/x-tgz" ), -- ( ".tbz" , "application/x-bzip-compressed-tar" ), -- ( ".text" , "text/plain" ), -- ( ".tgz" , "application/x-tgz" ), -- ( ".tif" , "image/tiff" ), -- ( ".tiff" , "image/tiff" ), -- ( ".torrent" , "application/x-bittorrent" ), -- ( ".ts" , "video/mp2t" ), -- ( ".txt" , "text/plain" ), -- ( ".wav" , "audio/x-wav" ), -- ( ".wax" , "audio/x-ms-wax" ), -- ( ".webm" , "video/webm" ), -- ( ".wma" , "audio/x-ms-wma" ), -- ( ".wmv" , "video/x-ms-wmv" ), -- ( ".xbm" , "image/x-xbitmap" ), -- ( ".xlam" , "application/vnd.ms-excel.addin.macroEnabled.12" ), -- ( ".xls" , "application/vnd.ms-excel" ), -- ( ".xlsb" , "application/vnd.ms-excel.sheet.binary.macroEnabled.12" ), -- ( ".xlsx" , S.append "application/vnd.openxmlformats-officedocument." -- "spreadsheetml.sheet" ), -- ( ".xltx" , S.append "application/vnd.openxmlformats-officedocument." -- "spreadsheetml.template" ), -- ( ".xml" , "text/xml" ), -- ( ".xpm" , "image/x-xpixmap" ), -- ( ".xwd" , "image/x-xwindowdump" ), -- ( ".zip" , "application/zip" ) ] --defaultMimeTypes :: MimeMap -- | Determine a given file's MIME type from its filename and the provided -- MIME map. fileType :: MimeMap -> FilePath -> ByteString -- | Serves static files from a directory using the default configuration -- as given in defaultDirectoryConfig. serveDirectory :: MonadSnap m => FilePath -> m () -- | Serves static files from a directory. Configuration options are passed -- in a DirectoryConfig that captures various choices about -- desired behavior. The relative path given in rqPathInfo is -- searched for a requested file, and the file is served with the -- appropriate mime type if it is found. Absolute paths and ".." -- are prohibited to prevent files from being served from outside the -- sandbox. serveDirectoryWith :: MonadSnap m => DirectoryConfig m -> FilePath -> m () -- | Serves a single file specified by a full or relative path. If the file -- does not exist, throws an exception (not that it does not pass -- to the next handler). The path restrictions on serveDirectory -- don't apply to this function since the path is not being supplied by -- the user. serveFile :: MonadSnap m => FilePath -> m () -- | Same as serveFile, with control over the MIME mapping used. serveFileAs :: MonadSnap m => ByteString -> FilePath -> m () -- | This module contains primitives and helper functions for handling -- requests with Content-type: multipart/form-data, i.e. HTML -- forms and file uploads. -- -- Typically most users will want to use handleFileUploads, which -- writes uploaded files to a temporary directory before sending them on -- to a handler specified by the user. -- -- Users who wish to handle their file uploads differently can use the -- lower-level interface called handleMultipart. That function -- takes uploaded files and streams them to a consumer of the user's -- choosing. -- -- Using these functions requires making "policy" decisions which Snap -- can't really make for users, such as "what's the largest PDF file a -- user is allowed to upload?" and "should we read form inputs into the -- parameters mapping?". Policy is specified on a "global" basis (using -- UploadPolicy), and on a per-file basis (using -- PartUploadPolicy, which allows you to reject or limit the size -- of certain uploaded Content-types). -- -- Example usage: -- --
-- {-# LANGUAGE OverloadedStrings #-}
--
-- module Main where
--
-- import qualified Data.ByteString.Char8 as B8
-- import Data.Functor ((<$>))
-- import Snap.Core (Snap, route, writeBS)
-- import Snap.Http.Server (quickHttpServe)
-- import Snap.Util.FileUploads
-- import System.Posix (FileOffset, fileSize, getFileStatus)
--
-- uploadForm :: Snap ()
-- uploadForm = writeBS "<form enctype=\"multipart/form-data\" action=\"/do-upload\" method=\"POST\">\
-- \<input name=\"file\" type=\"file\" />\
-- \<input type=\"submit\" value=\"Send File\" />\
-- \</form>"
--
-- getFileSize :: FilePath -> IO FileOffset
-- getFileSize path = fileSize <$> getFileStatus path
--
-- -- Upload handler that prints out the uploaded file's size.
-- doUpload :: Snap ()
-- doUpload = do
-- l <- handleFileUploads "/tmp" defaultUploadPolicy
-- (const $ allowWithMaximumSize (getMaximumFormInputSize defaultUploadPolicy))
-- (\pinfo mbfname -> do fsize <- either (const $ return 0) getFileSize mbfname
-- return (partFileName pinfo, fsize))
-- writeBS . B8.pack . show $ l
--
-- site :: Snap ()
-- site = route
-- [ ("/upload", uploadForm)
-- , ("/do-upload", doUpload)]
--
-- main :: IO ()
-- main = quickHttpServe site
--
module Snap.Util.FileUploads
-- | Processes form data and calls provided storage function on file parts.
--
-- You can use this together with withTemporaryStore,
-- storeAsLazyByteString or provide your own callback to store
-- uploaded files.
--
-- If you need to process uploaded file mime type or file name, do it in
-- the store callback function.
--
-- See also foldMultipart.
--
-- Example using with small files which can safely be stored in memory.
--
-- -- import qualified Data.ByteString.Lazy as Lazy -- -- handleSmallFiles :: MonadSnap m => [(ByteString, ByteString, Lazy.ByteString)] -- handleSmallFiles = handleFormUploads uploadPolicy filePolicy store -- -- where -- uploadPolicy = defaultUploadPolicy -- filePolicy = setMaximumFileSize (64*1024) -- $ setMaximumNumberOfFiles 5 -- defaultUploadPolicy -- store partInfo stream = do -- content <- storeAsLazyByteString partInfo stream -- let -- fileName = partFileName partInfo -- fileMime = partContentType partInfo -- in (fileName, fileMime, content) --handleFormUploads :: MonadSnap m => UploadPolicy -> FileUploadPolicy -> (PartInfo -> InputStream ByteString -> IO a) -> m ([FormParam], [FormFile a]) -- | Given an upload policy and a function to consume uploaded "parts", -- consume a request body uploaded with Content-type: -- multipart/form-data. -- -- If setProcessFormInputs is True, then parts with -- disposition form-data (a form parameter) will be processed -- and returned as first element of resulting pair. Parts with other -- disposition will be fed to PartFold handler. -- -- If setProcessFormInputs is False, then parts with any -- disposition will be fed to PartFold handler and first element -- of returned pair will be empty. In this case it is important that you -- limit number of form inputs and sizes of inputs in your -- PartFold handler to avoid common DOS attacks. -- -- Note: THE REQUEST MUST BE CORRECTLY ENCODED. If the request's -- Content-type is not "multipart/formdata", this -- function skips processing using pass. -- -- Most users will opt for the higher-level handleFileUploads, -- which writes to temporary files, rather than handleMultipart. -- This function should be chosen, however, if you need to stream -- uploaded files directly to your own processing function: e.g. to a -- database or a remote service via RPC. -- -- If the client's upload rate passes below the configured minimum (see -- setMinimumUploadRate and setMinimumUploadSeconds), this -- function terminates the connection. This setting is there to protect -- the server against slowloris-style denial of service attacks. -- -- Exceptions -- -- If the given UploadPolicy stipulates that you wish form inputs -- to be processed (using setProcessFormInputs), and a form input -- exceeds the maximum allowable size or the form exceeds maximum number -- of inputs, this function will throw a PolicyViolationException. -- -- If an uploaded part contains MIME headers longer than a fixed internal -- threshold (currently 32KB), this function will throw a -- BadPartException. -- -- Since: 1.0.3.0 foldMultipart :: MonadSnap m => UploadPolicy -> PartFold a -> a -> m ([FormParam], a) -- | A type alias for a function that will process one of the parts of a -- multipart/form-data HTTP request body with accumulator. type PartFold a = PartInfo -> InputStream ByteString -> a -> IO a -- | A form parameter name-value pair type FormParam = (ByteString, ByteString) -- | Contents of form field of type file data FormFile a FormFile :: !ByteString -> a -> FormFile a -- | Name of a field [formFileName] :: FormFile a -> !ByteString -- | Result of storing file [formFileValue] :: FormFile a -> a -- | Stores file body in memory as Lazy ByteString. storeAsLazyByteString :: InputStream ByteString -> IO ByteString -- | Store files in a temporary directory, and clean up on function exit. -- -- Files are safe to move until function exists. -- -- If asynchronous exception is thrown during cleanup, temporary files -- may remain. -- --
-- uploadsHandler = withTemporaryStore "vartmp" "upload-" $ store -> do -- (inputs, files) <- handleFormUploads defaultUploadpolicy -- defaultFileUploadPolicy -- (const store) -- saveFiles files --withTemporaryStore :: MonadSnap m => FilePath -> String -> ((InputStream ByteString -> IO FilePath) -> m a) -> m a -- | Reads uploaded files into a temporary directory and calls a user -- handler to process them. -- -- Note: THE REQUEST MUST BE CORRECTLY ENCODED. If the request's -- Content-type is not "multipart/formdata", this -- function skips processing using pass. -- -- Given a temporary directory, global and file-specific upload policies, -- and a user handler, this function consumes a request body uploaded -- with Content-type: multipart/form-data. Each file is read -- into the temporary directory, and is then passed to the user handler. -- After the user handler runs (but before the Response body is -- streamed to the client), the files are deleted from disk; so if you -- want to retain or use the uploaded files in the generated response, -- you need to move or otherwise process them. -- -- The argument passed to the user handler is a tuple: -- --
-- (PartInfo, Either PolicyViolationException FilePath) ---- -- The first half of this tuple is a PartInfo, which contains the -- information the client browser sent about the given upload part (like -- filename, content-type, etc). The second half of this tuple is an -- Either stipulating that either: -- --
-- foo `catch` \(e :: FileUploadException) -> ... ---- -- you can catch a BadPartException, a -- PolicyViolationException, etc. data FileUploadException -- | Human-readable error message corresponding to the -- FileUploadException. fileUploadExceptionReason :: FileUploadException -> Text -- | Thrown when a part is invalid in some way (e.g. the headers are too -- large). data BadPartException -- | Human-readable error message corresponding to the -- BadPartException. badPartExceptionReason :: BadPartException -> Text -- | Thrown when an UploadPolicy or PartUploadPolicy is -- violated. data PolicyViolationException -- | Human-readable error message corresponding to the -- PolicyViolationException. policyViolationExceptionReason :: PolicyViolationException -> Text -- | Helpers for running a Snap web handler with compression. module Snap.Util.GZip -- | Runs a Snap web handler with compression if available. -- -- If the client has indicated support for gzip or -- deflate in its Accept-Encoding header, and the -- Content-Type in the response is one of the following types: -- --
application/x-javascript
application/json
text/css
text/html
text/javascript
text/plain
text/xml
application/x-font-truetype
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/" M.empty >> T.addHeader "Accept-Encoding" "gzip,deflate" -- ghci> let h = modifyResponse (setContentType "text/plain") >> writeBS "some text" -- ghci> T.runHandler r h -- HTTP/1.1 200 OK -- content-type: text/plain -- server: Snap/test -- date: Fri, 08 Aug 2014 15:40:45 GMT -- -- some text -- ghci> T.runHandler r (withCompression h) -- HTTP/1.1 200 OK -- content-type: text/plain -- vary: Accept-Encoding -- content-encoding: gzip -- server: Snap/test -- date: Fri, 08 Aug 2014 15:40:10 GMT --withCompression :: MonadSnap m => m a -> m () -- | The same as withCompression, with control over which MIME types -- to compress. withCompression' :: MonadSnap m => Set ByteString -> m a -> m () -- | Turn off compression by setting "Content-Encoding: identity" in the -- response headers. withCompression is a no-op when a -- content-encoding is already set. noCompression :: MonadSnap m => m () -- | Thrown when the 'Accept-Encoding' request header has invalid format. data BadAcceptEncodingException compressibleMimeTypes :: Set ByteString instance GHC.Show.Show Snap.Util.GZip.BadAcceptEncodingException instance GHC.Exception.Type.Exception Snap.Util.GZip.BadAcceptEncodingException -- | This module provides facilities for patching incoming -- Requests to correct the value of rqClientAddr if the -- snap server is running behind a proxy. -- -- Example usage: -- --
-- m :: Snap () -- m = undefined -- code goes here -- -- applicationHandler :: Snap () -- applicationHandler = behindProxy X_Forwarded_For m --module Snap.Util.Proxy -- | What kind of proxy is this? Affects which headers behindProxy -- pulls the original remote address from. -- -- Currently only proxy servers that send X-Forwarded-For or -- Forwarded-For are supported. data ProxyType -- | no proxy, leave the request alone NoProxy :: ProxyType -- | Use the Forwarded-For or X-Forwarded-For header X_Forwarded_For :: ProxyType -- | Rewrite rqClientAddr if we're behind a proxy. -- -- Example: -- --
-- ghci> :set -XOverloadedStrings -- ghci> import qualified Data.Map as M -- ghci> import qualified Snap.Test as T -- ghci> let r = T.get "/foo" M.empty >> T.addHeader "X-Forwarded-For" "1.2.3.4" -- ghci> let h = getsRequest rqClientAddr >>= writeBS) -- ghci> T.runHandler r h -- HTTP/1.1 200 OK -- server: Snap/test -- date: Fri, 08 Aug 2014 14:32:29 GMT -- -- 127.0.0.1 -- ghci> T.runHandler r (behindProxy X_Forwarded_For h) -- HTTP/1.1 200 OK -- server: Snap/test -- date: Fri, 08 Aug 2014 14:33:02 GMT -- -- 1.2.3.4 --behindProxy :: MonadSnap m => ProxyType -> m a -> m a instance GHC.Classes.Ord Snap.Util.Proxy.ProxyType instance GHC.Classes.Eq Snap.Util.Proxy.ProxyType instance GHC.Show.Show Snap.Util.Proxy.ProxyType instance GHC.Read.Read Snap.Util.Proxy.ProxyType