views:

118

answers:

2

Server-side sessions are not [yet] part of the Snap Framework. Is there a way to add some sort of server side state?

Let's pretend I want to increment a counter for each HTTP request. How would I do it?

+1  A: 

Easiest way is to put the state behind an mvar:

fooHandler :: MVar Int -> Snap ()
fooHandler mvar = do
    x <- liftIO $ modifyMVar mvar $ \y -> let y'=y+1 in (y',y')
    writeBS $ S.pack $ "Incremented counter to: " ++ show x

Initialize the mvar when the site is initialized. Hope this helps.

how_gauche
Watch out, it's likely that you'll need to write `\y -> let y'=y+1 in y' \`seq\` (y',y')` instead. Otherwise, you may be storing unevaluated thunks in your mvar instead of values. Or use strict mvars from the [strict-concurrency](http://hackage.haskell.org/package/strict-concurrency) package.
Yitz
Given the signature of modifyMVar is `modifyMVar :: MVar a -> (a -> IO (a, b)) -> IO b`, it's rather `modifyMVar mvar $ \y -> let y' = y+1 in y' ``seq`` return (y',y')` right?
gawi
gawi: You are questioning if the `modifyMVar ...` code is type-correct? Just manually resolve the types. You already said `modifyMVar :: MVar a -> (a -> IO (a, b)) -> IO b` so it better be true that `mvar :: MVar a`, and we know this from fooHandler (`mvar :: MVar Int`, so `a ~ Int` and the lambda `(\y -> let y' ... :: TypeOf y in y' \`seq\` return (y',y') :: a -> IO (a,b)`. So `(y',y')` forces `a` and `b` to be the same. Using hoogle you should know `seq :: a -> b -> b` where `b` is `return (y',y') :: IO (a,a)` so that's correct. The `a` and `Int` unifies and things look good.
TomMD
@TomMD Thanks for the Hindley-Milner walkthrough.
gawi
@gawi The fact that you know Hindley-Milner seem a kind indication my comment was unneeded. Oh well ;-)
TomMD
@TomMD Your comment may be helpful for anybody on StackOverflow. I usually don't do HM in my head often enough. I'm a bit lazy and I let GHC point out type errors and then I try to guess: "GHC is expecting an `IO`, so let's try to put a `return` here..."
gawi
+4  A: 

The above answer is correct as far as it goes, but it doesn't deal with some real issues.

First up is server restarts. If your storage is more than caching, it needs to be durable across server restarts.

Second is code reloading. Future versions of Snap, starting with 0.3 (probably due in early December) will have dynamic code reloading in development use. This is a huge advantage in terms of development speed, but it makes server-local state an interesting mental exercise. If the programmer changes the type/initialization/whatever of the server-local state, it needs to be re-initialized. There are some tremendous engineering challenges there.

When I was writing the dynamic-reloading code for 0.3, I struggled with that issue for for a while. Then I looked at other platforms. PHP? Stores everything externally (database, memcache, whatever). No cross-request storage in-memory at all. Ruby on Rails? Same.

When combined with the challenges inherent in the first issue, I came to the conclusion that the server should be stateless, aside from possible caching optimizations. Leave durability concerns for libraries/external processes that are designed for it.

So I designed the common interface used by the production and development loaders (one uses static loading, the other dynamic loading) to take 3 functions: An initialization function, a cleanup function, and a handler that uses the state returned by the initialization function. In production mode, that compiles down to calling initialize at server startup, and cleanup at server shutdown. In development mode, it compiles down to: for each request, dynamically load all 3, then run init, handler, cleanup. Obviously, no state will survive cross-request that way.

And then my answer becomes: Do your cross-request storage via some mechanism with built-in durability, and have the server state just be the interface to that. Use something like happstack-state or sqlite if you want to work in-process, or a database or some other external store if you want to work outside the local process.

Just as an added note, managing "global" resources like a connection pool or the like is also far easier in Snap 0.3, due to the addition of the MonadSnap interface.

Carl
I'm curious to read more about dynamic-code reloading. I understand that it implies a lifecycle to restore the server state between restarts, but does it also target to have multiple versions running at the same time (à la Erlang)? Does it involve the dynamic linking loader (dlopen)? I guess I will follow the Snap blog closely.
gawi
The dynamic reloading is currently done entirely with the ghc api. It's not very erlang-esque in any way. It's much closer to ruby or php, just re-interpreting the actions as needed on every request. (Well, some optimizations are in place to make it less often than that, but it's basically every request.)
Carl