{- hums - The Haskell UPnP Server Copyright (C) 2009 Bardur Arantsson 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 . -} 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")