Update documentation
This commit is contained in:
parent
e4cc8662d0
commit
618c284485
@ -21,6 +21,7 @@ This project was originally forked from the amazing [HTTPure](https://github.com
|
|||||||
* λ fp-style http server & request handlers
|
* λ fp-style http server & request handlers
|
||||||
* 🛣 Powerful & type-safe routing dsl
|
* 🛣 Powerful & type-safe routing dsl
|
||||||
* 🕵🏻♂️ Easy json parsing and data validation
|
* 🕵🏻♂️ Easy json parsing and data validation
|
||||||
|
* 🥪 Supports Node/Express middlewares
|
||||||
* 📜 Extensive documentation & examples
|
* 📜 Extensive documentation & examples
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,6 +79,15 @@ main = serve { port: 8080 } { route: api, router: apiRouter }
|
|||||||
Nothing -> "defaulting to ascending"
|
Nothing -> "defaulting to ascending"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Extensible requests and node middlewares
|
||||||
|
|
||||||
|
In HTTPurple 🪁 requests are extensible records, so you can add data to the request. This is particularly useful when implementing middlewares that e.g. add user information to the incoming requets.
|
||||||
|
|
||||||
|
Furthermore, HTTPurple 🪁 adds support for (application-level) node/express middlewares.
|
||||||
|
|
||||||
|
See [`Middleware.md`](./Middleware.md) for more information.
|
||||||
|
|
||||||
|
|
||||||
## Startup options
|
## Startup options
|
||||||
|
|
||||||
HTTPurple 🪁 greatly simplifies the startup options and functions. The `serve`, `serve'`, `serveSecure` and `serveSecure'` have been merged into a single function `serve` that accepts listen options as the first parameter and uses sane defaults if you don't provide any.
|
HTTPurple 🪁 greatly simplifies the startup options and functions. The `serve`, `serve'`, `serveSecure` and `serveSecure'` have been merged into a single function `serve` that accepts listen options as the first parameter and uses sane defaults if you don't provide any.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Writing and Using Middleware in HTTPure
|
# Writing and Using Middleware in HTTPurple 🪁
|
||||||
|
|
||||||
Since HTTPure routers are just pure functions, you can write a middleware by
|
Since HTTPurple 🪁 routers are just pure functions, you can write a middleware by
|
||||||
simply creating a function that takes a router and an `HTTPure.Request`, and
|
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
|
returns an `HTTPure.ResponseM`. You can then simply use function composition to
|
||||||
combine middlewares, and pass your router to your composed middleware to
|
combine middlewares, and pass your router to your composed middleware to
|
||||||
@ -9,6 +9,12 @@ generate the decorated router!
|
|||||||
See [the Middleware example](./Examples/Middleware/Main.purs) to see how you can
|
See [the Middleware example](./Examples/Middleware/Main.purs) to see how you can
|
||||||
build, compose, and consume different types of middleware.
|
build, compose, and consume different types of middleware.
|
||||||
|
|
||||||
|
🎉 **New:**
|
||||||
|
|
||||||
|
HTTPurple 🪁 now supports extensible middlewares that allows you to add further data to your request, see [Extensible Middlewares](#extensible-middlewares).
|
||||||
|
|
||||||
|
HTTPurple 🪁 now supports Node.js/Express middlewares, see [Node.js Middlewares](#node-middlewares).
|
||||||
|
|
||||||
## Writing Middleware
|
## Writing Middleware
|
||||||
|
|
||||||
A middleware is a function with the signature:
|
A middleware is a function with the signature:
|
||||||
@ -83,7 +89,7 @@ router in each successive middleware from right to left. So when the router
|
|||||||
executes on a request, those middlewares will actually _execute_
|
executes on a request, those middlewares will actually _execute_
|
||||||
left-to-right--or from the outermost wrapper inwards.
|
left-to-right--or from the outermost wrapper inwards.
|
||||||
|
|
||||||
In other words, say you have the following HTTPure server:
|
In other words, say you have the following HTTPurple 🪁 server:
|
||||||
|
|
||||||
```purescript
|
```purescript
|
||||||
middleware letter router request = do
|
middleware letter router request = do
|
||||||
@ -97,7 +103,7 @@ main = HTTPure.serve port composedRouter $ Console.log "Server is up!"
|
|||||||
composedRouter = middleware "A" <<< middleware "B" $ router
|
composedRouter = middleware "A" <<< middleware "B" $ router
|
||||||
```
|
```
|
||||||
|
|
||||||
When this HTTPure server receives a request, the logs will include:
|
When this HTTPurple 🪁 server receives a request, the logs will include:
|
||||||
|
|
||||||
```
|
```
|
||||||
Starting Middleware A
|
Starting Middleware A
|
||||||
@ -106,3 +112,186 @@ Starting Middleware B
|
|||||||
Ending Middleware B
|
Ending Middleware B
|
||||||
Ending Middleware A
|
Ending Middleware A
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Extensible Middlewares
|
||||||
|
|
||||||
|
The base type for requests, `ExtRequest` is now extensible:
|
||||||
|
```purescript
|
||||||
|
type RequestR route ext =
|
||||||
|
( method :: Method
|
||||||
|
, path :: Path
|
||||||
|
, query :: Query
|
||||||
|
, route :: route
|
||||||
|
, headers :: RequestHeaders
|
||||||
|
, body :: RequestBody
|
||||||
|
, httpVersion :: Version
|
||||||
|
, url :: String
|
||||||
|
| ext
|
||||||
|
)
|
||||||
|
type ExtRequest route ext = { | RequestR route ext }
|
||||||
|
```
|
||||||
|
and the old `Request` is just a type alias for an extensible request without any further data:
|
||||||
|
```purescript
|
||||||
|
type Request route = { | RequestR route () }
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows us to write middlewares that extend our request with additional information.
|
||||||
|
|
||||||
|
E.g. we can write an authenticator middleware that adds user information to the request:
|
||||||
|
```purescript
|
||||||
|
authenticator ::
|
||||||
|
forall route extIn extOut.
|
||||||
|
Nub (RequestR route extOut) (RequestR route extOut) =>
|
||||||
|
Union extIn (user :: Maybe String) extOut =>
|
||||||
|
Middleware route extIn extOut
|
||||||
|
authenticator router request@{ headers } = case Headers.lookup headers "X-Token" of
|
||||||
|
Just token | token == "123" -> router $ merge request { user: Just "John Doe" }
|
||||||
|
_ -> router $ merge request { user: Nothing :: Maybe String }
|
||||||
|
```
|
||||||
|
The type `Middleware` is defined as
|
||||||
|
```purescript
|
||||||
|
type MiddlewareM m route extIn extOut = (ExtRequest route extOut -> m Response) -> ExtRequest route extIn -> m Response
|
||||||
|
type Middleware route extIn extOut = MiddlewareM Aff route extIn extOut
|
||||||
|
```
|
||||||
|
and adds the `extOut` extension to our request handler. In our case, it adds `(user :: Maybe String)` to the request handler. `extIn` defines the input extension that the middleware receives. At the root level, this will be an empty row `()` (unless we use a node middleware).
|
||||||
|
Not fixing `extIn` to `()` however allows us to stack our middlewars.
|
||||||
|
|
||||||
|
Let's add another middleware, that adds the request time to the request:
|
||||||
|
```purescript
|
||||||
|
requestTime ::
|
||||||
|
forall route extIn extOut.
|
||||||
|
Nub (RequestR route extOut) (RequestR route extOut) =>
|
||||||
|
Union extIn (time :: JSDate) extOut =>
|
||||||
|
Middleware route extIn extOut
|
||||||
|
requestTime router request = do
|
||||||
|
time <- liftEffect JSDate.now
|
||||||
|
router $ merge request { time }
|
||||||
|
```
|
||||||
|
Similar to the `authenticator` middleware, we add a new field `time` to the request.
|
||||||
|
|
||||||
|
We can now compose our middleware stack:
|
||||||
|
```purescript
|
||||||
|
middlewareStack :: forall route. (ExtRequest route (user :: Maybe String, time :: JSDate) -> ResponseM) -> Request route -> ResponseM
|
||||||
|
middlewareStack = authenticator <<< requestTime
|
||||||
|
```
|
||||||
|
|
||||||
|
We can now use our middleware stack. Let's define a simple route:
|
||||||
|
```purescript
|
||||||
|
data SayHello = SayHello
|
||||||
|
|
||||||
|
derive instance Generic SayHello _
|
||||||
|
|
||||||
|
sayHelloRoute :: RD.RouteDuplex' SayHello
|
||||||
|
sayHelloRoute = RD.root $ RG.sum
|
||||||
|
{ "SayHello": RG.noArgs
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
and a router that makes use of our newly added information:
|
||||||
|
```purescript
|
||||||
|
-- | Say 'hello <USER>' when run with X-Token, otherwise 'hello anonymous'
|
||||||
|
sayHello :: ExtRequest SayHello (user :: Maybe String, time :: JSDate) -> ResponseM
|
||||||
|
sayHello { user: Just user, time } = ok $ "hello " <> user <> ", it is " <> JSDate.toDateString time <> " " <> JSDate.toTimeString time
|
||||||
|
sayHello { user: Nothing, time } = ok $ "hello " <> "anonymous, it is " <> JSDate.toDateString time <> " " <> JSDate.toTimeString time
|
||||||
|
```
|
||||||
|
As you can see, we are now using an `ExtRequest` with the additional information `(user :: Maybe String, time :: JSDate)`, which we can use in our function body.
|
||||||
|
|
||||||
|
Finally, we wrap our `sayHello` router with our `middlewareStack`:
|
||||||
|
|
||||||
|
```purescript
|
||||||
|
main =
|
||||||
|
serve { hostname: "localhost", port: 8080 } { route: sayHelloRoute, router: middlewareStack sayHello }
|
||||||
|
```
|
||||||
|
|
||||||
|
See the full example in the [`Examples/ExtensibleMiddleware`](./Examples/ExtensibleMiddleware/) folder.
|
||||||
|
|
||||||
|
|
||||||
|
## Node Middlewares
|
||||||
|
|
||||||
|
Node/Express middlewares are no supported, but currently only on the application level (i.e. they will be run on every request).
|
||||||
|
|
||||||
|
For our example, we'll use two existing node middlewares, and one custom node middleware.
|
||||||
|
The first one is `morgan`, which adds logging to our http server.
|
||||||
|
Install it using
|
||||||
|
```bash
|
||||||
|
npm install morgan --save
|
||||||
|
```
|
||||||
|
|
||||||
|
To use it, we create a FFI and export it as `morgan`
|
||||||
|
```javascript
|
||||||
|
import { default as M } from "morgan";
|
||||||
|
export const morgan = M("tiny")
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we can define the foreign import in our Purescript file:
|
||||||
|
```purescript
|
||||||
|
foreign import morgan :: NodeMiddleware ()
|
||||||
|
```
|
||||||
|
The empty row `()` indicates, that this middleware doesn't add any information to our request.
|
||||||
|
|
||||||
|
Next, we'll add `helmet` which adds some security headers to our response.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install helmet --save
|
||||||
|
```
|
||||||
|
|
||||||
|
Add it to our ffi
|
||||||
|
```javascript
|
||||||
|
import { default as H } from "helmet";
|
||||||
|
export const helmet = H()
|
||||||
|
```
|
||||||
|
and define the foreign import
|
||||||
|
```purescript
|
||||||
|
foreign import helmet :: NodeMiddleware ()
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, let's define a small node middleware that adds something to our request. Add a simple authenticating middleware to our ffi:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export const authenticator = function (req, res, next) {
|
||||||
|
if(req.headers["x-token"] == "123") {
|
||||||
|
req.user = "John Doe"
|
||||||
|
} else {
|
||||||
|
req.user = null
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
If the middleware receives the http header `x-token` with value `123`, it will add the user, otherwise `null`.
|
||||||
|
|
||||||
|
Our foreign import now looks like this:
|
||||||
|
```purescript
|
||||||
|
type AuthenticatorR = (user :: Nullable String)
|
||||||
|
|
||||||
|
foreign import authenticator :: NodeMiddleware (user :: Nullable String)
|
||||||
|
```
|
||||||
|
Our node middleware extends requests with `(user :: Nullable String)`.
|
||||||
|
|
||||||
|
We can now compose our middlewares into a single `NodeMiddlewareStack`:
|
||||||
|
```purescript
|
||||||
|
nodeMiddleware ∷ NodeMiddlewareStack () AuthenticatorR
|
||||||
|
nodeMiddleware = NodeMiddlewareStack $ usingMiddleware morgan >=> usingMiddleware helmet >=> usingMiddleware authenticator
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's define a simple route with a handler, that makes use of our node middleware:
|
||||||
|
```purescript
|
||||||
|
data Route = Hello
|
||||||
|
|
||||||
|
derive instance Generic Route _
|
||||||
|
|
||||||
|
route :: RouteDuplex' Route
|
||||||
|
route = mkRoute
|
||||||
|
{ "Hello": noArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: ServerM
|
||||||
|
main =
|
||||||
|
serveNodeMiddleware { port: 8080 } { route, router: router, nodeMiddleware }
|
||||||
|
where
|
||||||
|
router { route: Hello, user } = case Nullable.toMaybe user of
|
||||||
|
Just u -> ok $ "hello user " <> u
|
||||||
|
Nothing -> ok $ "hello anonymous"
|
||||||
|
```
|
||||||
|
Note, that we have to use `serveNodeMiddleware` instead of `serve` and pass the `nodeMiddleware` along the `route` and `router`. In the router, we gain access to the nullable user.
|
||||||
|
|
||||||
|
You can see a full example of node middlewares in the [`Examples/NodeMiddleware`](./Examples/NodeMiddleware/) folder.
|
||||||
|
Loading…
Reference in New Issue
Block a user