Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions lib/resty/proxy-wasm.lua
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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);
]]


Expand Down Expand Up @@ -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

Expand Down
22 changes: 22 additions & 0 deletions proxy_wasm_abi.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
43 changes: 43 additions & 0 deletions src/http/ngx_http_wasm_api.c
Original file line number Diff line number Diff line change
@@ -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"


Expand Down Expand Up @@ -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;
}
29 changes: 28 additions & 1 deletion src/http/ngx_http_wasm_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down Expand Up @@ -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 {
Expand All @@ -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, {} }
};

Expand Down
42 changes: 37 additions & 5 deletions src/http/ngx_http_wasm_module.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "ngx_http_wasm_module.h"
#include "ngx_http_wasm_state.h"
#include "ngx_http_wasm_ctx.h"
#include "vm/vm.h"
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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);

Expand All @@ -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;
}
18 changes: 18 additions & 0 deletions src/http/ngx_http_wasm_module.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef NGX_HTTP_WASM_MODULE_H
#define NGX_HTTP_WASM_MODULE_H


#include <ngx_core.h>


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
34 changes: 34 additions & 0 deletions t/http_send_response.t
Original file line number Diff line number Diff line change
@@ -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
65 changes: 65 additions & 0 deletions t/testdata/http_send_response/main.go
Original file line number Diff line number Diff line change
@@ -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
}