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 11, 2022
1 parent c4235cd commit f3a902c
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 1 deletion.
88 changes: 87 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,92 @@ ngx_proxy_wasm_hfuncs_get_configuration(ngx_wavm_instance_t *instance,
}


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;
}


static ngx_int_t
ngx_proxy_wasm_hfuncs_get_property(ngx_wavm_instance_t *instance,
wasm_val_t args[], wasm_val_t rets[])
{
const char *path_data;
int32_t path_size;
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
ngx_http_wasm_req_ctx_t *rctx;
ngx_str_t name;
ngx_uint_t hash;
ngx_http_variable_value_t *vv;
static const char *http_prefix = "ngx\0http\0";
static const int http_prefix_len = 9;
#endif

fctx = ngx_proxy_wasm_instance2fctx(instance);

path_data = ngx_wavm_memory_lift(instance->memory, args[0].of.i32);
path_size = args[1].of.i32;
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 > http_prefix_len &&
memcmp(path_data, http_prefix, http_prefix_len) == 0)
{
name.data = (u_char *)(path_data + http_prefix_len);
name.len = path_size - http_prefix_len;

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

rctx = ngx_http_proxy_wasm_get_rctx(instance);

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 +1382,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
50 changes: 50 additions & 0 deletions t/03-proxy_wasm/132-proxy_get_property.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 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() * (blocks() * 4);

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.http.hostname';
echo ok;
}
--- response_body
ok
--- error_log eval
[
qr/\[info\] .*? ngx.http.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.http.pid';
echo ok;
}
--- response_body
ok
--- error_log eval
[
qr/\[info\] .*? ngx.http.pid: [0-9]+/,
]
--- no_error_log
[error]
1 change: 1 addition & 0 deletions t/lib/proxy-wasm-tests/hostcalls/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ impl TestHttpHostcalls {
"/t/log/response_headers" => test_log_response_headers(self),
"/t/log/response_body" => test_log_response_body(self),
"/t/log/current_time" => test_log_current_time(self),
"/t/log/property" => test_log_property(self),

/* send_local_response */
"/t/send_local_response/status/204" => test_send_status(self, 204),
Expand Down
15 changes: 15 additions & 0 deletions t/lib/proxy-wasm-tests/hostcalls/src/test_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,21 @@ pub(crate) fn test_log_request_path(ctx: &mut TestHttpHostcalls) {
info!("path: {}", path);
}

pub(crate) fn test_log_property(ctx: &mut TestHttpHostcalls) {
let name = ctx.config.get("name")
.expect("expected a name argument");

match ctx.get_property(name.split(".").collect()) {
Some(p) => {
match std::str::from_utf8(&p) {
Ok(value) => info!("{}: {}", name, value),
Err(_) => panic!("failed converting {} to UTF-8", name),
}
},
None => panic!("{} property not found", name),
}
}

pub(crate) fn test_send_status(ctx: &mut TestHttpHostcalls, status: u32) {
ctx.send_http_response(status, vec![], None)
}
Expand Down

0 comments on commit f3a902c

Please sign in to comment.