├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── coverity.yml ├── .gitignore ├── .travis-ci ├── after_success.sh ├── install.sh ├── script-autotools.sh └── script-cmake.sh ├── .travis.yml ├── CHANGES.md ├── CMakeLists.txt ├── COPYING ├── HACKING.md ├── INSTALL ├── LICENSE ├── Makefile.am ├── README.md ├── autogen.sh ├── bench.html ├── bench_str.csv ├── cmake └── Modules │ ├── FindCheck.cmake │ ├── FindJemalloc.cmake │ ├── FindJudy.cmake │ └── FindPCRE2.cmake ├── config.h.cmake ├── configure.ac ├── dist-debian ├── changelog ├── compat ├── control ├── copyright ├── docs ├── libr3-dev.dirs ├── libr3-dev.install ├── libr3.dirs ├── libr3.install ├── rules └── source │ └── format ├── examples ├── Makefile.am ├── routing.c ├── simple.c └── simple_cpp.cpp ├── gen_route_tests.rb ├── gen_routes.rb ├── include ├── memory.h ├── r3.h ├── r3.hpp ├── r3_gvc.h ├── r3_json.h ├── r3_list.h ├── r3_slug.h └── str_array.h ├── m4 └── .keep ├── package.json ├── php └── r3 │ ├── annotation │ ├── annot.h │ ├── base.c │ ├── lemon │ ├── lemon.c │ ├── lempar.c │ ├── parser.c │ ├── parser.h │ ├── parser.lemon │ ├── parser.out │ ├── scanner.c │ ├── scanner.h │ └── scanner.re │ ├── config.m4 │ ├── ct_helper.c │ ├── ct_helper.h │ ├── hash.c │ ├── hash.h │ ├── php_expandable_mux.c │ ├── php_expandable_mux.h │ ├── php_r3.c │ ├── php_r3.h │ ├── r3_controller.c │ ├── r3_controller.h │ ├── r3_functions.c │ ├── r3_functions.h │ ├── r3_mux.c │ ├── r3_mux.h │ ├── r3_persistent.c │ └── r3_persistent.h ├── r3.pc.in ├── run-benchmark ├── src ├── CMakeLists.txt ├── Makefile.am ├── edge.c ├── gvc.c ├── json.c ├── list.c ├── match_entry.c ├── memory.c ├── node.c ├── r3_debug.h ├── slug.c ├── slug.h ├── str.c ├── str.h └── token.c └── tests ├── CMakeLists.txt ├── Makefile.am ├── bench.c ├── bench.h ├── check_gvc.c ├── check_host.c ├── check_http_scheme.c ├── check_json.c ├── check_remote_addr.c ├── check_routes.c ├── check_routes2.c ├── check_slug.c ├── check_str_array.c └── check_tree.c /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Automated dependency updates. 2 | # 3 | # For configuration options see: 4 | # https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates 5 | version: 2 6 | updates: 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | open-pull-requests-limit: 10 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: [push, pull_request] 3 | 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | autotools: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Prepare 12 | run: | 13 | sudo apt update -qq 14 | sudo apt install -qq check lcov 15 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 16 | - name: Build 17 | run: | 18 | ./autogen.sh 19 | ./configure --enable-check --enable-debug --enable-gcov 20 | make V=1 21 | - name: Install 22 | run: sudo make install 23 | - name: Run tests 24 | run: make check 25 | - name: Collect coverage 26 | run: lcov --capture -d '.' --exclude '/usr*' -o coverage.info 27 | - name: Upload coverage 28 | if: github.repository == 'c9s/r3' 29 | uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6 30 | with: 31 | github-token: ${{ secrets.GITHUB_TOKEN }} 32 | file: coverage.info 33 | format: lcov 34 | 35 | cmake: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - name: Prepare 39 | run: | 40 | sudo apt update -qq 41 | sudo apt install -qq check ninja-build 42 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 43 | - name: Build and test 44 | run: | 45 | mkdir build && cd build 46 | cmake -GNinja .. 47 | ninja -v 48 | ctest --verbose 49 | 50 | sanitizers: 51 | name: ${{ matrix.sanitizer }}-sanitizer [${{ matrix.compiler }}] 52 | runs-on: ubuntu-latest 53 | strategy: 54 | fail-fast: false 55 | matrix: 56 | compiler: [gcc, clang] 57 | sanitizer: [thread, undefined, leak, address] 58 | steps: 59 | - name: Prepare 60 | run: | 61 | sudo apt update -qq 62 | sudo apt install -qq check 63 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 64 | - name: Build 65 | env: 66 | CC: ${{ matrix.compiler }} 67 | run: | 68 | mkdir build && cd build 69 | CFLAGS="-fsanitize=${{ matrix.sanitizer }} -fno-sanitize-recover=all -fno-omit-frame-pointer" cmake .. 70 | VERBOSE=1 make all 71 | - name: Test 72 | run: | 73 | cd build 74 | ctest --verbose 75 | -------------------------------------------------------------------------------- /.github/workflows/coverity.yml: -------------------------------------------------------------------------------- 1 | name: coverity 2 | on: 3 | push: 4 | branches: [2.0] 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | analyze: 11 | if: github.repository == 'c9s/r3' 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 16 | - name: Prepare 17 | run: | 18 | ./autogen.sh 19 | ./configure --enable-check 20 | - uses: vapier/coverity-scan-action@2068473c7bdf8c2fb984a6a40ae76ee7facd7a85 # v1.8.0 21 | with: 22 | project: r3 23 | email: yoanlin93+github@gmail.com 24 | token: ${{ secrets.COVERITY_TOKEN }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | # cmake 4 | CMakeCache.txt 5 | CMakeFiles 6 | CTestTestfile.cmake 7 | Testing 8 | cmake_install.cmake 9 | install_manifest.txt 10 | 11 | # tags 12 | tags 13 | 14 | # autotools 15 | *.a 16 | *.lo 17 | *.la 18 | *.dylib 19 | *.o 20 | 21 | Makefile 22 | Makefile.in 23 | aclocal.m4 24 | autom4te.cache 25 | compile 26 | config.guess 27 | config.log 28 | config.status 29 | config.sub 30 | configure 31 | depcomp 32 | gumbo.pc 33 | gumbo_test 34 | gumbo_test.log 35 | gumbo_test.trs 36 | install-sh 37 | libtool 38 | ltmain.sh 39 | m4/ 40 | missing 41 | test-driver 42 | test-suite.log 43 | config.h 44 | 45 | tests/*.log 46 | tests/*.trs 47 | configure.scan 48 | autoscan.log 49 | .libs 50 | .deps 51 | r3.pc 52 | stamp-h1 53 | tests/bench_str.csv 54 | tests/check_host 55 | 56 | config.h.in 57 | examples/simple 58 | examples/simple_cpp 59 | -------------------------------------------------------------------------------- /.travis-ci/after_success.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ x$COVERALLS == xyes ]; then 4 | coveralls --exclude php --exclude 3rdparty 5 | fi 6 | -------------------------------------------------------------------------------- /.travis-ci/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | apt-get update -qq 4 | apt-get install -qq \ 5 | autoconf \ 6 | automake \ 7 | build-essential \ 8 | check \ 9 | clang \ 10 | cmake \ 11 | graphviz-dev \ 12 | libjemalloc-dev \ 13 | libpcre2-dev \ 14 | libtool \ 15 | ninja-build \ 16 | pkg-config 17 | 18 | if [ x$COVERALLS == xyes ]; then 19 | pip install cpp-coveralls 20 | fi 21 | 22 | if [ x$VALGRIND == xyes ]; then 23 | apt-get install valgrind 24 | fi 25 | -------------------------------------------------------------------------------- /.travis-ci/script-autotools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ev 4 | 5 | ./autogen.sh 6 | ./configure --enable-check $CONFIGURE_OPTION 7 | make V=1 8 | make install 9 | if [ x$VALGRIND == xyes ]; then 10 | make check 11 | else 12 | make check V=1 13 | fi 14 | 15 | # XXX: tracing memory leak, disabled for some mystery reason for automake... 16 | #if [ x$VALGRIND == xyes && x$DEBUG == xyes ]; then 17 | # valgrind ./tests/check_* -v --trace-children=yes --show-leak-kinds=full --leak-check=full 18 | #fi 19 | -------------------------------------------------------------------------------- /.travis-ci/script-cmake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ev 4 | 5 | mkdir build && cd build 6 | cmake -GNinja .. 7 | ninja -v 8 | ctest 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: required 3 | 4 | services: 5 | - docker 6 | 7 | git: 8 | depth: 1 9 | 10 | matrix: 11 | include: 12 | - compiler: gcc 13 | env: 14 | - TYPE=autotools 15 | - CONFIGURE_OPTION='--enable-debug --enable-gcov --with-malloc=jemalloc' 16 | - COVERALLS=yes 17 | - VALGRIND=no 18 | - DEBUG=yes 19 | - CC=gcc 20 | - CXX=g++ 21 | - compiler: gcc 22 | env: 23 | - TYPE=autotools 24 | - CONFIGURE_OPTION='--enable-debug --enable-gcov' 25 | - COVERALLS=yes 26 | - VALGRIND=yes 27 | - DEBUG=yes 28 | - CC=gcc 29 | - CXX=g++ 30 | - compiler: clang 31 | env: 32 | - TYPE=autotools 33 | - CONFIGURE_OPTION='--enable-debug --enable-gcov' 34 | - COVERALLS=yes 35 | - VALGRIND=yes 36 | - DEBUG=yes 37 | - CC=clang 38 | - CXX=clang++ 39 | - compiler: gcc 40 | env: 41 | - TYPE=cmake 42 | - CONFIGURE_OPTION='--enable-debug --enable-gcov' 43 | - COVERALLS=yes 44 | - VALGRIND=yes 45 | - DEBUG=yes 46 | - CC=gcc 47 | - CXX=g++ 48 | #power Jobs Added 49 | - compiler: gcc 50 | arch: pc64le 51 | env: 52 | - TYPE=autotools 53 | - CONFIGURE_OPTION='--enable-debug --enable-gcov --with-malloc=jemalloc' 54 | - COVERALLS=yes 55 | - VALGRIND=no 56 | - DEBUG=yes 57 | - CC=gcc 58 | - CXX=g++ 59 | - compiler: gcc 60 | arch: ppc64le 61 | env: 62 | - TYPE=autotools 63 | - CONFIGURE_OPTION='--enable-debug --enable-gcov' 64 | - COVERALLS=yes 65 | - VALGRIND=yes 66 | - DEBUG=yes 67 | - CC=gcc 68 | - CXX=g++ 69 | - compiler: clang 70 | arch: ppc64le 71 | env: 72 | - TYPE=autotools 73 | - CONFIGURE_OPTION='--enable-debug --enable-gcov' 74 | - COVERALLS=yes 75 | - VALGRIND=yes 76 | - DEBUG=yes 77 | - CC=clang 78 | - CXX=clang++ 79 | - compiler: gcc 80 | arch: ppc64le 81 | env: 82 | - TYPE=cmake 83 | - CONFIGURE_OPTION='--enable-debug --enable-gcov' 84 | - COVERALLS=yes 85 | - VALGRIND=yes 86 | - DEBUG=yes 87 | - CC=gcc 88 | - CXX=g++ 89 | 90 | before_install: 91 | - docker run -d 92 | --name build 93 | -v $(pwd):/travis 94 | -e "CONFIGURE_OPTION=$CONFIGURE_OPTION" 95 | -e "COVERALLS=$COVERALLS" 96 | -e "VALGRIND=$VALGRIND" 97 | -e "DEBUG=$DEBUG" 98 | -e "CC=$CC" 99 | -e "CXX=$CXX" 100 | ubuntu:16.04 101 | tail -f /dev/null 102 | - docker ps 103 | 104 | install: 105 | - docker exec -t build bash -c "cd /travis && .travis-ci/install.sh" 106 | 107 | script: 108 | - docker exec -t build bash -c "cd /travis && .travis-ci/script-$TYPE.sh" 109 | 110 | after_success: 111 | - docker exec -t build bash -c "cd /travis && .travis-ci/after_success.sh" 112 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | by Yo-An Lin 4 | 5 | 6 | ### 2.0 - Wed Nov 11 11:08:22 2015 7 | 8 | - Renamed node/edge struct to R3Node and R3Edge 9 | 10 | 11 | ### 1.3.3 - Sat Jun 28 00:53:48 2014 12 | 13 | - Fix graphviz generator. 14 | 15 | 16 | ### 1.3.2 - Sat Jun 28 00:54:22 2014 17 | 18 | - `HAVE_STRNDUP` and `HAVE_STRDUP` definition fix 19 | 20 | ### 1.3.0 - Tue Jun 3 18:47:14 2014 21 | 22 | - Added Incorrect slug syntax warnings 23 | - Added error message support for pcre/pcre-jit compile 24 | - Added JSON encode support for the tree structure 25 | - Improved Graphivz Related Functions 26 | - More failing test cases 27 | 28 | ### 1.2.1 - Tue May 27 21:16:13 2014 29 | 30 | - Bug fixes. 31 | - Function declaration improvement. 32 | - pkg-config flags update (r3.pc) 33 | 34 | ### 1.2 - Fri May 23 23:30:11 2014 35 | 36 | - Added simple pattern optimization. 37 | - Clean up. 38 | - Bug fixes. 39 | 40 | ### 0.9999 - Mon May 19 10:03:41 2014 41 | 42 | API changes: 43 | 44 | 1. Removed the `route` argument from `r3_tree_insert_pathl_ex`: 45 | 46 | node * r3_tree_insert_pathl_ex(node *tree, char *path, int path_len, void * data); 47 | 48 | This reduce the interface complexity, e.g., 49 | 50 | r3_tree_insert_path(n, "/user2/{id:\\d+}", &var2); 51 | 52 | 2. The original `r3_tree_insert_pathl_ex` has been moved to `r3_tree_insert_pathl_ex` as a private API. 53 | 54 | 3. Moved `r3_tree_matchl` to `r3_tree_matchl` since it require the length of the path string. 55 | 56 | m = r3_tree_matchl( n , "/foo", strlen("/foo"), entry); 57 | 58 | 4. Added `r3_tree_match` for users to match a path without the length of the path string. 59 | 60 | m = r3_tree_match( n , "/foo", entry); 61 | 62 | 5. Added `r3_tree_match_entry` for users want to match a `match_entry`, which is just a macro to simplify the use: 63 | 64 | 65 | #define r3_tree_match_entry(n, entry) r3_tree_matchl(n, entry->path, entry->path_len, entry) 66 | 67 | 68 | 6. Please note that A path that is inserted by `r3_tree_insert_route` can only be matched by `r3_tree_match_route`. 69 | 70 | 7. Added `r3_` prefix to `route` related methods. 71 | 72 | 73 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(r3 VERSION 2.0.0) 3 | 4 | list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules) 5 | set(CMAKE_C_STANDARD 99) 6 | 7 | find_package(Check) 8 | find_package(PCRE2 REQUIRED) 9 | 10 | include(CheckSymbolExists) 11 | include(CheckIncludeFile) 12 | check_symbol_exists(strdup string.h HAVE_STRDUP) 13 | check_symbol_exists(strndup string.h HAVE_STRNDUP) 14 | check_include_file(stdbool.h HAVE_STDBOOL_H) 15 | configure_file(config.h.cmake config.h) 16 | 17 | add_subdirectory(src) 18 | 19 | install( 20 | FILES 21 | include/memory.h 22 | include/r3.h 23 | include/r3_list.h 24 | include/r3_slug.h 25 | include/r3_gvc.h 26 | include/r3_json.h 27 | include/str_array.h 28 | include/r3.hpp 29 | DESTINATION include/r3) 30 | 31 | # Configure substitutions for r3.pc. The variables set here must match the 32 | # @@ in r3.pc.in. 33 | set(prefix ${CMAKE_INSTALL_PREFIX}) 34 | set(exec_prefix ${prefix}) 35 | set(includedir ${prefix}/include) 36 | set(libdir ${prefix}/lib) 37 | set(PACKAGE_VERSION ${PROJECT_VERSION}) 38 | configure_file(r3.pc.in r3.pc @ONLY) 39 | install( 40 | FILES 41 | ${PROJECT_BINARY_DIR}/r3.pc 42 | DESTINATION lib/pkgconfig) 43 | 44 | if(CHECK_FOUND) 45 | enable_testing() 46 | add_subdirectory(tests) 47 | else() 48 | message(STATUS "Skipping unit tests, Check library not found!") 49 | endif() 50 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 yoanlin93@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | Related Articles 2 | ===================== 3 | 4 | Judy Array 5 | ------------ 6 | 7 | - A 10-MINUTE DESCRIPTION OF HOW JUDY ARRAYS WORK AND WHY THEY ARE SO FAST 8 | - Hashtables vs Judy Arrays, Round 1 9 | - This Hash Table Is Faster Than a Judy Array 10 | - A Performance Comparison of Judy to Hash Tables 11 | - Faster (sometimes) Associative Arrays with Node.js 12 | 13 | Jemalloc 14 | ------------ 15 | https://github.com/jemalloc/jemalloc/wiki/Getting-Started 16 | 17 | 18 | Prefix Tree / Radix Tree 19 | ------------------------- 20 | - Radix Tree: 21 | - Radix Tree in Linux Kernel: 22 | - Radix Tree in Linux Kernel (source code): 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 yoanlin93@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS=src . examples 2 | 3 | if ENABLE_CHECK 4 | SUBDIRS += tests 5 | endif 6 | 7 | lib_LTLIBRARIES = libr3.la 8 | libr3_la_SOURCES = 9 | libr3_la_LIBADD = src/libr3core.la 10 | libr3_la_LDFLAGS = 11 | 12 | AM_CFLAGS=$(DEPS_CFLAGS) $(GVC_DEPS_CFLAGS) -I$(top_builddir) -I$(top_builddir)/include -Wall -std=c99 13 | AM_LDFLAGS=$(DEPS_LIBS) $(GVC_DEPS_LIBS) 14 | 15 | 16 | ACLOCAL_AMFLAGS=-I m4 17 | 18 | if ENABLE_DEBUG 19 | AM_CFLAGS += -ggdb -fprofile-arcs -ftest-coverage 20 | else 21 | AM_CFLAGS += -O2 22 | endif 23 | 24 | if USE_JEMALLOC 25 | AM_LDFLAGS += -ljemalloc 26 | endif 27 | 28 | r3_includedir = $(includedir)/r3 29 | r3_include_HEADERS = \ 30 | include/memory.h \ 31 | include/r3.h \ 32 | include/r3_list.h \ 33 | include/r3_slug.h \ 34 | include/r3_gvc.h \ 35 | include/r3_json.h \ 36 | include/str_array.h \ 37 | include/r3.hpp \ 38 | $(NULL) 39 | 40 | pkgconfigdir = $(libdir)/pkgconfig 41 | pkgconfig_DATA = r3.pc 42 | 43 | EXTRA_DIST = \ 44 | autogen.sh \ 45 | bench.html \ 46 | demo.c \ 47 | gen_routes.rb \ 48 | HACKING.md \ 49 | LICENSE \ 50 | README.md \ 51 | $(NULL) 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | R3 2 | ================ 3 | 4 | [![Build Status](https://travis-ci.org/c9s/r3.svg?branch=2.0)](https://travis-ci.org/c9s/r3) 5 | 6 | [![Coverage Status](https://coveralls.io/repos/c9s/r3/badge.svg)](https://coveralls.io/r/c9s/r3) 7 | 8 | R3 is an URL router library with high performance, thus, it's implemented in C. 9 | It compiles your R3Route paths into a prefix trie. 10 | 11 | By using the prefix tree constructed in the start-up time, you can dispatch 12 | the path to the controller with high efficiency. 13 | 14 | 15 | 16 | Requirement 17 | ----------------------- 18 | 19 | ### Build Requirement 20 | 21 | * autoconf 22 | * automake 23 | * check 24 | * pkg-config 25 | 26 | ### Runtime Requirement 27 | 28 | * pcre2 29 | * (optional) graphviz version 2.38.0 (20140413.2041) 30 | * (optional) libjson-c-dev 31 | 32 | Pattern Syntax 33 | ----------------------- 34 | 35 | /blog/post/{id} use [^/]+ regular expression by default. 36 | /blog/post/{id:\d+} use `\d+` regular expression instead of default. 37 | 38 | 39 | API 40 | ------------------------ 41 | 42 | ```c 43 | #include 44 | 45 | // create a router tree with 10 children capacity (this capacity can grow dynamically) 46 | R3Node *n = r3_tree_create(10); 47 | 48 | int route_data = 3; 49 | 50 | // insert the R3Route path into the router tree 51 | r3_tree_insert_path(n, "/bar", &route_data); // ignore the length of path 52 | 53 | r3_tree_insert_pathl(n, "/zoo", strlen("/zoo"), &route_data ); 54 | r3_tree_insert_pathl(n, "/foo/bar", strlen("/foo/bar"), &route_data ); 55 | 56 | r3_tree_insert_pathl(n ,"/post/{id}", strlen("/post/{id}") , &route_data ); 57 | 58 | r3_tree_insert_pathl(n, "/user/{id:\\d+}", strlen("/user/{id:\\d+}"), &route_data ); 59 | 60 | 61 | // if you want to catch error, you may call the extended path function for insertion 62 | int data = 10; 63 | char *errstr = NULL; 64 | R3Node *ret = r3_tree_insert_pathl_ex(n, "/foo/{name:\\d{5}", strlen("/foo/{name:\\d{5}"), NULL, &data, &errstr); 65 | if (ret == NULL) { 66 | // failed insertion 67 | printf("error: %s\n", errstr); 68 | free(errstr); // errstr is created from `asprintf`, so you have to free it manually. 69 | } 70 | 71 | 72 | // let's compile the tree! 73 | char *errstr = NULL; 74 | int err = r3_tree_compile(n, &errstr); 75 | if (err != 0) { 76 | // fail 77 | printf("error: %s\n", errstr); 78 | free(errstr); // errstr is created from `asprintf`, so you have to free it manually. 79 | } 80 | 81 | 82 | // dump the compiled tree 83 | r3_tree_dump(n, 0); 84 | 85 | // match a route 86 | R3Node *matched_node = r3_tree_matchl(n, "/foo/bar", strlen("/foo/bar"), NULL); 87 | if (matched_node) { 88 | int ret = *( (int*) matched_node->data ); 89 | } 90 | 91 | // release the tree 92 | r3_tree_free(n); 93 | ``` 94 | 95 | 96 | **Capture Dynamic Variables** 97 | 98 | If you want to capture the variables from regular expression, you will need to 99 | create a `match_entry` object and pass the object to `r3_tree_matchl` function, 100 | the catched variables will be pushed into the match entry structure: 101 | 102 | ```c 103 | match_entry * entry = match_entry_create("/foo/bar"); 104 | 105 | // free the match entry 106 | match_entry_free(entry); 107 | ``` 108 | 109 | And you can even specify the request method restriction: 110 | 111 | ```c 112 | entry->request_method = METHOD_GET; 113 | entry->request_method = METHOD_POST; 114 | entry->request_method = METHOD_GET | METHOD_POST; 115 | ``` 116 | 117 | When using `match_entry`, you may match the R3Route with `r3_tree_match_entry` function: 118 | 119 | ```c 120 | R3Node * matched_node = r3_tree_match_entry(n, entry); 121 | ``` 122 | 123 | 124 | 125 | 126 | **Release Memory** 127 | 128 | To release the memory, you may call `r3_tree_free(R3Node *tree)` to release the whole tree structure, 129 | `node*`, `edge*`, `route*` objects that were inserted into the tree will be freed. 130 | 131 | 132 | 133 | 134 | 135 | 136 | ### Routing with conditions 137 | 138 | ```c 139 | // create a router tree with 10 children capacity (this capacity can grow dynamically) 140 | n = r3_tree_create(10); 141 | 142 | int route_data = 3; 143 | 144 | // insert the R3Route path into the router tree 145 | r3_tree_insert_routel(n, METHOD_GET | METHOD_POST, "/blog/post", sizeof("/blog/post") - 1, &route_data ); 146 | 147 | char *errstr = NULL; 148 | int err = r3_tree_compile(n, &errstr); 149 | if (err != 0) { 150 | // fail 151 | printf("error: %s\n", errstr); 152 | free(errstr); // errstr is created from `asprintf`, so you have to free it manually. 153 | } 154 | 155 | 156 | // in your http server handler 157 | 158 | // create the match entry for capturing dynamic variables. 159 | match_entry * entry = match_entry_create("/blog/post"); 160 | entry->request_method = METHOD_GET; 161 | 162 | 163 | R3Route *matched_R3Route = r3_tree_match_route(n, entry); 164 | matched_route->data; // get the data from matched route 165 | 166 | // free the objects at the end 167 | match_entry_free(entry); 168 | r3_tree_free(n); 169 | ``` 170 | 171 | Slug 172 | ----------------------- 173 | A slug is a placeholder, which captures the string from the URL as a variable. 174 | Slugs will be compiled into regular expression patterns. 175 | 176 | Slugs without patterns (like `/user/{userId}`) will be compiled into the `[^/]+` pattern. 177 | 178 | To specify the pattern of a slug, you may write a colon to separate the slug name and the pattern: 179 | 180 | "/user/{userId:\\d+}" 181 | 182 | The above R3Route will use `\d+` as its pattern. 183 | 184 | 185 | Optimization 186 | ----------------------- 187 | Simple regular expressions are optimized through a regexp pattern to opcode 188 | translator, which translates simple patterns into small & fast scanners. 189 | 190 | By using this method, r3 reduces the matching overhead of pcre2 library. 191 | 192 | Optimized patterns are: `[a-z]+`, `[0-9]+`, `\d+`, `\w+`, `[^/]+`, `[^-]+` or `.*`. 193 | 194 | Slugs without specified regular expression will be compiled into the `[^/]+` pattern. therefore, it's optimized too. 195 | 196 | Complex regular expressions will still use libpcre2 to match URL (partially). 197 | 198 | 199 | Performance 200 | ----------------------- 201 | The routing benchmark from stevegraham/rails' PR : 202 | 203 | omg 10462.0 (±6.7%) i/s - 52417 in 5.030416s 204 | 205 | And here is the result of the router journey: 206 | 207 | omg 9932.9 (±4.8%) i/s - 49873 in 5.033452s 208 | 209 | r3 uses the same R3Route path data for benchmarking, and here is the benchmark: 210 | 211 | 3 runs, 5000000 iterations each run, finished in 1.308894 seconds 212 | 11460057.83 i/sec 213 | 214 | 215 | ### The Route Paths Of Benchmark 216 | 217 | The R3Route path generator is from : 218 | 219 | ```ruby 220 | #!/usr/bin/env ruby 221 | arr = ["foo", "bar", "baz", "qux", "quux", "corge", "grault", "garply"] 222 | paths = arr.permutation(3).map { |a| "/#{a.join '/'}" } 223 | paths.each do |path| 224 | puts "r3_tree_insert_path(n, \"#{path}\", NULL);" 225 | end 226 | ``` 227 | 228 | Function prefix mapping 229 | ----------------------- 230 | 231 | |Function Prefix |Description | 232 | |------------------|------------------------------------------------------------------------------------| 233 | |`r3_tree_*` |Tree related operations, which require a node to operate a whole tree | 234 | |`r3_node_*` |Single node related operations, which do not go through its own children or parent. | 235 | |`r3_edge_*` |Edge related operations | 236 | |`r3_route_*` |Route related operations, which are needed only when the tree is defined by routes | 237 | |`match_entry_*` |Match entry related operations, a `match_entry` is just like the request parameters | 238 | 239 | 240 | 241 | 242 | Rendering Routes With Graphviz 243 | --------------------------------------- 244 | 245 | The `r3_tree_render_file` API let you render the whole R3Route trie into a image. 246 | 247 | To use graphviz, you need to enable graphviz while you run `configure`: 248 | 249 | 250 | ./configure --enable-graphviz 251 | 252 | 253 | Here is the sample code of generating graph output: 254 | 255 | 256 | ```c 257 | R3Node * n = r3_tree_create(1); 258 | 259 | r3_tree_insert_path(n, "/foo/bar/baz", NULL); 260 | r3_tree_insert_path(n, "/foo/bar/qux", NULL); 261 | r3_tree_insert_path(n, "/foo/bar/quux", NULL); 262 | r3_tree_insert_path(n, "/foo/bar/corge", NULL); 263 | r3_tree_insert_path(n, "/foo/bar/grault", NULL); 264 | r3_tree_insert_path(n, "/garply/grault/foo", NULL); 265 | r3_tree_insert_path(n, "/garply/grault/bar", NULL); 266 | r3_tree_insert_path(n, "/user/{id}", NULL); 267 | r3_tree_insert_path(n, "/post/{title:\\w+}", NULL); 268 | 269 | char *errstr = NULL; 270 | int err; 271 | err = r3_tree_compile(n, &errstr); 272 | if (err != 0) { 273 | // fail 274 | printf("error: %s\n", errstr); 275 | free(errstr); // errstr is created from `asprintf`, so you have to free it manually. 276 | } 277 | 278 | r3_tree_render_file(n, "png", "check_gvc.png"); 279 | r3_tree_free(n); 280 | ``` 281 | 282 | 283 | ![Imgur](http://imgur.com/HrUoEbI.png) 284 | 285 | Or you can even export it with dot format: 286 | 287 | ```dot 288 | digraph g { 289 | graph [bb="0,0,205.1,471"]; 290 | node [label="\N"]; 291 | "{root}" [height=0.5, 292 | pos="35.097,453", 293 | width=0.97491]; 294 | "#1" [height=0.5, 295 | pos="35.097,366", 296 | width=0.75]; 297 | .... 298 | ``` 299 | 300 | ### Graphviz Related Functions 301 | 302 | ```c 303 | int r3_tree_render_file(const R3Node * tree, const char * format, const char * filename); 304 | 305 | int r3_tree_render(const R3Node * tree, const char *layout, const char * format, FILE *fp); 306 | 307 | int r3_tree_render_dot(const R3Node * tree, const char *layout, FILE *fp); 308 | 309 | int r3_tree_render_file(const R3Node * tree, const char * format, const char * filename); 310 | ``` 311 | 312 | 313 | JSON Output 314 | ---------------------------------------- 315 | 316 | You can render the whole tree structure into json format output. 317 | 318 | Please run `configure` with the `--enable-json` option. 319 | 320 | Here is the sample code to generate JSON string: 321 | 322 | ```c 323 | json_object * obj = r3_node_to_json_object(n); 324 | 325 | const char *json = r3_node_to_json_pretty_string(n); 326 | printf("Pretty JSON: %s\n",json); 327 | 328 | const char *json = r3_node_to_json_string(n); 329 | printf("JSON: %s\n",json); 330 | ``` 331 | 332 | 333 | Use case in PHP 334 | ----------------------- 335 | **not implemented yet** 336 | 337 | ```php 338 | // Here is the paths data structure 339 | $paths = [ 340 | '/blog/post/{id}' => [ 'controller' => 'PostController' , 'action' => 'item' , 'method' => 'GET' ] , 341 | '/blog/post' => [ 'controller' => 'PostController' , 'action' => 'list' , 'method' => 'GET' ] , 342 | '/blog/post' => [ 'controller' => 'PostController' , 'action' => 'create' , 'method' => 'POST' ] , 343 | '/blog' => [ 'controller' => 'BlogController' , 'action' => 'list' , 'method' => 'GET' ] , 344 | ]; 345 | $rs = r3_compile($paths, 'persisten-table-id'); 346 | $ret = r3_dispatch($rs, '/blog/post/3' ); 347 | list($complete, $route, $variables) = $ret; 348 | 349 | // matched conditions aren't done yet 350 | list($error, $message) = r3_validate($route); // validate R3Route conditions 351 | if ( $error ) { 352 | echo $message; // "Method not allowed", "..."; 353 | } 354 | ``` 355 | 356 | Install 357 | ---------------------- 358 | 359 | sudo apt-get install check libpcre2 libpcre2-dev libjemalloc-dev libjemalloc1 build-essential libtool automake autoconf pkg-config 360 | sudo apt-get install graphviz-dev graphviz # if you want graphviz 361 | ./autogen.sh 362 | ./configure && make 363 | sudo make install 364 | 365 | And we support debian-based distro now! 366 | 367 | sudo apt-get install build-essential autoconf automake libpcre2-dev pkg-config debhelper libtool check 368 | mv dist-debian debian 369 | dpkg-buildpackage -b -us -uc 370 | sudo gdebi ../libr3*.deb 371 | 372 | 373 | #### Run Unit Tests 374 | 375 | ./configure --enable-check 376 | make check 377 | 378 | #### Enable Graphviz 379 | 380 | ./configure --enable-graphviz 381 | 382 | #### With jemalloc 383 | 384 | ./configure --with-malloc=jemalloc 385 | 386 | ubuntu PPA 387 | ---------------------- 388 | 389 | The PPA for libr3 can be found in . 390 | 391 | Binding For Other Languages 392 | --------------------------- 393 | 394 | * Perl Router::R3 by @CindyLinz 395 | * Python pyr3 by @lucemia 396 | * Python pyr3 by @thedrow 397 | * Haskell r3 by @MnO2 398 | * Vala r3-vala by @Ronmi 399 | 400 | Node.js 401 | 402 | * node-r3 by @othree 403 | * node-libr3 by @caasi 404 | 405 | Ruby 406 | 407 | * Ruby rr3 by @tonytonyjan 408 | * mruby r3 409 | * mruby rake r3 410 | 411 | 412 | License 413 | -------------------- 414 | This software is released under MIT License. 415 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | rm -rf autom4te.cache Makefile.in aclocal.m4 5 | aclocal --force 6 | 7 | # GNU libtool is named differently on some systems. This code tries several 8 | # variants like glibtoolize (MacOSX) and libtoolize1x (FreeBSD) 9 | 10 | set +ex 11 | echo "Looking for a version of libtoolize (which can have different names)..." 12 | libtoolize="" 13 | for l in glibtoolize libtoolize15 libtoolize14 libtoolize ; do 14 | $l --version > /dev/null 2>&1 15 | if [ $? = 0 ]; then 16 | libtoolize=$l 17 | echo "Found $l" 18 | break 19 | fi 20 | echo "Did not find $l" 21 | done 22 | 23 | if [ "x$libtoolize" = "x" ]; then 24 | echo "Can't find libtoolize on your system" 25 | exit 1 26 | fi 27 | 28 | set -ex 29 | $libtoolize -c -f 30 | autoconf -f -W all,no-obsolete 31 | autoheader -f -W all 32 | # automake -a -c -f -W all 33 | automake --add-missing --foreign --copy -c -W all 34 | 35 | rm -rf autom4te.cache 36 | exit 0 37 | 38 | # end autogen.sh 39 | -------------------------------------------------------------------------------- /bench.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Benchmark 6 | 7 | 8 | 9 | 10 | 11 | 181 | 182 | 183 |
184 | 185 |

R3: Router Benchmark

186 | 187 |
188 | 189 |

Data Set

190 | 191 |

This testing data is from https://github.com/stevegraham/rails/pull/1

192 | 193 |
/foo/bar/baz
194 | /foo/bar/qux
195 | /foo/bar/quux
196 | /foo/bar/corge
197 | /foo/bar/grault
198 | /foo/bar/garply
199 | /foo/baz/bar
200 | /foo/baz/qux
201 | /foo/baz/quux
202 | /foo/baz/corge
203 | /foo/baz/grault
204 | /foo/baz/garply
205 | /foo/qux/bar
206 | /foo/qux/baz
207 | /foo/qux/quux
208 | /foo/qux/corge
209 | /foo/qux/grault
210 | /foo/qux/garply
211 | /foo/quux/bar
212 | /foo/quux/baz
213 | /foo/quux/qux
214 | /foo/quux/corge
215 | /foo/quux/grault
216 | /foo/quux/garply
217 | /foo/corge/bar
218 | /foo/corge/baz
219 | /foo/corge/qux
220 | /foo/corge/quux
221 | /foo/corge/grault
222 | /foo/corge/garply
223 | /foo/grault/bar
224 | /foo/grault/baz
225 | /foo/grault/qux
226 | /foo/grault/quux
227 | /foo/grault/corge
228 | /foo/grault/garply
229 | /foo/garply/bar
230 | /foo/garply/baz
231 | /foo/garply/qux
232 | /foo/garply/quux
233 | /foo/garply/corge
234 | /foo/garply/grault
235 | /bar/foo/baz
236 | /bar/foo/qux
237 | /bar/foo/quux
238 | /bar/foo/corge
239 | /bar/foo/grault
240 | /bar/foo/garply
241 | /bar/baz/foo
242 | /bar/baz/qux
243 | /bar/baz/quux
244 | /bar/baz/corge
245 | /bar/baz/grault
246 | /bar/baz/garply
247 | /bar/qux/foo
248 | /bar/qux/baz
249 | /bar/qux/quux
250 | /bar/qux/corge
251 | /bar/qux/grault
252 | /bar/qux/garply
253 | /bar/quux/foo
254 | /bar/quux/baz
255 | /bar/quux/qux
256 | /bar/quux/corge
257 | /bar/quux/grault
258 | /bar/quux/garply
259 | /bar/corge/foo
260 | /bar/corge/baz
261 | /bar/corge/qux
262 | /bar/corge/quux
263 | /bar/corge/grault
264 | /bar/corge/garply
265 | /bar/grault/foo
266 | /bar/grault/baz
267 | /bar/grault/qux
268 | /bar/grault/quux
269 | /bar/grault/corge
270 | /bar/grault/garply
271 | /bar/garply/foo
272 | /bar/garply/baz
273 | /bar/garply/qux
274 | /bar/garply/quux
275 | /bar/garply/corge
276 | /bar/garply/grault
277 | /baz/foo/bar
278 | /baz/foo/qux
279 | /baz/foo/quux
280 | /baz/foo/corge
281 | /baz/foo/grault
282 | /baz/foo/garply
283 | /baz/bar/foo
284 | /baz/bar/qux
285 | /baz/bar/quux
286 | /baz/bar/corge
287 | /baz/bar/grault
288 | /baz/bar/garply
289 | /baz/qux/foo
290 | /baz/qux/bar
291 | /baz/qux/quux
292 | /baz/qux/corge
293 | /baz/qux/grault
294 | /baz/qux/garply
295 | /baz/quux/foo
296 | /baz/quux/bar
297 | /baz/quux/qux
298 | /baz/quux/corge
299 | /baz/quux/grault
300 | /baz/quux/garply
301 | /baz/corge/foo
302 | /baz/corge/bar
303 | /baz/corge/qux
304 | /baz/corge/quux
305 | /baz/corge/grault
306 | /baz/corge/garply
307 | /baz/grault/foo
308 | /baz/grault/bar
309 | /baz/grault/qux
310 | /baz/grault/quux
311 | /baz/grault/corge
312 | /baz/grault/garply
313 | /baz/garply/foo
314 | /baz/garply/bar
315 | /baz/garply/qux
316 | /baz/garply/quux
317 | /baz/garply/corge
318 | /baz/garply/grault
319 | /qux/foo/bar
320 | /qux/foo/baz
321 | /qux/foo/quux
322 | /qux/foo/corge
323 | /qux/foo/grault
324 | /qux/foo/garply
325 | /qux/bar/foo
326 | /qux/bar/baz
327 | /qux/bar/quux
328 | /qux/bar/corge
329 | /qux/bar/grault
330 | /qux/bar/garply
331 | /qux/baz/foo
332 | /qux/baz/bar
333 | /qux/baz/quux
334 | /qux/baz/corge
335 | /qux/baz/grault
336 | /qux/baz/garply
337 | /qux/quux/foo
338 | /qux/quux/bar
339 | /qux/quux/baz
340 | /qux/quux/corge
341 | /qux/quux/grault
342 | /qux/quux/garply
343 | /qux/corge/foo
344 | /qux/corge/bar
345 | /qux/corge/baz
346 | /qux/corge/quux
347 | /qux/corge/grault
348 | /qux/corge/garply
349 | /qux/grault/foo
350 | /qux/grault/bar
351 | /qux/grault/baz
352 | /qux/grault/quux
353 | /qux/grault/corge
354 | /qux/grault/garply
355 | /qux/garply/foo
356 | /qux/garply/bar
357 | /qux/garply/baz
358 | /qux/garply/quux
359 | /qux/garply/corge
360 | /qux/garply/grault
361 | /quux/foo/bar
362 | /quux/foo/baz
363 | /quux/foo/qux
364 | /quux/foo/corge
365 | /quux/foo/grault
366 | /quux/foo/garply
367 | /quux/bar/foo
368 | /quux/bar/baz
369 | /quux/bar/qux
370 | /quux/bar/corge
371 | /quux/bar/grault
372 | /quux/bar/garply
373 | /quux/baz/foo
374 | /quux/baz/bar
375 | /quux/baz/qux
376 | /quux/baz/corge
377 | /quux/baz/grault
378 | /quux/baz/garply
379 | /quux/qux/foo
380 | /quux/qux/bar
381 | /quux/qux/baz
382 | /quux/qux/corge
383 | /quux/qux/grault
384 | /quux/qux/garply
385 | /quux/corge/foo
386 | /quux/corge/bar
387 | /quux/corge/baz
388 | /quux/corge/qux
389 | /quux/corge/grault
390 | /quux/corge/garply
391 | /quux/grault/foo
392 | /quux/grault/bar
393 | /quux/grault/baz
394 | /quux/grault/qux
395 | /quux/grault/corge
396 | /quux/grault/garply
397 | /quux/garply/foo
398 | /quux/garply/bar
399 | /quux/garply/baz
400 | /quux/garply/qux
401 | /quux/garply/corge
402 | /quux/garply/grault
403 | /corge/foo/bar
404 | /corge/foo/baz
405 | /corge/foo/qux
406 | /corge/foo/quux
407 | /corge/foo/grault
408 | /corge/foo/garply
409 | /corge/bar/foo
410 | /corge/bar/baz
411 | /corge/bar/qux
412 | /corge/bar/quux
413 | /corge/bar/grault
414 | /corge/bar/garply
415 | /corge/baz/foo
416 | /corge/baz/bar
417 | /corge/baz/qux
418 | /corge/baz/quux
419 | /corge/baz/grault
420 | /corge/baz/garply
421 | /corge/qux/foo
422 | /corge/qux/bar
423 | /corge/qux/baz
424 | /corge/qux/quux
425 | /corge/qux/grault
426 | /corge/qux/garply
427 | /corge/quux/foo
428 | /corge/quux/bar
429 | /corge/quux/baz
430 | /corge/quux/qux
431 | /corge/quux/grault
432 | /corge/quux/garply
433 | /corge/grault/foo
434 | /corge/grault/bar
435 | /corge/grault/baz
436 | /corge/grault/qux
437 | /corge/grault/quux
438 | /corge/grault/garply
439 | /corge/garply/foo
440 | /corge/garply/bar
441 | /corge/garply/baz
442 | /corge/garply/qux
443 | /corge/garply/quux
444 | /corge/garply/grault
445 | /grault/foo/bar
446 | /grault/foo/baz
447 | /grault/foo/qux
448 | /grault/foo/quux
449 | /grault/foo/corge
450 | /grault/foo/garply
451 | /grault/bar/foo
452 | /grault/bar/baz
453 | /grault/bar/qux
454 | /grault/bar/quux
455 | /grault/bar/corge
456 | /grault/bar/garply
457 | /grault/baz/foo
458 | /grault/baz/bar
459 | /grault/baz/qux
460 | /grault/baz/quux
461 | /grault/baz/corge
462 | /grault/baz/garply
463 | /grault/qux/foo
464 | /grault/qux/bar
465 | /grault/qux/baz
466 | /grault/qux/quux
467 | /grault/qux/corge
468 | /grault/qux/garply
469 | /grault/quux/foo
470 | /grault/quux/bar
471 | /grault/quux/baz
472 | /grault/quux/qux
473 | /grault/quux/corge
474 | /grault/quux/garply
475 | /grault/corge/foo
476 | /grault/corge/bar
477 | /grault/corge/baz
478 | /grault/corge/qux
479 | /grault/corge/quux
480 | /grault/corge/garply
481 | /grault/garply/foo
482 | /grault/garply/bar
483 | /grault/garply/baz
484 | /grault/garply/qux
485 | /grault/garply/quux
486 | /grault/garply/corge
487 | /garply/foo/bar
488 | /garply/foo/baz
489 | /garply/foo/qux
490 | /garply/foo/quux
491 | /garply/foo/corge
492 | /garply/foo/grault
493 | /garply/bar/foo
494 | /garply/bar/baz
495 | /garply/bar/qux
496 | /garply/bar/quux
497 | /garply/bar/corge
498 | /garply/bar/grault
499 | /garply/baz/foo
500 | /garply/baz/bar
501 | /garply/baz/qux
502 | /garply/baz/quux
503 | /garply/baz/corge
504 | /garply/baz/grault
505 | /garply/qux/foo
506 | /garply/qux/bar
507 | /garply/qux/baz
508 | /garply/qux/quux
509 | /garply/qux/corge
510 | /garply/qux/grault
511 | /garply/quux/foo
512 | /garply/quux/bar
513 | /garply/quux/baz
514 | /garply/quux/qux
515 | /garply/quux/corge
516 | /garply/quux/grault
517 | /garply/corge/foo
518 | /garply/corge/bar
519 | /garply/corge/baz
520 | /garply/corge/qux
521 | /garply/corge/quux
522 | /garply/corge/grault
523 | /garply/grault/foo
524 | /garply/grault/bar
525 | /garply/grault/baz
526 | /garply/grault/qux
527 | /garply/grault/quux
528 | /garply/grault/corge
529 | 
530 |
531 | 532 | 533 | 543 | 544 | 545 | -------------------------------------------------------------------------------- /cmake/Modules/FindCheck.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find the CHECK libraries 2 | # Once done this will define 3 | # 4 | # CHECK_FOUND - system has check 5 | # CHECK_INCLUDE_DIRS - the check include directory 6 | # CHECK_LIBRARIES - check library 7 | # 8 | # Copyright (c) 2007 Daniel Gollub 9 | # Copyright (c) 2007-2009 Bjoern Ricks 10 | # 11 | # Redistribution and use is allowed according to the terms of the New 12 | # BSD license. 13 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 14 | 15 | 16 | INCLUDE( FindPkgConfig ) 17 | 18 | IF ( Check_FIND_REQUIRED ) 19 | SET( _pkgconfig_REQUIRED "REQUIRED" ) 20 | ELSE( Check_FIND_REQUIRED ) 21 | SET( _pkgconfig_REQUIRED "" ) 22 | ENDIF ( Check_FIND_REQUIRED ) 23 | 24 | IF ( CHECK_MIN_VERSION ) 25 | PKG_SEARCH_MODULE( CHECK ${_pkgconfig_REQUIRED} check>=${CHECK_MIN_VERSION} ) 26 | ELSE ( CHECK_MIN_VERSION ) 27 | PKG_SEARCH_MODULE( CHECK ${_pkgconfig_REQUIRED} check ) 28 | ENDIF ( CHECK_MIN_VERSION ) 29 | 30 | # Look for CHECK include dir and libraries 31 | IF( NOT CHECK_FOUND AND NOT PKG_CONFIG_FOUND ) 32 | 33 | FIND_PATH( CHECK_INCLUDE_DIRS check.h ) 34 | 35 | FIND_LIBRARY( CHECK_LIBRARIES NAMES check ) 36 | 37 | IF ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES ) 38 | SET( CHECK_FOUND 1 ) 39 | IF ( NOT Check_FIND_QUIETLY ) 40 | MESSAGE ( STATUS "Found CHECK: ${CHECK_LIBRARIES}" ) 41 | ENDIF ( NOT Check_FIND_QUIETLY ) 42 | ELSE ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES ) 43 | IF ( Check_FIND_REQUIRED ) 44 | MESSAGE( FATAL_ERROR "Could NOT find CHECK" ) 45 | ELSE ( Check_FIND_REQUIRED ) 46 | IF ( NOT Check_FIND_QUIETLY ) 47 | MESSAGE( STATUS "Could NOT find CHECK" ) 48 | ENDIF ( NOT Check_FIND_QUIETLY ) 49 | ENDIF ( Check_FIND_REQUIRED ) 50 | ENDIF ( CHECK_INCLUDE_DIRS AND CHECK_LIBRARIES ) 51 | ENDIF( NOT CHECK_FOUND AND NOT PKG_CONFIG_FOUND ) 52 | 53 | # Hide advanced variables from CMake GUIs 54 | MARK_AS_ADVANCED( CHECK_INCLUDE_DIRS CHECK_LIBRARIES ) 55 | -------------------------------------------------------------------------------- /cmake/Modules/FindJemalloc.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find jemalloc headers and libraries. 2 | # 3 | # Usage of this module as follows: 4 | # 5 | # find_package(JeMalloc) 6 | # 7 | # Variables used by this module, they can change the default behaviour and need 8 | # to be set before calling find_package: 9 | # 10 | # JEMALLOC_ROOT_DIR Set this variable to the root installation of 11 | # jemalloc if the module has problems finding 12 | # the proper installation path. 13 | # 14 | # Variables defined by this module: 15 | # 16 | # JEMALLOC_FOUND System has jemalloc libs/headers 17 | # JEMALLOC_LIBRARIES The jemalloc library/libraries 18 | # JEMALLOC_INCLUDE_DIR The location of jemalloc headers 19 | 20 | find_path(JEMALLOC_ROOT_DIR 21 | NAMES include/jemalloc/jemalloc.h 22 | ) 23 | 24 | find_library(JEMALLOC_LIBRARIES 25 | NAMES jemalloc 26 | HINTS ${JEMALLOC_ROOT_DIR}/lib 27 | ) 28 | 29 | find_path(JEMALLOC_INCLUDE_DIR 30 | NAMES jemalloc/jemalloc.h 31 | HINTS ${JEMALLOC_ROOT_DIR}/include 32 | ) 33 | 34 | include(FindPackageHandleStandardArgs) 35 | find_package_handle_standard_args(JeMalloc DEFAULT_MSG 36 | JEMALLOC_LIBRARIES 37 | JEMALLOC_INCLUDE_DIR 38 | ) 39 | 40 | mark_as_advanced( 41 | JEMALLOC_ROOT_DIR 42 | JEMALLOC_LIBRARIES 43 | JEMALLOC_INCLUDE_DIR 44 | ) 45 | -------------------------------------------------------------------------------- /cmake/Modules/FindJudy.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2007-2009 LuaDist. 2 | # Created by Peter Kapec 3 | # Redistribution and use of this file is allowed according to the terms of the MIT license. 4 | # For details see the COPYRIGHT file distributed with LuaDist. 5 | # Note: 6 | # Searching headers and libraries is very simple and is NOT as powerful as scripts 7 | # distributed with CMake, because LuaDist defines directories to search for. 8 | # Everyone is encouraged to contact the author with improvements. Maybe this file 9 | # becomes part of CMake distribution sometimes. 10 | 11 | # - Find judy 12 | # Find the native Judy headers and libraries. 13 | # 14 | # Judy_INCLUDE_DIRS - where to find judy.h, etc. 15 | # Judy_LIBRARIES - List of libraries when using judy. 16 | # Judy_FOUND - True if judy found. 17 | 18 | # Look for the header file. 19 | FIND_PATH(Judy_INCLUDE_DIR NAMES Judy.h) 20 | 21 | # Look for the library. 22 | FIND_LIBRARY(Judy_LIBRARY NAMES judy) 23 | 24 | # Handle the QUIETLY and REQUIRED arguments and set Judy_FOUND to TRUE if all listed variables are TRUE. 25 | INCLUDE(FindPackageHandleStandardArgs) 26 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(Judy DEFAULT_MSG Judy_LIBRARY Judy_INCLUDE_DIR) 27 | 28 | # Copy the results to the output variables. 29 | IF(Judy_FOUND) 30 | SET(Judy_LIBRARIES ${Judy_LIBRARY}) 31 | SET(Judy_INCLUDE_DIRS ${Judy_INCLUDE_DIR}) 32 | ELSE(Judy_FOUND) 33 | SET(Judy_LIBRARIES) 34 | SET(Judy_INCLUDE_DIRS) 35 | ENDIF(Judy_FOUND) 36 | 37 | MARK_AS_ADVANCED(Judy_INCLUDE_DIRS Judy_LIBRARIES) 38 | -------------------------------------------------------------------------------- /cmake/Modules/FindPCRE2.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2007-2009 LuaDist. 2 | # Created by Peter Kapec 3 | # Redistribution and use of this file is allowed according to the terms of the MIT license. 4 | # For details see the COPYRIGHT file distributed with LuaDist. 5 | # Note: 6 | # Searching headers and libraries is very simple and is NOT as powerful as scripts 7 | # distributed with CMake, because LuaDist defines directories to search for. 8 | # Everyone is encouraged to contact the author with improvements. Maybe this file 9 | # becomes part of CMake distribution sometimes. 10 | 11 | # - Find pcre2 12 | # Find the native PCRE2 headers and libraries. 13 | # 14 | # PCRE2_INCLUDE_DIRS - where to find pcre2.h, etc. 15 | # PCRE2_LIBRARIES - List of libraries when using pcre2. 16 | # PCRE2_FOUND - True if pcre2 found. 17 | 18 | # Look for the header file. 19 | FIND_PATH(PCRE2_INCLUDE_DIR NAMES pcre2.h) 20 | 21 | # Look for the library. 22 | FIND_LIBRARY(PCRE2_LIBRARY NAMES pcre2-8) 23 | 24 | # Handle the QUIETLY and REQUIRED arguments and set PCRE2_FOUND to TRUE if all listed variables are TRUE. 25 | INCLUDE(FindPackageHandleStandardArgs) 26 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(PCRE2 DEFAULT_MSG PCRE2_LIBRARY PCRE2_INCLUDE_DIR) 27 | 28 | # Copy the results to the output variables. 29 | IF(PCRE2_FOUND) 30 | SET(PCRE2_LIBRARIES ${PCRE2_LIBRARY}) 31 | SET(PCRE2_INCLUDE_DIRS ${PCRE2_INCLUDE_DIR}) 32 | ELSE(PCRE2_FOUND) 33 | SET(PCRE2_LIBRARIES) 34 | SET(PCRE2_INCLUDE_DIRS) 35 | ENDIF(PCRE2_FOUND) 36 | 37 | MARK_AS_ADVANCED(PCRE2_INCLUDE_DIRS PCRE2_LIBRARIES) 38 | -------------------------------------------------------------------------------- /config.h.cmake: -------------------------------------------------------------------------------- 1 | #cmakedefine HAVE_STRDUP @HAVE_STRDUP@ 2 | #cmakedefine HAVE_STRNDUP @HAVE_STRNDUP@ 3 | #cmakedefine HAVE_STDBOOL_H @HAVE_STDBOOL_H@ 4 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([r3], 2.0.0) 2 | AC_PREREQ([2.64]) 3 | AC_USE_SYSTEM_EXTENSIONS 4 | AC_CONFIG_HEADERS(config.h) 5 | AC_CONFIG_MACRO_DIR([m4]) 6 | AM_SILENT_RULES([yes]) 7 | AM_INIT_AUTOMAKE([foreign subdir-objects]) 8 | LT_INIT 9 | AC_PROG_CC 10 | AC_PROG_CC_STDC 11 | AC_PROG_CXX 12 | AC_PROG_INSTALL 13 | AC_HEADER_STDBOOL 14 | 15 | # older debian 16 | AC_PROG_LIBTOOL 17 | AM_PROG_CC_C_O 18 | 19 | AC_CHECK_HEADERS([stdlib.h string.h sys/time.h]) 20 | 21 | # Checks for typedefs, structures, and compiler characteristics. 22 | AC_C_INLINE 23 | AC_TYPE_SIZE_T 24 | 25 | # Checks for library functions. 26 | AC_CHECK_FUNCS([gettimeofday memset strchr strdup strndup strstr]) 27 | PKG_PROG_PKG_CONFIG 28 | 29 | 30 | 31 | AC_ARG_ENABLE([gcov], 32 | [AS_HELP_STRING([--enable-gcov], 33 | [use Gcov to test the test suite])], 34 | [], 35 | [enable_gcov=no]) 36 | AM_CONDITIONAL([COND_GCOV],[test '!' "$enable_gcov" = no]) 37 | 38 | 39 | 40 | AC_ARG_WITH([malloc], 41 | AS_HELP_STRING([--without-malloc], [Use the default malloc])) 42 | 43 | AS_IF([test "x$with_malloc" == "xjemalloc"], 44 | [AC_CHECK_HEADERS([jemalloc/jemalloc.h], [ 45 | found_jemalloc=yes; break 46 | ])]) 47 | 48 | if test "x$found_jemalloc" == "xyes" ; then 49 | 50 | AC_MSG_CHECKING([Checking jemalloc version]) 51 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], 52 | [[ 53 | #ifdef JEMALLOC_VERSION_MAJOR > 2 54 | return 0; 55 | #endif 56 | return 1; 57 | ]])], 58 | [ 59 | AC_MSG_RESULT([yes]) 60 | AC_DEFINE_UNQUOTED([USE_JEMALLOC], 1, [Define to 1 if you have the PATH_MAX macro.]) 61 | have_jemalloc=yes 62 | ], 63 | [ 64 | AC_MSG_RESULT([no]) 65 | AC_DEFINE_UNQUOTED([USE_JEMALLOC], 0, [Define to 1 if you have the PATH_MAX macro.]) 66 | have_jemalloc=no 67 | ] 68 | ) 69 | fi 70 | AM_CONDITIONAL(USE_JEMALLOC, test "x$have_jemalloc" = "xyes") 71 | 72 | # AM_CONDITIONAL(USE_JEMALLOC, test "x$found_jemalloc" = "xyes") 73 | # AC_DEFINE(USE_JEMALLOC, test "x$found_jemalloc" = "xyes" , "use jemalloc") 74 | 75 | 76 | PKG_CHECK_MODULES(DEPS, [libpcre2-8]) 77 | AC_SUBST(DEPS_CFLAGS) 78 | AC_SUBST(DEPS_LIBS) 79 | 80 | 81 | AC_ARG_ENABLE(debug,AS_HELP_STRING([--enable-debug],[enable debug])) 82 | if test "x$enable_debug" = "xyes"; then 83 | AC_DEFINE(DEBUG, 1, "debug") 84 | fi 85 | AM_CONDITIONAL(ENABLE_DEBUG, test "x$enable_debug" = "xyes") 86 | 87 | 88 | 89 | AC_ARG_ENABLE(graphviz, AS_HELP_STRING([--enable-graphviz],[enable graphviz support])) 90 | if test "x$enable_graphviz" = "xyes" ; then 91 | PKG_CHECK_MODULES(GVC_DEPS, [libgvc]) 92 | AC_SUBST(GVC_DEPS_CFLAGS) 93 | AC_SUBST(GVC_DEPS_LIBS) 94 | AC_DEFINE(ENABLE_GRAPHVIZ, 1, "whether graphviz is enable") 95 | fi 96 | AM_CONDITIONAL(ENABLE_GRAPHVIZ, test "x$enable_graphviz" = "xyes") 97 | 98 | 99 | 100 | 101 | AC_ARG_ENABLE(json, AS_HELP_STRING([--enable-json],[enable json encoder])) 102 | if test "x$enable_json" = "xyes"; then 103 | PKG_CHECK_MODULES(JSONC, [json-c]) 104 | AC_SUBST(JSONC_CFLAGS) 105 | AC_SUBST(JSONC_LIBS) 106 | AC_DEFINE(ENABLE_JSON, 1, [enable json]) 107 | fi 108 | AM_CONDITIONAL(ENABLE_JSON, test "x$enable_json" = "xyes") 109 | 110 | 111 | # This does not work because configure does not look into /opt/local/include... 112 | # AC_CHECK_HEADERS([check.h],[ enable_check=yes ],[ enable_check=unset ]) 113 | 114 | 115 | AC_ARG_ENABLE(check, 116 | AS_HELP_STRING([--enable-check], 117 | [enable unit testing]), 118 | , enable_check=unset) 119 | 120 | if test "x$enable_check" != "xunset" ; then 121 | PKG_CHECK_MODULES(CHECK,[check >= 0.9.4],:,[ 122 | ifdef([AM_PATH_CHECK], 123 | [AM_PATH_CHECK(,[have_check="yes"])], 124 | AC_MSG_WARN([Check not found; cannot run unit tests!]) 125 | [have_check="no"] 126 | )] 127 | ]) 128 | fi 129 | AM_CONDITIONAL(ENABLE_CHECK, test "x$enable_check" = "xyes") 130 | 131 | AC_CONFIG_FILES([ 132 | r3.pc 133 | Makefile 134 | src/Makefile 135 | tests/Makefile 136 | examples/Makefile 137 | ]) 138 | AC_OUTPUT 139 | -------------------------------------------------------------------------------- /dist-debian/changelog: -------------------------------------------------------------------------------- 1 | libr3 (1.3.1-1) unstable; urgency=low 2 | 3 | * Initial release using libr3 1.3.1. 4 | 5 | -- Ronmi Ren Thu, 12 Jun 2014 10:54:16 +0800 6 | -------------------------------------------------------------------------------- /dist-debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /dist-debian/control: -------------------------------------------------------------------------------- 1 | Source: libr3 2 | Priority: optional 3 | Maintainer: Ronmi Ren 4 | Build-Depends: debhelper (>= 8.0.0), automake, autotools-dev, autoconf, 5 | libtool, libpcre2-dev, pkg-config, check 6 | Standards-Version: 3.9.4 7 | Section: libs 8 | Homepage: https://github.com/c9s/r3 9 | 10 | Package: libr3-dev 11 | Section: libdevel 12 | Architecture: any 13 | Depends: libr3 (= ${binary:Version}) 14 | Description: Development files for libr3 15 | libr3 (https://github.com/c9s/r3) is an URL router library with high 16 | performance, thus, it's implemented in C. It compiles your R3Route paths into 17 | a prefix trie. 18 | . 19 | This package contains header files for libr3. 20 | 21 | Package: libr3 22 | Section: libs 23 | Architecture: any 24 | Depends: ${shlibs:Depends}, ${misc:Depends} 25 | Description: High performance URL routing library written in C. 26 | libr3 (https://github.com/c9s/r3) is an URL router library with high 27 | performance, thus, it's implemented in C. It compiles your R3Route paths into 28 | a prefix trie. 29 | -------------------------------------------------------------------------------- /dist-debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: libr3 3 | Source: https://github.com/c9s/r3 4 | 5 | Files: * 6 | Copyright: 2014 yoanlin93@gmail.com 7 | License: MIT 8 | 9 | Files: debian/* 10 | Copyright: 2014 Ronmi Ren 11 | License: MIT 12 | 13 | License: MIT 14 | Permission is hereby granted, free of charge, to any person obtaining a 15 | copy of this software and associated documentation files (the "Software"), 16 | to deal in the Software without restriction, including without limitation 17 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 18 | and/or sell copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following conditions: 20 | . 21 | The above copyright notice and this permission notice shall be included 22 | in all copies or substantial portions of the Software. 23 | . 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 25 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | -------------------------------------------------------------------------------- /dist-debian/docs: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /dist-debian/libr3-dev.dirs: -------------------------------------------------------------------------------- 1 | usr/lib 2 | usr/include 3 | -------------------------------------------------------------------------------- /dist-debian/libr3-dev.install: -------------------------------------------------------------------------------- 1 | usr/include/* 2 | usr/lib/lib*.a 3 | usr/lib/lib*.so 4 | usr/lib/pkgconfig/* 5 | -------------------------------------------------------------------------------- /dist-debian/libr3.dirs: -------------------------------------------------------------------------------- 1 | usr/lib 2 | -------------------------------------------------------------------------------- /dist-debian/libr3.install: -------------------------------------------------------------------------------- 1 | usr/lib/lib*.so.* 2 | -------------------------------------------------------------------------------- /dist-debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # Uncomment this to turn on verbose mode. 5 | #export DH_VERBOSE=1 6 | 7 | %: 8 | dh $@ --with autotools-dev 9 | 10 | override_dh_auto_configure: 11 | ./autogen.sh 12 | ./configure --enable-check --prefix=/usr 13 | -------------------------------------------------------------------------------- /dist-debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /examples/Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CFLAGS=$(DEPS_CFLAGS) $(GVC_DEPS_CFLAGS) -I$(top_builddir)/include -Wall -std=c99 2 | AM_CXXFLAGS=$(DEPS_CFLAGS) $(GVC_DEPS_CFLAGS) -I$(top_builddir)/include -Wall 3 | AM_LDFLAGS=$(DEPS_LIBS) $(GVC_DEPS_LIBS) $(top_builddir)/libr3.la 4 | 5 | noinst_PROGRAMS = simple simple_cpp 6 | simple_SOURCES = simple.c 7 | simple_cpp_SOURCES = simple_cpp.cpp 8 | -------------------------------------------------------------------------------- /examples/routing.c: -------------------------------------------------------------------------------- 1 | /* 2 | * check_slug.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include 8 | #include 9 | #include "../include/r3.h" 10 | 11 | 12 | 13 | 14 | void test1(void) { 15 | R3Node *n = r3_tree_create(10); 16 | 17 | int route_data1 = 3; 18 | int route_data2 = 44; 19 | int route_data3 = 555; 20 | 21 | // insert the R3Route path into the router tree 22 | r3_tree_insert_routel(n, METHOD_GET | METHOD_POST, "/blog", sizeof("/blog") - 1, &route_data1 ); 23 | r3_tree_insert_routel(n, METHOD_GET | METHOD_POST, "/blog/{idl:\\d+}/asf/{id}", strlen("/blog/{idl:\\d+}/asf/{id}"), &route_data2 ); 24 | r3_tree_insert_routel(n, METHOD_GET | METHOD_POST, "/blog3/{idl:\\d{3}}/asd/{id:[0-9]+}/qwe", sizeof("/blog3/{idl:\\d{3}}/asd/{id:[0-9]+}/qwe") - 1, &route_data3 ); 25 | 26 | char *errstr = NULL; 27 | int err = r3_tree_compile(n, &errstr); 28 | if (err != 0) { 29 | // fail 30 | printf("error: %s\n", errstr); 31 | free(errstr); // errstr is created from `asprintf`, so you have to free it manually. 32 | } 33 | // r3_tree_dump(n,0); 34 | 35 | 36 | // in your http server handler 37 | 38 | // create the match entry for capturing dynamic variables. 39 | match_entry * entry; 40 | R3Route *matched_route; 41 | int i; 42 | for (int k = 0; k < 3000000; k++) { 43 | // printf("round N%d\n",k); 44 | entry = match_entry_create("/blog/432/asf/678"); 45 | entry->request_method = METHOD_GET; 46 | matched_route = r3_tree_match_route(n, entry); 47 | // if (matched_route) { 48 | // printf("Routed data is: %d\n", *(int*)matched_route->data); // get the data from matched route 49 | // if (entry->vars.tokens.size == entry->vars.slugs.size) { 50 | // for (i = 0; i < entry->vars.tokens.size; i++) { 51 | // // entry->vars.slugs.entries[i]; 52 | // // entry->vars.tokens.entries[i]; 53 | // printf("Slug name is: %*.*s\n",entry->vars.slugs.entries[i].len, 54 | // entry->vars.slugs.entries[i].len, entry->vars.slugs.entries[i].base); 55 | // printf("Slug value is: %*.*s\n",entry->vars.tokens.entries[i].len, 56 | // entry->vars.tokens.entries[i].len, entry->vars.tokens.entries[i].base); 57 | // } 58 | // } else { 59 | // // printf("Slugs and tokens sizes are not equal\n"); 60 | // // for (i = 0; i < entry->vars.slugs.size; i++) { 61 | // // printf("Slug name is: %*.*s\n",entry->vars.slugs.entries[i].len, 62 | // // entry->vars.slugs.entries[i].len, entry->vars.slugs.entries[i].base); 63 | // // } 64 | // // for (i = 0; i < entry->vars.tokens.size; i++) { 65 | // // printf("Slug value is: %*.*s\n",entry->vars.tokens.entries[i].len, 66 | // // entry->vars.tokens.entries[i].len, entry->vars.tokens.entries[i].base); 67 | // // } 68 | // } 69 | // } 70 | // free the objects at the end 71 | match_entry_free(entry); 72 | } 73 | // entry = match_entry_create("/blog/aaa/asd/123/qwe"); 74 | // if (entry != NULL) { 75 | // entry->request_method = METHOD_GET; 76 | // matched_route = r3_tree_match_route(n, entry); 77 | // if (matched_route != NULL) { 78 | // // printf("Routed data is: %d\n", *(int*)matched_route->data); // get the data from matched route 79 | // for (int i = 0; i < entry->vars->len; i++) { 80 | // // entry->vars->slugs[i]; 81 | // // entry->vars->tokens[i]; 82 | // printf("Slug name is: %s\n",entry->vars->slugs[i]); 83 | // printf("Slug value is: %s\n",entry->vars->tokens[i]); 84 | // } 85 | // } 86 | // } 87 | // // free the objects at the end 88 | // match_entry_free(entry); 89 | 90 | r3_tree_free(n); 91 | } 92 | 93 | int main (int argc, char *argv[]) { 94 | test1(); 95 | } 96 | -------------------------------------------------------------------------------- /examples/simple.c: -------------------------------------------------------------------------------- 1 | /* 2 | * bench.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include 8 | #include 9 | #include "r3.h" 10 | 11 | int main() 12 | { 13 | R3Node * n = r3_tree_create(3); 14 | 15 | r3_tree_insert_path(n, "/foo/bar/baz", NULL); 16 | r3_tree_insert_path(n, "/foo/bar/qux", NULL); 17 | r3_tree_insert_path(n, "/foo/bar/quux", NULL); 18 | r3_tree_insert_path(n, "/bar/foo/baz", NULL); 19 | r3_tree_insert_path(n, "/bar/foo/quux", NULL); 20 | r3_tree_insert_path(n, "/bar/garply/grault", NULL); 21 | r3_tree_insert_path(n, "/baz/foo/bar", NULL); 22 | r3_tree_insert_path(n, "/baz/foo/qux", NULL); 23 | r3_tree_insert_path(n, "/baz/foo/quux", NULL); 24 | r3_tree_insert_path(n, "/qux/foo/quux", NULL); 25 | r3_tree_insert_path(n, "/qux/foo/corge", NULL); 26 | r3_tree_insert_path(n, "/qux/foo/grault", NULL); 27 | r3_tree_insert_path(n, "/corge/quux/foo", NULL); 28 | r3_tree_insert_path(n, "/corge/quux/bar", NULL); 29 | r3_tree_insert_path(n, "/corge/quux/baz", NULL); 30 | r3_tree_insert_path(n, "/corge/quux/qux", NULL); 31 | r3_tree_insert_path(n, "/corge/quux/grault", NULL); 32 | r3_tree_insert_path(n, "/grault/foo/bar", NULL); 33 | r3_tree_insert_path(n, "/grault/foo/baz", NULL); 34 | r3_tree_insert_path(n, "/garply/baz/quux", NULL); 35 | r3_tree_insert_path(n, "/garply/baz/corge", NULL); 36 | r3_tree_insert_path(n, "/garply/baz/grault", NULL); 37 | r3_tree_insert_path(n, "/garply/qux/foo", NULL); 38 | 39 | char *errstr = NULL; 40 | int err = r3_tree_compile(n, &errstr); 41 | if(err) { 42 | printf("%s\n",errstr); 43 | free(errstr); 44 | return 1; 45 | } 46 | 47 | R3Node *m; 48 | 49 | m = r3_tree_match(n , "/qux/bar/corge", NULL); 50 | 51 | match_entry * e = match_entry_createl("/garply/baz/grault", strlen("/garply/baz/grault") ); 52 | m = r3_tree_match_entry(n , e); 53 | if (m) { 54 | printf("Matched! %s\n", e->path.base); 55 | } 56 | match_entry_free(e); 57 | r3_tree_free(n); 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /examples/simple_cpp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | void example_1() { 9 | // create a router tree with 10 children capacity (this capacity can grow dynamically) 10 | r3::Tree tree(10); 11 | 12 | // insert the R3Route path into the router tree 13 | int route_data_1 = 1; 14 | tree.insert_path("/bar", &route_data_1); // ignore the length of path 15 | 16 | int route_data_2 = 2; 17 | tree.insert_pathl("/zoo", strlen("/zoo"), &route_data_2); 18 | int route_data_3 = 3; 19 | tree.insert_pathl("/foo/bar", strlen("/foo/bar"), &route_data_3); 20 | 21 | int route_data_4 = 4; 22 | tree.insert_pathl("/post/{id}", strlen("/post/{id}") , &route_data_4); 23 | 24 | int route_data_5 = 5; 25 | tree.insert_pathl("/user/{id:\\d+}", strlen("/user/{id:\\d+}"), 26 | &route_data_5); 27 | 28 | // if you want to catch error, you may call the extended path function for insertion 29 | int data = 10; 30 | char* errstr; 31 | r3::Node ret = tree.insert_pathl("/foo/{name:\\d{5}", 32 | strlen("/foo/{name:\\d{5}"), &data, &errstr); 33 | if (ret == NULL) { 34 | // failed insertion 35 | cout << "error: " << errstr << endl; 36 | free(errstr); // errstr is created from `asprintf`, so you have to free it manually. 37 | } 38 | 39 | // let's compile the tree! 40 | int err = tree.compile(&errstr); 41 | if (err != 0) { 42 | cout << "error: " << errstr << endl; 43 | free(errstr); // errstr is created from `asprintf`, so you have to free it manually. 44 | } 45 | 46 | // dump the compiled tree 47 | tree.dump(0); 48 | 49 | // match a route 50 | r3::Node matched_node = tree.matchl("/foo/bar", strlen("/foo/bar")); 51 | if (matched_node) { 52 | int ret = *static_cast(matched_node.data()); 53 | cout << "match path ret: " << ret << endl; 54 | } 55 | 56 | r3::MatchEntry entry("/foo/bar"); 57 | matched_node = tree.match_entry(entry); 58 | if (matched_node) { 59 | int ret = *static_cast(matched_node.data()); 60 | cout << "match entry ret: " << ret << endl; 61 | } 62 | } 63 | 64 | void example_2() { 65 | // create a router tree with 10 children capacity (this capacity can grow dynamically) 66 | r3::Tree tree(10); 67 | 68 | // insert the R3Route path into the router tree 69 | int route_data = 1; 70 | tree.insert_routel(METHOD_GET | METHOD_POST, "/blog/post", 71 | sizeof("/blog/post") - 1, &route_data); 72 | 73 | char* errstr; 74 | int err = tree.compile(&errstr); 75 | if (err != 0) { 76 | cout << "errstr: " << errstr << endl; 77 | free(errstr); // errstr is created from `asprintf`, so you have to free it manually. 78 | } 79 | 80 | // in your http server handler 81 | 82 | // create the match entry for capturing dynamic variables. 83 | r3::MatchEntry entry("/blog/post"); 84 | entry.set_request_method(METHOD_GET); 85 | 86 | r3::Route matched_route = tree.match_route(entry); 87 | if (matched_route) { 88 | int ret = *static_cast(matched_route.data()); 89 | cout << "match route ret: " << ret << endl; 90 | } 91 | } 92 | 93 | int main() { 94 | example_1(); 95 | example_2(); 96 | return 0; 97 | } 98 | -------------------------------------------------------------------------------- /gen_route_tests.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | puts < 9 | #include 10 | #include 11 | #include 12 | #include "r3.h" 13 | #include "r3_slug.h" 14 | #include "zmalloc.h" 15 | 16 | START_TEST (test_routes) 17 | { 18 | node * n = r3_tree_create(10); 19 | node * m = NULL; 20 | 21 | END 22 | 23 | 24 | arr = ["foo", "bar", "baz", "qux", "quux", "corge", "grault", "garply"] 25 | paths = arr.permutation(3).map { |a| "/#{a.join '/'}" } 26 | paths.each_index do |idx| 27 | path = paths.fetch(idx) 28 | puts " char *data#{idx} = \"#{path}\";" 29 | puts " r3_tree_insert_path(n, \"#{path}\", (void*) data#{idx});" 30 | end 31 | 32 | 33 | puts <data == data#{idx});" 44 | puts " ck_assert(m->endpoint > 0);" 45 | end 46 | 47 | 48 | 49 | puts < 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | #ifdef __GNUC__ 32 | #define R3_GNUC_VERSION ((__GNUC__ << 16) | (__GNUC_MINOR__ << 8) | __GNUC_PATCHLEVEL__) 33 | #else 34 | #define R3_GNUC_VERSION 0 35 | #endif 36 | 37 | #if __STDC_VERSION__ >= 201112L 38 | #define R3_NORETURN _Noreturn 39 | #elif defined(__clang__) || defined(__GNUC__) && R3_GNUC_VERSION >= 0x20500 40 | // noreturn was not defined before gcc 2.5 41 | #define R3_NORETURN __attribute__((noreturn)) 42 | #else 43 | #define R3_NORETURN 44 | #endif 45 | 46 | #if !defined(__clang__) && defined(__GNUC__) && R3_GNUC_VERSION >= 0x40900 47 | // returns_nonnull was seemingly not defined before gcc 4.9 (exists in 4.9.1 but not in 4.8.2) 48 | #define R3_RETURNS_NONNULL __attribute__((returns_nonnull)) 49 | #else 50 | #define R3_RETURNS_NONNULL 51 | #endif 52 | 53 | /** 54 | * buffer structure compatible with iovec 55 | */ 56 | typedef struct st_r3_iovec_t { 57 | const char *base; 58 | unsigned int len; 59 | } r3_iovec_t; 60 | 61 | #define R3_VECTOR(type) \ 62 | struct { \ 63 | type *entries; \ 64 | unsigned int size; \ 65 | unsigned int capacity; \ 66 | } 67 | 68 | typedef R3_VECTOR(void) r3_vector_t; 69 | 70 | /** 71 | * prints an error message and aborts 72 | */ 73 | R3_NORETURN void r3_fatal(const char *msg); 74 | 75 | /** 76 | * constructor for r3_iovec_t 77 | */ 78 | static r3_iovec_t r3_iovec_init(const void *base, unsigned int len); 79 | 80 | /** 81 | * wrapper of malloc; allocates given size of memory or dies if impossible 82 | */ 83 | R3_RETURNS_NONNULL static void *r3_mem_alloc(unsigned int sz); 84 | 85 | /** 86 | * wrapper of realloc; reallocs the given chunk or dies if impossible 87 | */ 88 | static void *r3_mem_realloc(void *oldp, unsigned int sz); 89 | 90 | /** 91 | * grows the vector so that it could store at least new_capacity elements of given size (or dies if impossible). 92 | * @param vector the vector 93 | * @param element_size size of the elements stored in the vector 94 | * @param new_capacity the capacity of the buffer after the function returns 95 | */ 96 | #define r3_vector_reserve(vector, new_capacity) \ 97 | r3_vector__reserve((r3_vector_t *)(void *)(vector), sizeof((vector)->entries[0]), (new_capacity)) 98 | static void r3_vector__reserve(r3_vector_t *vector, unsigned int element_size, unsigned int new_capacity); 99 | void r3_vector__expand(r3_vector_t *vector, unsigned int element_size, unsigned int new_capacity); 100 | 101 | /* inline defs */ 102 | 103 | inline r3_iovec_t r3_iovec_init(const void *base, unsigned int len) 104 | { 105 | /* intentionally declared to take a "const void*" since it may contain any type of data and since _some_ buffers are constant */ 106 | r3_iovec_t buf; 107 | buf.base = (char *)base; 108 | buf.len = len; 109 | return buf; 110 | } 111 | 112 | inline void *r3_mem_alloc(unsigned int sz) 113 | { 114 | void *p = malloc(sz); 115 | if (p == NULL) 116 | r3_fatal("no memory"); 117 | return p; 118 | } 119 | 120 | inline void *r3_mem_realloc(void *oldp, unsigned int sz) 121 | { 122 | void *newp = realloc(oldp, sz); 123 | if (newp == NULL) { 124 | r3_fatal("no memory"); 125 | return oldp; 126 | } 127 | return newp; 128 | } 129 | 130 | inline void r3_vector__reserve(r3_vector_t *vector, unsigned int element_size, unsigned int new_capacity) 131 | { 132 | if (vector->capacity < new_capacity) { 133 | r3_vector__expand(vector, element_size, new_capacity); 134 | } 135 | } 136 | 137 | #ifdef __cplusplus 138 | } 139 | #endif 140 | 141 | #endif 142 | -------------------------------------------------------------------------------- /include/r3.h: -------------------------------------------------------------------------------- 1 | /* 2 | * r3.h 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #ifndef R3_NODE_H 8 | #define R3_NODE_H 9 | 10 | #include 11 | #include 12 | #include 13 | #define PCRE2_CODE_UNIT_WIDTH 8 14 | #include 15 | 16 | #if __STDC_VERSION__ <= 201710L 17 | #ifdef HAVE_STDBOOL_H 18 | # include 19 | #elif !defined(bool) && !defined(__cplusplus) 20 | typedef unsigned char bool; 21 | # define bool bool /* For redefinition guards */ 22 | # define false 0 23 | # define true 1 24 | #endif 25 | #endif 26 | 27 | #include "str_array.h" 28 | #include "r3_slug.h" 29 | #include "memory.h" 30 | 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | struct _edge; 37 | struct _node; 38 | struct _route; 39 | typedef struct _edge R3Edge; 40 | typedef struct _node R3Node; 41 | typedef struct _R3Route R3Route; 42 | 43 | struct _node { 44 | R3_VECTOR(R3Edge) edges; 45 | R3_VECTOR(R3Route) routes; 46 | char * combined_pattern; 47 | pcre2_code * pcre_pattern; 48 | pcre2_match_data * match_data; 49 | 50 | // edges are mostly less than 255 51 | unsigned int compare_type; // compare_type: pcre, opcode, string 52 | unsigned int endpoint; // endpoint, should be zero for non-endpoint nodes 53 | 54 | // the pointer of R3Route data 55 | void * data; 56 | }; 57 | 58 | #define r3_node_edge_pattern(node,i) node->edges.entries[i].pattern.base 59 | #define r3_node_edge_pattern_len(node,i) node->edges.entries[i].pattern.len 60 | 61 | struct _edge { 62 | r3_iovec_t pattern; // 8 bytes 63 | R3Node * child; // 8 bytes 64 | unsigned int opcode; // 4byte 65 | unsigned int has_slug; // 4byte 66 | }; 67 | 68 | struct _R3Route { 69 | r3_iovec_t path; 70 | R3_VECTOR(r3_iovec_t) slugs; 71 | int request_method; // can be (GET || POST) 72 | r3_iovec_t host; // required host name 73 | 74 | void * data; 75 | 76 | r3_iovec_t remote_addr_pattern; 77 | 78 | unsigned int remote_addr_v4; 79 | int remote_addr_v4_bits; 80 | 81 | unsigned int remote_addr_v6[4]; 82 | int remote_addr_v6_bits[4]; 83 | 84 | int http_scheme; // can be (SCHEME_HTTP or SCHEME_HTTPS) 85 | 86 | }; 87 | 88 | typedef struct _R3Entry match_entry; 89 | struct _R3Entry { 90 | str_array vars; 91 | r3_iovec_t path; // current path to dispatch 92 | int request_method; // current request method 93 | 94 | void * data; // R3Route ptr 95 | 96 | r3_iovec_t host; // the request host 97 | r3_iovec_t remote_addr; 98 | 99 | int http_scheme; 100 | }; 101 | 102 | 103 | R3Node * r3_tree_create(int cap); 104 | 105 | // R3Node * r3_node_create(); 106 | 107 | void r3_tree_free(R3Node * tree); 108 | 109 | R3Edge * r3_node_connectl(R3Node * n, const char * pat, int len, int strdup, R3Node *child); 110 | 111 | #define r3_node_connect(n, pat, child) r3_node_connectl(n, pat, strlen(pat), 0, child) 112 | 113 | R3Edge * r3_node_find_edge(const R3Node * n, const char * pat, unsigned int pat_len); 114 | 115 | R3Edge * r3_node_append_edge(R3Node *n); 116 | 117 | R3Edge * r3_node_find_common_prefix(R3Node *n, const char *path, int path_len, int *prefix_len, char **errstr); 118 | 119 | R3Node * r3_tree_insert_pathl(R3Node *tree, const char *path, int path_len, void * data); 120 | 121 | #define r3_tree_insert_pathl(tree, path, path_len, data) r3_tree_insert_pathl_ex(tree, path, path_len, 0, 0, data, NULL) 122 | 123 | 124 | 125 | R3Route * r3_tree_insert_routel(R3Node * tree, int method, const char *path, int path_len, void *data); 126 | 127 | R3Route * r3_tree_insert_routel_ex(R3Node * tree, int method, const char *path, int path_len, void *data, char **errstr); 128 | 129 | #define r3_tree_insert_routel(n, method, path, path_len, data) r3_tree_insert_routel_ex(n, method, path, path_len, data, NULL) 130 | 131 | #define r3_tree_insert_path(n,p,d) r3_tree_insert_pathl_ex(n,p,strlen(p), 0, 0, d, NULL) 132 | 133 | #define r3_tree_insert_route(n,method,path,data) r3_tree_insert_routel(n, method, path, strlen(path), data) 134 | 135 | 136 | /** 137 | * The private API to insert a path 138 | */ 139 | R3Node * r3_tree_insert_pathl_ex(R3Node *tree, const char *path, unsigned int path_len, int method, unsigned int router, void * data, char **errstr); 140 | 141 | void r3_tree_dump(const R3Node * n, int level); 142 | 143 | 144 | R3Edge * r3_node_find_edge_str(const R3Node * n, const char * str, int str_len); 145 | 146 | 147 | int r3_tree_compile(R3Node *n, char** errstr); 148 | 149 | int r3_tree_compile_patterns(R3Node * n, char** errstr); 150 | 151 | R3Node * r3_tree_matchl(const R3Node * n, const char * path, unsigned int path_len, match_entry * entry); 152 | 153 | #define r3_tree_match(n,p,e) r3_tree_matchl(n,p, strlen(p), e) 154 | 155 | // R3Node * r3_tree_match_entry(R3Node * n, match_entry * entry); 156 | #define r3_tree_match_entry(n, entry) r3_tree_matchl(n, entry->path.base, entry->path.len, entry) 157 | 158 | bool r3_node_has_slug_edges(const R3Node *n); 159 | 160 | // R3Edge * r3_edge_createl(const char * pattern, int pattern_len, R3Node * child); 161 | 162 | void r3_edge_initl(R3Edge *e, const char * pattern, int pattern_len, R3Node * child); 163 | 164 | R3Node * r3_edge_branch(R3Edge *e, int dl); 165 | 166 | void r3_edge_free(R3Edge * edge); 167 | 168 | 169 | 170 | 171 | 172 | R3Route * r3_route_create(const char * path); 173 | 174 | // R3Route * r3_route_createl(const char * path, int path_len); 175 | 176 | 177 | R3Route * r3_node_append_route(R3Node *tree, const char * path, int path_len, int method, void *data); 178 | 179 | void r3_route_free(R3Route * route); 180 | 181 | int r3_route_cmp(const R3Route *r1, const match_entry *r2); 182 | 183 | R3Route * r3_tree_match_route(const R3Node *n, match_entry * entry); 184 | 185 | #define r3_route_create(p) r3_route_createl(p, strlen(p)) 186 | 187 | 188 | #define METHOD_GET 2 189 | #define METHOD_POST 2<<1 190 | #define METHOD_PUT 2<<2 191 | #define METHOD_DELETE 2<<3 192 | #define METHOD_PATCH 2<<4 193 | #define METHOD_HEAD 2<<5 194 | #define METHOD_OPTIONS 2<<6 195 | 196 | #define SCHEME_HTTP 2 197 | #define SCHEME_HTTPS 2<<1 198 | 199 | 200 | int r3_pattern_to_opcode(const char * pattern, unsigned int len); 201 | 202 | enum { NODE_COMPARE_STR, NODE_COMPARE_PCRE, NODE_COMPARE_OPCODE }; 203 | 204 | enum { OP_EXPECT_MORE_DIGITS = 1, OP_EXPECT_MORE_WORDS, OP_EXPECT_NOSLASH, 205 | OP_EXPECT_NODASH, OP_EXPECT_MORE_ALPHA, OP_GREEDY_ANY}; 206 | 207 | 208 | 209 | match_entry * match_entry_createl(const char * path, int path_len); 210 | 211 | #define match_entry_create(path) match_entry_createl(path,strlen(path)) 212 | 213 | void match_entry_free(match_entry * entry); 214 | 215 | 216 | 217 | 218 | #ifdef __cplusplus 219 | } 220 | #endif 221 | 222 | 223 | 224 | 225 | #endif /* !R3_NODE_H */ 226 | -------------------------------------------------------------------------------- /include/r3.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * r3.hpp 3 | * Copyright (C) 2014 whitglint 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #ifndef R3_HPP 8 | #define R3_HPP 9 | 10 | #include 11 | #include "r3.h" 12 | 13 | namespace r3 { 14 | template 15 | class Base { 16 | public: 17 | Base(T* p) 18 | : p_(p) { 19 | } 20 | 21 | void* data() const { 22 | return p_->data; 23 | } 24 | 25 | T* get() const { 26 | return p_; 27 | } 28 | 29 | bool is_null() const { 30 | return p_ == NULL; 31 | } 32 | 33 | operator void*() const { 34 | return p_; 35 | } 36 | 37 | private: 38 | T* p_; 39 | }; 40 | typedef Base Node; 41 | typedef Base Route; 42 | 43 | class MatchEntry : public Base { 44 | public: 45 | explicit MatchEntry(const char* path) 46 | : Base(match_entry_create(path)) { 47 | } 48 | 49 | MatchEntry(const char* path, int path_len) 50 | : Base(match_entry_createl(path, path_len)) { 51 | } 52 | 53 | ~MatchEntry() { 54 | if (get()) { 55 | match_entry_free(get()); 56 | } 57 | } 58 | 59 | int request_method() const { 60 | return get()->request_method; 61 | } 62 | 63 | void set_request_method(int request_method) { 64 | get()->request_method = request_method; 65 | } 66 | 67 | private: 68 | MatchEntry(const MatchEntry&); 69 | MatchEntry& operator =(const MatchEntry&); 70 | }; 71 | 72 | class Tree : public Base { 73 | public: 74 | explicit Tree(int cap) 75 | : Base(r3_tree_create(cap)) { 76 | } 77 | 78 | ~Tree() { 79 | if (get()) { 80 | r3_tree_free(get()); 81 | } 82 | } 83 | 84 | int compile(char** errstr = NULL) { 85 | return r3_tree_compile(get(), errstr); 86 | } 87 | 88 | void dump(int level) const { 89 | r3_tree_dump(get(), level); 90 | } 91 | 92 | Node insert_path(const char* path, void* data, char** errstr = NULL) { 93 | return r3_tree_insert_pathl_ex(get(), path, std::strlen(path), 0, 0, 94 | data, errstr); 95 | } 96 | 97 | Node insert_pathl(const char* path, int path_len, void* data, 98 | char** errstr = NULL) { 99 | return r3_tree_insert_pathl_ex(get(), path, path_len, 0, 0, data, 100 | errstr); 101 | } 102 | 103 | Route insert_route(int method, const char* path, void* data, 104 | char** errstr = NULL) { 105 | return r3_tree_insert_routel_ex(get(), method, path, 106 | std::strlen(path), data, errstr); 107 | } 108 | 109 | Route insert_routel(int method, const char* path, int path_len, 110 | void* data, char** errstr = NULL) { 111 | return r3_tree_insert_routel_ex(get(), method, path, path_len, data, 112 | errstr); 113 | } 114 | 115 | Node match(const char* path, MatchEntry* entry = NULL) const { 116 | return r3_tree_match(get(), path, 117 | entry != NULL ? entry->get() : NULL); 118 | } 119 | 120 | Node matchl(const char* path, int path_len, MatchEntry* entry = NULL) 121 | const { 122 | return r3_tree_matchl(get(), path, path_len, 123 | entry != NULL ? entry->get() : NULL); 124 | } 125 | 126 | Node match_entry(MatchEntry& entry) const { 127 | return r3_tree_match_entry(get(), entry.get()); 128 | } 129 | 130 | Route match_route(MatchEntry& entry) const { 131 | return r3_tree_match_route(get(), entry.get()); 132 | } 133 | 134 | private: 135 | Tree(const Tree&); 136 | Tree& operator =(const Tree&); 137 | }; 138 | } 139 | 140 | #endif // R3_HPP 141 | -------------------------------------------------------------------------------- /include/r3_gvc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * r3_gvc.h 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #ifndef R3_GVC_H 8 | #define R3_GVC_H 9 | 10 | #include 11 | #include 12 | #include "r3.h" 13 | 14 | void r3_tree_build_ag_nodes(Agraph_t * g, Agnode_t * ag_parent_node, const node * n, int * node_cnt); 15 | 16 | int r3_tree_render(const node * tree, const char *layout, const char * format, FILE *fp); 17 | 18 | int r3_tree_render_dot(const node * tree, const char *layout, FILE *fp); 19 | 20 | int r3_tree_render_file(const node * tree, const char * format, const char * filename); 21 | 22 | 23 | #endif /* !R3_GVC_H */ 24 | -------------------------------------------------------------------------------- /include/r3_json.h: -------------------------------------------------------------------------------- 1 | /* 2 | * r3_json.h 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef R3_JSON_H 9 | #define R3_JSON_H 10 | 11 | #include 12 | #include "r3.h" 13 | 14 | json_object * r3_edge_to_json_object(const R3Edge * e); 15 | json_object * r3_node_to_json_object(const R3Node * n); 16 | json_object * r3_route_to_json_object(const R3Route * r); 17 | 18 | const char * r3_node_to_json_string_ext(const R3Node * n, int options); 19 | const char * r3_node_to_json_pretty_string(const R3Node * n); 20 | const char * r3_node_to_json_string(const R3Node * n); 21 | 22 | 23 | 24 | #endif /* !R3_JSON_H */ 25 | -------------------------------------------------------------------------------- /include/r3_list.h: -------------------------------------------------------------------------------- 1 | /* 2 | * r3_list.h 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef R3_LIST_H 9 | #define R3_LIST_H 10 | 11 | #include 12 | 13 | typedef struct _list_item { 14 | void *value; 15 | struct _list_item *prev; 16 | struct _list_item *next; 17 | } list_item; 18 | 19 | typedef struct { 20 | int count; 21 | list_item *head; 22 | list_item *tail; 23 | pthread_mutex_t mutex; 24 | } list; 25 | 26 | list *list_create(); 27 | void list_free(list *l); 28 | 29 | list_item *list_add_element(list *l, void *ptr); 30 | int list_remove_element(list *l, void *ptr); 31 | void list_each_element(list *l, int (*func)(list_item *)); 32 | 33 | 34 | 35 | #endif /* !R3_LIST_H */ 36 | -------------------------------------------------------------------------------- /include/r3_slug.h: -------------------------------------------------------------------------------- 1 | /* 2 | * r3_str.h 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #ifndef R3_SLUG_H 8 | #define R3_SLUG_H 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | char * r3_slug_compile(const char * str, unsigned int len); 15 | 16 | const char * r3_slug_find_pattern(const char *s1, unsigned int str_len, unsigned int *len); 17 | 18 | const char * r3_slug_find_name(const char *s1, unsigned int str_len, unsigned int *len); 19 | 20 | const char * r3_slug_find_placeholder(const char *s1, unsigned int str_len, unsigned int *len); 21 | 22 | int r3_slug_count(const char * needle, int len, char **errstr); 23 | 24 | char * r3_inside_slug(const char * needle, int needle_len, char *offset, char **errstr); 25 | 26 | #ifdef __cplusplus 27 | } 28 | #endif 29 | 30 | #endif /* !R3_SLUG_H */ 31 | -------------------------------------------------------------------------------- /include/str_array.h: -------------------------------------------------------------------------------- 1 | /* 2 | * str_array.h 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef STR_ARRAY_H 9 | #define STR_ARRAY_H 10 | 11 | #include "memory.h" 12 | 13 | #if __STDC_VERSION__ <= 201710L 14 | #ifdef HAVE_STDBOOL_H 15 | # include 16 | #elif !defined(bool) && !defined(__cplusplus) 17 | typedef unsigned char bool; 18 | # define bool bool /* For redefinition guards */ 19 | # define false 0 20 | # define true 1 21 | #endif 22 | #endif 23 | 24 | typedef struct _str_array { 25 | R3_VECTOR(r3_iovec_t) slugs; 26 | R3_VECTOR(r3_iovec_t) tokens; 27 | } str_array; 28 | 29 | bool str_array_append(str_array * l, const char * token, unsigned int len); 30 | 31 | void str_array_free(str_array *l); 32 | 33 | void str_array_dump_slugs(const str_array *l); 34 | 35 | void str_array_dump(const str_array *l); 36 | 37 | #define str_array_fetch(t,i) t->tokens.entries[i] 38 | #define str_array_len(t) t->tokens.size 39 | #define str_array_cap(t) t->tokens.capacity 40 | 41 | #endif /* !STR_ARRAY_H */ 42 | -------------------------------------------------------------------------------- /m4/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c9s/r3/967e74a4a22f7fffadc57105a6c43601c4813612/m4/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r3", 3 | "version": "2.0.0", 4 | "repo": "brendanashworth/r3", 5 | "description": "high-performance path dispatching library", 6 | "keywords": ["path", "dispatch", "performance", "r3", "c9s"], 7 | "license": "MIT", 8 | "src": ["include/memory.h", "include/r3.h", "include/r3.hpp", "include/r3_gvc.h", "include/r3_json.h", "include/r3_list.h", "include/r3_slug.h", "include/str_array.h", "src/edge.c", "src/gvc.c", "src/json.c", "src/list.c", "src/match_entry.c", "src/node.c", "src/slug.c", "src/slug.h", "src/str.c", "src/token.c"] 9 | } 10 | -------------------------------------------------------------------------------- /php/r3/annotation/annot.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | +------------------------------------------------------------------------+ 4 | | Phalcon Framework | 5 | +------------------------------------------------------------------------+ 6 | | Copyright (c) 2011-2014 Phalcon Team (http://www.phalconphp.com) | 7 | +------------------------------------------------------------------------+ 8 | | This source file is subject to the New BSD License that is bundled | 9 | | with this package in the file docs/LICENSE.txt. | 10 | | | 11 | | If you did not receive a copy of the license and are unable to | 12 | | obtain it through the world-wide-web, please send an email | 13 | | to license@phalconphp.com so we can send you a copy immediately. | 14 | +------------------------------------------------------------------------+ 15 | | Authors: Andres Gutierrez | 16 | | Eduar Carvajal | 17 | +------------------------------------------------------------------------+ 18 | */ 19 | 20 | typedef struct _phannot_parser_token { 21 | char *token; 22 | int opcode; 23 | int token_len; 24 | int free_flag; 25 | } phannot_parser_token; 26 | 27 | typedef struct _phannot_parser_status { 28 | zval *ret; 29 | phannot_scanner_state *scanner_state; 30 | phannot_scanner_token *token; 31 | int status; 32 | zend_uint syntax_error_len; 33 | char *syntax_error; 34 | } phannot_parser_status; 35 | 36 | #define PHANNOT_PARSING_OK 1 37 | #define PHANNOT_PARSING_FAILED 0 38 | 39 | extern int phannot_parse_annotations(zval *result, zval *view_code, zval *template_path, zval *line TSRMLS_DC); 40 | int phannot_internal_parse_annotations(zval **result, zval *view_code, zval *template_path, zval *line, zval **error_msg TSRMLS_DC); 41 | -------------------------------------------------------------------------------- /php/r3/annotation/lemon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c9s/r3/967e74a4a22f7fffadc57105a6c43601c4813612/php/r3/annotation/lemon -------------------------------------------------------------------------------- /php/r3/annotation/parser.h: -------------------------------------------------------------------------------- 1 | #define PHANNOT_COMMA 1 2 | #define PHANNOT_AT 2 3 | #define PHANNOT_IDENTIFIER 3 4 | #define PHANNOT_PARENTHESES_OPEN 4 5 | #define PHANNOT_PARENTHESES_CLOSE 5 6 | #define PHANNOT_STRING 6 7 | #define PHANNOT_EQUALS 7 8 | #define PHANNOT_COLON 8 9 | #define PHANNOT_INTEGER 9 10 | #define PHANNOT_DOUBLE 10 11 | #define PHANNOT_NULL 11 12 | #define PHANNOT_FALSE 12 13 | #define PHANNOT_TRUE 13 14 | #define PHANNOT_BRACKET_OPEN 14 15 | #define PHANNOT_BRACKET_CLOSE 15 16 | #define PHANNOT_SBRACKET_OPEN 16 17 | #define PHANNOT_SBRACKET_CLOSE 17 18 | -------------------------------------------------------------------------------- /php/r3/annotation/parser.lemon: -------------------------------------------------------------------------------- 1 | /* 2 | +------------------------------------------------------------------------+ 3 | | Phalcon Framework | 4 | +------------------------------------------------------------------------+ 5 | | Copyright (c) 2011-2014 Phalcon Team (http://www.phalconphp.com) | 6 | +------------------------------------------------------------------------+ 7 | | This source file is subject to the New BSD License that is bundled | 8 | | with this package in the file docs/LICENSE.txt. | 9 | | | 10 | | If you did not receive a copy of the license and are unable to | 11 | | obtain it through the world-wide-web, please send an email | 12 | | to license@phalconphp.com so we can send you a copy immediately. | 13 | +------------------------------------------------------------------------+ 14 | | Authors: Andres Gutierrez | 15 | | Eduar Carvajal | 16 | +------------------------------------------------------------------------+ 17 | */ 18 | 19 | %token_prefix PHANNOT_ 20 | %token_type {phannot_parser_token*} 21 | %default_type {zval*} 22 | %extra_argument {phannot_parser_status *status} 23 | %name phannot_ 24 | 25 | %left COMMA . 26 | 27 | %include { 28 | 29 | #ifdef HAVE_CONFIG_H 30 | #include "config.h" 31 | #endif 32 | 33 | #include "php.h" 34 | #include "ext/standard/php_smart_str.h" 35 | #include "Zend/zend_exceptions.h" 36 | 37 | #include "parser.h" 38 | #include "scanner.h" 39 | #include "annot.h" 40 | 41 | static zval *phannot_ret_literal_zval(int type, phannot_parser_token *T) 42 | { 43 | zval *ret; 44 | 45 | MAKE_STD_ZVAL(ret); 46 | array_init(ret); 47 | add_assoc_long(ret, "type", type); 48 | if (T) { 49 | add_assoc_stringl(ret, "value", T->token, T->token_len, 0); 50 | efree(T); 51 | } 52 | 53 | return ret; 54 | } 55 | 56 | static zval *phannot_ret_array(zval *items) 57 | { 58 | zval *ret; 59 | 60 | MAKE_STD_ZVAL(ret); 61 | array_init(ret); 62 | add_assoc_long(ret, "type", PHANNOT_T_ARRAY); 63 | 64 | if (items) { 65 | add_assoc_zval(ret, "items", items); 66 | } 67 | 68 | return ret; 69 | } 70 | 71 | static zval *phannot_ret_zval_list(zval *list_left, zval *right_list) 72 | { 73 | 74 | zval *ret; 75 | HashPosition pos; 76 | HashTable *list; 77 | 78 | MAKE_STD_ZVAL(ret); 79 | array_init(ret); 80 | 81 | if (list_left) { 82 | 83 | list = Z_ARRVAL_P(list_left); 84 | if (zend_hash_index_exists(list, 0)) { 85 | zend_hash_internal_pointer_reset_ex(list, &pos); 86 | for (;; zend_hash_move_forward_ex(list, &pos)) { 87 | 88 | zval ** item; 89 | 90 | if (zend_hash_get_current_data_ex(list, (void**) &item, &pos) == FAILURE) { 91 | break; 92 | } 93 | 94 | Z_ADDREF_PP(item); 95 | add_next_index_zval(ret, *item); 96 | 97 | } 98 | zval_ptr_dtor(&list_left); 99 | } else { 100 | add_next_index_zval(ret, list_left); 101 | } 102 | } 103 | 104 | add_next_index_zval(ret, right_list); 105 | 106 | return ret; 107 | } 108 | 109 | static zval *phannot_ret_named_item(phannot_parser_token *name, zval *expr) 110 | { 111 | zval *ret; 112 | 113 | MAKE_STD_ZVAL(ret); 114 | array_init(ret); 115 | add_assoc_zval(ret, "expr", expr); 116 | if (name != NULL) { 117 | add_assoc_stringl(ret, "name", name->token, name->token_len, 0); 118 | efree(name); 119 | } 120 | 121 | return ret; 122 | } 123 | 124 | static zval *phannot_ret_annotation(phannot_parser_token *name, zval *arguments, phannot_scanner_state *state) 125 | { 126 | 127 | zval *ret; 128 | 129 | MAKE_STD_ZVAL(ret); 130 | array_init(ret); 131 | 132 | add_assoc_long(ret, "type", PHANNOT_T_ANNOTATION); 133 | 134 | if (name) { 135 | add_assoc_stringl(ret, "name", name->token, name->token_len, 0); 136 | efree(name); 137 | } 138 | 139 | if (arguments) { 140 | add_assoc_zval(ret, "arguments", arguments); 141 | } 142 | 143 | Z_ADDREF_P(state->active_file); 144 | add_assoc_zval(ret, "file", state->active_file); 145 | add_assoc_long(ret, "line", state->active_line); 146 | 147 | return ret; 148 | } 149 | 150 | } 151 | 152 | %syntax_error { 153 | if (status->scanner_state->start_length) { 154 | { 155 | 156 | char *token_name = NULL; 157 | const phannot_token_names *tokens = phannot_tokens; 158 | int token_found = 0; 159 | int active_token = status->scanner_state->active_token; 160 | int near_length = status->scanner_state->start_length; 161 | 162 | if (active_token) { 163 | do { 164 | if (tokens->code == active_token) { 165 | token_found = 1; 166 | token_name = tokens->name; 167 | break; 168 | } 169 | ++tokens; 170 | } while (tokens[0].code != 0); 171 | } 172 | 173 | if (!token_name) { 174 | token_found = 0; 175 | token_name = estrndup("UNKNOWN", strlen("UNKNOWN")); 176 | } 177 | 178 | status->syntax_error_len = 128 + strlen(token_name) + Z_STRLEN_P(status->scanner_state->active_file); 179 | status->syntax_error = emalloc(sizeof(char) * status->syntax_error_len); 180 | 181 | if (near_length > 0) { 182 | if (status->token->value) { 183 | snprintf(status->syntax_error, status->syntax_error_len, "Syntax error, unexpected token %s(%s), near to '%s' in %s on line %d", token_name, status->token->value, status->scanner_state->start, Z_STRVAL_P(status->scanner_state->active_file), status->scanner_state->active_line); 184 | } else { 185 | snprintf(status->syntax_error, status->syntax_error_len, "Syntax error, unexpected token %s, near to '%s' in %s on line %d", token_name, status->scanner_state->start, Z_STRVAL_P(status->scanner_state->active_file), status->scanner_state->active_line); 186 | } 187 | } else { 188 | if (active_token != PHANNOT_T_IGNORE) { 189 | if (status->token->value) { 190 | snprintf(status->syntax_error, status->syntax_error_len, "Syntax error, unexpected token %s(%s), at the end of docblock in %s on line %d", token_name, status->token->value, Z_STRVAL_P(status->scanner_state->active_file), status->scanner_state->active_line); 191 | } else { 192 | snprintf(status->syntax_error, status->syntax_error_len, "Syntax error, unexpected token %s, at the end of docblock in %s on line %d", token_name, Z_STRVAL_P(status->scanner_state->active_file), status->scanner_state->active_line); 193 | } 194 | } else { 195 | snprintf(status->syntax_error, status->syntax_error_len, "Syntax error, unexpected EOF, at the end of docblock in %s on line %d", Z_STRVAL_P(status->scanner_state->active_file), status->scanner_state->active_line); 196 | } 197 | status->syntax_error[status->syntax_error_len-1] = '\0'; 198 | } 199 | 200 | if (!token_found) { 201 | if (token_name) { 202 | efree(token_name); 203 | } 204 | } 205 | } 206 | } else { 207 | status->syntax_error_len = 48 + Z_STRLEN_P(status->scanner_state->active_file); 208 | status->syntax_error = emalloc(sizeof(char) * status->syntax_error_len); 209 | sprintf(status->syntax_error, "Syntax error, unexpected EOF in %s", Z_STRVAL_P(status->scanner_state->active_file)); 210 | } 211 | 212 | status->status = PHANNOT_PARSING_FAILED; 213 | } 214 | 215 | %token_destructor { 216 | if ($$) { 217 | if ($$->free_flag) { 218 | efree($$->token); 219 | } 220 | efree($$); 221 | } 222 | } 223 | 224 | program ::= annotation_language(Q) . { 225 | status->ret = Q; 226 | } 227 | 228 | %destructor annotation_language { zval_ptr_dtor(&$$); } 229 | 230 | annotation_language(R) ::= annotation_list(L) . { 231 | R = L; 232 | } 233 | 234 | %destructor annotation_list { zval_ptr_dtor(&$$); } 235 | 236 | annotation_list(R) ::= annotation_list(L) annotation(S) . { 237 | R = phannot_ret_zval_list(L, S); 238 | } 239 | 240 | annotation_list(R) ::= annotation(S) . { 241 | R = phannot_ret_zval_list(NULL, S); 242 | } 243 | 244 | 245 | %destructor annotation { zval_ptr_dtor(&$$); } 246 | 247 | annotation(R) ::= AT IDENTIFIER(I) PARENTHESES_OPEN argument_list(L) PARENTHESES_CLOSE . { 248 | R = phannot_ret_annotation(I, L, status->scanner_state); 249 | } 250 | 251 | annotation(R) ::= AT IDENTIFIER(I) PARENTHESES_OPEN PARENTHESES_CLOSE . { 252 | R = phannot_ret_annotation(I, NULL, status->scanner_state); 253 | } 254 | 255 | annotation(R) ::= AT IDENTIFIER(I) . { 256 | R = phannot_ret_annotation(I, NULL, status->scanner_state); 257 | } 258 | 259 | %destructor argument_list { zval_ptr_dtor(&$$); } 260 | 261 | argument_list(R) ::= argument_list(L) COMMA argument_item(I) . { 262 | R = phannot_ret_zval_list(L, I); 263 | } 264 | 265 | argument_list(R) ::= argument_item(I) . { 266 | R = phannot_ret_zval_list(NULL, I); 267 | } 268 | 269 | %destructor argument_item { zval_ptr_dtor(&$$); } 270 | 271 | argument_item(R) ::= expr(E) . { 272 | R = phannot_ret_named_item(NULL, E); 273 | } 274 | 275 | argument_item(R) ::= STRING(S) EQUALS expr(E) . { 276 | R = phannot_ret_named_item(S, E); 277 | } 278 | 279 | argument_item(R) ::= STRING(S) COLON expr(E) . { 280 | R = phannot_ret_named_item(S, E); 281 | } 282 | 283 | argument_item(R) ::= IDENTIFIER(I) EQUALS expr(E) . { 284 | R = phannot_ret_named_item(I, E); 285 | } 286 | 287 | argument_item(R) ::= IDENTIFIER(I) COLON expr(E) . { 288 | R = phannot_ret_named_item(I, E); 289 | } 290 | 291 | %destructor expr { zval_ptr_dtor(&$$); } 292 | 293 | expr(R) ::= annotation(S) . { 294 | R = S; 295 | } 296 | 297 | expr(R) ::= array(A) . { 298 | R = A; 299 | } 300 | 301 | expr(R) ::= IDENTIFIER(I) . { 302 | R = phannot_ret_literal_zval(PHANNOT_T_IDENTIFIER, I); 303 | } 304 | 305 | expr(R) ::= INTEGER(I) . { 306 | R = phannot_ret_literal_zval(PHANNOT_T_INTEGER, I); 307 | } 308 | 309 | expr(R) ::= STRING(S) . { 310 | R = phannot_ret_literal_zval(PHANNOT_T_STRING, S); 311 | } 312 | 313 | expr(R) ::= DOUBLE(D) . { 314 | R = phannot_ret_literal_zval(PHANNOT_T_DOUBLE, D); 315 | } 316 | 317 | expr(R) ::= NULL . { 318 | R = phannot_ret_literal_zval(PHANNOT_T_NULL, NULL); 319 | } 320 | 321 | expr(R) ::= FALSE . { 322 | R = phannot_ret_literal_zval(PHANNOT_T_FALSE, NULL); 323 | } 324 | 325 | expr(R) ::= TRUE . { 326 | R = phannot_ret_literal_zval(PHANNOT_T_TRUE, NULL); 327 | } 328 | 329 | array(R) ::= BRACKET_OPEN argument_list(A) BRACKET_CLOSE . { 330 | R = phannot_ret_array(A); 331 | } 332 | 333 | array(R) ::= SBRACKET_OPEN argument_list(A) SBRACKET_CLOSE . { 334 | R = phannot_ret_array(A); 335 | } 336 | -------------------------------------------------------------------------------- /php/r3/annotation/scanner.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | +------------------------------------------------------------------------+ 4 | | Phalcon Framework | 5 | +------------------------------------------------------------------------+ 6 | | Copyright (c) 2011-2014 Phalcon Team (http://www.phalconphp.com) | 7 | +------------------------------------------------------------------------+ 8 | | This source file is subject to the New BSD License that is bundled | 9 | | with this package in the file docs/LICENSE.txt. | 10 | | | 11 | | If you did not receive a copy of the license and are unable to | 12 | | obtain it through the world-wide-web, please send an email | 13 | | to license@phalconphp.com so we can send you a copy immediately. | 14 | +------------------------------------------------------------------------+ 15 | | Authors: Andres Gutierrez | 16 | | Eduar Carvajal | 17 | +------------------------------------------------------------------------+ 18 | */ 19 | 20 | #define PHANNOT_SCANNER_RETCODE_EOF -1 21 | #define PHANNOT_SCANNER_RETCODE_ERR -2 22 | #define PHANNOT_SCANNER_RETCODE_IMPOSSIBLE -3 23 | 24 | /** Modes */ 25 | #define PHANNOT_MODE_RAW 0 26 | #define PHANNOT_MODE_ANNOTATION 1 27 | 28 | #define PHANNOT_T_IGNORE 297 29 | 30 | #define PHANNOT_T_DOCBLOCK_ANNOTATION 299 31 | #define PHANNOT_T_ANNOTATION 300 32 | 33 | /* Literals & Identifiers */ 34 | #define PHANNOT_T_INTEGER 301 35 | #define PHANNOT_T_DOUBLE 302 36 | #define PHANNOT_T_STRING 303 37 | #define PHANNOT_T_NULL 304 38 | #define PHANNOT_T_FALSE 305 39 | #define PHANNOT_T_TRUE 306 40 | #define PHANNOT_T_IDENTIFIER 307 41 | #define PHANNOT_T_ARRAY 308 42 | #define PHANNOT_T_ARBITRARY_TEXT 309 43 | 44 | /* Operators */ 45 | #define PHANNOT_T_AT '@' 46 | #define PHANNOT_T_DOT '.' 47 | #define PHANNOT_T_COMMA ',' 48 | #define PHANNOT_T_EQUALS '=' 49 | #define PHANNOT_T_COLON ':' 50 | #define PHANNOT_T_BRACKET_OPEN '{' 51 | #define PHANNOT_T_BRACKET_CLOSE '}' 52 | #define PHANNOT_T_SBRACKET_OPEN '[' 53 | #define PHANNOT_T_SBRACKET_CLOSE ']' 54 | #define PHANNOT_T_PARENTHESES_OPEN '(' 55 | #define PHANNOT_T_PARENTHESES_CLOSE ')' 56 | 57 | /* List of tokens and their names */ 58 | typedef struct _phannot_token_names { 59 | char *name; 60 | unsigned int code; 61 | } phannot_token_names; 62 | 63 | /* Active token state */ 64 | typedef struct _phannot_scanner_state { 65 | char* start; 66 | char* end; 67 | int active_token; 68 | unsigned int start_length; 69 | int mode; 70 | unsigned int active_line; 71 | zval *active_file; 72 | } phannot_scanner_state; 73 | 74 | /* Extra information tokens */ 75 | typedef struct _phannot_scanner_token { 76 | char *value; 77 | int opcode; 78 | int len; 79 | } phannot_scanner_token; 80 | 81 | int phannot_get_token(phannot_scanner_state *s, phannot_scanner_token *token); 82 | 83 | extern const phannot_token_names phannot_tokens[]; 84 | -------------------------------------------------------------------------------- /php/r3/annotation/scanner.re: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | +------------------------------------------------------------------------+ 4 | | Phalcon Framework | 5 | +------------------------------------------------------------------------+ 6 | | Copyright (c) 2011-2014 Phalcon Team (http://www.phalconphp.com) | 7 | +------------------------------------------------------------------------+ 8 | | This source file is subject to the New BSD License that is bundled | 9 | | with this package in the file docs/LICENSE.txt. | 10 | | | 11 | | If you did not receive a copy of the license and are unable to | 12 | | obtain it through the world-wide-web, please send an email | 13 | | to license@phalconphp.com so we can send you a copy immediately. | 14 | +------------------------------------------------------------------------+ 15 | | Authors: Andres Gutierrez | 16 | | Eduar Carvajal | 17 | +------------------------------------------------------------------------+ 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include "php.h" 25 | 26 | #include "scanner.h" 27 | 28 | #define YYCTYPE unsigned char 29 | #define YYCURSOR (s->start) 30 | #define YYLIMIT (s->end) 31 | #define YYMARKER q 32 | 33 | int phannot_get_token(phannot_scanner_state *s, phannot_scanner_token *token) { 34 | 35 | char next, *q = YYCURSOR, *start = YYCURSOR; 36 | int status = PHANNOT_SCANNER_RETCODE_IMPOSSIBLE; 37 | 38 | while (PHANNOT_SCANNER_RETCODE_IMPOSSIBLE == status) { 39 | 40 | if (s->mode == PHANNOT_MODE_RAW) { 41 | 42 | if (*YYCURSOR == '\n') { 43 | s->active_line++; 44 | } 45 | 46 | next = *(YYCURSOR+1); 47 | 48 | if (*YYCURSOR == '\0' || *YYCURSOR == '@') { 49 | if ((next >= 'A' && next <= 'Z') || (next >= 'a' && next <= 'z')) { 50 | s->mode = PHANNOT_MODE_ANNOTATION; 51 | continue; 52 | } 53 | } 54 | 55 | ++YYCURSOR; 56 | token->opcode = PHANNOT_T_IGNORE; 57 | return 0; 58 | 59 | } else { 60 | 61 | /*!re2c 62 | re2c:indent:top = 2; 63 | re2c:yyfill:enable = 0; 64 | 65 | INTEGER = [\-]?[0-9]+; 66 | INTEGER { 67 | token->opcode = PHANNOT_T_INTEGER; 68 | token->value = estrndup(start, YYCURSOR - start); 69 | token->len = YYCURSOR - start; 70 | q = YYCURSOR; 71 | return 0; 72 | } 73 | 74 | DOUBLE = ([\-]?[0-9]+[\.][0-9]+); 75 | DOUBLE { 76 | token->opcode = PHANNOT_T_DOUBLE; 77 | token->value = estrndup(start, YYCURSOR - start); 78 | token->len = YYCURSOR - start; 79 | q = YYCURSOR; 80 | return 0; 81 | } 82 | 83 | 'null' { 84 | token->opcode = PHANNOT_T_NULL; 85 | return 0; 86 | } 87 | 88 | 'false' { 89 | token->opcode = PHANNOT_T_FALSE; 90 | return 0; 91 | } 92 | 93 | 'true' { 94 | token->opcode = PHANNOT_T_TRUE; 95 | return 0; 96 | } 97 | 98 | STRING = (["] ([\\]["]|[\\].|[\001-\377]\[\\"])* ["])|(['] ([\\][']|[\\].|[\001-\377]\[\\'])* [']); 99 | STRING { 100 | token->opcode = PHANNOT_T_STRING; 101 | token->value = estrndup(q, YYCURSOR - q - 1); 102 | token->len = YYCURSOR - q - 1; 103 | q = YYCURSOR; 104 | return 0; 105 | } 106 | 107 | IDENTIFIER = ('\x5C'?[a-zA-Z_]([a-zA-Z0-9_]*)('\x5C'[a-zA-Z_]([a-zA-Z0-9_]*))*); 108 | IDENTIFIER { 109 | token->opcode = PHANNOT_T_IDENTIFIER; 110 | token->value = estrndup(start, YYCURSOR - start); 111 | token->len = YYCURSOR - start; 112 | q = YYCURSOR; 113 | return 0; 114 | } 115 | 116 | "(" { 117 | token->opcode = PHANNOT_T_PARENTHESES_OPEN; 118 | return 0; 119 | } 120 | 121 | ")" { 122 | token->opcode = PHANNOT_T_PARENTHESES_CLOSE; 123 | return 0; 124 | } 125 | 126 | "{" { 127 | token->opcode = PHANNOT_T_BRACKET_OPEN; 128 | return 0; 129 | } 130 | 131 | "}" { 132 | token->opcode = PHANNOT_T_BRACKET_CLOSE; 133 | return 0; 134 | } 135 | 136 | "[" { 137 | token->opcode = PHANNOT_T_SBRACKET_OPEN; 138 | return 0; 139 | } 140 | 141 | "]" { 142 | token->opcode = PHANNOT_T_SBRACKET_CLOSE; 143 | return 0; 144 | } 145 | 146 | "@" { 147 | token->opcode = PHANNOT_T_AT; 148 | return 0; 149 | } 150 | 151 | "=" { 152 | token->opcode = PHANNOT_T_EQUALS; 153 | return 0; 154 | } 155 | 156 | ":" { 157 | token->opcode = PHANNOT_T_COLON; 158 | return 0; 159 | } 160 | 161 | "," { 162 | token->opcode = PHANNOT_T_COMMA; 163 | return 0; 164 | } 165 | 166 | [ \t\r]+ { 167 | token->opcode = PHANNOT_T_IGNORE; 168 | return 0; 169 | } 170 | 171 | [\n] { 172 | s->active_line++; 173 | token->opcode = PHANNOT_T_IGNORE; 174 | return 0; 175 | } 176 | 177 | "\000" { 178 | status = PHANNOT_SCANNER_RETCODE_EOF; 179 | break; 180 | } 181 | 182 | [^] { 183 | status = PHANNOT_SCANNER_RETCODE_ERR; 184 | break; 185 | } 186 | 187 | */ 188 | 189 | } 190 | } 191 | 192 | return status; 193 | } 194 | -------------------------------------------------------------------------------- /php/r3/config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 for extension r3 2 | 3 | PHP_ARG_WITH(r3, for r3 support, 4 | [ --with-r3 Include r3 support]) 5 | 6 | dnl PHP_ARG_ENABLE(r3, whether to enable r3 support, 7 | dnl Make sure that the comment is aligned: 8 | dnl [ --enable-r3 Enable r3 support]) 9 | 10 | if test "$PHP_R3" != "no"; then 11 | SEARCH_PATH="/usr/local /usr" 12 | SEARCH_FOR="/include/r3/r3.h" 13 | if test -r $PHP_R3/$SEARCH_FOR; then 14 | R3_DIR=$PHP_R3 15 | else 16 | AC_MSG_CHECKING([for r3 files in default path]) 17 | for i in $SEARCH_PATH ; do 18 | if test -r $i/$SEARCH_FOR; then 19 | R3_DIR=$i 20 | AC_MSG_RESULT(found in $i) 21 | fi 22 | done 23 | fi 24 | 25 | if test -z "$R3_DIR"; then 26 | AC_MSG_RESULT([not found]) 27 | AC_MSG_ERROR([Please reinstall the r3 distribution]) 28 | fi 29 | 30 | echo $R3_DIR 31 | dnl # --with-r3 -> add include path 32 | PHP_ADD_INCLUDE($R3_DIR/include) 33 | 34 | LIBNAME=r3 35 | LIBSYMBOL=r3_route_create 36 | 37 | PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, 38 | [ 39 | PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $R3_DIR/lib, R3_SHARED_LIBADD) 40 | AC_DEFINE(HAVE_R3LIB,1,[ ]) 41 | ],[ 42 | AC_MSG_ERROR([wrong r3 lib version or lib not found]) 43 | ],[ 44 | -L$R3_DIR/lib -lm 45 | ]) 46 | 47 | PHP_SUBST(R3_SHARED_LIBADD) 48 | 49 | PHP_NEW_EXTENSION(r3, [ct_helper.c hash.c php_expandable_mux.c php_r3.c r3_controller.c r3_functions.c r3_mux.c r3_persistent.c], $ext_shared) 50 | fi 51 | -------------------------------------------------------------------------------- /php/r3/ct_helper.c: -------------------------------------------------------------------------------- 1 | 2 | #include "php.h" 3 | #include "string.h" 4 | #include "main/php_main.h" 5 | #include "Zend/zend_API.h" 6 | #include "zend_exceptions.h" 7 | #include "zend_interfaces.h" 8 | #include "zend_object_handlers.h" 9 | #include "ext/pcre/php_pcre.h" 10 | #include "ext/standard/php_string.h" 11 | #include "ct_helper.h" 12 | 13 | 14 | #define ZEND_FIND_FUNC(ce, name, name_len, fe) \ 15 | zend_hash_find(&ce->function_table, name, name_len, (void **) &fe) 16 | 17 | #define ZEND_FIND_FUNC_QUICK(ce, name, name_len, fe) \ 18 | zend_hash_quick_find(&ce->function_table, name, name_len, zend_inline_hash_func(name, name_len) , (void **) &fe) 19 | 20 | /* {{{ zend_call_method 21 | Only returns the returned zval if retval_ptr != NULL */ 22 | ZEND_API zval* zend_call_method_with_3_params(zval **object_pp, zend_class_entry *obj_ce, zend_function **fn_proxy, const char *function_name, int function_name_len, zval **retval_ptr_ptr, int param_count, zval* arg1, zval* arg2, zval* arg3 TSRMLS_DC) 23 | { 24 | int result; 25 | zend_fcall_info fci; 26 | zval z_fname; 27 | zval *retval; 28 | HashTable *function_table; 29 | 30 | zval **params[3]; 31 | 32 | params[0] = &arg1; 33 | params[1] = &arg2; 34 | params[2] = &arg3; 35 | 36 | fci.size = sizeof(fci); 37 | /*fci.function_table = NULL; will be read form zend_class_entry of object if needed */ 38 | fci.object_ptr = object_pp ? *object_pp : NULL; 39 | fci.function_name = &z_fname; 40 | fci.retval_ptr_ptr = retval_ptr_ptr ? retval_ptr_ptr : &retval; 41 | fci.param_count = param_count; 42 | fci.params = params; 43 | fci.no_separation = 1; 44 | fci.symbol_table = NULL; 45 | 46 | if (!fn_proxy && !obj_ce) { 47 | /* no interest in caching and no information already present that is 48 | * needed later inside zend_call_function. */ 49 | ZVAL_STRINGL(&z_fname, function_name, function_name_len, 0); 50 | fci.function_table = !object_pp ? EG(function_table) : NULL; 51 | result = zend_call_function(&fci, NULL TSRMLS_CC); 52 | } else { 53 | zend_fcall_info_cache fcic; 54 | 55 | fcic.initialized = 1; 56 | if (!obj_ce) { 57 | obj_ce = object_pp ? Z_OBJCE_PP(object_pp) : NULL; 58 | } 59 | if (obj_ce) { 60 | function_table = &obj_ce->function_table; 61 | } else { 62 | function_table = EG(function_table); 63 | } 64 | if (!fn_proxy || !*fn_proxy) { 65 | if (zend_hash_find(function_table, function_name, function_name_len+1, (void **) &fcic.function_handler) == FAILURE) { 66 | /* error at c-level */ 67 | zend_error(E_CORE_ERROR, "Couldn't find implementation for method %s%s%s", obj_ce ? obj_ce->name : "", obj_ce ? "::" : "", function_name); 68 | } 69 | if (fn_proxy) { 70 | *fn_proxy = fcic.function_handler; 71 | } 72 | } else { 73 | fcic.function_handler = *fn_proxy; 74 | } 75 | fcic.calling_scope = obj_ce; 76 | if (object_pp) { 77 | fcic.called_scope = Z_OBJCE_PP(object_pp); 78 | } else if (obj_ce && 79 | !(EG(called_scope) && 80 | instanceof_function(EG(called_scope), obj_ce TSRMLS_CC))) { 81 | fcic.called_scope = obj_ce; 82 | } else { 83 | fcic.called_scope = EG(called_scope); 84 | } 85 | fcic.object_ptr = object_pp ? *object_pp : NULL; 86 | result = zend_call_function(&fci, &fcic TSRMLS_CC); 87 | } 88 | if (result == FAILURE) { 89 | /* error at c-level */ 90 | if (!obj_ce) { 91 | obj_ce = object_pp ? Z_OBJCE_PP(object_pp) : NULL; 92 | } 93 | if (!EG(exception)) { 94 | zend_error(E_CORE_ERROR, "Couldn't execute method %s%s%s", obj_ce ? obj_ce->name : "", obj_ce ? "::" : "", function_name); 95 | } 96 | } 97 | if (!retval_ptr_ptr) { 98 | if (retval) { 99 | zval_ptr_dtor(&retval); 100 | } 101 | return NULL; 102 | } 103 | return *retval_ptr_ptr; 104 | } 105 | /* }}} */ 106 | 107 | 108 | char * find_place_holder(char *pattern, int pattern_len) { 109 | char needle_char[2] = { ':', 0 }; 110 | return php_memnstr(pattern, 111 | needle_char, 112 | 1, 113 | pattern + pattern_len); 114 | } 115 | 116 | -------------------------------------------------------------------------------- /php/r3/ct_helper.h: -------------------------------------------------------------------------------- 1 | #ifndef PHP_CT_HELPER_H 2 | #define PHP_CT_HELPER_H 1 3 | 4 | ZEND_API zval* zend_call_method_with_3_params(zval **object_pp, zend_class_entry *obj_ce, zend_function **fn_proxy, const char *function_name, int function_name_len, zval **retval_ptr_ptr, int param_count, zval* arg1, zval* arg2, zval* arg3 TSRMLS_DC); 5 | 6 | char * find_place_holder(char *pattern, int pattern_len); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /php/r3/hash.c: -------------------------------------------------------------------------------- 1 | 2 | #include "hash.h" 3 | 4 | HashTable * zend_hash_clone_persistent(HashTable* src TSRMLS_DC) 5 | { 6 | zval **tmp; 7 | return my_copy_hashtable(NULL, src, (ht_copy_fun_t) my_copy_zval_ptr, (void*) &tmp, sizeof(zval *), 1 TSRMLS_CC); 8 | } 9 | 10 | HashTable * zend_hash_clone(HashTable* src TSRMLS_DC) 11 | { 12 | zval **tmp; 13 | return my_copy_hashtable(NULL, src, (ht_copy_fun_t) my_copy_zval_ptr, (void*) &tmp, sizeof(zval *), 0 TSRMLS_CC); 14 | } 15 | 16 | 17 | /** 18 | * Recursively copy hash and all its value. 19 | * 20 | * This replaces zend_hash_copy 21 | */ 22 | HashTable * my_copy_hashtable(HashTable *target, HashTable *source, ht_copy_fun_t copy_fn, void *tmp, uint size, int persistent TSRMLS_DC) 23 | { 24 | Bucket *curr = NULL, *prev = NULL , *newp = NULL; 25 | void *new_entry; 26 | int first = 1; 27 | 28 | assert(source != NULL); 29 | 30 | // allocate persistent memory for target and initialize it. 31 | if (!target) { 32 | target = pemalloc(sizeof(source[0]), persistent); 33 | } 34 | memcpy(target, source, sizeof(source[0])); 35 | target->arBuckets = pemalloc(target->nTableSize * sizeof(Bucket*), persistent); 36 | 37 | memset(target->arBuckets, 0, target->nTableSize * sizeof(Bucket*)); 38 | target->pInternalPointer = NULL; 39 | target->pListHead = NULL; 40 | 41 | // since it's persistent, destructor should be NULL 42 | target->persistent = persistent; 43 | 44 | if (! target->persistent) { 45 | target->pDestructor = ZVAL_PTR_DTOR; 46 | } 47 | 48 | curr = source->pListHead; 49 | while (curr) { 50 | // hash index 51 | int n = curr->h % target->nTableSize; 52 | 53 | // allocate new bucket 54 | // from apc 55 | #ifdef ZEND_ENGINE_2_4 56 | if (!curr->nKeyLength) { 57 | newp = (Bucket*) pemalloc(sizeof(Bucket), persistent); 58 | memcpy(newp, curr, sizeof(Bucket)); 59 | } else if (IS_INTERNED(curr->arKey)) { 60 | newp = (Bucket*) pemalloc(sizeof(Bucket), persistent); 61 | memcpy(newp, curr, sizeof(Bucket)); 62 | } else { 63 | // ugly but we need to copy 64 | newp = (Bucket*) pemalloc(sizeof(Bucket) + curr->nKeyLength, persistent); 65 | memcpy(newp, curr, sizeof(Bucket) + curr->nKeyLength ); 66 | newp->arKey = (const char*)(newp+1); 67 | } 68 | #else 69 | newp = (Bucket*) pecalloc(1, (sizeof(Bucket) + curr->nKeyLength - 1), persistent); 70 | memcpy(newp, curr, sizeof(Bucket) + curr->nKeyLength - 1); 71 | #endif 72 | 73 | 74 | /* insert 'newp' into the linked list at its hashed index */ 75 | if (target->arBuckets[n]) { 76 | newp->pNext = target->arBuckets[n]; 77 | newp->pLast = NULL; 78 | newp->pNext->pLast = newp; 79 | } else { 80 | newp->pNext = newp->pLast = NULL; 81 | } 82 | target->arBuckets[n] = newp; 83 | 84 | // now we copy the bucket data using our 'copy_fn' 85 | newp->pData = copy_fn(NULL, curr->pData, persistent TSRMLS_CC); 86 | memcpy(&newp->pDataPtr, newp->pData, sizeof(void*)); 87 | 88 | /* insert 'newp' into the table-thread linked list */ 89 | newp->pListLast = prev; 90 | newp->pListNext = NULL; 91 | 92 | if (prev) { 93 | prev->pListNext = newp; 94 | } 95 | if (first) { 96 | target->pListHead = newp; 97 | first = 0; 98 | } 99 | prev = newp; 100 | 101 | curr = curr->pListNext; 102 | } 103 | 104 | target->pListTail = newp; 105 | zend_hash_internal_pointer_reset(target); 106 | 107 | // return the newly allocated memory 108 | return target; 109 | } 110 | 111 | -------------------------------------------------------------------------------- /php/r3/hash.h: -------------------------------------------------------------------------------- 1 | #ifndef HASH_H 2 | #define HASH_H 1 3 | 4 | #include "php.h" 5 | #include "string.h" 6 | #include "main/php_main.h" 7 | #include "Zend/zend_API.h" 8 | #include "zend_exceptions.h" 9 | #include "zend_interfaces.h" 10 | #include "zend_object_handlers.h" 11 | #include "ext/pcre/php_pcre.h" 12 | #include "ext/standard/php_string.h" 13 | #include "php_r3.h" 14 | #include "r3_mux.h" 15 | #include "r3_functions.h" 16 | 17 | 18 | #if R3_DEBUG 19 | #define HT_OK 0 20 | #define HT_IS_DESTROYING 1 21 | #define HT_DESTROYED 2 22 | #define HT_CLEANING 3 23 | 24 | static void _zend_is_inconsistent(const HashTable *ht, const char *file, int line) 25 | { 26 | if (ht->inconsistent==HT_OK) { 27 | return; 28 | } 29 | switch (ht->inconsistent) { 30 | case HT_IS_DESTROYING: 31 | zend_output_debug_string(1, "%s(%d) : ht=%p is being destroyed", file, line, ht); 32 | break; 33 | case HT_DESTROYED: 34 | zend_output_debug_string(1, "%s(%d) : ht=%p is already destroyed", file, line, ht); 35 | break; 36 | case HT_CLEANING: 37 | zend_output_debug_string(1, "%s(%d) : ht=%p is being cleaned", file, line, ht); 38 | break; 39 | default: 40 | zend_output_debug_string(1, "%s(%d) : ht=%p is inconsistent", file, line, ht); 41 | break; 42 | } 43 | zend_bailout(); 44 | } 45 | #define IS_CONSISTENT(a) _zend_is_inconsistent(a, __FILE__, __LINE__); 46 | #define SET_INCONSISTENT(n) ht->inconsistent = n; 47 | #else 48 | #define IS_CONSISTENT(a) 49 | #define SET_INCONSISTENT(n) 50 | #endif 51 | 52 | HashTable * zend_hash_clone_persistent(HashTable* src TSRMLS_DC); 53 | 54 | HashTable * zend_hash_clone(HashTable* src TSRMLS_DC); 55 | 56 | typedef void* (*ht_copy_fun_t)(void*, void*, int TSRMLS_DC); 57 | HashTable * my_copy_hashtable(HashTable *target, HashTable *source, ht_copy_fun_t copy_fn, void *tmp, uint size, int persistent TSRMLS_DC); 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /php/r3/php_expandable_mux.c: -------------------------------------------------------------------------------- 1 | #include "php.h" 2 | #include "string.h" 3 | #include "main/php_main.h" 4 | #include "Zend/zend_API.h" 5 | #include "zend_exceptions.h" 6 | #include "zend_interfaces.h" 7 | #include "zend_object_handlers.h" 8 | #include "ext/pcre/php_pcre.h" 9 | #include "ext/standard/php_string.h" 10 | #include "r3_functions.h" 11 | #include "php_expandable_mux.h" 12 | 13 | zend_class_entry *ce_r3_expandable_mux; 14 | 15 | const zend_function_entry expandable_mux_methods[] = { 16 | PHP_ABSTRACT_ME(ExpandableMux, expand, NULL) 17 | PHP_FE_END 18 | }; 19 | 20 | /** 21 | * TODO: Use zend_class_implements to register Controller class. 22 | * 23 | * zend_class_implements(mysqli_result_class_entry TSRMLS_CC, 1, zend_ce_traversable); 24 | */ 25 | static int implement_expandable_mux_interface_handler(zend_class_entry *interface, zend_class_entry *implementor TSRMLS_DC) 26 | { 27 | if (implementor->type == ZEND_USER_CLASS && 28 | !instanceof_function(implementor, ce_r3_expandable_mux TSRMLS_CC) 29 | ) { 30 | zend_error(E_ERROR, "R3\\ExpandableMux can't be implemented by user classes"); 31 | } 32 | return SUCCESS; 33 | } 34 | 35 | 36 | void r3_init_expandable_mux(TSRMLS_D) 37 | { 38 | zend_class_entry ce_interface; 39 | INIT_CLASS_ENTRY(ce_interface, "R3\\ExpandableMux", expandable_mux_methods); 40 | 41 | // if(Z_TYPE_PP(current) == IS_OBJECT && instanceof_function(Z_OBJCE_PP(current), curl_CURLFile_class TSRMLS_CC)) 42 | ce_r3_expandable_mux = zend_register_internal_interface(&ce_interface TSRMLS_CC); 43 | ce_r3_expandable_mux->interface_gets_implemented = implement_expandable_mux_interface_handler; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /php/r3/php_expandable_mux.h: -------------------------------------------------------------------------------- 1 | #ifndef PHP_EXPANDABLE_MUX_H 2 | #define PHP_EXPANDABLE_MUX_H 1 3 | 4 | #include "php.h" 5 | #include "string.h" 6 | #include "main/php_main.h" 7 | #include "Zend/zend_API.h" 8 | #include "Zend/zend_variables.h" 9 | #include "zend_exceptions.h" 10 | #include "zend_interfaces.h" 11 | #include "zend_object_handlers.h" 12 | #include "ext/pcre/php_pcre.h" 13 | #include "ext/standard/php_string.h" 14 | #include "php_r3.h" 15 | #include "r3_functions.h" 16 | 17 | extern zend_class_entry *ce_r3_expandable_mux; 18 | 19 | void r3_init_expandable_mux(TSRMLS_D); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /php/r3/php_r3.c: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_CONFIG_H 2 | #include "config.h" 3 | #endif 4 | 5 | #include "php.h" 6 | #include "string.h" 7 | #include "main/php_main.h" 8 | #include "Zend/zend_API.h" 9 | #include "Zend/zend_variables.h" 10 | #include "zend_exceptions.h" 11 | #include "zend_interfaces.h" 12 | #include "zend_object_handlers.h" 13 | #include "ext/standard/php_string.h" 14 | 15 | #include "php_r3.h" 16 | // #include "ct_helper.h" 17 | #include "r3_functions.h" 18 | // #include "r3_mux.h" 19 | // #include "php_expandable_mux.h" 20 | // #include "r3_controller.h" 21 | 22 | ZEND_DECLARE_MODULE_GLOBALS(r3); 23 | 24 | 25 | // persistent list entry type for HashTable 26 | int le_mux_hash_table; 27 | 28 | // persistent list entry type for boolean 29 | int le_mux_bool; 30 | 31 | // persistent list entry type for int 32 | int le_mux_int; 33 | 34 | // persistent list entry type for string 35 | int le_mux_string; 36 | 37 | zend_class_entry *ce_r3_exception; 38 | 39 | 40 | // #define DEBUG 1 41 | static const zend_function_entry r3_functions[] = { 42 | PHP_FE(r3_match, NULL) 43 | PHP_FE_END 44 | }; 45 | 46 | void r3_init_exception(TSRMLS_D) { 47 | zend_class_entry e; 48 | INIT_CLASS_ENTRY(e, "R3Exception", NULL); 49 | ce_r3_exception = zend_register_internal_class_ex(&e, (zend_class_entry*)zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC); 50 | } 51 | 52 | void r3_mux_le_hash_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) 53 | { 54 | HashTable *h = (HashTable*) rsrc->ptr; 55 | if (h) { 56 | // zend_hash_destroy(h); 57 | // pefree(h, 1); 58 | } 59 | } 60 | 61 | zend_module_entry r3_module_entry = { 62 | STANDARD_MODULE_HEADER, 63 | PHP_R3_EXTNAME, 64 | r3_functions, 65 | PHP_MINIT(r3), 66 | PHP_MSHUTDOWN(r3), 67 | PHP_RINIT(r3), 68 | NULL, 69 | NULL, 70 | PHP_R3_VERSION, 71 | STANDARD_MODULE_PROPERTIES 72 | }; 73 | 74 | PHP_INI_BEGIN() 75 | PHP_INI_ENTRY("r3.fstat", "0", PHP_INI_ALL, NULL) 76 | // STD_PHP_INI_ENTRY("r3.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals) 77 | PHP_INI_END() 78 | 79 | #ifdef COMPILE_DL_R3 80 | ZEND_GET_MODULE(r3) 81 | #endif 82 | 83 | static void php_r3_init_globals(zend_r3_globals *r3_globals) 84 | { 85 | // r3_globals->persistent_list = (HashTable*) 86 | // array_init(r3_globals->persistent_list); 87 | } 88 | 89 | PHP_MINIT_FUNCTION(r3) { 90 | ZEND_INIT_MODULE_GLOBALS(r3, php_r3_init_globals, NULL); 91 | REGISTER_INI_ENTRIES(); 92 | // r3_init_mux(TSRMLS_C); 93 | // r3_init_expandable_mux(TSRMLS_C); 94 | // r3_init_controller(TSRMLS_C); 95 | le_mux_hash_table = zend_register_list_destructors_ex(NULL, r3_mux_le_hash_dtor, "hash table", module_number); 96 | return SUCCESS; 97 | } 98 | 99 | PHP_MSHUTDOWN_FUNCTION(r3) { 100 | UNREGISTER_INI_ENTRIES(); 101 | return SUCCESS; 102 | } 103 | 104 | PHP_RINIT_FUNCTION(r3) { 105 | return SUCCESS; 106 | } 107 | -------------------------------------------------------------------------------- /php/r3/php_r3.h: -------------------------------------------------------------------------------- 1 | #ifndef PHP_R3_H 2 | #define PHP_R3_H 1 3 | 4 | #include "php.h" 5 | #include "string.h" 6 | #include "main/php_main.h" 7 | #include "Zend/zend_API.h" 8 | #include "Zend/zend_variables.h" 9 | #include "zend_exceptions.h" 10 | #include "zend_interfaces.h" 11 | #include "zend_object_handlers.h" 12 | #include "ext/standard/php_string.h" 13 | 14 | #define PHP_R3_VERSION "1.3.1" 15 | #define PHP_R3_EXTNAME "r3" 16 | 17 | 18 | #ifdef ZTS 19 | #include "TSRM.h" 20 | #endif 21 | 22 | extern int r3_globals_id; 23 | 24 | extern int le_mux_hash_table; 25 | 26 | // global variable structure 27 | ZEND_BEGIN_MODULE_GLOBALS(r3) 28 | // zval *mux_array; 29 | HashTable * persistent_list; 30 | // zend_bool direction; 31 | ZEND_END_MODULE_GLOBALS(r3) 32 | 33 | #ifdef ZTS 34 | #define R3_G(v) TSRMG(r3_globals_id, zend_r3_globals *, v) 35 | #else 36 | #define R3_G(v) (r3_globals.v) 37 | #endif 38 | 39 | 40 | 41 | #define ZEND_HASH_FETCH(hash,key,ret) \ 42 | zend_hash_find(hash, key, sizeof(key), (void**)&ret) == SUCCESS 43 | 44 | #define PUSH_PARAM(arg) zend_vm_stack_push(arg TSRMLS_CC) 45 | #define POP_PARAM() (void)zend_vm_stack_pop(TSRMLS_C) 46 | #define PUSH_EO_PARAM() 47 | #define POP_EO_PARAM() 48 | 49 | #define CALL_METHOD_BASE(classname, name) zim_##classname##_##name 50 | 51 | #define CALL_METHOD_HELPER(classname, name, retval, thisptr, num, param) \ 52 | PUSH_PARAM(param); PUSH_PARAM((void*)num); \ 53 | PUSH_EO_PARAM(); \ 54 | CALL_METHOD_BASE(classname, name)(num, retval, NULL, thisptr, 0 TSRMLS_CC); \ 55 | POP_EO_PARAM(); \ 56 | POP_PARAM(); POP_PARAM(); 57 | 58 | #define CALL_METHOD(classname, name, retval, thisptr) \ 59 | CALL_METHOD_BASE(classname, name)(0, retval, NULL, thisptr, 0 TSRMLS_CC); 60 | 61 | #define CALL_METHOD1(classname, name, retval, thisptr, param1) \ 62 | CALL_METHOD_HELPER(classname, name, retval, thisptr, 1, param1); 63 | 64 | #define CALL_METHOD2(classname, name, retval, thisptr, param1, param2) \ 65 | PUSH_PARAM(param1); \ 66 | CALL_METHOD_HELPER(classname, name, retval, thisptr, 2, param2); \ 67 | POP_PARAM(); 68 | 69 | #define CALL_METHOD3(classname, name, retval, thisptr, param1, param2, param3) \ 70 | PUSH_PARAM(param1); PUSH_PARAM(param2); \ 71 | CALL_METHOD_HELPER(classname, name, retval, thisptr, 3, param3); \ 72 | POP_PARAM(); POP_PARAM(); 73 | 74 | PHP_MINIT_FUNCTION(r3); 75 | PHP_MSHUTDOWN_FUNCTION(r3); 76 | PHP_RINIT_FUNCTION(r3); 77 | 78 | /* 79 | zval * php_r3_match(zval *z_routes, char *path, int path_len TSRMLS_DC); 80 | 81 | zval * call_mux_method(zval * object , char * method_name , int method_name_len, int param_count, zval* arg1, zval* arg2, zval* arg3 TSRMLS_DC); 82 | 83 | zend_class_entry ** get_pattern_compiler_ce(TSRMLS_D); 84 | 85 | */ 86 | extern zend_class_entry *ce_r3_exception; 87 | 88 | extern zend_module_entry r3_module_entry; 89 | 90 | void r3_init_exception(TSRMLS_D); 91 | 92 | void r3_mux_le_hash_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC); 93 | 94 | // PHP_FUNCTION(r3_match); 95 | 96 | #define phpext_r3_ptr &r3_module_entry 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /php/r3/r3_controller.h: -------------------------------------------------------------------------------- 1 | #ifndef PHP_CONTROLLER_H 2 | #define PHP_CONTROLLER_H 1 3 | 4 | #include "php.h" 5 | #include "string.h" 6 | #include "main/php_main.h" 7 | #include "Zend/zend_API.h" 8 | #include "Zend/zend_variables.h" 9 | #include "zend_exceptions.h" 10 | #include "zend_interfaces.h" 11 | #include "zend_object_handlers.h" 12 | #include "ext/pcre/php_pcre.h" 13 | #include "ext/standard/php_string.h" 14 | #include "php_r3.h" 15 | #include "r3_functions.h" 16 | 17 | extern zend_class_entry *ce_r3_controller; 18 | 19 | void r3_init_controller(TSRMLS_D); 20 | 21 | char * translate_method_name_to_path(const char *method_name); 22 | zend_bool phannot_fetch_argument_value(zval **arg, zval** value TSRMLS_DC); 23 | zend_bool phannot_fetch_argument_type(zval **arg, zval **type TSRMLS_DC); 24 | int strpos(const char *haystack, char *needle); 25 | 26 | PHP_METHOD(Controller, __construct); 27 | PHP_METHOD(Controller, expand); 28 | PHP_METHOD(Controller, getActionMethods); 29 | PHP_METHOD(Controller, getActionRoutes); 30 | PHP_METHOD(Controller, before); 31 | PHP_METHOD(Controller, after); 32 | PHP_METHOD(Controller, toJson); 33 | 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /php/r3/r3_functions.h: -------------------------------------------------------------------------------- 1 | #ifndef PHP_R3_FUNCTIONS_H 2 | #define PHP_R3_FUNCTIONS_H 1 3 | 4 | #define REQ_METHOD_GET 1 5 | #define REQ_METHOD_POST 2 6 | #define REQ_METHOD_PUT 3 7 | #define REQ_METHOD_DELETE 4 8 | #define REQ_METHOD_PATCH 5 9 | #define REQ_METHOD_HEAD 6 10 | #define REQ_METHOD_OPTIONS 7 11 | 12 | #include "php.h" 13 | #include "string.h" 14 | #include "main/php_main.h" 15 | #include "Zend/zend_API.h" 16 | #include "zend_exceptions.h" 17 | #include "zend_interfaces.h" 18 | #include "zend_object_handlers.h" 19 | #include "ext/pcre/php_pcre.h" 20 | #include "ext/standard/php_string.h" 21 | #include "php_r3.h" 22 | #include "r3_mux.h" 23 | #include "r3_functions.h" 24 | 25 | extern inline zval * php_r3_match(zval *z_routes, char *path, int path_len TSRMLS_DC); 26 | extern inline int get_current_request_method_const(HashTable * server_vars_hash TSRMLS_DC); 27 | extern inline int get_current_https(HashTable * server_vars_hash TSRMLS_DC); 28 | extern inline HashTable * fetch_server_vars_hash(TSRMLS_D); 29 | extern inline zval * fetch_server_var(HashTable *server_vars_hash, char *key , int key_len TSRMLS_DC); 30 | extern inline zval * get_current_http_host(HashTable * server_vars_hash TSRMLS_DC); 31 | extern inline zval * get_current_request_uri(HashTable * server_vars_hash TSRMLS_DC); 32 | extern inline zval * get_current_request_method(HashTable * server_vars_hash TSRMLS_DC); 33 | 34 | extern inline int validate_request_method(zval **z_route_options_pp, int current_request_method TSRMLS_DC); 35 | extern inline int validate_domain(zval **z_route_options_pp, zval * http_host TSRMLS_DC); 36 | extern inline int validate_https(zval **z_route_options_pp, int https TSRMLS_DC); 37 | 38 | #if ((PHP_MAJOR_VERSION == 5) && (PHP_MINOR_VERSION > 2)) || (PHP_MAJOR_VERSION > 5) 39 | #define R3_STORE_EG_ENVIRON() \ 40 | { \ 41 | zval ** __old_return_value_pp = EG(return_value_ptr_ptr); \ 42 | zend_op ** __old_opline_ptr = EG(opline_ptr); \ 43 | zend_op_array * __old_op_array = EG(active_op_array); 44 | 45 | #define R3_RESTORE_EG_ENVIRON() \ 46 | EG(return_value_ptr_ptr) = __old_return_value_pp;\ 47 | EG(opline_ptr) = __old_opline_ptr; \ 48 | EG(active_op_array) = __old_op_array; \ 49 | } 50 | 51 | #else 52 | 53 | #define R3_STORE_EG_ENVIRON() \ 54 | { \ 55 | zval ** __old_return_value_pp = EG(return_value_ptr_ptr); \ 56 | zend_op ** __old_opline_ptr = EG(opline_ptr); \ 57 | zend_op_array * __old_op_array = EG(active_op_array); \ 58 | zend_function_state * __old_func_state = EG(function_state_ptr); 59 | 60 | #define R3_RESTORE_EG_ENVIRON() \ 61 | EG(return_value_ptr_ptr) = __old_return_value_pp;\ 62 | EG(opline_ptr) = __old_opline_ptr; \ 63 | EG(active_op_array) = __old_op_array; \ 64 | EG(function_state_ptr) = __old_func_state; \ 65 | } 66 | 67 | #endif 68 | 69 | #define CHECK(p) { if ((p) == NULL) return NULL; } 70 | 71 | 72 | zval* my_copy_zval(zval* dst, const zval* src, int persistent TSRMLS_DC); 73 | 74 | zval** my_copy_zval_ptr(zval** dst, const zval** src, int persistent TSRMLS_DC); 75 | 76 | 77 | zval * _r3_fetch_mux(char *name TSRMLS_DC); 78 | int mux_loader(char *path, zval *result TSRMLS_DC); 79 | int _r3_store_mux(char *name, zval * mux TSRMLS_DC) ; 80 | 81 | void my_zval_copy_ctor_persistent_func(zval *zvalue ZEND_FILE_LINE_DC); 82 | 83 | PHP_FUNCTION(r3_match); 84 | PHP_FUNCTION(r3_sort_routes); 85 | PHP_FUNCTION(r3_store_mux); 86 | PHP_FUNCTION(r3_fetch_mux); 87 | PHP_FUNCTION(r3_delete_mux); 88 | PHP_FUNCTION(r3_persistent_dispatch); 89 | 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /php/r3/r3_mux.h: -------------------------------------------------------------------------------- 1 | #ifndef PHP_MUX_H 2 | #define PHP_MUX_H 1 3 | 4 | #include "php.h" 5 | #include "string.h" 6 | #include "main/php_main.h" 7 | #include "Zend/zend_API.h" 8 | #include "Zend/zend_variables.h" 9 | #include "zend_exceptions.h" 10 | #include "zend_interfaces.h" 11 | #include "zend_object_handlers.h" 12 | #include "ext/pcre/php_pcre.h" 13 | #include "ext/standard/php_string.h" 14 | #include "php_r3.h" 15 | #include "r3_functions.h" 16 | 17 | extern zend_class_entry *ce_r3_mux; 18 | 19 | void r3_init_mux(TSRMLS_D); 20 | 21 | zend_class_entry ** get_pattern_compiler_ce(TSRMLS_D); 22 | zval * compile_route_pattern(zval *z_pattern, zval *z_options, zend_class_entry **ce_pattern_compiler TSRMLS_DC); 23 | 24 | extern inline zval * call_mux_method(zval * object , char * method_name , int method_name_len, int param_count, zval* arg1, zval* arg2, zval* arg3 TSRMLS_DC); 25 | 26 | extern inline void mux_add_route(INTERNAL_FUNCTION_PARAMETERS); 27 | 28 | 29 | PHP_METHOD(Mux, __construct); 30 | PHP_METHOD(Mux, __destruct); 31 | PHP_METHOD(Mux, getId); 32 | PHP_METHOD(Mux, add); 33 | PHP_METHOD(Mux, any); 34 | PHP_METHOD(Mux, length); 35 | PHP_METHOD(Mux, compile); 36 | PHP_METHOD(Mux, sort); 37 | PHP_METHOD(Mux, appendRoute); 38 | PHP_METHOD(Mux, appendPCRERoute); 39 | PHP_METHOD(Mux, setRoutes); 40 | PHP_METHOD(Mux, getRoutes); 41 | PHP_METHOD(Mux, getRoute); 42 | PHP_METHOD(Mux, match); 43 | PHP_METHOD(Mux, dispatch); 44 | PHP_METHOD(Mux, getSubMux); 45 | PHP_METHOD(Mux, getRequestMethodConstant); 46 | PHP_METHOD(Mux, export); 47 | PHP_METHOD(Mux, mount); 48 | 49 | PHP_METHOD(Mux, get); 50 | PHP_METHOD(Mux, post); 51 | PHP_METHOD(Mux, put); 52 | PHP_METHOD(Mux, delete); 53 | PHP_METHOD(Mux, head); 54 | PHP_METHOD(Mux, patch); 55 | PHP_METHOD(Mux, options); 56 | 57 | PHP_METHOD(Mux, __set_state); 58 | 59 | // static method 60 | PHP_METHOD(Mux, generate_id); 61 | 62 | 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /php/r3/r3_persistent.c: -------------------------------------------------------------------------------- 1 | #include "php.h" 2 | #include "php_r3.h" 3 | #include "r3_persistent.h" 4 | #include "php_expandable_mux.h" 5 | 6 | inline int persistent_store(char *key, int key_len, int list_type, void * val TSRMLS_DC) 7 | { 8 | zend_rsrc_list_entry new_le; 9 | zend_rsrc_list_entry *le; 10 | 11 | // store it if it's not in persistent_list 12 | if ( zend_hash_find(&EG(persistent_list), key, key_len + 1, (void**) &le) == SUCCESS ) { 13 | zend_hash_del(&EG(persistent_list), key, key_len + 1); 14 | } 15 | new_le.type = list_type; 16 | new_le.ptr = val; 17 | return zend_hash_update(&EG(persistent_list), key, key_len + 1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL); 18 | } 19 | 20 | inline void * persistent_fetch(char *key, int key_len TSRMLS_DC) 21 | { 22 | zend_rsrc_list_entry *le; 23 | if ( zend_hash_find(&EG(persistent_list), key, key_len + 1, (void**) &le) == SUCCESS ) { 24 | return le->ptr; 25 | } 26 | return NULL; 27 | } 28 | 29 | 30 | inline void * r3_persistent_fetch(char *ns, char *key TSRMLS_DC) 31 | { 32 | char *newkey; 33 | int newkey_len; 34 | void *ptr; 35 | newkey_len = spprintf(&newkey, 0, "r3_%s_%s", ns, key); 36 | ptr = persistent_fetch(newkey, newkey_len TSRMLS_CC); 37 | efree(newkey); 38 | return ptr; 39 | } 40 | 41 | /* 42 | * Store persistent value with r3 namespace. 43 | */ 44 | inline int r3_persistent_store(char *ns, char *key, int list_type, void * val TSRMLS_DC) 45 | { 46 | char *newkey; 47 | int newkey_len; 48 | int status; 49 | newkey_len = spprintf(&newkey, 0, "r3_%s_%s", ns, key); 50 | status = persistent_store(newkey, newkey_len, list_type, val TSRMLS_CC); 51 | efree(newkey); 52 | return status; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /php/r3/r3_persistent.h: -------------------------------------------------------------------------------- 1 | #ifndef R3_PERSISTENT_H 2 | #define R3_PERSISTENT_H 1 3 | 4 | extern inline int persistent_store(char *key, int key_len, int list_type, void * val TSRMLS_DC); 5 | extern inline int r3_persistent_store(char *ns, char *key, int list_type, void * val TSRMLS_DC) ; 6 | 7 | extern inline void * persistent_fetch(char *key, int key_len TSRMLS_DC); 8 | extern inline void * r3_persistent_fetch(char *ns, char *key TSRMLS_DC); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /r3.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@prefix@ 2 | exec_prefix=@exec_prefix@ 3 | includedir=@includedir@/r3 4 | libdir=@libdir@ 5 | 6 | Name: r3 7 | Description: High-performance URL router library 8 | Version: @PACKAGE_VERSION@ 9 | Requires: libpcre2-8 10 | Libs: -L${libdir} -lr3 11 | CFlags: -I${includedir} 12 | -------------------------------------------------------------------------------- /run-benchmark: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | i=10 3 | while [ $i -gt 0 ] ; do 4 | bash tests/bench 5 | i=$(($i - 1)) 6 | done 7 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(r3 STATIC 2 | edge.c 3 | match_entry.c 4 | memory.c 5 | node.c 6 | slug.c 7 | str.c 8 | token.c) 9 | 10 | target_compile_definitions(r3 11 | PRIVATE 12 | _GNU_SOURCE) 13 | 14 | target_include_directories(r3 15 | PUBLIC 16 | ${PROJECT_BINARY_DIR} 17 | ${PROJECT_SOURCE_DIR}/3rdparty 18 | ${PROJECT_SOURCE_DIR}/include) 19 | 20 | target_link_libraries(r3 21 | PUBLIC 22 | ${PCRE2_LIBRARIES}) 23 | 24 | install( 25 | TARGETS r3 26 | DESTINATION lib) 27 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CFLAGS=$(DEPS_CFLAGS) $(GVC_DEPS_CFLAGS) $(JSONC_CFLAGS) -I$(top_builddir) -I$(top_builddir)/include -Wall -std=c99 2 | AM_LDFLAGS=$(DEPS_LIBS) $(GVC_DEPS_LIBS) $(JSONC_LIBS) 3 | MAYBE_COVERAGE=--coverage 4 | 5 | noinst_LTLIBRARIES = libr3core.la 6 | # lib_LIBRARIES = libr3.a 7 | libr3core_la_SOURCES = node.c edge.c str.c token.c match_entry.c slug.c memory.c 8 | 9 | if ENABLE_JSON 10 | libr3core_la_SOURCES += json.c 11 | endif 12 | 13 | if COND_GCOV 14 | # MAYBE_COVERAGE=--coverage --no-inline 15 | AM_CFLAGS += $(MAYBE_COVERAGE) 16 | endif 17 | 18 | MOSTLYCLEANFILES = *.gcov *.gcda *.gcno 19 | 20 | 21 | 22 | # libr3_la_LDFLAGS = -export-symbols-regex '^r3_|^match_' 23 | 24 | # libr3_la_LIBADD=$(DEPS_LIBS) $(LIBOBJS) $(ALLOCA) 25 | # libr3core_la_LIBADD=$(DEPS_LIBS) 26 | # libr3core_la_CFLAGS=$(DEPS_CFLAGS) -I$(top_builddir) -I$(top_builddir)/include -Wall -std=c99 27 | 28 | if ENABLE_GRAPHVIZ 29 | libr3core_la_SOURCES += gvc.c 30 | endif 31 | 32 | # AM_CFLAGS=$(DEPS_CFLAGS) 33 | -------------------------------------------------------------------------------- /src/edge.c: -------------------------------------------------------------------------------- 1 | /* 2 | * edge.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include "config.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // Jemalloc memory management 14 | // #include 15 | 16 | #include "r3.h" 17 | #include "r3_slug.h" 18 | #include "slug.h" 19 | 20 | 21 | #define CHECK_PTR(ptr) if (ptr == NULL) return NULL; 22 | 23 | 24 | 25 | void r3_edge_initl(R3Edge *e, const char * pattern, int pattern_len, R3Node * child) 26 | { 27 | e->pattern.base = (char*) pattern; 28 | e->pattern.len = (unsigned int)pattern_len; 29 | // e->opcode = 0; 30 | e->child = child; 31 | e->has_slug = r3_path_contains_slug_char(e->pattern.base, e->pattern.len); 32 | } 33 | 34 | // R3Edge * r3_edge_createl(const char * pattern, int pattern_len, R3Node * child) 35 | // { 36 | // R3Edge * e = (R3Edge*) malloc( sizeof(R3Edge) ); 37 | // CHECK_PTR(e); 38 | // e->pattern = (char*) pattern; 39 | // e->pattern_len = pattern_len; 40 | // e->opcode = 0; 41 | // e->child = child; 42 | // e->has_slug = r3_path_contains_slug_char(e->pattern); 43 | // return e; 44 | // } 45 | 46 | 47 | 48 | /** 49 | * r3_edge_branch splits the edge and append the rest part as the child of the 50 | * first level child 51 | * 52 | * branch the edge pattern at "dl" offset, 53 | * and insert a dummy child between the edges. 54 | * 55 | * A -> [EDGE: abcdefg] -> B -> [EDGE:branch1], [EDGE:branch2] 56 | * A -> [EDGE: abcd] -> B1 -> [efg] -> B2 (new child with copied data from B) 57 | * 58 | */ 59 | R3Node * r3_edge_branch(R3Edge *e, int dl) { 60 | R3Node * new_child; 61 | R3Edge * new_edge; 62 | 63 | // the rest string 64 | const char * s1 = e->pattern.base + dl; 65 | int s1_len = e->pattern.len - dl; 66 | 67 | // the suffix edge of the leaf 68 | new_child = r3_tree_create(3); 69 | 70 | new_edge = r3_node_append_edge(new_child); 71 | r3_edge_initl(new_edge, s1, s1_len, e->child); 72 | e->child = new_child; 73 | 74 | // truncate the original edge pattern 75 | e->pattern.len = dl; 76 | return new_child; 77 | } 78 | 79 | void r3_edge_free(R3Edge * e) { 80 | if (e) { 81 | if ( e->child ) { 82 | r3_tree_free(e->child); 83 | } 84 | // free itself 85 | // free(e); 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/gvc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * gvz.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include "config.h" 8 | #include 9 | #include 10 | #include 11 | #include "r3.h" 12 | #include "r3_gvc.h" 13 | 14 | void r3_tree_build_ag_nodes(Agraph_t * g, Agnode_t * ag_parent_node, const node * n, int * node_cnt) { 15 | if (!n) 16 | return; 17 | 18 | for ( int i = 0 ; i < n->edges.size ; i++ ) { 19 | edge * e = n->edges.entries + i; 20 | (*node_cnt)++; 21 | 22 | Agnode_t *agn_child = NULL; 23 | Agedge_t *agn_edge = NULL; 24 | 25 | char *nodename = NULL; 26 | if ( e && e->child && e->child->combined_pattern ) { 27 | int r = asprintf(&nodename,"%s", e->child->combined_pattern); 28 | if (r) {}; 29 | } else { 30 | int r = asprintf(&nodename,"#%d", *node_cnt); 31 | if (r) {}; 32 | } 33 | 34 | agn_child = agnode(g, nodename, 1); 35 | agn_edge = agedge(g, ag_parent_node, agn_child, 0, 1); 36 | agsafeset(agn_edge, "label", e->pattern, ""); 37 | if (e->child && e->child->endpoint) { 38 | agsafeset(agn_child, "shape", "doublecircle", ""); 39 | } 40 | r3_tree_build_ag_nodes(g, agn_child, e->child, node_cnt); 41 | } 42 | } 43 | 44 | 45 | 46 | /** 47 | * Render a tree to tree graph image via graphviz (dot) 48 | */ 49 | int r3_tree_render(const node * tree, const char *layout, const char * format, FILE *fp) 50 | { 51 | Agraph_t *g; 52 | /* set up a graphviz context - but only once even for multiple graphs */ 53 | GVC_t *gvc = NULL; 54 | gvc = gvContext(); 55 | /* Create a simple digraph */ 56 | // g = agopen("g", Agdirected, 0); 57 | g = agopen("g", Agundirected, 0); 58 | 59 | // create self node 60 | Agnode_t *ag_root = agnode(g, "{root}", 1); 61 | int n = 0; 62 | r3_tree_build_ag_nodes(g, ag_root, tree, &n); 63 | gvLayout(gvc, g, layout); 64 | gvRender(gvc, g, format, fp); 65 | gvFreeLayout(gvc, g); 66 | agclose(g); 67 | return 0; 68 | } 69 | 70 | 71 | 72 | 73 | 74 | 75 | /** 76 | * Render a tree to tree graph image via graphviz (dot) 77 | */ 78 | int r3_tree_render_dot(const node * tree, const char *layout, FILE *fp) 79 | { 80 | return r3_tree_render(tree, layout, "dot", fp); 81 | } 82 | 83 | 84 | /** 85 | * Render a tree to tree graph image via graphviz (dot) 86 | */ 87 | int r3_tree_render_file(const node * tree, const char * format, const char * filename) 88 | { 89 | Agraph_t *g; 90 | 91 | GVC_t *gvc = NULL; 92 | gvc = gvContext(); 93 | /* 94 | // set up a graphviz context - but only once even for multiple graphs 95 | static GVC_t *gvc; 96 | if (!gvc) { 97 | gvc = gvContext(); 98 | } 99 | */ 100 | 101 | /* Create a simple digraph */ 102 | // g = agopen("g", Agdirected, 0); 103 | g = agopen("g", Agundirected, 0); 104 | 105 | // create self node 106 | Agnode_t *ag_root = agnode(g, "{root}", 1); 107 | int n = 0; 108 | r3_tree_build_ag_nodes(g, ag_root, tree, &n); 109 | 110 | gvLayout(gvc, g, "dot"); 111 | gvRenderFilename(gvc, g, format, filename); 112 | gvFreeLayout(gvc, g); 113 | 114 | agclose(g); 115 | return 0; 116 | } 117 | 118 | -------------------------------------------------------------------------------- /src/json.c: -------------------------------------------------------------------------------- 1 | /* 2 | * json.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include "config.h" 8 | #include 9 | #include "r3.h" 10 | #include "r3_json.h" 11 | 12 | json_object * r3_route_to_json_object(const R3Route * r) { 13 | json_object *obj; 14 | 15 | obj = json_object_new_object(); 16 | json_object_object_add(obj, "path", json_object_new_string(r->path.base)); 17 | json_object_object_add(obj, "allowed_methods", json_object_new_int(r->request_method)); 18 | 19 | 20 | if (r->host) { 21 | json_object_object_add(obj, "host", json_object_new_string(r->host.base)); 22 | } 23 | if (r->remote_addr_pattern) { 24 | json_object_object_add(obj, "remote_addr_pattern", json_object_new_string(r->remote_addr_pattern.base)); 25 | } 26 | return obj; 27 | } 28 | 29 | 30 | json_object * r3_edge_to_json_object(const R3Edge * e) { 31 | json_object *obj; 32 | 33 | obj = json_object_new_object(); 34 | json_object_object_add(obj, "pattern", json_object_new_string(e->pattern.base)); 35 | json_object_object_add(obj, "opcode", json_object_new_int(e->opcode)); 36 | json_object_object_add(obj, "slug", json_object_new_boolean(e->has_slug)); 37 | 38 | if (e->child) { 39 | json_object *node_obj = r3_node_to_json_object(e->child); 40 | json_object_object_add(obj, "child", node_obj); 41 | } 42 | return obj; 43 | } 44 | 45 | json_object * r3_node_to_json_object(const R3Node * n) { 46 | json_object *obj; 47 | 48 | obj = json_object_new_object(); 49 | 50 | if (n->combined_pattern) { 51 | json_object_object_add(obj, "re", json_object_new_string(n->combined_pattern)); 52 | } 53 | json_object_object_add(obj, "endpoint", json_object_new_int(n->endpoint)); 54 | json_object_object_add(obj, "compare", json_object_new_int(n->compare_type)); 55 | 56 | 57 | int i; 58 | 59 | if ( n->edge_len > 0 ) { 60 | json_object *edges_array = json_object_new_array(); 61 | json_object_object_add(obj, "edges", edges_array); 62 | for (i = 0 ; i < n->edge_len ; i++ ) { 63 | json_object *edge_json_obj = r3_edge_to_json_object(n->edges.entries + i); 64 | json_object_array_add(edges_array, edge_json_obj); 65 | } 66 | } 67 | 68 | if ( n->route_len > 0 ) { 69 | json_object *routes_array = json_object_new_array(); 70 | json_object_object_add(obj, "routes", routes_array); 71 | for (i = 0; i < n->route_len; i++ ) { 72 | json_object *route_json_obj = r3_route_to_json_object(n->routes.entries + i); 73 | json_object_array_add(routes_array, route_json_obj); 74 | } 75 | } 76 | 77 | 78 | return obj; 79 | } 80 | 81 | 82 | const char * r3_node_to_json_string_ext(const R3Node * n, int options) { 83 | json_object *obj = r3_node_to_json_object(n); 84 | return json_object_to_json_string_ext(obj, options); 85 | } 86 | 87 | const char * r3_node_to_json_pretty_string(const R3Node * n) { 88 | json_object *obj = r3_node_to_json_object(n); 89 | return json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED); 90 | } 91 | 92 | const char * r3_node_to_json_string(const R3Node * n) { 93 | json_object *obj = r3_node_to_json_object(n); 94 | return json_object_to_json_string(obj); 95 | } 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/list.c: -------------------------------------------------------------------------------- 1 | /* 2 | * list.c Copyright (C) 2014 c9s 3 | * 4 | * Distributed under terms of the MIT license. 5 | */ 6 | #include 7 | #include "r3_list.h" 8 | 9 | /* Naive linked list implementation */ 10 | 11 | list * 12 | list_create() 13 | { 14 | list *l = (list *) malloc(sizeof(list)); 15 | l->count = 0; 16 | l->head = NULL; 17 | l->tail = NULL; 18 | pthread_mutex_init(&(l->mutex), NULL); 19 | return l; 20 | } 21 | 22 | void 23 | list_free(l) 24 | list *l; 25 | { 26 | if (l) { 27 | list_item *li, *tmp; 28 | 29 | pthread_mutex_lock(&(l->mutex)); 30 | 31 | if (l != NULL) { 32 | li = l->head; 33 | while (li != NULL) { 34 | tmp = li->next; 35 | li = tmp; 36 | } 37 | } 38 | pthread_mutex_unlock(&(l->mutex)); 39 | pthread_mutex_destroy(&(l->mutex)); 40 | free(l); 41 | } 42 | } 43 | 44 | list_item * list_add_element(list * l, void * ptr) 45 | { 46 | list_item *li; 47 | 48 | pthread_mutex_lock(&(l->mutex)); 49 | 50 | li = (list_item *) malloc(sizeof(list_item)); 51 | li->value = ptr; 52 | li->next = NULL; 53 | li->prev = l->tail; 54 | 55 | if (l->tail == NULL) { 56 | l->head = l->tail = li; 57 | } else { 58 | l->tail = li; 59 | } 60 | l->count++; 61 | 62 | pthread_mutex_unlock(&(l->mutex)); 63 | 64 | return li; 65 | } 66 | 67 | int 68 | list_remove_element(l, ptr) 69 | list *l; 70 | void *ptr; 71 | { 72 | int result = 0; 73 | list_item *li = l->head; 74 | 75 | pthread_mutex_lock(&(l->mutex)); 76 | 77 | while (li != NULL) { 78 | if (li->value == ptr) { 79 | if (li->prev == NULL) { 80 | l->head = li->next; 81 | } else { 82 | li->prev->next = li->next; 83 | } 84 | 85 | if (li->next == NULL) { 86 | l->tail = li->prev; 87 | } else { 88 | li->next->prev = li->prev; 89 | } 90 | l->count--; 91 | free(li); 92 | result = 1; 93 | break; 94 | } 95 | li = li->next; 96 | } 97 | 98 | pthread_mutex_unlock(&(l->mutex)); 99 | 100 | return result; 101 | } 102 | 103 | void 104 | list_each_element(l, func) 105 | list *l; 106 | int (*func) (list_item *); 107 | { 108 | list_item *li; 109 | 110 | pthread_mutex_lock(&(l->mutex)); 111 | 112 | li = l->head; 113 | while (li != NULL) { 114 | if (func(li) == 1) { 115 | break; 116 | } 117 | li = li->next; 118 | } 119 | 120 | pthread_mutex_unlock(&(l->mutex)); 121 | } 122 | -------------------------------------------------------------------------------- /src/match_entry.c: -------------------------------------------------------------------------------- 1 | /* 2 | * match_entry.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include "config.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "r3.h" 14 | 15 | match_entry * match_entry_createl(const char * path, int path_len) { 16 | match_entry * entry = r3_mem_alloc( sizeof(match_entry) ); 17 | memset(entry, 0, sizeof(*entry)); 18 | r3_vector_reserve(&entry->vars.tokens, 3); 19 | entry->path.base = path; 20 | entry->path.len = path_len; 21 | return entry; 22 | } 23 | 24 | void match_entry_free(match_entry * entry) { 25 | assert(entry); 26 | free(entry->vars.tokens.entries); 27 | free(entry); 28 | } 29 | -------------------------------------------------------------------------------- /src/memory.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 DeNA Co., Ltd. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "config.h" 24 | #include 25 | #include 26 | #include 27 | #include "memory.h" 28 | 29 | void r3_fatal(const char *msg) 30 | { 31 | fprintf(stderr, "fatal:%s\n", msg); 32 | abort(); 33 | } 34 | 35 | void r3_vector__expand(r3_vector_t *vector, unsigned int element_size, unsigned int new_capacity) 36 | { 37 | void *new_entries; 38 | assert(vector->capacity < new_capacity); 39 | if (!vector->capacity) 40 | vector->capacity = 4; 41 | while (vector->capacity < new_capacity) 42 | vector->capacity *= 2; 43 | new_entries = r3_mem_realloc(vector->entries, element_size * vector->capacity); 44 | vector->entries = new_entries; 45 | } 46 | -------------------------------------------------------------------------------- /src/r3_debug.h: -------------------------------------------------------------------------------- 1 | #ifndef R3_DEBUG_H 2 | #define R3_DEBUG_H 3 | 4 | // #define DEBUG 1 5 | #ifdef DEBUG 6 | 7 | #define info(fmt, ...) \ 8 | do { fprintf(stderr, fmt, ##__VA_ARGS__); } while (0) 9 | 10 | #define debug(fmt, ...) \ 11 | do { fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ 12 | __LINE__, __func__, __VA_ARGS__); } while (0) 13 | 14 | #else 15 | #define info(...); 16 | #define debug(...); 17 | #endif 18 | 19 | #endif /* !DEBUG_H */ 20 | -------------------------------------------------------------------------------- /src/slug.c: -------------------------------------------------------------------------------- 1 | /* 2 | * slug.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include "config.h" 8 | #include 9 | #include 10 | #include 11 | #include "r3.h" 12 | #include "r3_slug.h" 13 | #include "slug.h" 14 | #include "r3_debug.h" 15 | 16 | 17 | 18 | r3_slug_t * r3_slug_new(const char * path, int path_len) { 19 | r3_slug_t * s = malloc(sizeof(r3_slug_t)); 20 | if (!s) 21 | return NULL; 22 | s->path = (char*) path; 23 | s->path_len = path_len; 24 | 25 | s->begin = NULL; 26 | s->end = NULL; 27 | s->len = 0; 28 | 29 | s->pattern = NULL; 30 | s->pattern_len = 0; 31 | return s; 32 | } 33 | 34 | void r3_slug_free(r3_slug_t * s) { 35 | free(s); 36 | } 37 | 38 | 39 | /** 40 | * Return 1 means OK 41 | * Return 0 means Empty 42 | * Return -1 means Error 43 | */ 44 | int r3_slug_check(r3_slug_t *s) { 45 | // if it's empty 46 | if (s->begin == NULL && s->len == 0) { 47 | return 0; 48 | } 49 | if (s->begin && s->begin == s->end && s->len == 0) { 50 | return 0; 51 | } 52 | 53 | // if the head is defined, we should also have end pointer 54 | if (s->begin && s->end == NULL) { 55 | return -1; 56 | } 57 | return 0; 58 | } 59 | 60 | 61 | char * r3_slug_to_str(const r3_slug_t *s) { 62 | char *str = NULL; 63 | int r = asprintf(&str, "slug: '%.*s', pattern: '%.*s', path: '%.*s'", s->len, s->begin, s->pattern_len, s->pattern, s->path_len, s->path); 64 | if (r) {}; 65 | return str; 66 | } 67 | 68 | 69 | 70 | /* 71 | r3_slug_t * r3_slug_parse_next(r3_slug_t *s, char **errstr) { 72 | return r3_slug_parse(s->end, s->path_len - (s->end - s->begin), errstr); 73 | } 74 | 75 | Return 0 => Empty, slug not found 76 | Return 1 => Slug found 77 | Return -1 => Slug parsing error 78 | */ 79 | 80 | int r3_slug_parse(r3_slug_t *s, const char *needle, int needle_len, const char *offset, char **errstr) { 81 | s->path = (char*) needle; 82 | s->path_len = needle_len; 83 | 84 | if (offset == NULL) { 85 | offset = (char*) needle; // from the begining of the needle 86 | } 87 | 88 | // there is no slug 89 | if (!r3_path_contains_slug_char(offset, needle_len - (offset-needle))) { 90 | return 0; 91 | } 92 | 93 | int state = 0; 94 | const char * p = offset; 95 | 96 | while( (p-needle) < needle_len) { 97 | // escape one character 98 | if (*p == '\\' ) { 99 | p++; p++; 100 | continue; 101 | } 102 | 103 | // slug starts with '{' 104 | if (state == 0 && *p == '{') { 105 | s->begin = ++p; 106 | state++; 107 | continue; 108 | } 109 | 110 | // in the middle of the slug (pattern) 111 | if (state == 1 && *p == ':') { 112 | // start from next 113 | s->pattern = ++p; 114 | continue; 115 | } 116 | 117 | // slug closed. 118 | if (state == 1 && *p == '}') { 119 | s->end = p; 120 | s->len = s->end - s->begin; 121 | if (s->pattern) { 122 | s->pattern_len = p - s->pattern; 123 | } 124 | state--; 125 | p++; 126 | break; 127 | } 128 | 129 | // might be inside the pattern 130 | if ( *p == '{' ) { 131 | state++; 132 | } else if ( *p == '}' ) { 133 | state--; 134 | } 135 | p++; 136 | }; 137 | 138 | if (state != 0) { 139 | if (errstr) { 140 | char *err = NULL; 141 | int r = asprintf(&err, "Incomplete slug pattern. PATH (%d): '%s', OFFSET: %ld, STATE: %d", needle_len, needle, p - needle, state); 142 | if (r) {}; 143 | *errstr = err; 144 | } 145 | return -1; 146 | } 147 | info("found slug\n"); 148 | return 1; 149 | } 150 | 151 | 152 | /** 153 | * provide a quick way to count slugs, simply search for '{' 154 | */ 155 | int r3_slug_count(const char * needle, int len, char **errstr) { 156 | int cnt = 0; 157 | int state = 0; 158 | char * p = (char*) needle; 159 | 160 | while( (p-needle) < len) { 161 | if (*p == '\\' ) { 162 | p++; p++; 163 | continue; 164 | } 165 | 166 | if (state == 1 && *p == '}') { 167 | cnt++; 168 | } 169 | if ( *p == '{' ) { 170 | state++; 171 | } else if ( *p == '}' ) { 172 | state--; 173 | } 174 | p++; 175 | }; 176 | info("FOUND PATTERN: '%s' (%d), STATE: %d\n", needle, len, state); 177 | if (state != 0) { 178 | if (errstr) { 179 | char *err = NULL; 180 | int r = asprintf(&err, "Incomplete slug pattern. PATTERN (%d): '%s', OFFSET: %ld, STATE: %d", len, needle, p - needle, state); 181 | if (r) {}; 182 | *errstr = err; 183 | } 184 | return -1; 185 | } 186 | return cnt; 187 | } 188 | 189 | 190 | -------------------------------------------------------------------------------- /src/slug.h: -------------------------------------------------------------------------------- 1 | /* 2 | * slug.h 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #ifndef SLUG_H 8 | #define SLUG_H 9 | 10 | typedef struct { 11 | /** 12 | * source path 13 | */ 14 | const char * path; 15 | 16 | int path_len; 17 | 18 | /** 19 | * slug start pointer 20 | */ 21 | const char * begin; 22 | 23 | /** 24 | * slug end pointer 25 | */ 26 | const char * end; 27 | 28 | /** 29 | * slug length 30 | */ 31 | int len; 32 | 33 | // slug pattern pointer if we have one 34 | const char * pattern; 35 | 36 | // the length of custom pattern, if the pattern is found. 37 | int pattern_len; 38 | 39 | } r3_slug_t; 40 | 41 | 42 | r3_slug_t * r3_slug_new(const char * path, int path_len); 43 | 44 | int r3_slug_check(r3_slug_t *s); 45 | 46 | int r3_slug_parse(r3_slug_t *s, const char *needle, int needle_len, const char *offset, char **errstr); 47 | 48 | char * r3_slug_to_str(const r3_slug_t *s); 49 | 50 | void r3_slug_free(r3_slug_t * s); 51 | 52 | static inline int r3_path_contains_slug_char(const char *str, unsigned int len) { 53 | for (unsigned int i = 0; i < len; i++) { 54 | if (str[i] == '{') return 1; 55 | } 56 | return 0; 57 | } 58 | 59 | #endif /* !SLUG_H */ 60 | -------------------------------------------------------------------------------- /src/str.c: -------------------------------------------------------------------------------- 1 | /* 2 | * str.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include "config.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "r3.h" 13 | #include "r3_slug.h" 14 | #include "str.h" 15 | #include "slug.h" 16 | 17 | static const char * strnchr(const char* str, unsigned int len, int ch) { 18 | for (unsigned int i = 0; i < len; i++) { 19 | if (str[i] == ch) return str + i; 20 | } 21 | return NULL; 22 | } 23 | 24 | int r3_pattern_to_opcode(const char * pattern, unsigned int len) { 25 | if ( strncmp(pattern, "\\w+",len) == 0 ) { 26 | return OP_EXPECT_MORE_WORDS; 27 | } 28 | if ( strncmp(pattern, "[0-9a-z]+",len) == 0 || strncmp(pattern, "[a-z0-9]+",len) == 0 ) { 29 | return OP_EXPECT_MORE_WORDS; 30 | } 31 | if ( strncmp(pattern, "[a-z]+",len) == 0 ) { 32 | return OP_EXPECT_MORE_ALPHA; 33 | } 34 | if ( strncmp(pattern, "\\d+", len) == 0 ) { 35 | return OP_EXPECT_MORE_DIGITS; 36 | } 37 | if ( strncmp(pattern, "[0-9]+", len) == 0 ) { 38 | return OP_EXPECT_MORE_DIGITS; 39 | } 40 | if ( strncmp(pattern, "[^/]+", len) == 0 ) { 41 | return OP_EXPECT_NOSLASH; 42 | } 43 | if ( strncmp(pattern, "[^-]+", len) == 0 ) { 44 | return OP_EXPECT_NODASH; 45 | } 46 | if ( strncmp(pattern, ".*", len) == 0 ) { 47 | return OP_GREEDY_ANY; 48 | } 49 | return 0; 50 | } 51 | 52 | 53 | 54 | 55 | char * r3_inside_slug(const char * needle, int needle_len, char *offset, char **errstr) { 56 | char * s1 = offset; 57 | char * s2 = offset; 58 | 59 | short found_s1 = 0; 60 | short found_s2 = 0; 61 | 62 | while( s1 >= needle && (s1 - needle < needle_len) ) { 63 | if ( *s1 == '{' ) { 64 | found_s1 = 1; 65 | break; 66 | } 67 | s1--; 68 | } 69 | 70 | const char * end = needle + needle_len; 71 | while( (s2 + 1) < end ) { 72 | if ( *s2 == '}' ) { 73 | found_s2 = 1; 74 | break; 75 | } 76 | s2++; 77 | } 78 | if (found_s1 && found_s2) { 79 | return s1; 80 | } 81 | if (found_s1 || found_s2) { 82 | // wrong slug pattern 83 | if(errstr) { 84 | int r = asprintf(errstr, "Incomplete slug pattern"); 85 | if (r) {}; 86 | } 87 | return NULL; 88 | } 89 | return NULL; 90 | } 91 | 92 | const char * r3_slug_find_placeholder(const char *s1, unsigned int str_len, unsigned int *len) { 93 | const char *c; 94 | const char *s2; 95 | int cnt = 0; 96 | if ((c = strnchr(s1, str_len, '{'))) { 97 | // find closing '}' 98 | s2 = c; 99 | unsigned int j = str_len - (c - s1); 100 | for (unsigned int i = 0; i < j; i++) { 101 | if (*s2 == '{' ) 102 | cnt++; 103 | else if (*s2 == '}' ) 104 | cnt--; 105 | if (cnt == 0) 106 | break; 107 | s2++; 108 | } 109 | } else { 110 | return NULL; 111 | } 112 | if (cnt!=0) { 113 | return NULL; 114 | } 115 | if(len) { 116 | *len = s2 - c + 1; 117 | } 118 | return c; 119 | } 120 | 121 | 122 | /** 123 | * given a slug string, duplicate the pattern string of the slug 124 | */ 125 | const char * r3_slug_find_pattern(const char *s1, unsigned int str_len, unsigned int *len) { 126 | const char *c; 127 | const char *s2; 128 | unsigned int cnt = 1; 129 | if ( (c = strnchr(s1, str_len, ':')) ) { 130 | c++; 131 | // find closing '}' 132 | s2 = c; 133 | unsigned int j = str_len - (c - s1); 134 | for (unsigned int i = 0; i < j; i++) { 135 | if (*s2 == '{' ) 136 | cnt++; 137 | else if (*s2 == '}' ) 138 | cnt--; 139 | if (cnt == 0) 140 | break; 141 | s2++; 142 | } 143 | 144 | } else { 145 | return NULL; 146 | } 147 | if (cnt!=0) { 148 | return NULL; 149 | } 150 | *len = s2 - c; 151 | return c; 152 | } 153 | 154 | 155 | /** 156 | * given a slug string, duplicate the parameter name string of the slug 157 | */ 158 | const char * r3_slug_find_name(const char *s1, unsigned int str_len, unsigned int *len) { 159 | const char * c; 160 | const char * s2; 161 | unsigned int plholder; 162 | if ((c = r3_slug_find_placeholder(s1, str_len, &plholder))) { 163 | c++; 164 | if (( s2 = strnchr(c, plholder, ':') )) { 165 | *len = s2 - c; 166 | return c; 167 | } else { 168 | *len = plholder - 2; 169 | return c; 170 | } 171 | } else { 172 | return NULL; 173 | } 174 | } 175 | 176 | 177 | /** 178 | * @param char * sep separator 179 | */ 180 | char * r3_slug_compile(const char * str, unsigned int len) 181 | { 182 | const char *s1 = NULL; 183 | char *o = NULL; 184 | const char *pat = NULL; 185 | char sep = '/'; 186 | 187 | 188 | // append prefix 189 | unsigned int s1_len; 190 | s1 = r3_slug_find_placeholder(str, len, &s1_len); 191 | 192 | if ( !s1 ) { 193 | return strndup(str,len); 194 | } 195 | 196 | char * out = NULL; 197 | if (!(out = calloc(1, sizeof(char) * 200))) { 198 | return (NULL); 199 | } 200 | 201 | o = out; 202 | strncat(o, "^", 1); 203 | o++; 204 | 205 | strncat(o, str, s1 - str); // string before slug 206 | o += (s1 - str); 207 | 208 | 209 | unsigned int pat_len; 210 | pat = r3_slug_find_pattern(s1, s1_len, &pat_len); 211 | 212 | if (pat) { 213 | *o = '('; 214 | o++; 215 | strncat(o, pat, pat_len ); 216 | o += pat_len; 217 | *o = ')'; 218 | o++; 219 | } else { 220 | sprintf(o, "([^%c]+)", sep); 221 | o+= strlen("([^*]+)"); 222 | } 223 | s1 += s1_len; 224 | strncat(o, s1, len - (s1 - str)); // string after slug 225 | return out; 226 | } 227 | 228 | 229 | char * ltrim_slash(char* str) 230 | { 231 | char * p = str; 232 | while (*p == '/') p++; 233 | return strdup(p); 234 | } 235 | 236 | void print_indent(int level) { 237 | int len = level * 2; 238 | while(len--) { 239 | printf(" "); 240 | } 241 | } 242 | 243 | 244 | 245 | #ifndef HAVE_STRDUP 246 | char *strdup(const char *s) { 247 | char *out; 248 | int count = 0; 249 | while( s[count] ) 250 | ++count; 251 | ++count; 252 | out = malloc(sizeof(char) * count); 253 | out[--count] = 0; 254 | while( --count >= 0 ) 255 | out[count] = s[count]; 256 | return out; 257 | } 258 | #endif 259 | 260 | 261 | 262 | #ifndef HAVE_STRNDUP 263 | char *strndup(const char *s, int n) { 264 | char *out; 265 | int count = 0; 266 | while( count < n && s[count] ) 267 | ++count; 268 | ++count; 269 | out = malloc(sizeof(char) * count); 270 | out[--count] = 0; 271 | while( --count >= 0 ) 272 | out[count] = s[count]; 273 | return out; 274 | } 275 | #endif 276 | -------------------------------------------------------------------------------- /src/str.h: -------------------------------------------------------------------------------- 1 | #ifndef R3_STR_INTERN_H 2 | #define R3_STR_INTERN_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | void print_indent(int level); 9 | 10 | #ifdef __cplusplus 11 | } 12 | #endif 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/token.c: -------------------------------------------------------------------------------- 1 | /* 2 | * token.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include "config.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "r3.h" 13 | #include "r3_slug.h" 14 | #include "str_array.h" 15 | #include "memory.h" 16 | 17 | void str_array_free(str_array *l) { 18 | assert(l); 19 | free(l->tokens.entries); 20 | } 21 | 22 | bool str_array_append(str_array * l, const char * token, unsigned int len) { 23 | r3_vector_reserve(&l->tokens, l->tokens.size + 1); 24 | r3_iovec_t *temp = l->tokens.entries + l->tokens.size++; 25 | memset(temp, 0, sizeof(*temp)); 26 | temp->base = token; 27 | temp->len = len; 28 | return true; 29 | } 30 | 31 | void str_array_dump_slugs(const str_array *l) { 32 | if (l->tokens.size) { 33 | printf("["); 34 | for ( int i = 0; i < l->tokens.size ; i++ ) { 35 | printf("\"%*.*s\"", l->slugs.entries[i].len,l->slugs.entries[i].len,l->slugs.entries[i].base ); 36 | if ( i + 1 != l->tokens.size ) { 37 | printf(", "); 38 | } 39 | } 40 | printf("]\n"); 41 | } else { 42 | printf("[]\n"); 43 | } 44 | } 45 | 46 | void str_array_dump(const str_array *l) { 47 | printf("["); 48 | for ( int i = 0; i < l->tokens.size ; i++ ) { 49 | printf("\"%*.*s\"", l->tokens.entries[i].len,l->tokens.entries[i].len,l->tokens.entries[i].base ); 50 | // printf("\"%s\"", l->tokens.entries[i] ); 51 | if ( i + 1 != l->tokens.size ) { 52 | printf(", "); 53 | } 54 | } 55 | printf("]\n"); 56 | } 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(add_r3_test NAME) 2 | add_executable(${NAME} ${ARGN}) 3 | 4 | target_include_directories(${NAME} 5 | PRIVATE 6 | ${CHECK_INCLUDE_DIRS} 7 | ${PROJECT_BINARY_DIR} 8 | ${PROJECT_SOURCE_DIR}/src) 9 | 10 | target_link_libraries(${NAME} 11 | ${CHECK_LDFLAGS} 12 | r3) 13 | 14 | add_test(NAME ${NAME} COMMAND ${NAME}) 15 | endfunction() 16 | 17 | add_r3_test(check_tree check_tree.c) 18 | add_r3_test(check_slug check_slug.c) 19 | add_r3_test(check_routes check_routes.c) 20 | add_r3_test(check_str_array check_str_array.c) 21 | add_r3_test(check_host check_host.c) 22 | add_r3_test(check_http_scheme check_http_scheme.c) 23 | add_r3_test(check_remote_addr check_remote_addr.c) 24 | add_r3_test(check_routes2 check_routes2.c) 25 | 26 | add_executable(bench bench.c) 27 | target_link_libraries(bench r3) 28 | -------------------------------------------------------------------------------- /tests/Makefile.am: -------------------------------------------------------------------------------- 1 | TESTS = 2 | 3 | AM_CFLAGS=$(DEPS_CFLAGS) $(GVC_DEPS_CFLAGS) $(JSONC_CFLAGS) @CHECK_CFLAGS@ -I$(top_builddir) -I$(top_builddir)/include -I$(top_builddir)/src -Wall -std=c99 -ggdb `pkg-config --cflags --libs check` 4 | AM_LDFLAGS=$(DEPS_LIBS) $(GVC_DEPS_LIBS) $(JSONC_LIBS) @CHECK_LIBS@ $(top_builddir)/libr3.la 5 | 6 | if USE_JEMALLOC 7 | AM_CFLAGS += -ljemalloc 8 | endif 9 | 10 | noinst_HEADERS = \ 11 | bench.h \ 12 | $(NULL) 13 | 14 | dist_noinst_DATA = \ 15 | $(NULL) 16 | 17 | 18 | TESTS += check_slug 19 | check_slug_SOURCES = check_slug.c 20 | 21 | TESTS += check_tree 22 | check_tree_SOURCES = check_tree.c 23 | 24 | TESTS += check_routes 25 | check_routes_SOURCES = check_routes.c 26 | 27 | TESTS += check_str_array 28 | check_str_array_SOURCES = check_str_array.c 29 | 30 | TESTS += check_host 31 | check_host_SOURCES = check_host.c 32 | 33 | TESTS += check_remote_addr 34 | check_remote_addr_SOURCES = check_remote_addr.c 35 | 36 | TESTS += check_http_scheme 37 | check_http_scheme_SOURCES = check_http_scheme.c 38 | 39 | TESTS += check_routes2 40 | check_routes2_SOURCES = check_routes2.c 41 | 42 | 43 | if ENABLE_JSON 44 | TESTS += check_json 45 | check_json_SOURCES = check_json.c 46 | endif 47 | 48 | 49 | if ENABLE_GRAPHVIZ 50 | TESTS += check_gvc 51 | check_gvc_SOURCES = check_gvc.c 52 | endif 53 | 54 | 55 | 56 | check_PROGRAMS = $(TESTS) 57 | 58 | noinst_PROGRAMS = bench 59 | bench_SOURCES = bench.c 60 | 61 | # AM_CFLAGS=$(DEPS_CFLAGS) -I$(top_builddir)/include 62 | # AM_CFLAGS=$(DEPS_CFLAGS) -I$(top_builddir) -I$(top_builddir)/include 63 | CLEANFILES = check_tree.log 64 | -------------------------------------------------------------------------------- /tests/bench.h: -------------------------------------------------------------------------------- 1 | /* 2 | * bench.h 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef BENCH_H 9 | #define BENCH_H 10 | 11 | #include 12 | 13 | #define MICRO_IN_SEC 1000000.00 14 | #define SEC_IN_MIN 60 15 | #define NUL '\0' 16 | 17 | typedef struct { 18 | long N; // N for each run 19 | long R; // runs 20 | double secs; 21 | double start; 22 | double end; 23 | } bench; 24 | 25 | unsigned long unixtime(void); 26 | 27 | double microtime(void); 28 | 29 | void bench_start(bench *b); 30 | 31 | void bench_stop(bench *b); 32 | 33 | double bench_iteration_speed(bench *b); 34 | 35 | void bench_print_summary(bench *b); 36 | 37 | double bench_duration(bench *b); 38 | 39 | void bench_append_csv(char *filename, int countOfB, ...); 40 | 41 | #define MEASURE(B) \ 42 | bench B; B.N = 1; B.R = 1; \ 43 | printf("Measuring " #B "...\n"); \ 44 | bench_start(&B); 45 | 46 | #define END_MEASURE(B) \ 47 | bench_stop(&B); 48 | 49 | #define BENCHMARK(B) \ 50 | bench B; B.N = 5000000; B.R = 3; \ 51 | printf("Benchmarking " #B "...\n"); \ 52 | bench_start(&B); \ 53 | for (int _r = 0; _r < B.R ; _r++ ) { \ 54 | for (int _i = 0; _i < B.N ; _i++ ) { 55 | 56 | #define END_BENCHMARK(B) \ 57 | } \ 58 | } \ 59 | bench_stop(&B); 60 | 61 | #define BENCHMARK_SUMMARY(B) bench_print_summary(&B); 62 | 63 | #define BENCHMARK_RECORD_CSV(filename, countOfB, ...) \ 64 | bench_append_csv(filename, countOfB, __VA_ARGS__) 65 | 66 | #define BR(b) &b 67 | 68 | #endif /* !BENCH_H */ 69 | -------------------------------------------------------------------------------- /tests/check_gvc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * check_gvc.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include "config.h" 8 | #include 9 | #include 10 | #include 11 | #include "r3.h" 12 | #include "r3_gvc.h" 13 | #include "r3_slug.h" 14 | #include "bench.h" 15 | 16 | START_TEST (test_gvc_render_dot) 17 | { 18 | node * n = r3_tree_create(1); 19 | 20 | r3_tree_insert_path(n, "/foo/bar/baz", NULL); 21 | r3_tree_insert_path(n, "/foo/bar/qux", NULL); 22 | r3_tree_insert_path(n, "/foo/bar/quux", NULL); 23 | r3_tree_insert_path(n, "/foo/bar/corge", NULL); 24 | r3_tree_insert_path(n, "/foo/bar/grault", NULL); 25 | r3_tree_insert_path(n, "/garply/grault/foo", NULL); 26 | r3_tree_insert_path(n, "/garply/grault/bar", NULL); 27 | 28 | r3_tree_compile(n, NULL); 29 | 30 | r3_tree_render_dot(n, "dot", stderr); 31 | 32 | r3_tree_free(n); 33 | } 34 | END_TEST 35 | 36 | START_TEST (test_gvc_render_file) 37 | { 38 | node * n = r3_tree_create(1); 39 | 40 | r3_tree_insert_path(n, "/foo/bar/baz", NULL); 41 | r3_tree_insert_path(n, "/foo/bar/qux", NULL); 42 | r3_tree_insert_path(n, "/foo/bar/quux", NULL); 43 | r3_tree_insert_path(n, "/foo/bar/corge", NULL); 44 | r3_tree_insert_path(n, "/foo/bar/grault", NULL); 45 | r3_tree_insert_path(n, "/garply/grault/foo", NULL); 46 | r3_tree_insert_path(n, "/garply/grault/bar", NULL); 47 | r3_tree_insert_path(n, "/user/{id}", NULL); 48 | r3_tree_insert_path(n, "/post/{title:\\w+}", NULL); 49 | 50 | char *errstr = NULL; 51 | int errcode; 52 | errcode = r3_tree_compile(n, &errstr); 53 | 54 | r3_tree_render_file(n, "png", "check_gvc.png"); 55 | r3_tree_free(n); 56 | } 57 | END_TEST 58 | 59 | 60 | Suite* r3_suite (void) { 61 | Suite *suite = suite_create("gvc test"); 62 | TCase *tcase = tcase_create("test_gvc"); 63 | tcase_add_test(tcase, test_gvc_render_file); 64 | tcase_add_test(tcase, test_gvc_render_dot); 65 | suite_add_tcase(suite, tcase); 66 | return suite; 67 | } 68 | 69 | int main (int argc, char *argv[]) { 70 | int number_failed; 71 | Suite *suite = r3_suite(); 72 | SRunner *runner = srunner_create(suite); 73 | srunner_run_all(runner, CK_NORMAL); 74 | number_failed = srunner_ntests_failed(runner); 75 | srunner_free(runner); 76 | return number_failed; 77 | } 78 | -------------------------------------------------------------------------------- /tests/check_host.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "r3.h" 7 | #include "r3_slug.h" 8 | 9 | START_TEST (test_hosts) 10 | { 11 | R3Node * n = r3_tree_create(10); 12 | R3Route * route = NULL; 13 | match_entry * entry; 14 | R3Route *matched_route; 15 | 16 | char * uri0 = "/foo"; 17 | char * host0 = "foo.com"; 18 | route = r3_tree_insert_routel(n, 0, uri0, strlen(uri0), &uri0); 19 | route->host.base = host0; 20 | route->host.len = strlen(host0); 21 | 22 | char * uri1 = "/bar"; 23 | char * host1 = "*.bar.com"; 24 | route = r3_tree_insert_routel(n, 0, uri1, strlen(uri1), &uri1); 25 | route->host.base = host1; 26 | route->host.len = strlen(host1); 27 | 28 | char * err = NULL; 29 | r3_tree_compile(n, &err); 30 | ck_assert(err == NULL); 31 | 32 | entry = match_entry_create("/foo"); 33 | entry->host.base = host0; 34 | entry->host.len = strlen(host0); 35 | matched_route = r3_tree_match_route(n, entry); 36 | ck_assert(matched_route != NULL); 37 | ck_assert(matched_route->data == &uri0); 38 | match_entry_free(entry); 39 | 40 | entry = match_entry_create("/bar"); 41 | entry->host.base = "www.bar.com"; 42 | entry->host.len = strlen("www.bar.com"); 43 | matched_route = r3_tree_match_route(n, entry); 44 | ck_assert(matched_route != NULL); 45 | ck_assert(matched_route->data == &uri1); 46 | match_entry_free(entry); 47 | 48 | entry = match_entry_create("/bar"); 49 | entry->host.base = "bar.com"; 50 | entry->host.len = strlen("bar.com"); 51 | matched_route = r3_tree_match_route(n, entry); 52 | ck_assert(matched_route == NULL); 53 | match_entry_free(entry); 54 | 55 | entry = match_entry_create("/bar"); 56 | entry->host.base = ".bar.com"; 57 | entry->host.len = strlen(".bar.com"); 58 | matched_route = r3_tree_match_route(n, entry); 59 | ck_assert(matched_route == NULL); 60 | match_entry_free(entry); 61 | 62 | entry = match_entry_create("/bar"); 63 | entry->host.base = "a.bar.com"; 64 | entry->host.len = strlen("a.bar.com"); 65 | matched_route = r3_tree_match_route(n, entry); 66 | ck_assert(matched_route != NULL); 67 | ck_assert(matched_route->data == &uri1); 68 | match_entry_free(entry); 69 | 70 | r3_tree_free(n); 71 | } 72 | END_TEST 73 | 74 | 75 | 76 | Suite* r3_suite (void) { 77 | Suite *suite = suite_create("r3 host tests"); 78 | TCase *tcase = tcase_create("testcase"); 79 | tcase_add_test(tcase, test_hosts); 80 | suite_add_tcase(suite, tcase); 81 | return suite; 82 | } 83 | 84 | int main (int argc, char *argv[]) { 85 | int number_failed; 86 | Suite *suite = r3_suite(); 87 | SRunner *runner = srunner_create(suite); 88 | srunner_run_all(runner, CK_NORMAL); 89 | number_failed = srunner_ntests_failed(runner); 90 | srunner_free(runner); 91 | return number_failed; 92 | } 93 | -------------------------------------------------------------------------------- /tests/check_http_scheme.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "r3.h" 8 | #include "r3_slug.h" 9 | 10 | START_TEST (test_http_scheme) 11 | { 12 | R3Node * n = r3_tree_create(10); 13 | R3Route * route = NULL; 14 | match_entry * entry; 15 | R3Route *matched_route; 16 | 17 | char * uri0 = "/foo"; 18 | route = r3_tree_insert_routel(n, 0, uri0, strlen(uri0), &uri0); 19 | 20 | char * uri1 = "/bar"; 21 | route = r3_tree_insert_routel(n, 0, uri1, strlen(uri1), &uri1); 22 | route->http_scheme = SCHEME_HTTPS; 23 | 24 | char * uri2 = "/boo"; 25 | route = r3_tree_insert_routel(n, 0, uri2, strlen(uri2), &uri2); 26 | route->http_scheme = SCHEME_HTTP | SCHEME_HTTPS; 27 | 28 | char * err = NULL; 29 | r3_tree_compile(n, &err); 30 | ck_assert(err == NULL); 31 | 32 | 33 | entry = match_entry_create("/foo"); 34 | matched_route = r3_tree_match_route(n, entry); 35 | ck_assert(matched_route != NULL); 36 | ck_assert(matched_route->data == &uri0); 37 | match_entry_free(entry); 38 | 39 | entry = match_entry_create("/foo"); 40 | entry->http_scheme = SCHEME_HTTP; 41 | matched_route = r3_tree_match_route(n, entry); 42 | ck_assert(matched_route != NULL); 43 | ck_assert(matched_route->data == &uri0); 44 | match_entry_free(entry); 45 | 46 | entry = match_entry_create("/bar"); 47 | matched_route = r3_tree_match_route(n, entry); 48 | ck_assert(matched_route == NULL); 49 | match_entry_free(entry); 50 | 51 | entry = match_entry_create("/bar"); 52 | entry->http_scheme = SCHEME_HTTP; 53 | matched_route = r3_tree_match_route(n, entry); 54 | ck_assert(matched_route == NULL); 55 | match_entry_free(entry); 56 | 57 | entry = match_entry_create("/bar"); 58 | entry->http_scheme = SCHEME_HTTPS; 59 | matched_route = r3_tree_match_route(n, entry); 60 | ck_assert(matched_route != NULL); 61 | ck_assert(matched_route->data == &uri1); 62 | match_entry_free(entry); 63 | 64 | entry = match_entry_create("/boo"); 65 | matched_route = r3_tree_match_route(n, entry); 66 | ck_assert(matched_route == NULL); 67 | match_entry_free(entry); 68 | 69 | entry = match_entry_create("/boo"); 70 | entry->http_scheme = SCHEME_HTTP; 71 | matched_route = r3_tree_match_route(n, entry); 72 | ck_assert(matched_route != NULL); 73 | ck_assert(matched_route->data == &uri2); 74 | match_entry_free(entry); 75 | 76 | entry = match_entry_create("/boo"); 77 | entry->http_scheme = SCHEME_HTTPS; 78 | matched_route = r3_tree_match_route(n, entry); 79 | ck_assert(matched_route != NULL); 80 | ck_assert(matched_route->data == &uri2); 81 | match_entry_free(entry); 82 | 83 | r3_tree_free(n); 84 | } 85 | END_TEST 86 | 87 | 88 | 89 | Suite* r3_suite (void) { 90 | Suite *suite = suite_create("r3 remote_addr tests"); 91 | TCase *tcase = tcase_create("testcase"); 92 | tcase_add_test(tcase, test_http_scheme); 93 | suite_add_tcase(suite, tcase); 94 | return suite; 95 | } 96 | 97 | int main (int argc, char *argv[]) { 98 | int number_failed; 99 | Suite *suite = r3_suite(); 100 | SRunner *runner = srunner_create(suite); 101 | srunner_run_all(runner, CK_NORMAL); 102 | number_failed = srunner_ntests_failed(runner); 103 | srunner_free(runner); 104 | return number_failed; 105 | } 106 | -------------------------------------------------------------------------------- /tests/check_json.c: -------------------------------------------------------------------------------- 1 | /* 2 | * check_json.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include "config.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "r3.h" 13 | #include "r3_slug.h" 14 | #include "r3_json.h" 15 | #include "zmalloc.h" 16 | 17 | START_TEST (test_json_encode) 18 | { 19 | R3Node * n; 20 | n = r3_tree_create(10); 21 | 22 | ck_assert(n); 23 | 24 | r3_tree_insert_path(n, "/zoo", NULL); 25 | r3_tree_insert_path(n, "/foo", NULL); 26 | r3_tree_insert_path(n, "/bar", NULL); 27 | 28 | r3_tree_insert_route(n, METHOD_GET, "/post/get", NULL); 29 | r3_tree_insert_route(n, METHOD_POST, "/post/post", NULL); 30 | 31 | r3_tree_compile(n, NULL); 32 | 33 | json_object * obj = r3_node_to_json_object(n); 34 | ck_assert(obj); 35 | 36 | const char *json = r3_node_to_json_pretty_string(n); 37 | 38 | printf("JSON: %s\n",json); 39 | } 40 | END_TEST 41 | 42 | Suite* r3_suite (void) { 43 | Suite *suite = suite_create("json test"); 44 | TCase *tcase = tcase_create("json test"); 45 | tcase_add_test(tcase, test_json_encode); 46 | suite_add_tcase(suite, tcase); 47 | return suite; 48 | } 49 | 50 | int main (int argc, char *argv[]) { 51 | int number_failed; 52 | Suite *suite = r3_suite(); 53 | SRunner *runner = srunner_create(suite); 54 | srunner_run_all(runner, CK_NORMAL); 55 | number_failed = srunner_ntests_failed(runner); 56 | srunner_free(runner); 57 | return number_failed; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /tests/check_remote_addr.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "r3.h" 10 | #include "r3_slug.h" 11 | 12 | START_TEST (test_remote_addrs) 13 | { 14 | R3Node * n = r3_tree_create(10); 15 | R3Route * route = NULL; 16 | match_entry * entry; 17 | R3Route *matched_route; 18 | 19 | char * uri0 = "/foo"; 20 | route = r3_tree_insert_routel(n, 0, uri0, strlen(uri0), &uri0); 21 | route->remote_addr_v4 = 0; 22 | route->remote_addr_v4_bits = 0; 23 | 24 | char * uri1 = "/bar"; 25 | route = r3_tree_insert_routel(n, 0, uri1, strlen(uri1), &uri1); 26 | route->remote_addr_v4 = inet_network("127.0.0.1"); 27 | route->remote_addr_v4_bits = 32; 28 | 29 | char * uri2 = "/boo"; 30 | route = r3_tree_insert_routel(n, 0, uri2, strlen(uri2), &uri2); 31 | route->remote_addr_v4 = inet_network("127.0.0.1"); 32 | route->remote_addr_v4_bits = 24; 33 | 34 | char * err = NULL; 35 | r3_tree_compile(n, &err); 36 | ck_assert(err == NULL); 37 | 38 | 39 | entry = match_entry_create("/foo"); 40 | entry->remote_addr.base = "127.0.0.1"; 41 | entry->remote_addr.len = sizeof("127.0.0.1") - 1; 42 | matched_route = r3_tree_match_route(n, entry); 43 | ck_assert(matched_route != NULL); 44 | ck_assert(matched_route->data == &uri0); 45 | match_entry_free(entry); 46 | 47 | entry = match_entry_create("/bar"); 48 | entry->remote_addr.base = "127.0.0.1"; 49 | entry->remote_addr.len = sizeof("127.0.0.1") - 1; 50 | matched_route = r3_tree_match_route(n, entry); 51 | ck_assert(matched_route != NULL); 52 | ck_assert(matched_route->data == &uri1); 53 | match_entry_free(entry); 54 | 55 | entry = match_entry_create("/bar"); 56 | entry->remote_addr.base = "127.0.0.2"; 57 | entry->remote_addr.len = sizeof("127.0.0.2") - 1; 58 | matched_route = r3_tree_match_route(n, entry); 59 | ck_assert(matched_route == NULL); 60 | match_entry_free(entry); 61 | 62 | entry = match_entry_create("/boo"); 63 | entry->remote_addr.base = "127.0.0.1"; 64 | entry->remote_addr.len = sizeof("127.0.0.1") - 1; 65 | matched_route = r3_tree_match_route(n, entry); 66 | ck_assert(matched_route != NULL); 67 | ck_assert(matched_route->data == &uri2); 68 | match_entry_free(entry); 69 | 70 | entry = match_entry_create("/boo"); 71 | entry->remote_addr.base = "127.0.0.2"; 72 | entry->remote_addr.len = sizeof("127.0.0.2") - 1; 73 | matched_route = r3_tree_match_route(n, entry); 74 | ck_assert(matched_route != NULL); 75 | ck_assert(matched_route->data == &uri2); 76 | match_entry_free(entry); 77 | 78 | entry = match_entry_create("/boo"); 79 | entry->remote_addr.base = "127.0.1.2"; 80 | entry->remote_addr.len = sizeof("127.0.1.2") - 1; 81 | matched_route = r3_tree_match_route(n, entry); 82 | ck_assert(matched_route == NULL); 83 | match_entry_free(entry); 84 | 85 | entry = match_entry_create("/boo"); 86 | entry->remote_addr.base = "127.0.1.333"; // invalid ip address 87 | entry->remote_addr.len = sizeof("127.0.1.333") - 1; 88 | matched_route = r3_tree_match_route(n, entry); 89 | ck_assert(matched_route == NULL); 90 | match_entry_free(entry); 91 | 92 | r3_tree_free(n); 93 | } 94 | END_TEST 95 | 96 | 97 | void parse_ipv6(const char *ipv6, int nmask, R3Route * route) 98 | { 99 | struct in6_addr addr6; 100 | int ret = inet_pton(AF_INET6, ipv6, (void *)&addr6); 101 | ck_assert(ret == 1); 102 | 103 | for (int i = 0; i < 4; i++) { 104 | route->remote_addr_v6[i] = ntohl(addr6.__in6_u.__u6_addr32[i]); 105 | 106 | if (nmask >= 32) { 107 | route->remote_addr_v6_bits[i] = 32; 108 | } else if (nmask > 0) { 109 | route->remote_addr_v6_bits[i] = nmask; 110 | } else { 111 | route->remote_addr_v6_bits[i] = 0; 112 | } 113 | 114 | nmask -= 32; 115 | } 116 | } 117 | 118 | 119 | START_TEST (test_remote_addrs_ipv6) 120 | { 121 | R3Node * n = r3_tree_create(10); 122 | R3Route * route = NULL; 123 | match_entry * entry; 124 | R3Route *matched_route; 125 | 126 | char * uri0 = "/foo"; 127 | route = r3_tree_insert_routel(n, 0, uri0, strlen(uri0), &uri0); 128 | parse_ipv6("fe80:fe80::1", 128, route); // "fe80:fe80::1" 129 | 130 | char * uri1 = "/bar"; 131 | route = r3_tree_insert_routel(n, 0, uri1, strlen(uri1), &uri1); 132 | parse_ipv6("fe80:fe80::", 32, route); // "fe80:fe80::/32" 133 | 134 | char * uri2 = "/goo"; 135 | route = r3_tree_insert_routel(n, 0, uri2, strlen(uri2), &uri2); 136 | parse_ipv6("::1", 128, route); // "::1" 137 | 138 | char * err = NULL; 139 | r3_tree_compile(n, &err); 140 | ck_assert(err == NULL); 141 | 142 | entry = match_entry_create("/foo"); 143 | entry->remote_addr.base = "fe80:fe80::1"; 144 | entry->remote_addr.len = sizeof("fe80:fe80::1") - 1; 145 | matched_route = r3_tree_match_route(n, entry); 146 | ck_assert(matched_route != NULL); 147 | ck_assert(matched_route->data == &uri0); 148 | match_entry_free(entry); 149 | 150 | entry = match_entry_create("/foo"); 151 | entry->remote_addr.base = "fe80:fe80::2"; 152 | entry->remote_addr.len = sizeof("fe80:fe80::2") - 1; 153 | matched_route = r3_tree_match_route(n, entry); 154 | ck_assert(matched_route == NULL); 155 | match_entry_free(entry); 156 | 157 | entry = match_entry_create("/foo"); 158 | entry->remote_addr.base = "fe88:fe80::1"; 159 | entry->remote_addr.len = sizeof("fe88:fe80::1") - 1; 160 | matched_route = r3_tree_match_route(n, entry); 161 | ck_assert(matched_route == NULL); 162 | match_entry_free(entry); 163 | 164 | entry = match_entry_create("/bar"); 165 | entry->remote_addr.base = "fe80:fe80::1"; 166 | entry->remote_addr.len = sizeof("fe80:fe80::1") - 1; 167 | matched_route = r3_tree_match_route(n, entry); 168 | ck_assert(matched_route != NULL); 169 | ck_assert(matched_route->data == &uri1); 170 | match_entry_free(entry); 171 | 172 | entry = match_entry_create("/bar"); 173 | entry->remote_addr.base = "fe88:fe80::1"; 174 | entry->remote_addr.len = sizeof("fe88:fe80::1") - 1; 175 | matched_route = r3_tree_match_route(n, entry); 176 | ck_assert(matched_route == NULL); 177 | match_entry_free(entry); 178 | 179 | entry = match_entry_create("/goo"); 180 | entry->remote_addr.base = "::1"; 181 | entry->remote_addr.len = sizeof("::1") - 1; 182 | matched_route = r3_tree_match_route(n, entry); 183 | ck_assert(matched_route != NULL); 184 | ck_assert(matched_route->data == &uri2); 185 | match_entry_free(entry); 186 | 187 | entry = match_entry_create("/goo"); 188 | entry->remote_addr.base = "::2"; 189 | entry->remote_addr.len = sizeof("::2") - 1; 190 | matched_route = r3_tree_match_route(n, entry); 191 | ck_assert(matched_route == NULL); 192 | match_entry_free(entry); 193 | 194 | r3_tree_free(n); 195 | } 196 | END_TEST 197 | 198 | 199 | Suite* r3_suite (void) { 200 | Suite *suite = suite_create("r3 remote_addr tests"); 201 | TCase *tcase = tcase_create("testcase"); 202 | tcase_add_test(tcase, test_remote_addrs); 203 | tcase_add_test(tcase, test_remote_addrs_ipv6); 204 | suite_add_tcase(suite, tcase); 205 | return suite; 206 | } 207 | 208 | int main (int argc, char *argv[]) { 209 | int number_failed; 210 | Suite *suite = r3_suite(); 211 | SRunner *runner = srunner_create(suite); 212 | srunner_run_all(runner, CK_NORMAL); 213 | number_failed = srunner_ntests_failed(runner); 214 | srunner_free(runner); 215 | return number_failed; 216 | } 217 | -------------------------------------------------------------------------------- /tests/check_routes2.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "r3.h" 7 | #include "r3_slug.h" 8 | 9 | START_TEST (greedy_pattern) 10 | { 11 | R3Node * n = r3_tree_create(10); 12 | match_entry * entry; 13 | R3Route *matched_route; 14 | 15 | char * uri0 = "/foo{:.*}"; 16 | r3_tree_insert_routel(n, 0, uri0, strlen(uri0), &uri0); 17 | 18 | char * err = NULL; 19 | r3_tree_compile(n, &err); 20 | ck_assert(err == NULL); 21 | 22 | entry = match_entry_create("/foo/bar"); 23 | matched_route = r3_tree_match_route(n, entry); 24 | ck_assert(matched_route != NULL); 25 | ck_assert(matched_route->data == &uri0); 26 | match_entry_free(entry); 27 | 28 | entry = match_entry_create("/foo"); 29 | matched_route = r3_tree_match_route(n, entry); 30 | ck_assert(matched_route != NULL); 31 | ck_assert(matched_route->data == &uri0); 32 | match_entry_free(entry); 33 | 34 | entry = match_entry_create("/foo/"); 35 | matched_route = r3_tree_match_route(n, entry); 36 | ck_assert(matched_route != NULL); 37 | ck_assert(matched_route->data == &uri0); 38 | match_entry_free(entry); 39 | 40 | entry = match_entry_create("/foo/bar/foo/mmasdfasdfasd/f/asdf/as/df"); 41 | matched_route = r3_tree_match_route(n, entry); 42 | ck_assert(matched_route != NULL); 43 | ck_assert(matched_route->data == &uri0); 44 | match_entry_free(entry); 45 | 46 | r3_tree_free(n); 47 | } 48 | END_TEST 49 | 50 | START_TEST (common_pattern) 51 | { 52 | R3Node * n = r3_tree_create(10); 53 | match_entry * entry; 54 | R3Route *r, *matched_route; 55 | 56 | char * uri0 = "/foo/{slug}"; 57 | r = r3_tree_insert_routel(n, 0, uri0, strlen(uri0), &uri0); 58 | ck_assert(r != NULL); 59 | char * uri1 = "/foo/{slug}/bar"; 60 | r = r3_tree_insert_routel(n, 0, uri1, strlen(uri1), &uri1); 61 | ck_assert(r != NULL); 62 | 63 | char * err = NULL; 64 | r3_tree_compile(n, &err); 65 | ck_assert(err == NULL); 66 | 67 | entry = match_entry_create("/foo/bar"); 68 | matched_route = r3_tree_match_route(n, entry); 69 | ck_assert(matched_route != NULL); 70 | ck_assert(matched_route->data == &uri0); 71 | match_entry_free(entry); 72 | 73 | entry = match_entry_create("/foo/bar/bar"); 74 | matched_route = r3_tree_match_route(n, entry); 75 | ck_assert(matched_route != NULL); 76 | ck_assert(matched_route->data == &uri1); 77 | match_entry_free(entry); 78 | 79 | r3_tree_free(n); 80 | } 81 | END_TEST 82 | 83 | START_TEST (incomplete_pattern) 84 | { 85 | R3Node * n = r3_tree_create(10); 86 | R3Route * r; 87 | 88 | char * uri0 = "{slug"; 89 | r = r3_tree_insert_routel(n, 0, uri0, strlen(uri0), &uri0); 90 | ck_assert(r == NULL); 91 | char * uri1 = "/foo/{slug"; 92 | r = r3_tree_insert_routel(n, 0, uri1, strlen(uri1), &uri1); 93 | ck_assert(r == NULL); 94 | 95 | r3_tree_free(n); 96 | } 97 | END_TEST 98 | 99 | Suite* r3_suite (void) { 100 | Suite *suite = suite_create("r3 routes2 tests"); 101 | TCase *tcase = tcase_create("testcase"); 102 | tcase_add_test(tcase, greedy_pattern); 103 | tcase_add_test(tcase, common_pattern); 104 | tcase_add_test(tcase, incomplete_pattern); 105 | suite_add_tcase(suite, tcase); 106 | return suite; 107 | } 108 | 109 | int main (int argc, char *argv[]) { 110 | int number_failed; 111 | Suite *suite = r3_suite(); 112 | SRunner *runner = srunner_create(suite); 113 | srunner_run_all(runner, CK_NORMAL); 114 | number_failed = srunner_ntests_failed(runner); 115 | srunner_free(runner); 116 | return number_failed; 117 | } 118 | -------------------------------------------------------------------------------- /tests/check_slug.c: -------------------------------------------------------------------------------- 1 | /* 2 | * check_slug.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include "config.h" 8 | #include 9 | #include 10 | #include 11 | #include "r3.h" 12 | #include "r3_slug.h" 13 | #include "slug.h" 14 | #include "r3_debug.h" 15 | 16 | START_TEST (test_pattern_to_opcode) 17 | { 18 | ck_assert( r3_pattern_to_opcode("\\w+", strlen("\\w+")) == OP_EXPECT_MORE_WORDS ); 19 | ck_assert( r3_pattern_to_opcode("\\d+", strlen("\\d+")) == OP_EXPECT_MORE_DIGITS ); 20 | ck_assert( r3_pattern_to_opcode("[^/]+",strlen("[^/]+")) == OP_EXPECT_NOSLASH ); 21 | ck_assert( r3_pattern_to_opcode("[^-]+",strlen("[^-]+")) == OP_EXPECT_NODASH ); 22 | } 23 | END_TEST 24 | 25 | START_TEST (test_r3_slug_compile) 26 | { 27 | char * path = "/user/{id}"; 28 | char * c = NULL; 29 | ck_assert_str_eq( c = r3_slug_compile(path, strlen(path) ) , "^/user/([^/]+)" ); 30 | free(c); 31 | 32 | char * path2 = "/what/{id}-foo"; 33 | ck_assert_str_eq( c = r3_slug_compile(path2, strlen(path2) ) , "^/what/([^/]+)-foo" ); 34 | free(c); 35 | 36 | char * path3 = "-{id}"; 37 | ck_assert_str_eq( c = r3_slug_compile(path3, strlen(path3)), "^-([^/]+)" ); 38 | free(c); 39 | 40 | char * path4 = "-{idx:\\d{3}}"; 41 | ck_assert_str_eq( c = r3_slug_compile(path4, strlen(path4)), "^-(\\d{3})" ); 42 | free(c); 43 | } 44 | END_TEST 45 | 46 | START_TEST (test_contains_slug) 47 | { 48 | char *test_str = "/user/{id}/{name}"; 49 | ck_assert( r3_path_contains_slug_char(test_str, strlen(test_str)) ); 50 | } 51 | END_TEST 52 | 53 | START_TEST (test_r3_slug_find_pattern) 54 | { 55 | unsigned int len; 56 | char *test_str = "{name:\\s+}"; 57 | const char * namerex = r3_slug_find_pattern(test_str, strlen(test_str), &len); 58 | ck_assert( strncmp(namerex, "\\s+", len) == 0 ); 59 | } 60 | END_TEST 61 | 62 | START_TEST (test_r3_slug_find_name) 63 | { 64 | unsigned int len; 65 | char *test_str = "{name:\\s+}"; 66 | const char * namerex = r3_slug_find_name(test_str, strlen(test_str), &len); 67 | ck_assert( strncmp(namerex, "name", len) == 0 ); 68 | } 69 | END_TEST 70 | 71 | START_TEST (test_r3_slug_find_name_without_pattern) 72 | { 73 | unsigned int len; 74 | char *test_str = "{name}"; 75 | const char * namerex = r3_slug_find_name(test_str, strlen(test_str), &len); 76 | ck_assert( strncmp(namerex, "name", len) == 0 ); 77 | } 78 | END_TEST 79 | 80 | START_TEST (test_r3_slug_find_name_with_multiple_slug) 81 | { 82 | unsigned int len; 83 | char *test_str = "{name}/{name2}"; 84 | const char * namerex = r3_slug_find_name(test_str, strlen(test_str), &len); 85 | ck_assert( strncmp(namerex, "name", len) == 0 ); 86 | } 87 | END_TEST 88 | 89 | START_TEST (test_r3_slug_find_placeholder) 90 | { 91 | unsigned int slug_len = 0; 92 | const char * slug; 93 | char *test_str = "/user/{name:\\s+}/to/{id}"; 94 | slug = r3_slug_find_placeholder(test_str, strlen(test_str), &slug_len); 95 | ck_assert( strncmp(slug, "{name:\\s+}", slug_len) == 0 ); 96 | 97 | test_str = "/user/{idx:\\d{3}}/to/{idy:\\d{3}}"; 98 | slug = r3_slug_find_placeholder(test_str, strlen(test_str), &slug_len); 99 | ck_assert( slug_len == strlen("{idx:\\d{3}}") ); 100 | ck_assert( strncmp(slug, "{idx:\\d{3}}", slug_len) == 0 ); 101 | } 102 | END_TEST 103 | 104 | START_TEST (test_r3_inside_slug) 105 | { 106 | char * pattern = "/user/{name:\\s+}/to/{id}"; 107 | char * offset = strchr(pattern, '{') + 2; 108 | ck_assert( r3_inside_slug(pattern, strlen(pattern), offset, NULL) != NULL ); 109 | ck_assert( *(r3_inside_slug(pattern, strlen(pattern), offset, NULL)) == '{' ); 110 | ck_assert( ! r3_inside_slug(pattern, strlen(pattern), pattern, NULL) ); 111 | } 112 | END_TEST 113 | 114 | START_TEST (test_incomplete_slug) 115 | { 116 | int cnt = 0; 117 | char * errstr = NULL; 118 | char * pattern = "/user/{name:\\d{3}}/to/{id"; 119 | cnt = r3_slug_count(pattern, strlen(pattern), &errstr); 120 | ck_assert_int_eq(cnt, -1); 121 | ck_assert(errstr != NULL); 122 | printf("%s\n",errstr); 123 | free(errstr); 124 | } 125 | END_TEST 126 | 127 | 128 | /* 129 | START_TEST (test_slug_parse_with_pattern) 130 | { 131 | char * pattern = "/user/{name:\\d{3}}"; 132 | char * errstr = NULL; 133 | r3_slug_t s; 134 | int ret; 135 | ret = r3_slug_parse(&s, pattern, strlen(pattern), pattern, &errstr); 136 | ck_assert(ret); 137 | 138 | char * out = r3_slug_to_str(&s); 139 | ck_assert(out); 140 | printf("%s\n",out); 141 | free(out); 142 | } 143 | END_TEST 144 | 145 | 146 | START_TEST (test_slug_parse_without_pattern) 147 | { 148 | char * pattern = "/user/{name}"; 149 | char * errstr = NULL; 150 | r3_slug_t *s = r3_slug_new(pattern, strlen(pattern)); 151 | int ret; 152 | ret = r3_slug_parse(s, pattern, strlen(pattern), pattern, &errstr); 153 | ck_assert(s); 154 | 155 | char * out = r3_slug_to_str(s); 156 | ck_assert(out); 157 | printf("%s\n",out); 158 | free(out); 159 | 160 | r3_slug_free(s); 161 | } 162 | END_TEST 163 | */ 164 | 165 | 166 | 167 | 168 | 169 | 170 | START_TEST (test_r3_slug_count) 171 | { 172 | int cnt = 0; 173 | char * pattern = "/user/{name:\\s+}/to/{id}"; 174 | char * errstr = NULL; 175 | cnt = r3_slug_count(pattern, strlen(pattern), &errstr); 176 | ck_assert_int_eq(cnt, 2); 177 | if(errstr) free(errstr); 178 | 179 | char * pattern2 = "/user/{name:\\d{3}}/to/{id}"; 180 | cnt = r3_slug_count(pattern2, strlen(pattern2), &errstr); 181 | ck_assert_int_eq(cnt, 2); 182 | if(errstr) free(errstr); 183 | 184 | char * pattern3 = "/user/{name:\\d{3}}/to/{id}"; 185 | cnt = r3_slug_count(pattern3, strlen(pattern3), &errstr); 186 | ck_assert_int_eq(cnt, 2); 187 | if(errstr) free(errstr); 188 | } 189 | END_TEST 190 | 191 | START_TEST (test_r3_slug_find_placeholder_with_broken_slug) 192 | { 193 | unsigned int slug_len = 0; 194 | char *sl_test = "/user/{name:\\s+/to/{id"; 195 | const char * slug = r3_slug_find_placeholder(sl_test, strlen(sl_test), &slug_len); 196 | ck_assert(slug == 0); 197 | } 198 | END_TEST 199 | 200 | 201 | Suite* r3_suite (void) { 202 | Suite *suite = suite_create("slug test"); 203 | TCase *tcase = tcase_create("test_slug"); 204 | tcase_set_timeout(tcase, 30); 205 | tcase_add_test(tcase, test_contains_slug); 206 | tcase_add_test(tcase, test_r3_inside_slug); 207 | tcase_add_test(tcase, test_r3_slug_find_pattern); 208 | tcase_add_test(tcase, test_r3_slug_find_placeholder); 209 | tcase_add_test(tcase, test_r3_slug_find_placeholder_with_broken_slug); 210 | tcase_add_test(tcase, test_r3_slug_count); 211 | tcase_add_test(tcase, test_r3_slug_compile); 212 | tcase_add_test(tcase, test_pattern_to_opcode); 213 | tcase_add_test(tcase, test_incomplete_slug); 214 | tcase_add_test(tcase, test_r3_slug_find_name); 215 | tcase_add_test(tcase, test_r3_slug_find_name_without_pattern); 216 | tcase_add_test(tcase, test_r3_slug_find_name_with_multiple_slug); 217 | 218 | // tcase_add_test(tcase, test_slug_parse_with_pattern); 219 | // tcase_add_test(tcase, test_slug_parse_without_pattern); 220 | 221 | suite_add_tcase(suite, tcase); 222 | return suite; 223 | } 224 | 225 | int main (int argc, char *argv[]) { 226 | int number_failed; 227 | Suite *suite = r3_suite(); 228 | SRunner *runner = srunner_create(suite); 229 | srunner_run_all(runner, CK_NORMAL); 230 | number_failed = srunner_ntests_failed(runner); 231 | srunner_free(runner); 232 | return number_failed; 233 | } 234 | -------------------------------------------------------------------------------- /tests/check_str_array.c: -------------------------------------------------------------------------------- 1 | /* 2 | * check_str_array.c 3 | * Copyright (C) 2014 c9s 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | #include "config.h" 8 | #include 9 | #include 10 | #include 11 | #include "str_array.h" 12 | 13 | START_TEST (test_str_array) 14 | { 15 | str_array *vars = r3_mem_alloc(sizeof(str_array)); 16 | memset(vars, 0, sizeof(*vars)); 17 | 18 | char *test_str = "abc"; 19 | ck_assert( str_array_append(vars, test_str, strlen(test_str))); 20 | ck_assert( vars->tokens.size == 1 ); 21 | 22 | char *test_str1 = "foo"; 23 | ck_assert( str_array_append(vars, test_str1, strlen(test_str1))); 24 | ck_assert( vars->tokens.size == 2 ); 25 | 26 | char *test_str2 = "bar"; 27 | ck_assert( str_array_append(vars, test_str2, strlen(test_str2))); 28 | ck_assert( vars->tokens.size == 3 ); 29 | 30 | char *test_str3 = "zoo"; 31 | ck_assert( str_array_append(vars, test_str3, strlen(test_str3))); 32 | ck_assert( vars->tokens.size == 4 ); 33 | 34 | str_array_free(vars); 35 | free(vars); 36 | } 37 | END_TEST 38 | 39 | START_TEST (test_access_macros) 40 | { 41 | str_array *vars = r3_mem_alloc(sizeof(str_array)); 42 | memset(vars, 0, sizeof(*vars)); 43 | ck_assert( str_array_len(vars) == 0); 44 | ck_assert( str_array_cap(vars) == 0); 45 | 46 | r3_vector_reserve(&vars->tokens, 4); 47 | ck_assert( str_array_len(vars) == 0); 48 | ck_assert( str_array_cap(vars) == 4); 49 | 50 | char *token1 = "first"; 51 | char *token2 = "second"; 52 | ck_assert( str_array_append(vars, token1, strlen(token1))); 53 | ck_assert( str_array_append(vars, token2, strlen(token2))); 54 | ck_assert( str_array_len(vars) == 2); 55 | ck_assert( str_array_cap(vars) == 4); 56 | 57 | ck_assert( strncmp(str_array_fetch(vars,0).base, "first", 5) == 0); 58 | ck_assert( str_array_fetch(vars,0).len == 5); 59 | ck_assert( strncmp(str_array_fetch(vars,1).base, "second", 6) == 0); 60 | ck_assert( str_array_fetch(vars,1).len == 6); 61 | 62 | str_array_free(vars); 63 | free(vars); 64 | } 65 | END_TEST 66 | 67 | Suite* r3_suite (void) { 68 | Suite *suite = suite_create("str_array test"); 69 | TCase *tcase = tcase_create("testcase"); 70 | tcase_add_test(tcase, test_str_array); 71 | tcase_add_test(tcase, test_access_macros); 72 | suite_add_tcase(suite, tcase); 73 | return suite; 74 | } 75 | 76 | int main (int argc, char *argv[]) { 77 | int number_failed; 78 | Suite *suite = r3_suite(); 79 | SRunner *runner = srunner_create(suite); 80 | srunner_run_all(runner, CK_NORMAL); 81 | number_failed = srunner_ntests_failed(runner); 82 | srunner_free(runner); 83 | return number_failed; 84 | } 85 | --------------------------------------------------------------------------------