hums-0.2.2: src/HttpServer.hs
{-
hums - The Haskell UPnP Server
Copyright (C) 2009 Bardur Arantsson <bardur@scientician.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-}
module HttpServer ( runHttpServer
, UrlHandler
, sendHeaders
, sendErrorResponse
, sendOkHeaders
, sendBody
, sendXmlResponse
, sendPartialContentHeaders
, HttpError(..)
) where
import Network.HTTP.Base
import Network.HTTP.Headers
import Network.HTTP.Stream
import Network.Socket
import Network.StreamSocket()
import System.IO
import Control.Monad
import Control.Concurrent
import Control.Exception
import Data.Word
import Text.Regex
import Text.Printf
import Network.URI
import RegexExtra
-- Request handlers.
type RequestHandler =
Socket -> Request String -> IO ()
-- URL handlers.
type UrlHandler =
Socket -> Request String -> [String] -> IO ()
runHttpServer' :: RequestHandler -> Word16 -> IO ()
runHttpServer' r p = do
let p' = (fromIntegral p) :: PortNumber
let sa = SockAddrInet p' iNADDR_ANY
s <- socket AF_INET Stream 0
setSocketOption s ReuseAddr 1
bindSocket s sa
listen s 5
forever $ acceptConnection s r
acceptConnection :: Socket -> RequestHandler -> IO ()
acceptConnection listenSocket r = do
(s,_) <- accept listenSocket
forkIO $ bracket
(return s)
sClose
(handleHttpConnection r)
return () -- Don't care about the thread ID.
handleHttpConnection :: RequestHandler -> Socket -> IO ()
handleHttpConnection r c = do
result <- receiveHTTP c
case result of
Left _ -> error "Error reading request"
Right req -> do
-- Got the request, now let's invoke the request handler.
r c req
sendHeaders :: Socket -> ResponseCode -> String -> [Header] -> IO ()
sendHeaders conn rCode rReason rHeaders = do
let rsp = Response { rspCode = rCode
, rspReason = rReason
, rspHeaders = rHeaders
, rspBody = "" }
writeBlock conn $ show rsp -- Writes headers only + CRLF.
return ()
-- Write data to body of request. May be called multiple times after
-- sendHeaders has been called.
sendBody :: Stream s => s -> String -> IO ()
sendBody conn body = do
writeBlock conn body
return ()
myRequestHandler :: [(Regex, UrlHandler)] -> Socket -> Request String -> IO ()
myRequestHandler hs s r = do
case dispatch hs (urlDecode $ uriPath $ rqURI r) of
Nothing -> sendErrorResponse s InternalServerError []
Just (h,gs) -> h s r gs
runHttpServer :: [(Regex, UrlHandler)] -> Word16 -> IO ()
runHttpServer hs p = do
runHttpServer' (myRequestHandler hs) p
{-
Response generator functions.
-}
sendOkHeaders :: Socket -> [Header] -> Int -> IO ()
sendOkHeaders conn hs contentLength =
let h1 = Header HdrContentLength $ printf "%d" contentLength in
let h2 = Header HdrConnection "close" in
sendHeaders conn (2,0,0) "OK" (h1 : h2 : hs)
sendPartialContentHeaders :: Socket -> [Header] -> (Integer,Integer) -> Int -> IO ()
sendPartialContentHeaders conn hs (rLow,rHigh) entitySize =
let h1 = Header HdrContentLength $ printf "%d" (rHigh-rLow+1) in
let h2 = Header HdrContentRange $ printf "%d-%d/%d" rLow rHigh entitySize in
let h3 = Header HdrConnection "close" in
sendHeaders conn (2,0,6) "Partial Content" (h1 : h2 : h3 : hs)
sendXmlResponse :: Socket -> [Header] -> String -> IO ()
sendXmlResponse conn hs xml = do
sendOkHeaders conn ( (Header HdrContentType "text/xml") : hs ) $ length xml
sendBody conn xml
data HttpError = NotFound
| InternalServerError
| NotImplemented
sendErrorResponse :: Socket -> HttpError -> [Header] -> IO ()
sendErrorResponse conn e hs = do
sendHeaders conn c r
([ Header HdrContentLength "0"
, Header HdrConnection "close"
] ++ hs)
where
(c,r) = case e of
NotFound -> ((4,0,4),"NOT FOUND")
InternalServerError -> ((5,0,4), "INTERNAL SERVER ERROR")
NotImplemented -> ((5,0,1), "NOT IMPLEMENTED")