7 module Network.HTTP.Lucu.ResponseWriter
11 import qualified Blaze.ByteString.Builder.HTTP as BB
12 import Control.Applicative
13 import Control.Concurrent
14 import Control.Concurrent.STM
15 import Control.Exception
17 import qualified Data.Ascii as A
18 import Data.Monoid.Unicode
19 import qualified Data.Sequence as S
20 import Data.Sequence (ViewR(..))
21 import Network.HTTP.Lucu.Config
22 import Network.HTTP.Lucu.HandleLike
23 import Network.HTTP.Lucu.HttpVersion
24 import Network.HTTP.Lucu.Interaction
25 import Network.HTTP.Lucu.Response
26 import Prelude.Unicode
27 import System.IO (hPutStrLn, stderr)
28 import System.IO.Error
34 , cQueue ∷ !InteractionQueue
38 responseWriter ∷ HandleLike h ⇒ Config → h → InteractionQueue → ThreadId → IO ()
39 responseWriter cnf h tQueue readerTID
40 = awaitSomethingToWrite (Context cnf h tQueue readerTID)
43 , Handler handleAsyncE
45 , Handler handleOthers
48 handleIOE ∷ IOException → IO ()
50 | isIllegalOperation e
51 = return () -- EPIPE: should be ignored at all.
55 handleAsyncE ∷ AsyncException → IO ()
56 handleAsyncE ThreadKilled = terminate'
57 handleAsyncE e = terminate e
59 handleBIOS ∷ BlockedIndefinitelyOnSTM → IO ()
60 handleBIOS = terminate
62 handleOthers ∷ SomeException → IO ()
63 handleOthers = terminate
65 terminate ∷ Exception e ⇒ e → IO ()
67 = do hPutStrLn stderr "requestWriter caught an exception:"
68 hPutStrLn stderr (show $ toException e)
74 awaitSomethingToWrite ∷ HandleLike h ⇒ Context h → IO ()
75 awaitSomethingToWrite ctx@(Context {..})
78 do queue ← readTVar cQueue
81 queue' :> itr → do writeTVar cQueue queue'
82 return $ writeSomething ctx itr
84 writeSomething ∷ HandleLike h ⇒ Context h → SomeInteraction → IO ()
85 writeSomething ctx itr
86 = let writer = writeResponseForNI ctx <$> fromInteraction itr <|>
87 writeResponseForSEI ctx <$> fromInteraction itr <|>
88 writeResponseForSYI ctx <$> fromInteraction itr
92 Nothing → fail "Internal error: unknown interaction type"
94 writeResponseForNI ∷ HandleLike h
98 writeResponseForNI = writeContinueIfNeeded
100 writeContinueIfNeeded ∷ HandleLike h
104 writeContinueIfNeeded ctx@(Context {..}) ni@(NI {..})
105 = do isNeeded ← atomically $ readTMVar niSendContinue
107 $ do let cont = Response {
108 resVersion = HttpVersion 1 1
109 , resStatus = fromStatusCode Continue
112 hPutBuilder cHandle $ A.toBuilder $ printResponse cont
116 writeHeader ∷ HandleLike h
120 writeHeader ctx@(Context {..}) ni@(NI {..})
121 = do res ← atomically $
122 do state ← readTVar niState
123 if state ≥ SendingBody then
126 retry -- Too early to write header fields.
127 hPutBuilder cHandle $ A.toBuilder $ printResponse res
129 writeBodyIfNeeded ctx ni
131 writeBodyIfNeeded ∷ HandleLike h
135 writeBodyIfNeeded ctx ni@(NI {..})
138 do willDiscardBody ← readTVar niWillDiscardBody
139 if willDiscardBody then
140 return $ discardBody ctx ni
142 if niWillChunkBody then
143 return $ writeChunkedBody ctx ni
145 return $ writeNonChunkedBody ctx ni
147 discardBody ∷ HandleLike h
151 discardBody ctx ni@(NI {..})
154 do chunk ← tryTakeTMVar niBodyToSend
156 Just _ → return $ discardBody ctx ni
157 Nothing → do state ← readTVar niState
159 return $ finalize ctx ni
163 writeChunkedBody ∷ HandleLike h
167 writeChunkedBody ctx@(Context {..}) ni@(NI {..})
170 do chunk ← tryTakeTMVar niBodyToSend
173 do hPutBuilder cHandle $ BB.chunkedTransferEncoding b
175 writeChunkedBody ctx ni
176 Nothing → do state ← readTVar niState
179 do hPutBuilder cHandle BB.chunkedTransferTerminator
185 writeNonChunkedBody ∷ HandleLike h
189 writeNonChunkedBody ctx@(Context {..}) ni@(NI {..})
192 do chunk ← tryTakeTMVar niBodyToSend
195 do hPutBuilder cHandle b
197 writeNonChunkedBody ctx ni
198 Nothing → do state ← readTVar niState
200 return $ finalize ctx ni
204 finalize ∷ HandleLike h ⇒ Context h → NormalInteraction → IO ()
205 finalize ctx@(Context {..}) (NI {..})
208 do willClose ← readTVar niWillClose
209 sentContinue ← takeTMVar niSendContinue
211 if needToClose willClose sentContinue then
212 -- The RequestReader is probably blocking on
213 -- hWaitForInput so we have to kill it before closing
214 -- the socket. THINKME: Couldn't that somehow be
216 do killThread cReader
219 awaitSomethingToWrite ctx
221 needToClose ∷ Bool → Bool → Bool
222 needToClose willClose sentContinue
223 -- Explicitly instructed to close the connection.
225 -- We've sent both "HTTP/1.1 100 Continue" and a final
226 -- response, so nothing prevents our connection from keeping
228 | sentContinue = False
229 -- We've got "Expect: 100-continue" but have sent a final
230 -- response without sending "HTTP/1.1 100
231 -- Continue". According to the RFC 2616 (HTTP/1.1), it is
232 -- undecidable whether the client will send us its
233 -- (rejected) request body OR start a completely new request
234 -- in this situation. So the only possible thing to do is to
235 -- brutally shutdown the connection.
236 | niExpectedContinue = True
237 -- The client didn't expect 100-continue so we haven't sent
238 -- one. No need to do anything special.
241 writeResponseForSEI ∷ HandleLike h
243 → SemanticallyInvalidInteraction
245 writeResponseForSEI ctx@(Context {..}) (SEI {..})
246 = do hPutBuilder cHandle $ A.toBuilder $ printResponse seiResponse
247 unless seiWillDiscardBody $
248 if seiWillChunkBody then
249 do hPutBuilder cHandle $ BB.chunkedTransferEncoding seiBodyToSend
250 hPutBuilder cHandle BB.chunkedTransferTerminator
252 hPutBuilder cHandle seiBodyToSend
254 if seiWillClose ∨ seiExpectedContinue then
255 do killThread cReader
258 awaitSomethingToWrite ctx
260 writeResponseForSYI ∷ HandleLike h
262 → SyntacticallyInvalidInteraction
264 writeResponseForSYI (Context {..}) (SYI {..})
265 = do hPutBuilder cHandle $ A.toBuilder $ printResponse syiResponse
266 hPutBuilder cHandle syiBodyToSend