Network.Wai.Middleware.Rewrite

wai-extra-3.0.19.1: Provides some basic WAI handlers and middleware.

Safe Haskell | None
Language | Haskell2010

Network.Wai.Middleware.Rewrite

Contents

- How to use this module
  - A note on semantics
  - Paths and Queries
  - An example rewriting paths with queries
  - Upgrading from wai-extra ≤ 3.0.16.1
- `Middleware`
  - Recommended functions
  - Deprecated
- Operating on `Request`s

Synopsis

- type PathsAndQueries = ([Text], Query)
- rewriteWithQueries :: (PathsAndQueries -> RequestHeaders -> IO PathsAndQueries) -> Middleware
- rewritePureWithQueries :: (PathsAndQueries -> RequestHeaders -> PathsAndQueries) -> Middleware
- rewrite :: ([Text] -> RequestHeaders -> IO [Text]) -> Middleware
- rewritePure :: ([Text] -> RequestHeaders -> [Text]) -> Middleware
- rewriteRequest :: (PathsAndQueries -> RequestHeaders -> IO PathsAndQueries) -> Request -> IO Request
- rewriteRequestPure :: (PathsAndQueries -> RequestHeaders -> PathsAndQueries) -> Request -> Request

# How to use this module

This module provides `Middleware` to rewrite URL paths. It also provides functions that will convert a `Request` to a modified `Request`. Both operations require a function that takes URL parameters and headers, and returns new URL parameters. Parameters are pieces of URL paths and query parameters.

If you are a new user of the library, use `rewriteWithQueries` or `rewritePureWithQueries` for middleware. For modifying `Request`s directly, use `rewriteRequest` or `rewriteRequestPure`.

## A note on semantics

Versions of this library in wai-extra ≤ 3.0.16.1 exported only `rewrite` and `rewritePure` and both modified `rawPathInfo` of the underlying requests. Such modification has been proscribed. The semantics of these functions have not changed; instead the recommended approach is to use `rewriteWithQueries` and `rewritePureWithQueries`. The new functions are slightly different, as described in the section on upgrading; code for previous library versions can be upgraded with a single change, and as the type of the new function is different the compiler will indicate where this change must be made.

The `rewriteRequest` and `rewriteRequestPure` functions use the new semantics, too.

## Paths and Queries

This library defines the type synonym `PathsAndQueries` to make code handling paths and queries easier to read.

e.g. /foo/bar would look like

    ["foo", "bar"] :: Text

?bar=baz would look like

    [("bar", Just "baz")] :: QueryText

Together,

/foo?bar=baz would look like

    (["foo"],[("bar", Just "baz")]) :: PathsAndQueries

type PathsAndQueries = ([Text], Query) #

A tuple of the path sections as [`Text`] and query parameters as `Query`. This makes writing type signatures for the conversion function far more pleasant.

Note that this uses `Query` not `QueryText` to more accurately reflect the paramaters that can be supplied in URLs. It may be safe to treat parameters as text; use the `queryToQueryText` and `queryTextToQuery` functions to interconvert.

## An example rewriting paths with queries

Let’s say we want to replace a website written in PHP with one written using WAI. We’ll use the http-reverse-proxy package to serve the old site from the new site, but there’s a problem. The old site uses pages like

    index.php?page=page

whereas the new site would look like

    index/page

In doing this, we want to separate the migration code from our new website. So we’d like to handle links internally using the path formulation, but externally have the old links still work.

Therefore, we will use middleware (`rewritePureWithQueries`) from this module to rewrite incoming requests from the query formulation to the paths formulation.

    {-# LANGUAGE ViewPatterns #-}

    rewritePathFromPhp :: Middleware
    rewritePathFromPhp = rewritePureWithQueries pathFromPhp

    pathFromPhp :: PathsAndQueries -> H.RequestHeaders -> PathsAndQueries
    pathFromPhp (pieces, queries) _ = piecesConvert pieces queries
        where
            piecesConvert :: [Text] -> H.Query -> PathsAndQueries
            piecesConvert ["index.php"] qs@(join . lookup "page" -> Just page) =
                ( ["index", TE.decodeUtf8With TE.lenientDecode page]
                , delete ("page", pure page) qs
                )
            piecesConvert ps qs = (ps, qs)

On the other side, we will use `rewriteRequestPure` to rewrite outgoing requests to the original website from the reverse proxy code (using the `WPRModifiedRequest` or `WPRModifiedRequestSecure` constructors. Note, these links will only work if the haddock documentation for http-reverse-proxy is installed).

    rewritePhpFromPath :: Request -> Request
    rewritePhpFromPath = rewriteRequestPure phpFromPath

    phpFromPath :: PathsAndQueries -> H.RequestHeaders -> PathsAndQueries
    phpFromPath (pieces, queries) _ = piecesConvert pieces queries
        where
            piecesConvert :: [Text] -> H.Query -> PathsAndQueries
            piecesConvert ["index", page] qs = ( ["index.php"], ("page", pure . TE.encodeUtf8 $ page) : qs )
            piecesConvert ps qs = (ps, qs)

For the whole example, see https://gist.github.com/dbaynard/c844d0df124f68ec8b6da152c581ce6d.

## Upgrading from wai-extra ≤ 3.0.16.1

It is quite simple to upgrade from `rewrite` and `rewritePure`, to `rewriteWithQueries` and `rewritePureWithQueries`. Insert `first`, which specialises to

    first :: ([Text] -> [Text]) -> PathsAndQueries -> PathsAndQueries

as the following example demonstrates.

Old versions of the libary could only handle path pieces, not queries. This could have been supplied to `rewritePure`.

    staticConvert' :: [Text] -> H.RequestHeaders -> [Text]
    staticConvert' pieces _ = piecesConvert pieces
      where
       piecesConvert [] = ["static", "html", "pages.html"]
       piecesConvert route@("pages":_) = "static":"html":route

Instead, use this function, supplied to `rewritePureWithQueries`.

    staticConvert :: PathsAndQueries -> H.RequestHeaders -> PathsAndQueries
    staticConvert pathsAndQueries _ = first piecesConvert pathsAndQueries
      where
       piecesConvert [] = ["static", "html", "pages.html"]
       piecesConvert route@("pages":_) = "static":"html":route

The former formulation is deprecated for two reasons:

1. The original formulation of `rewrite` modified `rawPathInfo`, which is deprecated behaviour.
2. The original formulation did not allow query parameters to influence the path.

Concerning the first point, take care with semantics of your program when upgrading as the upgraded functions no longer modify `rawPathInfo`.

# `Middleware`

## Recommended functions

rewriteWithQueries :: (PathsAndQueries -> RequestHeaders -> IO PathsAndQueries) -> Middleware #

Rewrite based on your own conversion function for paths and queries. This function is to be supplied by users of this library, and operates in `IO`.

rewritePureWithQueries :: (PathsAndQueries -> RequestHeaders -> PathsAndQueries) -> Middleware #

Rewrite based on pure conversion function for paths and queries. This function is to be supplied by users of this library.

## Deprecated

rewrite :: ([Text] -> RequestHeaders -> IO [Text]) -> Middleware #

Warning: This modifies the `rawPathInfo` field of a `Request`. This is not recommended behaviour; it is however how this function has worked in the past. Use `rewriteWithQueries` instead

Rewrite based on your own conversion function for paths only, to be supplied by users of this library (with the conversion operating in `IO`).

For new code, use `rewriteWithQueries` instead.

rewritePure :: ([Text] -> RequestHeaders -> [Text]) -> Middleware #

Warning: This modifies the `rawPathInfo` field of a `Request`. This is not recommended behaviour; it is however how this function has worked in the past. Use `rewritePureWithQueries` instead

Rewrite based on pure conversion function for paths only, to be supplied by users of this library.

For new code, use `rewritePureWithQueries` instead.

# Operating on `Request`s

rewriteRequest :: (PathsAndQueries -> RequestHeaders -> IO PathsAndQueries) -> Request -> IO Request #

Modify a `Request` using the supplied function in `IO`. This is suitable for the reverse proxy example.

rewriteRequestPure :: (PathsAndQueries -> RequestHeaders -> PathsAndQueries) -> Request -> Request #

Modify a `Request` using the pure supplied function. This is suitable for the reverse proxy example.