Skip to content

Commit dcf1086

Browse files
committed
feat(proxy-wasm) add get_property, supporting Nginx variables
Implements the `get_property` call for the proxy-wasm SDK. The actual properties themselves are not listed by the proxy-wasm specification, so they seem to be proxy-specific. In this initial iteration, the only properties implemented are Nginx variables, obtained using the Nginx API via `ngx_http_get_variable` and hence exposed in the `ngx.http.*` property path. The ABI for property path values (that is, how they should be parsed by a host implementation) is not specified in the proxy-wasm spec docs either, so I have followed an implementation compatible with the input produced by the proxy-wasm-rust-sdk, which expects from the user a path as a vector of strings (`vec!["ngx", "http", "pid"]`) and produces a byte string to the host by joining them using `\0` bytes. With regard to property support, we could mimic the support for many Envoy attributes which are exposed via `get_property` by mapping them to the equivalent Nginx data. The list is available here: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes.html?highlight=attributes However, the future of `get_property` and `set_property` is currently put into question: they are not included in the vNEXT docs, at least as of their state in 2020: see https://github.com/proxy-wasm/spec/pull/1/files#r472951567 — "I've dropped `{get,set}_property` from this iteration, since the current implementation is not really portable (it's an opaque pass-through to CEL), and I wanted to get a consensus on this MVP first... but we should definitely add something that can be standardized in its place as soon as this is merged.". More info about the current lack of standardization of properties here: proxy-wasm/proxy-wasm-cpp-host#90 The Envoy properties seem to be the "de facto" properties for proxy-wasm and which properties are proxy-specific or proxy-independent are ill-defined (they just map to the Envoy internals), but on our side, we start from a clean slate by using the `ngx` namespace for entries that map 1-to-1 to Nginx values.
1 parent 7f20c12 commit dcf1086

File tree

5 files changed

+365
-1
lines changed

5 files changed

+365
-1
lines changed

src/common/proxy_wasm/ngx_proxy_wasm_host.c

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,99 @@ ngx_proxy_wasm_hfuncs_get_configuration(ngx_wavm_instance_t *instance,
854854
}
855855

856856

857+
#ifdef NGX_WASM_HTTP
858+
static ngx_uint_t
859+
hash_str(u_char *src, size_t n)
860+
{
861+
ngx_uint_t key;
862+
863+
key = 0;
864+
865+
while (n--) {
866+
key = ngx_hash(key, *src);
867+
src++;
868+
}
869+
870+
return key;
871+
}
872+
#endif
873+
874+
875+
static ngx_int_t
876+
ngx_proxy_wasm_hfuncs_get_property(ngx_wavm_instance_t *instance,
877+
wasm_val_t args[], wasm_val_t rets[])
878+
{
879+
ngx_wavm_ptr_t *ret_data;
880+
int32_t *ret_size;
881+
u_char *prop_data = NULL;
882+
int32_t prop_size = 0;
883+
ngx_wavm_ptr_t p = 0;
884+
ngx_proxy_wasm_filter_ctx_t *fctx;
885+
#ifdef NGX_WASM_HTTP
886+
const char *path_data;
887+
int32_t path_size;
888+
ngx_http_wasm_req_ctx_t *rctx;
889+
ngx_str_t name;
890+
ngx_uint_t hash;
891+
ngx_http_variable_value_t *vv;
892+
static const char *ngx_prefix = "ngx\0";
893+
static const ngx_int_t ngx_prefix_len = 4;
894+
#endif
895+
896+
fctx = ngx_proxy_wasm_instance2fctx(instance);
897+
898+
#ifdef NGX_WASM_HTTP
899+
path_data = ngx_wavm_memory_lift(instance->memory, args[0].of.i32);
900+
path_size = args[1].of.i32;
901+
#endif
902+
ret_data = ngx_wavm_memory_lift(instance->memory, args[2].of.i32);
903+
ret_size = ngx_wavm_memory_lift(instance->memory, args[3].of.i32);
904+
905+
#ifdef NGX_WASM_HTTP
906+
if (path_size > ngx_prefix_len &&
907+
ngx_memcmp(path_data, ngx_prefix, ngx_prefix_len) == 0)
908+
{
909+
name.data = (u_char *)(path_data + ngx_prefix_len);
910+
name.len = path_size - ngx_prefix_len;
911+
912+
hash = hash_str(name.data, name.len);
913+
914+
rctx = ngx_http_proxy_wasm_get_rctx(instance);
915+
if (!rctx) {
916+
return ngx_proxy_wasm_result_notfound(rets);
917+
}
918+
919+
vv = ngx_http_get_variable(rctx->r, &name, hash);
920+
921+
if (vv && !vv->not_found) {
922+
prop_data = vv->data;
923+
prop_size = vv->len;
924+
}
925+
}
926+
#endif
927+
928+
if (!prop_data) {
929+
return ngx_proxy_wasm_result_notfound(rets);
930+
}
931+
932+
p = ngx_proxy_wasm_alloc(fctx, prop_size);
933+
if (p == 0) {
934+
return ngx_proxy_wasm_result_err(rets);
935+
}
936+
937+
if (!ngx_wavm_memory_memcpy(instance->memory, p,
938+
prop_data, prop_size))
939+
{
940+
return ngx_proxy_wasm_result_invalid_mem(rets);
941+
}
942+
943+
*ret_data = p;
944+
*ret_size = prop_size;
945+
946+
return ngx_proxy_wasm_result_ok(rets);
947+
}
948+
949+
857950
static ngx_int_t
858951
ngx_proxy_wasm_hfuncs_resume_http_request(ngx_wavm_instance_t *instance,
859952
wasm_val_t args[], wasm_val_t rets[])
@@ -1296,7 +1389,7 @@ static ngx_wavm_host_func_def_t ngx_proxy_wasm_hfuncs[] = {
12961389
ngx_wavm_arity_i32 },
12971390

12981391
{ ngx_string("proxy_get_property"),
1299-
&ngx_proxy_wasm_hfuncs_nop,
1392+
&ngx_proxy_wasm_hfuncs_get_property,
13001393
ngx_wavm_arity_i32x4,
13011394
ngx_wavm_arity_i32 },
13021395

src/http/proxy_wasm/ngx_http_proxy_wasm.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ ngx_http_proxy_wasm_get_rctx(ngx_wavm_instance_t *instance)
2323

2424
fctx = ngx_proxy_wasm_instance2fctx(instance);
2525
pwctx = fctx->parent;
26+
if (!pwctx) {
27+
return NULL;
28+
}
29+
2630
rctx = (ngx_http_wasm_req_ctx_t *) pwctx->data;
2731

2832
return rctx;
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# vim:set ft= ts=4 sts=4 sw=4 et fdm=marker:
2+
3+
use strict;
4+
use lib '.';
5+
use t::TestWasm;
6+
7+
skip_valgrind();
8+
9+
plan tests => repeat_each() * (4 * 4 + 6 * 5);
10+
11+
run_tests();
12+
13+
__DATA__
14+
15+
=== TEST 1: proxy_wasm - get_property() gets Nginx $hostname variable
16+
--- wasm_modules: hostcalls
17+
--- load_nginx_modules: ngx_http_echo_module
18+
--- config
19+
location /t {
20+
proxy_wasm hostcalls 'test=/t/log/property name=ngx.hostname';
21+
echo ok;
22+
}
23+
--- response_body
24+
ok
25+
--- error_log eval
26+
[
27+
qr/\[info\] .*? ngx.hostname: [a-z0-9]+/,
28+
]
29+
--- no_error_log
30+
[error]
31+
32+
33+
34+
=== TEST 2: proxy_wasm - get_property() gets an Nginx $pid variable
35+
All get_property calls for Nginx variables return strings, so this
36+
should print the $pid as ASCII numbers.
37+
--- wasm_modules: hostcalls
38+
--- load_nginx_modules: ngx_http_echo_module
39+
--- config
40+
location /t {
41+
proxy_wasm hostcalls 'test=/t/log/property name=ngx.pid';
42+
echo ok;
43+
}
44+
--- response_body
45+
ok
46+
--- error_log eval
47+
[
48+
qr/\[info\] .*? ngx.pid: [0-9]+/,
49+
]
50+
--- no_error_log
51+
[error]
52+
53+
54+
55+
=== TEST 3: proxy_wasm - get_property() reports if an ngx.* property is not found
56+
--- wasm_modules: hostcalls
57+
--- load_nginx_modules: ngx_http_echo_module
58+
--- config
59+
location /t {
60+
proxy_wasm hostcalls 'test=/t/log/property name=ngx.nonexistent_property';
61+
echo ok;
62+
}
63+
--- response_body
64+
ok
65+
--- error_log eval
66+
[
67+
qr/\[info\] .*? property not found: ngx.nonexistent_property/,
68+
]
69+
--- no_error_log
70+
[error]
71+
72+
73+
74+
=== TEST 4: proxy_wasm - get_property() reports if a generic property is not found
75+
--- wasm_modules: hostcalls
76+
--- load_nginx_modules: ngx_http_echo_module
77+
--- config
78+
location /t {
79+
proxy_wasm hostcalls 'test=/t/log/property name=nonexistent_property';
80+
echo ok;
81+
}
82+
--- response_body
83+
ok
84+
--- error_log eval
85+
[
86+
qr/\[info\] .*? property not found: nonexistent_property/,
87+
]
88+
--- no_error_log
89+
[error]
90+
91+
92+
93+
=== TEST 5: proxy_wasm - get_property() works on on_request_headers
94+
--- wasm_modules: hostcalls
95+
--- load_nginx_modules: ngx_http_echo_module
96+
--- config
97+
location /t {
98+
proxy_wasm hostcalls 'on=request_headers test=/t/log/property name=ngx.hostname';
99+
echo ok;
100+
}
101+
--- response_body
102+
ok
103+
--- error_log eval
104+
[
105+
qr/\[info\] .*? \[hostcalls\] on_request_headers/,
106+
qr/\[info\] .*? ngx.hostname: [a-z0-9]+/,
107+
]
108+
--- no_error_log
109+
[error]
110+
111+
112+
113+
=== TEST 6: proxy_wasm - get_property() works on on_request_body
114+
--- wasm_modules: hostcalls
115+
--- load_nginx_modules: ngx_http_echo_module
116+
--- config
117+
location /t {
118+
proxy_wasm hostcalls 'on=request_body test=/t/log/property name=ngx.hostname';
119+
echo ok;
120+
}
121+
--- request
122+
POST /t/echo/body
123+
ok
124+
--- response_body
125+
ok
126+
--- error_log eval
127+
[
128+
qr/\[info\] .*? \[hostcalls\] on_request_body/,
129+
qr/\[info\] .*? ngx.hostname: [a-z0-9]+/,
130+
]
131+
--- no_error_log
132+
[error]
133+
134+
135+
136+
=== TEST 7: proxy_wasm - get_property() works on on_response_headers
137+
--- wasm_modules: hostcalls
138+
--- load_nginx_modules: ngx_http_echo_module
139+
--- config
140+
location /t {
141+
proxy_wasm hostcalls 'on=response_headers test=/t/log/property name=ngx.hostname';
142+
echo ok;
143+
}
144+
--- response_body
145+
ok
146+
--- error_log eval
147+
[
148+
qr/\[info\] .*? \[hostcalls\] on_response_headers/,
149+
qr/\[info\] .*? ngx.hostname: [a-z0-9]+/,
150+
]
151+
--- no_error_log
152+
[error]
153+
154+
155+
156+
=== TEST 8: proxy_wasm - get_property() works on on_response_body
157+
--- wasm_modules: hostcalls
158+
--- load_nginx_modules: ngx_http_echo_module
159+
--- config
160+
location /t {
161+
proxy_wasm hostcalls 'on=response_body test=/t/log/property name=ngx.hostname';
162+
echo ok;
163+
}
164+
--- response_body
165+
ok
166+
--- error_log eval
167+
[
168+
qr/\[info\] .*? \[hostcalls\] on_response_body/,
169+
qr/\[info\] .*? ngx.hostname: [a-z0-9]+/,
170+
]
171+
--- no_error_log
172+
[error]
173+
174+
175+
176+
=== TEST 9: proxy_wasm - get_property() works on on_log
177+
--- wasm_modules: hostcalls
178+
--- load_nginx_modules: ngx_http_echo_module
179+
--- config
180+
location /t {
181+
proxy_wasm hostcalls 'on=log test=/t/log/property name=ngx.hostname';
182+
echo ok;
183+
}
184+
--- response_body
185+
ok
186+
--- error_log eval
187+
[
188+
qr/\[info\] .*? \[hostcalls\] on_log/,
189+
qr/\[info\] .*? ngx.hostname: [a-z0-9]+/,
190+
]
191+
--- no_error_log
192+
[error]
193+
194+
195+
196+
=== TEST 10: proxy_wasm - get_property() for ngx.* does not work on on_tick
197+
on_tick runs on the root context, so it does not have access to ngx_http_* calls.
198+
--- wasm_modules: hostcalls
199+
--- load_nginx_modules: ngx_http_echo_module
200+
--- config
201+
location /t {
202+
proxy_wasm hostcalls 'tick_period=10 test=/t/log/property name=ngx.hostname';
203+
echo_sleep 0.150;
204+
echo ok;
205+
}
206+
--- response_body
207+
ok
208+
--- error_log eval
209+
[
210+
qr/\[info\] .*? \[hostcalls\] on_tick/,
211+
qr/\[info\] .*? property not found: ngx.hostname/,
212+
]
213+
--- no_error_log
214+
[error]

0 commit comments

Comments
 (0)