The Snap team would like to announce the release of version 0.8 of the Snap Framework.

NOTE: If you use the heist snaplet, then 0.8 will break your application. It will still compile, but it won’t work. See below for details.

New features / improvements

Running behind proxy servers

Added a new Snap.Util.Proxy module that will rewrite the rqRemoteAddr field in an incoming Request based on parsing the value of the X-Forwarded-For header. Support for this has also been integrated into snap-server’s basic command-line argument processing, so, assuming your proxy server uses the standard X-Forwarded-For header, you can tell snap-server-enabled applications to do proxy address translation by running the binary with the “--proxy=X_Forwarded_For” flag.

Timeout handling

Timeout handling has been tweaked slightly in Snap 0.8; the Snap monad contained a function to return an action to set the timeout:

getTimeoutAction :: MonadSnap m => m (Int -> IO ())

Several users, however, wanted support for timer extension semantics. Snap now exports a new function:

getTimeoutModifier :: MonadSnap m => m ((Int -> Int) -> IO ())

Accordingly, there are new convenience functions to go along with setTimeout:

extendTimeout :: MonadSnap m => Int -> m ()
modifyTimeout :: MonadSnap m => (Int -> Int) -> m ()

Note that this has also caused a breaking change to the escapeHttp function, which hopefully only Jasper should care about :-)

Separate maps for form parameters and query parameters

A bug from the stone age, Snap now separates out form parameters from query parameters. Request objects now contain rqQueryParams and rqPostParams Params objects, with the old rqParams now being the union of the two. Both rqQueryParams and rqPostParams are immutable.

Improvements to Snap.Test

The Snap.Test test-harness module was updated with some new functionality:

  • RequestBuilder wasn’t setting rqContextPath properly – the docs say that rqContextPath must begin and end with a slash.

  • Exposed the runHandlerM function to run a Snap handler from an arbitrary monad transformer over Snap.

  • Added new evalHandler and evalHandlerM functions to get the monadic value produced by a Snap action.

Improvement to the stock 404 page

The fallthrough 404 page (which users should always override for their own sites) has been extended to report the rqURI for the request.

Changes to buffering semantics

For various reasons (mostly due to shortcomings in the various Haskell zlib libraries that, up until recently, made it difficult to pass Z_SYNC_FLUSH through to the zlib library), Snap would sometimes cause certain kinds long-polling/interactive applications to stall because there was no reliable way to indicate that you wanted to flush the output buffer.

While that’s still the case, Michael Snoyman has fixed the underlying issue in zlib-bindings, the functionality just has to be exposed through zlib-enum. Until then, we’ve added a field to Response allowing users to turn off buffering altogether, and added some functions to modify it:

getBufferingMode :: Response -> Bool
setBufferingMode :: Bool        -- ^ if True, buffer the output, if False, send
                                -- output immediately
                 -> Response
                 -> Response

Obviously, removing buffering might kill performance, so it’s only recommended to use this if you’re doing something like Comet where you care about sending some output immediately but holding the output open to write more later.

Turn TCP_NODELAY on for all of our sockets

We do our own buffering, so the Nagle algorithm only serves to potentially delay our packets.

Snaplets: add bracketHandler function

Added the following function to snap:

bracketHandler :: IO a -> (a -> IO x) -> (a -> Handler b v c) -> Handler b v c

Heist: expose evalHeistT

Heist 0.8.0 now exposes evalHeistT:

evalHeistT :: Monad m => HeistT m a -> X.Node -> HeistState m -> m a

Heist: support for binding JSON values

Added the bindJson splice:

------------------------------------------------------------------------------
-- | This splice binds convenience tags for the given JSON (or
-- JSON-convertible) value and runs the tag's child nodes using the new
-- bindings.
--
-- /Tags bound when you pass in an object/
--
-- Tags bound for an object looking like this:
--
-- > { "k_1": v_1, ..., "k_N": v_N }
--
-- @\<value:{k_i}\>@    -- treats v_i as text
-- @\<snippet:{k_i}\>@  -- treats v_i as HTML
-- @\<with:{k_i}\>@     -- explodes v_i and runs its children
--
-- @\<value var=\"foo.bar.baz\"\/>@ -- walks the JSON tree to find
-- \"foo.bar.baz\", and interprets it as a string
-- @\<snippet var=\"foo.bar.baz\"\/\>@
-- @\<with var=\"foo.bar.baz\"\>...\<with\>@
--
-- /Tags bound when you pass in anything else/
--
-- @\<value\/\>@    --  the given JSON value, as a string
-- @\<snippet\/\>@  --  the given JSON value, parsed and spliced in as HTML
--
bindJson :: (ToJSON a, Monad m) => a -> Splice m

This should make it easy to use JSON values (or JSON-convertible) in heist templates.

Bugfixes

Route capture

Capture variables in routes no longer capture the empty string. Formerly, the following code would match the input url “/foo/”:

handler :: Snap ()
handler = route [ ("foo/:id", fooHandler) ]

Users found that this violated the principle of least surprise (especially since the capture handler might supersede the handler for “foo/”), so the semantics have been changed.

Fixed bug #121, in which Snap was emitting wrong links in a directory index under some circumstances.

Fix the way snap intercalates headers

The HTTP standard states that if the same header appears in a request or response multiple times, they may be treated as if they were one header intercalated with a comma. We were improperly intercalating with a space before.

Libev backend: setting a short timeout now works

Fixed a bug in snap-server’s libev backend, in which timeouts could only be extended, never shortened.

SSL bug re: cleaning up after initialization

We were not correctly cleaning up the socket if SSL handshaking failed. Also, changed from bidirectional OpenSSL shutdown to unidirectional.

Fix bug in http header size limiting

Snap will now read no more than 256kB of HTTP headers information.

Snaplets no longer depend on the value of $CWD after initialization

Snaplets were relying on the value of the current working directory after initialization (most notably on reload) – snaplets now store fully-qualified pathnames internally.

Fix embedSnaplet routing bug

Fixed a bug in embedSnaplet that caused routes to be prepended with one too many prefixes.

Snaplet initialization cleanup

Exceptions thrown by Snaplet initializers are now caught properly, and unload actions that were registered before the exception was thrown are now executed.

Heist snaplet filepath change

The Heist snaplet used to look for templates in the user-specified directory relative to the project root. This yielded incorrect behavior when using the snaplet somewhere other than at the top level. Now the Heist snaplet looks in it’s correct root in the filesystem: snaplets/heist. If your application has this code

hs <- nestSnaplet "" heist $ heistInit "templates"

…in 0.7 your templates would have been in templates. In 0.8 they should reside in snaplets/heist/templates.

New semantics for addTemplatesAt

The URLs generated by addTemplatesAt are now (correctly) relative to the snaplet’s root URL. If you use this function or the addTemplates function to manually add templates that were not loaded by heistInit, you will have to change your code.

Heist: fix small bug with $-expansion in attributes

Fixed Heist bug #16, in which a bare “$” in an html attribute would incorrectly trigger variable expansion.

Heist: fix stack overflow when using attribute substitutions

Fixed Heist bug #13, in which using an attribute substitution inside an apply tag led to a stack overflow.

Dependency changes

Snap has been updated to the newest version of too many dependencies to count.