Refuse requests that do not match a schema
The orders API takes a new order as a JSON body in a POST to /orders. A body with an amount of zero, or in a currency the backend does not handle, should never reach the backend. request-validation reads each request body against a JSON Schema at the gateway. A body that does not match is answered by the gateway and is not passed on.
The check belongs on a route that takes only the new orders. On a route that also answers reads, a GET without a body is refused as well. This guide creates the writes route orders-writes beside orders-api and puts the check on it. At the end, an order with a positive amount in EUR or USD reaches your backend, and one with an amount of 0, or in GBP, is answered 422 with a sentence you choose.
Before you start
- This guide starts from
orders-apias Route requests to a backend leaves it, with key-auth from Require an API key. A plugin from another guide that this page does not name can change the answers below; remove it first or work on a copy of the route. orders-apitakes GET and POST for/ordersand everything under it, and sends them to the backend nodes10.0.1.10:8080and10.0.1.11:8080.- The host name of your gateway. In the console it is the first part of the Address line on a route's Overview tab,
api.example.comin this page's examples; replace it in every request below. - The requests below post to
/orders. The one under What to know reads/orders/42. - The fields a new order must carry and the values each may take. The team that builds the backend knows them. When its own tests hold a schema, start from that one, so the gateway and the backend refuse the same bodies. This guide asks for an integer
amountof at least 1 and acurrencyofEURorUSD. - The Editor or Environment administrator role in this environment.
Steps
1. Create the writes route
In the menu, select Routes, then New route. Fill it in the way Route requests to a backend fills in orders-api, with these differences.
| Field | Value | Why |
|---|---|---|
| Name | orders-writes | A name that tells the two routes apart. |
| Paths | /orders | The path a new order is posted to. |
| Methods | POST only | Reads stay on orders-api, where no schema applies. |
| Priority | 10 | orders-api takes POST on /orders too. Of two routes that share a path, the one with the higher priority is tried first. The Priority of orders-api is empty, which counts as 0, so 10 wins. |
Under Target, send it to the same two backend nodes as orders-api, 10.0.1.10:8080 and 10.0.1.11:8080, as in Route requests to a backend. Select Create route.
2. Choose request-validation
Open orders-writes under Routes, select its Plugins tab, then Add plugin at the top right of the tab. Open the Traffic category, or type request-validation in the search box. Select request-validation, then Configure. A panel with the plugin's form opens.
3. Write the body schema
Body schema is a box that takes JSON. Paste the schema of a new order into it. It asks for an object with an integer amount of at least 1 and a currency of EUR or USD.
{
"type": "object",
"required": [
"amount",
"currency"
],
"properties": {
"amount": {
"type": "integer",
"minimum": 1
},
"currency": {
"type": "string",
"enum": [
"EUR",
"USD"
]
}
}
}
Leave Header schema empty. This guide checks the body only.
4. Choose the refusal and add the plugin
Fill in two more fields and leave Request body limit as it is.
| Field | Value | Why |
|---|---|---|
| Rejected code | 422 | The status a refused order gets. |
| Rejected message | The order body was not understood. | The whole body of the refusal. Left empty, the client reads the validator's own sentence, which names the properties of your schema. |
Select Add plugin at the bottom of the panel. The tab now lists request-validation, and the plugin is saved like this:
{
"body_schema": {
"type": "object",
"required": [
"amount",
"currency"
],
"properties": {
"amount": {
"type": "integer",
"minimum": 1
},
"currency": {
"type": "string",
"enum": [
"EUR",
"USD"
]
}
}
},
"rejected_code": 422,
"rejected_msg": "The order body was not understood."
}
From now on, every new order whose body does not match the schema is answered by the gateway, and your backend never sees it.
5. Ask for a key on the writes route
orders-writes does not carry the plugins of orders-api, so a new order needs no key yet. Add key-auth to orders-writes as steps 1 to 3 of Require an API key show, on orders-writes instead of orders-api.
Check it
Post an order with an amount of 0, then one in GBP, then a valid one with an amount of 5 in EUR. With key-auth on orders-writes from step 5, send the key of mobile-app in the x-api-key header with every request below. The first one:
curl -i -L -X POST 'https://<gateway host>/orders' \
-H 'Content-Type: application/json' \
-d '{"amount":0,"currency":"EUR"}'
The valid order:
curl -i -L -X POST 'https://<gateway host>/orders' \
-H 'Content-Type: application/json' \
-d '{"amount":5,"currency":"EUR"}'
- An amount of zero
- An order whose amount is
0is answered422withThe order body was not understood.; the backend never sees it. - A currency the schema does not list
- An order in a currency the schema does not list, GBP, is answered
422with the same message. - A valid order
- An order with a positive amount in euros is passed on and answered
200by the backend.
The body of the last answer is whatever your backend answers to a new order.
What to know
- A request that
orders-writestakes runs only the plugins oforders-writes, and none of those onorders-api. Put on the writes route every plugin a new order must pass, as step 5 does for key-auth. - The plugin checks every request that reaches its route, reads included. On a route that answers reads, it refuses a plain GET for
/orders/42, because a request without a body does not match the schema.
curl -i -L 'https://<gateway host>/orders/42'
- A GET on a route that carries the plugin
- A GET without a body on a route that carries this plugin is answered
422too, so the plugin belongs on the route that takes the writes and not on the one that answers reads.
- A body that passes does not reach the backend byte for byte. The backend gets the parsed JSON written out again, with keys reordered and spacing and number formatting changed.
- Rejected code also takes a 2xx status. The refused request is then still not passed on, but the client reads a success status.
- A body longer than Request body limit is refused with the Rejected code before the schema is applied.
- Header schema checks the headers the same way. A header reaches that schema as text, so a property typed as a number never matches.
- A plugin with neither a body schema nor a header schema is refused when you save.
Related
- Route requests to a backend: the route for reads, and how a route is filled in.
- How G8 handles a request: how the gateway picks one route among several on the same path.
- Require an API key: who may send orders at all. Put it on both routes.
- request-validation and Route in the reference.