hpc-tracer-0.3.1: src/Network/TrivialWebServer.hs
-- (c) Andy Gill
module Network.TrivialWebServer where
import System.Posix
import System.Posix.Signals
import Network
import IO
import Monad
import Control.Concurrent
import Control.Exception as Exc
import Control.Concurrent.Chan
import qualified List
import qualified Char
-- Trivial web server, taking from the Cherry chess rendering software.
-- Takes callback for each request as an argument.
server :: Int -> Int -> Server -> IO ()
server threadCount portNo (Server { serve = serve }) = do
installHandler sigPIPE Ignore Nothing
chan <- newChan
sequence [ forkIO (worker chan) | i <- take threadCount [0..]]
sock <- listenOn (PortNumber $ fromIntegral portNo)
loopIO
(do (h,nm,port) <- accept sock
-- print (h,nm,port)
writeChan chan h) `finally` sClose sock
return ()
where
loopIO m = do m
loopIO m
worker chan = do
tid <- myThreadId
h <- readChan chan
t <- hGetBuffering h
-- print t
ln <- IO.hGetLine h
-- print $ ">> " ++ show tid ++ ":" ++ ln
case words ln of
["GET",url,"HTTP/1.1"]
-> do
-- print ("GET",url)
serve file args $ Response
{ reply = sendMsg h "200 OK"
, replyWithFailure = sendMsg h "400 Bad Request" False "text/html"
}
where (file,args) = splitup url
_ -> sendMsg h "400 Bad Request" False "text/html" $
"<html><body>Bad Request</body></html>\n"
worker chan
sendMsg h code cache thing reply
= (do -- print $ "<< " ++ reply
hPutStr h $ "HTTP/1.1 " ++ code ++ "\r\n"
hPutStr h $ "Connection: close\r\n"
hPutStr h $ "Content-Type: " ++ thing ++ "\r\n"
hPutStr h $ "Content-Length: " ++
show (length reply) ++ "\r\n"
hPutStr h $ "Cache-Control: " ++
(if cache
then "max-age=3600"
else "no-cache")
++ "\r\n"
hPutStr h $ "\r\n"
hPutStr h $ reply ++ "\r\n"
IO.hClose h
-- we choose to ignore exceptions inside here
) `Exc.catch` \ e -> do print "####################"
print e
return ()
splitup url = case span (/= '?') url of
(path,'?':args) -> (path,splitargs args)
(path,_) -> (path,[])
where
splitargs xs = case span (/= '=') xs of
(index,'=':rest) ->
case span (/= '&') rest of
(value,'&':rest') -> (index,clean value) : splitargs rest'
(value,_) -> (index,clean value) : []
_ -> []
clean ('%':d1:d2:cs)
= Char.chr (read $ "0x" ++ [d1,d2]) : clean cs
clean (c:cs) = c : clean cs
clean [] = []
-- These calls may be asyncroynously(sp) done.
-- The contract is that you must return quickly (aka loading
-- a web page). If you have an expensive computation, you should
-- return, and call the reply continuation later when you are done.
-- This allows this thread to continue to service requests.
data Server = Server
{ serve :: String -- ^the URI
-> [(String,String)] -- ^the arguments after questionmark
-> Response -- ^the way to reply with a message
-> IO ()
}
data Response = Response
{ reply :: Bool -- ^ True => cache the result in the browser
-> String -- ^ the content-type
-> String -- ^ the body
-> IO ()
, replyWithFailure
:: String -- ^ short html message
-> IO ()
}