purescript-httpurple/docs/Middleware.md
2018-07-08 16:28:59 -07:00

3.7 KiB

Writing and Using Middleware in HTTPure

Since HTTPure routers are just pure functions, you can write a middleware by simply creating a function that takes a router and an HTTPure.Request, and returns an HTTPure.ResponseM. You can then simply use function composition to combine middlewares, and pass your router to your composed middleware to generate the decorated router!

See the Middleware example to see how you can build, compose, and consume different types of middleware.

Writing Middleware

A middleware is a function with the signature:

(HTTPure.Request -> HTTPure.ResponseM) -> HTTPure.Request -> HTTPure.ResponseM

Note that the first argument is just the signature for a router function. So essentially, your middleware should take a router and return a new router. That's it! You can do pretty much anything with middlewares. Here are a few examples of common middleware patterns:

You can write a middleware that wraps all future work in some behavior, like logging or timing:

myMiddleware router request = do
  doSomethingBefore
  response <- router request
  doSomethingAfter
  pure response

Or perhaps a middleware that injects something into the response:

myMiddleware router request = do
  response <- router request
  HTTPure.response' response.status response.headers $
    response.body <> "\n\nGenerated using my super duper middleware!"

You could even write a middleware that handles routing for some specific cases:

myMiddleware _ { path: [ "somepath" ] } = HTTPure.ok "Handled by my middleware!"
myMiddleware router request = router request

Or even a middleware that conditionally includes another middleware:

myMiddleware router = if something then someOtherMiddleware router else router

Just make sure your middlewares follow the correct signature, and users will be able to compose them at will!

Note that because there is nothing fancy happening here, you could always write higher order functions that don't follow this signature, if it makes sense. For instance, you could write a function that takes two routers, and selects which one to use based on some criteria. There is nothing wrong with this, but you should try to use the middleware signature mentioned above as much as possible as it will make your middleware easier to consume and compose.

Consuming Middleware

Consuming middleware easy: simply compose all the middleware you want, and then pass your router to the composed middleware. For instance:

main = HTTPure.serve port composedRouter $ Console.log "Server is up!"
  where
    composedRouter = middlewareA <<< middlewareB <<< middlewareC $ router

Be aware of the ordering of the middleware that you compose--since we used <<<, the middlewares will compose right-to-left. But because middlewares choose when to apply the router to the request, this is a bit like wrapping the router in each successive middleware from right to left. So when the router executes on a request, those middlewares will actually execute left-to-right--or from the outermost wrapper inwards.

In other words, say you have the following HTTPure server:

middleware letter router request = do
  EffectClass.liftEffect $ Console.log $ "Starting Middleware " <> letter
  response <- router request
  EffectClass.liftEffect $ Console.log $ "Ending Middleware " <> letter
  pure response

main = HTTPure.serve port composedRouter $ Console.log "Server is up!"
  where
    composedRouter = middleware "A" <<< middleware "B" $ router

When this HTTPure server receives a request, the logs will include:

Starting Middleware A
Starting Middleware B
...
Ending Middleware B
Ending Middleware A