diff --git a/apisix/plugins/request-id.lua b/apisix/plugins/request-id.lua index ceddcdf949690..1aac9615d2f84 100644 --- a/apisix/plugins/request-id.lua +++ b/apisix/plugins/request-id.lua @@ -27,6 +27,9 @@ local tostring = tostring local math_pow = math.pow local math_ceil = math.ceil local math_floor = math.floor +local math_random = math.random +local str_byte = string.byte +local ffi = require "ffi" local plugin_name = "request-id" @@ -40,7 +43,27 @@ local schema = { properties = { header_name = {type = "string", default = "X-Request-Id"}, include_in_response = {type = "boolean", default = true}, - algorithm = {type = "string", enum = {"uuid", "snowflake", "nanoid"}, default = "uuid"} + algorithm = { + type = "string", + enum = {"uuid", "snowflake", "nanoid", "range_id"}, + default = "uuid" + }, + range_id = { + type = "object", + properties = { + length = { + type = "integer", + minimum = 6, + default = 16 + }, + char_set = { + type = "string", + -- The Length is set to 6 just avoid too short length, it may repeat + minLength = 6, + default = "abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789" + } + } + } } } @@ -202,14 +225,27 @@ local function next_id() return snowflake:next_id() end +-- generate range_id +local function get_range_id(range_id) + local res = ffi.new("unsigned char[?]", range_id.length) + for i = 0, range_id.length - 1 do + res[i] = str_byte(range_id.char_set, math_random(#range_id.char_set)) + end + return ffi.string(res, range_id.length) +end -local function get_request_id(algorithm) - if algorithm == "uuid" then +local function get_request_id(conf) + if conf.algorithm == "uuid" then return uuid() end - if algorithm == "nanoid" then + if conf.algorithm == "nanoid" then return nanoid.safe_simple() end + + if conf.algorithm == "range_id" then + return get_range_id(conf.range_id) + end + return next_id() end @@ -218,7 +254,7 @@ function _M.rewrite(conf, ctx) local headers = ngx.req.get_headers() local uuid_val if not headers[conf.header_name] then - uuid_val = get_request_id(conf.algorithm) + uuid_val = get_request_id(conf) core.request.set_header(ctx, conf.header_name, uuid_val) else uuid_val = headers[conf.header_name] diff --git a/docs/en/latest/plugins/request-id.md b/docs/en/latest/plugins/request-id.md index 31469dee33458..20f534d800494 100644 --- a/docs/en/latest/plugins/request-id.md +++ b/docs/en/latest/plugins/request-id.md @@ -44,7 +44,9 @@ The Plugin will not add a unique ID if the request already has a header with the | ------------------- | ------- | -------- | -------------- | ------------------------------- | ---------------------------------------------------------------------- | | header_name | string | False | "X-Request-Id" | | Header name for the unique request ID. | | include_in_response | boolean | False | true | | When set to `true`, adds the unique request ID in the response header. | -| algorithm | string | False | "uuid" | ["uuid", "snowflake", "nanoid"] | Algorithm to use for generating the unique request ID. | +| algorithm | string | False | "uuid" | ["uuid", "snowflake", "nanoid", "range_id"] | Algorithm to use for generating the unique request ID. | +| range_id.char_set | string | False | "abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789| The minimum string length is 6 | Character set for range_id | +| range_id.length | integer | False | 16 | Minimum 6 | Id length for range_id algorithm | ### Using snowflake algorithm to generate unique ID diff --git a/docs/zh/latest/plugins/request-id.md b/docs/zh/latest/plugins/request-id.md index 7ae62a8418fc5..88cbba76b3915 100644 --- a/docs/zh/latest/plugins/request-id.md +++ b/docs/zh/latest/plugins/request-id.md @@ -42,7 +42,9 @@ description: 本文介绍了 Apache APISIX request-id 插件的相关操作, | ------------------- | ------- | -------- | -------------- | ------ | ------------------------------ | | header_name | string | 否 | "X-Request-Id" | | unique ID 的请求头的名称。 | | include_in_response | boolean | 否 | true | | 当设置为 `true` 时,将 unique ID 加入返回头。 | -| algorithm | string | 否 | "uuid" | ["uuid", "snowflake", "nanoid"] | 指定的 unique ID 生成算法。 | +| algorithm | string | 否 | "uuid" | ["uuid", "snowflake", "nanoid", "range_id"] | 指定的 unique ID 生成算法。 | +| range_id.char_set | string | 否 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789| 字符串长度最小为 6 | range_id 算法的字符集 | +| range_id.length | integer | 否 | 16 | 最小值为 6 | range_id 算法的 id 长度 | ### 使用 snowflake 算法生成 unique ID diff --git a/t/plugin/request-id2.t b/t/plugin/request-id2.t new file mode 100644 index 0000000000000..ff8aa1d7295fa --- /dev/null +++ b/t/plugin/request-id2.t @@ -0,0 +1,184 @@ +# +# 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'; + +worker_connections(1024); +repeat_each(1); +no_long_string(); +no_root_location(); + +add_block_preprocessor(sub { + my ($block) = @_; + + if (!$block->request) { + $block->set_value("request", "GET /t"); + } +}); + +run_tests; + +__DATA__ + +=== TEST 1: check config with algorithm range_id +--- 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": { + "request-id": { + "algorithm": "range_id", + "range_id": { + "char_set": "abcdefg", + "length": 20 + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1982": 1 + }, + "type": "roundrobin" + }, + "uri": "/opentracing" + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 2: add plugin with algorithm range_id +--- 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": { + "request-id": { + "algorithm": "range_id" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1982": 1 + }, + "type": "roundrobin" + }, + "uri": "/opentracing" + }]] + ) + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 3: add plugin with algorithm range_id +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local http = require "resty.http" + local v = {} + local ids = {} + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "request-id": { + "algorithm": "range_id", + "range_id": {} + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1982": 1 + }, + "type": "roundrobin" + }, + "uri": "/opentracing" + }]] + ) + if code >= 300 then + ngx.say("algorithm range_id is error") + end + for i = 1, 180 do + local th = assert(ngx.thread.spawn(function() + local httpc = http.new() + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/opentracing" + local res, err = httpc:request_uri(uri, + { + method = "GET", + headers = { + ["Content-Type"] = "application/json", + } + } + ) + if not res then + ngx.log(ngx.ERR, err) + return + end + local id = res.headers["X-Request-Id"] + if not id then + return -- ignore if the data is not synced yet. + end + if #id ~= 16 then + ngx.say(id) + ngx.say("incorrect length for id") + return + end + for j = 180, #id do + local start, en = string.find(id, '[a-zA-Z0-9]', j) + if start ~= j or en ~= j then + ngx.say("incorrect char set for id") + ngx.say(id) + return + end + end + if ids[id] == true then + ngx.say("ids not unique") + return + end + ids[id] = true + end, i)) + table.insert(v, th) + end + for i, th in ipairs(v) do + ngx.thread.wait(th) + end + ngx.say("true") + } + } +--- wait: 5 +--- response_body +true