Skip to content

Commit a8c4f2f

Browse files
authored
feat: implement proxy_send_http_response (#15)
1 parent 8c95d3b commit a8c4f2f

File tree

8 files changed

+266
-6
lines changed

8 files changed

+266
-6
lines changed

lib/resty/proxy-wasm.lua

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
local ffi = require("ffi")
22
local base = require("resty.core.base")
33
local ffi_gc = ffi.gc
4+
local ffi_str = ffi.string
45
local C = ffi.C
56
local get_request = base.get_request
67

@@ -9,12 +10,19 @@ base.allows_subsystem("http")
910

1011

1112
ffi.cdef[[
13+
typedef unsigned char u_char;
14+
typedef struct {
15+
size_t len;
16+
u_char *data;
17+
} ngx_str_t;
1218
typedef long ngx_int_t;
1319
void *ngx_http_wasm_load_plugin(const char *code, size_t size);
1420
void ngx_http_wasm_unload_plugin(void *plugin);
1521
void *ngx_http_wasm_on_configure(void *plugin, const char *conf, size_t size);
1622
void ngx_http_wasm_delete_plugin_ctx(void *hwp_ctx);
23+
1724
ngx_int_t ngx_http_wasm_on_http(void *hwp_ctx, void *r, int type);
25+
ngx_str_t *ngx_http_wasm_fetch_local_body(void *r);
1826
]]
1927

2028

@@ -77,6 +85,17 @@ function _M.on_http_request_headers(plugin_ctx)
7785
return nil, "failed to run proxy_on_http_request_headers"
7886
end
7987

88+
if rc >= 100 then
89+
ngx.status = rc
90+
local p = C.ngx_http_wasm_fetch_local_body(r)
91+
if p ~= nil then
92+
local body = ffi_str(p.data, p.len)
93+
ngx.print(body)
94+
end
95+
96+
ngx.exit(0)
97+
end
98+
8099
return true
81100
end
82101

proxy_wasm_abi.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,25 @@ because proxy-wasm-go-sdk uses it.
156156
- `i32 (size_t*) return_buffer_size`
157157
* returns:
158158
- `i32 (proxy_result_t) call_result`
159+
160+
161+
## HTTP (L7) extensions
162+
163+
### `proxy_send_http_response`
164+
165+
* params:
166+
- `i32 (uint32_t) response_code`
167+
- `i32 (const char*) response_code_details_data`
168+
- `i32 (size_t) response_code_details_size`
169+
- `i32 (const char*) response_body_data`
170+
- `i32 (size_t) response_body_size`
171+
- `i32 (const char*) additional_headers_map_data`
172+
- `i32 (size_t) additional_headers_size`
173+
- `i32 (uint32_t) grpc_status`
174+
* returns:
175+
- `i32 (proxy_result_t) call_result`
176+
177+
Sends HTTP response without forwarding request to the upstream.
178+
Note: we only implement the handling of response_code and response_body.
179+
180+
We only implement `proxy_send_local_response` as an alias because proxy-wasm-go-sdk uses it.

src/http/ngx_http_wasm_api.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "vm/vm.h"
22
#include "ngx_http_wasm_api.h"
3+
#include "ngx_http_wasm_module.h"
34
#include "ngx_http_wasm_state.h"
45

56

@@ -157,3 +158,45 @@ proxy_get_buffer_bytes(int32_t type, int32_t start, int32_t length,
157158

158159
return PROXY_RESULT_OK;
159160
}
161+
162+
163+
int32_t
164+
proxy_send_http_response(int32_t res_code,
165+
int32_t res_code_details_data, int32_t res_code_details_size,
166+
int32_t body, int32_t body_size,
167+
int32_t headers, int32_t headers_size,
168+
int32_t grpc_status)
169+
{
170+
ngx_http_wasm_main_conf_t *wmcf;
171+
ngx_http_request_t *r;
172+
ngx_log_t *log;
173+
const u_char *p;
174+
175+
r = ngx_http_wasm_get_req();
176+
if (r == NULL) {
177+
return PROXY_RESULT_BAD_ARGUMENT;
178+
}
179+
180+
log = r->connection->log;
181+
182+
wmcf = ngx_http_get_module_main_conf(r, ngx_http_wasm_module);
183+
/* TODO handle other args */
184+
wmcf->code = res_code;
185+
wmcf->body.len = body_size;
186+
187+
if (body_size > 0) {
188+
p = ngx_wasm_vm.get_memory(log, body, body_size);
189+
if (p == NULL) {
190+
return PROXY_RESULT_INVALID_MEMORY_ACCESS;
191+
}
192+
193+
wmcf->body.data = ngx_palloc(r->pool, body_size);
194+
if (wmcf->body.data == NULL) {
195+
ngx_log_error(NGX_LOG_ERR, log, 0, "no memory");
196+
return PROXY_RESULT_INTERNAL_FAILURE;
197+
}
198+
ngx_memcpy(wmcf->body.data, p, body_size);
199+
}
200+
201+
return PROXY_RESULT_OK;
202+
}

src/http/ngx_http_wasm_api.h

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include "ngx_http_wasm_types.h"
99

1010

11-
#define MAX_WASM_API_ARG 5
11+
#define MAX_WASM_API_ARG 8
1212

1313

1414
#define DEFINE_WASM_API(NAME, ARG, ARG_CHECK) \
@@ -54,14 +54,34 @@
5454
int32_t p5 = args[4].of.i32; \
5555
int32_t res = NAME(p1, p2, p3, p4, p5);
5656

57+
#define DEFINE_WASM_API_ARG_I32_I32_I32_I32_I32_I32_I32_I32 \
58+
int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t
59+
60+
#define DEFINE_WASM_API_ARG_CHECK_I32_I32_I32_I32_I32_I32_I32_I32(NAME) \
61+
int32_t p1 = args[0].of.i32; \
62+
int32_t p2 = args[1].of.i32; \
63+
int32_t p3 = args[2].of.i32; \
64+
int32_t p4 = args[3].of.i32; \
65+
int32_t p5 = args[4].of.i32; \
66+
int32_t p6 = args[5].of.i32; \
67+
int32_t p7 = args[6].of.i32; \
68+
int32_t p8 = args[7].of.i32; \
69+
int32_t res = NAME(p1, p2, p3, p4, p5, p6, p7, p8);
70+
71+
5772
#define DEFINE_WASM_NAME(NAME, ARG) \
5873
{ngx_string(#NAME), wasmtime_##NAME, ARG},
74+
#define DEFINE_WASM_NAME_ALIAS(NAME, ARG, ALIAS) \
75+
{ngx_string(#NAME), wasmtime_##NAME, ARG}, \
76+
{ngx_string(#ALIAS), wasmtime_##NAME, ARG},
5977
#define DEFINE_WASM_NAME_ARG_I32 \
6078
1, {WASM_I32}
6179
#define DEFINE_WASM_NAME_ARG_I32_I32_I32 \
6280
3, {WASM_I32, WASM_I32, WASM_I32}
6381
#define DEFINE_WASM_NAME_ARG_I32_I32_I32_I32_I32 \
6482
5, {WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32}
83+
#define DEFINE_WASM_NAME_ARG_I32_I32_I32_I32_I32_I32_I32_I32 \
84+
8, {WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32}
6585

6686

6787
typedef struct {
@@ -81,12 +101,19 @@ DEFINE_WASM_API(proxy_log,
81101
DEFINE_WASM_API(proxy_get_buffer_bytes,
82102
DEFINE_WASM_API_ARG_I32_I32_I32_I32_I32,
83103
DEFINE_WASM_API_ARG_CHECK_I32_I32_I32_I32_I32(proxy_get_buffer_bytes))
104+
DEFINE_WASM_API(proxy_send_http_response,
105+
DEFINE_WASM_API_ARG_I32_I32_I32_I32_I32_I32_I32_I32,
106+
DEFINE_WASM_API_ARG_CHECK_I32_I32_I32_I32_I32_I32_I32_I32(
107+
proxy_send_http_response))
84108

85109

86110
static ngx_wasm_host_api_t host_apis[] = {
87111
DEFINE_WASM_NAME(proxy_set_effective_context, DEFINE_WASM_NAME_ARG_I32)
88112
DEFINE_WASM_NAME(proxy_log, DEFINE_WASM_NAME_ARG_I32_I32_I32)
89113
DEFINE_WASM_NAME(proxy_get_buffer_bytes, DEFINE_WASM_NAME_ARG_I32_I32_I32_I32_I32)
114+
DEFINE_WASM_NAME_ALIAS(proxy_send_http_response,
115+
DEFINE_WASM_NAME_ARG_I32_I32_I32_I32_I32_I32_I32_I32,
116+
proxy_send_local_response)
90117
{ ngx_null_string, NULL, 0, {} }
91118
};
92119

src/http/ngx_http_wasm_module.c

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <ngx_config.h>
22
#include <ngx_core.h>
33
#include <ngx_http.h>
4+
#include "ngx_http_wasm_module.h"
45
#include "ngx_http_wasm_state.h"
56
#include "ngx_http_wasm_ctx.h"
67
#include "vm/vm.h"
@@ -24,11 +25,6 @@ static ngx_str_t proxy_on_request_headers =
2425
ngx_string("proxy_on_request_headers");
2526

2627

27-
typedef struct {
28-
ngx_str_t vm;
29-
} ngx_http_wasm_main_conf_t;
30-
31-
3228
typedef enum {
3329
HTTP_REQUEST_HEADERS = 1,
3430
} ngx_http_wasm_phase_t;
@@ -89,6 +85,8 @@ ngx_http_wasm_create_main_conf(ngx_conf_t *cf)
8985

9086
/* set by ngx_pcalloc:
9187
* wmcf->vm = { 0, NULL };
88+
* wmcf->code = 0;
89+
* wmcf->body = { 0, NULL };
9290
*/
9391

9492
return wmcf;
@@ -561,6 +559,7 @@ ngx_http_wasm_on_http(ngx_http_wasm_plugin_ctx_t *hwp_ctx, ngx_http_request_t *r
561559
ngx_int_t rc;
562560
ngx_log_t *log;
563561
ngx_http_wasm_http_ctx_t *http_ctx;
562+
ngx_http_wasm_main_conf_t *wmcf;
564563

565564
log = r->connection->log;
566565

@@ -569,6 +568,7 @@ ngx_http_wasm_on_http(ngx_http_wasm_plugin_ctx_t *hwp_ctx, ngx_http_request_t *r
569568
return NGX_DECLINED;
570569
}
571570

571+
wmcf = ngx_http_get_module_main_conf(r, ngx_http_wasm_module);
572572
hwp_ctx->state->r = r;
573573
ngx_http_wasm_set_state(hwp_ctx->state);
574574

@@ -583,5 +583,37 @@ ngx_http_wasm_on_http(ngx_http_wasm_plugin_ctx_t *hwp_ctx, ngx_http_request_t *r
583583
true, NGX_WASM_PARAM_I32_I32_I32, http_ctx->id,
584584
0, 1);
585585
ngx_http_wasm_set_state(NULL);
586+
587+
if (rc < 0) {
588+
return rc;
589+
}
590+
591+
if (wmcf->code >= 100) {
592+
int32_t code = wmcf->code;
593+
594+
/* reset code for next use */
595+
wmcf->code = 0;
596+
597+
/* Return given http response instead of reaching the upstream.
598+
* The body will be fetched later by ngx_http_wasm_fetch_local_body
599+
* */
600+
return code;
601+
}
602+
586603
return rc;
587604
}
605+
606+
607+
ngx_str_t *
608+
ngx_http_wasm_fetch_local_body(ngx_http_request_t *r)
609+
{
610+
ngx_http_wasm_main_conf_t *wmcf;
611+
612+
/* call after ngx_http_wasm_on_http */
613+
614+
wmcf = ngx_http_get_module_main_conf(r, ngx_http_wasm_module);
615+
if (wmcf->body.len) {
616+
return &wmcf->body;
617+
}
618+
return NULL;
619+
}

src/http/ngx_http_wasm_module.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#ifndef NGX_HTTP_WASM_MODULE_H
2+
#define NGX_HTTP_WASM_MODULE_H
3+
4+
5+
#include <ngx_core.h>
6+
7+
8+
typedef struct {
9+
ngx_str_t vm;
10+
uint32_t code;
11+
ngx_str_t body;
12+
} ngx_http_wasm_main_conf_t;
13+
14+
15+
extern ngx_module_t ngx_http_wasm_module;
16+
17+
18+
#endif // NGX_HTTP_WASM_MODULE_H

t/http_send_response.t

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use t::WASM 'no_plan';
2+
3+
run_tests();
4+
5+
__DATA__
6+
7+
=== TEST 1: sanity
8+
--- config
9+
location /t {
10+
content_by_lua_block {
11+
local wasm = require("resty.proxy-wasm")
12+
local plugin = assert(wasm.load("t/testdata/http_send_response/main.go.wasm"))
13+
local ctx = assert(wasm.on_configure(plugin, '403_body'))
14+
assert(wasm.on_http_request_headers(ctx))
15+
}
16+
}
17+
--- error_code: 403
18+
--- response_body chomp
19+
should not pass
20+
21+
22+
23+
=== TEST 2: without body
24+
--- config
25+
location /t {
26+
content_by_lua_block {
27+
local wasm = require("resty.proxy-wasm")
28+
local plugin = assert(wasm.load("t/testdata/http_send_response/main.go.wasm"))
29+
local ctx = assert(wasm.on_configure(plugin, '502'))
30+
assert(wasm.on_http_request_headers(ctx))
31+
}
32+
}
33+
--- error_code: 502
34+
--- response_body chomp
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package main
2+
3+
import (
4+
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
5+
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
6+
)
7+
8+
func main() {
9+
proxywasm.SetVMContext(&vmContext{})
10+
}
11+
12+
type vmContext struct {
13+
// Embed the default VM context here,
14+
// so that we don't need to reimplement all the methods.
15+
types.DefaultVMContext
16+
}
17+
18+
// Override types.DefaultVMContext.
19+
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
20+
return &pluginContext{}
21+
}
22+
23+
type pluginContext struct {
24+
// Embed the default plugin context here,
25+
// so that we don't need to reimplement all the methods.
26+
types.DefaultPluginContext
27+
}
28+
29+
// Override types.DefaultPluginContext.
30+
func (*pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
31+
return &httpContext{contextID: contextID}
32+
}
33+
34+
type httpContext struct {
35+
// Embed the default http context here,
36+
// so that we don't need to reimplement all the methods.
37+
types.DefaultHttpContext
38+
contextID uint32
39+
}
40+
41+
// Override types.DefaultHttpContext.
42+
func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
43+
data, err := proxywasm.GetPluginConfiguration()
44+
if err != nil {
45+
proxywasm.LogCriticalf("error reading plugin configuration: %v", err)
46+
return types.ActionContinue
47+
}
48+
49+
action := string(data)
50+
51+
if action == "403_body" {
52+
body := "should not pass"
53+
err = proxywasm.SendHttpResponse(403, [][2]string{
54+
{"powered-by", "proxy-wasm-go-sdk!!"},
55+
}, []byte(body), -1)
56+
} else if action == "502" {
57+
err = proxywasm.SendHttpResponse(502, nil, nil, -1)
58+
}
59+
60+
if err != nil {
61+
proxywasm.LogErrorf("failed to send local response: %v", err)
62+
return types.ActionContinue
63+
}
64+
return types.ActionPause
65+
}

0 commit comments

Comments
 (0)