Route
Routes match incoming requests and send them to an upstream.
routeWhat a route is
A route is the entry a request meets first. It says which requests the gateway takes and where it sends them. Its target is a shared upstream, a service whose upstream, plugins and Hosts it inherits, or nodes listed on the route itself.
The plugins on the route run on every request it matches. A request that no route takes is answered 404 Route Not Found. How G8 handles a request shows where the route sits between the client and your backend.
How matching works
A request is routed here when every rule under Matching holds:
- its path is one of the Paths, where a trailing
*matches everything under the path; - its method is one of the Methods, or any method when none is checked;
- its host is one of the Hosts, where a leading
*matches the subdomains and not the bare domain; when the list is empty, the Hosts of the route's service apply, and any host when it has none; - its client address is one of the Client addresses, when any is listed;
- and the Conditions hold: rules on the path, the host, the client address, a header, a query argument, a cookie or a form field, each of which Negate turns into its opposite.
When more than one route holds, Priority is not the first thing looked at. First comes a route that lists Hosts, or whose service does; among those, one that names the request's host exactly comes before one that matches it through a leading *. Then a route whose Path is the request path exactly comes before one with a trailing *, and a longer Path before a shorter one. Only among routes on the same Path that name the host the same way is the higher Priority tried first. A route whose other rules fail is passed over for the next.
When to use it
- Expose a backend under a path, for example
/orders/, and let only the methods it serves reach it. - Send the same path to different instances by host name, by a header, or for a sub-tree like
/orders/archive/. - Give a backend its own host name, or give up on it when it does not answer in time.
- Send requests from given networks to other instances than everyone else's.
Not for a TCP or TLS connection rather than an HTTP request — see Stream route (Stream routes forward raw TCP and UDP connections to an upstream), plugins that run on every request of the environment, whatever the route — see Global rule (Global rules run their plugins on every request the engine handles) or refusing clients from other networks — see ip-restriction (Allows or denies clients by IP address or CIDR range).
Guides
Pages that walk through a job with a route, step by step.
- Answer with a mock while the backend is builtPut mocking on a route so it answers a made-up order while the real backend is still being written.
- Route requests to a backendCreate a route that sends requests for one path to two backend nodes, and find the address clients call.
- Accept signed tokensPut jwt-auth on a route and give the client the credential that checks the tokens it signs.
- Ask for a username and passwordPut basic-auth on a route so that a client signs in with a username and password, and a browser asks for them.
- Let a browser app call the APIPut cors on a route so a web app served from another origin can call the API from the browser, cookies included.
- Refuse requests that do not match a schemaCheck the body of each new order against a JSON Schema at the gateway and refuse the ones that do not match.
Set it up
Send GET and POST on /orders and everything under it to two backend nodes of the orders service
Under General set Name to orders-api. Under Matching fill Paths as in the JSON below and fill Methods as in the JSON below. Under Target choose Nodes on this route and fill Upstream as in the JSON below.
{
"name": "orders-api",
"uris": [
"/orders",
"/orders/*"
],
"methods": [
"GET",
"POST"
],
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.10:8080": 1,
"10.0.1.11:8080": 1
}
}
}
What you will see
- Repeated requests are all answered
200, and the two backend nodes share them, as each node's own log shows: the two share the traffic. - A request to /orders itself is answered
200as well. - A POST is answered
200like a GET. - A DELETE, a method the route does not list, is answered
404 Route Not Found: no route takes it.
More scenarios
Put requests under /orders/ on the orders service and take its Hosts
A route with no Hosts of its own that sits on a service takes the service's Hosts, and it counts as a route with Hosts when routes are ordered. `service_id` holds the engine's ID of the service, not its name. In the console you pick the service and its ID is filled in.
Under General set Name to orders. Under Matching set Paths to /orders/*. Under Target choose Service and pick your service in Service.
{
"name": "orders",
"uri": "/orders/*",
"service_id": "00000000000000000025"
}
What you will see
- Requests for api.example.com, the service's host, are answered
200by the service's instances. - The route lists no Hosts of its own, so the service's apply: a request for any other host is answered
404 Route Not Found.
Put requests under /orders/ on the orders service and keep one plugin of the route's own
A route on a service runs the service's plugins; where both carry a plugin, the route's own copy is used. `service_id` holds the ID the engine gave the service when it was created, not its name. In the console you pick the service and its ID is filled in.
Under General set Name to orders. Under Matching set Paths to /orders/*. Under Target choose Service and pick your service in Service. Once it is created, open its Plugins tab and add cors with its settings from the JSON below.
{
"name": "orders",
"uri": "/orders/*",
"service_id": "00000000000000000025",
"plugins": {
"cors": {
"allow_origins": "https://orders.example.com",
"allow_methods": "GET,POST",
"allow_headers": "Authorization,Content-Type"
}
}
}
What you will see
- A browser request from
https://orders.example.comis answered200withAccess-Control-Allow-Origin: https://orders.example.com. The route's own cors settings apply, not the service's. - The same answer carries
X-RateLimit-Limit: 100from the rate limit the service adds to every route on it.
Send requests that carry X-Tenant: acme to the tenant's instances
The route with the Condition needs the higher Priority on the shared Path, or the plain route, tried first, takes every request.
Under General set Name to orders-acme. Under Matching set Paths to /orders/* and set Priority to 10. Under Conditions fill Conditions as in the JSON below. Under Target choose Nodes on this route and fill Upstream as in the JSON below.
{
"name": "orders-acme",
"uri": "/orders/*",
"priority": 10,
"vars": [
[
"http_x_tenant",
"==",
"acme"
]
],
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.70:8080": 1
}
}
}
What you will see
- A request that carries X-Tenant: acme is answered
200by the tenant's instances, because this route is tried first on the path through its higher Priority and its Condition holds. - A request with another tenant, or without the header, is passed over here and answered
200by the route for /orders/*.
Serve requests under /orders/ for every partner subdomain from separate instances
A route that lists Hosts is checked before every route without one, whatever the priorities; a leading * matches the subdomains and not the bare domain.
Under General set Name to orders-partners. Under Matching set Paths to /orders/* and fill Hosts as in the JSON below. Under Target choose Nodes on this route and fill Upstream as in the JSON below.
{
"name": "orders-partners",
"uri": "/orders/*",
"hosts": [
"*.partners.example.com"
],
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.60:8080": 1
}
}
}
What you will see
- Requests for a subdomain such as acme.partners.example.com are answered
200by this route's instances, although the route for /orders/* without Hosts carries the higher Priority: a route with Hosts is checked before every route without one. - So is a request for /orders/list, although a route without Hosts names that exact path.
- partners.example.com itself is no subdomain, and its requests are answered by the route without Hosts.
Hold every consumer under /orders/ to the route's limit unless the consumer or its group carries one of its own
For a plugin both carry, the copy on the consumer is the one that runs, failing that the copy on its consumer group, and the route's only for the rest; key-auth names the consumer first.
Under General set Name to orders-api. Under Matching set Paths to /orders/*. Under Target choose Nodes on this route and fill Upstream as in the JSON below. Once it is created, open its Plugins tab and add key-auth and limit-count, each with its settings from the JSON below.
{
"name": "orders-api",
"uri": "/orders/*",
"plugins": {
"key-auth": {},
"limit-count": {
"count": 100,
"time_window": 60,
"key": "consumer_name",
"rejected_code": 429
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.10:8080": 1
}
}
}
What you will see
- A consumer without a limit of its own is answered
200withX-RateLimit-Limit: 100, the route's. - A consumer whose own plugins carry limit-count is counted by that copy instead:
X-RateLimit-Limit: 2000. - A consumer in a group whose plugins carry limit-count is counted by the group's copy:
X-RateLimit-Limit: 1000. - A consumer in that group with a limit-count of its own is counted by its own:
X-RateLimit-Limit: 5000.
Apply the shared plugin config under /orders/ and keep one plugin of the route's own
Where the route and its plugin config both carry a plugin, the route's own copy is used whole; the config's other plugins apply beside it.
Under General set Name to orders. Under Matching set Paths to /orders/*. Under Target choose Nodes on this route and fill Upstream as in the JSON below. Under Plugins pick your plugin config in Plugin config. Once it is created, open its Plugins tab and add cors with its settings from the JSON below.
{
"name": "orders",
"uri": "/orders/*",
"plugin_config_id": "public-api-defaults",
"plugins": {
"cors": {
"allow_origins": "https://orders.example.com",
"allow_methods": "GET,POST",
"allow_headers": "Authorization,Content-Type"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.10:8080": 1
}
}
}
What you will see
- A browser request from
https://orders.example.comis answered200withAccess-Control-Allow-Origin: https://orders.example.com. The route's own cors settings apply, not the config's. - The same answer carries
X-RateLimit-Limit: 100from the rate limit the config adds to the route.
Send everything under /orders/archive/ to the archive instances while /orders/* keeps the rest
The longest matching Path is taken first, whatever the priorities; a Path with a trailing * does not take the path itself.
Under General set Name to orders-archive. Under Matching set Paths to /orders/archive/*. Under Target choose Nodes on this route and fill Upstream as in the JSON below.
{
"name": "orders-archive",
"uri": "/orders/archive/*",
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.50:8080": 1
}
}
}
What you will see
- Requests under /orders/archive/ are answered
200by the archive instances, although the route for /orders/* carries the higher Priority: the longest matching Path is taken first. - A request to /orders/archive itself, without the slash, is not under the Path and is answered by the route for /orders/*, like every other request under /orders/.
Give up on a backend that has not answered within 3 seconds
Connect, Send and Read are set together; without them the gateway waits its own, longer time for the backend.
Under General set Name to orders. Under Matching set Paths to /orders/*. Under Target choose Nodes on this route and fill Upstream as in the JSON below. Under Timeouts fill Custom timeouts as in the JSON below.
{
"name": "orders",
"uri": "/orders/*",
"timeout": {
"connect": 3,
"send": 3,
"read": 3
},
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.10:8080": 1
}
}
}
What you will see
- A backend that has not answered within the Read timeout is given up on, and the client receives
504 Gateway Time-out.
Give acme.partners.example.com its own instances beside the route for every partner subdomain
A route that names the host exactly is taken before one that matches it through a leading *, whatever the priorities and before the Paths are compared.
Under General set Name to orders-acme-partner. Under Matching set Paths to /orders/* and fill Hosts as in the JSON below. Under Target choose Nodes on this route and fill Upstream as in the JSON below.
{
"name": "orders-acme-partner",
"uri": "/orders/*",
"hosts": [
"acme.partners.example.com"
],
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.80:8080": 1
}
}
}
What you will see
- Requests for acme.partners.example.com are answered
200by this route's instances, although the route for every partner subdomain carries the higher Priority: a route that names the host exactly is taken before one that matches it through a leading *. - So is a request for /orders/list, although a second route for every partner subdomain names that exact path: the host is looked at before the Path.
- Requests for another partner, such as beta.partners.example.com, are answered by the route for every partner subdomain.
Send /orders/list to its own instance while /orders/* keeps the rest
An exact Path is taken before a Path with a trailing *, whatever the two priorities are.
Under General set Name to orders-list. Under Matching set Paths to /orders/list. Under Target choose Nodes on this route and fill Upstream as in the JSON below.
{
"name": "orders-list",
"uri": "/orders/list",
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.30:8080": 1
}
}
}
What you will see
- A request to /orders/list is answered
200by the instance on this route, although the route for /orders/* carries the higher Priority: an exact Path is taken before a Path with a trailing *. - Every other request under /orders/ is still answered
200by the route for /orders/*.
Send requests under /orders/ to instances found through DNS
DNS discovery must be enabled in the gateway's static configuration first; without it every request is refused with 503.
Under General set Name to orders-discovered. Under Matching set Paths to /orders/*. Under Target choose Nodes on this route and fill Upstream as in the JSON below.
{
"name": "orders-discovered",
"uri": "/orders/*",
"upstream": {
"type": "roundrobin",
"discovery_type": "dns",
"service_name": "orders.internal.example.com:8080"
}
}
What you will see
- Requests are answered
200by an instance DNS found for the service name.
Keep answering under /orders/ while one of the two instances is stopped
With no health check set, a request the stopped node refuses is tried on the other node, so clients keep getting answers. An active health check, which this example does not set, is added on the upstream and takes a stopped node out of rotation so requests stop trying it first.
Under General set Name to orders-api. Under Matching fill Paths as in the JSON below and fill Methods as in the JSON below. Under Target choose Nodes on this route and fill Upstream as in the JSON below.
{
"name": "orders-api",
"uris": [
"/orders",
"/orders/*"
],
"methods": [
"GET",
"POST"
],
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.10:8080": 1,
"10.0.1.11:8080": 1
}
}
}
What you will see
- With the second backend node stopped, repeated requests are all answered
200, every one by the first backend node. - A POST is answered
200by the first backend node as well.
Send requests under /orders/ to a backend that expects its own host name
With verification on, the certificate of the node must be valid for orders.example.com, the name the request now carries.
Under General set Name to orders-public. Under Matching set Paths to /orders/*. Under Target choose Nodes on this route and fill Upstream as in the JSON below.
{
"name": "orders-public",
"uri": "/orders/*",
"upstream": {
"type": "roundrobin",
"scheme": "https",
"nodes": {
"10.0.1.20:443": 1
},
"pass_host": "rewrite",
"upstream_host": "orders.example.com",
"tls": {
"verify": true
}
}
}
What you will see
- The backend receives the request with the host name
orders.example.comand answers200.
Keep each checkout session on the same instance
Requests that carry the same session_id cookie reach the same instance; one without the cookie is placed by its client address instead, so cookie-less requests from one address reach one instance.
Under General set Name to checkout. Under Matching set Paths to /checkout/*. Under Target choose Nodes on this route and fill Upstream as in the JSON below.
{
"name": "checkout",
"uri": "/checkout/*",
"upstream": {
"type": "chash",
"hash_on": "cookie",
"key": "session_id",
"nodes": {
"10.0.1.10:8080": 1,
"10.0.1.11:8080": 1
}
}
}
Ramp an instance added later up to its full share behind /orders/
An instance added to the list later starts at 10 percent of its weight of 100 and reaches the whole of it over 60 seconds; the instances present when the upstream is created take their full share from the start.
Under General set Name to orders-warmed. Under Matching set Paths to /orders/*. Under Target choose Nodes on this route and fill Upstream as in the JSON below.
{
"name": "orders-warmed",
"uri": "/orders/*",
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.10:8080": 100,
"10.0.1.11:8080": 100
},
"warm_up_conf": {
"slow_start_time_seconds": 60,
"min_weight_percent": 10,
"interval": 5
}
}
}
Send POST requests under /orders/ to the writer instances, in front of the route for reads
Two routes on one Path are tried in the order of their Priority, and a route whose Methods do not include the request's method is passed over for the next.
Under General set Name to orders-writes. Under Matching set Paths to /orders/*, fill Methods as in the JSON below and set Priority to 10. Under Target choose Nodes on this route and fill Upstream as in the JSON below.
{
"name": "orders-writes",
"uri": "/orders/*",
"methods": [
"POST"
],
"priority": 10,
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.40:8080": 1
}
}
}
What you will see
- A POST under /orders/ is answered
200by the writer instances of this route, which is tried first on the path through its higher Priority. - A GET is passed over here, since GET is not among the Methods, and is answered
200by the route for reads on the same path. - A DELETE, which neither route lists, is answered
404 Route Not Found.
Things to know
- The engine assigns the
idwhen the object is created; it is not the name, and it is what other objects refer to. - Paths — A trailing
*does not take the path itself:/orders/*takes/orders/and what is under it, and not/orders. List/ordersas a second Path when it has to answer too. - Custom timeouts — Set together: once any of Connect, Send and Read is given, all three are required. A backend that has not answered within Read is given up on. A GET then goes to the next node when there is one, a POST is not sent again. The client receives
504 Gateway Time-outonce no node has answered in time. - Plugins — The engine runs them by their priority, not by the order they are written in.
- Plugin config — Where the route and its plugin config both carry a plugin, the route's own settings are used, whole; the config's other plugins apply beside the route's own.
- With service discovery, the discovery type the upstream names, for example
dns, must be enabled in the gateway's static configuration first. Without it every request is refused with503. - Client addresses — Behind a load balancer the client address the route is matched on is the load balancer's: the route is matched before its plugins run, so real-ip does not change it. To let clients in by their own address, leave Client addresses empty and add real-ip and ip-restriction to the route.
Settings
The fields as the console shows them, section by section. A field under “When … is …” is there only while that value is set. A field that holds the settings of another kind links to that kind’s page.
General
namestringRequireddescstringlabelsobjectKey and value pairs of your own, for grouping and filtering. They change nothing about traffic. An object whose keys are any name, each with a string as its value.
statusintegerdefault 1A disabled route stays configured but serves no traffic.
Matching
A request is routed here when every rule below holds.
urisarray of stringsRequired, or uri insteadRequest paths this route matches. A trailing * matches everything under the path. A single value may be stored under uri instead.
methodsarray of stringsLeave all unchecked to match any method.
hostsarray of stringsLeave empty to match any host. A leading * matches subdomains, for example *.example.com. A single value may be stored under host instead.
remote_addrsarray of stringsIPv4 or IPv6 address or CIDR, for example 10.0.0.0/8. Behind a load balancer this is the load balancer's address, not the client's: the route is matched before its plugins run, so real-ip does not change it. A single value may be stored under remote_addr instead.
priorityintegerdefault 0Orders the routes that share a Path and a Host: among them, the one with the higher priority is tried first. An exact Host comes before a * Host and both before none; then an exact Path before a trailing *, and a longer Path before a shorter one. Priority is looked at last.
Conditions
varsarrayVariables: uri, host, remote_addr, http_<header>, arg_<query>, cookie_<name>, post_arg_<name>. Negate turns a rule into its opposite.
Target
Where matching requests are sent.
Choose one:
Upstream
upstream_idstring or integerRequiredpattern
^[a-zA-Z0-9-_.]+$Service
service_idstring or integerRequiredpattern
^[a-zA-Z0-9-_.]+$Nodes on this route
upstreamobjectRequiredHas the settings of an upstream, kept here and shared with nothing; each field is described on that page.
enable_websocketbooleanLets a matching request upgrade the connection to WebSocket.
Timeouts
timeoutobjectSet together: once any of connect, send and read is given, all three are required. Left out, the engine's own timeouts apply.
timeout.connectnumberRequiredtimeout.readnumberRequiredtimeout.sendnumberRequiredPlugins
Plugins on this route are managed in the Plugins tab once the route exists.
pluginsobjectRequired when Target is NoneThe plugins of this route, keyed by plugin name. The engine runs them by their priority, not by the order they are written in.
plugin_config_idstring or integerPlugins from the config apply to this route as well.
pattern
^[a-zA-Z0-9-_.]+$