Skip to content

Commit dd81bf7

Browse files
committed
Introduce transaction_id policy
1 parent 5beda44 commit dd81bf7

File tree

7 files changed

+488
-0
lines changed

7 files changed

+488
-0
lines changed

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1111

1212
- Fixed 3scale Batcher policy unable to handle `app_id`/`access_token` contains special characters [PR #1457](https://github.com/3scale/APIcast/pull/1457) [THREESCALE-10934](https://issues.redhat.com/browse/THREESCALE-10934)
1313

14+
### Added
15+
16+
- Introduce `transaction_id` policy [PR #][] [THREESCALE-10973](https://issues.redhat.com/browse/THREESCALE-10973)
17+
1418
## [3.15.0] 2024-04-04
1519

1620
### Fixed

Diff for: gateway/src/apicast/policy/transaction_id/README.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# APICast Transaction-ID
2+
3+
## Description
4+
5+
When enabled this policy adds a new header with unique ID to all of the request processed by APIcast. The unique ID header can also be included in the response to the client.
6+
7+
If the header is empty or non-existent, this policy will generate a UUID as the value of the user-defined header name
8+
9+
If a header with the same name is already present in the client request or upstream response, the policy will not modify it.
10+
11+
## Example configuration
12+
13+
```
14+
"policy_chain": [
15+
{
16+
"name": "transaction_id",
17+
"configuration": {
18+
"header_name": "X-Transaction-ID"
19+
},
20+
"version": "builtin",
21+
},
22+
{
23+
"name": "apicast.policy.apicast"
24+
}
25+
]
26+
```
27+
28+
Use with Logging policy
29+
30+
```
31+
"policy_chain": [
32+
{
33+
"name": "transaction_id",
34+
"configuration": {
35+
"header_name": "X-Transaction-ID"
36+
},
37+
"version": "builtin",
38+
},
39+
{
40+
"name": "apicast.policy.logging",
41+
"configuration": {
42+
"enable_access_logs": false,
43+
"custom_logging": "\"{{request}}\" to service {{service.id}} and {{service.name}} with ID {{req.headers.x_trasaction_id}}",
44+
}
45+
}
46+
]
47+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"$schema": "http://apicast.io/policy-v1/schema#manifest#",
3+
"name": "Transaction ID",
4+
"summary": "Add unique ID to the request/response header",
5+
"description": ["This policy adds a unique ID to each request and response header proxied through APIcast",
6+
"The policy will not add a unique ID if the request/response already has a header with the configured header_name."
7+
],
8+
"version": "builtin",
9+
"configuration": {
10+
"type": "object",
11+
"properties": {
12+
"header_name": {
13+
"type": "string",
14+
"description": "The HTTP header name to use for the transaction IDs",
15+
"default": "X-Transaction-ID"
16+
},
17+
"include_in_response": {
18+
"type": "boolean",
19+
"description": "Whether to include the header to the response",
20+
"default": false
21+
}
22+
}
23+
}
24+
}

Diff for: gateway/src/apicast/policy/transaction_id/init.lua

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
return require('transaction_id')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--- Transaction ID policy
2+
-- This policy add a uniqud ID to the user defined header. This can help to identify
3+
-- request from access log or the trace
4+
5+
local policy = require('apicast.policy')
6+
local _M = policy.new('Transaction ID', 'builtin')
7+
8+
local uuid = require 'resty.jit-uuid'
9+
10+
local new = _M.new
11+
12+
function _M.new(config)
13+
local self = new(config)
14+
local conf = config or {}
15+
self.header_name = conf.header_name
16+
self.include_in_response = conf.include_in_response or false
17+
18+
return self
19+
end
20+
21+
function _M:rewrite(context)
22+
local transaction_id = ngx.req.get_headers()[self.header_name]
23+
24+
if not transaction_id or transaction_id == "" then
25+
transaction_id = uuid.generate_v4()
26+
ngx.req.set_header(self.header_name, transaction_id)
27+
end
28+
29+
if self.include_in_response then
30+
context.transaction_id = transaction_id
31+
end
32+
end
33+
34+
function _M:header_filter(context)
35+
if not self.include_in_response then
36+
return
37+
end
38+
39+
local transaction_id = ngx.resp.get_headers()[self.header_name]
40+
if not transaction_id or transaction_id == "" then
41+
transaction_id = context.transaction_id
42+
end
43+
ngx.header[self.header_name] = transaction_id
44+
end
45+
46+
return _M

Diff for: spec/policy/transaction_id/transaction_id_spec.lua

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
local TransactionIDPolicy = require('apicast.policy.transaction_id')
2+
local uuid = require('resty.jit-uuid')
3+
4+
describe('fapi_1_baseline_profile policy', function()
5+
local ngx_req_headers = {}
6+
local ngx_resp_headers = {}
7+
local context = {}
8+
before_each(function()
9+
ngx.header = {}
10+
ngx_req_headers = {}
11+
ngx_resp_headers = {}
12+
context = {}
13+
stub(ngx.req, 'get_headers', function() return ngx_req_headers end)
14+
stub(ngx.req, 'set_header', function(name, value) ngx_req_headers[name] = value end)
15+
stub(ngx.resp, 'get_headers', function() return ngx_resp_headers end)
16+
stub(ngx.resp, 'set_header', function(name, value) ngx_resp_headers[name] = value end)
17+
end)
18+
19+
describe('.new', function()
20+
it('works without configuration', function()
21+
assert(TransactionIDPolicy.new())
22+
end)
23+
end)
24+
25+
describe('.rewrite', function()
26+
it('do not overwrite existing header', function()
27+
ngx_req_headers['transaction-id'] = 'abc'
28+
local config = {header_name='transaction-id'}
29+
local transaction_id_policy = TransactionIDPolicy.new(config)
30+
transaction_id_policy:rewrite()
31+
assert.same('abc', ngx.req.get_headers()['transaction-id'])
32+
end)
33+
34+
it('generate uuid if header does not exist', function()
35+
local config = {header_name='transaction-id'}
36+
local transaction_id_policy = TransactionIDPolicy.new(config)
37+
transaction_id_policy:rewrite()
38+
assert.is_true(uuid.is_valid(ngx.req.get_headers()['transaction-id']))
39+
end)
40+
41+
it('generate uuid if header is empty', function()
42+
ngx_req_headers['transaction-id'] = ''
43+
local config = {header_name='transaction-id'}
44+
local transaction_id_policy = TransactionIDPolicy.new(config)
45+
transaction_id_policy:rewrite()
46+
assert.is_true(uuid.is_valid(ngx.req.get_headers()['transaction-id']))
47+
end)
48+
end)
49+
50+
describe('.header_filter', function()
51+
it('set response transaction-id if configured', function()
52+
ngx_req_headers['transaction-id'] = 'abc'
53+
local config = {header_name='transaction-id', include_in_response=true}
54+
local transaction_id_policy = TransactionIDPolicy.new(config)
55+
transaction_id_policy:rewrite(context)
56+
transaction_id_policy:header_filter(context)
57+
assert.same('abc', ngx.header['transaction-id'])
58+
end)
59+
60+
it('set response transaction-id if configured - uuid', function()
61+
ngx_req_headers['transaction-id'] = ''
62+
local config = {header_name='transaction-id', include_in_response=true}
63+
local transaction_id_policy = TransactionIDPolicy.new(config)
64+
transaction_id_policy:rewrite(context)
65+
local id = ngx.req.get_headers()['transaction-id']
66+
transaction_id_policy:header_filter(context)
67+
assert.same(id, ngx.header['transaction-id'])
68+
end)
69+
70+
it('do not override if response contain the exisitng header', function()
71+
ngx_req_headers['transaction-id'] = 'abc'
72+
ngx_resp_headers['transaction-id'] = 'edf'
73+
local config = {header_name='transaction-id', include_in_response=true}
74+
local transaction_id_policy = TransactionIDPolicy.new(config)
75+
transaction_id_policy:rewrite(context)
76+
transaction_id_policy:header_filter(context)
77+
assert.same('edf', ngx.header['transaction-id'])
78+
end)
79+
end)
80+
end)

0 commit comments

Comments
 (0)