Search code examples
haskellscottyhaskell-wai

How to run an action after response has been fully sent in Scotty / WAI


Upon a GET request, my Scotty webapp will run some computation and store its result in a temporary file, which it sends as the response using file.

Now I would like to run some cleanup (i.e. delete the temporary file) after the file has been sent. Scotty does not seem to include a way for doing so.

Is there is any functionality in WAI for achieving this?


Solution

  • wai gives us a function responseStream

    responseStream :: Status -> ResponseHeaders -> StreamingBody -> Response
    

    that constructs a Response out of a StreamingBody, which is actually a function

    type StreamingBody = (Builder -> IO ()) -> IO () -> IO ()
    

    that, given a "write" action, and a "flush" action, finds some bytes somewhere and performs all the writing and flushing.

    wai also provides us with a ready-made responseFile function:

    responseFile :: Status -> ResponseHeaders -> FilePath -> Maybe FilePart -> Response
    

    but it doesn't delete the file at the end. Could we modify it in some way? It seems that we can, with the help of the responseToStream auxiliary function

    responseToStream :: Response -> (Status, ResponseHeaders, (StreamingBody -> IO a) -> IO a)
    

    that "opens up" an already constructed Response, allowing us to tweak things.

    Like this:

    import Network.Wai
    import Network.HTTP.Types.Status (status200)
    import System.Directory (removeFile)
    
    responseFileDeleting' :: FilePath -> Response
    responseFileDeleting' filepath = 
        let (status,header,streamer) = 
                responseToStream $ responseFile status200 [] filepath Nothing
         in responseStream status header (\write flush ->
                 -- this would be a good place to put a bracket, if needed
                 do streamer (\body -> body write flush)
                    removeFile filepath)
    

    (Note: the type of streamer is a bit mind-twisting because of all the higher-orderness.)

    This solution has the disadvantage that requests have to wait until the file is deleted in order to complete. Another option could be to send filepaths to some kind of concurrent queue that performed the deletions in another thread.