Skip to content

Commit

Permalink
feat(proxy-wasm) add get_property, supporting Nginx variables
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
hishamhm committed Jul 14, 2022
1 parent 7f20c12 commit dcf1086
Show file tree
Hide file tree
Showing 5 changed files with 365 additions and 1 deletion.
95 changes: 94 additions & 1 deletion src/common/proxy_wasm/ngx_proxy_wasm_host.c
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,99 @@ ngx_proxy_wasm_hfuncs_get_configuration(ngx_wavm_instance_t *instance,
}


#ifdef NGX_WASM_HTTP
static ngx_uint_t
hash_str(u_char *src, size_t n)
{
ngx_uint_t key;

key = 0;

while (n--) {
key = ngx_hash(key, *src);
src++;
}

return key;
}
#endif


static ngx_int_t
ngx_proxy_wasm_hfuncs_get_property(ngx_wavm_instance_t *instance,
wasm_val_t args[], wasm_val_t rets[])
{
ngx_wavm_ptr_t *ret_data;
int32_t *ret_size;
u_char *prop_data = NULL;
int32_t prop_size = 0;
ngx_wavm_ptr_t p = 0;
ngx_proxy_wasm_filter_ctx_t *fctx;
#ifdef NGX_WASM_HTTP
const char *path_data;
int32_t path_size;
ngx_http_wasm_req_ctx_t *rctx;
ngx_str_t name;
ngx_uint_t hash;
ngx_http_variable_value_t *vv;
static const char *ngx_prefix = "ngx\0";
static const ngx_int_t ngx_prefix_len = 4;
#endif

fctx = ngx_proxy_wasm_instance2fctx(instance);

#ifdef NGX_WASM_HTTP
path_data = ngx_wavm_memory_lift(instance->memory, args[0].of.i32);
path_size = args[1].of.i32;
#endif
ret_data = ngx_wavm_memory_lift(instance->memory, args[2].of.i32);
ret_size = ngx_wavm_memory_lift(instance->memory, args[3].of.i32);

#ifdef NGX_WASM_HTTP
if (path_size > ngx_prefix_len &&
ngx_memcmp(path_data, ngx_prefix, ngx_prefix_len) == 0)
{
name.data = (u_char *)(path_data + ngx_prefix_len);
name.len = path_size - ngx_prefix_len;

hash = hash_str(name.data, name.len);

rctx = ngx_http_proxy_wasm_get_rctx(instance);
if (!rctx) {
return ngx_proxy_wasm_result_notfound(rets);
}

vv = ngx_http_get_variable(rctx->r, &name, hash);

if (vv && !vv->not_found) {
prop_data = vv->data;
prop_size = vv->len;
}
}
#endif

if (!prop_data) {
return ngx_proxy_wasm_result_notfound(rets);
}

p = ngx_proxy_wasm_alloc(fctx, prop_size);
if (p == 0) {
return ngx_proxy_wasm_result_err(rets);
}

if (!ngx_wavm_memory_memcpy(instance->memory, p,
prop_data, prop_size))
{
return ngx_proxy_wasm_result_invalid_mem(rets);
}

*ret_data = p;
*ret_size = prop_size;

return ngx_proxy_wasm_result_ok(rets);
}


static ngx_int_t
ngx_proxy_wasm_hfuncs_resume_http_request(ngx_wavm_instance_t *instance,
wasm_val_t args[], wasm_val_t rets[])
Expand Down Expand Up @@ -1296,7 +1389,7 @@ static ngx_wavm_host_func_def_t ngx_proxy_wasm_hfuncs[] = {
ngx_wavm_arity_i32 },

{ ngx_string("proxy_get_property"),
&ngx_proxy_wasm_hfuncs_nop,
&ngx_proxy_wasm_hfuncs_get_property,
ngx_wavm_arity_i32x4,
ngx_wavm_arity_i32 },

Expand Down
4 changes: 4 additions & 0 deletions src/http/proxy_wasm/ngx_http_proxy_wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ ngx_http_proxy_wasm_get_rctx(ngx_wavm_instance_t *instance)

fctx = ngx_proxy_wasm_instance2fctx(instance);
pwctx = fctx->parent;
if (!pwctx) {
return NULL;
}

rctx = (ngx_http_wasm_req_ctx_t *) pwctx->data;

return rctx;
Expand Down
214 changes: 214 additions & 0 deletions t/03-proxy_wasm/117-proxy_get_property.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# vim:set ft= ts=4 sts=4 sw=4 et fdm=marker:

use strict;
use lib '.';
use t::TestWasm;

skip_valgrind();

plan tests => repeat_each() * (4 * 4 + 6 * 5);

run_tests();

__DATA__
=== TEST 1: proxy_wasm - get_property() gets Nginx $hostname variable
--- wasm_modules: hostcalls
--- load_nginx_modules: ngx_http_echo_module
--- config
location /t {
proxy_wasm hostcalls 'test=/t/log/property name=ngx.hostname';
echo ok;
}
--- response_body
ok
--- error_log eval
[
qr/\[info\] .*? ngx.hostname: [a-z0-9]+/,
]
--- no_error_log
[error]
=== TEST 2: proxy_wasm - get_property() gets an Nginx $pid variable
All get_property calls for Nginx variables return strings, so this
should print the $pid as ASCII numbers.
--- wasm_modules: hostcalls
--- load_nginx_modules: ngx_http_echo_module
--- config
location /t {
proxy_wasm hostcalls 'test=/t/log/property name=ngx.pid';
echo ok;
}
--- response_body
ok
--- error_log eval
[
qr/\[info\] .*? ngx.pid: [0-9]+/,
]
--- no_error_log
[error]
=== TEST 3: proxy_wasm - get_property() reports if an ngx.* property is not found
--- wasm_modules: hostcalls
--- load_nginx_modules: ngx_http_echo_module
--- config
location /t {
proxy_wasm hostcalls 'test=/t/log/property name=ngx.nonexistent_property';
echo ok;
}
--- response_body
ok
--- error_log eval
[
qr/\[info\] .*? property not found: ngx.nonexistent_property/,
]
--- no_error_log
[error]
=== TEST 4: proxy_wasm - get_property() reports if a generic property is not found
--- wasm_modules: hostcalls
--- load_nginx_modules: ngx_http_echo_module
--- config
location /t {
proxy_wasm hostcalls 'test=/t/log/property name=nonexistent_property';
echo ok;
}
--- response_body
ok
--- error_log eval
[
qr/\[info\] .*? property not found: nonexistent_property/,
]
--- no_error_log
[error]
=== TEST 5: proxy_wasm - get_property() works on on_request_headers
--- wasm_modules: hostcalls
--- load_nginx_modules: ngx_http_echo_module
--- config
location /t {
proxy_wasm hostcalls 'on=request_headers test=/t/log/property name=ngx.hostname';
echo ok;
}
--- response_body
ok
--- error_log eval
[
qr/\[info\] .*? \[hostcalls\] on_request_headers/,
qr/\[info\] .*? ngx.hostname: [a-z0-9]+/,
]
--- no_error_log
[error]
=== TEST 6: proxy_wasm - get_property() works on on_request_body
--- wasm_modules: hostcalls
--- load_nginx_modules: ngx_http_echo_module
--- config
location /t {
proxy_wasm hostcalls 'on=request_body test=/t/log/property name=ngx.hostname';
echo ok;
}
--- request
POST /t/echo/body
ok
--- response_body
ok
--- error_log eval
[
qr/\[info\] .*? \[hostcalls\] on_request_body/,
qr/\[info\] .*? ngx.hostname: [a-z0-9]+/,
]
--- no_error_log
[error]
=== TEST 7: proxy_wasm - get_property() works on on_response_headers
--- wasm_modules: hostcalls
--- load_nginx_modules: ngx_http_echo_module
--- config
location /t {
proxy_wasm hostcalls 'on=response_headers test=/t/log/property name=ngx.hostname';
echo ok;
}
--- response_body
ok
--- error_log eval
[
qr/\[info\] .*? \[hostcalls\] on_response_headers/,
qr/\[info\] .*? ngx.hostname: [a-z0-9]+/,
]
--- no_error_log
[error]
=== TEST 8: proxy_wasm - get_property() works on on_response_body
--- wasm_modules: hostcalls
--- load_nginx_modules: ngx_http_echo_module
--- config
location /t {
proxy_wasm hostcalls 'on=response_body test=/t/log/property name=ngx.hostname';
echo ok;
}
--- response_body
ok
--- error_log eval
[
qr/\[info\] .*? \[hostcalls\] on_response_body/,
qr/\[info\] .*? ngx.hostname: [a-z0-9]+/,
]
--- no_error_log
[error]
=== TEST 9: proxy_wasm - get_property() works on on_log
--- wasm_modules: hostcalls
--- load_nginx_modules: ngx_http_echo_module
--- config
location /t {
proxy_wasm hostcalls 'on=log test=/t/log/property name=ngx.hostname';
echo ok;
}
--- response_body
ok
--- error_log eval
[
qr/\[info\] .*? \[hostcalls\] on_log/,
qr/\[info\] .*? ngx.hostname: [a-z0-9]+/,
]
--- no_error_log
[error]
=== TEST 10: proxy_wasm - get_property() for ngx.* does not work on on_tick
on_tick runs on the root context, so it does not have access to ngx_http_* calls.
--- wasm_modules: hostcalls
--- load_nginx_modules: ngx_http_echo_module
--- config
location /t {
proxy_wasm hostcalls 'tick_period=10 test=/t/log/property name=ngx.hostname';
echo_sleep 0.150;
echo ok;
}
--- response_body
ok
--- error_log eval
[
qr/\[info\] .*? \[hostcalls\] on_tick/,
qr/\[info\] .*? property not found: ngx.hostname/,
]
--- no_error_log
[error]
Loading

0 comments on commit dcf1086

Please sign in to comment.