Skip to content

Commit 7c9f86b

Browse files
committed
perf(router): adjust the sequence of fields generated inside a traditional compatible route expression
1 parent 1228042 commit 7c9f86b

File tree

3 files changed

+50
-38
lines changed

3 files changed

+50
-38
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
message: "Improved performance of the traditional compatible router by optimizing the expression generation."
2+
type: performance
3+
scope: Core

kong/router/transform.lua

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,15 @@ local function path_val_transform(op, p)
331331
end
332332

333333

334+
-- Generates an ATC router expression for traditional compat router flavor
335+
-- match priority for http is as follows:
336+
-- (sni if net.protocol == https) -> net.src.ip -> net.src.port
337+
-- -> net.dst.ip -> net.dst.port -> http.path -> http.headers
338+
-- -> http.host -> http.method
339+
--
340+
-- match priority for stream is as follows:
341+
-- (sni if net.protocol == tls) -> net.src.ip -> net.src.port
342+
-- -> net.dst.ip -> net.dst.port
334343
local function get_expression(route)
335344
-- we prefer the field 'expression', reject others
336345
if not is_null(route.expression) then
@@ -377,11 +386,39 @@ local function get_expression(route)
377386

378387
-- http expression, protocol = http/https/grpc/grpcs
379388

380-
gen = gen_for_field("http.method", OP_EQUAL, route.methods)
389+
gen = gen_for_field("http.path", path_op_transform, route.paths, path_val_transform)
381390
if gen then
382391
expression_append(expr_buf, LOGICAL_AND, gen)
383392
end
384393

394+
local headers = route.headers
395+
if not is_empty_field(headers) then
396+
headers_buf:reset()
397+
398+
for h, v in pairs(headers) do
399+
single_header_buf:reset():put("(")
400+
401+
for i, value in ipairs(v) do
402+
local name = "any(lower(http.headers." .. replace_dashes_lower(h) .. "))"
403+
local op = OP_EQUAL
404+
405+
-- value starts with "~*"
406+
if byte(value, 1) == TILDE and byte(value, 2) == ASTERISK then
407+
value = value:sub(3)
408+
op = OP_REGEX
409+
end
410+
411+
expression_append(single_header_buf, LOGICAL_OR,
412+
name .. " " .. op .. " " .. escape_str(value:lower()), i)
413+
end
414+
415+
expression_append(headers_buf, LOGICAL_AND,
416+
single_header_buf:put(")"):get())
417+
end
418+
419+
expression_append(expr_buf, LOGICAL_AND, headers_buf:get())
420+
end
421+
385422
local hosts = route.hosts
386423
if not is_empty_field(hosts) then
387424
hosts_buf:reset():put("(")
@@ -412,39 +449,11 @@ local function get_expression(route)
412449
expression_append(expr_buf, LOGICAL_AND, hosts_buf:put(")"):get())
413450
end
414451

415-
gen = gen_for_field("http.path", path_op_transform, route.paths, path_val_transform)
452+
gen = gen_for_field("http.method", OP_EQUAL, route.methods)
416453
if gen then
417454
expression_append(expr_buf, LOGICAL_AND, gen)
418455
end
419456

420-
local headers = route.headers
421-
if not is_empty_field(headers) then
422-
headers_buf:reset()
423-
424-
for h, v in pairs(headers) do
425-
single_header_buf:reset():put("(")
426-
427-
for i, value in ipairs(v) do
428-
local name = "any(lower(http.headers." .. replace_dashes_lower(h) .. "))"
429-
local op = OP_EQUAL
430-
431-
-- value starts with "~*"
432-
if byte(value, 1) == TILDE and byte(value, 2) == ASTERISK then
433-
value = value:sub(3)
434-
op = OP_REGEX
435-
end
436-
437-
expression_append(single_header_buf, LOGICAL_OR,
438-
name .. " " .. op .. " " .. escape_str(value:lower()), i)
439-
end
440-
441-
expression_append(headers_buf, LOGICAL_AND,
442-
single_header_buf:put(")"):get())
443-
end
444-
445-
expression_append(expr_buf, LOGICAL_AND, headers_buf:get())
446-
end
447-
448457
local str = expr_buf:get()
449458

450459
-- returns a local variable instead of using a tail call

spec/01-unit/08-router_spec.lua

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2257,14 +2257,14 @@ for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions"
22572257
it("empty hosts", function()
22582258
use_case[1].route.hosts = v
22592259

2260-
assert.equal(get_expression(use_case[1].route), [[(http.method == r#"GET"#) && (http.path ^= r#"/foo"#)]])
2260+
assert.equal(get_expression(use_case[1].route), [[(http.path ^= r#"/foo"#) && (http.method == r#"GET"#)]])
22612261
assert(new_router(use_case))
22622262
end)
22632263

22642264
it("empty headers", function()
22652265
use_case[1].route.headers = v
22662266

2267-
assert.equal(get_expression(use_case[1].route), [[(http.method == r#"GET"#) && (http.path ^= r#"/foo"#)]])
2267+
assert.equal(get_expression(use_case[1].route), [[(http.path ^= r#"/foo"#) && (http.method == r#"GET"#)]])
22682268
assert(new_router(use_case))
22692269
end)
22702270

@@ -2278,7 +2278,7 @@ for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions"
22782278
it("empty snis", function()
22792279
use_case[1].route.snis = v
22802280

2281-
assert.equal(get_expression(use_case[1].route), [[(http.method == r#"GET"#) && (http.path ^= r#"/foo"#)]])
2281+
assert.equal(get_expression(use_case[1].route), [[(http.path ^= r#"/foo"#) && (http.method == r#"GET"#)]])
22822282
assert(new_router(use_case))
22832283
end)
22842284
end
@@ -2302,15 +2302,15 @@ for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions"
23022302
it("path has '\"'", function()
23032303
use_case[1].route.paths = { [[~/\"/*$]], }
23042304

2305-
assert.equal([[(http.method == r#"GET"#) && (http.path ~ r#"^/\"/*$"#)]],
2305+
assert.equal([[(http.path ~ r#"^/\"/*$"#) && (http.method == r#"GET"#)]],
23062306
get_expression(use_case[1].route))
23072307
assert(new_router(use_case))
23082308
end)
23092309

23102310
it("path has '\"#'", function()
23112311
use_case[1].route.paths = { [[~/\"#/*$]], }
23122312

2313-
assert.equal([[(http.method == r#"GET"#) && (http.path ~ "^/\\\"#/*$")]],
2313+
assert.equal([[(http.path ~ "^/\\\"#/*$") && (http.method == r#"GET"#)]],
23142314
get_expression(use_case[1].route))
23152315
assert(new_router(use_case))
23162316
end)
@@ -2334,15 +2334,15 @@ for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions"
23342334
it("regex path has double '\\'", function()
23352335
use_case[1].route.paths = { [[~/\\/*$]], }
23362336

2337-
assert.equal([[(http.method == r#"GET"#) && (http.path ~ r#"^/\\/*$"#)]],
2337+
assert.equal([[(http.path ~ r#"^/\\/*$"#) && (http.method == r#"GET"#)]],
23382338
get_expression(use_case[1].route))
23392339
assert(new_router(use_case))
23402340
end)
23412341

23422342
it("regex path has '\\d'", function()
23432343
use_case[1].route.paths = { [[~/\d+]], }
23442344

2345-
assert.equal([[(http.method == r#"GET"#) && (http.path ~ r#"^/\d+"#)]],
2345+
assert.equal([[(http.path ~ r#"^/\d+"#) && (http.method == r#"GET"#)]],
23462346
get_expression(use_case[1].route))
23472347
assert(new_router(use_case))
23482348
end)
@@ -2366,7 +2366,7 @@ for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions"
23662366
use_case[1].route.methods = { "GET" }
23672367
use_case[1].route.headers = { test = { "~*Quote" }, }
23682368

2369-
assert.equal([[(http.method == r#"GET"#) && (any(lower(http.headers.test)) ~ r#"quote"#)]],
2369+
assert.equal([[(any(lower(http.headers.test)) ~ r#"quote"#) && (http.method == r#"GET"#)]],
23702370
get_expression(use_case[1].route))
23712371
assert(new_router(use_case))
23722372
end)

0 commit comments

Comments
 (0)