10
10
module Servant.Server.Internal.RoutingApplication where
11
11
12
12
import Control.Exception (bracket )
13
- import Control.Monad (ap , liftM )
13
+ import Control.Monad (ap , liftM , (>=>) )
14
14
import Control.Monad.Trans (MonadIO (.. ))
15
15
import Control.Monad.Trans.Except (runExceptT )
16
- import Data.IORef (newIORef , readIORef , writeIORef )
16
+ import Data.IORef (newIORef , readIORef , writeIORef , IORef , atomicModifyIORef )
17
17
import Network.Wai (Application , Request ,
18
18
Response , ResponseReceived )
19
19
import Prelude ()
@@ -107,10 +107,6 @@ data Delayed env c where
107
107
, authD :: DelayedIO auth
108
108
, bodyD :: DelayedIO body
109
109
, serverD :: captures -> auth -> body -> Request -> RouteResult c
110
- , cleanupD :: body -> IO ()
111
- -- not in DelayedIO because:
112
- -- - most likely should not depend on the request
113
- -- - simpler
114
110
} -> Delayed env c
115
111
116
112
instance Functor (Delayed env ) where
@@ -120,12 +116,33 @@ instance Functor (Delayed env) where
120
116
, ..
121
117
} -- Note [Existential Record Update]
122
118
119
+ -- | A mutable cleanup action
120
+ newtype CleanupRef = CleanupRef (IORef (IO () ))
121
+
122
+ newCleanupRef :: IO CleanupRef
123
+ newCleanupRef = CleanupRef <$> newIORef (return () )
124
+
125
+ -- | Add a clean up action to a 'CleanupRef'
126
+ addCleanup' :: IO () -> CleanupRef -> IO ()
127
+ addCleanup' act (CleanupRef ref) = atomicModifyIORef ref (\ act' -> (act' >> act, () ))
128
+
129
+ addCleanup :: IO () -> DelayedIO ()
130
+ addCleanup act = DelayedIO $ \ _req cleanupRef ->
131
+ addCleanup' act cleanupRef >> return (Route () )
132
+
133
+ -- | Run all the clean up actions registered in
134
+ -- a 'CleanupRef'.
135
+ runCleanup :: CleanupRef -> IO ()
136
+ runCleanup (CleanupRef ref) = do
137
+ act <- readIORef ref
138
+ act
139
+
123
140
-- | Computations used in a 'Delayed' can depend on the
124
141
-- incoming 'Request', may perform 'IO, and result in a
125
142
-- 'RouteResult, meaning they can either suceed, fail
126
143
-- (with the possibility to recover), or fail fatally.
127
144
--
128
- newtype DelayedIO a = DelayedIO { runDelayedIO :: Request -> IO (RouteResult a ) }
145
+ newtype DelayedIO a = DelayedIO { runDelayedIO :: Request -> CleanupRef -> IO (RouteResult a ) }
129
146
130
147
instance Functor DelayedIO where
131
148
fmap = liftM
@@ -135,36 +152,36 @@ instance Applicative DelayedIO where
135
152
(<*>) = ap
136
153
137
154
instance Monad DelayedIO where
138
- return x = DelayedIO (const $ return (Route x))
155
+ return x = DelayedIO (\ _req _cleanup -> return (Route x))
139
156
DelayedIO m >>= f =
140
- DelayedIO $ \ req -> do
141
- r <- m req
157
+ DelayedIO $ \ req cl -> do
158
+ r <- m req cl
142
159
case r of
143
160
Fail e -> return $ Fail e
144
161
FailFatal e -> return $ FailFatal e
145
- Route a -> runDelayedIO (f a) req
162
+ Route a -> runDelayedIO (f a) req cl
146
163
147
164
instance MonadIO DelayedIO where
148
- liftIO m = DelayedIO (const $ Route <$> m)
165
+ liftIO m = DelayedIO (\ _req _cl -> Route <$> m)
149
166
150
167
-- | A 'Delayed' without any stored checks.
151
168
emptyDelayed :: RouteResult a -> Delayed env a
152
169
emptyDelayed result =
153
- Delayed (const r) r r r (\ _ _ _ _ -> result) ( const $ return () )
170
+ Delayed (const r) r r r (\ _ _ _ _ -> result)
154
171
where
155
172
r = return ()
156
173
157
174
-- | Fail with the option to recover.
158
175
delayedFail :: ServantErr -> DelayedIO a
159
- delayedFail err = DelayedIO (const $ return $ Fail err)
176
+ delayedFail err = DelayedIO (\ _req _cleanup -> return $ Fail err)
160
177
161
178
-- | Fail fatally, i.e., without any option to recover.
162
179
delayedFailFatal :: ServantErr -> DelayedIO a
163
- delayedFailFatal err = DelayedIO (const $ return $ FailFatal err)
180
+ delayedFailFatal err = DelayedIO (\ _req _cleanup -> return $ FailFatal err)
164
181
165
182
-- | Gain access to the incoming request.
166
183
withRequest :: (Request -> DelayedIO a ) -> DelayedIO a
167
- withRequest f = DelayedIO (\ req -> runDelayedIO (f req) req)
184
+ withRequest f = DelayedIO (\ req cl -> runDelayedIO (f req) req cl )
168
185
169
186
-- | Add a capture to the end of the capture block.
170
187
addCapture :: Delayed env (a -> b )
@@ -206,7 +223,6 @@ addBodyCheck Delayed{..} new =
206
223
Delayed
207
224
{ bodyD = (,) <$> bodyD <*> new
208
225
, serverD = \ c a (z, v) req -> ($ v) <$> serverD c a z req
209
- , cleanupD = cleanupD . fst -- not sure that's right
210
226
, ..
211
227
} -- Note [Existential Record Update]
212
228
@@ -249,19 +265,18 @@ passToServer Delayed{..} x =
249
265
runDelayed :: Delayed env a
250
266
-> env
251
267
-> Request
252
- -> IO ( RouteResult a , IO () )
253
- runDelayed Delayed { .. } env req = do
254
- cleanupRef <- newIORef ( return () )
255
- routeRes <- runDelayedIO
268
+ -> CleanupRef
269
+ -> IO ( RouteResult a )
270
+ runDelayed Delayed { .. } env req cleanupRef =
271
+ runDelayedIO
256
272
(do c <- capturesD env
257
273
methodD
258
274
a <- authD
259
275
b <- bodyD
260
- liftIO (writeIORef cleanupRef $ cleanupD b)
261
- DelayedIO $ \ r -> return (serverD c a b r)
276
+ DelayedIO $ \ r _cleanup -> return (serverD c a b r)
262
277
)
263
278
req
264
- fmap (routeRes,) $ readIORef cleanupRef
279
+ cleanupRef
265
280
266
281
-- | Runs a delayed server and the resulting action.
267
282
-- Takes a continuation that lets us send a response.
@@ -273,10 +288,11 @@ runAction :: Delayed env (Handler a)
273
288
-> (RouteResult Response -> IO r )
274
289
-> (a -> RouteResult Response )
275
290
-> IO r
276
- runAction action env req respond k =
277
- bracket (runDelayed action env req)
278
- snd
279
- (\ (res, _) -> go res >>= respond)
291
+ runAction action env req respond k = do
292
+ cleanupRef <- newCleanupRef
293
+ bracket (runDelayed action env req cleanupRef)
294
+ (const $ runCleanup cleanupRef)
295
+ (go >=> respond)
280
296
281
297
where
282
298
go (Fail e) = return $ Fail e
0 commit comments