packages feed

happstack-lite (empty) → 6.0.0

raw patch · 4 files changed

+510/−0 lines, 4 filesdep +basedep +bytestringdep +happstack-serversetup-changed

Dependencies added: base, bytestring, happstack-server, text

Files

+ Happstack/Lite.hs view
@@ -0,0 +1,455 @@+{-# LANGUAGE RecordWildCards #-}+module Happstack.Lite +     ( -- * Core Types+       Request+     , Response+     , ServerPart+     -- * Starting the Server+     , ServerConfig(..)+     , defaultServerConfig+     , serve+     -- * Routing an Incoming Request+     , method+     , Method(..)+     , MatchMethod(..)+     , dir+     , path+     , FromReqURI(..)+     , nullDir+     , guardRq+     -- * Creating a Response+     , ToMessage(..)+     , toResponseBS+     -- * Setting the Response Code+     , ok+     , internalServerError+     , unauthorized+     , notFound+     , seeOther+     , setResponseCode+     -- * Looking up Request Parameters+     , lookBS+     , lookBSs+     , lookText+     , lookTexts+     , lookFile+     , ContentType(..)+     -- * Cookies+     , Cookie(..)+     , CookieLife(..)+     , mkCookie  +     , addCookies+     , expireCookie+     , lookCookieValue+     -- * HTTP Headers+     , addHeaderM+     , setHeaderM+     , getHeaderM+     -- * File Serving+     , Browsing(..)+     , serveDirectory+     , serveFile+     , asContentType+     -- * Other+     , MonadPlus(..)+     ) where++import Control.Monad (MonadPlus(..))+import qualified Data.ByteString as B+import Data.ByteString.Lazy.Char8 (ByteString)+import Data.Int (Int64)+import Data.Maybe (fromMaybe)+import Data.Text.Lazy (Text)+import Happstack.Server (ContentType, Request, Response, ServerPart, FromReqURI, Method(..), MatchMethod, ToMessage(..), Cookie(..), CookieLife(..), Browsing, mkCookie)+import Happstack.Server.SURI (ToSURI)+import qualified Happstack.Server as S++-- * Starting the server++-- | configuration to be used with 'serve' function+data ServerConfig = +    ServerConfig { port      :: Int       -- ^ port to listen on+                 , ramQuota  :: Int64     -- ^ maximum amount of POST data (in bytes)+                 , diskQuota :: Int64     -- ^ maximum file upload size (in bytes)+                 , tmpDir    :: FilePath  -- ^ temporary directory for file uploads+                 }++-- | a reasonable default 'ServerConfig'+-- +-- > ServerConfig { port      = 8000+-- >              , ramQuota  = 1 * 10^6+-- >              , diskQuota = 20 * 10^6+-- >              , tmpDir    = "/tmp/"+-- >              }+defaultServerConfig :: ServerConfig+defaultServerConfig =+    ServerConfig { port      = 8000+                 , ramQuota  = 1 * 10^6+                 , diskQuota = 20 * 10^6+                 , tmpDir    = "/tmp/"+                 }++-- | start the server and handle requests using the supplied 'ServerPart'+serve :: Maybe ServerConfig  -- ^ if Nothing, then use 'defaultServerConfig'+      -> ServerPart Response -- ^ request handler+      -> IO ()+serve mServerConf part = +    let ServerConfig{..} = fromMaybe defaultServerConfig mServerConf+    in S.simpleHTTP (S.nullConf { S.port = port }) $+         do S.decodeBody (S.defaultBodyPolicy tmpDir diskQuota ramQuota (ramQuota `div` 10))+            part++-- * Routing on a URI path segment++-- | Pop a path element and run the supplied handler if it matches the+-- given string.+-- +-- > handler :: ServerPart Response+-- > handler = dir "foo" $ dir "bar" $ subHandler+-- +-- The path element can not contain \'/\'. See also 'dirs'.+dir :: String -> ServerPart a -> ServerPart a+dir = S.dir++-- | Pop a path element and parse it using the 'fromReqURI' in the+-- 'FromReqURI' class.+path :: (FromReqURI a) => (a -> ServerPart b) -> ServerPart b+path = S.path++-- | guard which only succeeds if there are no remaining path segments+--+-- Often used if you want to explicitly assign a route for '/'+-- +nullDir :: ServerPart ()+nullDir = S.nullDir++-- | Guard using an arbitrary function on the 'Request'.+guardRq :: (Request -> Bool) -> ServerPart ()+guardRq = S.guardRq++-- * Routing on the HTTP Request method++-- | Guard against the request method+--+-- Example:+--+-- > handler :: ServerPart Response+-- > handler =+-- >     do method [GET, HEAD]+-- >        ...+method :: (MatchMethod method) => method -> ServerPart ()+method = S.method++-- * Creating a Response++toResponse :: (ToMessage a) => a -> Response+toResponse = S.toResponse++-- | A low-level function to build a 'Response' from a content-type+-- and a 'ByteString'.+--+-- Creates a 'Response' in a manner similar to the 'ToMessage' class,+-- but without requiring an instance declaration.+-- +-- example:+-- +-- > import Data.ByteString.Char8 as C+-- > import Data.ByteString.Lazy.Char8 as L+-- > import Happstack.Lite+-- >+-- > main = serve Nothing $ ok $ toResponseBS (C.pack "text/plain") (L.pack "hello, world")+--+-- (note: 'C.pack' and 'L.pack' only work for ascii. For unicode strings you would need to use @utf8-string@, @text@, or something similar to create a valid 'ByteString').+toResponseBS :: B.ByteString -- ^ content-type+             -> ByteString -- ^ response body+             -> Response+toResponseBS = S.toResponseBS++-- * Response code++-- | Respond with @200 OK@.+-- +-- > main = serve Nothing $ ok "Everything is OK"+ok :: a -> ServerPart a+ok = S.ok++-- | Respond with @204 No Content@+--+-- A @204 No Content@ response may not contain a message-body. If you try to supply one, it will be dutifully ignored.+--+-- > main = serve Nothing $ noContent "This will be ignored."+noContent :: a -> ServerPart a+noContent = S.noContent++-- | Respond with @500 Internal Server Error@.+--+-- > main = serve Nothing $ internalServerError "Sorry, there was an internal server error."+internalServerError :: a -> ServerPart a+internalServerError = S.internalServerError++-- | Responds with @502 Bad Gateway@.+--+-- > main = serve Nothing $ badGateway "Bad Gateway."+badGateway :: a -> ServerPart a+badGateway = S.badGateway++-- | Respond with @400 Bad Request@.+--+-- > main = serve Nothing $ badRequest "Bad Request."+badRequest :: a -> ServerPart a+badRequest = S.badRequest++-- | Respond with @401 Unauthorized@.+--+-- > main = serve Nothing $ unauthorized "You are not authorized."+unauthorized :: a -> ServerPart a+unauthorized = S.unauthorized++-- | Respond with @403 Forbidden@.+--+-- > main = serve Nothing $ forbidden "Sorry, it is forbidden."+forbidden :: a -> ServerPart a+forbidden = S.forbidden++-- | Respond with @404 Not Found@.+-- +-- > main = serve Nothing $ notFound "What you are looking for has not been found."+notFound :: a -> ServerPart a+notFound = S.notFound++-- | Set an arbitrary return code in your response.+--+-- A filter for setting the response code. Generally you will use a+-- helper function like 'ok' or 'seeOther'.+-- +-- > main = serve Nothing $ do setResponseCode 200+-- >                           return "Everything is OK"+-- +setResponseCode :: Int -- ^ response code+                -> ServerPart ()+setResponseCode = S.setResponseCode++-- | Respond with @413 Request Entity Too Large@.+--+-- > main = serve Nothing $ requestEntityTooLarge "That's too big for me to handle."+requestEntityTooLarge :: a -> ServerPart a+requestEntityTooLarge = S.requestEntityTooLarge++-- | Respond with @303 See Other@.+--+-- > main = serve Nothing $ seeOther "http://example.org/" "What you are looking for is now at http://example.org/"+--+-- NOTE: The second argument of 'seeOther' is the message body which will sent to the browser. According to the HTTP 1.1 spec,+--+-- @the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s).@+--+-- This is because pre-HTTP\/1.1 user agents do not support 303. However, in practice you can probably just use @\"\"@ as the second argument.+seeOther :: (ToSURI uri) => uri -> a -> ServerPart a+seeOther = S.seeOther++-- | Respond with @302 Found@.+-- +-- You probably want 'seeOther'. This method is not in popular use anymore, and is generally treated like 303 by most user-agents anyway.+found :: (ToSURI uri) => uri -> a -> ServerPart a+found = S.found++-- | Respond with @301 Moved Permanently@.+--+-- > main = serve Nothing $ movedPermanently "http://example.org/" "What you are looking for is now at http://example.org/"+movedPermanently :: (ToSURI uri) => uri -> a -> ServerPart a+movedPermanently = S.movedPermanently++-- | Respond with @307 Temporary Redirect@.+--+-- > main = serve Nothing $ tempRedirect "http://example.org/" "What you are looking for is temporarily at http://example.org/"+tempRedirect :: (ToSURI uri) => uri -> a -> ServerPart a+tempRedirect = S.tempRedirect++-- * Request Parameters++-- | Gets the first matching named input parameter as a lazy 'ByteString'+--+-- Searches the QUERY_STRING followed by the Request body.+--+-- see also: 'lookBSs'+lookBS :: String -> ServerPart ByteString+lookBS = S.lookBS++-- | Gets all matches for the named input parameter as lazy 'ByteString's+--+-- Searches the QUERY_STRING followed by the Request body.+--+-- see also: 'lookBS'+lookBSs :: String -> ServerPart [ByteString]+lookBSs = S.lookBSs++-- | Gets the first matching named input parameter as a lazy 'Text'+--+-- Searches the QUERY_STRING followed by the Request body.+--+-- This function assumes the underlying octets are UTF-8 encoded.+--+-- see also: 'lookTexts'+lookText :: String -> ServerPart Text+lookText = S.lookText++-- | Gets all matches for the named input parameter as lazy 'Text's+--+-- Searches the QUERY_STRING followed by the Request body.+--+-- This function assumes the underlying octets are UTF-8 encoded.+--+-- see also: 'lookText'+lookTexts :: String -> ServerPart [Text]+lookTexts = S.lookTexts++-- | Gets the first matching named file+--+-- Files can only appear in the request body. Additionally, the form+-- must set enctype=\"multipart\/form-data\".+--+-- This function returns a tuple consisting of:+-- +--  (1) The temporary location of the uploaded file+--+--  (2) The local filename supplied by the browser+--+--  (3) The content-type supplied by the browser+--+-- NOTE: You must move the file from the temporary location before the+-- 'Response' is sent. The temporary files are automatically removed+-- after the 'Response' is sent.+lookFile :: String -- ^ name of input field to search for+         -> ServerPart (FilePath, FilePath, ContentType) -- ^ (temporary file location, uploaded file name, content-type)+lookFile = S.lookFile++-- * Cookies++-- | gets the named cookie as a string+lookCookieValue :: String -> ServerPart String+lookCookieValue = S.lookCookieValue++-- | Add the list 'Cookie' to the 'Response'.+-- +addCookies :: [(CookieLife, Cookie)] -> ServerPart ()+addCookies = S.addCookies++-- | Expire the named cookie immediately and set the cookie value to @\"\"@+--+-- > main = serve Nothing $+-- >   do expireCookie "name"+-- >      ok $ "The cookie has been expired."++expireCookie :: String -> ServerPart () +expireCookie = S.expireCookie++-- * Headers++-- | Get a header out of the request.+getHeaderM :: String -> ServerPart (Maybe B.ByteString)+getHeaderM = S.getHeaderM++-- | Add headers into the response.  This method does not overwrite+-- any existing header of the same name, hence the name 'addHeaderM'.+-- If you want to replace a header use 'setHeaderM'.+addHeaderM :: String -> String -> ServerPart ()+addHeaderM = S.addHeaderM++-- | Set a header into the response.  This will replace an existing+-- header of the same name.  Use 'addHeaderM' if you want to add more+-- than one header of the same name.+setHeaderM :: String -> String -> ServerPart ()+setHeaderM = S.setHeaderM++-- * File Serving++-- | Serve files and directories from a directory and its subdirectories using 'sendFile'.+-- +-- Usage:+--+-- > serveDirectory EnableBrowsing ["index.html"] "path/to/files/on/disk"+--+-- If the requested path does not match a file or directory on the+-- disk, then 'serveDirectory' calls 'mzero'.+--+-- If the requested path is a file then the file is served normally. +--+-- If the requested path is a directory, then the result depends on+-- what the first two arguments to the function are.+--+-- The first argument controls whether directory browsing is+-- enabled.+--+-- The second argument is a list of index files (such as+-- index.html).+--+-- When a directory is requested, 'serveDirectory' will first try to+-- find one of the index files (in the order they are listed). If that+-- fails, it will show a directory listing if 'EnableBrowsing' is set,+-- otherwise it will return @forbidden \"Directory index forbidden\"@.+-- +-- Here is an explicit list of all the possible outcomes when the+-- argument is a (valid) directory:+--+-- [@'DisableBrowsing', empty index file list@]+--+--  This will always return, forbidden \"Directory index forbidden\"+--+-- [@'DisableBrowsing', non-empty index file list@]+--+-- 1. If an index file is found it will be shown.+--+-- 2. Otherwise returns, forbidden \"Directory index forbidden\"+--+-- [@'EnableBrowsing', empty index file list@] +--+-- Always shows a directory index.+--+-- [@'EnableBrowsing', non-empty index file list@]+--+-- 1. If an index file is found it will be shown+--+-- 2. Otherwise shows a directory index+--+-- see also: 'defaultIxFiles', 'serveFile'+serveDirectory :: Browsing    -- ^ allow directory browsing+               -> [FilePath]  -- ^ index file names, in case the requested path is a directory+               -> FilePath    -- ^ file/directory to serve+               -> ServerPart Response+serveDirectory = S.serveDirectory++-- | Serve a single, specified file. The name of the file being served is specified explicity. It is not derived automatically from the 'Request' url.+-- +-- example 1:+-- +--  Serve as a specific content-type:+--+-- > serveFile (asContentType "image/jpeg") "/srv/data/image.jpg"+--+--+-- example 2:+-- +--  Serve guessing the content-type from the extension:+-- +-- > serveFile (guessContentTypeM mimeTypes) "/srv/data/image.jpg"+--+-- If the specified path does not exist or is not a file, this function will return 'mzero'.+-- +-- WARNING: No security checks are performed.+--+-- NOTE: alias for 'serveFileUsing' 'filePathSendFile'+serveFile :: (FilePath -> ServerPart String)   -- ^ function for determining content-type of file. Typically 'asContentType'+          -> FilePath                 -- ^ path to the file to serve+          -> ServerPart Response+serveFile = S.serveFile+++-- | returns a specific content type, completely ignoring the 'FilePath' argument. +--+-- Use this with 'serveFile' if you want to explicitly specify the+-- content-type.+--+-- see also: 'serveFile'+asContentType :: String  -- ^ the content-type to return+              -> (FilePath -> IO String)+asContentType = S.asContentType
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Jeremy Shaw 2011++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Jeremy Shaw nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+import Distribution.Simple+main = defaultMain
+ happstack-lite.cabal view
@@ -0,0 +1,22 @@+Name:                happstack-lite+Version:             6.0.0+Synopsis:            Happstack minus the useless stuff+Description:         This packages provides a subset of Happstack that is easier to learn but still very useful. It as 100% compatible with the full version of Happstack.+Homepage:            http://www.happstack.com/+License:             BSD3+License-file:        LICENSE+Author:              Jeremy Shaw+Maintainer:          jeremy@n-heptane.com+Stability:           Provisional+Category:            Web+Build-type:          Simple+Cabal-version:       >=1.6++Library+  Exposed-modules:+                 Happstack.Lite+  +  Build-depends: base < 5,+                 bytestring == 0.9.*,+                 happstack-server == 6.3.*,+                 text == 0.11.*