Commit 38d52d92 authored by Fylax's avatar Fylax

- Changed routing algorithm from linear search to tree based

- Ignore multiple consecutive forward slashes
- Added ability to add and remove routes at runtime
parent aaf1ae79
cmake_minimum_required (VERSION 3.0.2)
cmake_minimum_required (VERSION 3.8.2)
project (pistache)
include(CheckCXXCompilerFlag)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra -Wno-missing-field-initializers")
......@@ -8,15 +7,16 @@ option(PISTACHE_BUILD_TESTS "build tests alongside the project" OFF)
option(PISTACHE_BUILD_EXAMPLES "build examples alongside the project" OFF)
option(PISTACHE_INSTALL "add pistache as install target (recommended)" ON)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
if(COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
# Clang exports C++17 in std::experimental namespace (tested on Clang 5 and 6).
# This gives an error on date.h external library.
# Following workaround forces Clang to compile at best with C++14
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_CXX_STANDARD 14)
else()
message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
set(CMAKE_CXX_STANDARD 17)
endif()
set(CMAKE_CXX_STANDARD_REQUIRED OFF)
set(CMAKE_CXX_EXTENSIONS OFF)
include_directories (${CMAKE_CURRENT_SOURCE_DIR}/include)
add_subdirectory (src)
......
This diff is collapsed.
#pragma once
#define CUSTOM_STRING_VIEW 1
#if __cplusplus >= 201703L
# if defined(__has_include)
# if __has_include(<string_view>)
# undef CUSTOM_STRING_VIEW
# include <string_view>
# elif __has_include(<experimental/string_view>)
# undef CUSTOM_STRING_VIEW
# include <experimental/string_view>
namespace std {
typedef experimental::string_view string_view;
}
# endif
# endif
#endif
#ifdef CUSTOM_STRING_VIEW
#include <endian.h>
#include <string>
#include <stdexcept>
#include <cstring>
typedef std::size_t size_type;
namespace std {
class string_view {
private:
const char *data_;
size_type size_;
public:
static constexpr size_type npos = size_type(-1);
constexpr string_view() noexcept: data_(nullptr), size_(0) { }
constexpr string_view(const string_view& other) noexcept = default;
constexpr string_view(const char *s, size_type count) : data_(s), size_(count) { }
string_view(const char *s) : data_(s), size_(strlen(s)) { }
string_view
substr(size_type pos, size_type count = npos) const {
if (pos > size_) {
throw std::out_of_range("Out of range.");
}
size_type rcount = std::min(count, size_ - pos);
return {data_ + pos, rcount};
}
constexpr size_type
size() const noexcept {
return size_;
}
constexpr size_type
length() const noexcept {
return size_;
}
constexpr const char operator[](size_type pos) const {
return data_[pos];
}
constexpr const char *data() const noexcept {
return data_;
}
bool
operator==(const string_view &rhs) const {
return (!std::lexicographical_compare(data_, data_ + size_, rhs.data_, rhs.data_ + rhs.size_) &&
!std::lexicographical_compare(rhs.data_, rhs.data_ + rhs.size_, data_, data_ + size_));
}
string_view &
operator=(const string_view &view) noexcept = default;
size_type find(string_view v, size_type pos = 0) const noexcept {
for(; size_ - pos >= v.size_; pos++) {
string_view compare = substr(pos, v.size_);
if (v == compare) {
return pos;
}
}
return npos;
}
size_type find(char ch, size_type pos = 0) const noexcept {
return find(string_view(&ch, 1), pos);
}
size_type find(const char *s, size_type pos, size_type count) const {
return find(string_view(s, count), pos);
}
size_type find(const char *s, size_type pos = 0) const {
return find(string_view(s), pos);
}
size_type rfind(string_view v, size_type pos = npos) const noexcept {
if (pos >= size_) {
pos = size_ - v.size_;
}
for(; pos >= 0 && pos != npos; pos--) {
string_view compare = substr(pos, v.size_);
if (v == compare) {
return pos;
}
}
return npos;
}
size_type rfind(char c, size_type pos = npos) const noexcept {
return rfind(string_view(&c, 1), pos);
}
size_type rfind(const char* s, size_type pos, size_type count) const {
return rfind(string_view(s, count), pos);
}
size_type rfind(const char* s, size_type pos = npos) const {
return rfind(string_view(s), pos);
}
constexpr bool empty() const noexcept {
return size_ == 0;
}
};
template<>
struct hash<string_view> {
//-----------------------------------------------------------------------------
// MurmurHash3 was written by Austin Appleby, and is placed in the public
// domain. The author hereby disclaims copyright to this source code.
private:
#ifdef __GNUC__
#define FORCE_INLINE __attribute__((always_inline)) inline
#else
#define FORCE_INLINE inline
#endif
static FORCE_INLINE std::uint32_t Rotl32(const std::uint32_t x,
const std::int8_t r) {
return (x << r) | (x >> (32 - r));
}
static FORCE_INLINE std::uint32_t Getblock(const std::uint32_t *p,
const int i) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
return p[i];
#else
std::uint32_t temp_number = p[i];
uint8_t (&number)[4] = *reinterpret_cast<uint8_t(*)[4]>(&temp_number);
std::swap(number[0], number[3]);
std::swap(number[1], number[2]);
return temp_number;
#endif
}
static FORCE_INLINE uint32_t fmix32(uint32_t h) {
h ^= h >> 16;
h *= 0x85ebca6b;
h ^= h >> 13;
h *= 0xc2b2ae35;
h ^= h >> 16;
return h;
}
public:
size_type
operator()(const string_view &str) const {
const size_t len = str.length();
const uint8_t *data = reinterpret_cast<const uint8_t *>(str.data());
const int nblocks = static_cast<int>(len / 4);
uint32_t h1 = 0; // seed
const uint32_t c1 = 0xcc9e2d51U;
const uint32_t c2 = 0x1b873593U;
const uint32_t *blocks = reinterpret_cast<const uint32_t *>(data +
nblocks * 4);
for (auto i = -nblocks; i; ++i) {
uint32_t k1 = Getblock(blocks, i);
k1 *= c1;
k1 = Rotl32(k1, 15);
k1 *= c2;
h1 ^= k1;
h1 = Rotl32(h1, 13);
h1 = h1 * 5 + 0xe6546b64;
}
const uint8_t *tail = data + nblocks * 4;
uint32_t k1 = 0;
switch (len & 3) {
case 3:
k1 ^= tail[2] << 16;
case 2:
k1 ^= tail[1] << 8;
case 1:
k1 ^= tail[0];
k1 *= c1;
k1 = Rotl32(k1, 15);
k1 *= c2;
h1 ^= k1;
default:
break;
}
h1 ^= len;
h1 = fmix32(h1);
return hash<int>()(h1);
}
#undef FORCE_INLINE
};
}
#endif
This diff is collapsed.
......@@ -21,3 +21,4 @@ pistache_test(net_test)
pistache_test(listener_test)
pistache_test(payload_test)
pistache_test(rest_server_test)
pistache_test(string_view_test)
\ No newline at end of file
......@@ -10,119 +10,135 @@
#include <pistache/router.h>
using namespace Pistache;
using namespace Pistache::Rest;
bool match(const Rest::Route& route, const std::string& req) {
return std::get<0>(route.match(req));
bool match(const SegmentTreeNode& routes, const std::string& req) {
const auto& s = SegmentTreeNode::sanitizeResource(req);
std::shared_ptr<Route> route;
std::tie(route, std::ignore, std::ignore) = routes.findRoute({s.data(), s.size()});
return route != nullptr;
}
bool matchParams(
const Rest::Route& route, const std::string& req,
std::initializer_list<std::pair<std::string, std::string>> list)
{
bool ok;
std::vector<Rest::TypedParam> params;
std::tie(ok, params, std::ignore) = route.match(req);
const SegmentTreeNode& routes, const std::string& req,
std::initializer_list<std::pair<std::string, std::string>> list) {
if (!ok) return false;
const auto& s = SegmentTreeNode::sanitizeResource(req);
std::shared_ptr<Route> route;
std::vector<TypedParam> params;
std::tie(route, params, std::ignore) = routes.findRoute(s);
if (route == nullptr) return false;
for (const auto& p: list) {
auto it = std::find_if(params.begin(), params.end(), [&](const Rest::TypedParam& param) {
return param.name() == p.first;
auto it = std::find_if(params.begin(), params.end(),
[&](const TypedParam& param) {
return param.name() == p.first;
});
if (it == std::end(params)) {
std::cerr << "Did not find param '" << p.first << "'" << std::endl;
return false;
}
if (it->as<std::string>() != p.second) {
std::cerr << "Param '" << p.first << "' mismatched ("
<< p.second << " != " << it->as<std::string>() << ")" << std::endl;
return false;
}
if (it == std::end(params)) return false;
if (it->as<std::string>() != p.second) return false;
}
return true;
}
bool matchSplat(
const Rest::Route& route, const std::string& req,
std::initializer_list<std::string> list)
{
bool ok;
std::vector<Rest::TypedParam> splats;
std::tie(ok, std::ignore, splats) = route.match(req);
if (!ok) return false;
if (list.size() != splats.size()) {
std::cerr << "Size mismatch (" << list.size() << " != " << splats.size() << ")"
<< std::endl;
return false;
}
const SegmentTreeNode& routes, const std::string& req,
std::initializer_list<std::string> list) {
const auto& s = SegmentTreeNode::sanitizeResource(req);
std::shared_ptr<Route> route;
std::vector<TypedParam> splats;
std::tie(route, std::ignore, splats) = routes.findRoute(s);
if (route == nullptr) return false;
if (list.size() != splats.size()) return false;
size_t i = 0;
for (const auto& s: list) {
for (const auto& s : list) {
auto splat = splats[i].as<std::string>();
if (splat != s) {
std::cerr << "Splat number " << i << " did not match ("
<< splat << " != " << s << ")" << std::endl;
return false;
}
if (splat != s) return false;
++i;
}
return true;
}
Rest::Route
makeRoute(std::string value) {
auto noop = [](const Http::Request&, Http::Response) { return Rest::Route::Result::Ok; };
return Rest::Route(value, Http::Method::Get, noop);
}
TEST(router_test, test_fixed_routes) {
auto r1 = makeRoute("/v1/hello");
ASSERT_TRUE(match(r1, "/v1/hello"));
ASSERT_FALSE(match(r1, "/v2/hello"));
ASSERT_FALSE(match(r1, "/v1/hell0"));
SegmentTreeNode routes;
auto s = SegmentTreeNode::sanitizeResource("/v1/hello");
routes.addRoute(std::string_view {s.data(), s.length()}, nullptr, nullptr);
ASSERT_TRUE(match(routes, "/v1/hello"));
ASSERT_FALSE(match(routes, "/v2/hello"));
ASSERT_FALSE(match(routes, "/v1/hell0"));
auto r2 = makeRoute("/a/b/c");
ASSERT_TRUE(match(r2, "/a/b/c"));
s = SegmentTreeNode::sanitizeResource("/a/b/c");
routes.addRoute(std::string_view {s.data(), s.length()}, nullptr, nullptr);
ASSERT_TRUE(match(routes, "/a/b/c"));
}
TEST(router_test, test_parameters) {
auto r1 = makeRoute("/v1/hello/:name");
ASSERT_TRUE(matchParams(r1, "/v1/hello/joe", {
{ ":name", "joe" }
}));
auto r2 = makeRoute("/greetings/:from/:to");
ASSERT_TRUE(matchParams(r2, "/greetings/foo/bar", {
{ ":from", "foo" },
{ ":to" , "bar" }
}));
SegmentTreeNode routes;
const auto& s = SegmentTreeNode::sanitizeResource("/v1/hello/:name/");
routes.addRoute(std::string_view {s.data(), s.length()}, nullptr, nullptr);
ASSERT_TRUE(matchParams(routes, "/v1/hello/joe", {
{ ":name", "joe" }
}));
const auto& p = SegmentTreeNode::sanitizeResource("/greetings/:from/:to");
routes.addRoute(std::string_view {p.data(), p.length()}, nullptr, nullptr);
ASSERT_TRUE(matchParams(routes, "/greetings/foo/bar", {
{ ":from", "foo" },
{ ":to" , "bar" }
}));
}
TEST(router_test, test_optional) {
auto r1 = makeRoute("/get/:key?");
ASSERT_TRUE(match(r1, "/get"));
ASSERT_TRUE(match(r1, "/get/"));
ASSERT_TRUE(matchParams(r1, "/get/foo", {
{ ":key", "foo" }
}));
ASSERT_TRUE(matchParams(r1, "/get/foo/", {
{ ":key", "foo" }
}));
ASSERT_FALSE(match(r1, "/get/foo/bar"));
SegmentTreeNode routes;
auto s = SegmentTreeNode::sanitizeResource("/get/:key?/bar");
routes.addRoute(std::string_view {s.data(), s.length()}, nullptr, nullptr);
ASSERT_FALSE(matchParams(routes, "/get/bar", {
{ ":key", "whatever" }
}));
ASSERT_TRUE(matchParams(routes, "/get/foo/bar", {
{ ":key", "foo" }
}));
}
TEST(router_test, test_splat) {
auto r1 = makeRoute("/say/*/to/*");
ASSERT_TRUE(match(r1, "/say/hello/to/user"));
ASSERT_FALSE(match(r1, "/say/hello/to"));
ASSERT_FALSE(match(r1, "/say/hello/to/user/please"));
SegmentTreeNode routes;
auto s = SegmentTreeNode::sanitizeResource("/say/*/to/*");
routes.addRoute(std::string_view {s.data(), s.length()}, nullptr, nullptr);
ASSERT_TRUE(match(routes, "/say/hello/to/user"));
ASSERT_FALSE(match(routes, "/say/hello/to"));
ASSERT_FALSE(match(routes, "/say/hello/to/user/please"));
ASSERT_TRUE(matchSplat(routes, "/say/hello/to/user", { "hello", "user" }));
ASSERT_TRUE(matchSplat(routes, "/say/hello/to/user/", { "hello", "user" }));
}
TEST(router_test, test_sanitize) {
SegmentTreeNode routes;
auto s = SegmentTreeNode::sanitizeResource("//v1//hello/");
routes.addRoute(std::string_view {s.data(), s.length()}, nullptr, nullptr);
ASSERT_TRUE(match(routes, "/v1/hello////"));
}
TEST(router_test, test_mixed) {
SegmentTreeNode routes;
auto s = SegmentTreeNode::sanitizeResource("/hello");
auto p = SegmentTreeNode::sanitizeResource("/*");
routes.addRoute(std::string_view {s.data(), s.length()}, nullptr, nullptr);
routes.addRoute(std::string_view {p.data(), p.length()}, nullptr, nullptr);
ASSERT_TRUE(match(routes, "/hello"));
ASSERT_TRUE(match(routes, "/hi"));
ASSERT_TRUE(matchSplat(r1, "/say/hello/to/user", { "hello", "user" }));
ASSERT_TRUE(matchSplat(r1, "/say/hello/to/user/", { "hello", "user" }));
ASSERT_FALSE(matchSplat(routes, "/hello", { "hello" }));
ASSERT_TRUE(matchSplat(routes, "/hi", { "hi" }));
}
#include "gtest/gtest.h"
#include <pistache/string_view.h>
TEST(string_view_test, substr_test) {
std::string_view orig ("test");
std::string_view targ ("est");
std::string_view sub = orig.substr(1);
ASSERT_EQ(sub, targ);
sub = orig.substr(1, 10);
ASSERT_EQ(sub, targ);
ASSERT_THROW(orig.substr(6), std::out_of_range);
}
TEST(string_view_test, find_test) {
std::string_view orig ("test");
std::string_view find ("est");
ASSERT_EQ(orig.find(find), 1);
ASSERT_EQ(orig.find(find, 1), 1);
ASSERT_EQ(orig.find(find, 2), std::size_t(-1));
ASSERT_EQ(orig.find('e'), 1);
ASSERT_EQ(orig.find('e', 1), 1);
ASSERT_EQ(orig.find('e', 2), std::size_t(-1));
ASSERT_EQ(orig.find('1'), std::size_t(-1));
ASSERT_EQ(orig.find("est"), 1);
ASSERT_EQ(orig.find("est", 1), 1);
ASSERT_EQ(orig.find("est", 1, 2), 1);
ASSERT_EQ(orig.find("set"), std::size_t(-1));
ASSERT_EQ(orig.find("est", 2), std::size_t(-1));
ASSERT_EQ(orig.find("est", 2, 2), std::size_t(-1));
}
TEST(string_view_test, rfind_test) {
std::string_view orig ("test");
std::string_view find ("est");
ASSERT_EQ(orig.rfind(find), 1);
ASSERT_EQ(orig.rfind(find, 1), 1);
ASSERT_EQ(orig.rfind('e'), 1);
ASSERT_EQ(orig.rfind('e', 1), 1);
ASSERT_EQ(orig.rfind('q'), std::size_t(-1));
ASSERT_EQ(orig.rfind("est"), 1);
ASSERT_EQ(orig.rfind("est", 1), 1);
ASSERT_EQ(orig.rfind("est", 1, 2), 1);
ASSERT_EQ(orig.rfind("set"), std::size_t(-1));
}
TEST(string_view_test, emptiness) {
std::string_view e1;
std::string_view e2 ("");
std::string_view e3 ("test", 0);
std::string_view ne ("test");
ASSERT_TRUE(e1.empty());
ASSERT_TRUE(e2.empty());
ASSERT_TRUE(e3.empty());
ASSERT_FALSE(ne.empty());
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment