Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wasm): run in http header_filter #5544

Merged
merged 1 commit into from
Nov 19, 2021
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
20 changes: 19 additions & 1 deletion apisix/wasm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ end
local function access_wrapper(self, conf, ctx)
local plugin_ctx, err = fetch_plugin_ctx(conf, ctx, self.plugin)
if not plugin_ctx then
core.log.error("failed to init wasm plugin ctx: ", err)
core.log.error("failed to fetch wasm plugin ctx: ", err)
return 503
end

Expand All @@ -77,6 +77,21 @@ local function access_wrapper(self, conf, ctx)
end


local function header_filter_wrapper(self, conf, ctx)
local plugin_ctx, err = fetch_plugin_ctx(conf, ctx, self.plugin)
if not plugin_ctx then
core.log.error("failed to fetch wasm plugin ctx: ", err)
return 503
end

local ok, err = wasm.on_http_response_headers(plugin_ctx)
if not ok then
core.log.error("failed to run wasm plugin: ", err)
return 503
end
end


function _M.require(attrs)
if not support_wasm then
return nil, "need to build APISIX-OpenResty to support wasm"
Expand All @@ -101,6 +116,9 @@ function _M.require(attrs)
mod.access = function (conf, ctx)
return access_wrapper(mod, conf, ctx)
end
mod.header_filter = function (conf, ctx)
return header_filter_wrapper(mod, conf, ctx)
end

-- the returned values need to be the same as the Lua's 'require'
return true, mod
Expand Down
9 changes: 8 additions & 1 deletion docs/en/latest/wasm.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,11 @@ Attributes below can be configured in the plugin:

| Name | Type | Requirement | Default | Valid | Description |
| --------------------------------------| ------------| -------------- | -------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| conf | string | required | | != "" | the plugin ctx configuration which can be fetched via Proxy WASM SDK |
| conf | string | required | | != "" | the plugin ctx configuration which can be fetched via Proxy WASM SDK |

Here is the mapping between Proxy WASM callbacks and APISIX's phases:

* `proxy_on_configure`: run once there is not PluginContext for the new configuration.
For example, when the first request hits the route which has WASM plugin configured.
* `proxy_on_http_request_headers`: run in the access phase.
* `proxy_on_http_response_headers`: run in the header_filter phase.
94 changes: 94 additions & 0 deletions t/wasm/response-rewrite.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX;

my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
my $version = eval { `$nginx_binary -V 2>&1` };

if ($version !~ m/\/apisix-nginx-module/) {
plan(skip_all => "apisix-nginx-module not installed");
} else {
plan('no_plan');
}

add_block_preprocessor(sub {
my ($block) = @_;

if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
$block->set_value("no_error_log", "[error]");
}

if (!defined $block->request) {
$block->set_value("request", "GET /t");
}

my $extra_yaml_config = <<_EOC_;
wasm:
plugins:
- name: wasm-response-rewrite
priority: 7997
file: t/wasm/response-rewrite/main.go.wasm
_EOC_
$block->set_value("extra_yaml_config", $extra_yaml_config);
});

run_tests();

__DATA__

=== TEST 1: response rewrite headers
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"wasm-response-rewrite": {
"conf": "{\"headers\":[{\"name\":\"x-wasm\",\"value\":\"apisix\"}]}"
}
}
}]]
)

if code >= 300 then
ngx.status = code
ngx.say(body)
return
end

ngx.say(body)
}
}
--- response_body
passed



=== TEST 2: hit
--- request
GET /hello
--- response_headers
x-wasm: apisix
89 changes: 89 additions & 0 deletions t/wasm/response-rewrite/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package main

import (
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"

"github.com/valyala/fastjson"
)

func main() {
proxywasm.SetVMContext(&vmContext{})
}

type vmContext struct {
types.DefaultVMContext
}

func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
return &pluginContext{}
}

type header struct {
Name string
Value string
}

type pluginContext struct {
types.DefaultPluginContext
Headers []header
}

func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus {
data, err := proxywasm.GetPluginConfiguration()
if err != nil {
proxywasm.LogErrorf("error reading plugin configuration: %v", err)
return types.OnPluginStartStatusFailed
}

var p fastjson.Parser
v, err := p.ParseBytes(data)
if err != nil {
proxywasm.LogErrorf("erorr decoding plugin configuration: %v", err)
return types.OnPluginStartStatusFailed
}
headers := v.GetArray("headers")
ctx.Headers = make([]header, len(headers))
for i, hdr := range headers {
ctx.Headers[i] = header{
Name: string(hdr.GetStringBytes("name")),
Value: string(hdr.GetStringBytes("value")),
}
}

return types.OnPluginStartStatusOK
}

func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
return &httpContext{parent: ctx}
}

type httpContext struct {
types.DefaultHttpContext
parent *pluginContext
}

func (ctx *httpContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
plugin := ctx.parent
for _, hdr := range plugin.Headers {
proxywasm.ReplaceHttpResponseHeader(hdr.Name, hdr.Value)
}
return types.ActionContinue
}