HORIZON HASKELLDocslts/ghc-9.10.x248f8f02026-10-05Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · 248f8f0 · 2026-10-05

Handler monad

2 declarations
newtypenewtype HandlerFor site a
#

A generic handler monad, which can have a different subsite and master site. We define a newtype for better error message.

Instances15Monad, Functor, Applicative, MonadIO, MonadThrow, PrimMonad, …

Read information from handler

valuegetsYesod :: MonadHandler m => (HandlerSite m -> a) -> m a
#

Get a specific component of the master site application argument. Analogous to the gets function for operating on StateT.

Request information

Request datatype

datadata YesodRequest
#

The parsed request information. This type augments the standard WAI Request with additional information.

Constructors

Convenience functions

valuelanguages :: MonadHandler m => m [Text]
#

Get the list of supported languages supplied by the user.

Languages are determined based on the following (in descending order of preference):

  • The _LANG get parameter.

  • The _LANG user session variable.

  • The _LANG cookie.

  • Accept-Language HTTP header.

Yesod will seek the first language from the returned list matched with languages supporting by your application. This language will be used to render i18n templates. If a matching language is not found the default language will be used.

This is handled by parseWaiRequest (not exposed).

NOTE: Before version 1.6.19.0, this function prioritized the session variable above all other sources.

Lookup parameters

Lookup authentication data

Multi-lookup

Responses

0 declarations

Pure

Streaming

Use a Source for the response body.

Note that, for ease of use, the underlying monad is a HandlerFor. This implies that you can run any HandlerFor action. However, since a streaming response occurs after the response headers have already been sent, some actions make no sense here. For example: short-circuit responses, setting headers, changing status codes, etc.

valuesendFlush :: Monad m => ConduitT i (Flush Builder) m ()
#

In a streaming response, send a flush command, causing all buffered data to be immediately sent to the client.

Redirecting

classclass RedirectUrl master a where
#

Some value which can be turned into a URL for redirects.

Methods

Instances6RedirectUrl
valueredirect :: (MonadHandler m, RedirectUrl (HandlerSite m) url) => url -> m a
#

Redirect to the given route. HTTP status code 303 for HTTP 1.1 clients and 302 for HTTP 1.0 This is the appropriate choice for a get-following-post technique, which should be the usual use case.

If you want direct control of the final status code, or need a different status code, please use redirectWith.

valueredirectToPost
  1. :: (MonadHandler m, RedirectUrl (HandlerSite m) url)
  2. => url
  3. -> m a
#

Redirect to a POST resource.

This is not technically a redirect; instead, it returns an HTML page with a POST form, and some Javascript to automatically submit the form. This can be useful when you need to post a plain link somewhere that needs to cause changes on the server.

datadata Fragment a b
#

Add a fragment identifier to a route to be used when redirecting. For example:

redirect (NewsfeedR :#: storyId)

@since 1.2.9.

Constructors

Instances2RedirectUrl, Show

Errors

valuenotFound :: MonadHandler m => m a
#

Return a 404 not found page. Also denotes no handler available.

Short-circuit responses

Note that since short-circuiting is implemented by using exceptions, using e.g. sendStatusJSON inside a runDB block will result in the database actions getting rolled back:

runDB $ do
  userId <- insert $ User "username" "email@example.com"
  postId <- insert $ BlogPost "title" "hi there!"
    The previous two inserts will be rolled back.
  sendStatusJSON Status.status200 ()
valuesendFile :: MonadHandler m => ContentType -> FilePath -> m a
#

Bypass remaining handler code and output the given file.

For some backends, this is more efficient than reading in the file to memory, since they can optimize file sending via a system call to sendfile.

Type specific response with custom status

valuesendWaiResponse :: MonadHandler m => Response -> m b
#

Send a Response. Please note: this function is rarely necessary, and will disregard any changes to response headers and session that you have already specified. This function short-circuits. It should be considered only for very specific needs. If you are not sure if you need it, you don't.

valuenotModified :: MonadHandler m => m a
#

Send a 304 not modified response immediately. This is a short-circuiting action.

Different representations

4 declarations

HTTP allows content negotation to determine what representation of data you would like to use. The most common example of this is providing both a user-facing HTML page and an API facing JSON response from the same URL. The means of achieving this is the Accept HTTP header, which provides a list of content types the client will accept, sorted by preference.

By using selectRep and provideRep, you can provide a number of different representations, e.g.:

selectRep $ do
  provideRep produceHtmlOutput
  provideRep produceJsonOutput

The first provided representation will be used if no matches are found.

valueprovideRepType
  1. :: (Monad m, ToContent a)
  2. => ContentType
  3. -> m a
  4. -> Writer (Endo [ProvidedRep m]) ()
#

Same as provideRep, but instead of determining the content type from the type of the value itself, you provide the content type separately. This can be a convenience instead of creating newtype wrappers for uncommonly used content types.

provideRepType "application/x-special-format" "This is the content"
datadata ProvidedRep (m :: Type -> Type)
#

Internal representation of a single provided representation.

Setting headers

8 declarations
valuedeleteCookie
  1. :: MonadHandler m
  2. => Text

    key

  3. -> Text

    path

  4. -> m ()
#

Unset the cookie on the client.

Note: although the value used for key and path is Text, you should only use ASCII values to be HTTP compliant.

valueaddHeader :: MonadHandler m => Text -> Text -> m ()
#

Set an arbitrary response header.

Note that, while the data type used here is Text, you must provide only ASCII value to be HTTP compliant.

valuereplaceOrAddHeader :: MonadHandler m => Text -> Text -> m ()
#

Replace an existing header with a new value or add a new header if not present.

Note that, while the data type used here is Text, you must provide only ASCII value to be HTTP compliant.

Content caching and expiration

valuecacheSeconds :: MonadHandler m => Int -> m ()
#

Set the Cache-Control header to indicate this response should be cached for the given number of seconds.

valueneverExpires :: MonadHandler m => m ()
#

Set the Expires header to some date in 2037. In other words, this content is never (realistically) expired.

valuesetEtag :: MonadHandler m => Text -> m ()
#

Check the if-none-match header and, if it matches the given value, return a 304 not modified response. Otherwise, set the etag header to the given value.

Note that it is the responsibility of the caller to ensure that the provided value is a valid etag value, no sanity checking is performed by this function.

valuesetWeakEtag :: MonadHandler m => Text -> m ()
#

Check the if-none-match header and, if it matches the given value, return a 304 not modified response. Otherwise, set the etag header to the given value.

A weak etag is only expected to be semantically identical to the prior content, but doesn't have to be byte-for-byte identical. Therefore it can be useful for dynamically generated content that may be difficult to perform bytewise hashing upon.

Note that it is the responsibility of the caller to ensure that the provided value is a valid etag value, no sanity checking is performed by this function.

Session

8 declarations
valuesetSession
  1. :: MonadHandler m
  2. => Text

    key

  3. -> Text

    value

  4. -> m ()
#

Set a variable in the user's session.

The session is handled by the clientsession package: it sets an encrypted and hashed cookie on the client. This ensures that all data is secure and not tampered with.

Ultimate destination

valuesetUltDestReferer :: MonadHandler m => m ()
#

Sets the ultimate destination to the referer request header, if present.

This function will not overwrite an existing ultdest.

valueredirectUltDest
  1. :: (RedirectUrl (HandlerSite m) url, MonadHandler m)
  2. => url

    default destination if nothing in session

  3. -> m a
#

Redirect to the ultimate destination in the user's session. Clear the value from the session.

The ultimate destination is set with setUltDest.

This function uses redirect, and thus will perform a temporary redirect to a GET request.

Messages

Subsites

4 declarations
newtypenewtype SubHandlerFor sub master a
#

A handler monad for subsite

Instances13Monad, Functor, Applicative, MonadIO, MonadThrow, MonadUnliftIO, …

Helpers for specific content

0 declarations

Hamlet

Misc

Lifting

2 declarations
valuehandlerToIO :: MonadIO m => HandlerFor site (HandlerFor site a -> m a)
#

Returns a function that runs HandlerFor actions inside IO.

Sometimes you want to run an inner HandlerFor action outside the control flow of an HTTP request (on the outer HandlerFor action). For example, you may want to spawn a new thread:

getFooR :: Handler RepHtml
getFooR = do
  runInnerHandler <- handlerToIO
  liftIO $ forkIO $ runInnerHandler $ do
    Code here runs inside HandlerFor but on a new thread.
    This is the inner HandlerFor.
    ...
  Code here runs inside the request's control flow.
  This is the outer HandlerFor.
  ...

Another use case for this function is creating a stream of server-sent events using HandlerFor actions (see yesod-eventsource).

Most of the environment from the outer HandlerFor is preserved on the inner HandlerFor, however:

  • The request body is cleared (otherwise it would be very difficult to prevent huge memory leaks).

  • The cache is cleared (see cached).

Changes to the response made inside the inner HandlerFor are ignored (e.g., session variables, cookies, response headers). This allows the inner HandlerFor to outlive the outer HandlerFor (e.g., on the forkIO example above, a response may be sent to the client without killing the new thread).

i18n

1 declaration

Per-request caching

6 declarations
valuecached :: (MonadHandler m, Typeable a) => m a -> m a
#

Use a per-request cache to avoid performing the same action multiple times. Values are stored by their type, the result of typeOf from Typeable. Therefore, you should use different newtype wrappers at each cache site.

For example, yesod-auth uses an un-exported newtype, CachedMaybeAuth and exports functions that utilize it such as maybeAuth. This means that another module can create its own newtype wrapper to cache the same type from a different action without any cache conflicts.

See the original announcement: http://www.yesodweb.com/blog/2013/03/yesod-1-2-cleaner-internals

valuecachedBy :: (MonadHandler m, Typeable a) => ByteString -> m a -> m a
#

a per-request cache. just like cached. cached can only cache a single value per type. cachedBy stores multiple values per type by usage of a ByteString key

cached is ideal to cache an action that has only one value of a type, such as the session's current user cachedBy is required if the action has parameters and can return multiple values per type. You can turn those parameters into a ByteString cache key. For example, caching a lookup of a Link by a token where multiple token lookups might be performed.

AJAX CSRF protection

0 declarations

When a user has authenticated with your site, all requests made from the browser to your server will include the session information that you use to verify that the user is logged in. Unfortunately, this allows attackers to make unwanted requests on behalf of the user by e.g. submitting an HTTP request to your site when the user visits theirs. This is known as a Cross Site Request Forgery (CSRF) attack.

To combat this attack, you need a way to verify that the request is valid. This is achieved by generating a random string ("token"), storing it in your encrypted session so that the server can look it up (see reqToken), and adding the token to HTTP requests made to your server. When a request comes in, the token in the request is compared to the one from the encrypted session. If they match, you can be sure the request is valid.

Yesod implements this behavior in two ways:

  1. The yesod-form package stores the CSRF token in a hidden field in the form, then validates it with functions like Yesod.Form.Functions.runFormPost.

  2. Yesod can store the CSRF token in a cookie which is accessible by Javascript. Requests made by Javascript can lookup this cookie and add it as a header to requests. The server then checks the token in the header against the one in the encrypted session.

The form-based approach has the advantage of working for users with Javascript disabled, while adding the token to the headers with Javascript allows things like submitting JSON or binary data in AJAX requests. Yesod supports checking for a CSRF token in either the POST parameters of the form (checkCsrfParamNamed), the headers (checkCsrfHeaderNamed), or both options (checkCsrfHeaderOrParam).

The easiest way to check both sources is to add the defaultCsrfMiddleware to your Yesod Middleware.

Opting-out of CSRF checking for specific routes

(Note: this code is generic to opting out of any Yesod middleware)

yesodMiddleware app = do
  maybeRoute <- getCurrentRoute
  let dontCheckCsrf = case maybeRoute of
                        Just HomeR                     -> True  -- Don't check HomeR
                        Nothing                        -> True  -- Don't check for 404s
                        _                              -> False -- Check other routes

  defaultYesodMiddleware $ defaultCsrfSetCookieMiddleware $ (if dontCheckCsrf then id else defaultCsrfCheckMiddleware) $ app

This can also be implemented using the csrfCheckMiddleware function.

Setting CSRF Cookies

Takes a SetCookie and overrides its value with a CSRF token, then sets the cookie.

Make sure to set the setCookiePath to the root path of your application, otherwise you'll generate a new CSRF token for every path of your app. If your app is run from from e.g. www.example.com/app1, use app1. The vast majority of sites will just use /.

Looking up CSRF Headers

Looking up CSRF POST Parameters

Checking CSRF Headers or POST Parameters