Router: matching query string support.

The "query" option matches decoded arguments, including plus ('+') to
space (' ').  Like "uri", it can be a string or an array of strings.
This commit is contained in:
Zhidao HONG
2021-11-05 22:56:34 +08:00
parent 1260add0f5
commit aee908bcbd
5 changed files with 101 additions and 0 deletions

View File

@@ -96,6 +96,12 @@ when updating from previous versions.
</para> </para>
</change> </change>
<change type="feature">
<para>
request routing by the query string.
</para>
</change>
<change type="bugfix"> <change type="bugfix">
<para> <para>
fixed building with glibc 2.34, notably Fedora 35. fixed building with glibc 2.34, notably Fedora 35.

View File

@@ -589,6 +589,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_match_members[] = {
.type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, .type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY,
.validator = nxt_conf_vldt_match_encoded_patterns, .validator = nxt_conf_vldt_match_encoded_patterns,
.u.string = "uri" .u.string = "uri"
}, {
.name = nxt_string("query"),
.type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY,
.validator = nxt_conf_vldt_match_encoded_patterns,
.u.string = "query"
}, { }, {
.name = nxt_string("arguments"), .name = nxt_string("arguments"),
.type = NXT_CONF_VLDT_OBJECT | NXT_CONF_VLDT_ARRAY, .type = NXT_CONF_VLDT_OBJECT | NXT_CONF_VLDT_ARRAY,

View File

@@ -148,6 +148,7 @@ struct nxt_http_request_s {
nxt_str_t *path; nxt_str_t *path;
nxt_str_t *args; nxt_str_t *args;
nxt_str_t args_decoded;
nxt_array_t *arguments; /* of nxt_http_name_value_t */ nxt_array_t *arguments; /* of nxt_http_name_value_t */
nxt_array_t *cookies; /* of nxt_http_name_value_t */ nxt_array_t *cookies; /* of nxt_http_name_value_t */
nxt_list_t *fields; nxt_list_t *fields;

View File

@@ -19,6 +19,7 @@ typedef enum {
NXT_HTTP_ROUTE_ARGUMENT, NXT_HTTP_ROUTE_ARGUMENT,
NXT_HTTP_ROUTE_COOKIE, NXT_HTTP_ROUTE_COOKIE,
NXT_HTTP_ROUTE_SCHEME, NXT_HTTP_ROUTE_SCHEME,
NXT_HTTP_ROUTE_QUERY,
NXT_HTTP_ROUTE_SOURCE, NXT_HTTP_ROUTE_SOURCE,
NXT_HTTP_ROUTE_DESTINATION, NXT_HTTP_ROUTE_DESTINATION,
} nxt_http_route_object_t; } nxt_http_route_object_t;
@@ -54,6 +55,7 @@ typedef struct {
nxt_conf_value_t *arguments; nxt_conf_value_t *arguments;
nxt_conf_value_t *cookies; nxt_conf_value_t *cookies;
nxt_conf_value_t *scheme; nxt_conf_value_t *scheme;
nxt_conf_value_t *query;
nxt_conf_value_t *source; nxt_conf_value_t *source;
nxt_conf_value_t *destination; nxt_conf_value_t *destination;
} nxt_http_route_match_conf_t; } nxt_http_route_match_conf_t;
@@ -247,6 +249,8 @@ static nxt_int_t nxt_http_route_test_argument(nxt_http_request_t *r,
nxt_http_route_rule_t *rule, nxt_array_t *array); nxt_http_route_rule_t *rule, nxt_array_t *array);
static nxt_int_t nxt_http_route_scheme(nxt_http_request_t *r, static nxt_int_t nxt_http_route_scheme(nxt_http_request_t *r,
nxt_http_route_rule_t *rule); nxt_http_route_rule_t *rule);
static nxt_int_t nxt_http_route_query(nxt_http_request_t *r,
nxt_http_route_rule_t *rule);
static nxt_int_t nxt_http_route_cookies(nxt_http_request_t *r, static nxt_int_t nxt_http_route_cookies(nxt_http_request_t *r,
nxt_http_route_rule_t *rule); nxt_http_route_rule_t *rule);
static nxt_array_t *nxt_http_route_cookies_parse(nxt_http_request_t *r); static nxt_array_t *nxt_http_route_cookies_parse(nxt_http_request_t *r);
@@ -365,6 +369,12 @@ static nxt_conf_map_t nxt_http_route_match_conf[] = {
offsetof(nxt_http_route_match_conf_t, cookies), offsetof(nxt_http_route_match_conf_t, cookies),
}, },
{
nxt_string("query"),
NXT_CONF_MAP_PTR,
offsetof(nxt_http_route_match_conf_t, query),
},
{ {
nxt_string("source"), nxt_string("source"),
NXT_CONF_MAP_PTR, NXT_CONF_MAP_PTR,
@@ -564,6 +574,19 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
test++; test++;
} }
if (mtcf.query != NULL) {
rule = nxt_http_route_rule_create(task, mp, mtcf.query, 1,
NXT_HTTP_ROUTE_PATTERN_NOCASE,
NXT_HTTP_ROUTE_ENCODING_URI_PLUS);
if (rule == NULL) {
return NULL;
}
rule->object = NXT_HTTP_ROUTE_QUERY;
test->rule = rule;
test++;
}
if (mtcf.source != NULL) { if (mtcf.source != NULL) {
addr_rule = nxt_http_route_addr_rule_create(task, mp, mtcf.source); addr_rule = nxt_http_route_addr_rule_create(task, mp, mtcf.source);
if (addr_rule == NULL) { if (addr_rule == NULL) {
@@ -1776,6 +1799,9 @@ nxt_http_route_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule)
case NXT_HTTP_ROUTE_SCHEME: case NXT_HTTP_ROUTE_SCHEME:
return nxt_http_route_scheme(r, rule); return nxt_http_route_scheme(r, rule);
case NXT_HTTP_ROUTE_QUERY:
return nxt_http_route_query(r, rule);
default: default:
break; break;
} }
@@ -2045,6 +2071,8 @@ nxt_http_route_arguments_parse(nxt_http_request_t *r)
return NULL; return NULL;
} }
r->args_decoded.start = dst_start;
start = r->args->start; start = r->args->start;
end = start + r->args->length; end = start + r->args->length;
@@ -2105,6 +2133,8 @@ nxt_http_route_arguments_parse(nxt_http_request_t *r)
} }
} }
r->args_decoded.length = dst - r->args_decoded.start;
if (name_length != 0 || dst != dst_start) { if (name_length != 0 || dst != dst_start) {
nv = nxt_http_route_argument(args, name, name_length, hash, dst_start, nv = nxt_http_route_argument(args, name, name_length, hash, dst_start,
dst); dst);
@@ -2200,6 +2230,21 @@ nxt_http_route_scheme(nxt_http_request_t *r, nxt_http_route_rule_t *rule)
} }
static nxt_int_t
nxt_http_route_query(nxt_http_request_t *r, nxt_http_route_rule_t *rule)
{
nxt_array_t *arguments;
arguments = nxt_http_route_arguments_parse(r);
if (nxt_slow_path(arguments == NULL)) {
return -1;
}
return nxt_http_route_test_rule(r, rule, r->args_decoded.start,
r->args_decoded.length);
}
static nxt_int_t static nxt_int_t
nxt_http_route_cookies(nxt_http_request_t *r, nxt_http_route_rule_t *rule) nxt_http_route_cookies(nxt_http_request_t *r, nxt_http_route_rule_t *rule)
{ {

View File

@@ -1320,6 +1320,50 @@ class TestRouting(TestApplicationProto):
self.route_match_invalid({"arguments": {"%%1F": ""}}) self.route_match_invalid({"arguments": {"%%1F": ""}})
self.route_match_invalid({"arguments": {"%7%F": ""}}) self.route_match_invalid({"arguments": {"%7%F": ""}})
def test_routes_match_query(self):
self.route_match({"query": "!"})
assert self.get(url='/')['status'] == 404
assert self.get(url='/?')['status'] == 404
assert self.get(url='/?foo')['status'] == 200
assert self.get(url='/?foo=')['status'] == 200
assert self.get(url='/?foo=baz')['status'] == 200
self.route_match({"query": "foo=%26"})
assert self.get(url='/?foo=&')['status'] == 200
self.route_match({"query": "a=b&c=d"})
assert self.get(url='/?a=b&c=d')['status'] == 200
self.route_match({"query": "a=b%26c%3Dd"})
assert self.get(url='/?a=b%26c%3Dd')['status'] == 200
assert self.get(url='/?a=b&c=d')['status'] == 200
self.route_match({"query": "a=b%26c%3Dd+e"})
assert self.get(url='/?a=b&c=d e')['status'] == 200
def test_routes_match_query_array(self):
self.route_match({
"query": ["foo", "bar"]
})
assert self.get()['status'] == 404, 'arr'
assert self.get(url='/?foo')['status'] == 200, 'arr 1'
assert self.get(url='/?bar')['status'] == 200, 'arr 2'
assert 'success' in self.conf_delete(
'routes/0/match/query/1'
), 'match query array configure 2'
assert self.get(url='/?bar')['status'] == 404, 'arr 2'
def test_routes_match_query_invalid(self):
self.route_match_invalid({"query": [1]})
self.route_match_invalid({"query": "%"})
self.route_match_invalid({"query": "%1G"})
self.route_match_invalid({"query": "%0"})
self.route_match_invalid({"query": "%%1F"})
self.route_match_invalid({"query": ["foo", "%3D", "%%1F"]})
def test_routes_match_cookies(self): def test_routes_match_cookies(self):
self.route_match({"cookies": {"foO": "bar"}}) self.route_match({"cookies": {"foO": "bar"}})