Skip to content

Commit 3656233

Browse files
authored
Merge pull request #1408 from tkan145/THREESCALE-9542-buffering-policy
[THREESCALE-9542] Part 1: buffering policy
2 parents ac68219 + 5bda7fc commit 3656233

File tree

10 files changed

+378
-67
lines changed

10 files changed

+378
-67
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1616
### Added
1717

1818
- Detect number of CPU shares when running on Cgroups V2 [PR #1410](https://github.com/3scale/apicast/pull/1410) [THREESCALE-10167](https://issues.redhat.com/browse/THREESCALE-10167)
19-
### Added
2019

21-
* Add support to use Basic Authentication with the forward proxy. [PR #1409](https://github.com/3scale/APIcast/pull/1409)
20+
- Add support to use Basic Authentication with the forward proxy. [PR #1409](https://github.com/3scale/APIcast/pull/1409)
21+
22+
- Added request unbuffered policy [PR #1408](https://github.com/3scale/APIcast/pull/1408) [THREESCALE-9542](https://issues.redhat.com/browse/THREESCALE-9542)
2223

2324
## [3.14.0] 2023-07-25
2425

gateway/conf.d/apicast.conf

Lines changed: 16 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -78,71 +78,25 @@ location @upstream {
7878
require('resty.ctx').apply()
7979
}
8080

81-
#{% capture proxy_cache_valid %}
82-
#{#} proxy_cache $cache_zone;
83-
#{#} proxy_cache_key $scheme$request_method$proxy_host$request_uri$service_id;
84-
#{#} proxy_no_cache $cache_request;
85-
#{#} proxy_cache_valid {{ env.APICAST_CACHE_STATUS_CODES | default: '200 302'}} {{ env.APICAST_CACHE_MAX_TIME | default: '1m' }};
86-
#{% endcapture %}
87-
#{{ proxy_cache_valid | replace: "#{#}", "" }}
88-
#
89-
90-
#{% if opentelemetry != empty %}
91-
# {% capture opentelemetry_propagate_directive %}
92-
#{#} opentelemetry_propagate;
93-
# {% endcapture %}
94-
# {{ opentelemetry_propagate_directive | replace: "#{#}", "" }}
95-
#{% endif %}
81+
proxy_request_buffering on;
82+
#{% include "conf.d/upstream_shared.conf" %}
9683

97-
proxy_pass $proxy_pass;
84+
# these are duplicated so when request is redirected here those phases are executed
85+
post_action @out_of_band_authrep_action;
86+
body_filter_by_lua_block { require('apicast.executor'):body_filter() }
87+
header_filter_by_lua_block { require('apicast.executor'):header_filter() }
88+
}
9889

99-
proxy_http_version 1.1;
100-
proxy_set_header X-Real-IP $remote_addr;
101-
proxy_set_header Host $http_host;
102-
proxy_set_header X-3scale-proxy-secret-token $secret_token;
103-
proxy_set_header X-3scale-debug "";
104-
proxy_set_header Connection $upstream_connection_header;
105-
proxy_set_header Upgrade $upstream_upgrade_header;
106-
107-
# This is a bit tricky. It uses liquid to set a SSL client certificate. In
108-
# NGINX, all this is not executed as it is commented with '#'. However, in
109-
# Liquid, all this will be evaluated. As a result, the following directives
110-
# are set optionally: proxy_ssl_certificate, proxy_ssl_certificate_key,
111-
# proxy_ssl_session_reuse, and proxy_ssl_password_file.
112-
113-
# {% if proxy_ssl_certificate != empty and proxy_ssl_certificate_key != empty %}
114-
# {% capture proxy_ssl %}
115-
#{#} proxy_ssl_certificate {{ proxy_ssl_certificate }};
116-
#{#} proxy_ssl_certificate_key {{ proxy_ssl_certificate_key }};
117-
# {% endcapture %}
118-
# {{ proxy_ssl | replace: "#{#}", "" }}
119-
#
120-
# {% if proxy_ssl_password_file != empty %}
121-
# {% capture proxy_ssl %}
122-
#{#} proxy_ssl_password_file {{ proxy_ssl_password_file }};
123-
# {% endcapture %}
124-
# {{ proxy_ssl | replace: "#{#}", "" }}
125-
# {% endif %}
126-
#
127-
# {% if proxy_ssl_session_reuse != empty %}
128-
# {% capture proxy_ssl %}
129-
#{#} proxy_ssl_session_reuse {{ proxy_ssl_session_reuse }};
130-
# {% endcapture %}
131-
# {{ proxy_ssl | replace: "#{#}", "" }}
132-
# {% endif %}
133-
# {% endif %}
90+
location @upstream_request_unbuffered {
91+
internal;
92+
93+
rewrite_by_lua_block {
94+
require('resty.ctx').apply()
95+
}
96+
97+
proxy_request_buffering off;
98+
#{% include "conf.d/upstream_shared.conf" %}
13499

135-
# When 'upstream_retry_cases' is empty, apply the same default as NGINX.
136-
# If the proxy_next_upstream directive is not declared, the retry policy
137-
# will never retry.
138-
# {% if upstream_retry_cases != empty %}
139-
# {% capture proxy_next_upstream %}
140-
#{#} proxy_next_upstream {{ upstream_retry_cases }};
141-
# {% endcapture %}
142-
# {{ proxy_next_upstream | replace: "#{#}", "" }}
143-
# {% else %}
144-
# proxy_next_upstream error timeout;
145-
# {% endif %}
146100
# these are duplicated so when request is redirected here those phases are executed
147101
post_action @out_of_band_authrep_action;
148102
body_filter_by_lua_block { require('apicast.executor'):body_filter() }
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#{% capture proxy_cache_valid %}
2+
#{#} proxy_cache $cache_zone;
3+
#{#} proxy_cache_key $scheme$request_method$proxy_host$request_uri$service_id;
4+
#{#} proxy_no_cache $cache_request;
5+
#{#} proxy_cache_valid {{ env.APICAST_CACHE_STATUS_CODES | default: '200 302'}} {{ env.APICAST_CACHE_MAX_TIME | default: '1m' }};
6+
#{% endcapture %}
7+
#{{ proxy_cache_valid | replace: "#{#}", "" }}
8+
#
9+
10+
#{% if opentelemetry != empty %}
11+
# {% capture opentelemetry_propagate_directive %}
12+
#{#} opentelemetry_propagate;
13+
# {% endcapture %}
14+
# {{ opentelemetry_propagate_directive | replace: "#{#}", "" }}
15+
#{% endif %}
16+
17+
proxy_pass $proxy_pass;
18+
19+
proxy_http_version 1.1;
20+
21+
proxy_set_header X-Real-IP $remote_addr;
22+
proxy_set_header Host $http_host;
23+
proxy_set_header X-3scale-proxy-secret-token $secret_token;
24+
proxy_set_header X-3scale-debug "";
25+
proxy_set_header Connection $upstream_connection_header;
26+
proxy_set_header Upgrade $upstream_upgrade_header;
27+
28+
# This is a bit tricky. It uses liquid to set a SSL client certificate. In
29+
# NGINX, all this is not executed as it is commented with '#'. However, in
30+
# Liquid, all this will be evaluated. As a result, the following directives
31+
# are set optionally: proxy_ssl_certificate, proxy_ssl_certificate_key,
32+
# proxy_ssl_session_reuse, and proxy_ssl_password_file.
33+
34+
# {% if proxy_ssl_certificate != empty and proxy_ssl_certificate_key != empty %}
35+
# {% capture proxy_ssl %}
36+
#{#} proxy_ssl_certificate {{ proxy_ssl_certificate }};
37+
#{#} proxy_ssl_certificate_key {{ proxy_ssl_certificate_key }};
38+
# {% endcapture %}
39+
# {{ proxy_ssl | replace: "#{#}", "" }}
40+
#
41+
# {% if proxy_ssl_password_file != empty %}
42+
# {% capture proxy_ssl %}
43+
#{#} proxy_ssl_password_file {{ proxy_ssl_password_file }};
44+
# {% endcapture %}
45+
# {{ proxy_ssl | replace: "#{#}", "" }}
46+
# {% endif %}
47+
#
48+
# {% if proxy_ssl_session_reuse != empty %}
49+
# {% capture proxy_ssl %}
50+
#{#} proxy_ssl_session_reuse {{ proxy_ssl_session_reuse }};
51+
# {% endcapture %}
52+
# {{ proxy_ssl | replace: "#{#}", "" }}
53+
# {% endif %}
54+
# {% endif %}
55+
56+
# When 'upstream_retry_cases' is empty, apply the same default as NGINX.
57+
# If the proxy_next_upstream directive is not declared, the retry policy
58+
# will never retry.
59+
# {% if upstream_retry_cases != empty %}
60+
# {% capture proxy_next_upstream %}
61+
#{#} proxy_next_upstream {{ upstream_retry_cases }};
62+
# {% endcapture %}
63+
# {{ proxy_next_upstream | replace: "#{#}", "" }}
64+
# {% else %}
65+
# proxy_next_upstream error timeout;
66+
# {% endif %}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# APICast Request Unbuffered
2+
3+
This policy allows to disable request buffering
4+
5+
## Example configuration
6+
7+
```
8+
{
9+
"name": "request_unbuffered",
10+
"version": "builtin",
11+
"configuration": {}
12+
}
13+
```
14+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"$schema": "http://apicast.io/policy-v1/schema#manifest#",
3+
"name": "Request Unbuffered",
4+
"summary": "Disable request buffering",
5+
"description": [
6+
"Disable request buffering. This is useful when proxying big payloads with HTTP/1.1 chunked encoding"
7+
],
8+
"version": "builtin",
9+
"configuration": {
10+
"type": "object",
11+
"properties": {}
12+
}
13+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
return require('request_unbuffered')
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-- Request Unbuffered policy
2+
-- This policy will disable request buffering
3+
4+
local policy = require('apicast.policy')
5+
local _M = policy.new('request_unbuffered')
6+
7+
local new = _M.new
8+
9+
--- Initialize a buffering
10+
-- @tparam[opt] table config Policy configuration.
11+
function _M.new(config)
12+
local self = new(config)
13+
return self
14+
end
15+
16+
function _M:export()
17+
return {
18+
request_unbuffered = true,
19+
}
20+
end
21+
22+
return _M

gateway/src/apicast/upstream.lua

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,15 @@ function _M:set_keepalive_key(context)
210210
end
211211
end
212212

213+
local function get_upstream_location_name(context)
214+
if context.upstream_location_name then
215+
return context.upstream_location_name
216+
end
217+
if context.request_unbuffered then
218+
return "@upstream_request_unbuffered"
219+
end
220+
end
221+
213222
--- Execute the upstream.
214223
--- @tparam table context any table (policy context, ngx.ctx) to store the upstream for later use by balancer
215224
function _M:call(context)
@@ -242,9 +251,9 @@ function _M:call(context)
242251

243252
self:set_keepalive_key(context or {})
244253
if not self.servers then self:resolve() end
245-
if context.upstream_location_name then
246-
self.location_name = context.upstream_location_name
247-
end
254+
255+
local upstream_location_name = get_upstream_location_name(context)
256+
self:update_location(upstream_location_name)
248257
context[self.upstream_name] = self
249258

250259
return exec(self)

spec/upstream_spec.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,22 @@ describe('Upstream', function()
217217
assert.spy(ngx.exec).was_called_with(upstream.location_name)
218218
end)
219219

220+
it('executes the upstream location when request_unbuffered provided in the context', function()
221+
local contexts = {
222+
["buffered_request"] = {ctx={}, upstream_location="@upstream"},
223+
["unbuffered_request"] = {ctx={request_unbuffered=true}, upstream_location="@upstream_request_unbuffered"},
224+
["upstream_location and buffered_request"] = {ctx={upstream_location_name="@grpc", request_unbuffered=true}, upstream_location="@grpc"},
225+
["upstream_location and unbuffered_request"] = {ctx={upstream_location_name="@grpc"}, upstream_location="@grpc"},
226+
}
227+
228+
for _, value in pairs(contexts) do
229+
local upstream = Upstream.new('http://localhost')
230+
upstream:call(value.ctx)
231+
232+
assert.spy(ngx.exec).was_called_with(value.upstream_location)
233+
end
234+
end)
235+
220236
it('skips executing the upstream location when missing', function()
221237
local upstream = Upstream.new('http://localhost')
222238
upstream.location_name = nil

0 commit comments

Comments
 (0)