snap-0.12.0: Top-level package for the Snap Web Framework

Safe Haskell | None

Snap.Snaplet

Contents

- Snaplet
- Lenses
- Snaplet Helper Functions
- MonadSnaplet
- Snaplet state manipulation
- Initializer
- Routes
- Handlers
- Serving Applications
- Snaplet Lenses

Description

Snaplets allow you to build web applications out of composable parts. This allows you to build self-contained units and glue them together to make your overall application.

A snaplet has a few moving parts, some user-defined and some provided by the snaplet API:

- each snaplet has its own configuration given to it at startup.
- each snaplet is given its own directory on the filesystem, from which it reads its configuration and in which it can store files.
- each snaplet comes with an `Initializer` which defines how to create an instance of the Snaplet at startup. The initializer decides how to interpret the snaplet configuration, which URLs to handle (and how), sets up the initial snaplet state, tells the snaplet runtime system how to clean the snaplet up, etc.
- each snaplet contains some user-defined in-memory state; for instance, a snaplet that talks to a database might contain a reference to a connection pool. The snaplet state is an ordinary Haskell record, with a datatype defined by the snaplet author. The initial state record is created during initialization and is available to snaplet `Handler`s when serving HTTP requests.

NOTE: This documentation is written as a prose tutorial of the snaplets API. Don't be scared by the fact that it's auto-generated and is filled with type signatures. Just keep reading.

Synopsis

- data Snaplet s
- data SnapletConfig
- snapletConfig :: forall s. Lens' (Snaplet s) SnapletConfig
- snapletValue :: forall s. Lens' (Snaplet s) s
- subSnaplet :: SnapletLens a b -> SnapletLens (Snaplet a) b
- class MonadSnaplet m where
  - with :: SnapletLens v v' -> m b v' a -> m b v a
  - withTop :: SnapletLens b v' -> m b v' a -> m b v a
  - with' :: SnapletLens (Snaplet v) v' -> m b v' a -> m b v a
  - withTop' :: SnapletLens (Snaplet b) v' -> m b v' a -> m b v a
  - getLens :: m b v (SnapletLens (Snaplet b) v)
  - getOpaqueConfig :: m b v SnapletConfig
- getSnapletAncestry :: (Monad (m b v), MonadSnaplet m) => m b v [Text]
- getSnapletFilePath :: (Monad (m b v), MonadSnaplet m) => m b v FilePath
- getSnapletName :: (Monad (m b v), MonadSnaplet m) => m b v (Maybe Text)
- getSnapletDescription :: (Monad (m b v), MonadSnaplet m) => m b v Text
- getSnapletUserConfig :: (Monad (m b v), MonadSnaplet m) => m b v Config
- getSnapletRootURL :: (Monad (m b v), MonadSnaplet m) => m b v ByteString
- snapletURL :: (Monad (m b v), MonadSnaplet m) => ByteString -> m b v ByteString
- getRoutePattern :: Handler b v (Maybe ByteString)
- setRoutePattern :: ByteString -> Handler b v ()
- getSnapletState :: Handler b v (Snaplet v)
- putSnapletState :: Snaplet v -> Handler b v ()
- modifySnapletState :: (Snaplet v -> Snaplet v) -> Handler b v ()
- getsSnapletState :: (Snaplet v -> b) -> Handler b1 v b
- data Initializer b v a
- data SnapletInit b v
- makeSnaplet :: Text -> Text -> Maybe (IO FilePath) -> Initializer b v v -> SnapletInit b v
- nestSnaplet :: ByteString -> SnapletLens v v1 -> SnapletInit b v1 -> Initializer b v (Snaplet v1)
- embedSnaplet :: ByteString -> SnapletLens v v1 -> SnapletInit v1 v1 -> Initializer b v (Snaplet v1)
- nameSnaplet :: Text -> SnapletInit b v -> SnapletInit b v
- onUnload :: IO () -> Initializer b v ()
- addPostInitHook :: (v -> EitherT Text IO v) -> Initializer b v ()
- addPostInitHookBase :: (Snaplet b -> EitherT Text IO (Snaplet b)) -> Initializer b v ()
- printInfo :: Text -> Initializer b v ()
- getRoutes :: Initializer b v [ByteString]
- addRoutes :: [(ByteString, Handler b v ())] -> Initializer b v ()
- wrapSite :: (Handler b v () -> Handler b v ()) -> Initializer b v ()
- data Handler b v a
- failIfNotLocal :: MonadSnap m => m b -> m b
- reloadSite :: Handler b v ()
- modifyMaster :: v -> Handler b v ()
- bracketHandler :: IO a -> (a -> IO x) -> (a -> Handler b v c) -> Handler b v c
- runSnaplet :: Maybe String -> SnapletInit b b -> IO (Text, Snap (), IO ())
- combineConfig :: Config Snap a -> Snap () -> IO (Config Snap a, Snap ())
- serveSnaplet :: Config Snap AppConfig -> SnapletInit b b -> IO ()
- loadAppConfig :: FileName -> FilePath -> IO Config
- type SnapletLens s a = ALens' s (Snaplet a)

# Snaplet

The heart of the snaplets infrastructure is state management. (Note: when we say "state" here, we mean in-memory Haskell objects, not external data storage or databases; how you deal with persisted data is up to you.) Most nontrivial pieces of a web application need some kind of runtime state or environment data. The datatype we use to handle this is called `Snaplet`:

data Snaplet s

Snaplet's type parameter `s` here is user-defined and can be any Haskell type. A value of type `Snaplet s` countains a couple of things:

- a value of type `s`, called the "user state".
- some bookkeeping data the framework uses to plug things together, like the snaplet's configuration, the snaplet's root directory on the filesystem, the snaplet's root URL, and so on.

data SnapletConfig

An opaque data type holding internal snaplet configuration data. It is exported publicly because the getOpaqueConfig function in MonadSnaplet makes implementing new instances of MonadSnaplet more convenient.

# Lenses

In the example above, the `Foo` snaplet has to be written to work with any base state (otherwise it wouldn't be reusable!), but functions written to work with the `Foo` snaplet want to be able to modify the `Foo` record within the context of the base state. Given that Haskell datatypes are pure, how do you allow for this?

Our solution is to use lenses, as defined in Edward Kmett's `lens` library (http://hackage.haskell.org/package/lens). A lens, notated as follows:

     SimpleLens a b

is conceptually a "getter" and a "setter" rolled up into one. The `lens` library provides the following functions:

     view :: (SimpleLens a b) -> a -> b
     set  :: (SimpleLens a b) -> b -> a -> a
     over :: (SimpleLens a b) -> (b -> b) -> a -> a

which allow you to get, set, and modify a value of type `b` within the context of type `a`. The `lens` package comes with a Template Haskell function called `makeLenses`, which auto-magically defines a lens for every record field having a name beginning with an underscore. In the `App` example above, adding the declaration:

     makeLenses ''App

would define lenses:

     foo                :: SimpleLens App (Snaplet Foo)
     bar                :: SimpleLens App (Snaplet Bar)
     someNonSnapletData :: SimpleLens App String

The coolest thing about `lens` lenses is that they compose using the `(.)` operator. If the `Foo` type had a field of type `Quux` within it with a lens `quux :: SimpleLens Foo Quux`, then you could create a lens of type `SimpleLens App Quux` by composition:

     import Control.Lens

     data Foo = Foo { _quux :: Quux }
     makeLenses ''Foo

     -- snapletValue is defined in the framework:
     snapletValue :: SimpleLens (Snaplet a) a

     appQuuxLens :: SimpleLens App Quux
     appQuuxLens = foo . snapletValue . quux

Lens composition is very similar to function composition except it works in the opposite direction (think Java-style System.out.println ordering) and it gives you a composed getter and setter at the same time.

# Snaplet Helper Functions

Your web application will itself get wrapped in a `Snaplet`, and the top-level user state of your application (which will likely contain other snaplets nested inside it) will look something like this:

     data App = App
         { _foo                :: Snaplet Foo
         , _bar                :: Snaplet Bar
         , _someNonSnapletData :: String
         }

Every web application using snaplets has a top-most user state which contains all of the application state; we call this state the "base" state.

We export several helper lenses for working with Snaplet types.

snapletConfig :: forall s. Lens' (Snaplet s) SnapletConfig

snapletValue :: forall s. Lens' (Snaplet s) s

subSnaplet :: SnapletLens a b -> SnapletLens (Snaplet a) b

Transforms a lens of the type you get from makeLenses to an similar lens that is more suitable for internal use.

# MonadSnaplet

The primary abstraction in the snaplet infrastructure is a combination of the reader and state monads. The state monad holds the top level application data type (from now on referred to as the base state). The reader monad holds a lens from the base state to the current snaplet's state. This allows quux snaplet functions to access and modify the Quux data structure without knowing anything about the App or Foo data structures. It also lets other snaplets call functions from the quux snaplet if they have the quux snaplet's lens `SimpleLens App (Snaplet Quux)`. We can view our application as a tree of snaplets and other pieces of data. The lenses are like pointers to nodes of the tree. If you have a pointer to a node, you can access the node and all of its children without knowing anything about the rest of the tree.

Several monads use this infrastructure. These monads need at least three type parameters. Two for the lens type, and the standard 'a' denoting the monad return value. You will usually see this written in type signatures as "m b v a" or some variation. The 'm' is the type variable of the MonadSnaplet type class. 'b' is the base state, and 'v' is the state of the current "view" snaplet (or simply, current state).

The MonadSnaplet type class distills the essence of the operations used with this pattern. Its functions define fundamental methods for navigating snaplet trees.

class MonadSnaplet m where

The m type parameter used in the MonadSnaplet type signatures will usually be either Initializer or Handler, but other monads may sometimes be useful.

Minimal complete definition:

- `withTop'`, `with'`, `getLens`, and `getOpaqueConfig`.

Methods

with

Arguments

:: SnapletLens v v' | A relative lens identifying a snaplet
-> m b v' a | Action from the lense's snaplet
-> m b v a |

Runs a child snaplet action in the current snaplet's context. If you think about snaplet lenses using a filesystem path metaphor, the lens supplied to this snaplet must be a relative path. In other words, the lens's base state must be the same as the current snaplet.

withTop

Arguments

:: SnapletLens b v' | An "absolute" lens identifying a snaplet
-> m b v' a | Action from the lense's snaplet
-> m b v a |

Like `with` but doesn't impose the requirement that the action being run be a descendant of the current snaplet. Using our filesystem metaphor again, the lens for this function must be an absolute path--it's base must be the same as the current base.

with' :: SnapletLens (Snaplet v) v' -> m b v' a -> m b v a

A variant of `with` accepting a lens from snaplet to snaplet. Unlike the lens used in the above `with` function, this lens formulation has an identity, which makes it useful in certain circumstances. The lenses generated by `makeLenses` will not work with this function, however the lens returned by `getLens` will.

    with = with' . subSnaplet

withTop' :: SnapletLens (Snaplet b) v' -> m b v' a -> m b v a

The absolute version of `with'`

getLens :: m b v (SnapletLens (Snaplet b) v)

Gets the lens for the current snaplet.

getOpaqueConfig :: m b v SnapletConfig

Gets the current snaplet's opaque config data type. You'll only use this function when writing MonadSnaplet instances.

Instances

MonadSnaplet Initializer |
MonadSnaplet Handler |

getSnapletAncestry :: (Monad (m b v), MonadSnaplet m) => m b v [Text]

Gets a list of the names of snaplets that are direct ancestors of the current snaplet.

getSnapletFilePath :: (Monad (m b v), MonadSnaplet m) => m b v FilePath

Gets the snaplet's path on the filesystem.

getSnapletName :: (Monad (m b v), MonadSnaplet m) => m b v (Maybe Text)

Gets the current snaple's name.

getSnapletDescription :: (Monad (m b v), MonadSnaplet m) => m b v Text

Gets a human readable description of the snaplet.

getSnapletUserConfig :: (Monad (m b v), MonadSnaplet m) => m b v Config

Gets the config data structure for the current snaplet.

getSnapletRootURL :: (Monad (m b v), MonadSnaplet m) => m b v ByteString

Gets the base URL for the current snaplet. Directories get added to the current snaplet path by calls to `nestSnaplet`.

snapletURL :: (Monad (m b v), MonadSnaplet m) => ByteString -> m b v ByteString

Constructs a url relative to the current snaplet.

getRoutePattern :: Handler b v (Maybe ByteString)

Gets the route pattern that matched for the handler. This lets you find out exactly which of the strings you used in addRoutes matched.

setRoutePattern :: ByteString -> Handler b v ()

Sets the route pattern that matched for the handler. Use this when to override the default pattern which is the key to the alist passed to addRoutes.

# Snaplet state manipulation

MonadSnaplet instances will typically have `MonadState v` instances. We provide the following convenience functions which give the equivalent to `MonadState (Snaplet v)` for the less common cases where you need to work with the Snaplet wrapper.

getSnapletState :: Handler b v (Snaplet v)

Gets the `Snaplet v` from the current snaplet's state.

putSnapletState :: Snaplet v -> Handler b v ()

Puts a new `Snaplet v` in the current snaplet's state.

modifySnapletState :: (Snaplet v -> Snaplet v) -> Handler b v ()

Modifies the `Snaplet v` in the current snaplet's state.

getsSnapletState :: (Snaplet v -> b) -> Handler b1 v b

Gets the `Snaplet v` from the current snaplet's state and applies a function to it.

# Initializer

The Initializer monad is where your application's initialization happens. Initializers are run at startup and any time a site reload is triggered. The Initializer's job is to construct a snaplet's routes and initial state, set up filesystem data, read config files, etc.

In order to initialize its state, a snaplet needs to initialize all the `Snaplet a` state for each of its subsnaplets. The only way to construct a `Snaplet a` type is by calling `nestSnaplet` or `embedSnaplet` from within an initializer.

data Initializer b v a

Monad used for initializing snaplets.

Instances

MonadSnaplet Initializer |
Monad (Initializer b v) |
Functor (Initializer b v) |
Applicative (Initializer b v) |
MonadIO (Initializer b v) |

data SnapletInit b v

Opaque newtype which gives us compile-time guarantees that the user is using makeSnaplet and either nestSnaplet or embedSnaplet correctly.

makeSnaplet

Arguments

:: Text | A default id for this snaplet. This is only used when the end-user has not already set an id using the nameSnaplet function.
-> Text | A human readable description of this snaplet.

-> Maybe (IO FilePath)

The path to the directory holding the snaplet's reference filesystem content. This will almost always be the directory returned by Cabal's getDataDir command, but it has to be passed in because it is defined in a package-specific import. Setting this value to Nothing doesn't preclude the snaplet from having files in in the filesystem, it just means that they won't be copied there automatically.

-> Initializer b v v | Snaplet initializer.
-> SnapletInit b v |

All snaplet initializers must be wrapped in a call to `makeSnaplet`, which handles standardized housekeeping common to all snaplets. Common usage will look something like this:

     fooInit :: SnapletInit b Foo
     fooInit = makeSnaplet "foo" "An example snaplet" Nothing $ do
         -- Your initializer code here
         return $ Foo 42

Note that you're writing your initializer code in the Initializer monad, and makeSnaplet converts it into an opaque SnapletInit type. This allows us to use the type system to ensure that the API is used correctly.

nestSnaplet

Arguments

:: ByteString | The root url for all the snaplet's routes. An empty string gives the routes the same root as the parent snaplet's routes.
-> SnapletLens v v1 | Lens identifying the snaplet
-> SnapletInit b v1 | The initializer function for the subsnaplet.
-> Initializer b v (Snaplet v1) |

Runs another snaplet's initializer and returns the initialized Snaplet value. Calling an initializer with nestSnaplet gives the nested snaplet access to the same base state that the current snaplet has. This makes it possible for the child snaplet to make use of functionality provided by sibling snaplets.

embedSnaplet

Arguments

:: ByteString | The root url for all the snaplet's routes. An empty string gives the routes the same root as the parent snaplet's routes. NOTE: Because of the stronger isolation provided by embedSnaplet, you should be more careful about using an empty string here.
-> SnapletLens v v1 | Lens identifying the snaplet
-> SnapletInit v1 v1 | The initializer function for the subsnaplet.
-> Initializer b v (Snaplet v1) |

Runs another snaplet's initializer and returns the initialized Snaplet value. The difference between this and nestSnaplet is the first type parameter in the third argument. The "v1 v1" makes the child snaplet think that it is top-level, which means that it will not be able to use functionality provided by snaplets included above it in the snaplet tree. This strongly isolates the child snaplet, and allows you to eliminate the b type variable. The embedded snaplet can still get functionality from other snaplets, but only if it nests or embeds the snaplet itself.

nameSnaplet

Arguments

:: Text | The snaplet name
-> SnapletInit b v | The snaplet initializer function
-> SnapletInit b v |

Sets a snaplet's name. All snaplets have a default name set by the snaplet author. This function allows you to override that name. You will have to do this if you have more than one instance of the same kind of snaplet because snaplet names must be unique. This function must immediately surround the snaplet's initializer. For example:

    fooState <- nestSnaplet "fooA" $ nameSnaplet "myFoo" $ fooInit

onUnload :: IO () -> Initializer b v ()

Attaches an unload handler to the snaplet. The unload handler will be called when the server shuts down, or is reloaded.

addPostInitHook :: (v -> EitherT Text IO v) -> Initializer b v ()

Adds an IO action that modifies the current snaplet state to be run at the end of initialization on the state that was created. This makes it easier to allow one snaplet's state to be modified by another snaplet's initializer. A good example of this is when a snaplet has templates that define its views. The Heist snaplet provides the `addTemplates` function which allows other snaplets to set up their own templates. `addTemplates` is implemented using this function.

addPostInitHookBase :: (Snaplet b -> EitherT Text IO (Snaplet b)) -> Initializer b v ()

Variant of addPostInitHook for when you have things wrapped in a Snaplet.

printInfo :: Text -> Initializer b v ()

Initializers should use this function for all informational or error messages to be displayed to the user. On application startup they will be sent to the console. When executed from the reloader, they will be sent back to the user in the HTTP response.

getRoutes :: Initializer b v [ByteString]

Lets you retrieve the list of routes currently set up by an Initializer. This can be useful in debugging.

# Routes

Snaplet initializers are also responsible for setting up any routes defined by the snaplet. To do that you'll usually use either `addRoutes` or `wrapSite`.

addRoutes :: [(ByteString, Handler b v ())] -> Initializer b v ()

Adds routing to the current `Handler`. The new routes are merged with the main routing section and take precedence over existing routing that was previously defined.

wrapSite

Arguments

:: (Handler b v () -> Handler b v ()) | Handler modifier function
-> Initializer b v () |

Wraps the base snaplet's routing in another handler, allowing you to run code before and after all routes in an application.

Here are some examples of things you might do:

     wrapSite (\site -> logHandlerStart >> site >> logHandlerFinished)
     wrapSite (\site -> ensureAdminUser >> site)

# Handlers

data Handler b v a

Snaplet infrastructure is available during runtime request processing through the Handler monad. There aren't very many standalone functions to read about here, but this is deceptive. The key is in the type class instances. Handler is an instance of `MonadSnap`, which means it is the monad you will use to write all your application routes. It also has a `MonadSnaplet` instance, which gives you all the functionality described above.

Instances

MonadSnaplet Handler |
MonadReader v (Handler b v) | The MonadState instance gives you access to the current snaplet's state.
MonadState v (Handler b v) | The MonadState instance gives you access to the current snaplet's state.
Monad (Handler b v) |
Functor (Handler b v) |
MonadPlus (Handler b v) |
Applicative (Handler b v) |
MonadCatchIO (Handler b v) |
Alternative (Handler b v) |
MonadIO (Handler b v) |
MonadSnap (Handler b v) |

failIfNotLocal :: MonadSnap m => m b -> m b

Pass if the request is not coming from localhost.

reloadSite :: Handler b v ()

Handler that reloads the site.

modifyMaster :: v -> Handler b v ()

Lets you change a snaplet's initial state. It's alomst like a reload, except that it doesn't run the initializer. It just modifies the result of the initializer. This can be used to let you define actions for reloading individual snaplets.

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

This function brackets a Handler action in resource acquisition and release. Like `bracketSnap`, this is provided because MonadCatchIO's `bracket` function doesn't work properly in the case of a short-circuit return from the action being bracketed.

In order to prevent confusion regarding the effects of the aquisition and release actions on the Handler state, this function doesn't accept Handler actions for the acquire or release actions.

This function will run the release action in all cases where the acquire action succeeded. This includes the following behaviors from the bracketed Snap action.

1. Normal completion
2. Short-circuit completion, either from calling `fail` or `finishWith`
3. An exception being thrown.

# Serving Applications

runSnaplet :: Maybe String -> SnapletInit b b -> IO (Text, Snap (), IO ())

Given an environment and a Snaplet initializer, produce a concatenated log of all messages generated during initialization, a snap handler, and a cleanup action. The environment is an arbitrary string such as "devel" or "production". This string is used to determine the name of the configuration files used by each snaplet. If an environment of Nothing is used, then runSnaplet defaults to "devel".

combineConfig :: Config Snap a -> Snap () -> IO (Config Snap a, Snap ())

Given a configuration and a snap handler, complete it and produce the completed configuration as well as a new toplevel handler with things like compression and a 500 handler set up.

serveSnaplet

Arguments

:: Config Snap AppConfig | The configuration of the server - you can usually pass a default `Config` via `defaultConfig`.
-> SnapletInit b b | The snaplet initializer function.
-> IO () |

Initialize and run a Snaplet. This function parses command-line arguments, runs the given Snaplet initializer, and starts an HTTP server running the Snaplet's toplevel `Handler`.

loadAppConfig

Arguments

:: FileName | The name of the config file to look for. In snap applications, this is something based on the environment...i.e. `devel.cfg`.
-> FilePath | Path to the root directory of your project.
-> IO Config |

Allows you to get all of your app's config data in the IO monad without the web server infrastructure.

# Snaplet Lenses

type SnapletLens s a = ALens' s (Snaplet a)