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