-
Notifications
You must be signed in to change notification settings - Fork 170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add document that explains the new policies system #512
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
# APIcast policies | ||
|
||
The behaviour of APIcast is customizable via policies. A policy basically tells | ||
APIcast what it should do in each of the nginx phases. This document details | ||
how policies work and how you can write your own and integrate them into | ||
APIcast. | ||
|
||
- [**Policies**](#policies) | ||
- [**Policy chains**](#policy-chains) | ||
- [**APIcast default policies**](#apicast-default-policies) | ||
- [**Write your own policy**](#write-your-own-policy) | ||
- [**Integrate your policies**](#integrate-your-policies) | ||
|
||
|
||
## Policies | ||
|
||
A policy tells APIcast what it should do in each of the nginx phases: `init`, | ||
`init_worker`, `rewrite`, `access`, `balancer`, `header_filter`, `body_filter`, | ||
`post_action`, and `log`. | ||
|
||
Policies can share data between them. They do that through what we call the | ||
`context`. Policies can read from and modify that context in every phase. | ||
|
||
|
||
## Policy chains | ||
|
||
Policies can be combined using a policy chain. A policy chain is simply a | ||
sorted list of policies. | ||
|
||
The way policy chains work is as follows: suppose that we have a policy A that | ||
describes what to do in the `rewrite` and `header_filter` phases and a policy B | ||
that describes what to run in `access` and `header_filter`. Assume also that | ||
when describing the chain, we indicate that policy A should be run before | ||
policy B. When APIcast receives a request, it will check the policy chain | ||
described to see what it should run on each phase: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what would the term/name to use here? "policy chain definition" (sustantivo) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure what you mean here @andrewdavidmackenzie |
||
- rewrite: execute the function policy A provides for this phase. | ||
- access: execute the function policy B provides for this phase. | ||
- balancer: do nothing. Neither policy A nor B describe what to do. | ||
- header_filter: execute first the function policy A provides for this phase | ||
and then the function policy B provides for this phase. Remember that policy | ||
chains define an order, and we specified that policy A comes before policy B. | ||
- body_filter: do nothing. Neither policy A nor B describe what to do. | ||
- post_action: do nothing. Neither policy A nor B describe what to do. | ||
- log: do nothing. Neither policy A nor B describe what to do. | ||
|
||
Notice that we did not indicate what APIcast does in the `init` and the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of describing "in the negative", explain this as part of APIcast start-up before this, then move onto the description of handling requests? |
||
`init_worker` phases. The reason is that those two are not executed in every | ||
request. `init` is executed when APIcast boots, and `init_worker` when each | ||
of each of its workers start. | ||
|
||
### Types | ||
|
||
There are two types of policy chains in APIcast: per-service chains and a | ||
global chain. Both of them can be configured. | ||
|
||
As the name indicates, per-service chains allow us to define a specific chain | ||
for a given service. This means that we can apply different policies to our | ||
services, or the same policies but configured differently, or in a different | ||
order. On the other hand, there's only one global chain, and the behavior it | ||
defines applies to all the services. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so, policies defined by both chains will be run? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, per-service are included as part of the global one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The order of policies in the global chain is configurable. The order of policies in per-service chains is also configurable. |
||
|
||
|
||
## APIcast default policies | ||
|
||
By default, APIcast applies the `apicast` policy to all the services. This | ||
policy includes all the functionality offered by APIcast (mapping rules | ||
matching, authorization and reporting against 3scale backend, etc.). In the | ||
future, this policy will be split and each of the resulting policies will be | ||
replaceable with custom ones. | ||
|
||
|
||
## Write your own policy | ||
|
||
To write your own policy you need to write a Lua module that instantiates a | ||
[Policy](../gateway/src/apicast/policy.lua) and defines a method for each of | ||
the phases where it needs to execute something. | ||
|
||
Suppose that we wanted to run a policy that logged a message in the `rewrite` | ||
and the `header_filter` phases. This is how our module would look | ||
like: | ||
```lua | ||
local policy = require('apicast.policy') | ||
local _M = policy.new('My custom policy') | ||
|
||
function _M:rewrite() | ||
ngx.log(ngx.INFO, 'Passing through the rewrite phase.') | ||
end | ||
|
||
function _M:header_filter() | ||
ngx.log(ngx.INFO, 'Passing through the header_filter phase') | ||
end | ||
|
||
return _M | ||
``` | ||
|
||
If we wanted to read or write in the `context`: | ||
```lua | ||
function _M:rewrite(context) | ||
-- Read something from the context. | ||
local smth = context.something | ||
|
||
-- Write 'b' into the context so other policies or later phases can read it. | ||
context.b = 'something_useful_to_be_shared' | ||
end | ||
``` | ||
|
||
Policies can also have a configuration: | ||
```lua | ||
local policy = require('apicast.policy') | ||
local _M = policy.new('My custom policy') | ||
local new = _M.new | ||
|
||
function _M.new(config) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe explain when new() is called? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that documenting when it's instantiated it's too much detail for this document. |
||
local self = new() | ||
|
||
-- 'config' contains two params that define the behaviour of our policy and | ||
-- we want to access them from other phases. | ||
self.option_a = config.option_a | ||
self.option_b = config.option_b | ||
|
||
return self | ||
end | ||
``` | ||
|
||
In the [policy folder](../gateway/src/apicast/policy) you can find several | ||
policies. These ones are quite simple and can be used as examples to write your | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "They are quite..." There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that's correct, because I'm not listing all of them, just some examples. |
||
own: | ||
- [Echo](../gateway/src/apicast/policy/echo.lua) | ||
- [Phase logger](../gateway/src/apicast/policy/phase_logger.lua) | ||
- [CORS](../gateway/src/apicast/policy/cors.lua) | ||
- [Headers](../gateway/src/apicast/policy/headers.lua) | ||
|
||
|
||
## Integrate your policies | ||
|
||
At some point, it will be possible to configure policies and policy chains | ||
through the 3scale UI, but you can also configure them in APIcast using a | ||
configuration file. Remember that APIcast allows to specify a config file using | ||
the `THREESCALE_CONFIG_FILE` env variable: | ||
```shell | ||
THREESCALE_CONFIG_FILE=my_config.json bin/apicast | ||
``` | ||
|
||
Policies are specified in the `proxy` object of each service. A policy chain is | ||
a JSON array where each object represents a policy with a name and an optional | ||
configuration. Notice that the configuration is received in the `new` method of | ||
the policy as shown above. | ||
|
||
Here's an example that shows how to define a policy chain with the CORS policy | ||
and the APIcast one. Please note that the configuration of services is more | ||
complex than shown here. This is a very simplified version to illustrate how | ||
policies can be configured: | ||
```json | ||
{ | ||
"services":[ | ||
{ | ||
"id":42, | ||
"proxy":{ | ||
"policy_chain":[ | ||
{ | ||
"name":"apicast.policy.cors", | ||
"configuration":{ | ||
"allow_headers":["X-Custom-Header-1","X-Custom-Header-2"], | ||
"allow_methods":["POST","GET","OPTIONS"], | ||
"allow_origin":"*", | ||
"allow_credentials":false | ||
} | ||
}, | ||
{ | ||
"name":"apicast.policy.apicast" | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
``` | ||
|
||
If instead of configuring the policy chain of a specific service, you want to | ||
customize the global policy chain, you can take a look at | ||
[this example](../examples/policy_chain/README.md). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe say "share data between phases" to be even more explicit (or people could understand sharing between policies)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both things are possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, whatever you write in the context will be available in later phases of the same policy and in other policies as well. Do you think that's clear in the document?