Skip to content

Commit 18884bc

Browse files
authored
feat: add ksuid algorithm on request-id plugin (#12573)
1 parent 9e34661 commit 18884bc

File tree

5 files changed

+269
-3
lines changed

5 files changed

+269
-3
lines changed

apisix-master-0.rockspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ dependencies = {
4040
"lua-resty-balancer = 0.04",
4141
"lua-resty-ngxvar = 0.5.2",
4242
"lua-resty-jit-uuid = 0.0.7",
43+
"lua-resty-ksuid = 1.0.1",
4344
"lua-resty-worker-events = 1.0.0",
4445
"lua-resty-healthcheck-api7 = 3.2.0",
4546
"api7-lua-resty-jwt = 0.2.5",

apisix/plugins/request-id.lua

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ local ngx = ngx
1919
local core = require("apisix.core")
2020
local uuid = require("resty.jit-uuid")
2121
local nanoid = require("nanoid")
22+
local ksuid = require("resty.ksuid")
2223
local math_random = math.random
2324
local str_byte = string.byte
2425
local ffi = require "ffi"
@@ -32,7 +33,7 @@ local schema = {
3233
include_in_response = {type = "boolean", default = true},
3334
algorithm = {
3435
type = "string",
35-
enum = {"uuid", "nanoid", "range_id"},
36+
enum = {"uuid", "nanoid", "range_id", "ksuid"},
3637
default = "uuid"
3738
},
3839
range_id = {
@@ -87,6 +88,10 @@ local function get_request_id(conf)
8788
return get_range_id(conf.range_id)
8889
end
8990

91+
if conf.algorithm == "ksuid" then
92+
return ksuid.generate()
93+
end
94+
9095
return uuid()
9196
end
9297

docs/en/latest/plugins/request-id.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ The `request-id` Plugin adds a unique ID to each request proxied through APISIX,
4040
| ------------------- | ------- | -------- | -------------- | ------------------------------- | ---------------------------------------------------------------------- |
4141
| header_name | string | False | "X-Request-Id" | | Name of the header that carries the request unique ID. Note that if a request carries an ID in the `header_name` header, the Plugin will use the header value as the unique ID and will not overwrite it with the generated ID. |
4242
| include_in_response | boolean | False | true | | If true, include the generated request ID in the response header, where the name of the header is the `header_name` value. |
43-
| algorithm | string | False | "uuid" | ["uuid","nanoid","range_id"] | Algorithm used for generating the unique ID. When set to `uuid` , the Plugin generates a universally unique identifier. When set to `nanoid`, the Plugin generates a compact, URL-safe ID. When set to `range_id`, the Plugin generates a sequential ID with specific parameters. |
43+
| algorithm | string | False | "uuid" | ["uuid","nanoid","range_id","ksuid"] | Algorithm used for generating the unique ID. When set to `uuid` , the Plugin generates a universally unique identifier. When set to `nanoid`, the Plugin generates a compact, URL-safe ID. When set to `range_id`, the Plugin generates a sequential ID with specific parameters. When set to `ksuid`, the Plugin generates a sequential ID with timestamp and random number. |
4444
| range_id | object | False | | | Configuration for generating a request ID using the `range_id` algorithm. |
4545
| range_id.char_set | string | False | "abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789" | minimum length 6 | Character set used for the `range_id` algorithm. |
4646
| range_id.length | integer | False | 16 | >=6 | Length of the generated ID for the `range_id` algorithm. |
@@ -243,6 +243,59 @@ You should receive an `HTTP/1.1 200 OK` response and see the response includes t
243243
X-Request-Id: kepgHWCH2ycQ6JknQKrX2
244244
```
245245

246+
### Use `ksuid` Algorithm
247+
248+
The following example demonstrates how to configure `request-id` on a Route and use the `ksuid` algorithm to generate the request ID.
249+
250+
Create a Route with the `request-id` Plugin as such:
251+
252+
```shell
253+
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
254+
-H "X-API-KEY: ${ADMIN_API_KEY}" \
255+
-d '{
256+
"id": "request-id-route",
257+
"uri": "/anything",
258+
"plugins": {
259+
"request-id": {
260+
"algorithm": "ksuid"
261+
}
262+
},
263+
"upstream": {
264+
"type": "roundrobin",
265+
"nodes": {
266+
"httpbin.org:80": 1
267+
}
268+
}
269+
}'
270+
```
271+
272+
Send a request to the Route:
273+
274+
```shell
275+
curl -i "http://127.0.0.1:9080/anything"
276+
```
277+
278+
You should receive an `HTTP/1.1 200 OK` response and see the response includes the `X-Request-Id` header with an ID generated using the `ksuid` algorithm:
279+
280+
```text
281+
X-Request-Id: 325ghCANEKjw6Jsfejg5p6QrLYB
282+
```
283+
284+
If the [ksuid](https://github.com/segmentio/ksuid?tab=readme-ov-file#command-line-tool) is installed, this ID can be viewed through `ksuid -f inspect 325ghCANEKjw6Jsfejg5p6QrLYB`:
285+
286+
``` text
287+
REPRESENTATION:
288+
289+
String: 325ghCANEKjw6Jsfejg5p6QrLYB
290+
Raw: 15430DBBD7F68AD7CA0AE277772AB36DDB1A3C13
291+
292+
COMPONENTS:
293+
294+
Time: 2025-09-01 16:39:23 +0800 CST
295+
Timestamp: 356715963
296+
Payload: D7F68AD7CA0AE277772AB36DDB1A3C13
297+
```
298+
246299
### Attach Request ID Globally and on a Route
247300

248301
The following example demonstrates how to configure `request-id` as a global Plugin and on a Route to attach two IDs.

docs/zh/latest/plugins/request-id.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ description: request-id 插件为通过 APISIX 代理的每个请求添加一个
3636
| ------------------- | ------- | -------- | -------------- | ------ | ------------------------------ |
3737
| header_name | string || "X-Request-Id" | | 携带请求唯一 ID 的标头的名称。请注意,如果请求在 `header_name` 标头中携带 ID,则插件将使用标头值作为唯一 ID,并且不会用生成的 ID 覆盖它。|
3838
| include_in_response | 布尔值 || true | | 如果为 true,则将生成的请求 ID 包含在响应标头中,其中标头的名称是 `header_name` 值。|
39-
| algorithm | string || "uuid" | ["uuid","nanoid","range_id"] | 用于生成唯一 ID 的算法。设置为 `uuid` 时,插件会生成一个通用唯一标识符。设置为 `nanoid` 时,插件会生成一个紧凑的、URL 安全的 ID。设置为 `range_id` 时,插件会生成具有特定参数的连续 ID。|
39+
| algorithm | string || "uuid" | ["uuid","nanoid","range_id","ksuid"] | 用于生成唯一 ID 的算法。设置为 `uuid` 时,插件会生成一个通用唯一标识符。设置为 `nanoid` 时,插件会生成一个紧凑的、URL 安全的 ID。设置为 `range_id` 时,插件会生成具有特定参数的连续 ID。设置为 `ksuid` 时,插件会生成具有时间戳和随机值的连续 ID。|
4040
| range_id | object || | |使用 `range_id` 算法生成请求 ID 的配置。|
4141
| range_id.char_set | string || "abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789" | 最小长度 6 | 用于 `range_id` 算法的字符集。|
4242
| range_id.length | integer || 16 | >=6 | 用于 `range_id` 算法的生成的 ID 的长度。|
@@ -239,6 +239,59 @@ curl -i "http://127.0.0.1:9080/anything"
239239
X-Request-Id: kepgHWCH2ycQ6JknQKrX2
240240
```
241241

242+
### 使用 `ksuid` 算法
243+
244+
以下示例演示如何在路由上配置 `request-id` 并使用 `ksuid` 算法生成请求 ID。
245+
246+
使用 `request-id` 插件创建路由,如下所示:
247+
248+
```shell
249+
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
250+
-H "X-API-KEY: ${ADMIN_API_KEY}" \
251+
-d '{
252+
"id": "request-id-route",
253+
"uri": "/anything",
254+
"plugins": {
255+
"request-id": {
256+
"algorithm": "ksuid"
257+
}
258+
},
259+
"upstream": {
260+
"type": "roundrobin",
261+
"nodes": {
262+
"httpbin.org:80": 1
263+
}
264+
}
265+
}'
266+
```
267+
268+
向路由发送请求:
269+
270+
```shell
271+
curl -i "http://127.0.0.1:9080/anything"
272+
```
273+
274+
您应该收到一个 `HTTP/1.1 200 OK` 响应,并看到响应包含 `X-Request-Id` 标头,其中的 ID 使用 `ksuid` 算法生成:
275+
276+
```text
277+
X-Request-Id: 325ghCANEKjw6Jsfejg5p6QrLYB
278+
```
279+
280+
如果装有[ksuid](https://github.com/segmentio/ksuid?tab=readme-ov-file#command-line-tool)命令工具,此 ID 可以通过`ksuid -f inspect 325ghCANEKjw6Jsfejg5p6QrLYB`查看:
281+
282+
``` text
283+
REPRESENTATION:
284+
285+
String: 325ghCANEKjw6Jsfejg5p6QrLYB
286+
Raw: 15430DBBD7F68AD7CA0AE277772AB36DDB1A3C13
287+
288+
COMPONENTS:
289+
290+
Time: 2025-09-01 16:39:23 +0800 CST
291+
Timestamp: 356715963
292+
Payload: D7F68AD7CA0AE277772AB36DDB1A3C13
293+
```
294+
242295
### 全局和在路由上附加请求 ID
243296

244297
以下示例演示如何将 `request-id` 配置为全局插件并在路由上附加两个 ID。

t/plugin/request-id3.t

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
use t::APISIX 'no_plan';
18+
19+
worker_connections(1024);
20+
repeat_each(1);
21+
no_long_string();
22+
no_root_location();
23+
24+
add_block_preprocessor(sub {
25+
my ($block) = @_;
26+
27+
if (!$block->request) {
28+
$block->set_value("request", "GET /t");
29+
}
30+
});
31+
32+
run_tests;
33+
34+
__DATA__
35+
36+
=== TEST 1: check config with algorithm ksuid
37+
--- config
38+
location /t {
39+
content_by_lua_block {
40+
local t = require("lib.test_admin").test
41+
local code, body = t('/apisix/admin/routes/1',
42+
ngx.HTTP_PUT,
43+
[[{
44+
"plugins": {
45+
"request-id": {
46+
"algorithm": "ksuid"
47+
}
48+
},
49+
"upstream": {
50+
"nodes": {
51+
"127.0.0.1:1982": 1
52+
},
53+
"type": "roundrobin"
54+
},
55+
"uri": "/opentracing"
56+
}]]
57+
)
58+
if code >= 300 then
59+
ngx.status = code
60+
end
61+
ngx.say(body)
62+
}
63+
}
64+
--- response_body
65+
passed
66+
67+
68+
69+
=== TEST 2: hit
70+
--- request
71+
GET /opentracing
72+
--- error_log
73+
X-Request-Id
74+
--- no_error_log
75+
[error]
76+
77+
78+
79+
=== TEST 3: add plugin with algorithm ksuid
80+
--- config
81+
location /t {
82+
content_by_lua_block {
83+
local t = require("lib.test_admin").test
84+
local http = require "resty.http"
85+
local v = {}
86+
local ids = {}
87+
local code, body = t('/apisix/admin/routes/1',
88+
ngx.HTTP_PUT,
89+
[[{
90+
"plugins": {
91+
"request-id": {
92+
"algorithm": "ksuid"
93+
}
94+
},
95+
"upstream": {
96+
"nodes": {
97+
"127.0.0.1:1982": 1
98+
},
99+
"type": "roundrobin"
100+
},
101+
"uri": "/opentracing"
102+
}]]
103+
)
104+
if code >= 300 then
105+
ngx.say("algorithm ksuid is error")
106+
end
107+
for i = 1, 180 do
108+
local th = assert(ngx.thread.spawn(function()
109+
local httpc = http.new()
110+
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/opentracing"
111+
local res, err = httpc:request_uri(uri,
112+
{
113+
method = "GET",
114+
headers = {
115+
["Content-Type"] = "application/json",
116+
}
117+
}
118+
)
119+
if not res then
120+
ngx.log(ngx.ERR, err)
121+
return
122+
end
123+
local id = res.headers["X-Request-Id"]
124+
if not id then
125+
return -- ignore if the data is not synced yet.
126+
end
127+
if #id ~= 27 then
128+
ngx.say(id)
129+
ngx.say("incorrect length for id")
130+
return
131+
end
132+
local start, en = string.find(id, '[a-zA-Z0-9]*')
133+
if start ~= 1 or en ~= 27 then
134+
ngx.say("incorrect char set for id")
135+
ngx.say(id)
136+
return
137+
end
138+
if ids[id] == true then
139+
ngx.say("ids not unique")
140+
return
141+
end
142+
ids[id] = true
143+
end, i))
144+
table.insert(v, th)
145+
end
146+
for i, th in ipairs(v) do
147+
ngx.thread.wait(th)
148+
end
149+
ngx.say("true")
150+
}
151+
}
152+
--- wait: 5
153+
--- response_body
154+
true

0 commit comments

Comments
 (0)