7 module Network.HTTP.Lucu.ResponseWriter
11 import qualified Blaze.ByteString.Builder.HTTP as BB
12 import Control.Concurrent
13 import Control.Concurrent.STM
14 import Control.Exception
16 import qualified Data.Ascii as A
17 import Data.Monoid.Unicode
18 import qualified Data.Sequence as S
19 import Data.Sequence (ViewR(..))
20 import Network.HTTP.Lucu.Config
21 import Network.HTTP.Lucu.HandleLike
22 import Network.HTTP.Lucu.HttpVersion
23 import Network.HTTP.Lucu.Interaction
24 import Network.HTTP.Lucu.Postprocess
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 $ writeContinueIfNeeded ctx itr
84 writeContinueIfNeeded ∷ HandleLike h
88 writeContinueIfNeeded ctx@(Context {..}) itr@(Interaction {..})
89 = do isNeeded ← atomically $ readTMVar itrSendContinue
91 $ do let cont = Response {
92 resVersion = HttpVersion 1 1
93 , resStatus = Continue
96 cont' ← completeUnconditionalHeaders cConfig cont
97 hPutBuilder cHandle $ A.toBuilder $ printResponse cont'
101 writeHeader ∷ HandleLike h
105 writeHeader ctx@(Context {..}) itr@(Interaction {..})
106 = do res ← atomically $
107 do state ← readTVar itrState
108 if state ≥ SendingBody then
111 retry -- Too early to write header fields.
112 hPutBuilder cHandle $ A.toBuilder $ printResponse res
114 writeBodyIfNeeded ctx itr
116 writeBodyIfNeeded ∷ HandleLike h
120 writeBodyIfNeeded ctx itr@(Interaction {..})
123 do willDiscardBody ← readTVar itrWillDiscardBody
124 if willDiscardBody then
125 return $ discardBody ctx itr
127 do willChunkBody ← readTVar itrWillChunkBody
128 if willChunkBody then
129 return $ writeChunkedBody ctx itr
131 return $ writeNonChunkedBody ctx itr
133 discardBody ∷ HandleLike h
137 discardBody ctx itr@(Interaction {..})
140 do chunk ← tryTakeTMVar itrBodyToSend
142 Just _ → return $ discardBody ctx itr
143 Nothing → do state ← readTVar itrState
145 return $ finalize ctx itr
149 writeChunkedBody ∷ HandleLike h
153 writeChunkedBody ctx@(Context {..}) itr@(Interaction {..})
156 do chunk ← tryTakeTMVar itrBodyToSend
159 do hPutBuilder cHandle $ BB.chunkedTransferEncoding b
161 writeChunkedBody ctx itr
162 Nothing → do state ← readTVar itrState
164 return $ finalize ctx itr
168 writeNonChunkedBody ∷ HandleLike h
172 writeNonChunkedBody ctx@(Context {..}) itr@(Interaction {..})
175 do chunk ← tryTakeTMVar itrBodyToSend
178 do hPutBuilder cHandle b
180 writeNonChunkedBody ctx itr
181 Nothing → do state ← readTVar itrState
183 return $ finalize ctx itr
187 finalize ∷ HandleLike h ⇒ Context h → Interaction → IO ()
188 finalize ctx@(Context {..}) (Interaction {..})
191 do sentContinue ← takeTMVar itrSendContinue
192 willDiscardBody ← readTVar itrWillDiscardBody
193 willChunkBody ← readTVar itrWillChunkBody
194 willClose ← readTVar itrWillClose
195 queue ← readTVar cQueue
196 case S.viewr queue of
198 → writeTVar cQueue queue'
200 → fail "finalize: cQueue is empty, which should never happen."
202 do when (((¬) willDiscardBody) ∧ willChunkBody)
203 $ do hPutBuilder cHandle BB.chunkedTransferTerminator
205 if willClose ∨ needToClose sentContinue then
206 -- The RequestReader is probably blocking on
207 -- hWaitForInput so we have to kill it before
208 -- closing the socket.
209 -- THINKME: Couldn't that somehow be avoided?
210 do killThread cReader
213 awaitSomethingToWrite ctx
215 needToClose ∷ Bool → Bool
216 needToClose sentContinue
217 -- We've sent both "HTTP/1.1 100 Continue" and a final
218 -- response, so nothing prevents our connection from keeping
220 | sentContinue = False
221 -- We've got "Expect: 100-continue" but have sent a final
222 -- response without sending "HTTP/1.1 100
223 -- Continue". According to the RFC 2616 (HTTP/1.1), it is
224 -- undecidable whether the client will send us its
225 -- (rejected) request body OR start a completely new request
226 -- in this situation. So the only possible thing to do is to
227 -- brutally shutdown the connection.
228 | itrExpectedContinue ≡ Just True = True
229 -- The client didn't expect 100-continue so we haven't sent
230 -- one. No need to do anything special.