From d516237aab479767e8b63cfedd2471a1db774a5c Mon Sep 17 00:00:00 2001 From: Yuansheng Date: Mon, 1 Jul 2019 10:46:51 +0800 Subject: [PATCH] optimize: optimized pattern `.*`, which can be used prefix matching. --- README.md | 12 ++++----- include/r3.h | 3 ++- src/node.c | 30 +++++++++++++++------ src/str.c | 3 +++ tests/Makefile.am | 3 +++ tests/check_routes2.c | 62 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 tests/check_routes2.c diff --git a/README.md b/README.md index fa1b6b0..f3a8423 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ R3Node * matched_node = r3_tree_match_entry(n, entry); **Release Memory** -To release the memory, you may call `r3_tree_free(R3Node *tree)` to release the whole tree structure, +To release the memory, you may call `r3_tree_free(R3Node *tree)` to release the whole tree structure, `node*`, `edge*`, `route*` objects that were inserted into the tree will be freed. @@ -189,7 +189,7 @@ translator, which translates simple patterns into small & fast scanners. By using this method, r3 reduces the matching overhead of pcre library. -Optimized patterns are: `[a-z]+`, `[0-9]+`, `\d+`, `\w+`, `[^/]+` or `[^-]+` +Optimized patterns are: `[a-z]+`, `[0-9]+`, `\d+`, `\w+`, `[^/]+`, `[^-]+` or `.*`. Slugs without specified regular expression will be compiled into the `[^/]+` pattern. therefore, it's optimized too. @@ -337,10 +337,10 @@ Use case in PHP ```php // Here is the paths data structure $paths = [ - '/blog/post/{id}' => [ 'controller' => 'PostController' , 'action' => 'item' , 'method' => 'GET' ] , - '/blog/post' => [ 'controller' => 'PostController' , 'action' => 'list' , 'method' => 'GET' ] , - '/blog/post' => [ 'controller' => 'PostController' , 'action' => 'create' , 'method' => 'POST' ] , - '/blog' => [ 'controller' => 'BlogController' , 'action' => 'list' , 'method' => 'GET' ] , + '/blog/post/{id}' => [ 'controller' => 'PostController' , 'action' => 'item' , 'method' => 'GET' ] , + '/blog/post' => [ 'controller' => 'PostController' , 'action' => 'list' , 'method' => 'GET' ] , + '/blog/post' => [ 'controller' => 'PostController' , 'action' => 'create' , 'method' => 'POST' ] , + '/blog' => [ 'controller' => 'BlogController' , 'action' => 'list' , 'method' => 'GET' ] , ]; $rs = r3_compile($paths, 'persisten-table-id'); $ret = r3_dispatch($rs, '/blog/post/3' ); diff --git a/include/r3.h b/include/r3.h index 8580d17..d331c66 100644 --- a/include/r3.h +++ b/include/r3.h @@ -208,7 +208,8 @@ int r3_pattern_to_opcode(const char * pattern, unsigned int len); enum { NODE_COMPARE_STR, NODE_COMPARE_PCRE, NODE_COMPARE_OPCODE }; -enum { OP_EXPECT_MORE_DIGITS = 1, OP_EXPECT_MORE_WORDS, OP_EXPECT_NOSLASH, OP_EXPECT_NODASH, OP_EXPECT_MORE_ALPHA }; +enum { OP_EXPECT_MORE_DIGITS = 1, OP_EXPECT_MORE_WORDS, OP_EXPECT_NOSLASH, + OP_EXPECT_NODASH, OP_EXPECT_MORE_ALPHA, OP_EXPECT_NOLINEBREAKS}; diff --git a/src/node.c b/src/node.c index 22d7eb0..9d4f059 100644 --- a/src/node.c +++ b/src/node.c @@ -223,7 +223,7 @@ int r3_tree_compile_patterns(R3Node * n, char **errstr) { free(n->combined_pattern); n->combined_pattern = cpat; - const char *pcre_error; + const char *pcre_error = NULL; int pcre_erroffset; unsigned int option_bits = 0; @@ -250,7 +250,7 @@ int r3_tree_compile_patterns(R3Node * n, char **errstr) { pcre_free_study(n->pcre_extra); } n->pcre_extra = pcre_study(n->pcre_pattern, 0, &pcre_error); - if (!n->pcre_extra) { + if (!n->pcre_extra && pcre_error) { if (errstr) { int r = asprintf(errstr, "PCRE study failed at offset %s, pattern: %s", pcre_error, n->combined_pattern); if (r) {}; @@ -312,18 +312,32 @@ R3Node * r3_tree_matchl(const R3Node * n, const char * path, unsigned int path_l case OP_EXPECT_NODASH: while (*pp != '-' && pp < pp_end) pp++; break; + case OP_EXPECT_NOLINEBREAKS: + while (*pp != '\n' && pp < pp_end) pp++; + break; } + // check match - if ((pp - path) > 0) { + if (e->opcode != OP_EXPECT_NOLINEBREAKS) { + if ((pp - path) > 0) { + if (entry) { + str_array_append(&entry->vars , path, pp - path); + } + restlen = pp_end - pp; + if (!restlen) { + return e->child && e->child->endpoint ? e->child : NULL; + } + return r3_tree_matchl(e->child, pp, restlen, entry); + } + + } else { if (entry) { str_array_append(&entry->vars , path, pp - path); } - restlen = pp_end - pp; - if (!restlen) { - return e->child && e->child->endpoint ? e->child : NULL; - } - return r3_tree_matchl(e->child, pp, restlen, entry); + + return e->child && e->child->endpoint ? e->child : NULL; } + e++; } } diff --git a/src/str.c b/src/str.c index f187440..af4d18a 100644 --- a/src/str.c +++ b/src/str.c @@ -43,6 +43,9 @@ int r3_pattern_to_opcode(const char * pattern, unsigned int len) { if ( strncmp(pattern, "[^-]+", len) == 0 ) { return OP_EXPECT_NODASH; } + if ( strncmp(pattern, ".*", len) == 0 ) { + return OP_EXPECT_NOLINEBREAKS; + } return 0; } diff --git a/tests/Makefile.am b/tests/Makefile.am index baa4b15..b72b122 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -36,6 +36,9 @@ check_remote_addr_SOURCES = check_remote_addr.c TESTS += check_http_scheme check_http_scheme_SOURCES = check_http_scheme.c +TESTS += check_routes2 +check_routes2_SOURCES = check_routes2.c + if ENABLE_JSON TESTS += check_json diff --git a/tests/check_routes2.c b/tests/check_routes2.c new file mode 100644 index 0000000..20f0b40 --- /dev/null +++ b/tests/check_routes2.c @@ -0,0 +1,62 @@ +#include "config.h" +#include +#include +#include +#include +#include "r3.h" +#include "r3_slug.h" + +START_TEST (test_routes2s) +{ + R3Node * n = r3_tree_create(10); + match_entry * entry; + R3Route *matched_route; + + char * uri0 = "/foo{:.*}"; + r3_tree_insert_routel(n, 0, uri0, strlen(uri0), &uri0); + + char * err = NULL; + r3_tree_compile(n, &err); + ck_assert(err == NULL); + + entry = match_entry_create("/foo/bar"); + matched_route = r3_tree_match_route(n, entry); + ck_assert(matched_route != NULL); + ck_assert(matched_route->data == &uri0); + + entry = match_entry_create("/foo"); + matched_route = r3_tree_match_route(n, entry); + ck_assert(matched_route == NULL); + + entry = match_entry_create("/foo/"); + matched_route = r3_tree_match_route(n, entry); + ck_assert(matched_route != NULL); + ck_assert(matched_route->data == &uri0); + + entry = match_entry_create("/foo/bar/foo/mmasdfasdfasd/f/asdf/as/df"); + matched_route = r3_tree_match_route(n, entry); + ck_assert(matched_route != NULL); + ck_assert(matched_route->data == &uri0); + + r3_tree_free(n); +} +END_TEST + +Suite* r3_suite (void) { + Suite *suite = suite_create("r3 routes2 tests"); + TCase *tcase = tcase_create("testcase"); + tcase_add_test(tcase, test_routes2s); + suite_add_tcase(suite, tcase); + return suite; +} + +int main (int argc, char *argv[]) { + int number_failed; + Suite *suite = r3_suite(); + SRunner *runner = srunner_create(suite); + srunner_run_all(runner, CK_NORMAL); + number_failed = srunner_ntests_failed(runner); + srunner_free(runner); + return number_failed; +} +