Usage and Tracking Handler for the API Gateway.
This module is under active development and is considered production ready.
This library requires an nginx build with OpenSSL, the ngx_lua module, LuaJIT 2.0, api-gateway-zmq-logger, api-gateway-request-validation, and api-gateway-hmac module.
This module is usually used for enforcing rate limiting and throttling policies. It is by design that business rules for the policies are not implemented in this module. A separate microservice should obtain usage information in order to track the requests and then notify the gateways when an action needs to be taken.
Bellow are the design principles used by this module:
- Async. Requests are not blocked while checking the remaining quotas.
- Quotas are tracked into a centralized microservice.
- The microservice should notify all the Gateway nodes when a limit is reached.
- The Gateways only enforce policies but are not aware of the business rules of the policies.
- Non blocking. The impact on the request time should be as little as possible. Some actions are still blocking when stopping or delaying requests but for other cases such as logging the requests the actions should be non-blocking.
- High-performance. Be able to sustain hundreds of thousands of requests per second.
- Enforcing limits should not downgrade the performance of the Gateway.
- It should avoid congesting the network.
- It should avoid congesting the number of available ports.
- It should cause no downtime of the Gateway.
- Enforcing limits should not downgrade the performance of the Gateway.
- Adaptive. Nodes may come up or go down at any time.
- There's an allowance of 2 - 5% overflow on the imposed limits. ( a limit of 10,000 requests per second may allow 10,500 requests per second in reality ).
- Fail-safe. In the event that the microservice component goes down all traffic should be permitted until it recovers.
A Tracking Domain is a formula involving a group of variables and their associated values.
It represents an identifier used to match requests that require a special action ( track
, block
, delay
, rewrite
).
The table bellow provides some examples of tracking domains:
Variable | Expected Value | Used for tracking requests ... |
---|---|---|
$service_id |
service_1 |
... hitting the service_1 service. |
$app_name |
my_application |
... generated by my_application calling any service. |
$service_id;$app_name |
service_1;my_application |
... generated by my_application but limited to the service_1 service. |
$service_id;$request_method |
service_1;POST |
... hitting the service_1 service using the POST http method. |
Pretty much any NGINX variable or user defined variable can be used to create the domain for tracking requests. This includes HTTP headers, query parameters, or URI parts.
The Expected Value can also be a wildcard (*
) besides a static value. This is useful to track any value for a given variable.
A Tracking Rule is a tuple created from a tracking domain, an expiration time, and an action.
For example to block
all requests having the header x-api-key:1234
the following rule can be added:
{
"id": 777,
"domain" : "1234",
"format": "$http_x_api_key",
"expire_at_utc": 1408065588203,
"action" : "BLOCK"
}
id
- is an identifier for the ruledomain
andformat
- is the actual domain as defined above.domain
field holds the Expected Value for the variables defined in theformat
field.$http_x_api_key
is an NGINX variable holding the value of the headerx-api-key
.expire_at_utc
- the timestamp in UTC when this rule should expire. In order to enforce this rule for100ms
the rule has to specify a timestamp with+100ms
in the future from the current time.action
- describes what to do when a request matches the tracking domain and in this example the action is toBLOCK
the request returning most probably a429
Status Code.
This module implements the following actions:
This action is useful to log usage information. This info is pushed into a message queue. The default implementation uses ZMQ. As mentioned above this module relies on a microservice to use this data and decide which action to be taken next.
In the example bellow the following Tracking Rule logs all requests containing a header named X-Api-Key
with a value of 1234
:
{
"id": 777,
"domain" : "1234",
"format": "$http_x_api_key",
"expire_at_utc": 1408065588203,
"action" : "TRACK"
}
It's also possible to specify a wildcard for the http_x_api_key
variable in order to precisely capture all its possible values:
{
"id": 777,
"domain" : "example.com;*",
"format": "$host;$http_x_api_key",
"expire_at_utc": 1408065588203,
"action" : "TRACK"
}
In this case the Gateway logs the value of the $host
along with the value of the X-Api-Key
header. This information can potentially be used to enforce a different quota per individual api-keys.
This action blocks requests with a 429
HTTP response code until the rule expires.
{
"id": 10,
"domain" : "1234",
"format": "$http_x_api_key",
"expire_at_utc": 1408065588203,
"action" : "BLOCK"
}
This action slows down requests by delaying them. The current implementation uses a random number between the specified delay
and delay/2
. For example for a delay of 5
the incoming requests may be delayed with 2.5
seconds to 5
seconds.
Delaying strategy works great for short spikes in traffic. It is best to start delaying requests before blocking them.
The following rule delays requests with the header X-Api-Key:1234
for max 5
seconds:
{
"id": 10,
"domain" : "1234",
"format": "$http_x_api_key",
"expire_at_utc": 1408065588203,
"action" : "DELAY",
"data": 5
}
The data
field is used to specify the delay
.
This action enables a per request routing override. It can be used for blue-green deployments or canary traffic.
The following rule enables a rewrite for a specific X-Api-Key
header:
{
"id": 200,
"domain" : "1234",
"format": "$http_x_api_key",
"expire_at_utc": 1583910454,
"action" : "REWRITE",
"meta": "canary.example.com"
}
# initial setup
# ...
init_worker_by_lua_block {
-- initialize a ZMQ Logger
local ZmqLogger = require "api-gateway.zmq.ZeroMQLogger"
local zmqLogger = ZmqLogger:new()
zmqLogger:connect(ZmqLogger.SOCKET_TYPE.ZMQ_PUB, "ipc:///tmp/nginx_queue_listen")
ngx.apiGateway = ngx.apiGateway or {}
ngx.apiGateway.zmqLogger = zmqLogger
ngx.apiGateway.validation = require "api-gateway.validation.factory" -- enforce ACTIONs
ngx.apiGateway.tracking = require "api-gateway.tracking.factory" -- track requests
}
# dictionaries used for storing the throttling / rate limiting rules
lua_shared_dict tracking_rules_dict 5m;
lua_shared_dict blocking_rules_dict 5m;
lua_shared_dict delaying_rules_dict 5m;
lua_shared_dict rewriting_rules_dict 5m;
# ...
#
# default validator for service plans that looks for Blocking rules to see if they match the request
#
location /validate_service_plan {
internal;
content_by_lua_block {
ngx.apiGateway.tracking.validateServicePlan()
}
}
location /t {
set $service_plan_validator "on; path=/validate_service_plan; ";
access_by_lua_block {
ngx.apiGateway.validation.validateRequest() -- enforce ACTIONS
}
...
log_by_lua_block {
ngx.apiGateway.tracking.track() -- track requests
}
}
Expose a simple HTTP Service to register rules and list the active ones:
server {
listen 5000;
server_name $hostname;
include tracking_service.conf;
}
See tracking_service.conf that details the HTTP endpoints.
The following request adds a new blocking rule (expiring on 7/1/2016
):
curl -i -X POST http://<docker_host_ip>:5000/tracking \
--data '{ "id": 10, "domain" : "1234", "format": "$http_x_api_key", "expire_at_utc": 1467331200000, "action" : "BLOCK"}'
{"result":"success"}
To check which rules are active:
curl http://<docker_host_ip>:5000/tracking/block
[{"domain":"1234","format":"$http_x_api_key","id":10,"action":"BLOCK","expire_at_utc":1467331200000}]
make test-docker
Test files are located in test/perl
folder and are based on the test-nginx
library.
This library is added as a git submodule under test/resources/test-nginx/
folder, from https://github.com/agentzh/test-nginx
.
The other libraries such as Redis
, test-nginx
would be located in test/resources/
.
Other files used when running the test are also located in test/resources
.
If you want to run a single test edit docker-compose.yml and replace in entrypoint
/tmp/perl
with the actual path to the test ( i.e. /tmp/perl/my_test.t
)
The complete entrypoint
config would look like:
entrypoint: ["prove", "-I", "/usr/local/test-nginx-0.24/lib", "-I", "/usr/local/test-nginx-0.24/inc", "-r", "/tmp/perl/my_test.t"]
This will only run my_test.t
test file.
The Makefile also exposes a way to run the tests using a native binary:
make test
This is intended to be used when the native binary is present and available on $PATH
.