perform IO inside wai application - haskell

I am following the basic dispatching section of wai application.
I am able to catch the url parameter. How can I perform IO operation using these params.
I would like to use runCommand of System.Process to execute a system command using these parameters.
:t runCommand give
runCommand :: String -> IO ProcessHandle
my Main.hs
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad
import Network.Wai
import Network.HTTP.Types
import Network.Wai.Handler.Warp (run)
import Data.ByteString
import Control.Monad
import System.Process
import qualified Data.ByteString.Lazy.Char8 as L8
main :: IO ()
main = do
run 8080 app
app :: Application
app request respond = respond $ case rawPathInfo request of
"/" -> indexHtml
"/wake" -> wakeMeUP request
_ -> fourNotFour
indexHtml :: Response
indexHtml = responseFile
status200
[("Content-Type","text/html")]
"index.html"
Nothing
wakeMeUP :: Request -> Response
wakeMeUP request =
let query = queryString request
hour = join $ lookup "hour" query
min = join $ lookup "min" query
--I would like to use runCommand "using hour and min variables"
in responseLBS
status200
[("Content-Type","text/plain")]
"Alarm set at...to be coded later"
fourNotFour :: Response
fourNotFour = responseLBS
status404
[("Content-Type","text/plain")]
"404 not found"

Your design prevents it, because of how you have written app,
app request respond = respond $ case rawPathInfo request of
which says that you immediately respond. Note the type of Application:
type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
Since the result type has an IO, you have the opportunity to do I/O before yielding a value. So you could:
app request respond = do
whateverResult <- runCommand "whatever"
respond $ ...
(You could also do it afterward, at least according to the types:
app request respond = do
rcvd <- respond $ ...
runCommand "whatever"
return rcvd
Though that's a bit odd to do for the continuation-passing idiom being used here (the (a -> b) -> b pattern in the Application type). It means that the command will be run after everything else, for some definition of "everything else" that we can't know without reading the wai source.)
Anyway, you probably don't want the command to be run inside app, but rather in wakeMeUp, which means you need to change some types around. In particular,
wakeMeUp :: Request -> IO Response
-- ^^
and suitably monadify the function. Then your app needs to not call respond immediately, so you can say
app request respond =
response <- case rawPathInfo request of
"/" -> return indexHtml
-- ^^^^^^
"/wake" -> wakeMeUp request
-- no change, because wakeMeUp now has an IO return type
...
respond response
If this is gibberish to you, it's time to do some monad tutorials. Or if you just want to get the damn thing working, I recommend Dan Piponi's The IO Monad For People who Simply Don't Care. Happy hacking!

Related

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?
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.

Differing Reader Behaviour

I'm writing a wrapper around a Warp server where users can specify routes and handlers to make a web server. I decided to try using Continuation Monads to allow handlers to exit using a continuation when a route matches.
Here are the types and definitions I'm starting with:
import Control.Monad.Cont
import Control.Monad.Reader
import qualified Network.Wai as W
import qualified Data.Text as T
type App r a = ContT r (ReaderT W.Request IO) a
type Handler a = ReaderT W.Request IO a
type Respond = ((Status, T.Text) -> App (Status, T.Text) ())
route :: T.Text -> Handler (Status, T.Text) -> Respond -> App (Status, T.Text) ()
route routePath handler respond = do
liftIO $ print $ "Checking" `T.append` routePath
pth <- path
when (routePath == pth) $ do
req <- ask
response <- liftIO $ runReaderT handler req
respond response
An app is a collection of routes, each route reads the current continuation from the Reader environment; I originally wrote it like this:
hello :: Handler (Status, T.Text)
hello = return (ok200, "Hello World!")
goodbye :: Handler (Status, T.Text)
goodbye = return (ok200, "Goodbye World!")
app :: Respond -> App (Status, T.Text) ()
app = do
route "/hello" hello
route "/goodbye" goodbye
Strangely this doesn't seem to work, it only prints "Checking /goodbye"; however if we instead write the reader in the next form it works properly, as far as I was aware these two definitions should be equivalent; but apparently I'm missing something:
app :: Respond -> App (Status, T.Text) ()
app resp = do
route "/hello" hello resp
route "/goodbye" goodbye resp
Is there any way I can get the proper behaviour using the original app definition? Is there some way that the Reader Monad is messing up the continuations somehow?
I suspect that somehow the monad definition for reader is interrupting the order of computation; but it's not clear to me how:
instance Monad ((->) r) where
f >>= k = \ r -> k (f r) r
Thanks!

IO monad issues when trying to implement WAI HTTP Server + Fallback Proxy

What I'm trying to do is to create a somewhat smart reverse proxy server that should process some requests on its own and forward the others to the backend of choice. To make it challenging I'm trying hard to do it in Haskell, which I am a total newbie in.
Here's the code I've come up so far:
{-# LANGUAGE OverloadedStrings #-}
import Control.Applicative
import Data.ByteString
import Network.HTTP.ReverseProxy
import Network.HTTP.Types
import Network.Wai
import Network.Wai.Handler.Warp
import Network.Wai.Middleware.RequestLogger
import qualified Network.HTTP.Client as HC
helloApp :: Application
helloApp req respond =
respond $ responseLBS status200 [("Content-Type", "text/plain")] "Hello"
proxyStubApp :: Application
proxyStubApp req respond =
respond $ responseLBS status200 [("Content-Type", "text/plain")] "You've hit the stub"
proxyApp :: IO Application
proxyApp = do
manager <- HC.newManager HC.defaultManagerSettings
return $ waiProxyTo (const $ return $ WPRProxyDest ProxyDest { pdHost = "localhost", pdPort = 9393 }) defaultOnExc manager
app :: Application
app req respond =
serve req respond
where serve = lookupServeFunction req
lookupServeFunction :: Request -> Application
lookupServeFunction req
| isInfixOf "sample_path" (rawPathInfo req) = proxyStubApp
| otherwise = helloApp
main = run 3011 =<< (logStdoutDev <$> return app)
It works fine, but when I exchange proxyStubApp for actual proxyApp I am forced to add IO all over the place. Particularly it gets added to app, consequently leaving me with the following compilation error message:
Couldn't match expected type ‘Request -> t5 -> t4’
with actual type ‘IO Application’
The equation(s) for ‘app’ have two arguments,
but its type ‘IO Application’ has none
I feel like I understand why it is happening, but I'm out of ideas of how to cope with it :( Or am I doing something totally wrong?
Thank you!
P.S. Here are the dependencies should you want to compile the thing on your own: wai warp http-types text bytestring wai-extra time http-reverse-proxy http-client
The IO in IO Application is kind-of redundant. Note that
type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
so, expanding proxyApp's arguments (what you already do in proxyStubApp), you get
proxyApp :: Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
proxyApp req continuation = do
manager <- HC.newManager HC.defaultManagerSettings
waiProxyTo (...) req respond
That works, because in either case
proxyApp :: IO Application
proxyApp = do
manager <- HC.newManager ...
...
and
proxyApp :: Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
proxyApp req continuation = do
manager <- HC.newManager ...
...
the IO action HC.newManager ... is "run within IO".
You may find it conceptually clearer to construct an Application in IO and hand it to some other place, and I won't argue with you. I want to note though, that you choose the Application based on the Request, so in a way you are in the hypothetical HTTP monad when choosing, so lookupServeFunction's signature Request -> Application makes more sense to me.
If you want to keep that type signature for proxyApp,
lookupServeFunction and app will have to be in IO as well and main will have to change accordingly, e.g.
myApp <- app
...
As haoformayor said, It is generally easier to work without the outer IO layer.
You might also like to simplify main.
fmap logStdoutDev (return app)
is the same as
return (logStdoutDev app)
and
run 3011 =<< return (logStdoutDev app)
is the same as
run 3011 (logStdoutDev app)
You might want to install hlint, which will help you spot these.

Snap interface - getting the OS and Browser information

I am using the Snap information and I was wondering if there was some type of Request function ( such as ::Request -> IO Snap() or ::Request -> Handler App App()) that returns the OS or Browser information of the user visiting the webpage.
I would like to get the OS and Browser information of the person who is visiting the webpage.
You can get the User-Agent HTTP header via getHeader, because Request has a HasHeaders instance.
Example snippet:
import qualified Data.ByteString.Char8 as CS
import qualified Data.CaseInsensitive as CI
import Data.Maybe (listToMaybe)
uaName :: ByteString
uaName = CS.pack "User-Agent"
-- You can avoid CS.pack with OverloadedStrings extension.
uahName :: CI ByteString
uahName = CI.mk uaName
-- OverloadedStrings also gets rid of the CI.mk call.
getUserAgent :: Request -> Snap (Maybe ByteString)
getUserAgent rq = return . coerce $ getHeader uahName rq
where
coerce :: Maybe [ByteString] -> Maybe ByteString
coerce = (>>= listToMaybe)
-- Some HTTP headers can appear multiple times, hence the list.
-- `coerce` ignores all but the first occurrence.
For more detailed / less voluntary information, you could inject JS into an initial request and set cookies that can be extracted with rqCookies in a lter request.

How to force yesod/warp to close open file handles before handling next request?

I've written a small server which accepts registrations as POST requests and persists them by appending them to a file. As soon as I put this server under load (I use Apache JMeter with 50 concurrent threads and a repeat count of 10, and the post consists of one field with ~7k of text data), I get lots of "resource busy, file is locked" errors:
02/Nov/2013:18:07:11 +0100 [Error#yesod-core] registrations.txt: openFile: resource busy (file is locked) #(yesod-core-1.2.4.2:Yesod.Core.Class.Yesod ./Yesod/Core/Class/Yesod.hs:485:5)
Here is a stripped-down version of the code:
{-# LANGUAGE QuasiQuotes, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings, TypeFamilies #-}
import Yesod
import Text.Hamlet
import Control.Applicative ((<$>), (<*>))
import Control.Monad.IO.Class (liftIO)
import Data.Text (Text, pack, unpack)
import Data.String
import System.IO (withFile, IOMode(..), hPutStrLn)
data Server = Server
data Registration = Registration
{ text :: Text
}
deriving (Show, Read)
mkYesod "Server" [parseRoutes|
/reg RegR POST
|]
instance Yesod Server
instance RenderMessage Server FormMessage where
renderMessage _ _ = defaultFormMessage
postRegR :: Handler Html
postRegR = do
result <- runInputPost $ Registration
<$> ireq textField "text"
liftIO $ saveRegistration result
defaultLayout [whamlet|<p>#{show result}|]
saveRegistration :: Registration -> IO ()
saveRegistration r = withFile "registrations.txt" AppendMode (\h -> hPutStrLn h $ "+" ++ show r)
main :: IO ()
main = warp 8080 Server
I compiled the code on purpose without -threaded, and the OS shows only a single thread running. Nonetheless it looks to me like the requests are not completely serialised, and a new request is already handled before the old one has been written to disk.
Could you tell me how I can avoid the error message and ensure that all requests are handled successfully? Performance is not an issue yet.
It's perfectly OK to write to a Handle from several threads. In fact, Handles have MVars inside them to prevent weird concurrent behaviour. What you probably want is not to handle [sic] MVars by hand (which can lead to deadlock if, for instance, a handler throws an exception) but lift the withFile call outside the individual handler threads. The file stays open all the time - opening it on each request would be slow anyway.
I don't know much about Yesod, but I'd recommend something like this (probably doesn't compile):
data Server = Server { handle :: Handle }
postRegR :: Handler Html
postRegR = do
h <- handle `fmap` getYesod
result <- runInputPost $ Registration
<$> ireq textField "text"
liftIO $ saveRegistration h result
defaultLayout [whamlet|<p>#{show result}|]
saveRegistration :: Handle -> Registration -> IO ()
saveRegistration h r = hPutStrLn h $ "+" ++ show r
main :: IO ()
main = withFile "registrations.txt" AppendMode $ \h -> warp 8080 (Server h)
-- maybe there's a better way?
Aside: if you wanted to file to be written asynchronously you could write to a queue (if it were a log file or something), but in your use case you probably want to let the user know if their registration failed, so I'd recommend staying with this form.
Even without -threaded the Haskell runtime will have several "green threads" running cooperatively. You need to use Control.Concurrent to limit access to the file because you cannot have several threads writing to it at once.
The easiest way is to have an MVar () in your Server and have each request "take" the unit from the MVar before opening the file and then put it back after the file operation has been completed. You can use bracket to ensure that the lock is released even if writing the file fails. E.g. something like
import Control.Concurrent
import Control.Exception (bracket_)
type Lock = MVar ()
data Server = Server { fileLock :: Lock }
saveRegistration :: Registration -> Lock -> IO ()
saveRegistration r lock = bracket_ acquire release updateFile where
acquire = takeMVar lock
release = putMVar lock ()
updateFile =
withFile "registrations.txt" AppendMode (\h -> hPutStrLn h $ "+" ++ show r)

Resources