From 3bd6e7c8fc68e0b2cd9990ef2269b42ce0731a85 Mon Sep 17 00:00:00 2001 From: spacewander Date: Sun, 5 Dec 2021 14:24:55 +0800 Subject: [PATCH] feat(wasm): allow running in the rewrite phase Signed-off-by: spacewander --- apisix/cli/ops.lua | 243 +----------------------- apisix/cli/schema.lua | 274 ++++++++++++++++++++++++++++ apisix/core/config_local.lua | 4 + apisix/discovery/consul_kv/init.lua | 3 - apisix/discovery/dns/init.lua | 4 - apisix/discovery/eureka/init.lua | 4 - apisix/discovery/nacos/init.lua | 4 - apisix/init.lua | 20 +- apisix/wasm.lua | 14 +- docs/en/latest/wasm.md | 3 +- t/wasm/route.t | 35 +++- 11 files changed, 333 insertions(+), 275 deletions(-) create mode 100644 apisix/cli/schema.lua diff --git a/apisix/cli/ops.lua b/apisix/cli/ops.lua index 3f28e8fedaa7..4db2adf4e378 100644 --- a/apisix/cli/ops.lua +++ b/apisix/cli/ops.lua @@ -18,19 +18,17 @@ local ver = require("apisix.core.version") local etcd = require("apisix.cli.etcd") local util = require("apisix.cli.util") local file = require("apisix.cli.file") +local schema = require("apisix.cli.schema") local ngx_tpl = require("apisix.cli.ngx_tpl") local profile = require("apisix.core.profile") local template = require("resty.template") local argparse = require("argparse") local pl_path = require("pl.path") -local jsonschema = require("jsonschema") local stderr = io.stderr local ipairs = ipairs local pairs = pairs -local pcall = pcall local print = print -local require = require local type = type local tostring = tostring local tonumber = tonumber @@ -148,227 +146,6 @@ local function get_lua_path(conf) end -local config_schema = { - type = "object", - properties = { - apisix = { - properties = { - config_center = { - enum = {"etcd", "yaml"}, - }, - lua_module_hook = { - pattern = "^[a-zA-Z._-]+$", - }, - proxy_protocol = { - type = "object", - properties = { - listen_http_port = { - type = "integer", - }, - listen_https_port = { - type = "integer", - }, - enable_tcp_pp = { - type = "boolean", - }, - enable_tcp_pp_to_upstream = { - type = "boolean", - }, - } - }, - proxy_cache = { - type = "object", - properties = { - zones = { - type = "array", - minItems = 1, - items = { - type = "object", - properties = { - name = { - type = "string", - }, - memory_size = { - type = "string", - }, - disk_size = { - type = "string", - }, - disk_path = { - type = "string", - }, - cache_levels = { - type = "string", - }, - }, - oneOf = { - { - required = {"name", "memory_size"}, - maxProperties = 2, - }, - { - required = {"name", "memory_size", "disk_size", - "disk_path", "cache_levels"}, - } - }, - }, - uniqueItems = true, - } - } - }, - port_admin = { - type = "integer", - }, - https_admin = { - type = "boolean", - }, - stream_proxy = { - type = "object", - properties = { - tcp = { - type = "array", - minItems = 1, - items = { - anyOf = { - { - type = "integer", - }, - { - type = "string", - }, - { - type = "object", - properties = { - addr = { - anyOf = { - { - type = "integer", - }, - { - type = "string", - }, - } - }, - tls = { - type = "boolean", - } - }, - required = {"addr"} - }, - }, - }, - uniqueItems = true, - }, - udp = { - type = "array", - minItems = 1, - items = { - anyOf = { - { - type = "integer", - }, - { - type = "string", - }, - }, - }, - uniqueItems = true, - }, - } - }, - dns_resolver = { - type = "array", - minItems = 1, - items = { - type = "string", - } - }, - dns_resolver_valid = { - type = "integer", - }, - ssl = { - type = "object", - properties = { - ssl_trusted_certificate = { - type = "string", - } - } - }, - } - }, - nginx_config = { - type = "object", - properties = { - envs = { - type = "array", - minItems = 1, - items = { - type = "string", - } - } - }, - }, - http = { - type = "object", - properties = { - custom_lua_shared_dict = { - type = "object", - } - } - }, - etcd = { - type = "object", - properties = { - resync_delay = { - type = "integer", - }, - user = { - type = "string", - }, - password = { - type = "string", - }, - tls = { - type = "object", - properties = { - cert = { - type = "string", - }, - key = { - type = "string", - }, - } - } - } - }, - wasm = { - type = "object", - properties = { - plugins = { - type = "array", - minItems = 1, - items = { - type = "object", - properties = { - name = { - type = "string" - }, - file = { - type = "string" - }, - priority = { - type = "integer" - } - }, - required = {"name", "file", "priority"} - } - } - } - }, - } -} - - local function init(env) if env.is_root_path then print('Warning! Running apisix under /root is only suitable for ' @@ -391,23 +168,9 @@ local function init(env) util.die("failed to read local yaml config of apisix: ", err, "\n") end - local validator = jsonschema.generate_validator(config_schema) - local ok, err = validator(yaml_conf) + local ok, err = schema.validate(yaml_conf) if not ok then - util.die("failed to validate config: ", err, "\n") - end - - if yaml_conf.discovery then - for kind, conf in pairs(yaml_conf.discovery) do - local ok, schema = pcall(require, "apisix.discovery." .. kind .. ".schema") - if ok then - local validator = jsonschema.generate_validator(schema) - local ok, err = validator(conf) - if not ok then - util.die("invalid discovery ", kind, " configuration: ", err, "\n") - end - end - end + util.die(err, "\n") end -- check the Admin API token diff --git a/apisix/cli/schema.lua b/apisix/cli/schema.lua new file mode 100644 index 000000000000..e479074560e0 --- /dev/null +++ b/apisix/cli/schema.lua @@ -0,0 +1,274 @@ +-- +-- 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. +-- + +local jsonschema = require("jsonschema") +local pairs = pairs +local pcall = pcall +local require = require + + +local _M = {} +local config_schema = { + type = "object", + properties = { + apisix = { + properties = { + config_center = { + enum = {"etcd", "yaml"}, + }, + lua_module_hook = { + pattern = "^[a-zA-Z._-]+$", + }, + proxy_protocol = { + type = "object", + properties = { + listen_http_port = { + type = "integer", + }, + listen_https_port = { + type = "integer", + }, + enable_tcp_pp = { + type = "boolean", + }, + enable_tcp_pp_to_upstream = { + type = "boolean", + }, + } + }, + proxy_cache = { + type = "object", + properties = { + zones = { + type = "array", + minItems = 1, + items = { + type = "object", + properties = { + name = { + type = "string", + }, + memory_size = { + type = "string", + }, + disk_size = { + type = "string", + }, + disk_path = { + type = "string", + }, + cache_levels = { + type = "string", + }, + }, + oneOf = { + { + required = {"name", "memory_size"}, + maxProperties = 2, + }, + { + required = {"name", "memory_size", "disk_size", + "disk_path", "cache_levels"}, + } + }, + }, + uniqueItems = true, + } + } + }, + port_admin = { + type = "integer", + }, + https_admin = { + type = "boolean", + }, + stream_proxy = { + type = "object", + properties = { + tcp = { + type = "array", + minItems = 1, + items = { + anyOf = { + { + type = "integer", + }, + { + type = "string", + }, + { + type = "object", + properties = { + addr = { + anyOf = { + { + type = "integer", + }, + { + type = "string", + }, + } + }, + tls = { + type = "boolean", + } + }, + required = {"addr"} + }, + }, + }, + uniqueItems = true, + }, + udp = { + type = "array", + minItems = 1, + items = { + anyOf = { + { + type = "integer", + }, + { + type = "string", + }, + }, + }, + uniqueItems = true, + }, + } + }, + dns_resolver = { + type = "array", + minItems = 1, + items = { + type = "string", + } + }, + dns_resolver_valid = { + type = "integer", + }, + ssl = { + type = "object", + properties = { + ssl_trusted_certificate = { + type = "string", + } + } + }, + } + }, + nginx_config = { + type = "object", + properties = { + envs = { + type = "array", + minItems = 1, + items = { + type = "string", + } + } + }, + }, + http = { + type = "object", + properties = { + custom_lua_shared_dict = { + type = "object", + } + } + }, + etcd = { + type = "object", + properties = { + resync_delay = { + type = "integer", + }, + user = { + type = "string", + }, + password = { + type = "string", + }, + tls = { + type = "object", + properties = { + cert = { + type = "string", + }, + key = { + type = "string", + }, + } + } + } + }, + wasm = { + type = "object", + properties = { + plugins = { + type = "array", + minItems = 1, + items = { + type = "object", + properties = { + name = { + type = "string" + }, + file = { + type = "string" + }, + priority = { + type = "integer" + }, + http_request_phase = { + enum = {"access", "rewrite"}, + default = "access", + }, + }, + required = {"name", "file", "priority"} + } + } + } + }, + } +} + + +function _M.validate(yaml_conf) + local validator = jsonschema.generate_validator(config_schema) + local ok, err = validator(yaml_conf) + if not ok then + return false, "failed to validate config: " .. err + end + + if yaml_conf.discovery then + for kind, conf in pairs(yaml_conf.discovery) do + local ok, schema = pcall(require, "apisix.discovery." .. kind .. ".schema") + if ok then + local validator = jsonschema.generate_validator(schema) + local ok, err = validator(conf) + if not ok then + return false, "invalid discovery " .. kind .. " configuration: " .. err + end + end + end + end + + return true +end + + +return _M diff --git a/apisix/core/config_local.lua b/apisix/core/config_local.lua index d17255b7d8b6..9494dad020a9 100644 --- a/apisix/core/config_local.lua +++ b/apisix/core/config_local.lua @@ -16,6 +16,7 @@ -- local file = require("apisix.cli.file") +local schema = require("apisix.cli.schema") local _M = {} @@ -39,6 +40,9 @@ function _M.local_conf(force) return nil, err end + -- fill the default value by the schema + schema.validate(default_conf) + config_data = default_conf return config_data end diff --git a/apisix/discovery/consul_kv/init.lua b/apisix/discovery/consul_kv/init.lua index 71b72c8d97ea..fbb2061dba25 100644 --- a/apisix/discovery/consul_kv/init.lua +++ b/apisix/discovery/consul_kv/init.lua @@ -18,7 +18,6 @@ local require = require local local_conf = require("apisix.core.config_local").local_conf() local core = require("apisix.core") local core_sleep = require("apisix.core.utils").sleep -local schema = require('apisix.discovery.consul_kv.schema') local resty_consul = require('resty.consul') local cjson = require('cjson') local http = require('resty.http') @@ -364,8 +363,6 @@ end function _M.init_worker() local consul_conf = local_conf.discovery.consul_kv - -- inject the default values - core.schema.check(schema, consul_conf) if consul_conf.dump then local dump = consul_conf.dump diff --git a/apisix/discovery/dns/init.lua b/apisix/discovery/dns/init.lua index 45a894080fa5..837f1e8e8ca0 100644 --- a/apisix/discovery/dns/init.lua +++ b/apisix/discovery/dns/init.lua @@ -17,7 +17,6 @@ local core = require("apisix.core") local config_local = require("apisix.core.config_local") -local schema = require('apisix.discovery.dns.schema') local ipairs = ipairs local error = error @@ -52,9 +51,6 @@ end function _M.init_worker() local local_conf = config_local.local_conf() - -- inject the default values - core.schema.check(schema, local_conf.discovery.dns) - local servers = local_conf.discovery.dns.servers local opts = { diff --git a/apisix/discovery/eureka/init.lua b/apisix/discovery/eureka/init.lua index 79faf9eea571..df72a5269e59 100644 --- a/apisix/discovery/eureka/init.lua +++ b/apisix/discovery/eureka/init.lua @@ -18,7 +18,6 @@ local local_conf = require("apisix.core.config_local").local_conf() local http = require("resty.http") local core = require("apisix.core") -local schema = require('apisix.discovery.eureka.schema') local ipmatcher = require("resty.ipmatcher") local ipairs = ipairs local tostring = tostring @@ -207,9 +206,6 @@ end function _M.init_worker() - -- inject the default values - core.schema.check(schema, local_conf.discovery.eureka) - default_weight = local_conf.discovery.eureka.weight or 100 log.info("default_weight:", default_weight, ".") local fetch_interval = local_conf.discovery.eureka.fetch_interval or 30 diff --git a/apisix/discovery/nacos/init.lua b/apisix/discovery/nacos/init.lua index 94a44a2aead7..d163b3952cd2 100644 --- a/apisix/discovery/nacos/init.lua +++ b/apisix/discovery/nacos/init.lua @@ -19,7 +19,6 @@ local require = require local local_conf = require('apisix.core.config_local').local_conf() local http = require('resty.http') local core = require('apisix.core') -local schema = require('apisix.discovery.nacos.schema') local ipairs = ipairs local type = type local math = math @@ -333,9 +332,6 @@ end function _M.init_worker() - -- inject the default values - core.schema.check(schema, local_conf.discovery.nacos) - events = require("resty.worker.events") events_list = events.event_list("discovery_nacos_update_application", "updating") diff --git a/apisix/init.lua b/apisix/init.lua index 801809f64b10..f5ef0c4c651a 100644 --- a/apisix/init.lua +++ b/apisix/init.lua @@ -15,6 +15,16 @@ -- limitations under the License. -- local require = require +-- set the JIT options before any code, to prevent error "changing jit stack size is not +-- allowed when some regexs have already been compiled and cached" +if require("ffi").os == "Linux" then + require("ngx.re").opt("jit_stack_size", 200 * 1024) +end + +require("jit.opt").start("minstitch=2", "maxtrace=4000", + "maxrecord=8000", "sizemcode=64", + "maxmcode=4000", "maxirconst=1000") + require("apisix.patch").patch() local core = require("apisix.core") local plugin = require("apisix.plugin") @@ -61,16 +71,6 @@ local _M = {version = 0.4} function _M.http_init(args) - require("resty.core") - - if require("ffi").os == "Linux" then - require("ngx.re").opt("jit_stack_size", 200 * 1024) - end - - require("jit.opt").start("minstitch=2", "maxtrace=4000", - "maxrecord=8000", "sizemcode=64", - "maxmcode=4000", "maxirconst=1000") - core.resolver.init_resolver(args) core.id.init() diff --git a/apisix/wasm.lua b/apisix/wasm.lua index 939549a2ad9f..7a6e81c4b813 100644 --- a/apisix/wasm.lua +++ b/apisix/wasm.lua @@ -62,7 +62,7 @@ local function fetch_plugin_ctx(conf, ctx, plugin) end -local function access_wrapper(self, conf, ctx) +local function http_request_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) @@ -113,9 +113,17 @@ function _M.require(attrs) plugin = plugin, type = "wasm", } - mod.access = function (conf, ctx) - return access_wrapper(mod, conf, ctx) + + if attrs.http_request_phase == "rewrite" then + mod.rewrite = function (conf, ctx) + return http_request_wrapper(mod, conf, ctx) + end + else + mod.access = function (conf, ctx) + return http_request_wrapper(mod, conf, ctx) + end end + mod.header_filter = function (conf, ctx) return header_filter_wrapper(mod, conf, ctx) end diff --git a/docs/en/latest/wasm.md b/docs/en/latest/wasm.md index 8d81d69a2e00..31e9ca4fac00 100644 --- a/docs/en/latest/wasm.md +++ b/docs/en/latest/wasm.md @@ -65,6 +65,7 @@ wasm: - name: wasm_log # the name of the plugin priority: 7999 # priority file: t/wasm/log/main.go.wasm # the path of `.wasm` file + http_request_phase: access # default to "access", can be one of ["access", "rewrite"] ``` That's all. Now you can use the wasm plugin as a regular plugin. @@ -99,5 +100,5 @@ 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_request_headers`: run in the access/rewrite phase, depends on the configuration of `http_request_phase`. * `proxy_on_http_response_headers`: run in the header_filter phase. diff --git a/t/wasm/route.t b/t/wasm/route.t index cb348011c1a7..79afcfc42118 100644 --- a/t/wasm/route.t +++ b/t/wasm/route.t @@ -36,7 +36,8 @@ add_block_preprocessor(sub { $block->set_value("request", "GET /t"); } - my $extra_yaml_config = <<_EOC_; + if (!defined $block->extra_yaml_config) { + my $extra_yaml_config = <<_EOC_; wasm: plugins: - name: wasm_log @@ -46,7 +47,8 @@ wasm: priority: 7998 file: t/wasm/log/main.go.wasm _EOC_ - $block->set_value("extra_yaml_config", $extra_yaml_config); + $block->set_value("extra_yaml_config", $extra_yaml_config); + } }); run_tests(); @@ -145,7 +147,28 @@ run plugin ctx 1 with conf zzz in http ctx 2 -=== TEST 4: plugin from service +=== TEST 4: run wasm plugin in rewrite phase (prior to the one run in access phase) +--- extra_yaml_config +wasm: + plugins: + - name: wasm_log + priority: 7999 + file: t/wasm/log/main.go.wasm + - name: wasm_log2 + priority: 7998 + file: t/wasm/log/main.go.wasm + http_request_phase: rewrite +--- request +GET /hello +--- grep_error_log eval +qr/run plugin ctx \d+ with conf \S+ in http ctx \d+/ +--- grep_error_log_out +run plugin ctx 1 with conf zzz in http ctx 2 +run plugin ctx 1 with conf blahblah in http ctx 2 + + + +=== TEST 5: plugin from service --- config location /t { content_by_lua_block { @@ -212,7 +235,7 @@ passed -=== TEST 5: hit +=== TEST 6: hit --- config location /t { content_by_lua_block { @@ -243,7 +266,7 @@ run plugin ctx 3 with conf blahblah in http ctx 4 -=== TEST 6: plugin from plugin_config +=== TEST 7: plugin from plugin_config --- config location /t { content_by_lua_block { @@ -316,7 +339,7 @@ passed -=== TEST 7: hit +=== TEST 8: hit --- config location /t { content_by_lua_block {