Commit 997c9cf5 authored by Ian Roddis's avatar Ian Roddis

Rebasing on master

parents e898c622 99cac24c
cmake_minimum_required (VERSION 3.0.2) cmake_minimum_required (VERSION 3.0.2)
project (pistache) project (pistache)
include(CheckCXXCompilerFlag) include(CheckCXXCompilerFlag)
option(PISTACHE_BUILD_TESTS "build tests alongside the project" OFF) option(PISTACHE_BUILD_TESTS "build tests alongside the project" OFF)
...@@ -13,19 +12,9 @@ if (NOT PISTACHE_MAX_PAYLOAD) ...@@ -13,19 +12,9 @@ if (NOT PISTACHE_MAX_PAYLOAD)
endif() endif()
add_definitions( -DMAX_PAYLOAD=${PISTACHE_MAX_PAYLOAD} ) add_definitions( -DMAX_PAYLOAD=${PISTACHE_MAX_PAYLOAD} )
CHECK_CXX_COMPILER_FLAG("-std=c++17" COMPILER_SUPPORTS_CXX17)
CHECK_CXX_COMPILER_FLAG("-std=c++14" COMPILER_SUPPORTS_CXX14)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
if(COMPILER_SUPPORTS_CXX11)
# 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(COMPILER_SUPPORTS_CXX17 AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
elseif(COMPILER_SUPPORTS_CXX14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
elseif(COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(COMPILER_SUPPORTS_CXX0X) elseif(COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
...@@ -99,3 +88,4 @@ endif() ...@@ -99,3 +88,4 @@ endif()
DESTINATION DESTINATION
lib/pkgconfig/ lib/pkgconfig/
) )
...@@ -121,6 +121,16 @@ class MyHandler : public Http::Handler { ...@@ -121,6 +121,16 @@ class MyHandler : public Http::Handler {
response.send(Http::Code::Method_Not_Allowed); response.send(Http::Code::Method_Not_Allowed);
} }
} }
else if (req.resource() == "/stream_binary") {
auto stream = response.stream(Http::Code::Ok);
char binary_data[] = "some \0\r\n data\n";
size_t chunk_size = 14;
for (size_t i = 0; i < 10; ++i) {
stream.write(binary_data, chunk_size);
stream.flush();
}
stream.ends();
}
else if (req.resource() == "/exception") { else if (req.resource() == "/exception") {
throw std::runtime_error("Exception thrown in the handler"); throw std::runtime_error("Exception thrown in the handler");
} }
......
...@@ -101,7 +101,7 @@ namespace Uri { ...@@ -101,7 +101,7 @@ namespace Uri {
bool has(const std::string& name) const; bool has(const std::string& name) const;
// Return empty string or "?key1=value1&key2=value2" if query exist // Return empty string or "?key1=value1&key2=value2" if query exist
std::string as_str() const; std::string as_str() const;
void clear() { void clear() {
params.clear(); params.clear();
} }
...@@ -317,6 +317,14 @@ public: ...@@ -317,6 +317,14 @@ public:
friend friend
ResponseStream& operator<<(ResponseStream& stream, const T& val); ResponseStream& operator<<(ResponseStream& stream, const T& val);
std::streamsize write(const char * data, std::streamsize sz) {
std::ostream os(&buf_);
os << std::hex << sz << crlf;
os.write(data, sz);
os << crlf;
return sz;
}
const Header::Collection& headers() const { const Header::Collection& headers() const {
return headers_; return headers_;
} }
......
...@@ -8,15 +8,11 @@ ...@@ -8,15 +8,11 @@
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <unordered_map>
#include <memory>
#include <pistache/http.h> #include <pistache/http.h>
#include <pistache/http_defs.h> #include <pistache/http_defs.h>
#include <pistache/flags.h> #include <pistache/flags.h>
#include "pistache/string_view.h"
namespace Pistache { namespace Pistache {
namespace Rest { namespace Rest {
...@@ -24,8 +20,8 @@ class Description; ...@@ -24,8 +20,8 @@ class Description;
namespace details { namespace details {
template<typename T> struct LexicalCast { template<typename T> struct LexicalCast {
static T cast(const std::string_view& value) { static T cast(const std::string& value) {
std::istringstream iss(std::string(value.data(), value.length())); std::istringstream iss(value);
T out; T out;
if (!(iss >> out)) if (!(iss >> out))
throw std::runtime_error("Bad lexical cast"); throw std::runtime_error("Bad lexical cast");
...@@ -34,8 +30,8 @@ namespace details { ...@@ -34,8 +30,8 @@ namespace details {
}; };
template<> template<>
struct LexicalCast<std::string_view> { struct LexicalCast<std::string> {
static std::string_view cast(const std::string_view& value) { static std::string cast(const std::string& value) {
return value; return value;
} }
}; };
...@@ -43,7 +39,7 @@ namespace details { ...@@ -43,7 +39,7 @@ namespace details {
class TypedParam { class TypedParam {
public: public:
TypedParam(const std::string_view& name, const std::string_view& value) TypedParam(const std::string& name, const std::string& value)
: name_(name) : name_(name)
, value_(value) , value_(value)
{ } { }
...@@ -53,88 +49,97 @@ public: ...@@ -53,88 +49,97 @@ public:
return details::LexicalCast<T>::cast(value_); return details::LexicalCast<T>::cast(value_);
} }
std::string_view name() const { std::string name() const {
return name_; return name_;
} }
private: private:
std::string_view name_; std::string name_;
std::string_view value_; std::string value_;
}; };
class Request; class Request;
struct Route { struct Route {
enum class Result { Ok, Failure };
enum class Status { Match, NotFound }; typedef std::function<Result (const Request&, Http::ResponseWriter)> Handler;
enum class Result { Ok, Failure }; Route(std::string resource, Http::Method method, Handler handler)
: resource_(std::move(resource))
, method_(method)
, handler_(std::move(handler))
, fragments_(Fragment::fromUrl(resource_))
{
}
typedef std::function<Result(const Request, Http::ResponseWriter)> Handler; std::tuple<bool, std::vector<TypedParam>, std::vector<TypedParam>>
match(const Http::Request& req) const;
explicit Route(Route::Handler handler) : handler_(std::move(handler)) { } std::tuple<bool, std::vector<TypedParam>, std::vector<TypedParam>>
match(const std::string& req) const;
template<typename... Args> template<typename... Args>
void invokeHandler(Args &&...args) const { void invokeHandler(Args&& ...args) const {
handler_(std::forward<Args>(args)...); handler_(std::forward<Args>(args)...);
} }
Handler handler_;
};
namespace Private {
class RouterHandler;
}
class FragmentTreeNode {
private: private:
enum class FragmentType { struct Fragment {
Fixed, Param, Optional, Splat explicit Fragment(std::string value);
};
/** bool match(const std::string& raw) const;
* Resource path are allocated on stack. To make them survive it is required to allocate them on heap. bool match(const Fragment& other) const;
* Since the main idea between string_view is to have constant substring and thus a reduced number of
* char* allocated on heap, this shared_ptr is used to know if a string_view can be destroyed (after a
* route removal).
*/
std::shared_ptr<char> resourceRef_;
std::unordered_map<std::string_view, std::shared_ptr<FragmentTreeNode>> fixed_; bool isParameter() const;
std::unordered_map<std::string_view, std::shared_ptr<FragmentTreeNode>> param_; bool isSplat() const;
std::unordered_map<std::string_view, std::shared_ptr<FragmentTreeNode>> optional_; bool isOptional() const;
std::shared_ptr<FragmentTreeNode> splat_;
std::shared_ptr<Route> route_;
static FragmentType getFragmentType(const std::string_view &fragment); std::string value() const {
return value_;
}
std::tuple<std::shared_ptr<Route>, std::vector<TypedParam>, std::vector<TypedParam>> static std::vector<Fragment> fromUrl(const std::string& url);
findRoute(const std::string_view &path,
std::vector<TypedParam> &params,
std::vector<TypedParam> &splats) const;
public: private:
FragmentTreeNode(); enum class Flag {
explicit FragmentTreeNode(const std::shared_ptr<char> &resourceReference); None = 0x0,
Fixed = 0x1,
Parameter = Fixed << 1,
Optional = Parameter << 1,
Splat = Optional << 1
};
void addRoute(const std::string_view &path, const Route::Handler &handler, void init(std::string value);
const std::shared_ptr<char> &resourceReference);
bool removeRoute(const std::string_view &path); void checkInvariant() const;
/** Flags<Flag> flags;
* Finds the correct route for the given path. std::string value_;
* \param[in] path Requested resource path (usually derived from request) };
* \throws std::runtime_error An empty path was given
* \return Found route with its resolved parameters and splats (if no route is found, first element of the tuple std::string resource_;
* is a null pointer). Http::Method method_;
Handler handler_;
/* @Performance: since we know that resource_ will live as long as the vector underneath,
* we would benefit from std::experimental::string_view to store fragments.
*
* We could use string_view instead of allocating strings everytime. However, string_view is
* only available in c++17, so I might have to come with my own lightweight implementation of
* it
*/ */
std::tuple<std::shared_ptr<Route>, std::vector<TypedParam>, std::vector<TypedParam>> std::vector<Fragment> fragments_;
findRoute(const std::string_view &path) const;
}; };
namespace Private {
class RouterHandler;
}
class Router { class Router {
public: public:
enum class Status { Match, NotFound };
static Router fromDescription(const Rest::Description& desc); static Router fromDescription(const Rest::Description& desc);
std::shared_ptr<Private::RouterHandler> std::shared_ptr<Private::RouterHandler>
...@@ -148,18 +153,18 @@ public: ...@@ -148,18 +153,18 @@ public:
void patch(std::string resource, Route::Handler handler); void patch(std::string resource, Route::Handler handler);
void del(std::string resource, Route::Handler handler); void del(std::string resource, Route::Handler handler);
void options(std::string resource, Route::Handler handler); void options(std::string resource, Route::Handler handler);
void removeRoute(Http::Method method, std::string resource);
void addCustomHandler(Route::Handler handler); void addCustomHandler(Route::Handler handler);
void addNotFoundHandler(Route::Handler handler); void addNotFoundHandler(Route::Handler handler);
inline bool hasNotFoundHandler() { return notFoundHandler != nullptr; } inline bool hasNotFoundHandler() { return notFoundHandler != nullptr; }
void invokeNotFoundHandler(const Http::Request &req, Http::ResponseWriter resp) const; void invokeNotFoundHandler(const Http::Request &req, Http::ResponseWriter resp) const;
Route::Status route(const Http::Request& request, Http::ResponseWriter response); Status route(const Http::Request& request, Http::ResponseWriter response);
private: private:
void addRoute(Http::Method method, std::string resource, Route::Handler handler); void addRoute(Http::Method method, std::string resource, Route::Handler handler);
std::unordered_map<Http::Method, FragmentTreeNode> routes; std::unordered_map<Http::Method, std::vector<Route>> routes;
std::vector<Route::Handler> customHandlers; std::vector<Route::Handler> customHandlers;
...@@ -187,7 +192,6 @@ namespace Private { ...@@ -187,7 +192,6 @@ namespace Private {
class Request : public Http::Request { class Request : public Http::Request {
public: public:
friend class FragmentTreeNode;
friend class Router; friend class Router;
bool hasParam(const std::string& name) const; bool hasParam(const std::string& name) const;
...@@ -198,7 +202,7 @@ public: ...@@ -198,7 +202,7 @@ public:
private: private:
explicit Request( explicit Request(
const Http::Request& request, const Http::Request& request,
std::vector<TypedParam>&& params, std::vector<TypedParam>&& params,
std::vector<TypedParam>&& splats); std::vector<TypedParam>&& splats);
...@@ -215,7 +219,6 @@ namespace Routes { ...@@ -215,7 +219,6 @@ namespace Routes {
void Patch(Router& router, std::string resource, Route::Handler handler); void Patch(Router& router, std::string resource, Route::Handler handler);
void Delete(Router& router, std::string resource, Route::Handler handler); void Delete(Router& router, std::string resource, Route::Handler handler);
void Options(Router& router, std::string resource, Route::Handler handler); void Options(Router& router, std::string resource, Route::Handler handler);
void Remove(Router& router, Http::Method method, std::string resource);
void NotFound(Router& router, Route::Handler handler); void NotFound(Router& router, Route::Handler handler);
......
#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
...@@ -9,7 +9,7 @@ set(SOURCE_FILES ...@@ -9,7 +9,7 @@ set(SOURCE_FILES
${SERVER_SOURCE_FILES} ${SERVER_SOURCE_FILES}
${CLIENT_SOURCE_FILES} ${CLIENT_SOURCE_FILES}
${INCLUDE_FILES} ${INCLUDE_FILES}
../include/pistache/string_view.h) )
add_library(pistache ${SOURCE_FILES}) add_library(pistache ${SOURCE_FILES})
set_target_properties(pistache PROPERTIES set_target_properties(pistache PROPERTIES
......
...@@ -745,9 +745,15 @@ RequestBuilder::body(std::string&& val) { ...@@ -745,9 +745,15 @@ RequestBuilder::body(std::string&& val) {
return *this; return *this;
} }
RequestBuilder&
RequestBuilder::timeout(std::chrono::milliseconds timeout) {
timeout_ = timeout;
return *this;
}
Async::Promise<Response> Async::Promise<Response>
RequestBuilder::send() { RequestBuilder::send() {
return client_->doRequest(std::move(request_), timeout_); return client_->doRequest(request_, timeout_);
} }
Client::Options& Client::Options&
......
...@@ -25,7 +25,7 @@ Request::Request( ...@@ -25,7 +25,7 @@ Request::Request(
bool bool
Request::hasParam(const std::string& name) const { Request::hasParam(const std::string& name) const {
auto it = std::find_if(params_.begin(), params_.end(), [&](const TypedParam& param) { auto it = std::find_if(params_.begin(), params_.end(), [&](const TypedParam& param) {
return param.name() == std::string_view(name.data(), name.length()); return param.name() == name;
}); });
return it != std::end(params_); return it != std::end(params_);
...@@ -34,7 +34,7 @@ Request::hasParam(const std::string& name) const { ...@@ -34,7 +34,7 @@ Request::hasParam(const std::string& name) const {
TypedParam TypedParam
Request::param(const std::string& name) const { Request::param(const std::string& name) const {
auto it = std::find_if(params_.begin(), params_.end(), [&](const TypedParam& param) { auto it = std::find_if(params_.begin(), params_.end(), [&](const TypedParam& param) {
return param.name() == std::string_view(name.data(), name.length()); return param.name() == name;
}); });
if (it == std::end(params_)) { if (it == std::end(params_)) {
...@@ -58,220 +58,151 @@ Request::splat() const { ...@@ -58,220 +58,151 @@ Request::splat() const {
return splats_; return splats_;
} }
FragmentTreeNode::FragmentTreeNode(): resourceRef_(), fixed_(), param_(), optional_(), splat_(), route_() { Route::Fragment::Fragment(std::string value)
std::shared_ptr<char> ptr(new char[1], std::default_delete<char[]>()); {
ptr.get()[0] = '/'; if (value.empty())
resourceRef_.swap(ptr); throw std::runtime_error("Invalid empty fragment");
}
FragmentTreeNode::FragmentTreeNode(const std::shared_ptr<char> &resourceReference) :
resourceRef_(resourceReference), fixed_(), param_(), optional_(), splat_(), route_() { }
FragmentTreeNode::FragmentType init(std::move(value));
FragmentTreeNode::getFragmentType(const std::string_view &fragment) { }
auto optpos = fragment.find('?'); bool
if (fragment[0] == ':') { Route::Fragment::match(const std::string& raw) const {
if (optpos != std::string_view::npos) { if (flags.hasFlag(Flag::Fixed)) {
if (optpos != fragment.length() - 1) { return raw == value_;
throw std::runtime_error("? should be at the end of the string");
}
return FragmentType::Optional;
}
return FragmentType::Param;
} else if (fragment[0] == '*') {
if (fragment.length() > 1) {
throw std::runtime_error("Invalid splat parameter");
}
return FragmentType::Splat;
} }
else if (flags.hasFlag(Flag::Parameter) || flags.hasFlag(Flag::Splat)) {
if (optpos != std::string_view::npos) { return true;
throw std::runtime_error("Only optional parameters are currently supported");
} }
return FragmentType::Fixed; return false;
}
bool
Route::Fragment::match(const Fragment& other) const {
return match(other.value());
} }
void void
FragmentTreeNode::addRoute(const std::string_view &path, Route::Fragment::init(std::string value) {
const Route::Handler &handler, if (value[0] == ':')
const std::shared_ptr<char> &resourceReference) { flags.setFlag(Flag::Parameter);
if (path.length() == 0) { else if (value[0] == '*') {
throw std::runtime_error("Invalid zero-length URL."); if (value.size() > 1)
throw std::runtime_error("Invalid splat parameter");
flags.setFlag(Flag::Splat);
} }
std::string_view currPath; else {
if (path.rfind('/') == path.length() - 1) { flags.setFlag(Flag::Fixed);
currPath = path.substr(0, path.length() - 1);
} else {
currPath = path;
} }
if (currPath.find('/') == std::string_view::npos) { // current leaf requested
if (route_ != nullptr) {
throw std::runtime_error("Requested route already exist.");
} else {
route_ = std::make_shared<Route>(handler);
}
} else { // recursion to correct descendant
const auto pos = currPath.find('/', 1);
const auto next = (pos == std::string_view::npos) ? currPath.substr(1) : currPath.substr(pos); // complete lower path
auto mid = (pos == std::string_view::npos) ? currPath.substr(1) : currPath.substr(1, currPath.find('/', pos) - 1); // middle resource name
std::unordered_map<std::string_view, std::shared_ptr<FragmentTreeNode>> *collection;
const auto fragmentType = getFragmentType(mid);
switch (fragmentType) {
case FragmentType::Fixed:
collection = &fixed_;
break;
case FragmentType::Param:
collection = &param_;
break;
case FragmentType::Optional:
mid = mid.substr(0, mid.length() - 1);
collection = &optional_;
break;
case FragmentType::Splat:
if (splat_ == nullptr) {
splat_ = std::make_shared<FragmentTreeNode>(resourceReference);
}
splat_->addRoute(next, handler, resourceReference);
return;
}
// Let's search for any '?'
auto pos = value.find('?');
if (pos != std::string::npos) {
if (value[0] != ':')
throw std::runtime_error("Only optional parameters are currently supported");
if (collection->count(mid) == 0) { // direct subpath does not exist if (pos != value.size() - 1)
collection->insert(std::make_pair(mid, std::make_shared<FragmentTreeNode>(resourceReference))); throw std::runtime_error("? should be at the end of the string");
}
collection->at(mid)->addRoute(next, handler, resourceReference);
}
}
bool Pistache::Rest::FragmentTreeNode::removeRoute(const std::string_view &path) { value_ = value.substr(0, pos);
if (path.length() == 0) { flags.setFlag(Flag::Optional);
throw std::runtime_error("Invalid zero-length URL.");
}
std::string_view currPath;
if (path.rfind('/') == path.length() - 1) {
currPath = path.substr(0, path.length());
} else { } else {
currPath = path; value_ = std::move(value);
} }
if (currPath.find('/') == std::string_view::npos) { // current leaf requested
route_.reset();
} else { // recursion to correct descendant
const auto pos = currPath.find('/', 1);
const auto next = (pos == std::string_view::npos) ? currPath.substr(1) : currPath.substr(pos); // complete lower path
auto mid = (pos == std::string_view::npos) ? currPath.substr(1) : currPath.substr(1, currPath.find('/', pos) - 1); // middle resource name
std::unordered_map<std::string_view, std::shared_ptr<FragmentTreeNode>> *collection;
auto fragmentType = getFragmentType(mid);
switch (fragmentType) {
case FragmentType::Fixed:
collection = &fixed_;
break;
case FragmentType::Param:
collection = &param_;
break;
case FragmentType::Optional:
mid = mid.substr(0, mid.length() - 1);
collection = &optional_;
break;
case FragmentType::Splat:
return splat_->removeRoute(next);
}
try { checkInvariant();
const bool removable = collection->at(mid)->removeRoute(next); }
if (removable) {
collection->erase(mid); void
} Route::Fragment::checkInvariant() const {
} catch (const std::out_of_range &) { auto check = [this](std::initializer_list<Flag> exclusiveFlags) {
throw std::runtime_error("Requested does not exist."); for (auto flag: exclusiveFlags) {
if (!flags.hasFlag(flag)) return;
} }
}
return fixed_.empty() && param_.empty() && throw std::logic_error(
optional_.empty() && splat_ == nullptr && route_ == nullptr; std::string("Invariant violated: invalid combination of flags for fragment ") + value_);
};
check({ Flag::Fixed, Flag::Optional });
check({ Flag::Fixed, Flag::Parameter });
check({ Flag::Splat, Flag::Fixed });
check({ Flag::Splat, Flag::Optional });
check({ Flag::Splat, Flag::Parameter });
} }
std::tuple<std::shared_ptr<Route>, std::vector<TypedParam>, std::vector<TypedParam>> std::vector<Route::Fragment>
Pistache::Rest::FragmentTreeNode::findRoute( Route::Fragment::fromUrl(const std::string& url) {
const std::string_view &path, std::vector<Route::Fragment> fragments;
std::vector<TypedParam> &params,
std::vector<TypedParam> &splats) const { std::istringstream iss(url);
if (path.length() == 0) { std::string p;
throw std::runtime_error("Invalid zero-length URL.");
} while (std::getline(iss, p, '/')) {
std::string_view currPath; if (p.empty()) continue;
if (path.rfind('/') == path.length() - 1) {
currPath = path.substr(0, path.length() - 1); fragments.push_back(Fragment(std::move(p)));
} else {
currPath = path;
} }
if (currPath.find('/') == std::string_view::npos) { // current leaf requested, or empty final optional
// in case of more than one optional at this point, as it is an ambuiguity,
// it is resolved by using the first optional
if (!optional_.empty()) {
auto optional = optional_.begin();
params.emplace_back(optional->first, std::string_view());
return optional->second->findRoute(currPath, params, splats);
} else return std::make_tuple(route_, std::move(params), std::move(splats));
} else { // recursion to correct descendant
const auto pos = currPath.find('/', 1);
const auto next = (pos == std::string_view::npos) ? currPath.substr(1) : currPath.substr(pos); // complete lower path
auto mid = (pos == std::string_view::npos) ? currPath.substr(1) : currPath.substr(1, currPath.find('/', pos) - 1); // middle resource name
if (fixed_.count(mid) != 0) {
auto result = fixed_.at(mid)->findRoute(next, params, splats);
auto route = std::get<0>(result);
if (route != nullptr) return result;
}
for (const auto &param: param_) { return fragments;
params.emplace_back(param.first, mid); }
auto result = param.second->findRoute(next, params, splats);
auto route = std::get<0>(result); bool
if (route != nullptr) return result; Route::Fragment::isParameter() const {
else params.pop_back(); return flags.hasFlag(Flag::Parameter);
} }
for (const auto &optional: optional_) { bool
params.emplace_back(optional.first, mid); Route::Fragment::isOptional() const {
auto result = optional.second->findRoute(next, params, splats); return isParameter() && flags.hasFlag(Flag::Optional);
}
auto route = std::get<0>(result); bool
if (route != nullptr) return result; Route::Fragment::isSplat() const {
else { return flags.hasFlag(Flag::Splat);
params.pop_back(); }
// try empty optional std::tuple<bool, std::vector<TypedParam>, std::vector<TypedParam>>
params.emplace_back(optional.first, std::string_view()); Route::match(const Http::Request& req) const
result = optional.second->findRoute(currPath, params, splats); {
return match(req.resource());
}
route = std::get<0>(result); std::tuple<bool, std::vector<TypedParam>, std::vector<TypedParam>>
if (route != nullptr) return result; Route::match(const std::string& req) const
else params.pop_back(); {
} static const auto NoMatch = std::make_tuple(false, std::vector<TypedParam>(), std::vector<TypedParam>());
auto reqFragments = Fragment::fromUrl(req);
if (reqFragments.size() > fragments_.size())
return NoMatch;
std::vector<TypedParam> params;
std::vector<TypedParam> splats;
for (std::vector<Fragment>::size_type i = 0; i < fragments_.size(); ++i) {
const auto& fragment = fragments_[i];
if (i >= reqFragments.size()) {
if (fragment.isOptional())
continue;
return NoMatch;
} }
if (splat_ != nullptr) { const auto& reqFragment = reqFragments[i];
splats.emplace_back(mid, mid); if (!fragment.match(reqFragment))
auto result = splat_->findRoute(next, params, splats); return NoMatch;
auto route = std::get<0>(result); if (fragment.isParameter()) {
if (route != nullptr) return result; params.push_back(TypedParam(fragment.value(), reqFragment.value()));
else splats.pop_back(); } else if (fragment.isSplat()) {
splats.push_back(TypedParam(reqFragment.value(), reqFragment.value()));
} }
return std::make_tuple(nullptr, std::vector<TypedParam>(), std::vector<TypedParam>());
} }
}
std::tuple<std::shared_ptr<Route>, std::vector<TypedParam>, std::vector<TypedParam>> return make_tuple(true, std::move(params), std::move(splats));
Pistache::Rest::FragmentTreeNode::findRoute(const std::string_view &path) const {
std::vector<TypedParam> params;
std::vector<TypedParam> splats;
return findRoute(path, params, splats);
} }
namespace Private { namespace Private {
...@@ -290,7 +221,7 @@ RouterHandler::onRequest( ...@@ -290,7 +221,7 @@ RouterHandler::onRequest(
auto result = router.route(req, std::move(resp)); auto result = router.route(req, std::move(resp));
/* @Feature: add support for a custom NotFound handler */ /* @Feature: add support for a custom NotFound handler */
if (result == Route::Status::NotFound) if (result == Router::Status::NotFound)
{ {
if (router.hasNotFoundHandler()) if (router.hasNotFoundHandler())
{ {
...@@ -301,6 +232,7 @@ RouterHandler::onRequest( ...@@ -301,6 +232,7 @@ RouterHandler::onRequest(
response.send(Http::Code::Not_Found, "Could not find a matching route"); response.send(Http::Code::Not_Found, "Could not find a matching route");
} }
} }
} // namespace Private } // namespace Private
Router Router
...@@ -362,12 +294,6 @@ Router::options(std::string resource, Route::Handler handler) { ...@@ -362,12 +294,6 @@ Router::options(std::string resource, Route::Handler handler) {
addRoute(Http::Method::Options, std::move(resource), std::move(handler)); addRoute(Http::Method::Options, std::move(resource), std::move(handler));
} }
void
Router::removeRoute(Http::Method method, std::string resource) {
auto& r = routes[method];
r.removeRoute(std::string_view(resource.data(), resource.length()));
}
void void
Router::addCustomHandler(Route::Handler handler) { Router::addCustomHandler(Route::Handler handler) {
customHandlers.push_back(std::move(handler)); customHandlers.push_back(std::move(handler));
...@@ -384,39 +310,37 @@ Router::invokeNotFoundHandler(const Http::Request &req, Http::ResponseWriter res ...@@ -384,39 +310,37 @@ Router::invokeNotFoundHandler(const Http::Request &req, Http::ResponseWriter res
notFoundHandler(Rest::Request(std::move(req), std::vector<TypedParam>(), std::vector<TypedParam>()), std::move(resp)); notFoundHandler(Rest::Request(std::move(req), std::vector<TypedParam>(), std::vector<TypedParam>()), std::move(resp));
} }
Route::Status Router::Status
Router::route(const Http::Request& req, Http::ResponseWriter response) { Router::route(const Http::Request& req, Http::ResponseWriter response) {
auto& r = routes[req.method()]; auto& r = routes[req.method()];
for (const auto& route: r) {
std::string_view path {req.resource().data(), req.resource().size()}; bool match;
auto result = r.findRoute(path); std::vector<TypedParam> params;
std::vector<TypedParam> splats;
auto route = std::get<0>(result); std::tie(match, params, splats) = route.match(req);
if (route != nullptr) { if (match) {
auto params = std::get<1>(result); route.invokeHandler(Request(req, std::move(params), std::move(splats)), std::move(response));
auto splats = std::get<2>(result); return Router::Status::Match;
route->invokeHandler(Request(req, std::move(params), std::move(splats)), std::move(response)); }
return Route::Status::Match;
} }
for (const auto& handler: customHandlers) { for (const auto& handler: customHandlers) {
auto resp = response.clone(); auto resp = response.clone();
auto handler1 = handler(Request(req, std::vector<TypedParam>(), std::vector<TypedParam>()), std::move(resp)); auto result = handler(Request(req, std::vector<TypedParam>(), std::vector<TypedParam>()), std::move(resp));
if (handler1 == Route::Result::Ok) return Route::Status::Match; if (result == Route::Result::Ok) return Router::Status::Match;
} }
if (hasNotFoundHandler()) invokeNotFoundHandler(req, std::move(response)); return Router::Status::NotFound;
return Route::Status::NotFound;
} }
void void
Router::addRoute(Http::Method method, std::string resource, Route::Handler handler) Router::addRoute(Http::Method method, std::string resource, Route::Handler handler)
{ {
auto& r = routes[method]; auto& r = routes[method];
std::shared_ptr<char> ptr(new char[resource.length()], std::default_delete<char[]>()); r.push_back(Route(std::move(resource), method, std::move(handler)));
memcpy(ptr.get(), resource.data(), resource.length());
r.addRoute(std::string_view(ptr.get(), resource.length()), handler, ptr);
} }
namespace Routes { namespace Routes {
void Get(Router& router, std::string resource, Route::Handler handler) { void Get(Router& router, std::string resource, Route::Handler handler) {
...@@ -443,10 +367,6 @@ void Options(Router& router, std::string resource, Route::Handler handler) { ...@@ -443,10 +367,6 @@ void Options(Router& router, std::string resource, Route::Handler handler) {
router.options(std::move(resource), std::move(handler)); router.options(std::move(resource), std::move(handler));
} }
void Remove(Router& router, Http::Method method, std::string resource) {
router.removeRoute(method, std::move(resource));
}
void NotFound(Router& router, Route::Handler handler) { void NotFound(Router& router, Route::Handler handler) {
router.addNotFoundHandler(std::move(handler)); router.addNotFoundHandler(std::move(handler));
} }
......
...@@ -16,4 +16,4 @@ pistache_test(router_test) ...@@ -16,4 +16,4 @@ pistache_test(router_test)
pistache_test(cookie_test) pistache_test(cookie_test)
pistache_test(view_test) pistache_test(view_test)
pistache_test(http_parsing_test) pistache_test(http_parsing_test)
pistache_test(string_view_test) pistache_test(http_client_test)
#include "gtest/gtest.h"
#include <pistache/http.h>
#include <pistache/client.h>
#include <pistache/endpoint.h>
#include <chrono>
using namespace Pistache;
struct HelloHandler : public Http::Handler {
HTTP_PROTOTYPE(HelloHandler)
void onRequest(const Http::Request& request, Http::ResponseWriter writer)
{
writer.send(Http::Code::Ok, "Hello, World!");
}
};
TEST(request_builder, multiple_send_test) {
const std::string address = "localhost:9080";
Http::Endpoint server(address);
auto server_opts = Http::Endpoint::options().threads(1);
server.init(server_opts);
server.setHandler(Http::make_handler<HelloHandler>());
server.serveThreaded();
Http::Client client;
auto client_opts = Http::Client::options().threads(1).maxConnectionsPerHost(1);
client.init(client_opts);
std::vector<Async::Promise<Http::Response>> responses;
const int RESPONSE_SIZE = 3;
int response_counter = 0;
auto rb = client.get(address);
for (int i = 0; i < RESPONSE_SIZE; ++i) {
auto response = rb.send();
response.then([&](Http::Response rsp) {
if (rsp.code() == Http::Code::Ok)
++response_counter;
}, Async::IgnoreException);
responses.push_back(std::move(response));
}
auto sync = Async::whenAll(responses.begin(), responses.end());
Async::Barrier<std::vector<Http::Response>> barrier(sync);
barrier.wait_for(std::chrono::seconds(5));
server.shutdown();
client.shutdown();
ASSERT_TRUE(response_counter == RESPONSE_SIZE);
}
\ No newline at end of file
...@@ -12,25 +12,23 @@ ...@@ -12,25 +12,23 @@
using namespace Pistache; using namespace Pistache;
bool match(const Rest::FragmentTreeNode& routes, const std::string& req) { bool match(const Rest::Route& route, const std::string& req) {
std::shared_ptr<Rest::Route> route; return std::get<0>(route.match(req));
std::tie(route, std::ignore, std::ignore) = routes.findRoute({req.data(), req.size()});
return route != nullptr;
} }
bool matchParams( bool matchParams(
const Rest::FragmentTreeNode& routes, const std::string& req, const Rest::Route& route, const std::string& req,
std::initializer_list<std::pair<std::string, std::string>> list) std::initializer_list<std::pair<std::string, std::string>> list)
{ {
std::shared_ptr<Rest::Route> route; bool ok;
std::vector<Rest::TypedParam> params; std::vector<Rest::TypedParam> params;
std::tie(route, params, std::ignore) = routes.findRoute({req.data(), req.size()}); std::tie(ok, params, std::ignore) = route.match(req);
if (route == nullptr) return false; if (!ok) return false;
for (const auto& p: list) { for (const auto& p: list) {
auto it = std::find_if(params.begin(), params.end(), [&](const Rest::TypedParam& param) { auto it = std::find_if(params.begin(), params.end(), [&](const Rest::TypedParam& param) {
return param.name() == std::string_view(p.first.data(), p.first.size()); return param.name() == p.first;
}); });
if (it == std::end(params)) { if (it == std::end(params)) {
std::cerr << "Did not find param '" << p.first << "'" << std::endl; std::cerr << "Did not find param '" << p.first << "'" << std::endl;
...@@ -47,14 +45,14 @@ bool matchParams( ...@@ -47,14 +45,14 @@ bool matchParams(
} }
bool matchSplat( bool matchSplat(
const Rest::FragmentTreeNode& routes, const std::string& req, const Rest::Route& route, const std::string& req,
std::initializer_list<std::string> list) std::initializer_list<std::string> list)
{ {
std::shared_ptr<Rest::Route> route; bool ok;
std::vector<Rest::TypedParam> splats; std::vector<Rest::TypedParam> splats;
std::tie(route, std::ignore, splats) = routes.findRoute({req.data(), req.size()}); std::tie(ok, std::ignore, splats) = route.match(req);
if (route == nullptr) return false; if (!ok) return false;
if (list.size() != splats.size()) { if (list.size() != splats.size()) {
std::cerr << "Size mismatch (" << list.size() << " != " << splats.size() << ")" std::cerr << "Size mismatch (" << list.size() << " != " << splats.size() << ")"
...@@ -76,57 +74,55 @@ bool matchSplat( ...@@ -76,57 +74,55 @@ bool matchSplat(
return true; return true;
} }
TEST(router_test, test_fixed_routes) { Rest::Route
Rest::FragmentTreeNode routes; makeRoute(std::string value) {
routes.addRoute(std::string_view("/v1/hello"), nullptr, nullptr); auto noop = [](const Http::Request&, Http::Response) { return Rest::Route::Result::Ok; };
return Rest::Route(value, Http::Method::Get, noop);
}
ASSERT_TRUE(match(routes, "/v1/hello")); TEST(router_test, test_fixed_routes) {
ASSERT_FALSE(match(routes, "/v2/hello")); auto r1 = makeRoute("/v1/hello");
ASSERT_FALSE(match(routes, "/v1/hell0")); ASSERT_TRUE(match(r1, "/v1/hello"));
ASSERT_FALSE(match(r1, "/v2/hello"));
ASSERT_FALSE(match(r1, "/v1/hell0"));
routes.addRoute(std::string_view("/a/b/c"), nullptr, nullptr); auto r2 = makeRoute("/a/b/c");
ASSERT_TRUE(match(routes, "/a/b/c")); ASSERT_TRUE(match(r2, "/a/b/c"));
} }
TEST(router_test, test_parameters) { TEST(router_test, test_parameters) {
Rest::FragmentTreeNode routes; auto r1 = makeRoute("/v1/hello/:name");
routes.addRoute(std::string_view("/v1/hello/:name"), nullptr, nullptr); ASSERT_TRUE(matchParams(r1, "/v1/hello/joe", {
ASSERT_TRUE(matchParams(routes, "/v1/hello/joe", {
{ ":name", "joe" } { ":name", "joe" }
})); }));
routes.addRoute(std::string_view("/greetings/:from/:to"), nullptr, nullptr); auto r2 = makeRoute("/greetings/:from/:to");
ASSERT_TRUE(matchParams(routes, "/greetings/foo/bar", { ASSERT_TRUE(matchParams(r2, "/greetings/foo/bar", {
{ ":from", "foo" }, { ":from", "foo" },
{ ":to" , "bar" } { ":to" , "bar" }
})); }));
} }
TEST(router_test, test_optional) { TEST(router_test, test_optional) {
Rest::FragmentTreeNode routes; auto r1 = makeRoute("/get/:key?");
routes.addRoute(std::string_view("/get/:key?"), nullptr, nullptr); ASSERT_TRUE(match(r1, "/get"));
ASSERT_TRUE(match(r1, "/get/"));
ASSERT_TRUE(match(routes, "/get")); ASSERT_TRUE(matchParams(r1, "/get/foo", {
ASSERT_TRUE(match(routes, "/get/"));
ASSERT_TRUE(matchParams(routes, "/get/foo", {
{ ":key", "foo" } { ":key", "foo" }
})); }));
ASSERT_TRUE(matchParams(routes, "/get/foo/", { ASSERT_TRUE(matchParams(r1, "/get/foo/", {
{ ":key", "foo" } { ":key", "foo" }
})); }));
ASSERT_FALSE(match(routes, "/get/foo/bar")); ASSERT_FALSE(match(r1, "/get/foo/bar"));
} }
TEST(router_test, test_splat) { TEST(router_test, test_splat) {
Rest::FragmentTreeNode routes; auto r1 = makeRoute("/say/*/to/*");
routes.addRoute(std::string_view("/say/*/to/*"), nullptr, nullptr); ASSERT_TRUE(match(r1, "/say/hello/to/user"));
ASSERT_FALSE(match(r1, "/say/hello/to"));
ASSERT_FALSE(match(r1, "/say/hello/to/user/please"));
ASSERT_TRUE(match(routes, "/say/hello/to/user")); ASSERT_TRUE(matchSplat(r1, "/say/hello/to/user", { "hello", "user" }));
ASSERT_FALSE(match(routes, "/say/hello/to")); ASSERT_TRUE(matchSplat(r1, "/say/hello/to/user/", { "hello", "user" }));
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" }));
}
\ No newline at end of file
#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