├── .gitignore ├── lua-resty-r3-dev-1.rockspec ├── benchmark ├── nginx │ ├── nginx.conf │ ├── nginx_apitools_router.conf │ └── nginx_lua_resty_r3.conf └── resty-cli │ ├── test_apitools_router.lua │ └── test_lua_resty_r3.lua ├── LICENSE ├── nginx.conf ├── Dockerfile ├── README.md └── lib └── resty └── r3.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *-sv 3 | tmp 4 | r3 5 | -------------------------------------------------------------------------------- /lua-resty-r3-dev-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-r3" 2 | version = "dev-1" 3 | source = { 4 | url = "https://github.com/toritori0318/lua-resty-r3/archive/master.tar.gz", 5 | dir = "lua-resty-r3-master" 6 | } 7 | description = { 8 | summary = "libr3 Lua-Openresty implementation", 9 | detailed = [[libr3 Lua-Openresty implementation.]], 10 | homepage = "https://github.com/toritori0318/lua-resty-r3", 11 | license = "MIT", 12 | maintainer = "toritori0318" 13 | } 14 | dependencies = { 15 | "lua >= 5.1" 16 | } 17 | build = { 18 | type = "builtin", 19 | modules = { 20 | ["resty.r3"] = "lib/resty/r3.lua", 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /benchmark/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | #user nobody; 2 | worker_processes auto; 3 | 4 | events { 5 | worker_connections 10240; 6 | } 7 | 8 | http { 9 | include mime.types; 10 | default_type application/octet-stream; 11 | 12 | sendfile on; 13 | tcp_nopush on; 14 | 15 | keepalive_timeout 65; 16 | 17 | server { 18 | listen 80; 19 | server_name localhost; 20 | access_log off; 21 | 22 | location = /foo/bar/baz/hoge/fuga/piyo/ { 23 | #echo hello!; 24 | index index.html index.htm; 25 | } 26 | 27 | location ~ ^/foo/(\w+)/(\w+) { 28 | index index.html index.htm; 29 | } 30 | 31 | location / { 32 | index index.html index.htm; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /benchmark/resty-cli/test_apitools_router.lua: -------------------------------------------------------------------------------- 1 | local router = require "resty.r3_apitools"; 2 | 3 | -- display time 4 | function time(title, block) 5 | local st = os.clock() 6 | block() 7 | local ed = os.clock() 8 | 9 | ngx.say(title .. ": " .. ed-st.. " sec") 10 | end 11 | 12 | -- handler 13 | function foo(params) 14 | --ngx.say("fooooooooooooooooooooooooooo") 15 | --ngx.say(tokens) 16 | end 17 | 18 | -- router 19 | local r = router.new() 20 | r:match("GET", "/", foo) 21 | r:match("GET", "/foo/bar/baz/hoge/fuga/piyo", foo) 22 | r:match("GET", "/foo/:id/:name", foo) 23 | 24 | 25 | ---------------------------------------------------------------------- 26 | -- bench 1 27 | time("get /", function() 28 | for i=0, 10000000 do 29 | r:execute("GET", "/") 30 | end 31 | end) 32 | 33 | -- bench 2 34 | time("get /foo/bar/baz/hoge/fuga/piyo", function() 35 | for i=0, 10000000 do 36 | r:execute("GET", "/foo/bar/baz/hoge/fuga/piyo") 37 | end 38 | end) 39 | 40 | -- bench 3 41 | time("get /foo/{id}/{name}", function() 42 | for i=0, 10000000 do 43 | r:execute("GET", "/foo/123/999") 44 | end 45 | end) 46 | 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 TSUYOSHI TORII 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /benchmark/resty-cli/test_lua_resty_r3.lua: -------------------------------------------------------------------------------- 1 | local r3router = require "resty.r3"; 2 | 3 | -- display time 4 | function time(title, block) 5 | local st = os.clock() 6 | block() 7 | local ed = os.clock() 8 | 9 | ngx.say(title .. ": " .. ed-st.. " sec") 10 | end 11 | 12 | -- handler 13 | function foo(tokens, params) 14 | --ngx.say("fooooooooooooooooooooooooooo") 15 | --ngx.say(tokens) 16 | end 17 | 18 | -- router 19 | local r = r3router.new({ 20 | {"GET", "/", foo}, 21 | {"GET", "/foo/bar/baz/hoge/fuga/piyo", foo}, 22 | {{"GET","POST"}, "/foo/{id}/{name}", foo} 23 | }) 24 | 25 | ---------------------------------------------------------------------- 26 | -- bench 1 27 | time("get /", function() 28 | for i=0, 10000000 do 29 | r:dispatch("GET", "/") 30 | end 31 | end) 32 | 33 | -- bench 2 34 | time("get /foo/bar/baz/hoge/fuga/piyo", function() 35 | for i=0, 10000000 do 36 | r:dispatch("GET", "/foo/bar/baz/hoge/fuga/piyo") 37 | end 38 | end) 39 | 40 | -- bench 3 41 | time("get /foo/{id}/{name}", function() 42 | for i=0, 10000000 do 43 | r:dispatch("GET", "/foo/123/999") 44 | end 45 | end) 46 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | daemon off; 2 | worker_processes 1; 3 | error_log logs/error.log; 4 | events { 5 | worker_connections 1024; 6 | } 7 | 8 | http { 9 | lua_code_cache off; 10 | lua_package_path "/code/lib/?.lua;;"; 11 | 12 | server { 13 | listen 80; 14 | 15 | location / { 16 | default_type text/html; 17 | content_by_lua ' 18 | local r3router = require "resty.r3"; 19 | 20 | -- foo handler 21 | function foo(tokens, params) 22 | ngx.say("fooooooooooooooooooooooo") 23 | ngx.say("tokens:" .. table.concat(tokens, ",")) 24 | for key, value in pairs(params) do 25 | ngx.say("param:" .. key .. "=" .. value) 26 | end 27 | end 28 | 29 | -- r3router 30 | local r = r3router.new({ 31 | {"GET", "/", function(tokens, params) ngx.say("hello r3!") end }, 32 | {"GET", "/foo", foo}, 33 | {{"GET","POST"}, "/foo/{id}/{name}", foo}, 34 | }) 35 | 36 | -- dispatcher 37 | local ok = r:dispatch_ngx() 38 | if ok then 39 | ngx.status = 200 40 | else 41 | ngx.status = 404 42 | ngx.print("Not found") 43 | end 44 | '; 45 | } 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:latest 2 | 3 | # all the apt-gets in one command & delete the cache after installing 4 | RUN yum install -y openssl-devel make gcc++ gcc-c++ wget tar zip perl 5 | 6 | # libr3 install 7 | RUN yum install -y git automake libtool 8 | RUN cd /tmp && git clone https://github.com/c9s/r3 9 | RUN cd /tmp/r3 && ./autogen.sh && ./configure && make && make install 10 | # ldconfig 11 | RUN echo "/usr/local/lib" >> /etc/ld.so.conf.d/default.conf 12 | RUN ldconfig 13 | 14 | 15 | # pcre 16 | RUN cd /tmp \ 17 | && wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.38.tar.gz \ 18 | && tar zxf pcre-8.38.tar.gz \ 19 | && mv pcre-8.38 /opt/pcre 20 | 21 | # openresty 22 | RUN cd /tmp \ 23 | && wget http://openresty.org/download/ngx_openresty-1.9.3.2.tar.gz \ 24 | && tar zxf ngx_openresty-1.9.3.2.tar.gz \ 25 | && cd ngx_openresty-1.9.3.2 \ 26 | && ./configure --with-luajit --prefix=/opt/openresty --with-http_gzip_static_module --with-pcre=/opt/pcre --with-pcre-jit \ 27 | && make \ 28 | && make install \ 29 | && ln -sf /opt/openresty/nginx/sbin/nginx /usr/local/bin/nginx 30 | RUN ln -sf /opt/openresty/luajit/bin/luajit-2.1.0-alpha /opt/openresty/luajit/bin/lua \ 31 | && ln -sf /opt/openresty/luajit/bin/lua /usr/local/bin/lua 32 | ADD nginx.conf /opt/openresty/nginx/conf/nginx.conf 33 | 34 | # volume 35 | VOLUME /code 36 | WORKDIR /code 37 | -------------------------------------------------------------------------------- /benchmark/nginx/nginx_apitools_router.conf: -------------------------------------------------------------------------------- 1 | #user nobody; 2 | worker_processes auto; 3 | 4 | events { 5 | worker_connections 10240; 6 | } 7 | 8 | http { 9 | init_by_lua ' 10 | require "resty.core" 11 | atrouter = require "router"; 12 | --atrouter = require "resty.r3_apitools"; 13 | -- foo handler 14 | function foo(params) 15 | ngx.say("fooooooooooooooooooooooo") 16 | ngx.say("tokens:" .. table.concat(tokens, ",")) 17 | for key, value in pairs(params) do 18 | ngx.say("param:" .. key .. "=" .. value) 19 | end 20 | end 21 | 22 | atr = atrouter.new() 23 | atr:match("GET", "/", function(params) 24 | ngx.say("hello1") 25 | end) 26 | atr:match("GET", "/foo/bar/baz/hoge/fuga/piyo/", function(params) 27 | --ngx.say("hello2") 28 | end) 29 | atr:match("GET", "/foo/:id/:name", function(params) 30 | ngx.say("hello3") 31 | end) 32 | '; 33 | 34 | include mime.types; 35 | default_type application/octet-stream; 36 | 37 | sendfile on; 38 | tcp_nopush on; 39 | 40 | keepalive_timeout 65; 41 | 42 | server { 43 | listen 80; 44 | server_name localhost; 45 | access_log off; 46 | 47 | location / { 48 | content_by_lua ' 49 | -- dispatcher 50 | local ok, err = atr:execute(ngx.var.request_method, ngx.var.uri) 51 | if not ok then 52 | ngx.status = 404 53 | ngx.print("Not found") 54 | end 55 | '; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /benchmark/nginx/nginx_lua_resty_r3.conf: -------------------------------------------------------------------------------- 1 | #user nobody; 2 | worker_processes auto; 3 | 4 | events { 5 | worker_connections 10240; 6 | } 7 | 8 | http { 9 | init_by_lua 'require "resty.core"'; 10 | init_worker_by_lua ' 11 | local r3router = require "resty.r3"; 12 | -- foo handler 13 | function foo(tokens, params) 14 | ngx.say("fooooooooooooooooooooooo") 15 | ngx.say("tokens:" .. table.concat(tokens, ",")) 16 | for key, value in pairs(params) do 17 | ngx.say("param:" .. key .. "=" .. value) 18 | end 19 | end 20 | 21 | r3r = r3router.new({ 22 | {"GET", "/", function(t, p) 23 | ngx.say("hello1") 24 | end}, 25 | {"GET", "/foo/bar/baz/hoge/fuga/piyo/", function(t, p) 26 | ngx.say("hello2") 27 | end}, 28 | {{"GET","POST"}, "/foo/{id}/{name}", function(t, p) 29 | ngx.say("hello3") 30 | end}, 31 | }) 32 | '; 33 | 34 | include mime.types; 35 | default_type application/octet-stream; 36 | 37 | sendfile on; 38 | tcp_nopush on; 39 | 40 | keepalive_timeout 65; 41 | 42 | server { 43 | listen 80; 44 | server_name localhost; 45 | access_log off; 46 | 47 | location / { 48 | content_by_lua ' 49 | -- dispatcher 50 | local ok, err = r3r:dispatch(ngx.var.request_method, ngx.var.uri) 51 | if not ok then 52 | ngx.status = 404 53 | ngx.print("Not found") 54 | end 55 | '; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lua-resty-r3 2 | ================ 3 | 4 | [libr3](https://github.com/c9s/r3) Lua-Openresty implementation. 5 | 6 | **This repository is an experimental.** 7 | 8 | ## Install 9 | 10 | ### libr3 11 | 12 | [See.](https://github.com/c9s/r3#install) 13 | 14 | ### lua-resty-r3 15 | 16 | ``` 17 | luarocks install https://raw.githubusercontent.com/toritori0318/lua-resty-r3/master/lua-resty-r3-dev-1.rockspec 18 | ``` 19 | 20 | ## SYNOPSYS 21 | 22 | ### Pattern1 23 | 24 | ```lua 25 | location / { 26 | content_by_lua ' 27 | -- foo handler 28 | function foo(tokens, params) 29 | ngx.say("fooooooooooooooooooooooo") 30 | ngx.say("tokens:" .. table.concat(tokens, ",")) 31 | for key, value in pairs(params) do 32 | ngx.say("param:" .. key .. "=" .. value) 33 | end 34 | end 35 | 36 | -- r3router 37 | local r3router = require "resty.r3"; 38 | local r = r3router.new() 39 | -- routing 40 | r:get("/", function(tokens, params) 41 | ngx.say("hello r3!") 42 | end) 43 | r:get("/foo", foo) 44 | r:get("/foo/{id}/{name}", foo) 45 | r:post("/foo/{id}/{name}", foo) 46 | -- don\'t forget! 47 | r:compile() 48 | 49 | -- dispatcher 50 | local ok = r:dispatch_ngx() 51 | ---- or manual 52 | ---- local ok = r:dispatch("GET", "/foo/123/999", ngx.req.get_uri_args(), ngx.req.get_post_args()) 53 | if ok then 54 | ngx.status = 200 55 | else 56 | ngx.status = 404 57 | ngx.print("Not found") 58 | end 59 | '; 60 | } 61 | ``` 62 | 63 | ### Pattern2 64 | 65 | ```lua 66 | location / { 67 | content_by_lua ' 68 | -- foo handler 69 | function foo(tokens, params) 70 | ngx.say("fooooooooooooooooooooooo") 71 | ngx.say("tokens:" .. table.concat(tokens, ",")) 72 | for key, value in pairs(params) do 73 | ngx.say("param:" .. key .. "=" .. value) 74 | end 75 | end 76 | 77 | -- r3router 78 | local r3router = require "resty.r3"; 79 | local r = r3router.new({ 80 | {"GET", "/", function(t, p) ngx.say("hello r3!") end }, 81 | {"GET", "/foo", foo}, 82 | {{"GET","POST"}, "/foo/{id}/{name}", foo}, 83 | }) 84 | 85 | -- dispatcher 86 | local ok = r:dispatch_ngx() 87 | ---- or manual 88 | ---- local ok = r:dispatch("GET", "/foo/123/999", ngx.req.get_uri_args(), ngx.req.get_post_args()) 89 | if ok then 90 | ngx.status = 200 91 | else 92 | ngx.status = 404 93 | ngx.print("Not found") 94 | end 95 | '; 96 | } 97 | ``` 98 | 99 | ## Docker Setup 100 | 101 | ``` 102 | cd /path/to/lua-resty-r3 103 | docker run -p 89:80 -v "$(pwd)":/code -it toritori0318/lua-resty-r3 /opt/openresty/nginx/sbin/nginx 104 | ``` 105 | -------------------------------------------------------------------------------- /lib/resty/r3.lua: -------------------------------------------------------------------------------- 1 | local ffi = require "ffi" 2 | local ffi_cast = ffi.cast 3 | local ffi_cdef = ffi.cdef 4 | local ffi_string = ffi.string 5 | local pcre = ffi.load("pcre") 6 | local r3 = ffi.load("r3") 7 | local string_len = string.len 8 | local string_upper = string.upper 9 | local table_insert = table.insert 10 | local unpack = unpack or table.unpack 11 | 12 | ffi_cdef[[ 13 | typedef struct real_pcre pcre; 14 | typedef struct pcre_extra pcre_extra; 15 | 16 | struct _edge; 17 | struct _node; 18 | struct _route; 19 | typedef struct _edge edge; 20 | typedef struct _node node; 21 | typedef struct _route route; 22 | 23 | typedef struct _str_array { 24 | char **tokens; 25 | int len; 26 | int cap; 27 | } str_array; 28 | 29 | struct _node { 30 | edge ** edges; 31 | // edge ** edge_table; 32 | 33 | // edges are mostly less than 255 34 | unsigned char compare_type; // compare_type: pcre, opcode, string 35 | unsigned char edge_len; 36 | unsigned char endpoint; // endpoint, should be zero for non-endpoint nodes 37 | unsigned char ov_cnt; // capture vector array size for pcre 38 | 39 | // almost less than 255 40 | unsigned char edge_cap; 41 | unsigned char route_len; 42 | unsigned char route_cap; 43 | // <-- here comes a char[1] struct padding for alignment since we have 4 char above. 44 | 45 | 46 | /** compile-time variables here.... **/ 47 | 48 | /* the combined regexp pattern string from pattern_tokens */ 49 | pcre * pcre_pattern; 50 | pcre_extra * pcre_extra; 51 | 52 | route ** routes; 53 | 54 | char * combined_pattern; 55 | 56 | /** 57 | * the pointer of route data 58 | */ 59 | void * data; 60 | }; 61 | 62 | struct _edge { 63 | char * pattern; // 8 bytes 64 | node * child; // 8 bytes 65 | unsigned char pattern_len; // 1 byte 66 | unsigned char opcode:4; // 4 bit 67 | unsigned char has_slug:1; // 1 bit 68 | }; 69 | 70 | struct _route { 71 | char * path; 72 | int path_len; 73 | 74 | int request_method; // can be (GET || POST) 75 | 76 | char * host; // required host name 77 | int host_len; 78 | 79 | void * data; 80 | 81 | char * remote_addr_pattern; 82 | int remote_addr_pattern_len; 83 | }; 84 | 85 | typedef struct { 86 | str_array * vars; 87 | const char * path; // current path to dispatch 88 | int path_len; // the length of the current path 89 | int request_method; // current request method 90 | 91 | void * data; // route ptr 92 | 93 | char * host; // the request host 94 | int host_len; 95 | 96 | char * remote_addr; 97 | int remote_addr_len; 98 | } match_entry; 99 | 100 | str_array * str_array_create(int cap); 101 | 102 | bool str_array_is_full(const str_array * l); 103 | 104 | bool str_array_resize(str_array *l, int new_cap); 105 | 106 | bool str_array_append(str_array * list, char * token); 107 | 108 | void str_array_free(str_array *l); 109 | 110 | void str_array_dump(const str_array *l); 111 | 112 | str_array * split_route_pattern(char *pattern, int pattern_len); 113 | 114 | 115 | node * r3_tree_create(int cap); 116 | 117 | node * r3_node_create(); 118 | 119 | void r3_tree_free(node * tree); 120 | 121 | edge * r3_node_connectl(node * n, const char * pat, int len, int strdup, node *child); 122 | 123 | edge * r3_node_find_edge(const node * n, const char * pat, int pat_len); 124 | 125 | void r3_node_append_edge(node *n, edge *child); 126 | 127 | 128 | edge * r3_node_find_common_prefix(node *n, const char *path, int path_len, int *prefix_len, char **errstr); 129 | 130 | node * r3_tree_insert_pathl(node *tree, const char *path, int path_len, void * data); 131 | 132 | 133 | 134 | route * r3_tree_insert_routel(node *tree, int method, const char *path, int path_len, void *data); 135 | 136 | route * r3_tree_insert_routel_ex(node *tree, int method, const char *path, int path_len, void *data, char **errstr); 137 | 138 | 139 | 140 | node * r3_tree_insert_pathl_ex(node *tree, const char *path, int path_len, route * route, void * data, char ** errstr); 141 | 142 | void r3_tree_dump(const node * n, int level); 143 | 144 | 145 | edge * r3_node_find_edge_str(const node * n, const char * str, int str_len); 146 | 147 | 148 | int r3_tree_compile(node *n, char** errstr); 149 | 150 | int r3_tree_compile_patterns(node * n, char** errstr); 151 | 152 | node * r3_tree_matchl(const node * n, const char * path, int path_len, match_entry * entry); 153 | 154 | bool r3_node_has_slug_edges(const node *n); 155 | 156 | edge * r3_edge_createl(const char * pattern, int pattern_len, node * child); 157 | 158 | node * r3_edge_branch(edge *e, int dl); 159 | 160 | void r3_edge_free(edge * edge); 161 | 162 | 163 | 164 | 165 | 166 | route * r3_route_create(const char * path); 167 | 168 | route * r3_route_createl(const char * path, int path_len); 169 | 170 | 171 | void r3_node_append_route(node * n, route * route); 172 | 173 | void r3_route_free(route * route); 174 | 175 | int r3_route_cmp(const route *r1, const match_entry *r2); 176 | 177 | route * r3_tree_match_route(const node *n, match_entry * entry); 178 | 179 | 180 | int r3_pattern_to_opcode(const char * pattern, int pattern_len); 181 | 182 | 183 | match_entry * match_entry_createl(const char * path, int path_len); 184 | 185 | void match_entry_free(match_entry * entry); 186 | ]] 187 | 188 | 189 | 190 | local _M = { _VERSION = '0.01' } 191 | local mt = { __index = _M } 192 | 193 | local bit = require "bit" 194 | local _METHOD_GET = 2; 195 | local _METHOD_POST = bit.lshift(2,1); 196 | local _METHOD_PUT = bit.lshift(2,2); 197 | local _METHOD_DELETE = bit.lshift(2,3); 198 | local _METHOD_PATCH = bit.lshift(2,4); 199 | local _METHOD_HEAD = bit.lshift(2,5); 200 | local _METHOD_OPTIONS = bit.lshift(2,6); 201 | local _METHODS = { 202 | GET = _METHOD_GET, 203 | POST = _METHOD_POST, 204 | PUT = _METHOD_PUT, 205 | DELETE = _METHOD_DELETE, 206 | PATCH = _METHOD_PATCH, 207 | HEAD = _METHOD_HEAD, 208 | OPTIONS = _METHOD_OPTIONS, 209 | } 210 | 211 | ---------------------------------------------------------------- 212 | -- new 213 | ---------------------------------------------------------------- 214 | function _M.new(routes) 215 | local self = setmetatable({ 216 | tree = r3.r3_tree_create(10), 217 | match_data_index = 0, 218 | match_data = {}, 219 | }, mt) 220 | 221 | if not routes then return self end 222 | 223 | -- register routes 224 | for i, route in ipairs(routes) do 225 | local method = route[1] 226 | local bit_methods = nil 227 | if type(method) ~= "table" then 228 | bit_methods = _METHODS[method] 229 | else 230 | local methods = {} 231 | for i2, m in ipairs(method) do 232 | table_insert(methods, _METHODS[m]) 233 | end 234 | bit_methods = bit.bor(unpack(methods)) 235 | end 236 | local path = route[2] 237 | local handler = route[3] 238 | -- register 239 | self:insert_route(bit_methods, path, handler) 240 | end 241 | 242 | -- compile 243 | if self.match_data_index > 0 then 244 | self:compile() 245 | end 246 | 247 | return self 248 | end 249 | 250 | function _M.compile(self) 251 | return r3.r3_tree_compile(self.tree, nil) 252 | end 253 | 254 | function _M.dump(self, level) 255 | level = level or 0 256 | return r3.r3_tree_dump(self.tree, level) 257 | end 258 | 259 | function _M.tree_free(self) 260 | return r3.r3_tree_free(self.tree) 261 | end 262 | 263 | function _M.match_entry_free(self, entry) 264 | return r3.match_entry_free(entry) 265 | end 266 | 267 | function _M.insert_route(self, method, path, block) 268 | if not method or not path or not block then return end 269 | 270 | self.match_data_index = self.match_data_index + 1 271 | self.match_data[self.match_data_index] = block 272 | local dataptr = ffi_cast('void *', ffi_cast('intptr_t', self.match_data_index)) 273 | 274 | -- route * r3_tree_insert_routel_ex(node *tree, int method, const char *path, int path_len, void *data, char **errstr); 275 | r3.r3_tree_insert_routel_ex(self.tree, method, path, string_len(path), dataptr, nil) 276 | end 277 | 278 | function _M.match_route(self, method, route, ...) 279 | local block 280 | local stokens={} 281 | 282 | local entry = r3.match_entry_createl(route, string_len(route)) 283 | entry.request_method = method; 284 | node = r3.r3_tree_match_route(self.tree, entry) 285 | if node == nil then 286 | self:match_entry_free(entry); 287 | entry = nil 288 | return false 289 | end 290 | 291 | -- get match data from index 292 | local i = tonumber(ffi_cast('intptr_t', ffi_cast('void *', node.data))) 293 | block = self.match_data[i] 294 | 295 | -- token proc 296 | if entry ~= nil and entry.vars and entry.vars.len then 297 | for i=0, entry.vars.len-1 do 298 | local token = ffi_string(entry.vars.tokens[i]) 299 | table_insert(stokens, token) 300 | end 301 | end 302 | 303 | -- free 304 | self:match_entry_free(entry); 305 | entry = nil 306 | 307 | -- execute block 308 | block(stokens, ...) 309 | return true 310 | end 311 | 312 | ---------------------------------------------------------------- 313 | -- method 314 | ---------------------------------------------------------------- 315 | function _M.get(self, path, block) 316 | self:insert_route(_METHODS["GET"], path, block) 317 | end 318 | function _M.post(self, path, block) 319 | self:insert_route(_METHODS["POST"], path, block) 320 | end 321 | function _M.put(self, path, block) 322 | self:insert_route(_METHODS["PUT"], path, block) 323 | end 324 | function _M.delete(self, path, block) 325 | self:insert_route(_METHODS["DELETE"], path, block) 326 | end 327 | 328 | ---------------------------------------------------------------- 329 | -- dispatcher 330 | ---------------------------------------------------------------- 331 | function _M.dispatch(self, method, path, ...) 332 | return self:match_route(_METHODS[method], path, ...) 333 | end 334 | function _M.dispatch_ngx(self) 335 | local method = string_upper(ngx.var.request_method) 336 | local path = ngx.var.uri 337 | local params = {} 338 | local body = "" 339 | if method == "GET" then 340 | params = ngx.req.get_uri_args() 341 | elseif method == "POST" then 342 | ngx.req.read_body() 343 | params = ngx.req.get_post_args() 344 | else 345 | ngx.req.read_body() 346 | body = ngx.req.get_body_data() 347 | end 348 | return self:match_route(_METHODS[method], path, params, body) 349 | end 350 | 351 | return _M 352 | --------------------------------------------------------------------------------