├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── benchmark └── test_3_routes.lua ├── lib └── resty │ └── r3.lua ├── r3_resty.c ├── r3_resty.h ├── rockspec ├── lua-resty-libr3-0.6-0.rockspec ├── lua-resty-libr3-0.7-0.rockspec ├── lua-resty-libr3-0.8-0.rockspec ├── lua-resty-libr3-0.9-0.rockspec ├── lua-resty-libr3-1.0-0.rockspec ├── lua-resty-libr3-1.1-0.rockspec └── lua-resty-libr3-1.2-0.rockspec └── t ├── R3.pm ├── collectgarbage.t ├── disable-uri-cache-opt.t ├── host-url.t ├── host.t ├── lib └── ljson.lua ├── remote-addr.t ├── sanity.t └── wildcard-host.t /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # dev 55 | go 56 | t/servroot 57 | r3/ 58 | */\.* 59 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | dist: xenial 3 | 4 | sudo: required 5 | 6 | addons: 7 | apt: 8 | packages: 9 | - cpanminus 10 | - build-essential 11 | - libncurses5-dev 12 | - libpcre3-dev 13 | - libreadline-dev 14 | - libssl-dev 15 | - perl 16 | 17 | env: 18 | global: 19 | - JOBS=2 20 | - OPENRESTY_PREFIX=/usr/local/openresty 21 | 22 | before_install: 23 | - sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1) 24 | 25 | install: 26 | - wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add - 27 | - sudo apt-get -y install software-properties-common 28 | - sudo add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" 29 | - sudo apt-get update 30 | - sudo apt-get install openresty 31 | - git clone https://github.com/openresty/test-nginx.git test-nginx 32 | 33 | script: 34 | - export PATH=$OPENRESTY_PREFIX/nginx/sbin:$PATH 35 | - make compile > build.log 2>&1 || (cat build.log && exit 1) 36 | - prove -Itest-nginx/lib -r t 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | 3 | ENV OPENRESTY_PREFIX=/usr/local/openresty 4 | 5 | RUN apt-get update && apt-get -y install git-core check libpcre3 libpcre3-dev build-essential libtool cpanminus build-essential libncurses5-dev libpcre3-dev libreadline-dev libssl-dev perl \ 6 | automake autoconf pkg-config software-properties-common wget && rm -rf /var/lib/apt/lists/* 7 | 8 | RUN cpanm --notest Test::Nginx 9 | 10 | RUN wget -qO - https://openresty.org/package/pubkey.gpg | apt-key add - 11 | RUN add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" 12 | RUN apt-get update && apt-get -y install openresty && rm -rf /var/lib/apt/lists/* 13 | 14 | ENV PATH="${OPENRESTY_PREFIX}/nginx/sbin:${PATH}" 15 | 16 | CMD ["/bin/bash"] 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | INST_PREFIX ?= /usr 2 | INST_LIBDIR ?= $(INST_PREFIX)/lib/lua/5.1 3 | INST_LUADIR ?= $(INST_PREFIX)/share/lua/5.1 4 | INSTALL ?= install 5 | UNAME ?= $(shell uname) 6 | R3_CONFIGURE_OPT ?= 7 | 8 | CFLAGS := -O3 -g -Wall -fpic 9 | 10 | C_SO_NAME := libr3.so 11 | LDFLAGS := -shared 12 | 13 | BUILDER_IMAGE = lua-resty-libr3-builder 14 | 15 | # on Mac OS X, one should set instead: 16 | # for Mac OS X environment, use one of options 17 | ifeq ($(UNAME),Darwin) 18 | LDFLAGS := -bundle -undefined dynamic_lookup 19 | C_SO_NAME := libr3.dylib 20 | R3_CONFIGURE_OPT := --host=x86_64 21 | endif 22 | 23 | MY_CFLAGS := $(CFLAGS) -DBUILDING_SO 24 | MY_LDFLAGS := $(LDFLAGS) -fvisibility=hidden 25 | 26 | OBJS := r3_resty.o 27 | R3_FOLDER := r3 28 | R3_CONGIGURE := $(R3_FOLDER)/configure 29 | R3_STATIC_LIB := $(R3_FOLDER)/.libs/libr3.a 30 | 31 | .PHONY: default 32 | default: compile 33 | 34 | ### test: Run test suite. Use test=... for specific tests 35 | .PHONY: test 36 | test: compile 37 | TEST_NGINX_LOG_LEVEL=info \ 38 | prove -I../test-nginx/lib -I./ -r -s t/ 39 | 40 | ### clean: Remove generated files 41 | .PHONY: clean 42 | clean: 43 | rm -rf $(R3_FOLDER) 44 | rm -f $(C_SO_NAME) $(OBJS) ${R3_CONGIGURE} 45 | 46 | 47 | ### compile: Compile library 48 | .PHONY: compile 49 | 50 | compile: ${R3_FOLDER} ${R3_CONGIGURE} ${R3_STATIC_LIB} $(C_SO_NAME) 51 | 52 | ${OBJS} : %.o : %.c 53 | $(CC) $(MY_CFLAGS) -c $< 54 | 55 | ${C_SO_NAME} : ${OBJS} 56 | $(CC) $(MY_LDFLAGS) $(OBJS) $(R3_FOLDER)/.libs/libr3.a -o $@ 57 | 58 | ${R3_FOLDER} : 59 | git clone -b 2.0.3-iresty https://github.com/iresty/r3.git 60 | 61 | ${R3_CONGIGURE} : 62 | cd $(R3_FOLDER) && ./autogen.sh 63 | 64 | ${R3_STATIC_LIB} : 65 | cd $(R3_FOLDER) && ./configure $(R3_CONFIGURE_OPT) && make 66 | 67 | 68 | ### install: Install the library to runtime 69 | .PHONY: install 70 | install: 71 | $(INSTALL) -d $(INST_LUADIR)/resty/ 72 | $(INSTALL) lib/resty/*.lua $(INST_LUADIR)/resty/ 73 | $(INSTALL) $(C_SO_NAME) $(INST_LIBDIR)/ 74 | 75 | docker-builder: 76 | docker build -t $(BUILDER_IMAGE) . 77 | 78 | ### build-in-docker: Build the package a in Docker image 79 | build-in-docker: clean docker-builder 80 | docker run -v `pwd`:/app/ $(BUILDER_IMAGE):latest bash -c 'cd /app && make compile' 81 | 82 | ### test-in-docker: Test the package in a Docker image 83 | test-in-docker: clean docker-builder 84 | docker run -v `pwd`:/app/ $(BUILDER_IMAGE):latest bash -c 'cd /app && make test' 85 | 86 | ### help: Show Makefile rules 87 | .PHONY: help 88 | help: 89 | @echo Makefile rules: 90 | @echo 91 | @grep -E '^### [-A-Za-z0-9_]+:' Makefile | sed 's/###/ /' 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | This is Lua-Openresty implementation library base on FFI for [libr3](https://github.com/c9s/r3). 4 | 5 | *NOTICE:* 6 | The Apache APISIX has changed the router to [lua-resty-radixtree](https://github.com/api7/lua-resty-radixtree), which is better than this library. It is highly recommended that you use the new routing implementation. 7 | 8 | 9 | [![Build Status](https://travis-ci.org/iresty/lua-resty-libr3.svg?branch=master)](https://travis-ci.org/iresty/lua-resty-libr3) 10 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/iresty/lua-resty-libr3/blob/master/LICENSE) 11 | 12 | Table of Contents 13 | ================= 14 | 15 | * [Name](#name) 16 | * [Status](#status) 17 | * [Synopsis](#synopsis) 18 | * [Methods](#methods) 19 | * [new](#new) 20 | * [insert_route](#insert_route) 21 | * [add router](#add-router) 22 | * [compile](#compile) 23 | * [dispatch](#dispatch) 24 | * [dispatch2](#dispatch2) 25 | * [Install](#install) 26 | 27 | Status 28 | ====== 29 | 30 | **This repository is an experimental.** 31 | 32 | Synopsis 33 | ======== 34 | 35 | ```lua 36 | location / { 37 | content_by_lua_block { 38 | -- r3 router 39 | local r3 = require("resty.r3").new(); 40 | local encode_json = require("cjson.safe").encode 41 | 42 | function foo(params) -- foo handler 43 | ngx.say("foo: ", encode_json(params)) 44 | end 45 | 46 | -- routing 47 | r3:get("/foo/{id}/{name}", foo) 48 | 49 | -- don't forget!!! 50 | r3:compile() 51 | 52 | -- dispatch 53 | local ok = r3:dispatch("/foo/a/b", ngx.req.get_method()) 54 | if not ok then 55 | ngx.exit(404) 56 | end 57 | } 58 | } 59 | ``` 60 | 61 | [Back to TOC](#table-of-contents) 62 | 63 | Methods 64 | ======= 65 | 66 | new 67 | --- 68 | 69 | `syntax: r3, err = r3router:new()` 70 | 71 | Creates a r3 object. In case of failures, returns `nil` and a string describing the error. 72 | 73 | `syntax: r3, err = r3router:new(routes)` 74 | 75 | The routes is a array table, like `{ {...}, {...}, {...} }`, Each element in the array is a route, which is a hash table. 76 | 77 | The attributes of each element may contain these: 78 | * `path`: client request uri. 79 | * `handler`: Lua callback function. 80 | * `host`: optional, client request host, not only supports normal domain name, but also supports wildcard name, both `foo.com` and `*.foo.com` are valid. 81 | * `remote_addr`: optional, client remote address like `192.168.1.100`, and we can use CIDR format, eg `192.168.1.0/24`. 82 | * `methods`: optional, It's an array table, we can put one or more method names together. Here is the valid method name: "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS". 83 | 84 | 85 | Example: 86 | 87 | ```lua 88 | -- foo handler 89 | function foo(params) 90 | ngx.say("foo: ", require("cjson").encode(params)) 91 | end 92 | 93 | local r3route = require "resty.r3" 94 | local r3 = r3route.new({ 95 | { 96 | path = [[/foo/{:\w+}/{:\w+}"]], 97 | method = {"GET"}, 98 | handler = foo 99 | }, 100 | { 101 | path = [[/bar/{:\w+}/{:\w+}]], 102 | host = "*.bar.com", 103 | handler = foo 104 | }, 105 | { 106 | path = [[/alice/{:\w+}/{:\w+}]], 107 | remote_addr = "192.168.1.0/24", 108 | handler = foo 109 | }, 110 | { 111 | path = [[/bob/{:\w+}/{:\w+}]], 112 | method = {"GET"}, 113 | host = "*.bob.com", 114 | remote_addr = "192.168.1.0/24", 115 | handler = foo 116 | }, 117 | }) 118 | ``` 119 | 120 | [Back to TOC](#table-of-contents) 121 | 122 | insert_route 123 | ------------ 124 | 125 | `syntax: r3, err = r3:insert_route(path, callback, opts)` 126 | 127 | * `path`: Client request uri. 128 | * `callback`: Lua callback function. 129 | 130 | `opts` is optional argument, it is a Lua table. 131 | * `method`: It's an array table, we can put one or more method names together. 132 | * `host`: optional, client request host, not only supports normal domain name, but also supports wildcard name, both `foo.com` and `*.foo.com` are valid. 133 | * `remote_addr`: optional, client remote address like `192.168.1.100`, and we can use CIDR format, eg `192.168.1.0/24`. 134 | 135 | 136 | ```lua 137 | -- route 138 | local function foo(params) 139 | ngx.say("foo") 140 | end 141 | 142 | local r3route = require "resty.r3" 143 | local r3 = r3route.new() 144 | 145 | r3:insert_route("/a", foo) 146 | r3:insert_route("/b", foo, {method = {"GET"}}) 147 | ``` 148 | 149 | add router 150 | ---------- 151 | 152 | BTW, we can add a router by specifying a lowercase method name. 153 | 154 | Valid method name list: `get`, `post`, `put`, `delete`, `patch`, `head`, `options`. 155 | 156 | ```lua 157 | -- route 158 | local function foo(params) 159 | ngx.say("foo") 160 | end 161 | 162 | r3:get("/a", foo) 163 | r3:post("/b", foo) 164 | r3:put("/c", foo) 165 | r3:delete("/d", foo) 166 | ``` 167 | 168 | [Back to TOC](#table-of-contents) 169 | 170 | compile 171 | ------- 172 | 173 | `syntax: r3:compile()` 174 | 175 | It compiles our route paths into a prefix tree (trie). You must compile after adding all routes, otherwise it may fail to match. 176 | 177 | [Back to TOC](#table-of-contents) 178 | 179 | 180 | dispatch 181 | -------- 182 | 183 | `syntax: ok = r3:dispatch(path, method)` 184 | 185 | * `path`: client request uri. 186 | * `method`: method name of client request. 187 | 188 | `syntax: ok = r3:dispatch(path, opts)` 189 | 190 | * `path`: client request uri. 191 | * `opts`: a Lua tale 192 | * `method`: optional, method name of client request. 193 | * `host`: optional, client request host, not only supports normal domain name, but also supports wildcard name, both `foo.com` and `*.foo.com` are valid. 194 | * `remote_addr`: optional, client remote address like `192.168.1.100`, and we can use CIDR format, eg `192.168.1.0/24`. 195 | 196 | Dispatchs the path to the controller by `method`, `path` and `host`. 197 | 198 | ```lua 199 | local ok = r3:dispatch(ngx.var.uri, ngx.req.get_method()) 200 | ``` 201 | 202 | [Back to TOC](#table-of-contents) 203 | 204 | dispatch2 205 | --------- 206 | 207 | `syntax: ok = r3:dispatch2(param_tab, path, method)` 208 | 209 | `syntax: ok = r3:dispatch2(param_tab, path, opts)` 210 | 211 | Basically the same as `dispatch`, support for passing in a `table` object to 212 | store parsing parameters, makes it easier to reuse lua table. 213 | 214 | [Back to TOC](#table-of-contents) 215 | 216 | Install 217 | ======= 218 | 219 | ### Dependent library 220 | 221 | ```shell 222 | # Ubuntu 223 | sudo apt-get install check libpcre3 libpcre3-dev build-essential libtool \ 224 | automake autoconf pkg-config 225 | # CentOS 7 226 | sodu yum install gcc gcc-c++ git make automake autoconf pcre pcre-devel \ 227 | libtool pkgconfig 228 | ``` 229 | 230 | ### Compile and install 231 | 232 | ``` 233 | sudo make install 234 | ``` 235 | -------------------------------------------------------------------------------- /benchmark/test_3_routes.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yuansheng Wang 2 | 3 | local r3router = require "resty.r3"; 4 | 5 | -- display time 6 | function time(title, block) 7 | local st = os.clock() 8 | block() 9 | local ed = os.clock() 10 | 11 | ngx.say(title .. ": " .. ed-st.. " sec") 12 | ngx.flush() 13 | end 14 | 15 | -- handler 16 | function foo(tokens, params) 17 | --ngx.say("fooooooooooooooooooooooooooo") 18 | --ngx.say(tokens) 19 | end 20 | 21 | -- router 22 | local r3router = require "resty.r3"; 23 | local r = r3router.new() 24 | 25 | r:get("/", foo) 26 | for i = 1, 1000 do 27 | r:get("/a/" .. i, foo) 28 | end 29 | ngx.say("insert routes 1th") 30 | 31 | r:get("/foo/bar/baz/hoge/fuga/piyo", foo) 32 | for i = 1, 1000 do 33 | r:get("/foo/bar/baz/hoge/fuga/piyo/" .. i, foo) 34 | end 35 | ngx.say("insert routes 2th") 36 | 37 | r:insert_route({"GET", "POST"}, "/foo/{id}/{name}", foo) 38 | for i = 1, 1000 do 39 | r:insert_route({"GET", "POST"}, "/bar/{id}/{name}" .. i, foo) 40 | end 41 | ngx.say("insert route3 3th") 42 | 43 | ngx.say("inserted all routes") 44 | ngx.flush() 45 | 46 | r:compile() 47 | 48 | ngx.say("compiled routes") 49 | ngx.flush() 50 | 51 | ---------------------------------------------------------------------- 52 | -- bench 1 53 | time("get /", function() 54 | for i=0, 10000000 do 55 | r:dispatch("GET", "/") 56 | end 57 | end) 58 | 59 | -- bench 2 60 | time("get /foo/bar/baz/hoge/fuga/piyo", function() 61 | for i=0, 10000000 do 62 | r:dispatch("GET", "/foo/bar/baz/hoge/fuga/piyo") 63 | end 64 | end) 65 | 66 | -- bench 3 67 | time("get /foo/{id}/{name}", function() 68 | for i=0, 10000000 do 69 | r:dispatch("GET", "/foo/123/999") 70 | end 71 | end) 72 | 73 | -- bench 4 74 | time("fetch from lua table by key", function() 75 | local t = {} 76 | for i=1,1000 do 77 | t["a" .. i] = i 78 | end 79 | 80 | local v 81 | for i=0, 10000000 do 82 | v = t["a3"] 83 | end 84 | 85 | ngx.say("fetched val: ", v) 86 | end) 87 | -------------------------------------------------------------------------------- /lib/resty/r3.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yuansheng Wang 2 | 3 | local base = require("resty.core.base") 4 | local clear_tab = require("table.clear") 5 | local clone_tab = require("table.clone") 6 | local str_buff = base.get_string_buf(256) 7 | local buf_len_prt = base.get_size_ptr() 8 | local new_tab = base.new_tab 9 | local find_str = string.find 10 | local tonumber = tonumber 11 | local ipairs = ipairs 12 | local ffi = require "ffi" 13 | local ffi_cast = ffi.cast 14 | local ffi_cdef = ffi.cdef 15 | local ffi_string = ffi.string 16 | local C = ffi.C 17 | local insert_tab = table.insert 18 | local string = string 19 | local io = io 20 | local package = package 21 | local getmetatable=getmetatable 22 | local setmetatable=setmetatable 23 | local ngx_log = ngx.log 24 | local ngx_ERR = ngx.ERR 25 | local type = type 26 | local error = error 27 | local newproxy = _G.newproxy 28 | local str_sub = string.sub 29 | 30 | 31 | local function load_shared_lib(so_name) 32 | local string_gmatch = string.gmatch 33 | local string_match = string.match 34 | local io_open = io.open 35 | local io_close = io.close 36 | 37 | local cpath = package.cpath 38 | local tried_paths = new_tab(32, 0) 39 | local i = 1 40 | 41 | for k, _ in string_gmatch(cpath, "[^;]+") do 42 | local fpath = string_match(k, "(.*/)") 43 | fpath = fpath .. so_name 44 | -- Don't get me wrong, the only way to know if a file exist is trying 45 | -- to open it. 46 | local f = io_open(fpath) 47 | if f ~= nil then 48 | io_close(f) 49 | return ffi.load(fpath) 50 | end 51 | tried_paths[i] = fpath 52 | i = i + 1 53 | end 54 | 55 | return nil, tried_paths 56 | end 57 | 58 | 59 | local lib_name = "libr3.so" 60 | if ffi.os == "OSX" then 61 | lib_name = "libr3.dylib" 62 | end 63 | 64 | 65 | local r3, tried_paths = load_shared_lib(lib_name) 66 | if not r3 then 67 | tried_paths[#tried_paths + 1] = 'tried above paths but can not load ' 68 | .. lib_name 69 | error(table.concat(tried_paths, '\r\n', 1, #tried_paths)) 70 | end 71 | 72 | 73 | ffi_cdef[[ 74 | void *r3_create(int cap); 75 | void r3_free(void * tree); 76 | 77 | void *r3_insert(void *tree, int method, const char *path, 78 | int path_len, void *data, char **errstr); 79 | int r3_compile(void *tree, char** errstr); 80 | 81 | int r3_route_set_attr(void *router, const char *host, const char *remote_addr, 82 | int remote_addr_bits); 83 | int r3_route_attribute_free(void *router); 84 | 85 | void *r3_match_entry_create(const char *path, int method, const char *host, 86 | const char *remote_addr); 87 | void *r3_match_route(const void *tree, void *entry); 88 | void *r3_match_route_fetch_idx(void *route); 89 | 90 | int r3_match_entry_fetch_slugs(void *entry, size_t idx, char *val, 91 | size_t *val_len); 92 | int r3_match_entry_fetch_tokens(void *entry, size_t idx, char *val, 93 | size_t *val_len); 94 | 95 | void r3_match_entry_free(void *entry); 96 | 97 | unsigned int inet_network(const char *cp); 98 | 99 | void *ngx_create_pool(size_t size, void *log); 100 | void ngx_destroy_pool(void *pool); 101 | void *ngx_http_lua_pcre_malloc_init(void *pool); 102 | void ngx_http_lua_pcre_malloc_done(void *old_pool); 103 | 104 | extern void *ngx_cycle; 105 | 106 | typedef struct { 107 | void ****conf_ctx; 108 | void *pool; 109 | void *log; 110 | } fake_ngx_cycle; 111 | 112 | void *memcpy(void *dest, const void *src, size_t n); 113 | 114 | int is_valid_ipv4(const char *ipv4); 115 | int is_valid_ipv6(const char *ipv6); 116 | ]] 117 | 118 | 119 | local fake_ngx_cycle = ffi.new("fake_ngx_cycle") 120 | C.memcpy(fake_ngx_cycle, C.ngx_cycle, ffi.sizeof("fake_ngx_cycle")) 121 | 122 | 123 | local _M = { _VERSION = '0.01' } 124 | 125 | 126 | -- only work under lua51 or luajit 127 | local function setmt__gc(t, mt) 128 | local prox = newproxy(true) 129 | getmetatable(prox).__gc = function() mt.__gc(t) end 130 | t[prox] = true 131 | return setmetatable(t, mt) 132 | end 133 | 134 | 135 | local function gc_free(self) 136 | if ngx.worker.exiting() then 137 | self.pool = nil 138 | return 139 | end 140 | 141 | if self.pool then 142 | C.ngx_destroy_pool(self.pool) 143 | self.pool = nil 144 | end 145 | 146 | self:free() 147 | end 148 | 149 | 150 | local mt = { __index = _M, __gc = gc_free } 151 | 152 | 153 | local bit = require("bit") 154 | local _METHOD_GET = 2 155 | local _METHOD_POST = bit.lshift(2,1) 156 | local _METHOD_PUT = bit.lshift(2,2) 157 | local _METHOD_DELETE = bit.lshift(2,3) 158 | local _METHOD_PATCH = bit.lshift(2,4) 159 | local _METHOD_HEAD = bit.lshift(2,5) 160 | local _METHOD_OPTIONS = bit.lshift(2,6) 161 | 162 | 163 | local _METHODS = { 164 | GET = _METHOD_GET, 165 | POST = _METHOD_POST, 166 | PUT = _METHOD_PUT, 167 | DELETE = _METHOD_DELETE, 168 | PATCH = _METHOD_PATCH, 169 | HEAD = _METHOD_HEAD, 170 | OPTIONS = _METHOD_OPTIONS, 171 | } 172 | 173 | 174 | local route_opts = {} 175 | local function insert_route(self, opts) 176 | local method = opts.method 177 | local path = opts.path 178 | local host = opts.host 179 | local handler = opts.handler 180 | local remote_addr = opts.remote_addr or "0.0.0.0" 181 | local remote_addr_bits = tonumber(opts.remote_addr_bits) or 0 182 | 183 | if type(path) ~= "string" then 184 | error("invalid argument path") 185 | end 186 | 187 | if type(handler) ~= "function" then 188 | error("invalid argument handler") 189 | end 190 | 191 | if r3.is_valid_ipv4(remote_addr) ~= 0 then 192 | error("invalid argument remote_addr") 193 | end 194 | 195 | if not method or not path or not handler then 196 | return nil, "invalid argument of route" 197 | end 198 | 199 | insert_tab(self.cached_route_conf, clone_tab(opts)) 200 | 201 | if not self.disable_path_cache_opt 202 | and not find_str(path, [[{]], 1, true) then 203 | local host_is_wildcard 204 | local host_wildcard 205 | if host and host:sub(1, 1) == '*' then 206 | host_is_wildcard = true 207 | host_wildcard = host:sub(2):reverse() 208 | end 209 | 210 | local path_cache = { 211 | bit_methods = method, 212 | host_is_wildcard = host_is_wildcard, 213 | host_wildcard = host_wildcard, 214 | host = host, 215 | remote_addr = r3.inet_network(remote_addr), 216 | remote_addr_bits = remote_addr_bits, 217 | handler = handler, 218 | } 219 | 220 | if not self.hash_path[path] then 221 | self.hash_path[path] = {path_cache} 222 | 223 | else 224 | insert_tab(self.hash_path[path], path_cache) 225 | end 226 | 227 | return true 228 | end 229 | 230 | self.match_data_index = self.match_data_index + 1 231 | self.match_data[self.match_data_index] = handler 232 | local dataptr = ffi_cast('void *', 233 | ffi_cast('intptr_t', self.match_data_index)) 234 | 235 | local r3_node = r3.r3_insert(self.tree, method, path, #path, dataptr, nil) 236 | local ret = r3.r3_route_set_attr(r3_node, host, remote_addr, 237 | remote_addr_bits) 238 | if ret == -1 then 239 | ngx_log(ngx_ERR, "failed to set the attribute for route") 240 | end 241 | 242 | insert_tab(self.r3_nodes, r3_node) 243 | return r3_node 244 | end 245 | 246 | 247 | function _M.new(routes, opts) 248 | local route_n = routes and #routes or 10 249 | local disable_path_cache_opt = opts and opts.disable_path_cache_opt 250 | 251 | local pool = C.ngx_create_pool(128, fake_ngx_cycle.log) -- size: 128 252 | if not pool then 253 | error("failed to create single pool for r3 object") 254 | end 255 | 256 | local old_pool = C.ngx_http_lua_pcre_malloc_init(pool) 257 | 258 | local self = setmt__gc({ 259 | pool = pool, 260 | old_pool = old_pool, 261 | tree = r3.r3_create(route_n), 262 | hash_path = new_tab(0, route_n), 263 | r3_nodes = new_tab(128, 0), 264 | match_data_index = 0, 265 | match_data = new_tab(route_n, 0), 266 | disable_path_cache_opt = disable_path_cache_opt, 267 | cached_route_conf = new_tab(128, 0), 268 | }, mt) 269 | 270 | if not routes then return self end 271 | 272 | -- register routes 273 | for i = 1, route_n do 274 | local route = routes[i] 275 | 276 | if type(route.path) ~= "string" then 277 | error("invalid argument path", 2) 278 | end 279 | 280 | if type(route.handler) ~= "function" then 281 | error("invalid argument handler", 2) 282 | end 283 | 284 | local method = route.method 285 | local bit_methods 286 | if type(method) ~= "table" then 287 | bit_methods = method and _METHODS[method] or 0 288 | 289 | else 290 | bit_methods = 0 291 | for _, m in ipairs(method) do 292 | bit_methods = bit.bor(bit_methods, _METHODS[m]) 293 | end 294 | end 295 | 296 | clear_tab(route_opts) 297 | route_opts.path = route.path 298 | route_opts.handler = route.handler 299 | route_opts.method = bit_methods 300 | route_opts.host = route.host 301 | 302 | if route.remote_addr then 303 | local idx = find_str(route.remote_addr, "/", 1, true) 304 | if idx then 305 | route_opts.remote_addr = str_sub(route.remote_addr, 1, idx - 1) 306 | route_opts.remote_addr_bits = str_sub(route.remote_addr, 307 | idx + 1) 308 | 309 | else 310 | route_opts.remote_addr = route.remote_addr 311 | route_opts.remote_addr_bits = 32 312 | end 313 | end 314 | 315 | insert_route(self, route_opts) 316 | end 317 | 318 | return self 319 | end 320 | 321 | 322 | function _M.compile(self) 323 | local ret = r3.r3_compile(self.tree, nil) 324 | if self.old_pool then 325 | C.ngx_http_lua_pcre_malloc_done(self.old_pool) 326 | self.old_pool = nil 327 | end 328 | 329 | return ret 330 | end 331 | 332 | 333 | function _M.free(self) 334 | if not self.tree then 335 | return 336 | end 337 | 338 | for _, r3_node in ipairs(self.r3_nodes) do 339 | r3.r3_route_attribute_free(r3_node) 340 | end 341 | 342 | r3.r3_free(self.tree) 343 | self.tree = nil 344 | end 345 | 346 | 347 | local function match_route(self, path, opts, params, ...) 348 | local method = opts.method 349 | method = _METHODS[method] or 0 350 | 351 | local entry = r3.r3_match_entry_create(path, method, opts.host, 352 | opts.remote_addr) 353 | local matched_route = r3.r3_match_route(self.tree, entry) 354 | if matched_route == nil then 355 | r3.r3_match_entry_free(entry) 356 | return false 357 | end 358 | 359 | local data_idx = r3.r3_match_route_fetch_idx(matched_route) 360 | 361 | -- get match data from index 362 | local idx = tonumber(ffi_cast('intptr_t', ffi_cast('void *', data_idx))) 363 | local block = self.match_data[idx] 364 | 365 | if params then 366 | buf_len_prt[0] = 0 367 | local cnt = r3.r3_match_entry_fetch_slugs(entry, 0, nil, buf_len_prt) 368 | 369 | idx = 0 370 | for i = 0, cnt - 1 do 371 | r3.r3_match_entry_fetch_slugs(entry, i, str_buff, buf_len_prt) 372 | local key = ffi_string(str_buff, buf_len_prt[0]) 373 | 374 | r3.r3_match_entry_fetch_tokens(entry, i, str_buff, buf_len_prt) 375 | local val = ffi_string(str_buff, buf_len_prt[0]) 376 | 377 | if key == "" then 378 | idx = idx + 1 379 | params[idx] = val 380 | 381 | else 382 | params[key] = val 383 | end 384 | end 385 | end 386 | 387 | -- free 388 | r3.r3_match_entry_free(entry) 389 | 390 | -- execute block 391 | block(params, ...) 392 | return true 393 | end 394 | 395 | function _M.match_route(self, path, opts, ...) 396 | local params = new_tab(0, 4) 397 | return match_route(self, path, opts, params, ...) 398 | end 399 | 400 | ---------------------------------------------------------------- 401 | -- method 402 | ---------------------------------------------------------------- 403 | for _, name in ipairs({"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", 404 | "OPTIONS"}) do 405 | local l_name = string.lower(name) 406 | _M[l_name] = function (self, path, handler) 407 | if type(path) ~= "string" then 408 | error("invalid argument path") 409 | end 410 | 411 | if type(handler) ~= "function" then 412 | error("invalid argument handler") 413 | end 414 | 415 | clear_tab(route_opts) 416 | route_opts.method = _METHODS[name] 417 | route_opts.path = path 418 | route_opts.handler = handler 419 | return insert_route(self, route_opts) 420 | end 421 | end 422 | 423 | 424 | function _M.insert_route(self, path, handler, opts) 425 | -- method, path, handler 426 | if type(path) ~= "string" then 427 | error("invalid argument path", 2) 428 | end 429 | 430 | if type(handler) ~= "function" then 431 | error("invalid argument handler", 2) 432 | end 433 | 434 | if opts and type(opts) ~= "table" then 435 | error("invalid argument opts", 2) 436 | end 437 | 438 | local method 439 | local host 440 | local remote_addr 441 | local remote_addr_bits 442 | if opts then 443 | method = opts.method 444 | path = opts.path or path 445 | host = opts.host 446 | remote_addr = opts.remote_addr 447 | remote_addr_bits = opts.remote_addr_bits 448 | end 449 | 450 | local bit_methods 451 | if type(method) ~= "table" then 452 | bit_methods = method and _METHODS[method] or 0 453 | 454 | else 455 | bit_methods = 0 456 | for _, m in ipairs(method) do 457 | bit_methods = bit.bor(bit_methods, _METHODS[m]) 458 | end 459 | end 460 | 461 | clear_tab(route_opts) 462 | route_opts.path = path 463 | route_opts.handler = handler 464 | 465 | route_opts.method = bit_methods 466 | route_opts.host = host 467 | route_opts.remote_addr = remote_addr 468 | route_opts.remote_addr_bits = remote_addr_bits 469 | return insert_route(self, route_opts) 470 | end 471 | 472 | 473 | local function match_by_path_cache(route, params, opts, ...) 474 | local method = opts and opts.method 475 | if route.bit_methods ~= 0 then 476 | if not method or type(_METHODS[method]) ~= "number" or 477 | bit.band(route.bit_methods, _METHODS[method]) == 0 then 478 | return false 479 | end 480 | end 481 | 482 | if route.host then 483 | if #route.host > #opts.host then 484 | return false 485 | end 486 | 487 | if route.host_is_wildcard then 488 | local i = opts.host:reverse():find(route.host_wildcard, 1, true) 489 | if i ~= 1 then 490 | return false 491 | end 492 | 493 | elseif route.host ~= opts.host then 494 | return false 495 | end 496 | end 497 | 498 | if route.remote_addr and route.remote_addr > 0 then 499 | if not opts.remote_addr then 500 | return false 501 | end 502 | 503 | if r3.is_valid_ipv4(opts.remote_addr) ~= 0 then 504 | return false 505 | end 506 | 507 | local remote_addr_inet = r3.inet_network(opts.remote_addr) 508 | if bit.rshift(route.remote_addr, 32 - route.remote_addr_bits) 509 | ~= bit.rshift(remote_addr_inet, 32 - route.remote_addr_bits) then 510 | return false 511 | end 512 | end 513 | 514 | route.handler(params, ...) 515 | return true 516 | end 517 | 518 | 519 | local opts_method = {} 520 | local function dispatch2(self, params, path, opts, ...) 521 | if not self.disable_path_cache_opt and self.hash_path[path] then 522 | for _, route in ipairs(self.hash_path[path]) do 523 | local ok = match_by_path_cache(route, params, opts, ...) 524 | if ok then 525 | return ok 526 | end 527 | end 528 | end 529 | 530 | return match_route(self, path, opts, params, ...) 531 | end 532 | 533 | 534 | function _M.dispatch2(self, params, path, method_or_opts, ...) 535 | if type(path) ~= "string" then 536 | error("invalid argument path", 2) 537 | end 538 | 539 | local opts = method_or_opts 540 | if not method_or_opts or type(method_or_opts) == "string" then 541 | clear_tab(opts_method) 542 | opts_method.method = method_or_opts 543 | opts = opts_method 544 | end 545 | 546 | local ok = dispatch2(self, params, path, opts, ...) 547 | return ok 548 | end 549 | 550 | 551 | 552 | function _M.dispatch(self, path, method_or_opts, ...) 553 | if type(path) ~= "string" then 554 | error("invalid argument path", 2) 555 | end 556 | 557 | -- use dispatch2 is better, avoid temporary table 558 | local opts = method_or_opts 559 | if not method_or_opts or type(method_or_opts) == "string" then 560 | clear_tab(opts_method) 561 | opts_method.method = method_or_opts 562 | opts = opts_method 563 | end 564 | 565 | local ok = dispatch2(self, {}, path, opts, ...) 566 | return ok 567 | end 568 | 569 | 570 | return _M 571 | -------------------------------------------------------------------------------- /r3_resty.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "r3_resty.h" 4 | 5 | 6 | void * 7 | r3_create(int cap) 8 | { 9 | R3Node *tree = r3_tree_create(cap); 10 | return (void *)tree; 11 | } 12 | 13 | 14 | void 15 | r3_free(void * tree) 16 | { 17 | if (tree == NULL) { 18 | return; 19 | } 20 | 21 | r3_tree_free((R3Node *)tree); 22 | } 23 | 24 | 25 | void * 26 | r3_insert(void *tree, int method, const char *path, 27 | int path_len, void *data, char **errstr) 28 | { 29 | R3Node *r3_tree = (R3Node *)tree; 30 | 31 | R3Route *route = r3_tree_insert_routel_ex(r3_tree, method, path, path_len, 32 | data, errstr); 33 | return (void *)route; 34 | } 35 | 36 | int 37 | r3_route_set_attr(void *router, const char *host, const char *remote_addr, 38 | int remote_addr_bits) 39 | { 40 | R3Route *r3_router = (R3Route *)router; 41 | if (r3_router->host.base) { 42 | return -1; 43 | } 44 | 45 | if (host) { 46 | r3_router->host.len = strlen(host); 47 | char *host_buf = r3_mem_alloc(r3_router->host.len); 48 | memcpy(host_buf, host, r3_router->host.len); 49 | r3_router->host.base = host_buf; 50 | } 51 | 52 | if (remote_addr_bits == 0) { 53 | r3_router->remote_addr_v4_bits = 0; 54 | r3_router->remote_addr_v4 = 0; 55 | 56 | } else { 57 | r3_router->remote_addr_v4_bits = remote_addr_bits; 58 | r3_router->remote_addr_v4 = inet_network(remote_addr); 59 | } 60 | 61 | // fprintf(stderr, "addr: %u bits: %d\n", r3_router->remote_addr_v4, 62 | // r3_router->remote_addr_v4_bits); 63 | return 0; 64 | } 65 | 66 | int 67 | r3_route_attribute_free(void *router) 68 | { 69 | R3Route *r3_router = (R3Route *)router; 70 | if (!r3_router->host.base) { 71 | return 0; 72 | } 73 | 74 | free((void *)r3_router->host.base); 75 | r3_router->host.base = NULL; 76 | r3_router->host.len = 0; 77 | return 0; 78 | } 79 | 80 | 81 | int 82 | r3_compile(void *tree, char** errstr) 83 | { 84 | return r3_tree_compile((R3Node *)tree, errstr); 85 | } 86 | 87 | 88 | void * 89 | r3_match_entry_create(const char *path, int method, const char *host, 90 | const char *remote_addr) 91 | { 92 | match_entry *entry; 93 | 94 | entry = match_entry_create(path); 95 | entry->request_method = method; 96 | 97 | if (host) { 98 | entry->host.base = host; 99 | entry->host.len = strlen(host); 100 | } 101 | 102 | if (remote_addr) { 103 | entry->remote_addr.base = remote_addr; 104 | entry->remote_addr.len = sizeof(remote_addr) - 1; 105 | } 106 | 107 | return (void *) entry; 108 | } 109 | 110 | 111 | void * 112 | r3_match_route(const void *tree, void *entry) 113 | { 114 | R3Route *matched_route; 115 | 116 | matched_route = r3_tree_match_route((R3Node *)tree, 117 | (match_entry *)entry); 118 | return (void *)matched_route; 119 | } 120 | 121 | 122 | void * 123 | r3_match_route_fetch_idx(void *route) 124 | { 125 | R3Route *matched_route = route; 126 | 127 | if (matched_route == NULL) { 128 | return NULL; 129 | } 130 | 131 | return (void *)matched_route->data; 132 | } 133 | 134 | 135 | size_t 136 | r3_match_entry_fetch_slugs(void *entry, size_t idx, char *val, 137 | size_t *val_len) 138 | { 139 | match_entry *m_entry = entry; 140 | int i; 141 | 142 | if (val == NULL) { 143 | return m_entry->vars.slugs.size; 144 | } 145 | 146 | if (idx >= m_entry->vars.slugs.size) { 147 | return -1; 148 | } 149 | 150 | i = m_entry->vars.slugs.entries[idx].len; 151 | *val_len = i; 152 | 153 | sprintf(val, "%*.*s", i, i, m_entry->vars.slugs.entries[idx].base); 154 | return m_entry->vars.slugs.size; 155 | } 156 | 157 | 158 | size_t 159 | r3_match_entry_fetch_tokens(void *entry, size_t idx, char *val, 160 | size_t *val_len) 161 | { 162 | match_entry *m_entry = entry; 163 | int i_len; 164 | 165 | if (val == NULL) { 166 | return m_entry->vars.tokens.size; 167 | } 168 | 169 | if (idx >= m_entry->vars.tokens.size) { 170 | return -1; 171 | } 172 | 173 | i_len = m_entry->vars.tokens.entries[idx].len; 174 | *val_len = i_len; 175 | 176 | sprintf(val, "%*.*s", i_len, i_len, m_entry->vars.tokens.entries[idx].base); 177 | return m_entry->vars.tokens.size; 178 | } 179 | 180 | 181 | void 182 | r3_match_entry_free(void *entry) 183 | { 184 | match_entry *r3_entry = (match_entry *)entry; 185 | 186 | if (entry == NULL) { 187 | return; 188 | } 189 | 190 | match_entry_free(r3_entry); 191 | return; 192 | } 193 | 194 | 195 | int 196 | is_valid_ipv4(const char *ipv4) 197 | { 198 | struct in_addr addr; 199 | 200 | if(ipv4 == NULL) { 201 | return -1; 202 | } 203 | 204 | if(inet_pton(AF_INET, ipv4, (void *)&addr) != 1) { 205 | return -1; 206 | } 207 | 208 | return 0; 209 | } 210 | 211 | 212 | int 213 | is_valid_ipv6(const char *ipv6) 214 | { 215 | struct in6_addr addr6; 216 | 217 | if(ipv6 == NULL) { 218 | return -1; 219 | } 220 | 221 | if(inet_pton(AF_INET6, ipv6, (void *)&addr6) != 1) { 222 | return -1; 223 | } 224 | 225 | return 0; 226 | } 227 | -------------------------------------------------------------------------------- /r3_resty.h: -------------------------------------------------------------------------------- 1 | #ifndef LUA_RESTY_R3_RESTY_H 2 | #define LUA_RESTY_R3_RESTY_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | #include "r3/include/r3.h" 12 | 13 | 14 | #ifdef BUILDING_SO 15 | #ifndef __APPLE__ 16 | #define LSH_EXPORT __attribute__ ((visibility ("protected"))) 17 | #else 18 | /* OSX does not support protect-visibility */ 19 | #define LSH_EXPORT __attribute__ ((visibility ("default"))) 20 | #endif 21 | #else 22 | #define LSH_EXPORT 23 | #endif 24 | 25 | /* ************************************************************************** 26 | * 27 | * Export Functions 28 | * 29 | * ************************************************************************** 30 | */ 31 | 32 | void *r3_create(int cap); 33 | void r3_free(void * tree); 34 | 35 | void *r3_insert(void *tree, int method, const char *path, 36 | int path_len, void *data, char **errstr); 37 | int r3_compile(void *tree, char** errstr); 38 | 39 | int r3_route_set_attr(void *router, const char *host, const char *remote_addr, 40 | int remote_addr_bits); 41 | 42 | int r3_route_attribute_free(void *router); 43 | 44 | 45 | void *r3_match_entry_create(const char *path, int method, const char *host, 46 | const char *remote_addr); 47 | void *r3_match_route(const void *tree, void *entry); 48 | 49 | void *r3_match_route_fetch_idx(void *route); 50 | size_t r3_match_entry_fetch_slugs(void *entry, size_t idx, char *val, 51 | size_t *val_len); 52 | size_t r3_match_entry_fetch_tokens(void *entry, size_t idx, char *val, 53 | size_t *val_len); 54 | 55 | void r3_match_entry_free(void *entry); 56 | 57 | int is_valid_ipv4(const char *ipv4); 58 | int is_valid_ipv6(const char *ipv6); 59 | 60 | #ifdef __cplusplus 61 | } 62 | #endif 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /rockspec/lua-resty-libr3-0.6-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-libr3" 2 | version = "0.6-0" 3 | source = { 4 | url = "git://github.com/iresty/lua-resty-libr3", 5 | tag = "v0.6", 6 | } 7 | 8 | description = { 9 | summary = "This is a libr3 implementation base on FFI for Lua-Openresty", 10 | homepage = "https://github.com/iresty/lua-resty-libr3", 11 | license = "Apache License 2.0", 12 | maintainer = "Yuansheng Wang " 13 | } 14 | 15 | build = { 16 | type = "make", 17 | build_variables = { 18 | CFLAGS="$(CFLAGS)", 19 | LIBFLAG="$(LIBFLAG)", 20 | LUA_LIBDIR="$(LUA_LIBDIR)", 21 | LUA_BINDIR="$(LUA_BINDIR)", 22 | LUA_INCDIR="$(LUA_INCDIR)", 23 | LUA="$(LUA)", 24 | }, 25 | install_variables = { 26 | INST_PREFIX="$(PREFIX)", 27 | INST_BINDIR="$(BINDIR)", 28 | INST_LIBDIR="$(LIBDIR)", 29 | INST_LUADIR="$(LUADIR)", 30 | INST_CONFDIR="$(CONFDIR)", 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /rockspec/lua-resty-libr3-0.7-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-libr3" 2 | version = "0.7-0" 3 | source = { 4 | url = "git://github.com/iresty/lua-resty-libr3", 5 | tag = "v0.7", 6 | } 7 | 8 | description = { 9 | summary = "This is a libr3 implementation base on FFI for Lua-Openresty", 10 | homepage = "https://github.com/iresty/lua-resty-libr3", 11 | license = "Apache License 2.0", 12 | maintainer = "Yuansheng Wang " 13 | } 14 | 15 | build = { 16 | type = "make", 17 | build_variables = { 18 | CFLAGS="$(CFLAGS)", 19 | LIBFLAG="$(LIBFLAG)", 20 | LUA_LIBDIR="$(LUA_LIBDIR)", 21 | LUA_BINDIR="$(LUA_BINDIR)", 22 | LUA_INCDIR="$(LUA_INCDIR)", 23 | LUA="$(LUA)", 24 | }, 25 | install_variables = { 26 | INST_PREFIX="$(PREFIX)", 27 | INST_BINDIR="$(BINDIR)", 28 | INST_LIBDIR="$(LIBDIR)", 29 | INST_LUADIR="$(LUADIR)", 30 | INST_CONFDIR="$(CONFDIR)", 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /rockspec/lua-resty-libr3-0.8-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-libr3" 2 | version = "0.8-0" 3 | source = { 4 | url = "git://github.com/iresty/lua-resty-libr3", 5 | tag = "v0.8", 6 | } 7 | 8 | description = { 9 | summary = "This is a libr3 implementation base on FFI for Lua-Openresty", 10 | homepage = "https://github.com/iresty/lua-resty-libr3", 11 | license = "Apache License 2.0", 12 | maintainer = "Yuansheng Wang " 13 | } 14 | 15 | build = { 16 | type = "make", 17 | build_variables = { 18 | CFLAGS="$(CFLAGS)", 19 | LIBFLAG="$(LIBFLAG)", 20 | LUA_LIBDIR="$(LUA_LIBDIR)", 21 | LUA_BINDIR="$(LUA_BINDIR)", 22 | LUA_INCDIR="$(LUA_INCDIR)", 23 | LUA="$(LUA)", 24 | }, 25 | install_variables = { 26 | INST_PREFIX="$(PREFIX)", 27 | INST_BINDIR="$(BINDIR)", 28 | INST_LIBDIR="$(LIBDIR)", 29 | INST_LUADIR="$(LUADIR)", 30 | INST_CONFDIR="$(CONFDIR)", 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /rockspec/lua-resty-libr3-0.9-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-libr3" 2 | version = "0.9-0" 3 | source = { 4 | url = "git://github.com/iresty/lua-resty-libr3", 5 | tag = "v0.9", 6 | } 7 | 8 | description = { 9 | summary = "This is a libr3 implementation base on FFI for Lua-Openresty", 10 | homepage = "https://github.com/iresty/lua-resty-libr3", 11 | license = "Apache License 2.0", 12 | maintainer = "Yuansheng Wang " 13 | } 14 | 15 | build = { 16 | type = "make", 17 | build_variables = { 18 | CFLAGS="$(CFLAGS)", 19 | LIBFLAG="$(LIBFLAG)", 20 | LUA_LIBDIR="$(LUA_LIBDIR)", 21 | LUA_BINDIR="$(LUA_BINDIR)", 22 | LUA_INCDIR="$(LUA_INCDIR)", 23 | LUA="$(LUA)", 24 | }, 25 | install_variables = { 26 | INST_PREFIX="$(PREFIX)", 27 | INST_BINDIR="$(BINDIR)", 28 | INST_LIBDIR="$(LIBDIR)", 29 | INST_LUADIR="$(LUADIR)", 30 | INST_CONFDIR="$(CONFDIR)", 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /rockspec/lua-resty-libr3-1.0-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-libr3" 2 | version = "1.0-0" 3 | source = { 4 | url = "git://github.com/iresty/lua-resty-libr3", 5 | tag = "v1.0", 6 | } 7 | 8 | description = { 9 | summary = "This is a libr3 implementation base on FFI for Lua-Openresty", 10 | homepage = "https://github.com/iresty/lua-resty-libr3", 11 | license = "Apache License 2.0", 12 | maintainer = "Yuansheng Wang " 13 | } 14 | 15 | build = { 16 | type = "make", 17 | build_variables = { 18 | CFLAGS="$(CFLAGS)", 19 | LIBFLAG="$(LIBFLAG)", 20 | LUA_LIBDIR="$(LUA_LIBDIR)", 21 | LUA_BINDIR="$(LUA_BINDIR)", 22 | LUA_INCDIR="$(LUA_INCDIR)", 23 | LUA="$(LUA)", 24 | }, 25 | install_variables = { 26 | INST_PREFIX="$(PREFIX)", 27 | INST_BINDIR="$(BINDIR)", 28 | INST_LIBDIR="$(LIBDIR)", 29 | INST_LUADIR="$(LUADIR)", 30 | INST_CONFDIR="$(CONFDIR)", 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /rockspec/lua-resty-libr3-1.1-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-libr3" 2 | version = "1.1-0" 3 | source = { 4 | url = "git://github.com/iresty/lua-resty-libr3", 5 | tag = "v1.1", 6 | } 7 | 8 | description = { 9 | summary = "This is a libr3 implementation base on FFI for Lua-Openresty", 10 | homepage = "https://github.com/iresty/lua-resty-libr3", 11 | license = "Apache License 2.0", 12 | maintainer = "Yuansheng Wang " 13 | } 14 | 15 | build = { 16 | type = "make", 17 | build_variables = { 18 | CFLAGS="$(CFLAGS)", 19 | LIBFLAG="$(LIBFLAG)", 20 | LUA_LIBDIR="$(LUA_LIBDIR)", 21 | LUA_BINDIR="$(LUA_BINDIR)", 22 | LUA_INCDIR="$(LUA_INCDIR)", 23 | LUA="$(LUA)", 24 | }, 25 | install_variables = { 26 | INST_PREFIX="$(PREFIX)", 27 | INST_BINDIR="$(BINDIR)", 28 | INST_LIBDIR="$(LIBDIR)", 29 | INST_LUADIR="$(LUADIR)", 30 | INST_CONFDIR="$(CONFDIR)", 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /rockspec/lua-resty-libr3-1.2-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-libr3" 2 | version = "1.2-0" 3 | source = { 4 | url = "git://github.com/iresty/lua-resty-libr3", 5 | tag = "v1.2", 6 | } 7 | 8 | description = { 9 | summary = "This is a libr3 implementation base on FFI for Lua-Openresty", 10 | homepage = "https://github.com/iresty/lua-resty-libr3", 11 | license = "Apache License 2.0", 12 | maintainer = "Yuansheng Wang " 13 | } 14 | 15 | build = { 16 | type = "make", 17 | build_variables = { 18 | CFLAGS="$(CFLAGS)", 19 | LIBFLAG="$(LIBFLAG)", 20 | LUA_LIBDIR="$(LUA_LIBDIR)", 21 | LUA_BINDIR="$(LUA_BINDIR)", 22 | LUA_INCDIR="$(LUA_INCDIR)", 23 | LUA="$(LUA)", 24 | }, 25 | install_variables = { 26 | INST_PREFIX="$(PREFIX)", 27 | INST_BINDIR="$(BINDIR)", 28 | INST_LIBDIR="$(LIBDIR)", 29 | INST_LUADIR="$(LUADIR)", 30 | INST_CONFDIR="$(CONFDIR)", 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /t/R3.pm: -------------------------------------------------------------------------------- 1 | package t::R3; 2 | 3 | use lib 'lib'; 4 | 5 | use Test::Nginx::Socket::Lua::Stream -Base; 6 | 7 | repeat_each(2); 8 | log_level('info'); 9 | no_long_string(); 10 | no_shuffle(); 11 | 12 | my $pwd = `pwd`; 13 | chomp $pwd; 14 | 15 | add_block_preprocessor(sub { 16 | my ($block) = @_; 17 | 18 | my $http_config = $block->http_config // <<_EOC_; 19 | lua_package_path '$pwd/t/lib/?.lua;$pwd/lib/?.lua;\$prefix/?.lua;;'; 20 | lua_package_cpath '$pwd/?.so;;'; 21 | _EOC_ 22 | 23 | $block->set_value("http_config", $http_config); 24 | }); 25 | 26 | 1; 27 | -------------------------------------------------------------------------------- /t/collectgarbage.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use t::R3 'no_plan'; 4 | 5 | run_tests(); 6 | 7 | __DATA__ 8 | 9 | === TEST 1: create r3 object by `new` 10 | --- config 11 | location /foo { 12 | content_by_lua_block { 13 | local function test() 14 | -- foo handler 15 | local function foo(params) 16 | ngx.say("foo: ", require("ljson").encode(params)) 17 | end 18 | 19 | -- r3 router 20 | local r3router = require "resty.r3" 21 | local r = r3router.new({ 22 | { 23 | path = [[/foo/{:\w+}/{:\w+}]], 24 | host = "localhost", 25 | handler = foo, 26 | } 27 | }) 28 | 29 | r:compile() 30 | 31 | local ok = r:dispatch(ngx.var.uri, 32 | {method = ngx.req.get_method(), host = "localhost"}) 33 | 34 | collectgarbage() 35 | 36 | if ok then 37 | ngx.say("hit") 38 | else 39 | ngx.say("not hit") 40 | end 41 | end 42 | 43 | for i = 1, 20 do 44 | test() 45 | end 46 | } 47 | } 48 | --- request 49 | GET /foo/idv/namev 50 | --- no_error_log 51 | [error] 52 | 53 | 54 | 55 | === TEST 2: insert route with method 56 | --- config 57 | location /foo { 58 | content_by_lua_block { 59 | local t = {} 60 | local function test() 61 | local function foo(params) 62 | t.foo = (t.foo or 0) + 1 63 | end 64 | 65 | local function bar(params) 66 | t.bar = (t.bar or 0) + 1 67 | end 68 | 69 | -- r3 router 70 | local r3router = require "resty.r3" 71 | local r = r3router.new() 72 | 73 | -- insert route 74 | r:get("/foo", bar) 75 | r:get("/foo/{id}/{name}", foo) 76 | r:post("/foo/{id}/{name}", bar) 77 | 78 | -- don't forget! 79 | r:compile() 80 | 81 | local ok = r:dispatch(ngx.var.uri, ngx.req.get_method()) 82 | 83 | if ok then 84 | return "hit" 85 | end 86 | 87 | return "not hit" 88 | end 89 | 90 | for i=1,100 do 91 | local res = test() 92 | t[res] = (t[res] or 0) + 1 93 | collectgarbage() 94 | end 95 | ngx.say(require("ljson").encode(t)) 96 | } 97 | } 98 | --- request 99 | GET /foo/a/b 100 | --- no_error_log 101 | [error] 102 | --- response_body 103 | {"foo":100,"hit":100} 104 | -------------------------------------------------------------------------------- /t/disable-uri-cache-opt.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use t::R3 'no_plan'; 4 | 5 | run_tests(); 6 | 7 | __DATA__ 8 | 9 | === TEST 1: no pattern 10 | --- config 11 | location /foo { 12 | content_by_lua_block { 13 | -- foo handler 14 | local function foo(params) 15 | ngx.say("foo: ", require("ljson").encode(params)) 16 | end 17 | local function bar(params) 18 | ngx.say("bar: ", require("ljson").encode(params)) 19 | end 20 | 21 | -- r3 router 22 | local r3router = require "resty.r3" 23 | local r = r3router.new(nil, {disable_path_cache_opt = true}) 24 | 25 | -- insert route 26 | r:get("/foo", foo) 27 | r:get("/bar", bar) 28 | 29 | r:compile() 30 | 31 | local ok = r:dispatch(ngx.var.uri, ngx.req.get_method()) 32 | if ok then 33 | ngx.say("hit") 34 | else 35 | ngx.say("not hit") 36 | end 37 | 38 | ok = r:dispatch("/bar", ngx.req.get_method()) 39 | if ok then 40 | ngx.say("hit") 41 | else 42 | ngx.say("not hit") 43 | end 44 | } 45 | } 46 | --- request 47 | GET /foo 48 | --- no_error_log 49 | [error] 50 | --- response_body 51 | foo: [] 52 | hit 53 | bar: [] 54 | hit 55 | 56 | 57 | 58 | === TEST 2: method dispatch2, specified a table to store the parameter 59 | --- config 60 | location /t { 61 | content_by_lua_block { 62 | -- foo handler 63 | local bar_param_tab 64 | local function bar(params) 65 | bar_param_tab = params 66 | end 67 | 68 | -- r3 router 69 | local r3router = require "resty.r3" 70 | local r = r3router.new({ 71 | {method = {"GET"}, path = "/bar", handler = bar} 72 | }, 73 | { 74 | disable_path_cache_opt = true 75 | } 76 | ) 77 | 78 | r:compile() 79 | 80 | local param_tab = {} 81 | local ok = r:dispatch2(param_tab, "/bar", ngx.req.get_method()) 82 | if ok then 83 | ngx.say("hit") 84 | else 85 | ngx.say("not hit") 86 | end 87 | 88 | ngx.say("passed parameter table: ", param_tab == bar_param_tab) 89 | } 90 | } 91 | --- request 92 | GET /t 93 | --- no_error_log 94 | [error] 95 | --- response_body 96 | hit 97 | passed parameter table: true 98 | 99 | 100 | 101 | === TEST 3: multiple routes: same path, different method 102 | --- config 103 | location /foo { 104 | content_by_lua_block { 105 | -- foo handler 106 | local function post(params) 107 | ngx.say("post: ", require("ljson").encode(params)) 108 | end 109 | local function get(params) 110 | ngx.say("get: ", require("ljson").encode(params)) 111 | end 112 | 113 | -- r3 router 114 | local r3router = require "resty.r3" 115 | local r = r3router.new(nil, {disable_path_cache_opt=false}) 116 | 117 | -- insert route 118 | r:post("/foo", post) 119 | r:get("/foo", get) 120 | 121 | r:compile() 122 | 123 | local ok = r:dispatch("/foo", "GET") 124 | if ok then 125 | ngx.say("hit") 126 | else 127 | ngx.say("not hit") 128 | end 129 | 130 | ok = r:dispatch("/foo", "POST") 131 | if ok then 132 | ngx.say("hit") 133 | else 134 | ngx.say("not hit") 135 | end 136 | 137 | ok = r:dispatch("/foo", "PUT") 138 | if ok then 139 | ngx.say("hit") 140 | else 141 | ngx.say("not hit") 142 | end 143 | } 144 | } 145 | --- request 146 | GET /foo 147 | --- no_error_log 148 | [error] 149 | --- response_body 150 | get: [] 151 | hit 152 | post: [] 153 | hit 154 | not hit 155 | -------------------------------------------------------------------------------- /t/host-url.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use t::R3 'no_plan'; 4 | 5 | run_tests(); 6 | 7 | __DATA__ 8 | 9 | === TEST 1: host + uri 10 | --- config 11 | location /foo { 12 | content_by_lua_block { 13 | -- foo handler 14 | local function foo(params) 15 | ngx.say("foo: ", require("ljson").encode(params)) 16 | end 17 | 18 | -- r3 router 19 | local r3router = require "resty.r3" 20 | local r = r3router.new({ 21 | { 22 | path = [[{domain:[^/]+}/foo/{id:\w+}/{name:\w+}]], 23 | handler = foo, 24 | } 25 | }) 26 | 27 | r:compile() 28 | 29 | for i, domain in ipairs({"localhost", "127.0.0.1", "foo.com", "www.foo.com"}) do 30 | local ok = r:dispatch(domain .. ngx.var.uri, 31 | {method = ngx.req.get_method()}) 32 | if ok then 33 | ngx.say("hit") 34 | else 35 | ngx.say("not hit") 36 | end 37 | end 38 | } 39 | } 40 | --- request 41 | GET /foo/idv/namev 42 | --- no_error_log 43 | [error] 44 | --- response_body 45 | foo: {"domain":"localhost","id":"idv","name":"namev"} 46 | hit 47 | foo: {"domain":"127.0.0.1","id":"idv","name":"namev"} 48 | hit 49 | foo: {"domain":"foo.com","id":"idv","name":"namev"} 50 | hit 51 | foo: {"domain":"www.foo.com","id":"idv","name":"namev"} 52 | hit 53 | -------------------------------------------------------------------------------- /t/host.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use t::R3 'no_plan'; 4 | 5 | run_tests(); 6 | 7 | __DATA__ 8 | 9 | === TEST 1: sanity 10 | --- config 11 | location /foo { 12 | content_by_lua_block { 13 | -- foo handler 14 | local function foo(params) 15 | ngx.say("foo: ", require("ljson").encode(params)) 16 | end 17 | 18 | -- r3 router 19 | local r3router = require "resty.r3" 20 | local r = r3router.new({ 21 | { 22 | path = [[/foo/{:\w+}/{:\w+}]], 23 | host = "localhost", 24 | handler = foo, 25 | }, 26 | { 27 | method = {"GET", "POST"}, 28 | path = [[/bar/{:\w+}/{:\w+}]], 29 | host = "localhost", 30 | handler = foo, 31 | } 32 | }) 33 | 34 | r:compile() 35 | 36 | local ok = r:dispatch(ngx.var.uri, 37 | {method = ngx.req.get_method(), host = "localhost"}) 38 | 39 | if ok then 40 | ngx.say("hit") 41 | else 42 | ngx.say("not hit") 43 | end 44 | } 45 | } 46 | --- request 47 | GET /foo/idv/namev 48 | --- no_error_log 49 | [error] 50 | --- response_body 51 | foo: ["idv","namev"] 52 | hit 53 | 54 | 55 | 56 | === TEST 2: not match (host is different) 57 | --- config 58 | location /foo { 59 | content_by_lua_block { 60 | -- foo handler 61 | local function foo(params) 62 | ngx.say("foo: ", require("ljson").encode(params)) 63 | end 64 | 65 | -- r3 router 66 | local r3router = require "resty.r3" 67 | local r = r3router.new({ 68 | { 69 | path = [[/foo/{:\w+}/{:\w+}]], 70 | host = "not_found_host", 71 | handler = foo, 72 | } 73 | }) 74 | 75 | r:compile() 76 | local ok = r:dispatch(ngx.var.uri, 77 | {method = ngx.req.get_method(), host = "localhost"}) 78 | 79 | if ok then 80 | ngx.say("hit") 81 | else 82 | ngx.say("not hit") 83 | end 84 | } 85 | } 86 | --- request 87 | GET /foo/idv/namev 88 | --- no_error_log 89 | [error] 90 | --- response_body 91 | not hit 92 | 93 | 94 | 95 | === TEST 3: not match (host is different) 96 | --- config 97 | location /foo { 98 | content_by_lua_block { 99 | -- foo handler 100 | local function foo(params) 101 | ngx.say("foo: ", require("ljson").encode(params)) 102 | end 103 | 104 | -- r3 router 105 | local r3router = require "resty.r3" 106 | local r = r3router.new({ 107 | { 108 | path = [[/foo/idv/namev]], 109 | host = "localhost", 110 | handler = foo, 111 | } 112 | }) 113 | 114 | r:compile() 115 | 116 | local ok = r:dispatch(ngx.var.uri, 117 | {method = ngx.req.get_method(), 118 | host = "not_found_host"}) 119 | if ok then 120 | ngx.say("hit") 121 | else 122 | ngx.say("not hit") 123 | end 124 | } 125 | } 126 | --- request 127 | GET /foo/idv/namev 128 | --- no_error_log 129 | [error] 130 | --- response_body 131 | not hit 132 | -------------------------------------------------------------------------------- /t/lib/ljson.lua: -------------------------------------------------------------------------------- 1 | local ngx_null = ngx.null 2 | local tostring = tostring 3 | local byte = string.byte 4 | local gsub = string.gsub 5 | local sort = table.sort 6 | local pairs = pairs 7 | local ipairs = ipairs 8 | local concat = table.concat 9 | 10 | local ok, new_tab = pcall(require, "table.new") 11 | if not ok then 12 | new_tab = function (narr, nrec) return {} end 13 | end 14 | 15 | local _M = {} 16 | 17 | local metachars = { 18 | ['\t'] = '\\t', 19 | ["\\"] = "\\\\", 20 | ['"'] = '\\"', 21 | ['\r'] = '\\r', 22 | ['\n'] = '\\n', 23 | } 24 | 25 | local function encode_str(s) 26 | -- XXX we will rewrite this when string.buffer is implemented 27 | -- in LuaJIT 2.1 because string.gsub cannot be JIT compiled. 28 | return gsub(s, '["\\\r\n\t]', metachars) 29 | end 30 | 31 | local function is_arr(t) 32 | local exp = 1 33 | for k, _ in pairs(t) do 34 | if k ~= exp then 35 | return nil 36 | end 37 | exp = exp + 1 38 | end 39 | return exp - 1 40 | end 41 | 42 | local encode 43 | 44 | encode = function (v) 45 | if v == nil or v == ngx_null then 46 | return "null" 47 | end 48 | 49 | local typ = type(v) 50 | if typ == 'string' then 51 | return '"' .. encode_str(v) .. '"' 52 | end 53 | 54 | if typ == 'number' or typ == 'boolean' then 55 | return tostring(v) 56 | end 57 | 58 | if typ == 'table' then 59 | local n = is_arr(v) 60 | if n then 61 | local bits = new_tab(n, 0) 62 | for i, elem in ipairs(v) do 63 | bits[i] = encode(elem) 64 | end 65 | return "[" .. concat(bits, ",") .. "]" 66 | end 67 | 68 | local keys = {} 69 | local i = 0 70 | for key, _ in pairs(v) do 71 | i = i + 1 72 | keys[i] = key 73 | end 74 | sort(keys) 75 | 76 | local bits = new_tab(0, i) 77 | i = 0 78 | for _, key in ipairs(keys) do 79 | i = i + 1 80 | bits[i] = encode(key) .. ":" .. encode(v[key]) 81 | end 82 | return "{" .. concat(bits, ",") .. "}" 83 | end 84 | 85 | return '"<' .. typ .. '>"' 86 | end 87 | _M.encode = encode 88 | 89 | return _M 90 | -------------------------------------------------------------------------------- /t/remote-addr.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use t::R3 'no_plan'; 4 | 5 | run_tests(); 6 | 7 | __DATA__ 8 | 9 | === TEST 1: not match: 127.0.0.1 =~ no_remote_addr 10 | --- config 11 | location /foo { 12 | content_by_lua_block { 13 | -- foo handler 14 | local function foo(params) 15 | ngx.say("foo: ", require("ljson").encode(params)) 16 | end 17 | 18 | -- r3 router 19 | local r3router = require "resty.r3" 20 | local r = r3router.new({ 21 | { 22 | path = [[/foo/{:\w+}/{:\w+}]], 23 | remote_addr = "127.0.0.1", 24 | handler = foo, 25 | } 26 | }) 27 | 28 | r:compile() 29 | 30 | local ok = r:dispatch(ngx.var.uri, { 31 | method = ngx.req.get_method() 32 | }) 33 | 34 | if ok then 35 | ngx.say("hit") 36 | else 37 | ngx.say("not hit") 38 | end 39 | } 40 | } 41 | --- request 42 | GET /foo/idv/namev 43 | --- no_error_log 44 | [error] 45 | --- response_body 46 | not hit 47 | 48 | 49 | 50 | === TEST 2: match: 127.0.0.1 =~ 127.0.0.1 51 | --- config 52 | location /foo { 53 | content_by_lua_block { 54 | -- foo handler 55 | local function foo(params) 56 | ngx.say("foo: ", require("ljson").encode(params)) 57 | end 58 | 59 | -- r3 router 60 | local r3router = require "resty.r3" 61 | local r = r3router.new({ 62 | { 63 | path = [[/foo/{:\w+}/{:\w+}]], 64 | remote_addr = "127.0.0.1", 65 | handler = foo, 66 | } 67 | }) 68 | 69 | r:compile() 70 | 71 | local ok = r:dispatch(ngx.var.uri, { 72 | method = ngx.req.get_method(), 73 | remote_addr = "127.0.0.1", 74 | }) 75 | 76 | if ok then 77 | ngx.say("hit") 78 | else 79 | ngx.say("not hit") 80 | end 81 | } 82 | } 83 | --- request 84 | GET /foo/idv/namev 85 | --- no_error_log 86 | [error] 87 | --- response_body 88 | foo: ["idv","namev"] 89 | hit 90 | 91 | 92 | 93 | === TEST 3: match: 127.0.0.0/24 =~ 127.0.0.1 94 | --- config 95 | location /foo { 96 | content_by_lua_block { 97 | -- foo handler 98 | local function foo(params) 99 | ngx.say("foo: ", require("ljson").encode(params)) 100 | end 101 | 102 | -- r3 router 103 | local r3router = require "resty.r3" 104 | local r = r3router.new({ 105 | { 106 | path = [[/foo/{:\w+}/{:\w+}]], 107 | remote_addr = "127.0.0.0/24", 108 | handler = foo, 109 | } 110 | }) 111 | 112 | r:compile() 113 | 114 | local ok = r:dispatch(ngx.var.uri, { 115 | method = ngx.req.get_method(), 116 | remote_addr = "127.0.0.1", 117 | }) 118 | 119 | if ok then 120 | ngx.say("hit") 121 | else 122 | ngx.say("not hit") 123 | end 124 | } 125 | } 126 | --- request 127 | GET /foo/idv/namev 128 | --- no_error_log 129 | [error] 130 | --- response_body 131 | foo: ["idv","namev"] 132 | hit 133 | 134 | 135 | 136 | === TEST 4: not match: 127.0.0.0/24 =~ 127.0.1.1 137 | --- config 138 | location /foo { 139 | content_by_lua_block { 140 | -- foo handler 141 | local function foo(params) 142 | ngx.say("foo: ", require("ljson").encode(params)) 143 | end 144 | 145 | -- r3 router 146 | local r3router = require "resty.r3" 147 | local r = r3router.new({ 148 | { 149 | path = [[/foo/{:\w+}/{:\w+}]], 150 | remote_addr = "127.0.0.0/24", 151 | handler = foo, 152 | } 153 | }) 154 | 155 | r:compile() 156 | 157 | local ok = r:dispatch(ngx.var.uri, { 158 | method = ngx.req.get_method(), 159 | remote_addr = "127.0.1.1", 160 | }) 161 | 162 | if ok then 163 | ngx.say("hit") 164 | else 165 | ngx.say("not hit") 166 | end 167 | } 168 | } 169 | --- request 170 | GET /foo/idv/namev 171 | --- no_error_log 172 | [error] 173 | --- response_body 174 | not hit 175 | 176 | 177 | 178 | === TEST 5: static uri: 127.0.0.1 =~ 127.0.0.1 179 | --- config 180 | location /foo { 181 | content_by_lua_block { 182 | -- foo handler 183 | local function foo(params) 184 | ngx.say("foo: ", require("ljson").encode(params)) 185 | end 186 | 187 | -- r3 router 188 | local r3router = require "resty.r3" 189 | local r = r3router.new({ 190 | { 191 | path = [[/foo/idv/namev]], 192 | remote_addr = "127.0.0.1", 193 | handler = foo, 194 | } 195 | }) 196 | 197 | r:compile() 198 | 199 | local ok = r:dispatch(ngx.var.uri, { 200 | method = ngx.req.get_method(), 201 | remote_addr = "127.0.0.1", 202 | }) 203 | 204 | if ok then 205 | ngx.say("hit") 206 | else 207 | ngx.say("not hit") 208 | end 209 | } 210 | } 211 | --- request 212 | GET /foo/idv/namev 213 | --- no_error_log 214 | [error] 215 | --- response_body 216 | foo: [] 217 | hit 218 | 219 | 220 | 221 | === TEST 6: static uri: 127.0.0.0/24 =~ 127.0.0.33 222 | --- config 223 | location /foo { 224 | content_by_lua_block { 225 | -- foo handler 226 | local function foo(params) 227 | ngx.say("foo: ", require("ljson").encode(params)) 228 | end 229 | 230 | -- r3 router 231 | local r3router = require "resty.r3" 232 | local r = r3router.new({ 233 | { 234 | path = [[/foo/idv/namev]], 235 | remote_addr = "127.0.0.0/24", 236 | handler = foo, 237 | } 238 | }) 239 | 240 | r:compile() 241 | 242 | local ok = r:dispatch(ngx.var.uri, { 243 | method = ngx.req.get_method(), 244 | remote_addr = "127.0.0.33", 245 | }) 246 | 247 | if ok then 248 | ngx.say("hit") 249 | else 250 | ngx.say("not hit") 251 | end 252 | } 253 | } 254 | --- request 255 | GET /foo/idv/namev 256 | --- no_error_log 257 | [error] 258 | --- response_body 259 | foo: [] 260 | hit 261 | 262 | 263 | 264 | === TEST 7: static uri: 127.0.0.0/24 =~ 127.0.1.33 265 | --- config 266 | location /foo { 267 | content_by_lua_block { 268 | -- foo handler 269 | local function foo(params) 270 | ngx.say("foo: ", require("ljson").encode(params)) 271 | end 272 | 273 | -- r3 router 274 | local r3router = require "resty.r3" 275 | local r = r3router.new({ 276 | { 277 | path = [[/foo/idv/namev]], 278 | remote_addr = "127.0.0.0/24", 279 | handler = foo, 280 | } 281 | }) 282 | 283 | r:compile() 284 | 285 | local ok = r:dispatch(ngx.var.uri, { 286 | method = ngx.req.get_method(), 287 | remote_addr = "127.0.1.33", 288 | }) 289 | 290 | if ok then 291 | ngx.say("hit") 292 | else 293 | ngx.say("not hit") 294 | end 295 | } 296 | } 297 | --- request 298 | GET /foo/idv/namev 299 | --- no_error_log 300 | [error] 301 | --- response_body 302 | not hit 303 | 304 | 305 | 306 | === TEST 8: static uri: 127.0.0.1 =~ 127.0.0.2 307 | --- config 308 | location /foo { 309 | content_by_lua_block { 310 | -- foo handler 311 | local function foo(params) 312 | ngx.say("foo: ", require("ljson").encode(params)) 313 | end 314 | 315 | -- r3 router 316 | local r3router = require "resty.r3" 317 | local r = r3router.new({ 318 | { 319 | path = [[/foo/idv/namev]], 320 | remote_addr = "127.0.0.1", 321 | handler = foo, 322 | } 323 | }) 324 | 325 | r:compile() 326 | 327 | local ok = r:dispatch(ngx.var.uri, { 328 | method = ngx.req.get_method(), 329 | remote_addr = "127.0.0.2", 330 | }) 331 | 332 | if ok then 333 | ngx.say("hit") 334 | else 335 | ngx.say("not hit") 336 | end 337 | } 338 | } 339 | --- request 340 | GET /foo/idv/namev 341 | --- no_error_log 342 | [error] 343 | --- response_body 344 | not hit 345 | 346 | 347 | 348 | === TEST 9: invalid remote address 349 | --- config 350 | location /foo { 351 | content_by_lua_block { 352 | -- foo handler 353 | local function foo(params) 354 | ngx.say("foo: ", require("ljson").encode(params)) 355 | end 356 | 357 | -- r3 router 358 | local r3router = require "resty.r3" 359 | local r = r3router.new({ 360 | { 361 | path = [[/foo/idv/namev]], 362 | remote_addr = "127.0.0.1", 363 | handler = foo, 364 | } 365 | }) 366 | 367 | r:compile() 368 | 369 | local invalid_addr = {nil, "127.0.0.333"} 370 | for i = 1, 2 do 371 | local ok = r:dispatch(ngx.var.uri, { 372 | method = ngx.req.get_method(), 373 | remote_addr = invalid_addr[i], 374 | }) 375 | 376 | if ok then 377 | ngx.say("hit") 378 | else 379 | ngx.say("not hit") 380 | end 381 | end 382 | } 383 | } 384 | --- request 385 | GET /foo/idv/namev 386 | --- no_error_log 387 | [error] 388 | --- response_body 389 | not hit 390 | not hit 391 | 392 | 393 | 394 | === TEST 10: invalid remote address (new router) 395 | --- config 396 | location /foo { 397 | content_by_lua_block { 398 | -- foo handler 399 | local function foo(params) 400 | ngx.say("foo: ", require("ljson").encode(params)) 401 | end 402 | 403 | -- r3 router 404 | local r3router = require "resty.r3" 405 | local r = r3router.new({ 406 | { 407 | path = [[/foo/idv/namev]], 408 | remote_addr = "127.0.0.333", 409 | handler = foo, 410 | } 411 | }) 412 | 413 | r:compile() 414 | } 415 | } 416 | --- request 417 | GET /foo/idv/namev 418 | --- error_log 419 | invalid argument remote_addr 420 | --- error_code: 500 421 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use t::R3 'no_plan'; 4 | 5 | repeat_each(10); 6 | run_tests(); 7 | 8 | __DATA__ 9 | 10 | === TEST 1: sanity 11 | --- config 12 | location /foo { 13 | content_by_lua_block { 14 | -- foo handler 15 | local function foo(params) 16 | ngx.say("foo: ", require("ljson").encode(params)) 17 | end 18 | local function bar(params) 19 | ngx.say("bar: ", require("ljson").encode(params)) 20 | end 21 | 22 | -- r3 router 23 | local r3router = require "resty.r3" 24 | local r = r3router.new() 25 | 26 | -- insert route 27 | r:get("/", function(params) 28 | ngx.say("hello r3!") 29 | end) 30 | 31 | r:get("/foo", bar) 32 | r:get("/foo/{id}/{name}", foo) 33 | r:post("/foo/{id}/{name}", bar) 34 | 35 | -- don't forget! 36 | r:compile() 37 | 38 | local ok = r:dispatch(ngx.var.uri, ngx.req.get_method()) 39 | if ok then 40 | ngx.say("hit") 41 | else 42 | ngx.say("not hit") 43 | end 44 | } 45 | } 46 | --- request 47 | GET /foo/a/b 48 | --- no_error_log 49 | [error] 50 | --- response_body 51 | foo: {"id":"a","name":"b"} 52 | hit 53 | 54 | 55 | 56 | === TEST 2: anonymous variable 57 | --- config 58 | location /foo { 59 | content_by_lua_block { 60 | -- foo handler 61 | local function foo(params) 62 | ngx.say("foo: ", require("ljson").encode(params)) 63 | end 64 | local function bar(params) 65 | ngx.say("bar: ", require("ljson").encode(params)) 66 | end 67 | 68 | -- r3 router 69 | local r3router = require "resty.r3" 70 | local r = r3router.new() 71 | 72 | -- insert route 73 | r:get("/", function(params) 74 | ngx.say("hello r3!") 75 | end) 76 | 77 | r:get("/foo", bar) 78 | r:get([[/foo/{:\w+}/{:\w+}]], foo) 79 | 80 | r:compile() 81 | 82 | local ok = r:dispatch(ngx.var.uri, ngx.req.get_method()) 83 | if ok then 84 | ngx.say("hit") 85 | else 86 | ngx.say("not hit") 87 | end 88 | } 89 | } 90 | --- request 91 | GET /foo/idv/namev 92 | --- no_error_log 93 | [error] 94 | --- response_body 95 | foo: ["idv","namev"] 96 | hit 97 | 98 | 99 | 100 | === TEST 3: create r3 object with arguments 101 | --- config 102 | location /foo { 103 | content_by_lua_block { 104 | -- foo handler 105 | local function foo(params) 106 | ngx.say("foo: ", require("ljson").encode(params)) 107 | end 108 | 109 | -- r3 router 110 | local r3router = require "resty.r3" 111 | local r = r3router.new({ 112 | {method = {"GET"}, path = [[/foo/{:\w+}/{:\w+}]], handler = foo} 113 | }) 114 | 115 | r:compile() 116 | 117 | local ok = r:dispatch(ngx.var.uri, ngx.req.get_method()) 118 | if ok then 119 | ngx.say("hit") 120 | else 121 | ngx.say("not hit") 122 | end 123 | } 124 | } 125 | --- request 126 | GET /foo/idv/namev 127 | --- no_error_log 128 | [error] 129 | --- response_body 130 | foo: ["idv","namev"] 131 | hit 132 | 133 | 134 | 135 | === TEST 4: create r3 object with arguments 136 | --- config 137 | location /foo { 138 | content_by_lua_block { 139 | -- foo handler 140 | local function foo(params) 141 | ngx.say("foo: ", require("ljson").encode(params)) 142 | end 143 | 144 | -- r3 router 145 | local r3router = require "resty.r3" 146 | local r = r3router.new({ 147 | {path = [[/foo/{:\w+}/{:\w+}]], handler = foo} 148 | }) 149 | 150 | -- don't forget! 151 | r:compile() 152 | 153 | local ok = r:dispatch(ngx.var.uri, ngx.req.get_method()) 154 | if ok then 155 | ngx.say("hit") 156 | else 157 | ngx.say("not hit") 158 | end 159 | } 160 | } 161 | --- request 162 | GET /foo/idv/namev 163 | --- no_error_log 164 | [error] 165 | --- response_body 166 | foo: ["idv","namev"] 167 | hit 168 | 169 | 170 | 171 | === TEST 5: insert router 172 | --- config 173 | location /foo { 174 | content_by_lua_block { 175 | -- foo handler 176 | local function foo(params) 177 | ngx.say("foo: ", require("ljson").encode(params)) 178 | end 179 | 180 | -- r3 router 181 | local r3router = require "resty.r3" 182 | local r = r3router.new() 183 | 184 | r:insert_route([[/foo/{:\w+}/{:\w+}]], foo) 185 | 186 | -- don't forget! 187 | r:compile() 188 | 189 | local ok = r:dispatch(ngx.var.uri, ngx.req.get_method()) 190 | if ok then 191 | ngx.say("hit") 192 | else 193 | ngx.say("not hit") 194 | end 195 | } 196 | } 197 | --- request 198 | GET /foo/idv/namev 199 | --- no_error_log 200 | [error] 201 | --- response_body 202 | foo: ["idv","namev"] 203 | hit 204 | 205 | 206 | 207 | === TEST 6: free 208 | --- config 209 | location /foo { 210 | content_by_lua_block { 211 | -- foo handler 212 | local function foo(params) 213 | ngx.say("foo: ", require("ljson").encode(params)) 214 | end 215 | 216 | -- r3 router 217 | local r3router = require "resty.r3" 218 | local r = r3router.new() 219 | 220 | r:free() 221 | r:free() -- double free 222 | 223 | ngx.say("all done") 224 | } 225 | } 226 | --- request 227 | GET /foo/idv/namev 228 | --- no_error_log 229 | [error] 230 | --- response_body 231 | all done 232 | 233 | 234 | 235 | === TEST 7: no pattern 236 | --- config 237 | location /foo { 238 | content_by_lua_block { 239 | -- foo handler 240 | local function foo(params) 241 | ngx.say("foo: ", require("ljson").encode(params)) 242 | end 243 | local function bar(params) 244 | ngx.say("bar: ", require("ljson").encode(params)) 245 | end 246 | 247 | -- r3 router 248 | local r3router = require "resty.r3" 249 | local r = r3router.new() 250 | 251 | -- insert route 252 | r:get("/foo", foo) 253 | r:get("/bar", bar) 254 | 255 | r:compile() 256 | 257 | local ok = r:dispatch(ngx.var.uri, ngx.req.get_method()) 258 | if ok then 259 | ngx.say("hit") 260 | else 261 | ngx.say("not hit") 262 | end 263 | 264 | ok = r:dispatch("/bar", ngx.req.get_method()) 265 | if ok then 266 | ngx.say("hit") 267 | else 268 | ngx.say("not hit") 269 | end 270 | } 271 | } 272 | --- request 273 | GET /foo 274 | --- no_error_log 275 | [error] 276 | --- response_body 277 | foo: [] 278 | hit 279 | bar: [] 280 | hit 281 | 282 | 283 | 284 | === TEST 8: method dispatch2, specified a table to store the parameter 285 | --- config 286 | location /t { 287 | content_by_lua_block { 288 | -- foo handler 289 | local bar_param_tab 290 | local function bar(params) 291 | bar_param_tab = params 292 | end 293 | 294 | -- r3 router 295 | local r3router = require "resty.r3" 296 | local r = r3router.new({ 297 | {method = {"GET"}, path = "/bar", handler = bar} 298 | }) 299 | 300 | r:compile() 301 | 302 | local param_tab = {} 303 | local ok = r:dispatch2(param_tab, "/bar", ngx.req.get_method()) 304 | if ok then 305 | ngx.say("hit") 306 | else 307 | ngx.say("not hit") 308 | end 309 | 310 | ngx.say("passed parameter table: ", param_tab == bar_param_tab) 311 | } 312 | } 313 | --- request 314 | GET /t 315 | --- no_error_log 316 | [error] 317 | --- response_body 318 | hit 319 | passed parameter table: true 320 | 321 | 322 | 323 | === TEST 9: multiple routes: same uri, different method 324 | --- config 325 | location /foo { 326 | content_by_lua_block { 327 | -- foo handler 328 | local function post(params) 329 | ngx.say("post: ", require("ljson").encode(params)) 330 | end 331 | local function get(params) 332 | ngx.say("get: ", require("ljson").encode(params)) 333 | end 334 | 335 | -- r3 router 336 | local r3router = require "resty.r3" 337 | local r = r3router.new() 338 | 339 | -- insert route 340 | r:post("/foo", post) 341 | r:get("/foo", get) 342 | 343 | r:compile() 344 | 345 | local ok = r:dispatch("/foo", "GET") 346 | if ok then 347 | ngx.say("hit") 348 | else 349 | ngx.say("not hit") 350 | end 351 | 352 | ok = r:dispatch("/foo", "POST") 353 | if ok then 354 | ngx.say("hit") 355 | else 356 | ngx.say("not hit") 357 | end 358 | 359 | ok = r:dispatch("/foo", "PUT") 360 | if ok then 361 | ngx.say("hit") 362 | else 363 | ngx.say("not hit") 364 | end 365 | } 366 | } 367 | --- request 368 | GET /foo 369 | --- no_error_log 370 | [error] 371 | --- response_body 372 | get: [] 373 | hit 374 | post: [] 375 | hit 376 | not hit 377 | 378 | 379 | 380 | === TEST 10: no method in dispatch 381 | --- config 382 | location /foo { 383 | content_by_lua_block { 384 | -- foo handler 385 | local function foo(params) 386 | ngx.say("foo: ", require("ljson").encode(params)) 387 | end 388 | local function bar(params) 389 | ngx.say("bar: ", require("ljson").encode(params)) 390 | end 391 | 392 | -- r3 router 393 | local r3router = require "resty.r3" 394 | local r = r3router.new() 395 | 396 | -- insert route 397 | r:get("/", function(params) 398 | ngx.say("hello r3!") 399 | end) 400 | 401 | r:get("/foo", bar) 402 | r:get("/foo/{id}/{name}", foo) 403 | r:post("/foo/{id}/{name}", bar) 404 | 405 | -- don't forget! 406 | r:compile() 407 | 408 | local ok = r:dispatch(ngx.var.uri) 409 | if ok then 410 | ngx.say("hit") 411 | else 412 | ngx.say("not hit") 413 | end 414 | 415 | local ok = r:dispatch2(nil, ngx.var.uri) 416 | if ok then 417 | ngx.say("hit") 418 | else 419 | ngx.say("not hit") 420 | end 421 | } 422 | } 423 | --- request 424 | GET /foo/a/b 425 | --- no_error_log 426 | [error] 427 | --- response_body 428 | foo: {"id":"a","name":"b"} 429 | hit 430 | foo: null 431 | hit 432 | 433 | 434 | 435 | === TEST 11: dispatch: invalid path 436 | --- config 437 | location /foo { 438 | content_by_lua_block { 439 | -- foo handler 440 | local function foo(params) 441 | ngx.say("foo: ", require("ljson").encode(params)) 442 | end 443 | 444 | -- r3 router 445 | local r3router = require "resty.r3" 446 | local r = r3router.new() 447 | 448 | r:get("/foo", foo) 449 | 450 | -- don't forget! 451 | r:compile() 452 | 453 | r:dispatch(nil) 454 | } 455 | } 456 | --- request 457 | GET /foo/a/b 458 | --- error_code: 500 459 | --- error_log 460 | invalid argument path 461 | 462 | 463 | 464 | === TEST 12: dispatch: invalid path 465 | --- config 466 | location /foo { 467 | content_by_lua_block { 468 | -- foo handler 469 | local function foo(params) 470 | ngx.say("foo: ", require("ljson").encode(params)) 471 | end 472 | 473 | -- r3 router 474 | local r3router = require "resty.r3" 475 | local r = r3router.new() 476 | 477 | r:get("/foo", foo) 478 | 479 | -- don't forget! 480 | r:compile() 481 | 482 | r:dispatch2({}, nil) 483 | } 484 | } 485 | --- request 486 | GET /foo/a/b 487 | --- error_code: 500 488 | --- error_log 489 | invalid argument path 490 | 491 | 492 | 493 | === TEST 13: new: invalid path 494 | --- config 495 | location /foo { 496 | content_by_lua_block { 497 | -- r3 router 498 | local r3router = require "resty.r3" 499 | local r = r3router.new({ 500 | {method = {"GET"}, path = nil, handler = foo} 501 | }) 502 | 503 | r:get("/foo", bar) 504 | } 505 | } 506 | --- request 507 | GET /foo/a/b 508 | --- error_code: 500 509 | --- error_log 510 | invalid argument path 511 | 512 | 513 | 514 | === TEST 14: insert route: invalid path 515 | --- config 516 | location /foo { 517 | content_by_lua_block { 518 | -- foo handler 519 | local function foo(params) 520 | ngx.say("foo: ", require("ljson").encode(params)) 521 | end 522 | 523 | -- r3 router 524 | local r3router = require "resty.r3" 525 | local r = r3router.new({ 526 | {method = {"GET"}, path = "/foo", handler = foo} 527 | }) 528 | 529 | r:get(nil, bar) 530 | } 531 | } 532 | --- request 533 | GET /foo/a/b 534 | --- error_code: 500 535 | --- error_log 536 | invalid argument path 537 | 538 | 539 | 540 | === TEST 15: new: invalid hanlder 541 | --- config 542 | location /foo { 543 | content_by_lua_block { 544 | -- r3 router 545 | local r3router = require "resty.r3" 546 | local r = r3router.new({ 547 | {method = {"GET"}, path = "/foo", handler = nil} 548 | }) 549 | 550 | r:get("/foo", bar) 551 | } 552 | } 553 | --- request 554 | GET /foo/a/b 555 | --- error_code: 500 556 | --- error_log 557 | invalid argument handler 558 | 559 | 560 | 561 | === TEST 16: new: invalid hanlder 562 | --- config 563 | location /foo { 564 | content_by_lua_block { 565 | -- foo handler 566 | local function foo(params) 567 | ngx.say("foo: ", require("ljson").encode(params)) 568 | end 569 | 570 | -- r3 router 571 | local r3router = require "resty.r3" 572 | local r = r3router.new({ 573 | {method = {"GET"}, path = "/foo", handler = foo} 574 | }) 575 | 576 | r:get("/foo", nil) 577 | } 578 | } 579 | --- request 580 | GET /foo/a/b 581 | --- error_code: 500 582 | --- error_log 583 | invalid argument handler 584 | 585 | 586 | 587 | === TEST 17: any uri 588 | --- config 589 | location /foo { 590 | content_by_lua_block { 591 | local function bar(params) 592 | ngx.say("bar: ", require("ljson").encode(params)) 593 | end 594 | 595 | -- r3 router 596 | local r3router = require "resty.r3" 597 | local r = r3router.new() 598 | 599 | -- insert route 600 | r:get("{:.*}", function(params) 601 | ngx.say("hello r3!") 602 | end) 603 | 604 | r:get("/foo", bar) 605 | 606 | -- don't forget! 607 | r:compile() 608 | 609 | for i = 1, 3 do 610 | local ok = r:dispatch(ngx.var.uri, ngx.req.get_method()) 611 | if ok then 612 | ngx.say("hit") 613 | else 614 | ngx.say("not hit") 615 | end 616 | end 617 | 618 | local ok = r:dispatch("/", ngx.req.get_method()) 619 | if ok then 620 | ngx.say("hit") 621 | else 622 | ngx.say("not hit") 623 | end 624 | } 625 | } 626 | --- request 627 | GET /foo/a/b 628 | --- no_error_log 629 | [error] 630 | --- response_body 631 | hello r3! 632 | hit 633 | hello r3! 634 | hit 635 | hello r3! 636 | hit 637 | hello r3! 638 | hit 639 | 640 | 641 | 642 | === TEST 18: use `/foo{:/?}` both to match `/foo` and `/foo/` 643 | --- config 644 | location /t { 645 | content_by_lua_block { 646 | -- foo handler 647 | local function foo(params) 648 | ngx.say("foo: ", require("ljson").encode(params)) 649 | end 650 | 651 | -- r3 router 652 | local r3router = require "resty.r3" 653 | local r = r3router.new() 654 | 655 | -- insert route 656 | r:get("/foo{:/?}", foo) 657 | 658 | -- don't forget! 659 | r:compile() 660 | 661 | local ok = r:dispatch("/foo", ngx.req.get_method()) 662 | if ok then 663 | ngx.say("hit") 664 | else 665 | ngx.say("not hit") 666 | end 667 | 668 | ok = r:dispatch("/foo/", ngx.req.get_method()) 669 | if ok then 670 | ngx.say("hit") 671 | else 672 | ngx.say("not hit") 673 | end 674 | } 675 | } 676 | --- request 677 | GET /t 678 | --- no_error_log 679 | [error] 680 | --- response_body 681 | foo: [""] 682 | hit 683 | foo: ["/"] 684 | hit 685 | 686 | 687 | 688 | === TEST 19: multiple similar rules 689 | --- config 690 | location /t { 691 | content_by_lua_block { 692 | -- foo handler 693 | local function foo(params) 694 | ngx.say("foo: ", require("ljson").encode(params)) 695 | end 696 | 697 | -- r3 router 698 | local r3router = require "resty.r3" 699 | local r = r3router.new({ 700 | {host = "web2.lvh.me", path = "/", handler = foo}, 701 | {host = "web2.lvh.me", path = "/{:.*}", handler = foo}, 702 | {host = "web2.lvh.me", path = "/v2", handler = foo}, 703 | {host = "web2.lvh.me", path = "/v2/", handler = foo}, 704 | {host = "web2.lvh.me", path = "/v2/{:.*}", handler = foo}, 705 | }) 706 | 707 | -- insert route 708 | r:get("/foo{:/?}", foo) 709 | 710 | -- don't forget! 711 | r:compile() 712 | 713 | for _, url in ipairs({"/", "/v", "/v2", "/v2/", "/v2/v"}) do 714 | local ok = r:dispatch(url, {host = "web2.lvh.me"}) 715 | if ok then 716 | ngx.say("hit") 717 | else 718 | ngx.say("not hit") 719 | end 720 | end 721 | } 722 | } 723 | --- request 724 | GET /t 725 | --- no_error_log 726 | [error] 727 | --- response_body 728 | foo: [] 729 | hit 730 | foo: ["v"] 731 | hit 732 | foo: [] 733 | hit 734 | foo: [] 735 | hit 736 | foo: ["v2/v"] 737 | hit 738 | 739 | 740 | 741 | === TEST 20: bug: multiple similar rules (todo) 742 | --- config 743 | location /t { 744 | content_by_lua_block { 745 | local r = require("html.r3_obj").get() 746 | 747 | -- r:compile() 748 | 749 | for _, url in ipairs({"/v2/web2.txt"}) do 750 | local ok = r:dispatch(url, {host = "web2.lvh.me"}) 751 | if ok then 752 | ngx.say("hit") 753 | else 754 | ngx.say("not hit") 755 | end 756 | end 757 | } 758 | } 759 | --- user_files 760 | >>> ../html/r3_obj.lua 761 | 762 | -- foo handler 763 | local function foo(params) 764 | ngx.say("foo: ", require("cjson").encode(params)) 765 | end 766 | 767 | -- r3 router 768 | local r3router = require "resty.r3" 769 | local routes = { 770 | {host = "web2.lvh.me", path = "/{:.*}", handler = foo}, 771 | {host = "web2.lvh.me", path = "/v2/{:.*}", handler = foo}, 772 | } 773 | local r = r3router.new(routes) 774 | 775 | r:compile() 776 | 777 | local _M = { 778 | obj = r, 779 | routes = rotes, 780 | } 781 | 782 | function _M.get() 783 | return _M.obj 784 | end 785 | 786 | return _M 787 | 788 | --- request 789 | GET /t 790 | --- no_error_log 791 | [error] 792 | --- response_body 793 | foo: ["v2\/web2.txt"] 794 | hit 795 | 796 | 797 | 798 | === TEST 21: missing method 799 | --- config 800 | location /foo { 801 | content_by_lua_block { 802 | -- foo handler 803 | local function foo(params) 804 | ngx.say("foo: ", require("ljson").encode(params)) 805 | end 806 | 807 | -- r3 router 808 | local r3router = require "resty.r3" 809 | local r = r3router.new({ 810 | {path = "/hello", method = {"GET"}, handler = foo}, 811 | }) 812 | 813 | -- don't forget! 814 | r:compile() 815 | 816 | local ok = r:dispatch("/hello", ngx.req.get_method()) 817 | if ok then 818 | ngx.say("hit") 819 | else 820 | ngx.say("not hit") 821 | end 822 | 823 | ok = r:dispatch("/hello", {}) 824 | if ok then 825 | ngx.say("hit") 826 | else 827 | ngx.say("not hit") 828 | end 829 | } 830 | } 831 | --- request 832 | GET /foo/a/b 833 | --- no_error_log 834 | [error] 835 | --- response_body 836 | foo: [] 837 | hit 838 | not hit 839 | -------------------------------------------------------------------------------- /t/wildcard-host.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | 3 | use t::R3 'no_plan'; 4 | 5 | run_tests(); 6 | 7 | __DATA__ 8 | 9 | === TEST 1: sanity 10 | --- config 11 | location /foo { 12 | content_by_lua_block { 13 | -- foo handler 14 | local function foo(params) 15 | ngx.say("foo: ", require("ljson").encode(params)) 16 | end 17 | 18 | -- r3 router 19 | local r3router = require "resty.r3" 20 | local r = r3router.new({ 21 | { 22 | path = [[/foo/{:\w+}/{:\w+}]], 23 | host = "*.foo.com", 24 | handler = foo, 25 | } 26 | }) 27 | 28 | r:compile() 29 | 30 | local ok = r:dispatch(ngx.var.uri, { 31 | method = ngx.req.get_method(), 32 | host = "www.foo.com" 33 | }) 34 | 35 | if ok then 36 | ngx.say("hit") 37 | else 38 | ngx.say("not hit") 39 | end 40 | } 41 | } 42 | --- request 43 | GET /foo/idv/namev 44 | --- no_error_log 45 | [error] 46 | --- response_body 47 | foo: ["idv","namev"] 48 | hit 49 | 50 | 51 | 52 | === TEST 2: not match 53 | --- config 54 | location /foo { 55 | content_by_lua_block { 56 | -- foo handler 57 | local function foo(params) 58 | ngx.say("foo: ", require("ljson").encode(params)) 59 | end 60 | 61 | -- r3 router 62 | local r3router = require "resty.r3" 63 | local r = r3router.new({ 64 | { 65 | path = [[/foo/{:\w+}/{:\w+}]], 66 | host = "*.foo.com", 67 | handler = foo, 68 | } 69 | }) 70 | 71 | r:compile() 72 | local ok = r:dispatch(ngx.var.uri,{ 73 | method = ngx.req.get_method(), 74 | host = "foo.com" 75 | }) 76 | 77 | if ok then 78 | ngx.say("hit") 79 | else 80 | ngx.say("not hit") 81 | end 82 | } 83 | } 84 | --- request 85 | GET /foo/idv/namev 86 | --- no_error_log 87 | [error] 88 | --- response_body 89 | not hit 90 | 91 | 92 | 93 | === TEST 3: multiple route 94 | --- config 95 | location /foo { 96 | content_by_lua_block { 97 | -- foo handler 98 | local function foo(params) 99 | ngx.say("foo: ", require("ljson").encode(params)) 100 | end 101 | 102 | local function bar(params) 103 | ngx.say("bar: ", require("ljson").encode(params)) 104 | end 105 | 106 | -- r3 router 107 | local r3router = require "resty.r3" 108 | local r = r3router.new({ 109 | { 110 | path = [[/foo/{:\w+}/{:\w+}]], 111 | host = "*.bar.com", 112 | handler = bar, 113 | }, 114 | { 115 | path = [[/bar/{:\w+}/{:\w+}]], 116 | host = "*.foo.com", 117 | handler = bar, 118 | }, 119 | { 120 | path = [[/foo/{:\w+}/{:\w+}]], 121 | host = "*.foo.com", 122 | handler = foo, 123 | } 124 | }) 125 | 126 | r:compile() 127 | local ok = r:dispatch(ngx.var.uri,{ 128 | method = ngx.req.get_method(), 129 | host = "a.b.foo.com" 130 | }) 131 | 132 | if ok then 133 | ngx.say("hit") 134 | else 135 | ngx.say("not hit") 136 | end 137 | } 138 | } 139 | --- request 140 | GET /foo/idv/namev 141 | --- no_error_log 142 | [error] 143 | --- response_body 144 | foo: ["idv","namev"] 145 | hit 146 | 147 | 148 | 149 | === TEST 4: uri hash cache 150 | --- config 151 | location /foo { 152 | content_by_lua_block { 153 | -- foo handler 154 | local function foo(params) 155 | ngx.say("foo: ", require("ljson").encode(params)) 156 | end 157 | 158 | -- r3 router 159 | local r3router = require "resty.r3" 160 | local r = r3router.new({ 161 | { 162 | path = [[/foo/idv/namev]], 163 | host = "*.foo.com", 164 | handler = foo, 165 | } 166 | }) 167 | 168 | r:compile() 169 | 170 | local ok = r:dispatch(ngx.var.uri, { 171 | method = ngx.req.get_method(), 172 | host = "www.foo.com" 173 | }) 174 | 175 | if ok then 176 | ngx.say("hit") 177 | else 178 | ngx.say("not hit") 179 | end 180 | } 181 | } 182 | --- request 183 | GET /foo/idv/namev 184 | --- no_error_log 185 | [error] 186 | --- response_body 187 | foo: [] 188 | hit 189 | 190 | 191 | 192 | === TEST 5: uri hash cache 193 | --- config 194 | location /foo { 195 | content_by_lua_block { 196 | -- foo handler 197 | local function foo(params) 198 | ngx.say("foo: ", require("ljson").encode(params)) 199 | end 200 | 201 | -- r3 router 202 | local r3router = require "resty.r3" 203 | local r = r3router.new({ 204 | { 205 | path = [[/foo/idv/namev]], 206 | host = "*.foo.com", 207 | handler = foo, 208 | } 209 | }) 210 | 211 | r:compile() 212 | 213 | local ok = r:dispatch(ngx.var.uri, { 214 | method = ngx.req.get_method(), 215 | host = ".foo.com" 216 | }) 217 | 218 | if ok then 219 | ngx.say("hit") 220 | else 221 | ngx.say("not hit") 222 | end 223 | } 224 | } 225 | --- request 226 | GET /foo/idv/namev 227 | --- no_error_log 228 | [error] 229 | --- response_body 230 | not hit 231 | --------------------------------------------------------------------------------