Is there a standard way to start two scotty servers in the same application? In a toy project I'm trying:
main :: IO ()
main = do
scotty 3000 $ do
get "/" $ do
text "hello"
scotty 4000 $ do
post "/" $ do
text "world"
The first server spins up but the second one does not. It could also be a flaw in the way I'm understanding Haskell IO. Thanks!
The scotty procedure does not return, it takes over control and continually serves requests to the webroutes. If it did return then you'd have an issue with control flow - how would you keep the port open for when a request arrived?
One solution is to put each call to scotty in a separate thread. For example:
#!/usr/bin/env cabal
{- cabal:
build-depends: base, scotty
-}
{-# LANGUAGE OverloadedStrings #-}
import Control.Concurrent
import Web.Scotty
main :: IO ()
main = do
forkIO $ scotty 3000 $ do
get "/" $ do
text "hello"
scotty 4000 $ do
post "/" $ do
text "world"
With operation of:
% curl -XPOST localhost:4000
world%
% curl -XGET localhost:3000
hello%
I would use async:
import Control.Concurrent.Async
main :: IO ()
main = do
a1 <- async $ scotty 3000 $ do
get "/" $ do
text "hello"
a2 <- async $ scotty 4000 $ do
post "/" $ do
text "world"
waitAnyCatchCancel [a1, a2]
Related
I'm using Scotty to write a small web apps.
I need to run IO inside the ScottyM type.
There are several difficulties:
First I can't automatically derive type synonyms from MonadIO in order to run liftIO:
type ScottyM = ScottyT Text IO
Second, I don't know how to derive ScottyT from MonadIO:
newtype ScottyT e m a
Constructors
ScottyT
runS :: State (ScottyState e m) a
What are my options?
Thanks
Is it necessary for you to run your IO inside ScottyM instead of before you start scotty?
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Data.Monoid (mconcat)
main = do
print "Hello world!" -- Or your IO action of choice
scotty 3000 $
get "/:word" $ do
beam <- param "word"
html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]
Another option is to abuse notFound and next to run IO in ActionM, which is more straightforward:
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Control.Monad.Trans.Class (lift)
import Data.Monoid (mconcat)
main = scotty 3000 $ do
notFound $ do
lift $ print "Hello world!" -- Or some other IO action
next
get "/:word" $ do
beam <- param "word"
html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]
I am attempting to create a server that returns two different values from a route depending if a user has visited it before. I have the following code:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Web.Scotty
main = do
putStrLn "Starting Server..."
scotty 3000 $ do
get "/" $ do
-- if first time
text "hello!"
-- if second time
text "hello, again!"
I have two questions:
1. How can I check if a user has requested the route before?
2. Where and how can I persist application state?
You can use STM to keep a mutable variable in memory:
import Control.Concurrent.STM.TVar
main = do
putStrLn "Starting Server..."
state <- newTVarIO :: IO VisitorsData
scotty 3000 $ do
get "/" $ do
visitorsData <- readTVarIO state
-- if the visitor's ID/cookie is in visitorsData
text "hello!"
-- if new visitor, add them to visitorsData
atomically $ modifyTVar state $ insertVisitor visitorId visitorsData
-- if second time
text "hello, again!"
(If you expect to scale this to a complex server, you'll want to pass the TVar around in the form of a ReaderT pattern)
I have the following Scotty app which tries to use STM to keep a count of API calls served:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Web.Scotty
import Data.Monoid (mconcat)
import Control.Concurrent.STM
import Control.Monad.IO.Class
main :: IO ()
main = do
counter <- newTVarIO 0
scotty 3000 $
get "/:word" $ do
liftIO $ atomically $ do
counter' <- readTVar counter
writeTVar counter (counter' + 1)
liftIO $ do
counter' <- atomically (readTVar counter)
print counter'
beam <- param "word"
html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]
I "load test" the API like this:
ab -c 100 -n 100000 http://127.0.0.1:3000/z
However, the API serves roughly about 16 thousand requests and then gets "stuck" - ab stops with error apr_socket_recv: Operation timed out (60).
I think I'm misusing STM, but not sure what I'm doing wrong. Any suggestions?
Quick guess here. 16,000 is about the number of available TCP ports. Is is possible you have not closed any connections and therefore run out of open ports for ab?
Is there something that is like the opposite of liftIO? I'm using websockets, and I want to be able to listen for messages from the server in a separate thread. Here's what I'm doing:
import Network.WebSockets
import qualified Data.Text as T
import Control.Monad.IO.Class
import Control.Monad
import Control.Concurrent
import Control.Applicative
printMessages :: WebSockets Hybi00 ()
printMessages = forever $ do
resp <- receiveDataMessage
liftIO $ print resp
run :: WebSockets Hybi00 ()
run = do
liftIO . forkIO $ printMessages
forever $ do
line <- liftIO getLine
sendTextData . T.pack $ line
main = connect "0.0.0.0" 8080 "/" run
So printMessages listens for messages from the server and keeps printing them out. The problem is, forkIO expects a function that returns IO (). Is there any way for me to run printMessages in the IO monad?
If I'm understanding this right, the reason you want to receive messages in another thread is because the main thread will be waiting for user input to send.
From a look at the documentation, it seems like you'll have an easier time if you reverse the roles of the threads: receive in the main thread, and send asynchronously from the other.
Then you can use getSink :: Protocol p => WebSockets p (Sink p) to grab a sink before forking, which you can then use with sendSink :: Sink p -> Message p -> IO () which lives in IO, avoiding the whole problem of mixing monads.
In other words, restructure your code to something like this:
sendMessages :: Sink Hybi00 -> IO ()
sendMessages sink = forever $ do
line <- getLine
let msg = textData . T.pack $ line
sendSink sink msg
run :: WebSockets Hybi00 ()
run = do
sink <- getSink
liftIO . forkIO $ sendMessages sink
forever $ do
resp <- receiveDataMessage
liftIO $ print resp
main = connect "0.0.0.0" 8080 "/" run
I have the basic "hello world" application setup using wai, and would like to use wai-handler-devel, but am unsure how to go about it and can't find any examples of it in usage on a wai project.
{-# LANGUAGE OverloadedStrings #-}
import Network.Wai
import Network.HTTP.Types
import Network.Wai.Handler.Warp (run)
import Data.ByteString.Lazy.Char8 () -- Just for an orphan instance
app :: Application
app _ = return $ responseLBS
status200
[("Content-Type", "text/plain")]
"Hello, World!"
main :: IO ()
main = do
putStrLn $ "http://localhost:8080/"
run 8080 app
What do I need to do to get wai-handler-devel working with a basic wai app?
Note:
There is a fix here ( https://gist.github.com/1499226) i f you run into issues with "wai-handler-devel: command not found"
wai-handler-devel's Hackage page says that it should be invoked from the command-line like so:
$ wai-handler-devel <port> My.App.Module myApp
and that your application's type must look like this:
myApp :: (Application -> IO ()) -> IO ()
In this case, you should define myApp as follows:
myApp :: (Application -> IO ()) -> IO ()
myApp handler = handler app
although you may want to inline app entirely:
myApp :: (Application -> IO ()) -> IO ()
myApp handler = handler $ \_ -> return $ responseLBS
status200
[("Content-Type", "text/plain")]
"Hello, World!"
The type is like this so that you can do initialisation on start-up and the like in IO. I suggest reading the SmallApp and FullApp examples from wai-handler-devel's git repository; the latter is especially helpful, as it has debug output showing the flow of the code during a reload, and shows how to integrate a long-running database connection.
The run script for the FullApp example also shows how to use wai-handler-devel programmatically, including manually specifying Hamlet template dependencies (which the wai-handler-devel command-line tool determines automatically).
You should then be able to rewrite your main as follows:
main :: IO ()
main = do
putStrLn $ "http://localhost:8080/"
myApp (run 8080)
Of course, you could just as easily pass the run function from wai-handler-fastcgi, wai-handler-scgi or even wai-handler-webkit.