4 , GeneralizedNewtypeDeriving
9 module Network.HTTP.Lucu.Resource.Internal
18 , getRemoteCertificate
34 import Blaze.ByteString.Builder (Builder)
35 import Control.Applicative
36 import Control.Concurrent
37 import Control.Concurrent.STM
38 import Control.Exception
39 import Control.Monad.IO.Class
40 import Control.Monad.Reader
41 import Control.Monad.Unicode
42 import Data.Ascii (Ascii, CIAscii)
43 import qualified Data.Ascii as A
44 import qualified Data.ByteString as Strict
48 import Data.Monoid.Unicode
49 import qualified Data.Text as T
50 import Network.HTTP.Lucu.Abortion
51 import Network.HTTP.Lucu.Abortion.Internal
52 import Network.HTTP.Lucu.Config
53 import Network.HTTP.Lucu.DefaultPage
54 import qualified Network.HTTP.Lucu.Headers as H
55 import Network.HTTP.Lucu.Interaction
56 import Network.HTTP.Lucu.Postprocess
57 import Network.HTTP.Lucu.Request
58 import Network.HTTP.Lucu.Response
63 import Prelude hiding (catch)
64 import Prelude.Unicode
67 -- |The 'Resource' monad. This monad implements 'MonadIO' so it can do
71 unResource ∷ ReaderT NormalInteraction IO a
73 deriving (Applicative, Functor, Monad, MonadIO)
75 runResource ∷ Resource a → NormalInteraction → IO a
76 runResource = runReaderT ∘ unResource
78 -- |'ResourceDef' is basically a set of 'Resource' monads for each
80 data ResourceDef = ResourceDef {
81 -- |Whether to run a 'Resource' on a native thread (spawned by
82 -- 'forkOS') or to run it on a user thread (spanwed by
83 -- 'forkIO'). Generally you don't need to set this field to
85 resUsesNativeThread ∷ !Bool
86 -- | Whether to be greedy or not.
88 -- Say a client is trying to access \/aaa\/bbb\/ccc. If there is a
89 -- greedy resource at \/aaa\/bbb, it is always chosen even if
90 -- there is another resource at \/aaa\/bbb\/ccc. If the resource
91 -- at \/aaa\/bbb is not greedy, it is just ignored. Greedy
92 -- resources are like CGI scripts.
94 -- |A 'Resource' to be run when a GET request comes for the
95 -- resource path. If 'resGet' is Nothing, the system responds
96 -- \"405 Method Not Allowed\" for GET requests.
98 -- It also runs for HEAD request if the 'resHead' is 'Nothing'. In
99 -- that case 'putChunk' and such don't actually write a response
101 , resGet ∷ !(Maybe (Resource ()))
102 -- |A 'Resource' to be run when a HEAD request comes for the
103 -- resource path. If 'resHead' is Nothing, the system runs
104 -- 'resGet' instead. If 'resGet' is also Nothing, the system
105 -- responds \"405 Method Not Allowed\" for HEAD requests.
106 , resHead ∷ !(Maybe (Resource ()))
107 -- |A 'Resource' to be run when a POST request comes for the
108 -- resource path. If 'resPost' is Nothing, the system responds
109 -- \"405 Method Not Allowed\" for POST requests.
110 , resPost ∷ !(Maybe (Resource ()))
111 -- |A 'Resource' to be run when a PUT request comes for the
112 -- resource path. If 'resPut' is Nothing, the system responds
113 -- \"405 Method Not Allowed\" for PUT requests.
114 , resPut ∷ !(Maybe (Resource ()))
115 -- |A 'Resource' to be run when a DELETE request comes for the
116 -- resource path. If 'resDelete' is Nothing, the system responds
117 -- \"405 Method Not Allowed\" for DELETE requests.
118 , resDelete ∷ !(Maybe (Resource ()))
121 -- |'emptyResource' is a resource definition with no actual
122 -- handlers. You can construct a 'ResourceDef' by selectively
123 -- overriding 'emptyResource'. It is defined as follows:
126 -- emptyResource = ResourceDef {
127 -- resUsesNativeThread = False
128 -- , resIsGreedy = False
129 -- , resGet = Nothing
130 -- , resHead = Nothing
131 -- , resPost = Nothing
132 -- , resPut = Nothing
133 -- , resDelete = Nothing
136 emptyResource ∷ ResourceDef
137 emptyResource = ResourceDef {
138 resUsesNativeThread = False
139 , resIsGreedy = False
144 , resDelete = Nothing
147 spawnResource ∷ ResourceDef → NormalInteraction → IO ThreadId
148 spawnResource (ResourceDef {..}) ni@(NI {..})
149 = fork $ run `catch` processException
151 fork ∷ IO () → IO ThreadId
152 fork | resUsesNativeThread = forkOS
156 run = flip runResource ni $
158 fromMaybe notAllowed $ rsrc req
161 rsrc ∷ Request → Maybe (Resource ())
163 = case reqMethod req of
165 HEAD → case resHead of
171 _ → error $ "Unknown request method: " ⧺ show (reqMethod req)
173 notAllowed ∷ Resource ()
174 notAllowed = do setStatus MethodNotAllowed
178 $ intersperse (A.toAsciiBuilder ", ")
179 $ map A.toAsciiBuilder allowedMethods
181 allowedMethods ∷ [Ascii]
182 allowedMethods = nub $ concat [ methods resGet ["GET"]
183 , methods resHead ["GET", "HEAD"]
184 , methods resPost ["POST"]
185 , methods resPut ["PUT"]
186 , methods resDelete ["DELETE"]
189 methods ∷ Maybe a → [Ascii] → [Ascii]
194 toAbortion ∷ SomeException → Abortion
196 = case fromException e of
197 Just abortion → abortion
198 Nothing → mkAbortion' InternalServerError $ T.pack $ show e
200 processException ∷ SomeException → IO ()
202 = do let abo = toAbortion exc
203 state ← atomically $ readTVar niState
204 res ← atomically $ readTVar niResponse
205 if state ≤ DecidingHeader then
206 -- We still have a chance to reflect this abortion
207 -- in the response. Hooray!
208 flip runResource ni $
209 do setStatus $ aboStatus abo
210 mapM_ (uncurry setHeader) $ H.fromHeaders $ aboHeaders abo
211 setHeader "Content-Type" defaultPageContentType
212 deleteHeader "Content-Encoding"
213 putBuilder $ abortPage niConfig (Just niRequest) res abo
215 when (cnfDumpTooLateAbortionToStderr niConfig)
217 runResource (driftTo Done) ni
219 dumpAbortion ∷ Abortion → IO ()
222 $ concat [ "Lucu: an exception occured after "
223 , "sending the response header to the client:\n"
224 , " ", show abo, "\n"
227 getInteraction ∷ Resource NormalInteraction
228 getInteraction = Resource ask
230 -- |Get the 'Config' value for this httpd.
231 getConfig ∷ Resource Config
232 getConfig = niConfig <$> getInteraction
234 -- |Get the 'SockAddr' of the remote host.
235 getRemoteAddr ∷ Resource SockAddr
236 getRemoteAddr = niRemoteAddr <$> getInteraction
238 #if defined(HAVE_SSL)
239 -- | Return the X.509 certificate of the client, or 'Nothing' if:
241 -- * This request didn't came through an SSL stream.
243 -- * The client didn't send us its certificate.
245 -- * The 'OpenSSL.Session.VerificationMode' of
246 -- 'OpenSSL.Session.SSLContext' in 'SSLConfig' has not been set to
247 -- 'OpenSSL.Session.VerifyPeer'.
248 getRemoteCertificate ∷ Resource (Maybe X509)
249 getRemoteCertificate = niRemoteCert <$> getInteraction
252 -- |Return the 'Request' value representing the request header. You
253 -- usually don't need to call this function directly.
254 getRequest ∷ Resource Request
255 getRequest = niRequest <$> getInteraction
257 -- |Get the path of this 'Resource' (to be exact, 'ResourceDef') in
258 -- the 'Network.HTTP.Lucu.Resource.Tree.ResTree'. The result of this
259 -- action is the exact path in the tree even when the 'ResourceDef' is
264 -- > main = let tree = mkResTree [ (["foo"], resFoo) ]
265 -- > in runHttpd defaultConfig tree []
267 -- > resFoo = emptyResource {
268 -- > resIsGreedy = True
269 -- > , resGet = Just $ do requestURI <- getRequestURI
270 -- > resourcePath <- getResourcePath
271 -- > pathInfo <- getPathInfo
272 -- > -- uriPath requestURI == "/foo/bar/baz"
273 -- > -- resourcePath == ["foo"]
274 -- > -- pathInfo == ["bar", "baz"]
277 getResourcePath ∷ Resource [Strict.ByteString]
278 getResourcePath = niResourcePath <$> getInteraction
280 -- |@'getChunk' n@ attempts to read a chunk of request body up to @n@
281 -- bytes. You can incrementally read the request body by repeatedly
282 -- calling this function. If there is nothing to be read anymore,
283 -- 'getChunk' returns 'Strict.empty' and makes 'Resource' transit to
284 -- the /Deciding Header/ state.
285 getChunk ∷ Int → Resource Strict.ByteString
286 getChunk = (driftTo ReceivingBody *>) ∘ getChunk'
288 getChunk' ∷ Int → Resource Strict.ByteString
290 | n < 0 = fail ("getChunk: n must not be negative: " ⧺ show n)
292 | otherwise = do req ← getRequest
293 if reqMustHaveBody req then
294 askForInput =≪ getInteraction
296 driftTo DecidingHeader *> return (∅)
298 askForInput ∷ NormalInteraction → Resource Strict.ByteString
299 askForInput (NI {..})
300 = do -- Ask the RequestReader to get a chunk.
302 $ putTMVar niReceiveBodyReq (ReceiveBody n)
303 -- Then wait for a reply.
306 $ takeTMVar niReceivedBody
307 -- Have we got an EOF?
308 when (Strict.null chunk)
309 $ driftTo DecidingHeader
312 -- |Declare the response status code. If you don't call this function,
313 -- the status code will be defaulted to \"200 OK\".
314 setStatus ∷ StatusCode sc ⇒ sc → Resource ()
316 = do ni ← getInteraction
318 $ do state ← readTVar $ niState ni
319 when (state > DecidingHeader)
320 $ fail "Too late to declare the response status."
321 res ← readTVar $ niResponse ni
322 writeTVar (niResponse ni) $ setStatusCode sc res
324 -- |@'setHeader' name value@ declares the value of the response header
325 -- @name@ as @value@. Note that this function is not intended to be
326 -- used so frequently: there should be specialised functions like
327 -- 'setContentType' for every common headers.
329 -- Some important headers (especially \"Content-Length\" and
330 -- \"Transfer-Encoding\") may be silently dropped or overwritten by
331 -- the system not to corrupt the interaction with client at the
332 -- viewpoint of HTTP protocol layer. For instance, if we are keeping
333 -- the connection alive, without this manipulation it will be a
334 -- catastrophe when we send a header \"Content-Length: 10\" and
335 -- actually send a body of 20 bytes long to the remote peer. In this
336 -- case the client shall only accept the first 10 bytes of response
337 -- body and thinks that the residual 10 bytes is a part of the header
338 -- of the next response.
339 setHeader ∷ CIAscii → Ascii → Resource ()
340 setHeader name value = liftIO ∘ atomically ∘ go =≪ getInteraction
342 go ∷ NormalInteraction → STM ()
344 = do state ← readTVar niState
345 when (state > DecidingHeader) $
346 fail "Too late to declare a response header field."
347 res ← readTVar niResponse
348 writeTVar niResponse $ H.setHeader name value res
349 when (name ≡ "Content-Type") $
350 writeTVar niResponseHasCType True
352 -- |@'deleteHeader' name@ deletes a response header @name@ if
353 -- any. This function is not intended to be used so frequently.
354 deleteHeader ∷ CIAscii → Resource ()
355 deleteHeader name = liftIO ∘ atomically ∘ go =≪ getInteraction
357 go ∷ NormalInteraction → STM ()
359 = do state ← readTVar niState
360 when (state > DecidingHeader) $
361 fail "Too late to delete a response header field."
362 res ← readTVar niResponse
363 writeTVar niResponse $ H.deleteHeader name res
364 when (name ≡ "Content-Type") $
365 writeTVar niResponseHasCType False
367 -- |Run a 'Builder' to construct a chunk, and write it to the response
368 -- body. It can be safely applied to a 'Builder' producing an
369 -- infinitely long stream of octets.
371 -- Note that you must first declare the response header
372 -- \"Content-Type\" before applying this function. See:
374 putBuilder ∷ Builder → Resource ()
375 putBuilder b = liftIO ∘ atomically ∘ go =≪ getInteraction
377 go ∷ NormalInteraction → STM ()
379 = do driftTo' ni SendingBody
380 hasCType ← readTVar niResponseHasCType
383 $ mkAbortion' InternalServerError
384 "putBuilder: Content-Type has not been set."
385 putTMVar niBodyToSend b
387 driftTo ∷ InteractionState → Resource ()
388 driftTo = (getInteraction ≫=) ∘ ((liftIO ∘ atomically) ∘) ∘ flip driftTo'
390 driftTo' ∷ NormalInteraction → InteractionState → STM ()
391 driftTo' ni@(NI {..}) newState
392 = do oldState ← readTVar niState
395 driftFrom ∷ InteractionState → STM ()
397 | newState < oldState = throwStateError oldState newState
398 | newState ≡ oldState = return ()
400 = do let a = [oldState .. newState]
403 mapM_ (uncurry driftFromTo) c
404 writeTVar niState newState
406 throwStateError ∷ Monad m ⇒ InteractionState → InteractionState → m a
407 throwStateError Done SendingBody
408 = fail "It makes no sense to output something after finishing outputs."
409 throwStateError old new
410 = fail ("State error: " ⧺ show old ⧺ " ==> " ⧺ show new)
412 driftFromTo ∷ InteractionState → InteractionState → STM ()
413 driftFromTo ReceivingBody _
414 = putTMVar niReceiveBodyReq WasteAll
415 driftFromTo DecidingHeader _