wai-extra-3.1.10: Provides some basic WAI handlers and middleware.
Safe HaskellNone
LanguageHaskell2010

Network.Wai.Middleware.Rewrite

Synopsis

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 Requests 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) Source #

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 library 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 Source #

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 Source #

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

rewriteRoot :: Text -> Middleware Source #

Rewrite root requests (/) to a specified path

Note that index.html in example below should already be a valid route.

    rewriteRoot "index.html" :: Middleware

Since: 3.0.23.0

Deprecated

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

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 Source #

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 Requests

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

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

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

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