Skip to content

Commit d961e67

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 c4235cd commit d961e67

File tree

4 files changed

+158
-1
lines changed

4 files changed

+158
-1
lines changed

src/common/proxy_wasm/ngx_proxy_wasm_host.c

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,96 @@ 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 *http_prefix = "ngx\0http\0";
893+
static const int http_prefix_len = 9;
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 > http_prefix_len &&
907+
memcmp(path_data, http_prefix, http_prefix_len) == 0)
908+
{
909+
name.data = (u_char *)(path_data + http_prefix_len);
910+
name.len = path_size - http_prefix_len;
911+
912+
hash = hash_str(name.data, name.len);
913+
914+
rctx = ngx_http_proxy_wasm_get_rctx(instance);
915+
916+
vv = ngx_http_get_variable(rctx->r, &name, hash);
917+
918+
if (vv && !vv->not_found) {
919+
prop_data = vv->data;
920+
prop_size = vv->len;
921+
}
922+
}
923+
#endif
924+
925+
if (!prop_data) {
926+
return ngx_proxy_wasm_result_notfound(rets);
927+
}
928+
929+
p = ngx_proxy_wasm_alloc(fctx, prop_size);
930+
if (p == 0) {
931+
return ngx_proxy_wasm_result_err(rets);
932+
}
933+
934+
if (!ngx_wavm_memory_memcpy(instance->memory, p,
935+
prop_data, prop_size))
936+
{
937+
return ngx_proxy_wasm_result_invalid_mem(rets);
938+
}
939+
940+
*ret_data = p;
941+
*ret_size = prop_size;
942+
943+
return ngx_proxy_wasm_result_ok(rets);
944+
}
945+
946+
857947
static ngx_int_t
858948
ngx_proxy_wasm_hfuncs_resume_http_request(ngx_wavm_instance_t *instance,
859949
wasm_val_t args[], wasm_val_t rets[])
@@ -1296,7 +1386,7 @@ static ngx_wavm_host_func_def_t ngx_proxy_wasm_hfuncs[] = {
12961386
ngx_wavm_arity_i32 },
12971387

12981388
{ ngx_string("proxy_get_property"),
1299-
&ngx_proxy_wasm_hfuncs_nop,
1389+
&ngx_proxy_wasm_hfuncs_get_property,
13001390
ngx_wavm_arity_i32x4,
13011391
ngx_wavm_arity_i32 },
13021392

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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() * (blocks() * 4);
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.http.hostname';
21+
echo ok;
22+
}
23+
--- response_body
24+
ok
25+
--- error_log eval
26+
[
27+
qr/\[info\] .*? ngx.http.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.http.pid';
42+
echo ok;
43+
}
44+
--- response_body
45+
ok
46+
--- error_log eval
47+
[
48+
qr/\[info\] .*? ngx.http.pid: [0-9]+/,
49+
]
50+
--- no_error_log
51+
[error]

t/lib/proxy-wasm-tests/hostcalls/src/filter.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ impl TestHttpHostcalls {
124124
"/t/log/response_headers" => test_log_response_headers(self),
125125
"/t/log/response_body" => test_log_response_body(self),
126126
"/t/log/current_time" => test_log_current_time(self),
127+
"/t/log/property" => test_log_property(self),
127128

128129
/* send_local_response */
129130
"/t/send_local_response/status/204" => test_send_status(self, 204),

t/lib/proxy-wasm-tests/hostcalls/src/test_cases.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,21 @@ pub(crate) fn test_log_request_path(ctx: &mut TestHttpHostcalls) {
8080
info!("path: {}", path);
8181
}
8282

83+
pub(crate) fn test_log_property(ctx: &mut TestHttpHostcalls) {
84+
let name = ctx.config.get("name")
85+
.expect("expected a name argument");
86+
87+
match ctx.get_property(name.split(".").collect()) {
88+
Some(p) => {
89+
match std::str::from_utf8(&p) {
90+
Ok(value) => info!("{}: {}", name, value),
91+
Err(_) => panic!("failed converting {} to UTF-8", name),
92+
}
93+
},
94+
None => panic!("{} property not found", name),
95+
}
96+
}
97+
8398
pub(crate) fn test_send_status(ctx: &mut TestHttpHostcalls, status: u32) {
8499
ctx.send_http_response(status, vec![], None)
85100
}

0 commit comments

Comments
 (0)