diff --git a/apisix-master-0.rockspec b/apisix-master-0.rockspec index 9851045ef102..5d6c6dafd60d 100644 --- a/apisix-master-0.rockspec +++ b/apisix-master-0.rockspec @@ -47,7 +47,7 @@ dependencies = { "lua-resty-cookie = 0.2.0-1", "lua-resty-session = 3.10", "opentracing-openresty = 0.1", - "lua-resty-radixtree = 2.9.1", + "lua-resty-radixtree = 2.9.2", "lua-protobuf = 0.5.2-1", "lua-resty-openidc = 1.7.6-3", "luafilesystem = 1.7.0-2", @@ -83,6 +83,7 @@ dependencies = { "brotli-ffi = 0.3-1", "lua-ffi-zlib = 0.6-0", "api7-lua-resty-aws == 2.0.2-1", + "multipart = 0.5.9-1", } build = { diff --git a/apisix/cli/ops.lua b/apisix/cli/ops.lua index 16547fce360c..c10bcfaa769e 100644 --- a/apisix/cli/ops.lua +++ b/apisix/cli/ops.lua @@ -49,6 +49,9 @@ local str_find = string.find local str_byte = string.byte local str_sub = string.sub local str_format = string.format +local string = string +local table = table + local _M = {} @@ -502,17 +505,34 @@ Please modify "admin_key" in conf/config.yaml . if yaml_conf.apisix.ssl.ssl_trusted_certificate ~= nil then - local cert_path = yaml_conf.apisix.ssl.ssl_trusted_certificate - -- During validation, the path is relative to PWD - -- When Nginx starts, the path is relative to conf - -- Therefore we need to check the absolute version instead - cert_path = pl_path.abspath(cert_path) + local cert_paths = {} + local ssl_certificates = yaml_conf.apisix.ssl.ssl_trusted_certificate + for cert_path in string.gmatch(ssl_certificates, '([^,]+)') do + cert_path = util.trim(cert_path) + if cert_path == "system" then + local trusted_certs_path, err = util.get_system_trusted_certs_filepath() + if not trusted_certs_path then + util.die(err) + end + table.insert(cert_paths, trusted_certs_path) + else + -- During validation, the path is relative to PWD + -- When Nginx starts, the path is relative to conf + -- Therefore we need to check the absolute version instead + cert_path = pl_path.abspath(cert_path) + if not pl_path.exists(cert_path) then + util.die("certificate path", cert_path, "doesn't exist\n") + end - if not pl_path.exists(cert_path) then - util.die("certificate path", cert_path, "doesn't exist\n") + table.insert(cert_paths, cert_path) + end end - yaml_conf.apisix.ssl.ssl_trusted_certificate = cert_path + local combined_cert_filepath = yaml_conf.apisix.ssl.ssl_trusted_combined_path + or "/usr/local/apisix/conf/ssl_trusted_combined.pem" + util.gen_trusted_certs_combined_file(combined_cert_filepath, cert_paths) + + yaml_conf.apisix.ssl.ssl_trusted_certificate = combined_cert_filepath end -- enable ssl with place holder crt&key diff --git a/apisix/cli/schema.lua b/apisix/cli/schema.lua index e6720f88fa2a..1def95484bcb 100644 --- a/apisix/cli/schema.lua +++ b/apisix/cli/schema.lua @@ -209,6 +209,9 @@ local config_schema = { ssl_trusted_certificate = { type = "string", }, + ssl_trusted_combined_path = { + type = "string", + }, listen = { type = "array", items = { diff --git a/apisix/cli/util.lua b/apisix/cli/util.lua index bcd56a241aa8..d69468efb5da 100644 --- a/apisix/cli/util.lua +++ b/apisix/cli/util.lua @@ -24,6 +24,9 @@ local exit = os.exit local stderr = io.stderr local str_format = string.format local tonumber = tonumber +local io = io +local ipairs = ipairs +local assert = assert local _M = {} @@ -133,4 +136,54 @@ function _M.file_exists(file_path) return f ~= nil and close(f) end +do + local trusted_certs_paths = { + "/etc/ssl/certs/ca-certificates.crt", -- Debian/Ubuntu/Gentoo + "/etc/pki/tls/certs/ca-bundle.crt", -- Fedora/RHEL 6 + "/etc/ssl/ca-bundle.pem", -- OpenSUSE + "/etc/pki/tls/cacert.pem", -- OpenELEC + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", -- CentOS/RHEL 7 + "/etc/ssl/cert.pem", -- OpenBSD, Alpine + } + + -- Check if a file exists using Lua's built-in `io.open` + local function file_exists(path) + local file = io.open(path, "r") + if file then + file:close() + return true + else + return false + end + end + + function _M.get_system_trusted_certs_filepath() + for _, path in ipairs(trusted_certs_paths) do + if file_exists(path) then + return path + end + end + + return nil, + "Could not find trusted certs file in " .. + "any of the `system`-predefined locations. " .. + "Please install a certs file there or set " .. + "`lua_ssl_trusted_certificate` to a " .. + "specific file path instead of `system`" + end +end + + +function _M.gen_trusted_certs_combined_file(combined_filepath, paths) + local combined_file = assert(io.open(combined_filepath, "w")) + for _, path in ipairs(paths) do + local cert_file = assert(io.open(path, "r")) + combined_file:write(cert_file:read("*a")) + combined_file:write("\n") + cert_file:close() + end + combined_file:close() +end + + return _M diff --git a/apisix/plugins/body-transformer.lua b/apisix/plugins/body-transformer.lua index f9d5400e410c..9cb881a30386 100644 --- a/apisix/plugins/body-transformer.lua +++ b/apisix/plugins/body-transformer.lua @@ -29,12 +29,14 @@ local type = type local pcall = pcall local pairs = pairs local next = next +local multipart = require("multipart") local setmetatable = setmetatable local transform_schema = { type = "object", properties = { - input_format = { type = "string", enum = {"xml", "json", "encoded", "args", "plain"} }, + input_format = { type = "string", + enum = {"xml", "json", "encoded", "args", "plain", "multipart",}}, template = { type = "string" }, template_is_base64 = { type = "boolean" }, }, @@ -118,6 +120,10 @@ local decoders = { args = function() return req_get_uri_args() end, + multipart = function (data, content_type_header) + local res = multipart(data, content_type_header) + return res + end } @@ -128,11 +134,20 @@ end local function transform(conf, body, typ, ctx, request_method) local out = {} + local _multipart local format = conf[typ].input_format + local ct = ctx.var.http_content_type + if typ == "response" then + ct = ngx.header.content_type + end if (body or request_method == "GET") and format ~= "plain" then local err if format then - out, err = decoders[format](body) + out, err = decoders[format](body, ct) + if format == "multipart" then + _multipart = out + out = out:get_all_with_arrays() + end if not out then err = str_format("%s body decode: %s", typ, err) core.log.error(err, ", body=", body) @@ -160,7 +175,9 @@ local function transform(conf, body, typ, ctx, request_method) _body = body, _escape_xml = escape_xml, _escape_json = escape_json, + _multipart = _multipart }}) + local ok, render_out = pcall(render, out) if not ok then local err = str_format("%s template rendering: %s", typ, render_out) @@ -184,6 +201,8 @@ local function set_input_format(conf, typ, ct, method) conf[typ].input_format = "json" elseif str_find(ct:lower(), "application/x-www-form-urlencoded", nil, true) then conf[typ].input_format = "encoded" + elseif str_find(ct:lower(), "multipart/", nil, true) then + conf[typ].input_format = "multipart" end end end diff --git a/apisix/plugins/multi-auth.lua b/apisix/plugins/multi-auth.lua index 9a259d038238..7d34ffb23c41 100644 --- a/apisix/plugins/multi-auth.lua +++ b/apisix/plugins/multi-auth.lua @@ -18,6 +18,7 @@ local core = require("apisix.core") local require = require local pairs = pairs local type = type +local plugin = require("apisix.plugin") local schema = { type = "object", @@ -48,7 +49,7 @@ function _M.check_schema(conf) local auth_plugins = conf.auth_plugins for k, auth_plugin in pairs(auth_plugins) do for auth_plugin_name, auth_plugin_conf in pairs(auth_plugin) do - local auth = require("apisix.plugins." .. auth_plugin_name) + local auth = plugin.get(auth_plugin_name) if auth == nil then return false, auth_plugin_name .. " plugin did not found" else @@ -73,7 +74,7 @@ function _M.rewrite(conf, ctx) for k, auth_plugin in pairs(auth_plugins) do for auth_plugin_name, auth_plugin_conf in pairs(auth_plugin) do - local auth = require("apisix.plugins." .. auth_plugin_name) + local auth = plugin.get(auth_plugin_name) -- returns 401 HTTP status code if authentication failed, otherwise returns nothing. local auth_code, err = auth.rewrite(auth_plugin_conf, ctx) if type(err) == "table" then diff --git a/apisix/plugins/workflow.lua b/apisix/plugins/workflow.lua index 73d68375dd9d..bf5221dd1d91 100644 --- a/apisix/plugins/workflow.lua +++ b/apisix/plugins/workflow.lua @@ -49,7 +49,7 @@ local schema = { } } }, - required = {"case", "actions"} + required = {"actions"} } } }, @@ -117,9 +117,11 @@ function _M.check_schema(conf) end for idx, rule in ipairs(conf.rules) do - local ok, err = expr.new(rule.case) - if not ok then - return false, "failed to validate the 'case' expression: " .. err + if rule.case then + local ok, err = expr.new(rule.case) + if not ok then + return false, "failed to validate the 'case' expression: " .. err + end end local actions = rule.actions @@ -143,10 +145,12 @@ end function _M.access(conf, ctx) - local match_result for _, rule in ipairs(conf.rules) do - local expr, _ = expr.new(rule.case) - match_result = expr:eval(ctx.var) + local match_result = true + if rule.case then + local expr, _ = expr.new(rule.case) + match_result = expr:eval(ctx.var) + end if match_result then -- only one action is currently supported local action = rule.actions[1] diff --git a/ci/linux_openresty_common_runner.sh b/ci/linux_openresty_common_runner.sh index 1b73ceec92c6..6dda698ffefc 100755 --- a/ci/linux_openresty_common_runner.sh +++ b/ci/linux_openresty_common_runner.sh @@ -38,7 +38,15 @@ do_install() { # sudo apt-get install tree -y # tree deps + # The latest version of test-nginx is not compatible with the current set of tests with ---http2 + # due to this commit: https://github.com/openresty/test-nginx/commit/0ccd106cbe6878318e5a591634af8f1707c411a6 + # This change pins test-nginx to a commit before this one. git clone --depth 1 https://github.com/openresty/test-nginx.git test-nginx + cd test-nginx + git fetch --depth=1 origin ced30a31bafab6c68873efb17b6d80f39bcd95f5 + git checkout ced30a31bafab6c68873efb17b6d80f39bcd95f5 + cd .. + make utils mkdir -p build-cache diff --git a/conf/config.yaml.example b/conf/config.yaml.example index eea2335bc99d..99af131d198c 100644 --- a/conf/config.yaml.example +++ b/conf/config.yaml.example @@ -99,8 +99,9 @@ apisix: # - ip: 127.0.0.3 # If not set, default to `0.0.0.0`. # port: 9445 # enable_http3: true - # ssl_trusted_certificate: /path/to/ca-cert # Set the path to CA certificates used to verify client - # certificates in the PEM format. + ssl_trusted_combined_path: /usr/local/apisix/conf/ssl_trusted_combined.pem # All the trusted certificates will be combined into a single file + #ssl_trusted_certificate: system # Specifies comma separated list of trusted CA. Value can be either "system"(for using system available ca certs) or + # a file path with trusted CA certificates in the PEM format ssl_protocols: TLSv1.2 TLSv1.3 # TLS versions supported. ssl_ciphers: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 ssl_session_tickets: false # If true, session tickets are used for SSL/TLS connections. @@ -610,8 +611,9 @@ plugin_attr: # Plugin attributes # - 100 # - 200 # - 500 - # expire: 0 # The expiration time after metrics become inactive, unit: second. - # 0 means the metrics will not expire + # expire: 0 # The expiration time of metrics in seconds. + # 0 means the metrics will not expire. + # Only affect apisix_http_status, apisix_bandwidth, and apisix_http_latency. # If you need to set the expiration time, it is recommended to use 600, which is 10 minutes. server-info: # Plugin: server-info report_ttl: 60 # Set the TTL in seconds for server info in etcd. diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md index c7a236da77d0..e6099e65a2c4 100644 --- a/docs/en/latest/admin-api.md +++ b/docs/en/latest/admin-api.md @@ -913,25 +913,25 @@ Prerequisite: Consumer `jack` has been created. Create the `key-auth` Credential for consumer `jack`: - ```shell - curl http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials/auth-one \ - -H "X-API-KEY: $admin_key" -X PUT -i -d ' - { - "plugins": { - "key-auth": { - "key": "auth-one" - } +```shell +curl http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials/auth-one \ +-H "X-API-KEY: $admin_key" -X PUT -i -d ' +{ + "plugins": { + "key-auth": { + "key": "auth-one" } - }' - ``` + } +}' +``` - ``` - HTTP/1.1 200 OK - Date: Thu, 26 Dec 2019 08:17:49 GMT - ... +``` +HTTP/1.1 200 OK +Date: Thu, 26 Dec 2019 08:17:49 GMT +... - {"key":"\/apisix\/consumers\/jack\/credentials\/auth-one","value":{"update_time":1666260780,"plugins":{"key-auth":{"key":"auth-one"}},"create_time":1666260780}} - ``` +{"key":"\/apisix\/consumers\/jack\/credentials\/auth-one","value":{"update_time":1666260780,"plugins":{"key-auth":{"key":"auth-one"}},"create_time":1666260780}} +``` ## Upstream @@ -1278,7 +1278,7 @@ For notes on ID syntax please refer to: [ID Syntax](#quick-note-on-id-syntax) | labels | False | Match Rules | Attributes of the resource specified as key-value pairs. | {"version":"v2","build":"16","env":"production"} | | type | False | Auxiliary | Identifies the type of certificate, default `server`. | `client` Indicates that the certificate is a client certificate, which is used when APISIX accesses the upstream; `server` Indicates that the certificate is a server-side certificate, which is used by APISIX when verifying client requests. | | status | False | Auxiliary | Enables the current SSL. Set to `1` (enabled) by default. | `1` to enable, `0` to disable | -| ssl_protocols | False | An array of ssl protocols | It is used to control the SSL/TLS protocol version used between servers and clients. See [SSL Protocol](./ssl-protocol.md) for more examples. | `["TLSv1.2", "TLSv2.3"]` | +| ssl_protocols | False | An array of ssl protocols | It is used to control the SSL/TLS protocol version used between servers and clients. See [SSL Protocol](./ssl-protocol.md) for more examples. | `["TLSv1.1", "TLSv1.2", "TLSv1.3"]` | Example Configuration: diff --git a/docs/en/latest/plugins/jwt-auth.md b/docs/en/latest/plugins/jwt-auth.md index 1f8f470927ed..38fab4e873be 100644 --- a/docs/en/latest/plugins/jwt-auth.md +++ b/docs/en/latest/plugins/jwt-auth.md @@ -47,18 +47,18 @@ For Consumer: | exp | integer | False | 86400 | [1,...] | Expiry time of the token in seconds. | | base64_secret | boolean | False | false | | Set to true if the secret is base64 encoded. | | lifetime_grace_period | integer | False | 0 | [0,...] | Define the leeway in seconds to account for clock skew between the server that generated the jwt and the server validating it. Value should be zero (0) or a positive integer. | -| key_claim_name | string | False | key | | The name of the JWT claim that contains the user key (corresponds to Consumer's key attribute). | NOTE: `encrypt_fields = {"secret"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields). For Route: -| Name | Type | Required | Default | Description | -|--------|--------|----------|---------------|---------------------------------------------------------------------| -| header | string | False | authorization | The header to get the token from. | -| query | string | False | jwt | The query string to get the token from. Lower priority than header. | -| cookie | string | False | jwt | The cookie to get the token from. Lower priority than query. | -| hide_credentials | boolean | False | false | Set to true will not pass the authorization request of header\query\cookie to the Upstream.| +| Name | Type | Required | Default | Description | +|------------------|---------|----------|---------------|-------------------------------------------------------------------------------------------------| +| header | string | False | authorization | The header to get the token from. | +| query | string | False | jwt | The query string to get the token from. Lower priority than header. | +| cookie | string | False | jwt | The cookie to get the token from. Lower priority than query. | +| hide_credentials | boolean | False | false | Set to true will not pass the authorization request of header\query\cookie to the Upstream. | +| key_claim_name | string | False | key | The name of the JWT claim that contains the user key (corresponds to Consumer's key attribute). | You can implement `jwt-auth` with [HashiCorp Vault](https://www.vaultproject.io/) to store and fetch secrets and RSA keys pairs from its [encrypted KV engine](https://developer.hashicorp.com/vault/docs/secrets/kv) using the [APISIX Secret](../terminology/secret.md) resource. diff --git a/docs/en/latest/plugins/openid-connect.md b/docs/en/latest/plugins/openid-connect.md index 4483abd656dd..0ee7d7eb86b4 100644 --- a/docs/en/latest/plugins/openid-connect.md +++ b/docs/en/latest/plugins/openid-connect.md @@ -70,7 +70,7 @@ description: OpenID Connect allows the client to obtain user information from th | proxy_opts.http_proxy_authorization | string | False | | Basic [base64 username:password] | Default `Proxy-Authorization` header value to be used with `http_proxy`. Can be overridden with custom `Proxy-Authorization` request header. | | proxy_opts.https_proxy_authorization | string | False | | Basic [base64 username:password] | Default `Proxy-Authorization` header value to be used with `https_proxy`. Cannot be overridden with custom `Proxy-Authorization` request header since with with HTTPS the authorization is completed when connecting. | | proxy_opts.no_proxy | string | False | | | Comma separated list of hosts that should not be proxied. | -| authorization_params | object | False | | | Additional parameters to send in the in the request to the authorization endpoint. | +| authorization_params | object | False | | | Additional parameters to send in the request to the authorization endpoint. | | client_rsa_private_key | string | False | | | Client RSA private key used to sign JWT. | | client_rsa_private_key_id | string | False | | | Client RSA private key ID used to compute a signed JWT. | | client_jwt_assertion_expires_in | integer | False | 60 | | Life duration of the signed JWT in seconds. | @@ -101,7 +101,7 @@ Tutorial: [Use Keycloak with API Gateway to secure APIs](https://apisix.apache.o ::: -This plugin offers two scenorios: +This plugin offers two scenarios: 1. Authentication between Services: Set `bearer_only` to `true` and configure the `introspection_endpoint` or `public_key` attribute. In this scenario, APISIX will reject requests without a token or invalid token in the request header. diff --git a/docs/en/latest/plugins/prometheus.md b/docs/en/latest/plugins/prometheus.md index 01301c284e97..3200fb48a09c 100644 --- a/docs/en/latest/plugins/prometheus.md +++ b/docs/en/latest/plugins/prometheus.md @@ -96,6 +96,18 @@ plugin_attr: - 505 ``` +### Specifying `expire` + +`expire` sets the expiration time of `apisix_http_status`, `apisix_bandwidth`, and `apisix_http_latency` metrics in seconds. When set to 0, metrics will not expire. + +Here is a configuration example: + +```yaml title="conf/config.yaml" +plugin_attr: + prometheus: + expire: 86400 +``` + ## Metrics endpoint This Plugin will add the metrics endpoint `/apisix/prometheus/metrics` or your custom export URI for exposing the metrics. @@ -257,6 +269,7 @@ The following metrics are exported by the `prometheus` Plugin: | Name | Description | |----------|-------------------------------------------------------------------------------------------------------------------------------------| | type | Value can be one of `apisix`, `upstream`, or `request`. This translates to latency caused by APISIX, Upstream, or both (their sum). | + | route | `route_id` of the matched Route with request. Defaults to an empty string if the Routes don't match. | | service | `service_id` of the Route matching the request. If the Route does not have a `service_id` configured, it defaults to `$host`. | | consumer | `consumer_name` of the Consumer matching the request. Defaults to an empty string if it does not match. | | node | IP address of the Upstream node. | diff --git a/docs/en/latest/plugins/traffic-split.md b/docs/en/latest/plugins/traffic-split.md index 3a206a92405c..9eb00d65127f 100644 --- a/docs/en/latest/plugins/traffic-split.md +++ b/docs/en/latest/plugins/traffic-split.md @@ -63,7 +63,7 @@ The traffic ratio between Upstream services may be less accurate since round rob :::note -Some of the configuration fields supported in Upstream are not supported in weighted_upstreams.upstream. These fields are `service_name`, `discovery_type`, `checks`, `retries`, `retry_timeout`, `desc`, `scheme`, `labels`, `create_time`, and `update_time`. +Some of the configuration fields supported in Upstream are not supported in weighted_upstreams.upstream. These fields are `service_name`, `discovery_type`, `checks`, `retries`, `retry_timeout`, `desc`, `labels`, `create_time`, and `update_time`. As a workaround, you can create an Upstream object and configure it in `weighted_upstreams.upstream_id` to achieve these functionalities. diff --git a/docs/en/latest/plugins/workflow.md b/docs/en/latest/plugins/workflow.md index 2e2fdace19b8..99fd8fd75a81 100644 --- a/docs/en/latest/plugins/workflow.md +++ b/docs/en/latest/plugins/workflow.md @@ -36,7 +36,7 @@ The `workflow` plugin is used to introduce [lua-resty-expr](https://github.com/a | Name | Type | Required | Default | Valid values | Description | | ---------------------------- | ------------- | -------- | ------- | ------------ | ------------------------------------------------------------ | -| rules.case | array[array] | True | | | List of variables to match for filtering requests for conditional traffic split. It is in the format `{variable operator value}`. For example, `{"arg_name", "==", "json"}`. The variables here are consistent with NGINX internal variables. For details on supported operators, you can refer to [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). | +| rules.case | array[array] | False | | | List of variables to match for filtering requests for conditional traffic split. It is in the format `{variable operator value}`. For example, `{"arg_name", "==", "json"}`. The variables here are consistent with NGINX internal variables. For details on supported operators, you can refer to [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). | | rules.actions | array[object] | True | | | The action to be performed when the case matches successfully. Currently, only one element is supported in actions. The first child element of the actions' only element can be `return` or `limit-count`. | ### `actions` Attributes @@ -58,6 +58,7 @@ The `workflow` plugin is used to introduce [lua-resty-expr](https://github.com/a :::note In `rules`, match `case` in order according to the index of the `rules`, and execute `actions` directly if `case` match. +If `case` is missing, the default behavior is to match. ::: diff --git a/docs/en/latest/terminology/credential.md b/docs/en/latest/terminology/credential.md index 560d4314a704..21b263f38685 100644 --- a/docs/en/latest/terminology/credential.md +++ b/docs/en/latest/terminology/credential.md @@ -123,14 +123,14 @@ admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"/ 4. Test. -Test the request with the `auth-one` and `auth-two` keys, and they both respond correctly. + Test the request with the `auth-one` and `auth-two` keys, and they both respond correctly. ```shell curl http://127.0.0.1:9080/hello -H 'apikey: auth-one' -I curl http://127.0.0.1:9080/hello -H 'apikey: auth-two' -I ``` -Enable the `limit-count` plugin for the Consumer. + Enable the `limit-count` plugin for the Consumer. ```shell curl http://127.0.0.1:9180/apisix/admin/consumers \ @@ -148,4 +148,4 @@ Enable the `limit-count` plugin for the Consumer. }' ``` -Requesting the route more than 3 times in a row with each of the two keys, the test returns `503` and the request is restricted. + Requesting the route more than 3 times in a row with each of the two keys, the test returns `503` and the request is restricted. diff --git a/docs/zh/latest/admin-api.md b/docs/zh/latest/admin-api.md index f5cd5b144b05..0bc5a963c21b 100644 --- a/docs/zh/latest/admin-api.md +++ b/docs/zh/latest/admin-api.md @@ -926,25 +926,25 @@ Credential 对象 JSON 配置示例: 创建 Credential,并启用认证插件 `key-auth`: - ```shell - curl http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials/auth-one \ - -H "X-API-KEY: $admin_key" -X PUT -i -d ' - { - "plugins": { - "key-auth": { - "key": "auth-one" - } +```shell +curl http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials/auth-one \ +-H "X-API-KEY: $admin_key" -X PUT -i -d ' +{ + "plugins": { + "key-auth": { + "key": "auth-one" } - }' - ``` + } +}' +``` - ``` - HTTP/1.1 200 OK - Date: Thu, 26 Dec 2019 08:17:49 GMT - ... +``` +HTTP/1.1 200 OK +Date: Thu, 26 Dec 2019 08:17:49 GMT +... - {"key":"\/apisix\/consumers\/jack\/credentials\/auth-one","value":{"update_time":1666260780,"plugins":{"key-auth":{"key":"auth-one"}},"create_time":1666260780}} - ``` +{"key":"\/apisix\/consumers\/jack\/credentials\/auth-one","value":{"update_time":1666260780,"plugins":{"key-auth":{"key":"auth-one"}},"create_time":1666260780}} +``` ## Upstream @@ -1278,7 +1278,7 @@ SSL 资源请求地址:/apisix/admin/ssls/{id} | labels | 否 | 匹配规则 | 标识附加属性的键值对。 | {"version":"v2","build":"16","env":"production"} | | type | 否 | 辅助 | 标识证书的类型,默认值为 `server`。 | `client` 表示证书是客户端证书,APISIX 访问上游时使用;`server` 表示证书是服务端证书,APISIX 验证客户端请求时使用。 | | status | 否 | 辅助 | 当设置为 `1` 时,启用此 SSL,默认值为 `1`。 | `1` 表示启用,`0` 表示禁用 | -| ssl_protocols | 否 | tls 协议字符串数组 | 用于控制服务器与客户端之间使用的 SSL/TLS 协议版本。更多的配置示例,请参考[SSL 协议](./ssl-protocol.md)。 | | +| ssl_protocols | 否 | tls 协议字符串数组 | 用于控制服务器与客户端之间使用的 SSL/TLS 协议版本。更多的配置示例,请参考[SSL 协议](./ssl-protocol.md)。 | `["TLSv1.1", "TLSv1.2", "TLSv1.3"]` | SSL 对象 JSON 配置示例: diff --git a/docs/zh/latest/plugins/jwt-auth.md b/docs/zh/latest/plugins/jwt-auth.md index 88065cb50cd9..6c848aa3d4b2 100644 --- a/docs/zh/latest/plugins/jwt-auth.md +++ b/docs/zh/latest/plugins/jwt-auth.md @@ -58,6 +58,7 @@ Route 端: | query | string | 否 | jwt | 设置我们从哪个 query string 获取 token,优先级低于 header。 | | cookie | string | 否 | jwt | 设置我们从哪个 cookie 获取 token,优先级低于 query。 | | hide_credentials | boolean | 否 | false | 该参数设置为 `true` 时,则不会将含有认证信息的 header\query\cookie 传递给 Upstream。| +| key_claim_name | string | 否 | key | 包含用户密钥(对应消费者的密钥属性)的 JWT 声明的名称。| 您可以使用 [HashiCorp Vault](https://www.vaultproject.io/) 实施 `jwt-auth`,以从其[加密的 KV 引擎](https://developer.hashicorp.com/vault/docs/secrets/kv) 使用 [APISIX Secret](../terminology/secret.md) 资源。 diff --git a/docs/zh/latest/plugins/prometheus.md b/docs/zh/latest/plugins/prometheus.md index f6809a26eb31..39720704d289 100644 --- a/docs/zh/latest/plugins/prometheus.md +++ b/docs/zh/latest/plugins/prometheus.md @@ -43,7 +43,7 @@ description: 本文将介绍 API 网关 Apache APISIX 如何通过 prometheus ::: -## 如何修改暴露指标的 uri +### 如何修改暴露指标的 `export_uri` 你可以在配置文件 `./conf/config.yaml` 的 `plugin_attr` 列表下修改默认的 URI。 @@ -78,6 +78,18 @@ plugin_attr: - 505 ``` +### 如何修改指标的 `expire` + +`expire` 用于设置 `apisix_http_status`、`apisix_bandwidth` 和 `apisix_http_latency` 指标的过期时间(以秒为单位)。当设置为 0 时,指标不会过期。 + +配置示例如下: + +```yaml title="conf/config.yaml" +plugin_attr: + prometheus: + expire: 86400 +``` + ## API `prometheus` 插件会增加 `/apisix/prometheus/metrics` 接口或者你自定义的 URI 来暴露其指标信息。 @@ -231,6 +243,7 @@ scrape_configs: | 名称 | 描述 | | -------------| --------------------------------------------------------------------------------------- | | type | 该值可以是 `apisix`、`upstream` 和 `request`,分别表示耗时的来源是 APISIX、上游以及两者总和。 | + | route | 与请求匹配的路由的 `route_id`,如果未匹配,则默认为空字符串。 | | service | 与请求匹配的路由 的 `service_id`。当路由缺少 `service_id` 时,则默认为 `$host`。 | | consumer | 与请求匹配的消费者的 `consumer_name`。未匹配,则默认为空字符串。 | | node | 上游节点的 IP 地址。 | diff --git a/docs/zh/latest/plugins/traffic-split.md b/docs/zh/latest/plugins/traffic-split.md index 0cd3874d039b..1a725ae9834b 100644 --- a/docs/zh/latest/plugins/traffic-split.md +++ b/docs/zh/latest/plugins/traffic-split.md @@ -61,7 +61,7 @@ description: 本文介绍了 Apache APISIX traffic-split 插件的相关操作 :::note 注意 -目前 `weighted_upstreams.upstream` 的配置不支持 `service_name`、`discovery_type`、`checks`、`retries`、`retry_timeout`、`desc`、`scheme`、`labels`、`create_time` 和 `update_time` 等字段。如果你需要使用这些字段,可以在创建上游对象时指定这些字段,然后在该插件中配置 `weighted_upstreams.upstream_id` 属性即可。 +目前 `weighted_upstreams.upstream` 的配置不支持 `service_name`、`discovery_type`、`checks`、`retries`、`retry_timeout`、`desc`、`labels`、`create_time` 和 `update_time` 等字段。如果你需要使用这些字段,可以在创建上游对象时指定这些字段,然后在该插件中配置 `weighted_upstreams.upstream_id` 属性即可。 ::: diff --git a/docs/zh/latest/terminology/credential.md b/docs/zh/latest/terminology/credential.md index 4d183620240f..48ba4ff181e4 100644 --- a/docs/zh/latest/terminology/credential.md +++ b/docs/zh/latest/terminology/credential.md @@ -124,14 +124,14 @@ admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"/ 4. 测试插件 -分别使用 `auth-one` 和 `auth-two` 两个 key 来测试请求,都响应正常。 + 分别使用 `auth-one` 和 `auth-two` 两个 key 来测试请求,都响应正常。 ```shell curl http://127.0.0.1:9080/hello -H 'apikey: auth-one' -I curl http://127.0.0.1:9080/hello -H 'apikey: auth-two' -I ``` -为该 Consumer 启用 `limit-count` 插件。 + 为该 Consumer 启用 `limit-count` 插件。 ```shell curl http://127.0.0.1:9180/apisix/admin/consumers \ @@ -149,4 +149,4 @@ admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"/ }' ``` -分别使用这两个 key 连续 3 次以上请求该路由,测试返回 `503`,请求被限制。 + 分别使用这两个 key 连续 3 次以上请求该路由,测试返回 `503`,请求被限制。 diff --git a/docs/zh/latest/terminology/secret.md b/docs/zh/latest/terminology/secret.md index 810abb7ddf6f..22a3f4902ed8 100644 --- a/docs/zh/latest/terminology/secret.md +++ b/docs/zh/latest/terminology/secret.md @@ -173,7 +173,7 @@ secrets: :::tip -它现在支持使用 [`namespace` 字段](../admin-api.md#secret-config-body-requset-parameters] 设置 [HashiCorp Vault Enterprise](https://developer.hashicorp.com/vault/docs/enterprise/namespaces#vault-api-and-namespaces) 和 HCP Vault 所支持的多租户命名空间概念。 +它现在支持使用 [`namespace` 字段](../admin-api.md#secret-config-body-requset-parameters) 设置 [HashiCorp Vault Enterprise](https://developer.hashicorp.com/vault/docs/enterprise/namespaces#vault-api-and-namespaces) 和 HCP Vault 所支持的多租户命名空间概念。 ::: diff --git a/t/cli/test_stream_config.sh b/t/cli/test_stream_config.sh index baab138a0c99..2843b5c5d13a 100755 --- a/t/cli/test_stream_config.sh +++ b/t/cli/test_stream_config.sh @@ -78,6 +78,7 @@ echo " apisix: ssl: ssl_trusted_certificate: t/certs/mtls_ca.crt + ssl_trusted_combined_path: t/certs/mtls_ca_combined.crt proxy_mode: http&stream stream_proxy: tcp: @@ -86,7 +87,7 @@ apisix: make init -if ! grep "t/certs/mtls_ca.crt;" conf/nginx.conf > /dev/null; then +if ! grep "t/certs/mtls_ca_combined.crt;" conf/nginx.conf > /dev/null; then echo "failed: failed to set trust certificate" exit 1 fi diff --git a/t/cli/test_upstream_mtls.sh b/t/cli/test_upstream_mtls.sh index 0318a4539a27..b2b366aa0635 100755 --- a/t/cli/test_upstream_mtls.sh +++ b/t/cli/test_upstream_mtls.sh @@ -149,3 +149,63 @@ if ! grep -E 'self-signed certificate' logs/error.log; then fi echo "passed: when proxy_ssl_verify is enabled and ssl_trusted_certificate is wrong ca cert, got 502" + + +# test combined proxy_ssl_trusted_certificate success +echo ' +apisix: + ssl: + ssl_trusted_certificate: system, t/certs/apisix.crt +nginx_config: + http_configuration_snippet: | + server { + listen 1983 ssl; + server_name test.com; + ssl_certificate ../t/certs/apisix.crt; + ssl_certificate_key ../t/certs/apisix.key; + location /hello { + return 200 "hello world"; + } + } + http_server_configuration_snippet: | + proxy_ssl_verify on; +' > conf/config.yaml + +rm logs/error.log || true +make init +make run +sleep 0.1 + +curl -k -i http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/hello", + "upstream": { + "pass_host": "rewrite", + "nodes": { + "127.0.0.1:1983": 1 + }, + "scheme": "https", + "hash_on": "vars", + "upstream_host": "test.com", + "type": "roundrobin", + "tls": { + "client_cert": "-----BEGIN CERTIFICATE-----\nMIIEojCCAwqgAwIBAgIJAK253pMhgCkxMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV\nBAYTAkNOMRIwEAYDVQQIDAlHdWFuZ0RvbmcxDzANBgNVBAcMBlpodUhhaTEPMA0G\nA1UECgwGaXJlc3R5MREwDwYDVQQDDAh0ZXN0LmNvbTAgFw0xOTA2MjQyMjE4MDVa\nGA8yMTE5MDUzMTIyMTgwNVowVjELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUd1YW5n\nRG9uZzEPMA0GA1UEBwwGWmh1SGFpMQ8wDQYDVQQKDAZpcmVzdHkxETAPBgNVBAMM\nCHRlc3QuY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCM0rqJe\ncvgnCfOw4fATotPwk5Ba0gC2YvIrO+gSbQkyxXF5jhZB3W6BkWUWR4oNFLLSqcVb\nVDPitz/Mt46Mo8amuS6zTbQetGnBARzPLtmVhJfoeLj0efMiOepOSZflj9Ob4yKR\n2bGdEFOdHPjm+4ggXU9jMKeLqdVvxll/JiVFBW5smPtW1Oc/BV5terhscJdOgmRr\nabf9xiIis9/qVYfyGn52u9452V0owUuwP7nZ01jt6iMWEGeQU6mwPENgvj1olji2\nWjdG2UwpUVp3jp3l7j1ekQ6mI0F7yI+LeHzfUwiyVt1TmtMWn1ztk6FfLRqwJWR/\nEvm95vnfS3Le4S2ky3XAgn2UnCMyej3wDN6qHR1onpRVeXhrBajbCRDRBMwaNw/1\n/3Uvza8QKK10PzQR6OcQ0xo9psMkd9j9ts/dTuo2fzaqpIfyUbPST4GdqNG9NyIh\n/B9g26/0EWcjyO7mYVkaycrtLMaXm1u9jyRmcQQI1cGrGwyXbrieNp63AgMBAAGj\ncTBvMB0GA1UdDgQWBBSZtSvV8mBwl0bpkvFtgyiOUUcbszAfBgNVHSMEGDAWgBSZ\ntSvV8mBwl0bpkvFtgyiOUUcbszAMBgNVHRMEBTADAQH/MB8GA1UdEQQYMBaCCHRl\nc3QuY29tggoqLnRlc3QuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQAHGEul/x7ViVgC\ntC8CbXEslYEkj1XVr2Y4hXZXAXKd3W7V3TC8rqWWBbr6L/tsSVFt126V5WyRmOaY\n1A5pju8VhnkhYxYfZALQxJN2tZPFVeME9iGJ9BE1wPtpMgITX8Rt9kbNlENfAgOl\nPYzrUZN1YUQjX+X8t8/1VkSmyZysr6ngJ46/M8F16gfYXc9zFj846Z9VST0zCKob\nrJs3GtHOkS9zGGldqKKCj+Awl0jvTstI4qtS1ED92tcnJh5j/SSXCAB5FgnpKZWy\nhme45nBQj86rJ8FhN+/aQ9H9/2Ib6Q4wbpaIvf4lQdLUEcWAeZGW6Rk0JURwEog1\n7/mMgkapDglgeFx9f/XztSTrkHTaX4Obr+nYrZ2V4KOB4llZnK5GeNjDrOOJDk2y\nIJFgBOZJWyS93dQfuKEj42hA79MuX64lMSCVQSjX+ipR289GQZqFrIhiJxLyA+Ve\nU/OOcSRr39Kuis/JJ+DkgHYa/PWHZhnJQBxcqXXk1bJGw9BNbhM=\n-----END CERTIFICATE-----\n", + "client_key": "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" + } + } +}' + +sleep 1 + +code=$(curl -v -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.1:9080/hello) + +if [ ! $code -eq 200 ]; then + echo "failed: connection to upstream with mTLS failed" + exit 1 +fi + +sleep 0.1 + +make stop + +echo "passed: connection to upstream with mTLS success" diff --git a/t/core/config_etcd.t b/t/core/config_etcd.t index 7f31fc8592be..75b0e9bb67a2 100644 --- a/t/core/config_etcd.t +++ b/t/core/config_etcd.t @@ -60,7 +60,7 @@ qr/(connection refused){1,}/ apisix: node_listen: 1984 ssl: - ssl_trusted_certificate: t/servroot/conf/cert/etcd.pem + ssl_trusted_combined_path: t/servroot/conf/cert/etcd.pem deployment: role: traditional role_traditional: diff --git a/t/plugin/body-transformer-multipart.t b/t/plugin/body-transformer-multipart.t new file mode 100644 index 000000000000..9bf228209a59 --- /dev/null +++ b/t/plugin/body-transformer-multipart.t @@ -0,0 +1,269 @@ +# +# 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 'no_plan'; + +no_long_string(); +no_shuffle(); +no_root_location(); + +add_block_preprocessor(sub { + my ($block) = @_; + + if (!$block->request) { + $block->set_value("request", "GET /t"); + } +}); + +run_tests; + +__DATA__ + +=== TEST 1: multipart request body to json request body conversion +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin") + local core = require("apisix.core") + + local code, body = t.test('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/echo", + "plugins": { + "body-transformer": { + "request": { + "template": "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}" + } + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + return + end + ngx.sleep(0.5) + + local http = require("resty.http") + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/echo" + + local body = ([[ +--AaB03x +Content-Disposition: form-data; name="name" + +Larry +--AaB03x +Content-Disposition: form-data; name="age" + +10 +--AaB03x--]]) + + local opt = {method = "POST", body = body, headers = {["Content-Type"] = "multipart/related; boundary=AaB03x"}} + local httpc = http.new() + local res = httpc:request_uri(uri, opt) + + ngx.status = res.status + ngx.say(res.body or res.reason) + } + } +--- response_body +{"foo":"Larry world","bar":20} + + + +=== TEST 2: multipart response body to json response body conversion +--- config + location /demo { + content_by_lua_block { + ngx.header["Content-Type"] = "multipart/related; boundary=AaB03x" + ngx.say([[ +--AaB03x +Content-Disposition: form-data; name="name" + +Larry +--AaB03x +Content-Disposition: form-data; name="age" + +10 +--AaB03x--]]) + } + } + location /t { + content_by_lua_block { + local t = require("lib.test_admin") + local core = require("apisix.core") + + local code, body = t.test('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/hello", + "plugins": { + "proxy-rewrite": { + "uri": "/demo" + }, + "body-transformer": { + "response": { + "template": "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}" + } + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1984": 1 + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + return + end + ngx.sleep(0.5) + + local http = require("resty.http") + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" + + local opt = {method = "GET"} + local httpc = http.new() + local res = httpc:request_uri(uri, opt) + + ngx.status = res.status + ngx.say(res.body or res.reason) + } + } +--- response_body +{"foo":"Larry world","bar":20} + + + +=== TEST 3: multipart parse result accessible to template renderer +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin") + local core = require("apisix.core") + + local req_template = ngx.encode_base64[[ + {% + local core = require 'apisix.core' + local cjson = require 'cjson' + if tonumber(context.age) > 18 then + context._multipart:set_simple("status", "major") + else + context._multipart:set_simple("status", "minor") + end + local body = context._multipart:tostring() + %}{* body *} + ]] + + local code, body = t.test('/apisix/admin/routes/1', + ngx.HTTP_PUT, + string.format([[{ + "uri": "/echo", + "plugins": { + "body-transformer": { + "response": { + "template": "%s" + } + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } + }]], req_template) + ) + + if code >= 300 then + ngx.status = code + return + end + ngx.sleep(0.5) + + ------------------------#######################------------------- + + local http = require("resty.http") + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/echo" + + local body_minor = ([[ +--AaB03x +Content-Disposition: form-data; name="name" + +Larry +--AaB03x +Content-Disposition: form-data; name="age" + +10 +--AaB03x--]]) + + + local opt = {method = "POST", body = body_minor, headers = {["Content-Type"] = "multipart/related; boundary=AaB03x"}} + local httpc = http.new() + local res = httpc:request_uri(uri, opt) + assert(res.status == 200) + + ngx.say(res.body) + + } + } +--- response_body eval +qr/.*Content-Disposition: form-data; name=\"status\"\r\n\r\nminor.*/ + + + +=== TEST 4: multipart parse response accessible to template renderer (test with age == 19) +--- config + location /t { + content_by_lua_block { + + local http = require("resty.http") + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/echo" + + local body_major = ([[ +--AaB03x +Content-Disposition: form-data; name="name" + +Larry +--AaB03x +Content-Disposition: form-data; name="age" + +19 +--AaB03x--]]) + + + local opt = {method = "POST", body = body_major, headers = {["Content-Type"] = "multipart/related; boundary=AaB03x"}} + local httpc = http.new() + local res = httpc:request_uri(uri, opt) + assert(res.status == 200) + + ngx.say(res.body) + + } + } +--- response_body eval +qr/.*Content-Disposition: form-data; name=\"status\"\r\n\r\nmajor.*/ diff --git a/t/plugin/workflow-without-case.t b/t/plugin/workflow-without-case.t new file mode 100644 index 000000000000..2ce469a7f0f4 --- /dev/null +++ b/t/plugin/workflow-without-case.t @@ -0,0 +1,85 @@ +# +# 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 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); +no_shuffle(); +add_block_preprocessor(sub { + my ($block) = @_; + + if (!$block->request) { + $block->set_value("request", "GET /t"); + } +}); + +run_tests(); + + +__DATA__ + +=== TEST 1: set plugin +--- 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, + [[{ + "plugins": { + "workflow": { + "rules": [ + { + "actions": [ + [ + "return", + { + "code": 403 + } + ] + ] + } + ] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 2: trigger workflow +--- request +GET /hello +--- error_code: 403 diff --git a/t/router/radixtree-uri-with-parameter2.t b/t/router/radixtree-uri-with-parameter2.t new file mode 100644 index 000000000000..8659a8bc0007 --- /dev/null +++ b/t/router/radixtree-uri-with-parameter2.t @@ -0,0 +1,108 @@ +# +# 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 'no_plan'; + +repeat_each(1); +log_level('info'); +worker_connections(256); +no_root_location(); +no_shuffle(); + +our $yaml_config = <<_EOC_; +apisix: + node_listen: 1984 + router: + http: 'radixtree_uri_with_parameter' +_EOC_ + +add_block_preprocessor(sub { + my ($block) = @_; + + if (!$block->yaml_config) { + $block->set_value("yaml_config", $yaml_config); + } +}); + +run_tests(); + +__DATA__ + +=== TEST 1: set route with :name as a uri parameter +--- 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, + [[{ + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "plugins": { + "proxy-rewrite":{"uri":"/hello"} + }, + "uri": "/name/:name/bar" + }]], + [[{ + "value": { + "uri": "/name/:name/bar", + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "plugins": { + "proxy-rewrite":{"uri":"/hello"} + }, + }, + "key": "/apisix/routes/1" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- response_body +passed + + + +=== TEST 2: parameters with special characters should pass +--- request +GET /name/with%20space/bar +--- error_code: 200 +--- response_body +hello world + + + +=== TEST 3: failing case for the above test +--- request +GET /name/with%20space/foo +--- error_code: 404 +--- response_body +{"error_msg":"404 Route Not Found"}