Lucu-1.0: Embedded HTTP Server

Network.HTTP.Lucu.Resource

Contents

Description

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:

  1. A client issues an HTTP request.
  2. If the URI of it matches to any resource, the corresponding Rsrc Monad starts running on a newly spawned thread.
  3. 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.
  4. 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, the Rsrc 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 a Rsrc 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 the Rsrc 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.

Synopsis

Types

data Resource Source

Resource is basically a set of Rsrc monadic computations for each HTTP methods.

Constructors

Resource 

Fields

resGet :: !(Maybe (Rsrc ()))

A Rsrc to be run when a GET request comes for the resource path. If resGet is Nothing, the system responds "405 Method Not Allowed" for GET requests.

It also runs for HEAD request if the resHead is Nothing. In that case putChunk and such don't actually write a response body.

resHead :: !(Maybe (Rsrc ()))

A Rsrc to be run when a HEAD request comes for the resource path. If resHead is Nothing, the system runs resGet instead. If resGet is also Nothing, the system responds "405 Method Not Allowed" for HEAD requests.

resPost :: !(Maybe (Rsrc ()))

A Rsrc to be run when a POST request comes for the resource path. If resPost is Nothing, the system responds "405 Method Not Allowed" for POST requests.

resPut :: !(Maybe (Rsrc ()))

A Rsrc to be run when a PUT request comes for the resource path. If resPut is Nothing, the system responds "405 Method Not Allowed" for PUT requests.

resDelete :: !(Maybe (Rsrc ()))

A Rsrc to be run when a DELETE request comes for the resource path. If resDelete is Nothing, the system responds "405 Method Not Allowed" for DELETE requests.

Instances

Monoid Resource 
Collection Resource (Method, Rsrc ()) 
Unfoldable Resource (Method, Rsrc ()) 
Foldable Resource (Method, Rsrc ()) 
ResourceMapper (Path -> Maybe (Path, Resource))

A pure resource mapper.

ResourceMapper (Path -> MaybeT IO (Path, Resource))

An IO-based resource mapper.

data Rsrc a Source

The resource monad. This monad implements MonadIO so it can do any IO actions.

Instances

Monad Rsrc 
Functor Rsrc 
MonadFix Rsrc 
Applicative Rsrc 
MonadIO Rsrc 
Collection Resource (Method, Rsrc ()) 
Unfoldable Resource (Method, Rsrc ()) 
Foldable Resource (Method, Rsrc ()) 

data FormData Source

FormData represents a form value and possibly an uploaded file name.

Constructors

FormData 

Fields

fdFileName :: !(Maybe Text)

Nothing for non-file values.

fdMIMEType :: !MIMEType

MIME Type of this value, defaulted to "text/plain".

fdContent :: !ByteString

The form value.

Getting request header

These functions can be called regardless of the current state, and they don't change the state of Rsrc.

getConfig :: Rsrc ConfigSource

Get the Config value for this httpd.

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

Return the X.509 certificate of the client, or Nothing if:

  • This request didn't came through an SSL stream.
  • The client didn't send us its certificate.
  • The OpenSSL.Session.VerificationMode of OpenSSL.Session.SSLContext in SSLConfig has not been set to OpenSSL.Session.VerifyPeer.

getRequest :: Rsrc RequestSource

Return the Request value representing the request header. You usually don't need to call this function directly.

getMethod :: Rsrc MethodSource

Get the Method value of the request.

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) ]
          in Network.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

getHeader name returns the value of the request header field name. 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

foundNoEntity mStr tells the system that the Rsrc 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".

Receiving a request body

These functions make the Rsrc transit to the /Receiving Body/ state.

getChunk :: Int -> Rsrc ByteStringSource

getChunk n attempts to read a chunk of request body up to n bytes. You can incrementally read the request body by repeatedly calling this function. If there is nothing to be read anymore, getChunk returns empty and makes Rsrc transit to the Deciding Header state.

getChunks :: Maybe Int -> Rsrc ByteStringSource

getChunks limit attemts to read the entire request body up to limit 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

getForm limit attempts to read the request body with getChunks 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

redirect code uri declares the response status as code and "Location" header field as uri. The code must satisfy isRedirection or it raises an error.

setContentType :: MIMEType -> Rsrc ()Source

setContentType mType declares the response header "Content-Type" as mType. Declaring "Content-Type" is mandatory for sending a response body.

setContentEncoding :: [CIAscii] -> Rsrc ()Source

setContentEncoding codings declares the response header "Content-Encoding" as codings.

setWWWAuthenticate :: AuthChallenge -> Rsrc ()Source

setWWWAuthenticate challenge declares the response header "WWW-Authenticate" as challenge.

Less frequently used functions

setLocation :: URI -> Rsrc ()Source

setLocation uri declares the response header "Location" as uri. You usually don't need to call this function directly.

setHeader :: CIAscii -> Ascii -> Rsrc ()Source

setHeader name value declares the value of the response header name 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

deleteHeader name deletes a response header name 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.