]> gitweb @ CieloNegro.org - Lucu.git/blob - Network/HTTP/Lucu/RequestReader.hs
Slight speed improvement
[Lucu.git] / Network / HTTP / Lucu / RequestReader.hs
1 module Network.HTTP.Lucu.RequestReader
2     ( requestReader
3     )
4     where
5
6 import           Control.Concurrent.STM
7 import           Control.Exception
8 import           Control.Monad
9 import qualified Data.ByteString.Lazy.Char8 as B
10 import           Data.ByteString.Lazy.Char8 (ByteString)
11 import           Data.Maybe
12 import qualified Data.Sequence as S
13 import           Data.Sequence ((<|))
14 import           Network.Socket
15 import           Network.HTTP.Lucu.Config
16 import           Network.HTTP.Lucu.Chunk
17 import           Network.HTTP.Lucu.DefaultPage
18 import           Network.HTTP.Lucu.Interaction
19 import           Network.HTTP.Lucu.Parser
20 import           Network.HTTP.Lucu.Postprocess
21 import           Network.HTTP.Lucu.Preprocess
22 import           Network.HTTP.Lucu.Request
23 import           Network.HTTP.Lucu.Response
24 import           Network.HTTP.Lucu.Resource.Tree
25 import           Prelude hiding (catch)
26 import           System.IO
27
28
29 requestReader :: Config -> ResTree -> Handle -> SockAddr -> InteractionQueue -> IO ()
30 requestReader cnf tree h addr tQueue
31     = cnf `seq` tree `seq` h `seq` addr `seq` tQueue `seq`
32       do catch (do input <- B.hGetContents h
33                    acceptRequest input) $ \ exc ->
34              case exc of
35                IOException _               -> return ()
36                AsyncException ThreadKilled -> return ()
37                BlockedIndefinitely         -> putStrLn "requestReader: blocked indefinitely"
38                _                           -> print exc
39     where
40       acceptRequest :: ByteString -> IO ()
41       acceptRequest input
42           -- キューに最大パイプライン深度以上のリクエストが溜まってゐる
43           -- 時は、それが限度以下になるまで待つ。
44           = {-# SCC "acceptRequest" #-}
45             do atomically $ do queue    <- readTVar tQueue
46                                when (S.length queue >= cnfMaxPipelineDepth cnf)
47                                     retry
48
49                -- リクエストを讀む。パースできない場合は直ちに 400 Bad
50                -- Request 應答を設定し、それを出力してから切斷するやう
51                -- に ResponseWriter に通知する。
52                case parse requestP input of
53                  (Success req , input') -> acceptParsableRequest req input'
54                  (IllegalInput, _     ) -> acceptNonparsableRequest BadRequest
55                  (ReachedEOF  , _     ) -> acceptNonparsableRequest BadRequest
56
57       acceptNonparsableRequest :: StatusCode -> IO ()
58       acceptNonparsableRequest status
59           = {-# SCC "acceptNonparsableRequest" #-}
60             do itr <- newInteraction cnf addr Nothing
61                atomically $ do updateItr itr itrResponse
62                                              $ \ res -> res {
63                                                           resStatus = status
64                                                         }
65                                writeItr itr itrWillClose True
66                                writeItr itr itrState     Done
67                                writeDefaultPage itr
68                                postprocess itr
69                                enqueue itr
70
71       acceptParsableRequest :: Request -> ByteString -> IO ()
72       acceptParsableRequest req input
73           = {-# SCC "acceptParsableRequest" #-}
74             do itr <- newInteraction cnf addr (Just req)
75                action
76                    <- atomically $
77                       do preprocess itr
78                          isErr <- readItr itr itrResponse (isError . resStatus)
79                          if isErr then
80                              acceptSemanticallyInvalidRequest itr input
81                            else
82                              case findResource tree $ reqURI req of
83                                Nothing -- Resource が無かった
84                                    -> acceptRequestForNonexistentResource itr input
85
86                                Just (rsrcPath, rsrcDef) -- あった
87                                    -> acceptRequestForExistentResource itr input rsrcPath rsrcDef
88                action
89
90       acceptSemanticallyInvalidRequest :: Interaction -> ByteString -> STM (IO ())
91       acceptSemanticallyInvalidRequest itr input
92           = {-# SCC "acceptSemanticallyInvalidRequest" #-}
93             do writeItr itr itrState Done
94                writeDefaultPage itr
95                postprocess itr
96                enqueue itr
97                return $ acceptRequest input
98
99       acceptRequestForNonexistentResource :: Interaction -> ByteString -> STM (IO ())
100       acceptRequestForNonexistentResource itr input
101           = {-# SCC "acceptRequestForNonexistentResource" #-}
102             do updateItr itr itrResponse 
103                              $ \res -> res {
104                                          resStatus = NotFound
105                                        }
106                writeItr itr itrState Done
107                writeDefaultPage itr
108                postprocess itr
109                enqueue itr
110                return $ acceptRequest input
111
112       acceptRequestForExistentResource :: Interaction -> ByteString -> [String] -> ResourceDef -> STM (IO ())
113       acceptRequestForExistentResource oldItr input rsrcPath rsrcDef
114           = {-# SCC "acceptRequestForExistentResource" #-}
115             do let itr = oldItr { itrResourcePath = Just rsrcPath }
116                requestHasBody <- readItr itr itrRequestHasBody id
117                enqueue itr
118                return $ do runResource rsrcDef itr
119                            if requestHasBody then
120                                observeRequest itr input
121                              else
122                                acceptRequest input
123
124       observeRequest :: Interaction -> ByteString -> IO ()
125       observeRequest itr input
126           = {-# SCC "observeRequest" #-}
127             do isChunked <- atomically $ readItr itr itrRequestIsChunked id
128                if isChunked then
129                    observeChunkedRequest itr input
130                  else
131                    observeNonChunkedRequest itr input
132
133       observeChunkedRequest :: Interaction -> ByteString -> IO ()
134       observeChunkedRequest itr input
135           = {-# SCC "observeChunkedRequest" #-}
136             do action
137                    <- atomically $
138                       do isOver <- readItr itr itrReqChunkIsOver id
139                          if isOver then
140                              return $ acceptRequest input
141                            else
142                              do wantedM <- readItr itr itrReqBodyWanted id
143                                 if wantedM == Nothing then
144                                     do wasteAll <- readItr itr itrReqBodyWasteAll id
145                                        if wasteAll then
146                                            -- 破棄要求が來た
147                                            do remainingM <- readItr itr itrReqChunkRemaining id
148                                               if fmap (> 0) remainingM == Just True then
149                                                   -- 現在のチャンクをまだ
150                                                   -- 讀み終へてゐない
151                                                   do let (_, input') = B.splitAt (fromIntegral
152                                                                                   $ fromJust remainingM) input
153                                                          (footerR, input'') = parse chunkFooterP input'
154
155                                                      if footerR == Success () then
156                                                          -- チャンクフッタを正常に讀めた
157                                                          do writeItr itr itrReqChunkRemaining $ Just 0
158                                                          
159                                                             return $ observeChunkedRequest itr input''
160                                                        else
161                                                          return $ chunkWasMalformed itr
162                                                 else
163                                                   -- 次のチャンクを讀み始める
164                                                   seekNextChunk itr input
165                                          else
166                                            -- 要求がまだ來ない
167                                            retry
168                                   else
169                                     -- 受信要求が來た
170                                     do remainingM <- readItr itr itrReqChunkRemaining id
171                                        if fmap (> 0) remainingM == Just True then
172                                            -- 現在のチャンクをまだ讀み
173                                            -- 終へてゐない
174                                            do let wanted             = fromJust wantedM
175                                                   remaining          = fromJust remainingM
176                                                   bytesToRead        = fromIntegral $ min wanted remaining
177                                                   (chunk, input')    = B.splitAt bytesToRead input
178                                                   actualReadBytes    = fromIntegral $ B.length chunk
179                                                   newWanted          = case wanted - actualReadBytes of
180                                                                          0 -> Nothing
181                                                                          n -> Just n
182                                                   newRemaining       = Just $ remaining - actualReadBytes
183                                                   updateStates
184                                                       = do writeItr itr itrReqChunkRemaining newRemaining
185                                                            writeItr itr itrReqBodyWanted newWanted
186                                                            updateItr itr itrReceivedBody $ flip B.append chunk
187
188                                               if newRemaining == Just 0 then
189                                                   -- チャンクフッタを讀む
190                                                   case parse chunkFooterP input' of
191                                                     (Success _, input'')
192                                                         -> do updateStates
193                                                               return $ observeChunkedRequest itr input''
194                                                     _   -> return $ chunkWasMalformed itr
195                                                 else
196                                                   -- まだチャンクの終はりに達してゐない
197                                                   do updateStates
198                                                      return $ observeChunkedRequest itr input'
199                                          else
200                                            -- 次のチャンクを讀み始める
201                                            seekNextChunk itr input
202                action
203
204       seekNextChunk :: Interaction -> ByteString -> STM (IO ())
205       seekNextChunk itr input
206           = {-# SCC "seekNextChunk" #-}
207             case parse chunkHeaderP input of
208               -- 最終チャンク (中身が空)
209               (Success 0, input')
210                   -> case parse chunkTrailerP input' of
211                        (Success _, input'')
212                            -> do writeItr itr itrReqChunkLength $ Nothing
213                                  writeItr itr itrReqChunkRemaining $ Nothing
214                                  writeItr itr itrReqChunkIsOver True
215                                  
216                                  return $ acceptRequest input''
217                        _   -> return $ chunkWasMalformed itr
218               -- 最終でないチャンク
219               (Success len, input')
220                   -> do writeItr itr itrReqChunkLength $ Just len
221                         writeItr itr itrReqChunkRemaining $ Just len
222                         
223                         return $ observeChunkedRequest itr input'
224               -- チャンクヘッダがをかしい
225               _   -> return $ chunkWasMalformed itr
226
227       chunkWasMalformed :: Interaction -> IO ()
228       chunkWasMalformed itr
229           = {-# SCC "chunkWasMalformed" #-}
230             atomically $ do updateItr itr itrResponse 
231                                           $ \ res -> res {
232                                                        resStatus = BadRequest
233                                                      }
234                             writeItr itr itrWillClose True
235                             writeItr itr itrState Done
236                             writeDefaultPage itr
237                             postprocess itr
238
239       observeNonChunkedRequest :: Interaction -> ByteString -> IO ()
240       observeNonChunkedRequest itr input
241           = {-# SCC "observeNonChunkedRequest" #-}
242             do action
243                    <- atomically $
244                       do wantedM <- readItr itr itrReqBodyWanted id
245                          if wantedM == Nothing then
246                              do wasteAll <- readItr itr itrReqBodyWasteAll id
247                                 if wasteAll then
248                                     -- 破棄要求が來た
249                                     do remainingM <- readItr itr itrReqChunkRemaining id
250                                        
251                                        let (_, input') = if remainingM == Nothing then
252                                                              (B.takeWhile (\ _ -> True) input, B.empty)
253                                                          else
254                                                              B.splitAt (fromIntegral $ fromJust remainingM) input
255
256                                        writeItr itr itrReqChunkRemaining $ Just 0
257                                        writeItr itr itrReqChunkIsOver True
258
259                                        return $ acceptRequest input'
260                                   else
261                                     -- 要求がまだ来ない
262                                     retry
263                            else
264                                -- 受信要求が來た
265                                do remainingM <- readItr itr itrReqChunkRemaining id
266
267                                   let wanted          = fromJust wantedM
268                                       bytesToRead     = fromIntegral $ maybe wanted (min wanted) remainingM
269                                       (chunk, input') = B.splitAt bytesToRead input
270                                       newRemaining    = fmap
271                                                         (\ x -> x - (fromIntegral $ B.length chunk))
272                                                         remainingM
273                                       isOver          = B.length chunk < bytesToRead || newRemaining == Just 0
274
275                                   writeItr itr itrReqChunkRemaining newRemaining
276                                   writeItr itr itrReqChunkIsOver isOver
277                                   writeItr itr itrReqBodyWanted Nothing
278                                   writeItr itr itrReceivedBody chunk
279
280                                   if isOver then
281                                       return $ acceptRequest input'
282                                     else
283                                       return $ observeNonChunkedRequest itr input'
284                action
285
286       enqueue :: Interaction -> STM ()
287       enqueue itr = {-# SCC "enqueue" #-}
288                     do queue <- readTVar tQueue
289                        writeTVar tQueue (itr <| queue)