PageRenderTime 30ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Snap/Http/Server.hs

http://github.com/snapframework/snap-server
Haskell | 312 lines | 219 code | 38 blank | 55 comment | 5 complexity | 38b62fef4faeed3c126f0682f2dcd411 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. {-# LANGUAGE BangPatterns #-}
  2. {-# LANGUAGE CPP #-}
  3. {-# LANGUAGE OverloadedStrings #-}
  4. {-# LANGUAGE ScopedTypeVariables #-}
  5. ------------------------------------------------------------------------------
  6. -- | The Snap HTTP server is a high performance web server library written in
  7. -- Haskell. Together with the @snap-core@ library upon which it depends, it
  8. -- provides a clean and efficient Haskell programming interface to the HTTP
  9. -- protocol.
  10. --
  11. module Snap.Http.Server
  12. ( simpleHttpServe
  13. , httpServe
  14. , quickHttpServe
  15. , snapServerVersion
  16. , setUnicodeLocale
  17. , rawHttpServe
  18. , module Snap.Http.Server.Config
  19. ) where
  20. ------------------------------------------------------------------------------
  21. import Control.Applicative ((<$>), (<|>))
  22. import Control.Concurrent (killThread, newEmptyMVar, newMVar, putMVar, readMVar, withMVar)
  23. import Control.Concurrent.Extended (forkIOLabeledWithUnmaskBs)
  24. import Control.Exception (SomeException, bracket, catch, finally, mask, mask_)
  25. import qualified Control.Exception.Lifted as L
  26. import Control.Monad (liftM, when)
  27. import Control.Monad.Trans (MonadIO)
  28. import Data.ByteString.Char8 (ByteString)
  29. import qualified Data.ByteString.Char8 as S
  30. import qualified Data.ByteString.Lazy.Char8 as L
  31. import Data.Maybe (catMaybes, fromJust, fromMaybe)
  32. import qualified Data.Text as T
  33. import qualified Data.Text.Encoding as T
  34. import Data.Version (showVersion)
  35. import Data.Word (Word64)
  36. import Network.Socket (Socket, close)
  37. import Prelude (Bool (..), Eq (..), IO, Maybe (..), Monad (..), Show (..), String, const, flip, fst, id, mapM, mapM_, maybe, snd, unzip3, zip, ($), ($!), (++), (.))
  38. import System.IO (hFlush, hPutStrLn, stderr)
  39. #ifndef PORTABLE
  40. import System.Posix.Env
  41. #endif
  42. ------------------------------------------------------------------------------
  43. import Data.ByteString.Builder (Builder, toLazyByteString)
  44. ------------------------------------------------------------------------------
  45. import qualified Paths_snap_server as V
  46. import Snap.Core (MonadSnap (..), Request, Response, Snap, rqClientAddr, rqHeaders, rqMethod, rqURI, rqVersion, rspStatus)
  47. -- Don't use explicit imports for Snap.Http.Server.Config because we're
  48. -- re-exporting everything.
  49. import Snap.Http.Server.Config
  50. import qualified Snap.Http.Server.Types as Ty
  51. import Snap.Internal.Debug (debug)
  52. import Snap.Internal.Http.Server.Config (ProxyType (..), emptyStartupInfo, setStartupConfig, setStartupSockets)
  53. import Snap.Internal.Http.Server.Session (httpAcceptLoop, snapToServerHandler)
  54. import qualified Snap.Internal.Http.Server.Socket as Sock
  55. import qualified Snap.Internal.Http.Server.TLS as TLS
  56. import Snap.Internal.Http.Server.Types (AcceptFunc, ServerConfig, ServerHandler)
  57. import qualified Snap.Types.Headers as H
  58. import Snap.Util.GZip (withCompression)
  59. import Snap.Util.Proxy (behindProxy)
  60. import qualified Snap.Util.Proxy as Proxy
  61. import System.FastLogger (combinedLogEntry, logMsg, newLoggerWithCustomErrorFunction, stopLogger, timestampedLogEntry)
  62. ------------------------------------------------------------------------------
  63. -- | A short string describing the Snap server version
  64. snapServerVersion :: ByteString
  65. snapServerVersion = S.pack $! showVersion V.version
  66. ------------------------------------------------------------------------------
  67. rawHttpServe :: ServerHandler s -- ^ server handler
  68. -> ServerConfig s -- ^ server config
  69. -> [AcceptFunc] -- ^ listening server backends
  70. -> IO ()
  71. rawHttpServe h cfg loops = do
  72. mvars <- mapM (const newEmptyMVar) loops
  73. mask $ \restore -> bracket (mapM runLoop $ mvars `zip` loops)
  74. (\mvTids -> do
  75. mapM_ (killThread . snd) mvTids
  76. mapM_ (readMVar . fst) mvTids)
  77. (const $ restore $ mapM_ readMVar mvars)
  78. where
  79. -- parents and children have a mutual suicide pact
  80. runLoop (mvar, loop) = do
  81. tid <- forkIOLabeledWithUnmaskBs
  82. "snap-server http master thread" $
  83. \r -> (r $ httpAcceptLoop h cfg loop) `finally` putMVar mvar ()
  84. return (mvar, tid)
  85. ------------------------------------------------------------------------------
  86. -- | Starts serving HTTP requests using the given handler. This function never
  87. -- returns; to shut down the HTTP server, kill the controlling thread.
  88. --
  89. -- This function is like 'httpServe' except it doesn't setup compression,
  90. -- reverse proxy address translation (via 'Snap.Util.Proxy.behindProxy'), or
  91. -- the error handler; this allows it to be used from 'MonadSnap'.
  92. simpleHttpServe :: MonadSnap m => Config m a -> Snap () -> IO ()
  93. simpleHttpServe config handler = do
  94. conf <- completeConfig config
  95. let output = when (fromJust $ getVerbose conf) . hPutStrLn stderr
  96. (descrs, sockets, afuncs) <- unzip3 <$> listeners conf
  97. mapM_ (output . ("Listening on " ++) . S.unpack) descrs
  98. go conf sockets afuncs `finally` (mask_ $ do
  99. output "\nShutting down.."
  100. mapM_ (eatException . close) sockets)
  101. where
  102. eatException :: IO a -> IO ()
  103. eatException act =
  104. let r0 = return $! ()
  105. in (act >> r0) `catch` \(_::SomeException) -> r0
  106. --------------------------------------------------------------------------
  107. -- FIXME: this logging code *sucks*
  108. --------------------------------------------------------------------------
  109. debugE :: (MonadIO m) => ByteString -> m ()
  110. debugE s = debug $ "Error: " ++ S.unpack s
  111. --------------------------------------------------------------------------
  112. logE :: Maybe (ByteString -> IO ()) -> Builder -> IO ()
  113. logE elog b = let x = S.concat $ L.toChunks $ toLazyByteString b
  114. in (maybe debugE (\l s -> debugE s >> logE' l s) elog) x
  115. --------------------------------------------------------------------------
  116. logE' :: (ByteString -> IO ()) -> ByteString -> IO ()
  117. logE' logger s = (timestampedLogEntry s) >>= logger
  118. --------------------------------------------------------------------------
  119. logA :: Maybe (ByteString -> IO ())
  120. -> Request
  121. -> Response
  122. -> Word64
  123. -> IO ()
  124. logA alog = maybe (\_ _ _ -> return $! ()) logA' alog
  125. --------------------------------------------------------------------------
  126. logA' logger req rsp cl = do
  127. let hdrs = rqHeaders req
  128. let host = rqClientAddr req
  129. let user = Nothing -- TODO we don't do authentication yet
  130. let (v, v') = rqVersion req
  131. let ver = S.concat [ "HTTP/", bshow v, ".", bshow v' ]
  132. let method = bshow (rqMethod req)
  133. let reql = S.intercalate " " [ method, rqURI req, ver ]
  134. let status = rspStatus rsp
  135. let referer = H.lookup "referer" hdrs
  136. let userAgent = fromMaybe "-" $ H.lookup "user-agent" hdrs
  137. msg <- combinedLogEntry host user reql status cl referer userAgent
  138. logger msg
  139. --------------------------------------------------------------------------
  140. go conf sockets afuncs = do
  141. let tout = fromMaybe 60 $ getDefaultTimeout conf
  142. let shandler = snapToServerHandler handler
  143. setUnicodeLocale $ fromJust $ getLocale conf
  144. withLoggers (fromJust $ getAccessLog conf)
  145. (fromJust $ getErrorLog conf) $ \(alog, elog) -> do
  146. let scfg = Ty.setDefaultTimeout tout .
  147. Ty.setLocalHostname (fromJust $ getHostname conf) .
  148. Ty.setLogAccess (logA alog) .
  149. Ty.setLogError (logE elog) $
  150. Ty.emptyServerConfig
  151. maybe (return $! ())
  152. ($ mkStartupInfo sockets conf)
  153. (getStartupHook conf)
  154. rawHttpServe shandler scfg afuncs
  155. --------------------------------------------------------------------------
  156. mkStartupInfo sockets conf =
  157. setStartupSockets sockets $
  158. setStartupConfig conf emptyStartupInfo
  159. --------------------------------------------------------------------------
  160. maybeSpawnLogger f (ConfigFileLog fp) =
  161. liftM Just $ newLoggerWithCustomErrorFunction f fp
  162. maybeSpawnLogger _ _ = return Nothing
  163. --------------------------------------------------------------------------
  164. maybeIoLog (ConfigIoLog a) = Just a
  165. maybeIoLog _ = Nothing
  166. --------------------------------------------------------------------------
  167. withLoggers afp efp act =
  168. bracket (do mvar <- newMVar ()
  169. let f s = withMVar mvar
  170. (const $ S.hPutStr stderr s >> hFlush stderr)
  171. alog <- maybeSpawnLogger f afp
  172. elog <- maybeSpawnLogger f efp
  173. return (alog, elog))
  174. (\(alog, elog) -> do
  175. maybe (return ()) stopLogger alog
  176. maybe (return ()) stopLogger elog)
  177. (\(alog, elog) -> act ( liftM logMsg alog <|> maybeIoLog afp
  178. , liftM logMsg elog <|> maybeIoLog efp))
  179. {-# INLINE simpleHttpServe #-}
  180. ------------------------------------------------------------------------------
  181. listeners :: Config m a -> IO [(ByteString, Socket, AcceptFunc)]
  182. listeners conf = TLS.withTLS $ do
  183. let fs = catMaybes [httpListener, httpsListener, unixListener]
  184. mapM (\(str, mkAfunc) -> do (sock, afunc) <- mkAfunc
  185. return $! (str, sock, afunc)) fs
  186. where
  187. httpsListener = do
  188. b <- getSSLBind conf
  189. p <- getSSLPort conf
  190. cert <- getSSLCert conf
  191. chainCert <- getSSLChainCert conf
  192. key <- getSSLKey conf
  193. return (S.concat [ "https://"
  194. , b
  195. , ":"
  196. , bshow p ],
  197. do (sock, ctx) <- TLS.bindHttps b p cert chainCert key
  198. return (sock, TLS.httpsAcceptFunc sock ctx)
  199. )
  200. httpListener = do
  201. p <- getPort conf
  202. b <- getBind conf
  203. return (S.concat [ "http://"
  204. , b
  205. , ":"
  206. , bshow p ],
  207. do sock <- Sock.bindSocket b p
  208. if getProxyType conf == Just HaProxy
  209. then return (sock, Sock.haProxyAcceptFunc sock)
  210. else return (sock, Sock.httpAcceptFunc sock))
  211. unixListener = do
  212. path <- getUnixSocket conf
  213. let accessMode = getUnixSocketAccessMode conf
  214. return (T.encodeUtf8 . T.pack $ "unix:" ++ path,
  215. do sock <- Sock.bindUnixSocket accessMode path
  216. return (sock, Sock.httpAcceptFunc sock))
  217. ------------------------------------------------------------------------------
  218. -- | Starts serving HTTP requests using the given handler, with settings from
  219. -- the 'Config' passed in. This function never returns; to shut down the HTTP
  220. -- server, kill the controlling thread.
  221. httpServe :: Config Snap a -> Snap () -> IO ()
  222. httpServe config handler0 = do
  223. conf <- completeConfig config
  224. let !handler = chooseProxy conf
  225. let serve = compress conf . catch500 conf $ handler
  226. simpleHttpServe conf serve
  227. where
  228. chooseProxy conf = maybe handler0
  229. (\ptype -> pickProxy ptype handler0)
  230. (getProxyType conf)
  231. pickProxy NoProxy = id
  232. pickProxy HaProxy = id -- we handle this case elsewhere
  233. pickProxy X_Forwarded_For = behindProxy Proxy.X_Forwarded_For
  234. ------------------------------------------------------------------------------
  235. catch500 :: MonadSnap m => Config m a -> m () -> m ()
  236. catch500 conf = flip L.catch $ fromJust $ getErrorHandler conf
  237. ------------------------------------------------------------------------------
  238. compress :: MonadSnap m => Config m a -> m () -> m ()
  239. compress conf = if fromJust $ getCompression conf then withCompression else id
  240. ------------------------------------------------------------------------------
  241. -- | Starts serving HTTP using the given handler. The configuration is read
  242. -- from the options given on the command-line, as returned by
  243. -- 'commandLineConfig'. This function never returns; to shut down the HTTP
  244. -- server, kill the controlling thread.
  245. quickHttpServe :: Snap () -> IO ()
  246. quickHttpServe handler = do
  247. conf <- commandLineConfig defaultConfig
  248. httpServe conf handler
  249. ------------------------------------------------------------------------------
  250. -- | Given a string like \"en_US\", this sets the locale to \"en_US.UTF-8\".
  251. -- This doesn't work on Windows.
  252. setUnicodeLocale :: String -> IO ()
  253. #ifndef PORTABLE
  254. setUnicodeLocale lang = mapM_ (\k -> setEnv k (lang ++ ".UTF-8") True)
  255. [ "LANG"
  256. , "LC_CTYPE"
  257. , "LC_NUMERIC"
  258. , "LC_TIME"
  259. , "LC_COLLATE"
  260. , "LC_MONETARY"
  261. , "LC_MESSAGES"
  262. , "LC_PAPER"
  263. , "LC_NAME"
  264. , "LC_ADDRESS"
  265. , "LC_TELEPHONE"
  266. , "LC_MEASUREMENT"
  267. , "LC_IDENTIFICATION"
  268. , "LC_ALL" ]
  269. #else
  270. setUnicodeLocale = const $ return ()
  271. #endif
  272. ------------------------------------------------------------------------------
  273. bshow :: (Show a) => a -> ByteString
  274. bshow = S.pack . show