packages feed

hoogle-4.1.3: src/Web/Server.hs

{-# LANGUAGE RecordWildCards #-}

module Web.Server(server) where

import General.Base
import General.Web
import CmdLine.All
import Web.Response
import Control.Concurrent
import Control.Exception
import Network
import Network.HTTP
import Network.URI
import Network.Socket
import Data.Time
import General.System


server :: CmdLine -> IO ()
server q@Server{..} = withSocketsDo $ do
    stop <- httpServer port (talk q)
    flip finally stop $ do
        putStrLn $ "Started Hoogle Server on port " ++ show port
        -- must run under nohup, and with input from /dev/null
        shut <- if nostdin then return True else hIsClosed stdin
        eof <- if shut then return True else isEOF
        when eof $ forever $ threadDelay maxBound


-- | Given a port and a handler, return an action to shutdown the server
httpServer :: Int -> (Request String -> IO (Response String)) -> IO (IO ())
httpServer port handler = do
    s <- listenOn $ PortNumber $ fromIntegral port
    forkIO $ forever $ do
        (sock,host) <- Network.Socket.accept s
        bracket (socketConnection "" sock) close $ \strm -> do
            start <- getCurrentTime
            res <- receiveHTTP strm
            case res of
                Left x -> do
                    putStrLn $ "Bad request: " ++ show x
                    respondHTTP strm $ responseBadRequest $ show x
                Right x -> do
                    let msg = unescapeURL $ show $ rqURI x
                    res <- try $ handler x
                    case res of
                        Left e -> do
                            let s = show (e :: SomeException)
                            putStrLn $ "Crash when serving " ++ msg ++ ", " ++ s
                            respondHTTP strm $ responseError s
                        Right r -> do
                            respondHTTP strm r
                            end <- getCurrentTime
                            let t = floor $ diffUTCTime end start * 1000
                            putStrLn $ "Served in " ++ show t ++ "ms: " ++ msg
    return $ sClose s


-- FIXME: This should be in terms of Lazy ByteString's, for higher performance
--        serving of local files
talk :: CmdLine -> Request String -> IO (Response String)
talk Server{..} Request{rqURI=URI{uriPath=path,uriQuery=query}}
    | path `elem` ["/","/hoogle"] = do
        args <- cmdLineWeb $ parseHttpQueryArgs $ drop 1 query
        r <- response "/res" args{databases=databases}
        return $ if local_ then fmap rewriteFileLinks r else r
    | takeDirectory path == "/res" = serveFile True $ resources </> takeFileName path
    | local_ && "/file/" `isPrefixOf` path = serveFile False $ drop 6 path
    | otherwise = return $ responseNotFound $ show path


serveFile :: Bool -> FilePath -> IO (Response String)
serveFile cache file = do
    b <- doesFileExist file
    if not b
        then return $ responseNotFound file
        else do
            h <- openBinaryFile file ReadMode
            src <- hGetContents h
            return $ responseOk
                ([Header HdrContentType $ contentExt $ takeExtension file] ++
                 [Header HdrCacheControl "max-age=604800" {- 1 week -} | cache]) src


rewriteFileLinks :: String -> String
rewriteFileLinks x
    | "href='file://" `isPrefixOf` x = "href='/file/" ++ rewriteFileLinks (drop (13+1) x)
    | null x = x
    | otherwise = head x : rewriteFileLinks (tail x)


contentExt ".png" = "image/png"
contentExt ".css" = "text/css"
contentExt ".js" = "text/javascript"
contentExt _ = "text/plain"