hoogle-4.2.2: src/Web/Server.hs
{-# LANGUAGE RecordWildCards, ScopedTypeVariables, PatternGuards #-}
module Web.Server(server) where
import General.Base
import General.Web
import CmdLine.All
import Web.Response
import Network.HTTP.Types
import Web.Page
import System.IO.Unsafe(unsafeInterleaveIO)
import Control.Monad.IO.Class
import General.System
import Control.Concurrent
import Control.Exception
import System.Time
import Data.Time.Clock
import Network.Wai
import Network.Wai.Handler.Warp
import qualified Data.ByteString.Lazy.Char8 as LBS
import qualified Data.ByteString.Char8 as BS
server :: CmdLine -> IO ()
server q@Server{..} = do
resp <- respArgs q
v <- newMVar ()
putStrLn $ "Starting Hoogle Server on port " ++ show port
runSettings defaultSettings{settingsOnException=exception, settingsPort=port} $ \r -> liftIO $ do
start <- getCurrentTime
res <- talk resp q r
responseEvaluate res
stop <- getCurrentTime
let t = floor $ diffUTCTime stop start * 1000
withMVar v $ const $ putStrLn $ bsUnpack (rawPathInfo r) ++ bsUnpack (rawQueryString r) ++ " ms:" ++ show t
talk resp q r
exception :: SomeException -> IO ()
exception e | Just (_ :: InvalidRequest) <- fromException e = return ()
| otherwise = putStrLn $ "Error: " ++ show e
respArgs :: CmdLine -> IO (IO ResponseArgs)
respArgs Server{..} = do
t <- getTemplate
if dynamic
then return $ args t
else do x <- args t; return $ return x
where
getTemplate
| null template = return $ return defaultTemplates
| otherwise = do
let get = do x <- fmap (loadTemplates . unlines) $ mapM readFile' template
putStrLn "Templates loaded"
return x
if dynamic then buffer template get else return get
modTime ext = unsafeInterleaveIO $ do
TOD a _ <- getModificationTime $ resources </> "hoogle" <.> ext
return $ show a
args t = do
css <- modTime "css"; js <- modTime "js"
t <- t
return $ responseArgs{updatedCss=css, updatedJs=js, templates=t}
-- | Given a set of paths something relies on, and a value to generate it, return something that generates it minimally
buffer :: [FilePath] -> IO a -> IO (IO a)
buffer files act = do
val <- act
ts <- mapM getModificationTime files
ref <- newMVar (ts,val)
return $ modifyMVar ref $ \(ts,val) -> do
ts2 <- mapM getModificationTime files
if ts == ts2 then return ((ts,val),val) else do
val <- act
return ((ts2,val),val)
-- FIXME: Avoid all the conversions to/from LBS
talk :: IO ResponseArgs -> CmdLine -> Request -> IO Response
talk resp Server{..} Request{rawPathInfo=path_, rawQueryString=query_}
| path `elem` ["/","/hoogle"] = do
let args = parseHttpQueryArgs $ drop 1 query
cmd <- cmdLineWeb args
resp <- resp
r <- response resp cmd{databases=databases}
if local_ then rewriteFileLinks r else return r
| takeDirectory path == "/res" = serveFile True $ resources </> takeFileName path
| local_ && "/file/" `isPrefixOf` path = serveFile False $ drop 6 path
| otherwise = return $ responseNotFound $ show path
where (path,query) = (bsUnpack path_, bsUnpack query_)
serveFile :: Bool -> FilePath -> IO Response
serveFile cache file = do
b <- doesFileExist file
return $ if not b
then responseNotFound file
else ResponseFile statusOK hdr file Nothing
where hdr = [headerContentType $ fromString $ contentExt $ takeExtension file] ++
[headerCacheControl $ fromString "max-age=604800" {- 1 week -} | cache]
rewriteFileLinks :: Response -> IO Response
rewriteFileLinks r = do
(a,b,c) <- responseFlatten r
let res = LBS.fromChunks $ f $ BS.concat $ LBS.toChunks c
return $ responseLBS a b res
where
f x | BS.null b = [a]
| otherwise = a : rep : f (BS.drop nfind b)
where (a,b) = BS.breakSubstring find x
find = fromString "href='file://"
rep = fromString "href='/file/"
nfind = BS.length find
contentExt ".png" = "image/png"
contentExt ".css" = "text/css"
contentExt ".js" = "text/javascript"
contentExt ".html" = "text/html"
contentExt ".htm" = "text/html"
contentExt _ = "text/plain"