This is the Resource Monad; monadic actions to define a behavior
of resource. The Rsrc
Monad is a kind of IO
Monad thus it
implements MonadIO
class, and it is a state machine as well.
Request Processing Flow:
- A client issues an HTTP request.
- If the URI of it matches to any resource, the corresponding
Rsrc
Monad starts running on a newly spawned thread. - The
Rsrc
Monad looks at request headers, find (or not find) an entity, receive the request body (if any), send response headers, and then send a response body. This process will be discussed later. - The
Rsrc
Monad and its thread stops running. The client may or may not be sending us the next request at this point.
Rsrc
Monad takes the following states. The initial state is
Examining Request and the final state is Done.
- Examining Request
- In this state, a
Rsrc
looks at the request header fields and thinks about the corresponding entity for it. If there is a suitable entity, theRsrc
tells the system an entity tag and its last modification time (foundEntity
). If it found no entity, it tells the system so (foundNoEntity
). In case it is impossible to decide the existence of entity, which is a typical case for POST requests,Rsrc
does nothing in this state. - Receiving Body
- A
Rsrc
asks the system to receive a request body from the client. Before actually reading from the socket, the system sends "100 Continue" to the client if need be. When aRsrc
transits to the next state without receiving all or part of a request body, the system automatically discards it. - Deciding Header
- A
Rsrc
makes a decision of response status code and header fields. When it transits to the next state, the system validates and completes the header fields and then sends them to the client. - Sending Body
- In this state, a
Rsrc
asks the system to write some response body to the socket. When it transits to the next state without writing any response body, the system automatically completes it depending on the status code. (To be exact, such completion only occurs when theRsrc
transits to this state without even declaring the "Content-Type" header field. See:setContentType
) - Done
- Everything is over. A
Rsrc
can do nothing for the HTTP interaction anymore.
Note that the state transition is one-way: for instance, it is an
error to try to read a request body after writing some
response. This limitation is for efficiency. We don't want to read
the entire request before starting Rsrc
, nor we don't want to
postpone writing the entire response till the end of Rsrc
computation.
- data Resource = Resource {}
- data Rsrc a
- data FormData = FormData {
- fdFileName :: !(Maybe Text)
- fdMIMEType :: !MIMEType
- fdContent :: !ByteString
- getConfig :: Rsrc Config
- getRemoteAddr :: Rsrc SockAddr
- getRemoteAddr' :: Rsrc HostName
- getRemoteHost :: Rsrc (Maybe HostName)
- getRemoteCertificate :: Rsrc (Maybe X509)
- getRequest :: Rsrc Request
- getMethod :: Rsrc Method
- getRequestURI :: Rsrc URI
- getRequestVersion :: Rsrc HttpVersion
- getResourcePath :: Rsrc Path
- getPathInfo :: Rsrc [ByteString]
- getQueryForm :: Rsrc [(ByteString, FormData)]
- getHeader :: CIAscii -> Rsrc (Maybe Ascii)
- getAccept :: Rsrc [MIMEType]
- getAcceptEncoding :: Rsrc [(CIAscii, Maybe Double)]
- isEncodingAcceptable :: CIAscii -> Rsrc Bool
- getContentType :: Rsrc (Maybe MIMEType)
- getAuthorization :: Rsrc (Maybe AuthCredential)
- foundEntity :: ETag -> UTCTime -> Rsrc ()
- foundETag :: ETag -> Rsrc ()
- foundTimeStamp :: UTCTime -> Rsrc ()
- foundNoEntity :: Maybe Text -> Rsrc ()
- foundNoEntity' :: Rsrc ()
- getChunk :: Int -> Rsrc ByteString
- getChunks :: Maybe Int -> Rsrc ByteString
- getForm :: Maybe Int -> Rsrc [(ByteString, FormData)]
- setStatus :: StatusCode sc => sc -> Rsrc ()
- redirect :: StatusCode sc => sc -> URI -> Rsrc ()
- setContentType :: MIMEType -> Rsrc ()
- setContentEncoding :: [CIAscii] -> Rsrc ()
- setWWWAuthenticate :: AuthChallenge -> Rsrc ()
- setLocation :: URI -> Rsrc ()
- setHeader :: CIAscii -> Ascii -> Rsrc ()
- deleteHeader :: CIAscii -> Rsrc ()
- putChunk :: ByteString -> Rsrc ()
- putChunks :: ByteString -> Rsrc ()
- putBuilder :: Builder -> Rsrc ()
Types
Resource | |
|
The resource monad. This monad implements MonadIO
so it can do
any IO
actions.
FormData
represents a form value and possibly an uploaded file
name.
FormData | |
|
Getting request header
These functions can be called regardless of the current state,
and they don't change the state of Rsrc
.
getRemoteAddr :: Rsrc SockAddrSource
Get the SockAddr
of the remote host.
getRemoteAddr' :: Rsrc HostNameSource
Get the string representation of the address of remote host. If
you want a SockAddr
instead of HostName
, use getRemoteAddr
.
getRemoteHost :: Rsrc (Maybe HostName)Source
Resolve an address to the remote host.
getRemoteCertificate :: Rsrc (Maybe X509)Source
getRequest :: Rsrc RequestSource
Return the Request
value representing the request header. You
usually don't need to call this function directly.
getRequestURI :: Rsrc URISource
Get the URI of the request.
getRequestVersion :: Rsrc HttpVersionSource
Get the HTTP version of the request.
getResourcePath :: Rsrc PathSource
Get the path of this Rsrc
(to be exact, Resource
) in the
corresponding Network.HTTP.Lucu.ResourceTree
. The result of this
action is the exact path in the tree even when the Resource
is
Network.HTTP.Lucu.greedy
.
Example:
main ::IO
() main = let tree ::Network.HTTP.Lucu.ResourceTree
tree =fromList
[ ([foo],Network.HTTP.Lucu.greedy
resFoo) ] inNetwork.withSocketsDo
.
Network.HTTP.Lucu.runHttpd
defaultConfig
$Network.HTTP.Lucu.resourceMap
tree resFoo ::Resource
resFoo =singleton
(GET
, do requestURI <-getRequestURI
resourcePath <-getResourcePath
pathInfo <-getPathInfo
--Network.URI.uriPath
requestURI==
"foobar/baz" -- resourcePath == [foo] -- pathInfo == [bar, baz] ... )
getPathInfo :: Rsrc [ByteString]Source
This is an analogy of CGI PATH_INFO. getPathInfo
always returns
[]
if the corresponding Resource
is not greedy. See
getResourcePath
.
Note that the returned path components are URI-decoded.
getQueryForm :: Rsrc [(ByteString, FormData)]Source
Assume the query part of request URI as
application/x-www-form-urlencoded, and parse it into pairs of
(name, formData)
. This function doesn't read the request
body.
getHeader :: CIAscii -> Rsrc (Maybe Ascii)Source
returns the value of the request header field
getHeader
namename
. Comparison of header name is case-insensitive. Note that
this function is not intended to be used so frequently: there
should be functions like getContentType
for every common headers.
getAccept :: Rsrc [MIMEType]Source
Return the list of MIMEType
enumerated on the value of request
header "Accept", or []
if absent.
getAcceptEncoding :: Rsrc [(CIAscii, Maybe Double)]Source
Return the list of (contentCoding, qvalue)
enumerated on the
value of request header "Accept-Encoding". The list is sorted in
descending order by qvalue.
isEncodingAcceptable :: CIAscii -> Rsrc BoolSource
Return True
iff a given content-coding is acceptable by the
client.
getContentType :: Rsrc (Maybe MIMEType)Source
Return the value of request header "Content-Type" as MIMEType
.
getAuthorization :: Rsrc (Maybe AuthCredential)Source
Return the value of request header "Authorization" as
AuthCredential
.
Finding an entity
These functions can be called only in the Examining Request
state. They make the Rsrc
transit to the Receiving Body
state.
foundEntity :: ETag -> UTCTime -> Rsrc ()Source
Tell the system that the Rsrc
found an entity for the request
URI. If this is a GET or HEAD request, a found entity means a datum
to be replied. If this is a PUT or DELETE request, it means a datum
which was stored for the URI until now. For POST requests it raises
an error.
foundEntity
performs "If-Match" test or "If-None-Match" test
whenever possible, and if those tests fail, it immediately aborts
with status "412 Precondition Failed" or "304 Not Modified"
depending on the situation.
If the request method is either GET or HEAD, foundEntity
automatically puts "ETag" and "Last-Modified" headers into the
response.
foundETag :: ETag -> Rsrc ()Source
Tell the system that the Rsrc
found an entity for the request
URI. The only difference from foundEntity
is that foundETag
doesn't (nor can't) put "Last-Modified" header into the response.
Using this function is discouraged. You should use foundEntity
whenever possible.
foundTimeStamp :: UTCTime -> Rsrc ()Source
Tell the system that the Rsrc
found an entity for the
request URI. The only difference from foundEntity
is that
foundTimeStamp
performs "If-Modified-Since" test or
"If-Unmodified-Since" test instead of "If-Match" test or
"If-None-Match" test. Be aware that any tests based on a last
modification time are unsafe because it is possible to mess up such
tests by modifying the entity twice in a second.
Using this function is discouraged. You should use foundEntity
whenever possible.
foundNoEntity :: Maybe Text -> Rsrc ()Source
tells the system that the foundNoEntity
mStrRsrc
found no
entity for the request URI. mStr
is an optional error message to
be replied to the client.
If the request method is PUT, foundNoEntity
performs "If-Match"
test and when that fails it aborts with status "412 Precondition
Failed". If the request method is GET, HEAD, POST or DELETE,
foundNoEntity
always aborts with status "404 Not Found".
foundNoEntity' :: Rsrc ()Source
foundNoEntity'
is the same as
.
foundNoEntity
Nothing
Receiving a request body
These functions make the Rsrc
transit to the /Receiving
Body/ state.
getChunk :: Int -> Rsrc ByteStringSource
getChunks :: Maybe Int -> Rsrc ByteStringSource
attemts to read the entire request body up to
getChunks
limitlimit
bytes, and then make the Rsrc
transit to the /Deciding
Header/ state. When the actual size of the body is larger than
limit
bytes, getChunks
immediately aborts with status "413
Request Entity Too Large". When the request has no body, it
returns an empty string.
When the limit
is Nothing
, getChunks
uses the default
limitation value (cnfMaxEntityLength
) instead.
getChunks
returns a lazy ByteString
but it's not really
lazy: reading from the socket just happens at the computation of
getChunks
, not at the evaluation of the ByteString
.
getForm :: Maybe Int -> Rsrc [(ByteString, FormData)]Source
attempts to read the request body with
getForm
limitgetChunks
and parse it as application/x-www-form-urlencoded
or
multipart/form-data
. If the request header "Content-Type" is
neither of them, getForm
aborts with status "415 Unsupported
Media Type". If the request has no "Content-Type", it aborts
with "400 Bad Request".
Note that there are currently a few limitations on parsing
multipart/form-data
. See: parseMultipartFormData
Declaring response status and header fields
These functions can be called at any time before transiting to the Sending Body state, but they themselves never causes any state transitions.
setStatus :: StatusCode sc => sc -> Rsrc ()Source
Declare the response status code. If you don't call this function, the status code will be defaulted to "200 OK".
redirect :: StatusCode sc => sc -> URI -> Rsrc ()Source
declares the response status as redirect
code uricode
and
"Location" header field as uri
. The code
must satisfy
isRedirection
or it raises an error.
setContentType :: MIMEType -> Rsrc ()Source
declares the response header
"Content-Type" as setContentType
mTypemType
. Declaring "Content-Type" is
mandatory for sending a response body.
setContentEncoding :: [CIAscii] -> Rsrc ()Source
declares the response header
"Content-Encoding" as setContentEncoding
codingscodings
.
setWWWAuthenticate :: AuthChallenge -> Rsrc ()Source
declares the response header
"WWW-Authenticate" as setWWWAuthenticate
challengechallenge
.
Less frequently used functions
setLocation :: URI -> Rsrc ()Source
declares the response header "Location" as
setLocation
uriuri
. You usually don't need to call this function directly.
setHeader :: CIAscii -> Ascii -> Rsrc ()Source
declares the value of the response header
setHeader
name valuename
as value
. Note that this function is not intended to be
used so frequently: there should be specialised functions like
Network.HTTP.Lucu.setContentType
for every common headers.
Some important headers (especially "Content-Length" and "Transfer-Encoding") may be silently dropped or overwritten by the system not to corrupt the interaction with client at the viewpoint of HTTP protocol layer. For instance, if we are keeping the connection alive, without this manipulation it will be a catastrophe when we send a header "Content-Length: 10" and actually send a body of 20 bytes long to the remote peer. In this case the client shall only accept the first 10 bytes of response body and thinks that the residual 10 bytes is a part of the header of the next response.
deleteHeader :: CIAscii -> Rsrc ()Source
deletes a response header deleteHeader
namename
if
any. This function is not intended to be used so frequently.
Sending a response body
These functions make the Rsrc
transit to the Sending Body
state.
putChunk :: ByteString -> Rsrc ()Source
Write a chunk in ByteString
to the response body. You
must first declare the response header "Content-Type" before
applying this function. See setContentType
.
putChunks :: ByteString -> Rsrc ()Source
Write a chunk in lazy ByteString
to the response body. It
can be safely applied to an infinitely long ByteString
.
Note that you must first declare the response header
"Content-Type" before applying this function. See
setContentType
.
putBuilder :: Builder -> Rsrc ()Source
Run a Builder
to construct a chunk, and write it to the response
body. It can be safely applied to a Builder
producing an
infinitely long stream of octets.
Note that you must first declare the response header
"Content-Type" before applying this function. See
Network.HTTP.Lucu.setContentType
.