packages feed

iterIO-0.2.2: Examples/httptest.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

import Prelude hiding (catch, head, id, div)
import Control.Concurrent
import Control.Exception
import Control.Monad
import Control.Monad.Trans
import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy as L
-- import qualified Data.ByteString.Lazy.Char8 as L8
import Data.Monoid
import qualified Network.Socket as Net
import qualified OpenSSL as SSL
import qualified OpenSSL.Session as SSL
import System.IO
import System.Posix.Files

import Data.IterIO
-- import Data.IterIO.Parse
import Data.IterIO.Http
import Data.IterIO.HttpRoute
import Data.IterIO.SSL
import System.Directory (getAppUserDataDirectory)
import System.IO.Unsafe (unsafePerformIO)

type L = L.ByteString

data HttpServer = HttpServer {
      hsListenSock :: !Net.Socket
    , hsSslCtx :: !(Maybe SSL.SSLContext)
    , hsLog :: !(Maybe Handle)
    }

myListen :: Net.PortNumber -> IO Net.Socket
myListen pn = do
  sock <- Net.socket Net.AF_INET Net.Stream Net.defaultProtocol
  Net.setSocketOption sock Net.ReuseAddr 1
  Net.bindSocket sock (Net.SockAddrInet pn Net.iNADDR_ANY)
  Net.listen sock Net.maxListenQueue
  return sock

mkServer :: Net.PortNumber -> Maybe SSL.SSLContext -> IO HttpServer
mkServer port mctx = do
  sock <- myListen port
  h <- openBinaryFile "http.log" WriteMode
  hSetBuffering h NoBuffering
  return $ HttpServer sock mctx (Just h)

mimeMap :: String -> S8.ByteString
mimeMap = unsafePerformIO $ do
            path <- findMimeTypes ["mime.types"
                                  , "/etc/mime.types"
                                  , "/var/www/conf/mime.types"]
            enumFile path |$ mimeTypesI "application/octet-stream"
    where
      findMimeTypes (h:t) = do exist <- fileExist h
                               if exist then return h else findMimeTypes t
      findMimeTypes []    = return "mime.types" -- cause error

routeFS :: (MonadIO m) => FilePath -> HttpRoute m s
routeFS = routeFileSys mimeMap (dirRedir "index.html")

cabal_dir :: String
cabal_dir = (unsafePerformIO $ getAppUserDataDirectory "cabal") ++ "/share/doc"

route :: (MonadIO m) => HttpRoute m ()
route = mconcat
        [ routeTop $ routeConst $ resp301 "/cabal"
        , routeMap [ ("cabal", routeConst $ resp301 cabal_dir)
                    , ("static", routeFS "static") -- directory ./static
                    , ("favicon.ico"
                      -- serve /favicon.ico from file ./static/favicon.ico,
                      -- but tell browser to cache it for 1 day
                      , addHeader ("Cache-Control", "max-age=86400") $
                                  routeFS "static/favicon.ico")
                    ]
        , routePath cabal_dir $ routeFS cabal_dir
        , routePath "/usr/share/doc/ghc/html" $
                    routeFS "/usr/share/doc/ghc/html"
        ]

accept_loop :: HttpServer -> IO ()
accept_loop srv = loop
    where
      loop = do
        (s, addr) <- Net.accept $ hsListenSock srv
        hPutStrLn stderr (show addr)
        _ <- forkIO $ server s
        loop
      server s = do
        (iter, enum) <- maybe (iterStream s) (\ctx -> iterSSL ctx s True)
                        (hsSslCtx srv)
        let loger = maybe inumNop inumhLog $ hsLog srv
        enum |. loger |$ inumHttpServer (ioHttpServer handler) .| loger .| iter
      handler = runHttpRoute route

main :: IO ()
main = Net.withSocketsDo $ SSL.withOpenSSL $ do
  mctx <- if secure
          then do
            exists <- fileExist privkey
            unless exists $ genSelfSigned privkey "localhost"
            ctx <- simpleContext privkey
            return $ Just ctx
          else return Nothing
  srv <- mkServer (if secure then 4433 else 8000) mctx
  sem <- newQSem 0
  _ <- forkIO $ accept_loop srv `finally` signalQSem sem
  waitQSem sem
    where
      privkey = "testkey.pem"
      secure = True