From 2871018b0b6be415430c38ae8cdbeb7dcd30d3e0 Mon Sep 17 00:00:00 2001 From: spacewander Date: Wed, 13 Oct 2021 14:38:09 +0800 Subject: [PATCH] feat: implement proxy_send_http_response --- lib/resty/proxy-wasm.lua | 19 ++++++++ proxy_wasm_abi.md | 22 +++++++++ src/http/ngx_http_wasm_api.c | 43 ++++++++++++++++++ src/http/ngx_http_wasm_api.h | 29 +++++++++++- src/http/ngx_http_wasm_module.c | 42 ++++++++++++++--- src/http/ngx_http_wasm_module.h | 18 ++++++++ t/http_send_response.t | 34 ++++++++++++++ t/testdata/http_send_response/main.go | 65 +++++++++++++++++++++++++++ 8 files changed, 266 insertions(+), 6 deletions(-) create mode 100644 src/http/ngx_http_wasm_module.h create mode 100644 t/http_send_response.t create mode 100644 t/testdata/http_send_response/main.go diff --git a/lib/resty/proxy-wasm.lua b/lib/resty/proxy-wasm.lua index c5f1360..184cf06 100644 --- a/lib/resty/proxy-wasm.lua +++ b/lib/resty/proxy-wasm.lua @@ -1,6 +1,7 @@ local ffi = require("ffi") local base = require("resty.core.base") local ffi_gc = ffi.gc +local ffi_str = ffi.string local C = ffi.C local get_request = base.get_request @@ -9,12 +10,19 @@ base.allows_subsystem("http") ffi.cdef[[ +typedef unsigned char u_char; +typedef struct { + size_t len; + u_char *data; +} ngx_str_t; typedef long ngx_int_t; void *ngx_http_wasm_load_plugin(const char *code, size_t size); void ngx_http_wasm_unload_plugin(void *plugin); void *ngx_http_wasm_on_configure(void *plugin, const char *conf, size_t size); void ngx_http_wasm_delete_plugin_ctx(void *hwp_ctx); + ngx_int_t ngx_http_wasm_on_http(void *hwp_ctx, void *r, int type); +ngx_str_t *ngx_http_wasm_fetch_local_body(void *r); ]] @@ -77,6 +85,17 @@ function _M.on_http_request_headers(plugin_ctx) return nil, "failed to run proxy_on_http_request_headers" end + if rc >= 100 then + ngx.status = rc + local p = C.ngx_http_wasm_fetch_local_body(r) + if p ~= nil then + local body = ffi_str(p.data, p.len) + ngx.print(body) + end + + ngx.exit(0) + end + return true end diff --git a/proxy_wasm_abi.md b/proxy_wasm_abi.md index e9f5058..bfb7cd0 100644 --- a/proxy_wasm_abi.md +++ b/proxy_wasm_abi.md @@ -156,3 +156,25 @@ because proxy-wasm-go-sdk uses it. - `i32 (size_t*) return_buffer_size` * returns: - `i32 (proxy_result_t) call_result` + + +## HTTP (L7) extensions + +### `proxy_send_http_response` + +* params: + - `i32 (uint32_t) response_code` + - `i32 (const char*) response_code_details_data` + - `i32 (size_t) response_code_details_size` + - `i32 (const char*) response_body_data` + - `i32 (size_t) response_body_size` + - `i32 (const char*) additional_headers_map_data` + - `i32 (size_t) additional_headers_size` + - `i32 (uint32_t) grpc_status` +* returns: + - `i32 (proxy_result_t) call_result` + +Sends HTTP response without forwarding request to the upstream. +Note: we only implement the handling of response_code and response_body. + +We only implement `proxy_send_local_response` as an alias because proxy-wasm-go-sdk uses it. diff --git a/src/http/ngx_http_wasm_api.c b/src/http/ngx_http_wasm_api.c index 678a221..de6fe54 100644 --- a/src/http/ngx_http_wasm_api.c +++ b/src/http/ngx_http_wasm_api.c @@ -1,5 +1,6 @@ #include "vm/vm.h" #include "ngx_http_wasm_api.h" +#include "ngx_http_wasm_module.h" #include "ngx_http_wasm_state.h" @@ -157,3 +158,45 @@ proxy_get_buffer_bytes(int32_t type, int32_t start, int32_t length, return PROXY_RESULT_OK; } + + +int32_t +proxy_send_http_response(int32_t res_code, + int32_t res_code_details_data, int32_t res_code_details_size, + int32_t body, int32_t body_size, + int32_t headers, int32_t headers_size, + int32_t grpc_status) +{ + ngx_http_wasm_main_conf_t *wmcf; + ngx_http_request_t *r; + ngx_log_t *log; + const u_char *p; + + r = ngx_http_wasm_get_req(); + if (r == NULL) { + return PROXY_RESULT_BAD_ARGUMENT; + } + + log = r->connection->log; + + wmcf = ngx_http_get_module_main_conf(r, ngx_http_wasm_module); + /* TODO handle other args */ + wmcf->code = res_code; + wmcf->body.len = body_size; + + if (body_size > 0) { + p = ngx_wasm_vm.get_memory(log, body, body_size); + if (p == NULL) { + return PROXY_RESULT_INVALID_MEMORY_ACCESS; + } + + wmcf->body.data = ngx_palloc(r->pool, body_size); + if (wmcf->body.data == NULL) { + ngx_log_error(NGX_LOG_ERR, log, 0, "no memory"); + return PROXY_RESULT_INTERNAL_FAILURE; + } + ngx_memcpy(wmcf->body.data, p, body_size); + } + + return PROXY_RESULT_OK; +} diff --git a/src/http/ngx_http_wasm_api.h b/src/http/ngx_http_wasm_api.h index 92a8e5f..a80b162 100644 --- a/src/http/ngx_http_wasm_api.h +++ b/src/http/ngx_http_wasm_api.h @@ -8,7 +8,7 @@ #include "ngx_http_wasm_types.h" -#define MAX_WASM_API_ARG 5 +#define MAX_WASM_API_ARG 8 #define DEFINE_WASM_API(NAME, ARG, ARG_CHECK) \ @@ -54,14 +54,34 @@ int32_t p5 = args[4].of.i32; \ int32_t res = NAME(p1, p2, p3, p4, p5); +#define DEFINE_WASM_API_ARG_I32_I32_I32_I32_I32_I32_I32_I32 \ + int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t + +#define DEFINE_WASM_API_ARG_CHECK_I32_I32_I32_I32_I32_I32_I32_I32(NAME) \ + int32_t p1 = args[0].of.i32; \ + int32_t p2 = args[1].of.i32; \ + int32_t p3 = args[2].of.i32; \ + int32_t p4 = args[3].of.i32; \ + int32_t p5 = args[4].of.i32; \ + int32_t p6 = args[5].of.i32; \ + int32_t p7 = args[6].of.i32; \ + int32_t p8 = args[7].of.i32; \ + int32_t res = NAME(p1, p2, p3, p4, p5, p6, p7, p8); + + #define DEFINE_WASM_NAME(NAME, ARG) \ {ngx_string(#NAME), wasmtime_##NAME, ARG}, +#define DEFINE_WASM_NAME_ALIAS(NAME, ARG, ALIAS) \ + {ngx_string(#NAME), wasmtime_##NAME, ARG}, \ + {ngx_string(#ALIAS), wasmtime_##NAME, ARG}, #define DEFINE_WASM_NAME_ARG_I32 \ 1, {WASM_I32} #define DEFINE_WASM_NAME_ARG_I32_I32_I32 \ 3, {WASM_I32, WASM_I32, WASM_I32} #define DEFINE_WASM_NAME_ARG_I32_I32_I32_I32_I32 \ 5, {WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32} +#define DEFINE_WASM_NAME_ARG_I32_I32_I32_I32_I32_I32_I32_I32 \ + 8, {WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32} typedef struct { @@ -81,12 +101,19 @@ DEFINE_WASM_API(proxy_log, DEFINE_WASM_API(proxy_get_buffer_bytes, DEFINE_WASM_API_ARG_I32_I32_I32_I32_I32, DEFINE_WASM_API_ARG_CHECK_I32_I32_I32_I32_I32(proxy_get_buffer_bytes)) +DEFINE_WASM_API(proxy_send_http_response, + DEFINE_WASM_API_ARG_I32_I32_I32_I32_I32_I32_I32_I32, + DEFINE_WASM_API_ARG_CHECK_I32_I32_I32_I32_I32_I32_I32_I32( + proxy_send_http_response)) static ngx_wasm_host_api_t host_apis[] = { DEFINE_WASM_NAME(proxy_set_effective_context, DEFINE_WASM_NAME_ARG_I32) DEFINE_WASM_NAME(proxy_log, DEFINE_WASM_NAME_ARG_I32_I32_I32) DEFINE_WASM_NAME(proxy_get_buffer_bytes, DEFINE_WASM_NAME_ARG_I32_I32_I32_I32_I32) + DEFINE_WASM_NAME_ALIAS(proxy_send_http_response, + DEFINE_WASM_NAME_ARG_I32_I32_I32_I32_I32_I32_I32_I32, + proxy_send_local_response) { ngx_null_string, NULL, 0, {} } }; diff --git a/src/http/ngx_http_wasm_module.c b/src/http/ngx_http_wasm_module.c index c9b206e..279422e 100644 --- a/src/http/ngx_http_wasm_module.c +++ b/src/http/ngx_http_wasm_module.c @@ -1,6 +1,7 @@ #include #include #include +#include "ngx_http_wasm_module.h" #include "ngx_http_wasm_state.h" #include "ngx_http_wasm_ctx.h" #include "vm/vm.h" @@ -24,11 +25,6 @@ static ngx_str_t proxy_on_request_headers = ngx_string("proxy_on_request_headers"); -typedef struct { - ngx_str_t vm; -} ngx_http_wasm_main_conf_t; - - typedef enum { HTTP_REQUEST_HEADERS = 1, } ngx_http_wasm_phase_t; @@ -89,6 +85,8 @@ ngx_http_wasm_create_main_conf(ngx_conf_t *cf) /* set by ngx_pcalloc: * wmcf->vm = { 0, NULL }; + * wmcf->code = 0; + * wmcf->body = { 0, NULL }; */ return wmcf; @@ -561,6 +559,7 @@ ngx_http_wasm_on_http(ngx_http_wasm_plugin_ctx_t *hwp_ctx, ngx_http_request_t *r ngx_int_t rc; ngx_log_t *log; ngx_http_wasm_http_ctx_t *http_ctx; + ngx_http_wasm_main_conf_t *wmcf; log = r->connection->log; @@ -569,6 +568,7 @@ ngx_http_wasm_on_http(ngx_http_wasm_plugin_ctx_t *hwp_ctx, ngx_http_request_t *r return NGX_DECLINED; } + wmcf = ngx_http_get_module_main_conf(r, ngx_http_wasm_module); hwp_ctx->state->r = r; ngx_http_wasm_set_state(hwp_ctx->state); @@ -583,5 +583,37 @@ ngx_http_wasm_on_http(ngx_http_wasm_plugin_ctx_t *hwp_ctx, ngx_http_request_t *r true, NGX_WASM_PARAM_I32_I32_I32, http_ctx->id, 0, 1); ngx_http_wasm_set_state(NULL); + + if (rc < 0) { + return rc; + } + + if (wmcf->code >= 100) { + int32_t code = wmcf->code; + + /* reset code for next use */ + wmcf->code = 0; + + /* Return given http response instead of reaching the upstream. + * The body will be fetched later by ngx_http_wasm_fetch_local_body + * */ + return code; + } + return rc; } + + +ngx_str_t * +ngx_http_wasm_fetch_local_body(ngx_http_request_t *r) +{ + ngx_http_wasm_main_conf_t *wmcf; + + /* call after ngx_http_wasm_on_http */ + + wmcf = ngx_http_get_module_main_conf(r, ngx_http_wasm_module); + if (wmcf->body.len) { + return &wmcf->body; + } + return NULL; +} diff --git a/src/http/ngx_http_wasm_module.h b/src/http/ngx_http_wasm_module.h new file mode 100644 index 0000000..26ddd9d --- /dev/null +++ b/src/http/ngx_http_wasm_module.h @@ -0,0 +1,18 @@ +#ifndef NGX_HTTP_WASM_MODULE_H +#define NGX_HTTP_WASM_MODULE_H + + +#include + + +typedef struct { + ngx_str_t vm; + uint32_t code; + ngx_str_t body; +} ngx_http_wasm_main_conf_t; + + +extern ngx_module_t ngx_http_wasm_module; + + +#endif // NGX_HTTP_WASM_MODULE_H diff --git a/t/http_send_response.t b/t/http_send_response.t new file mode 100644 index 0000000..298a208 --- /dev/null +++ b/t/http_send_response.t @@ -0,0 +1,34 @@ +use t::WASM 'no_plan'; + +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- config +location /t { + content_by_lua_block { + local wasm = require("resty.proxy-wasm") + local plugin = assert(wasm.load("t/testdata/http_send_response/main.go.wasm")) + local ctx = assert(wasm.on_configure(plugin, '403_body')) + assert(wasm.on_http_request_headers(ctx)) + } +} +--- error_code: 403 +--- response_body chomp +should not pass + + + +=== TEST 2: without body +--- config +location /t { + content_by_lua_block { + local wasm = require("resty.proxy-wasm") + local plugin = assert(wasm.load("t/testdata/http_send_response/main.go.wasm")) + local ctx = assert(wasm.on_configure(plugin, '502')) + assert(wasm.on_http_request_headers(ctx)) + } +} +--- error_code: 502 +--- response_body chomp diff --git a/t/testdata/http_send_response/main.go b/t/testdata/http_send_response/main.go new file mode 100644 index 0000000..f994a17 --- /dev/null +++ b/t/testdata/http_send_response/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" + "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" +) + +func main() { + proxywasm.SetVMContext(&vmContext{}) +} + +type vmContext struct { + // Embed the default VM context here, + // so that we don't need to reimplement all the methods. + types.DefaultVMContext +} + +// Override types.DefaultVMContext. +func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { + return &pluginContext{} +} + +type pluginContext struct { + // Embed the default plugin context here, + // so that we don't need to reimplement all the methods. + types.DefaultPluginContext +} + +// Override types.DefaultPluginContext. +func (*pluginContext) NewHttpContext(contextID uint32) types.HttpContext { + return &httpContext{contextID: contextID} +} + +type httpContext struct { + // Embed the default http context here, + // so that we don't need to reimplement all the methods. + types.DefaultHttpContext + contextID uint32 +} + +// Override types.DefaultHttpContext. +func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action { + data, err := proxywasm.GetPluginConfiguration() + if err != nil { + proxywasm.LogCriticalf("error reading plugin configuration: %v", err) + return types.ActionContinue + } + + action := string(data) + + if action == "403_body" { + body := "should not pass" + err = proxywasm.SendHttpResponse(403, [][2]string{ + {"powered-by", "proxy-wasm-go-sdk!!"}, + }, []byte(body), -1) + } else if action == "502" { + err = proxywasm.SendHttpResponse(502, nil, nil, -1) + } + + if err != nil { + proxywasm.LogErrorf("failed to send local response: %v", err) + return types.ActionContinue + } + return types.ActionPause +}