{-# LANGUAGE CPP #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE OverlappingInstances #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS -fno-warn-orphans #-} ----------------------------------------------------------------------------- -- | -- Module : Happstack.Server.SimpleHTTP -- Copyright : (c) Happstack.com 2010; (c) HAppS Inc 2007 -- License : BSD-like -- -- Maintainer : Happstack team <happs@googlegroups.com> -- Stability : provisional -- Portability : requires mtl -- -- 'simpleHTTP' is a self-contained HTTP server which can be used to -- run a 'ServerPart'. -- -- A very simple, \"Hello World!\" web app looks like: -- -- > import Happstack.Server -- > main = simpleHTTP nullConf $ ok "Hello World!" -- -- By default the server will listen on port 8000. Run the app and point your browser at: <http://localhost:8000/> -- -- For FastCGI support see: <http://hackage.haskell.org/package/happstack-fastcgi> ----------------------------------------------------------------------------- module Happstack.Server.SimpleHTTP ( -- * SimpleHTTP simpleHTTP , simpleHTTP' , simpleHTTP'' , simpleHTTPWithSocket , simpleHTTPWithSocket' , bindPort , bindIPv4 , parseConfig , waitForTermination -- * Re-exported modules -- ** Basic ServerMonad functionality , module Happstack.Server.Monads -- ** HTTP Realm Authentication , module Happstack.Server.Auth -- ** Create and Set Cookies (see also "Happstack.Server.RqData") , module Happstack.Server.Cookie -- ** Error Handling , module Happstack.Server.Error -- ** Creating Responses , module Happstack.Server.Response -- ** Request Routing , module Happstack.Server.Routing -- ** Proxying , module Happstack.Server.Proxy -- ** Looking up values in Query String, Request Body, and Cookies , module Happstack.Server.RqData -- ** Output Validation , module Happstack.Server.Validation , module Happstack.Server.Types -- , module Happstack.Server.Internal.Monads ) where -- re-exports import Happstack.Server.Auth import Happstack.Server.Monads import Happstack.Server.Cookie import Happstack.Server.Error import Happstack.Server.Types import Happstack.Server.Proxy import Happstack.Server.Routing import Happstack.Server.RqData import Happstack.Server.Response import Happstack.Server.Validation import Control.Monad import Data.Maybe (fromMaybe) import qualified Data.Version as DV import Happstack.Server.Internal.Monads (FilterFun, WebT(..), unFilterFun, runServerPartT, ununWebT) import qualified Happstack.Server.Internal.Listen as Listen (listen, listen',listenOn, listenOnIPv4) -- So that we can disambiguate 'Writer.listen' import Network (Socket) import qualified Paths_happstack_server as Cabal import System.Console.GetOpt ( OptDescr(Option) , ArgDescr(ReqArg) , ArgOrder(Permute) , getOpt ) #ifdef UNIX import Control.Concurrent.MVar import System.Posix.Signals hiding (Handler) import System.Posix.IO ( stdInput ) import System.Posix.Terminal ( queryTerminal ) #endif -- | An array of 'OptDescr', useful for processing command line -- options into an 'Conf' for 'simpleHTTP'. ho :: [OptDescr (Conf -> Conf)] ho = [Option [] ["http-port"] (ReqArg (\h c -> c { port = readDec' h }) "port") "port to bind http server"] -- | Parse command line options into a 'Conf'. parseConfig :: [String] -> Either [String] Conf parseConfig args = case getOpt Permute ho args of (flags,_,[]) -> Right $ foldr ($) nullConf flags (_,_,errs) -> Left errs -- |start the server, and handle requests using the supplied -- 'ServerPart'. -- -- This function will not return, though it may throw an exception. -- -- NOTE: The server will only listen on IPv4 due to portability issues -- in the "Network" module. For IPv6 support, use -- 'simpleHTTPWithSocket' with custom socket. simpleHTTP :: (ToMessage a) => Conf -> ServerPartT IO a -> IO () simpleHTTP = simpleHTTP' id -- | A combination of 'simpleHTTP''' and 'mapServerPartT'. See -- 'mapServerPartT' for a discussion of the first argument of this -- function. -- -- NOTE: This function always binds to IPv4 ports until Network -- module is fixed to support IPv6 in a portable way. Use -- 'simpleHTTPWithSocket' with custom socket if you want different -- behaviour. simpleHTTP' :: (ToMessage b, Monad m, Functor m) => (UnWebT m a -> UnWebT IO b) -> Conf -> ServerPartT m a -> IO () simpleHTTP' toIO conf hs = Listen.listen conf (\req -> runValidator (fromMaybe return (validator conf)) =<< (simpleHTTP'' (mapServerPartT toIO hs) req)) -- | Generate a result from a 'ServerPartT' and a 'Request'. This is -- mainly used by CGI (and fast-cgi) wrappers. simpleHTTP'' :: (ToMessage b, Monad m, Functor m) => ServerPartT m b -> Request -> m Response simpleHTTP'' hs req = (runWebT $ runServerPartT hs req) >>= (return . (maybe standardNotFound id)) where standardNotFound = setHeader "Content-Type" "text/html" $ (toResponse notFoundHtml){rsCode=404} -- | Run 'simpleHTTP' with a previously bound socket. Useful if you -- want to run happstack as user on port 80. Use something like this: -- -- > import System.Posix.User (setUserID, UserEntry(..), getUserEntryForName) -- > -- > main = do -- > let conf = nullConf { port = 80 } -- > socket <- bindPort conf -- > -- do other stuff as root here -- > getUserEntryForName "www" >>= setUserID . userID -- > -- finally start handling incoming requests -- > tid <- forkIO $ simpleHTTPWithSocket socket Nothing conf impl -- -- Note: It's important to use the same conf (or at least the same -- port) for 'bindPort' and 'simpleHTTPWithSocket'. -- -- see also: 'bindPort', 'bindIPv4' simpleHTTPWithSocket :: (ToMessage a) => Socket -> Conf -> ServerPartT IO a -> IO () simpleHTTPWithSocket = simpleHTTPWithSocket' id -- | Like 'simpleHTTP'' with a socket. simpleHTTPWithSocket' :: (ToMessage b, Monad m, Functor m) => (UnWebT m a -> UnWebT IO b) -> Socket -> Conf -> ServerPartT m a -> IO () simpleHTTPWithSocket' toIO socket conf hs = Listen.listen' socket conf (\req -> runValidator (fromMaybe return (validator conf)) =<< (simpleHTTP'' (mapServerPartT toIO hs) req)) -- | Bind port and return the socket for use with 'simpleHTTPWithSocket'. This -- function always binds to IPv4 ports until Network module is fixed -- to support IPv6 in a portable way. bindPort :: Conf -> IO Socket bindPort conf = Listen.listenOn (port conf) -- | Bind to ip and port and return the socket for use with 'simpleHTTPWithSocket'. -- -- > -- > import Happstack.Server -- > -- > main = do let conf = nullConf -- > addr = "127.0.0.1" -- > s <- bindIPv4 addr (port conf) -- > simpleHTTPWithSocket s conf $ ok $ toResponse $ -- > "now listening on ip addr " ++ addr ++ -- > " and port " ++ show (port conf) -- bindIPv4 :: String -- ^ IP address to bind to (must be an IP address and not a host name) -> Int -- ^ port number to bind to -> IO Socket bindIPv4 addr prt = Listen.listenOnIPv4 addr prt -- | Takes your 'WebT', if it is 'mempty' it returns 'Nothing' else it -- converts the value to a 'Response' and applies your filter to it. runWebT :: forall m b. (Functor m, ToMessage b) => WebT m b -> m (Maybe Response) runWebT = (fmap . fmap) appFilterToResp . ununWebT where appFilterToResp :: (Either Response b, FilterFun Response) -> Response appFilterToResp (e, ff) = unFilterFun ff $ either id toResponse e notFoundHtml :: String notFoundHtml = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" ++ "<html><head><title>Happstack " ++ ver ++ " File not found</title></head>" ++ "<body><h1>Happstack " ++ ver ++ "</h1>" ++ "<p>Your file is not found<br>" ++ "To try again is useless<br>" ++ "It is just not here</p>" ++ "</body></html>" where ver = DV.showVersion Cabal.version -- | Wait for a signal. -- On unix, a signal is sigINT or sigTERM (aka Control-C). -- -- On windows, the signal is entering: e <return> waitForTermination :: IO () waitForTermination = do #ifdef UNIX istty <- queryTerminal stdInput mv <- newEmptyMVar void $ installHandler softwareTermination (CatchOnce (putMVar mv ())) Nothing void $ installHandler lostConnection (CatchOnce (putMVar mv ())) Nothing case istty of True -> void $ installHandler keyboardSignal (CatchOnce (putMVar mv ())) Nothing False -> return () takeMVar mv #else let loop 'e' = return () loop _ = getChar >>= loop loop 'c' #endif