Snap.Test

snap-core-1.0.5.1: Snap: A Haskell Web Framework (core interfaces and types)

Safe Haskell | None
Language | Haskell2010

Snap.Test

Contents

- Combinators and types for testing Snap handlers.
  - Types
  - Building Requests and testing handlers
    - Convenience functions for generating common types of HTTP requests
    - Precise control over building Requests
- HUnit Assertions
- Getting response bodies
- Dumping HTTP Messages

Description

The Snap.Test module contains primitives and combinators for testing Snap applications.

Synopsis

- data RequestBuilder (m :: Type -> Type) a
- type MultipartParams = [(ByteString, MultipartParam)]
- data MultipartParam
  - = FormData [ByteString]
  - | Files [FileData]
- data FileData = FileData {
  - fdFileName :: ByteString
  - fdContentType :: ByteString
  - fdContents :: ByteString
  }
- data RequestType
  - = GetRequest
  - | RequestWithRawBody Method ByteString
  - | MultipartPostRequest MultipartParams
  - | UrlEncodedPostRequest Params
  - | DeleteRequest
- buildRequest :: MonadIO m => RequestBuilder m () -> m Request
- runHandler :: MonadIO m => RequestBuilder m () -> Snap a -> m Response
- runHandlerM :: (MonadIO m, MonadSnap n) => (forall a. Request -> n a -> m Response) -> RequestBuilder m () -> n b -> m Response
- evalHandler :: MonadIO m => RequestBuilder m () -> Snap a -> m a
- evalHandlerM :: (MonadIO m, MonadSnap n) => (forall a. Request -> n a -> m a) -> RequestBuilder m () -> n b -> m b
- get :: forall (m :: Type -> Type). MonadIO m => ByteString -> Params -> RequestBuilder m ()
- head :: HasCallStack => [a] -> a
- postUrlEncoded :: forall (m :: Type -> Type). MonadIO m => ByteString -> Params -> RequestBuilder m ()
- postMultipart :: forall (m :: Type -> Type). MonadIO m => ByteString -> MultipartParams -> RequestBuilder m ()
- put :: forall (m :: Type -> Type). MonadIO m => ByteString -> ByteString -> ByteString -> RequestBuilder m ()
- postRaw :: forall (m :: Type -> Type). MonadIO m => ByteString -> ByteString -> ByteString -> RequestBuilder m ()
- delete :: forall (m :: Type -> Type). MonadIO m => ByteString -> Params -> RequestBuilder m ()
- addHeader :: forall (m :: Type -> Type). Monad m => CI ByteString -> ByteString -> RequestBuilder m ()
- setContentType :: forall (m :: Type -> Type). Monad m => ByteString -> RequestBuilder m ()
- setHeader :: forall (m :: Type -> Type). Monad m => CI ByteString -> ByteString -> RequestBuilder m ()
- addCookies :: forall (m :: Type -> Type). Monad m => [Cookie] -> RequestBuilder m ()
- setHttpVersion :: forall (m :: Type -> Type). Monad m => (Int, Int) -> RequestBuilder m ()
- setQueryString :: forall (m :: Type -> Type). Monad m => Params -> RequestBuilder m ()
- setQueryStringRaw :: forall (m :: Type -> Type). Monad m => ByteString -> RequestBuilder m ()
- setRequestPath :: forall (m :: Type -> Type). Monad m => ByteString -> RequestBuilder m ()
- setRequestType :: forall (m :: Type -> Type). MonadIO m => RequestType -> RequestBuilder m ()
- setSecure :: forall (m :: Type -> Type). Monad m => Bool -> RequestBuilder m ()
- assertSuccess :: Response -> Assertion
- assert404 :: Response -> Assertion
- assertRedirectTo :: ByteString -> Response -> Assertion
- assertRedirect :: Response -> Assertion
- assertBodyContains :: ByteString -> Response -> Assertion
- getResponseBody :: Response -> IO ByteString
- requestToString :: Request -> IO ByteString
- responseToString :: Response -> IO ByteString

# Combinators and types for testing Snap handlers.

## Types

data RequestBuilder (m :: Type -> Type) a #

RequestBuilder is a monad transformer that allows you to conveniently build a snap `Request` for testing.

#### Instances

Instances details

MonadTrans RequestBuilder # |
Instance details Defined in Snap.Internal.Test.RequestBuilder Methods lift :: Monad m => m a -> RequestBuilder m a #
Monad m => MonadState Request (RequestBuilder m) # |
Instance details Defined in Snap.Internal.Test.RequestBuilder Methods get :: RequestBuilder m Request # put :: Request -> RequestBuilder m () # state :: (Request -> (a, Request)) -> RequestBuilder m a #
MonadIO m => MonadIO (RequestBuilder m) # |
Instance details Defined in Snap.Internal.Test.RequestBuilder Methods liftIO :: IO a -> RequestBuilder m a #
Monad m => Applicative (RequestBuilder m) # |
Instance details Defined in Snap.Internal.Test.RequestBuilder Methods pure :: a -> RequestBuilder m a # (<*>) :: RequestBuilder m (a -> b) -> RequestBuilder m a -> RequestBuilder m b # liftA2 :: (a -> b -> c) -> RequestBuilder m a -> RequestBuilder m b -> RequestBuilder m c # (*>) :: RequestBuilder m a -> RequestBuilder m b -> RequestBuilder m b # (<*) :: RequestBuilder m a -> RequestBuilder m b -> RequestBuilder m a #
Functor m => Functor (RequestBuilder m) # |
Instance details Defined in Snap.Internal.Test.RequestBuilder Methods fmap :: (a -> b) -> RequestBuilder m a -> RequestBuilder m b # (<$) :: a -> RequestBuilder m b -> RequestBuilder m a #
Monad m => Monad (RequestBuilder m) # |
Instance details Defined in Snap.Internal.Test.RequestBuilder Methods (>>=) :: RequestBuilder m a -> (a -> RequestBuilder m b) -> RequestBuilder m b # (>>) :: RequestBuilder m a -> RequestBuilder m b -> RequestBuilder m b # return :: a -> RequestBuilder m a #
MonadFail m => MonadFail (RequestBuilder m) # |
Instance details Defined in Snap.Internal.Test.RequestBuilder Methods fail :: String -> RequestBuilder m a #

type MultipartParams = [(ByteString, MultipartParam)] #

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.

data MultipartParam #

A single "`multipart/form-data`" form parameter: either a list of regular form values or a set of file uploads.

Constructors

FormData [ByteString] | a form variable consisting of the given `ByteString` values.
Files [FileData] | a file upload consisting of the given `FileData` values.

#### Instances

Instances details

Show MultipartParam # |
Instance details Defined in Snap.Internal.Test.RequestBuilder Methods showsPrec :: Int -> MultipartParam -> ShowS # show :: MultipartParam -> String # showList :: [MultipartParam] -> ShowS #

data FileData #

Represents a single file upload for the `MultipartParam`.

Constructors

FileData |
Fields fdFileName :: ByteString the file's name fdContentType :: ByteString the file's content-type fdContents :: ByteString the file contents

#### Instances

Instances details

Show FileData # |
Instance details Defined in Snap.Internal.Test.RequestBuilder Methods showsPrec :: Int -> FileData -> ShowS # show :: FileData -> String # showList :: [FileData] -> ShowS #

data RequestType #

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.

Constructors

GetRequest |
RequestWithRawBody Method ByteString |
MultipartPostRequest MultipartParams |
UrlEncodedPostRequest Params |
DeleteRequest |

#### Instances

Instances details

Show RequestType # |
Instance details Defined in Snap.Internal.Test.RequestBuilder Methods showsPrec :: Int -> RequestType -> ShowS # show :: RequestType -> String # showList :: [RequestType] -> ShowS #

## Building Requests and testing handlers

buildRequest :: MonadIO m => RequestBuilder m () -> m Request #

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

runHandler #

Arguments

:: MonadIO m |
=> RequestBuilder m () | a request builder
-> Snap a | a web handler
-> m Response |

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!

runHandlerM #

Arguments

:: (MonadIO m, MonadSnap n) |
=> (forall a. Request -> n a -> m Response) | a function defining how the `MonadSnap` monad should be run
-> RequestBuilder m () | a request builder
-> n b | a web handler
-> 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`.

evalHandler :: MonadIO m => RequestBuilder m () -> Snap a -> m a #

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

evalHandlerM #

Arguments

:: (MonadIO m, MonadSnap n) |
=> (forall a. Request -> n a -> m a) | a function defining how the `MonadSnap` monad should be run
-> RequestBuilder m () | a request builder
-> n b | a web handler
-> m b |

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`.

### Convenience functions for generating common types of HTTP requests

get #

Arguments

:: forall (m :: Type -> Type). MonadIO m |
=> ByteString | request path
-> Params | request's form parameters
-> RequestBuilder m () |

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&param0=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"]

head :: HasCallStack => [a] -> a #

\(\mathcal{O}(1)\). Extract the first element of a list, which must be non-empty.

To disable the warning about partiality put `{-# OPTIONS_GHC -Wno-x-partial -Wno-unrecognised-warning-flags #-}` at the top of the file. To disable it throughout a package put the same options into `ghc-options` section of Cabal file. To disable it in GHCi put `:set -Wno-x-partial -Wno-unrecognised-warning-flags` into `~/.ghci` config file. See also the migration guide.

##### Examples

Expand

    >>> head [1, 2, 3]
    1

    >>> head [1..]
    1

    >>> head []
    *** Exception: Prelude.head: empty list

postUrlEncoded #

Arguments

:: forall (m :: Type -> Type). MonadIO m |
=> ByteString | request path
-> Params | request's form parameters
-> 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"]

postMultipart #

Arguments

:: forall (m :: Type -> Type). MonadIO m |
=> ByteString | request path
-> MultipartParams | multipart form parameters
-> 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

put #

Arguments

:: forall (m :: Type -> Type). MonadIO m |
=> ByteString | request path
-> ByteString | request body MIME content-type
-> ByteString | request body contents
-> 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

postRaw #

Arguments

:: forall (m :: Type -> Type). MonadIO m |
=> ByteString | request path
-> ByteString | request body MIME content-type
-> ByteString | request body contents
-> 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

delete #

Arguments

:: forall (m :: Type -> Type). MonadIO m |
=> ByteString | request path
-> Params | request's form parameters
-> 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

### Precise control over building Requests

addHeader :: forall (m :: Type -> Type). Monad m => CI ByteString -> ByteString -> 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

setContentType :: forall (m :: Type -> Type). Monad m => 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

setHeader :: forall (m :: Type -> Type). Monad m => CI ByteString -> 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

addCookies :: forall (m :: Type -> Type). Monad m => [Cookie] -> 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", ...}

setHttpVersion :: forall (m :: Type -> Type). Monad m => (Int, Int) -> 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

setQueryString :: forall (m :: Type -> Type). Monad m => Params -> 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&param1=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 :: forall (m :: Type -> Type). Monad m => ByteString -> 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&param1=qux"
    GET /foo/bar?param0=baz&param1=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"]

setRequestPath :: forall (m :: Type -> Type). 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

setRequestType :: forall (m :: Type -> Type). MonadIO m => RequestType -> 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

setSecure :: forall (m :: Type -> Type). Monad m => Bool -> 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

# HUnit Assertions

assertSuccess :: Response -> Assertion #

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}

assert404 :: 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"

assertRedirectTo #

Arguments

:: ByteString | The Response should redirect to this URI
-> 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)"

assertRedirect :: 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)"

assertBodyContains #

Arguments

:: ByteString | Regexp that will match the body content
-> 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"

# Getting response bodies

getResponseBody :: Response -> IO ByteString #

Given a `Response`, return its body as a `ByteString`.

Example:

    ghci> getResponseBody emptyResponse
    ""

# Dumping HTTP Messages

requestToString :: Request -> 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"

responseToString :: Response -> 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"