Commit 7db786eb authored by Jeppe Pihl's avatar Jeppe Pihl

Merge branch 'master' into bugfix/fix_clang_and_gcc_warnings

parents 8ce1ed80 60fbaa25
......@@ -4,7 +4,14 @@ dist: trusty
script: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make
compiler:
- clang
- gcc
- gcc-5
before_install:
- sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
- sudo apt-get update -qq
- sudo apt-get install -qq libyajl-dev libxml2-dev libxqilla-dev
- if [ "$CXX" = "clang++" ]; then sudo apt-get install -qq libstdc++-5-dev; fi
- if [ "$CXX" = "g++" ]; then sudo apt-get install -qq g++-5; fi
- if [ "$CXX" = "g++" ]; then export CXX="g++-5" CC="gcc-5"; fi
branches:
only:
- master
......
cmake_minimum_required (VERSION 3.0.2)
project (pistache)
include(CheckCXXCompilerFlag)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra -Wno-missing-field-initializers")
......@@ -8,9 +9,19 @@ 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++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++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")
elseif(COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
......@@ -39,3 +50,48 @@ if (PISTACHE_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
# Set version...
# Major and minor version...
set(VERSION_MAJOR 0)
set(VERSION_MINOR 0)
# Make available in a header file...
configure_file (
"include/pistache/version.h.in"
"include/pistache/version.h"
@ONLY
)
# Install header...
install (
FILES
${CMAKE_BINARY_DIR}/include/pistache/version.h
DESTINATION
include/pistache/
)
# Configure the pkg-config metadata...
# Initialize the metadata variables to support remote builds...
set(prefix ${CMAKE_INSTALL_PREFIX})
set(exec_prefix ${CMAKE_INSTALL_PREFIX}/bin)
set(libdir ${CMAKE_INSTALL_PREFIX}/lib)
set(includedir ${CMAKE_INSTALL_PREFIX}/include)
set(version ${VERSION_MAJOR}.${VERSION_MINOR})
# Perform substitutions...
configure_file (
"libpistache.pc.in"
"libpistache.pc"
@ONLY
)
# Install pkg-config metadata into standard location within the prefix...
install (
FILES
${CMAKE_BINARY_DIR}/libpistache.pc
DESTINATION
lib/pkgconfig/
)
......@@ -10,13 +10,48 @@ Full documentation is located at [http://pistache.io](http://pistache.io).
# Contributing
Pistache is an open-source project and will always stay open-source. However, working on an open-source project while having a full-time job is sometimes a difficult task to accomplish.
Pistache is an open-source project and will always stay open-source. Contributors are welcome!
That's why your help is needed. If you would like to contribute to the project in any way (submitting ideas, fixing bugs, writing documentation, ...), please join the
[cpplang Slack channel](https://cpplang.now.sh/). Drop a private message to `@octal` and I will invite you to the channel dedicated to Pistache.
Pistache was created by Mathieu Stefani, who can be reached via [cpplang Slack channel](https://cpplang.now.sh/). Drop a private message to `@octal` and he will invite you to the channel dedicated to Pistache.
For those that prefer IRC over Slack, the rag-tag crew of maintainers are on #pistache on freenode.
Hope to see you there !
# To Build:
To download the latest available release, clone the repository over github.
git clone https://github.com/oktal/pistache.git
Then, init the submodules:
git submodule update --init
Now, compile the sources:
cd pistache
mkdir build
cd build
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ..
make
sudo make install
If you want the examples built, then change change the cmake above to:
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DPISTACHE_BUILD_EXAMPLES=true ..
After running the above, you can then cd into the build/examples directory and run make.
Optionally, you can also build and run the tests (tests require the examples):
cmake -G "Unix Makefiles" -DPISTACHE_BUILD_EXAMPLES=true -DPISTACHE_BUILD_TESTS=true ..
make test
Be patient, async_test can take some time before completing.
And that's it, now you can start playing with your newly installed Pistache framework.
# Example
## Hello World (server)
......
......@@ -136,8 +136,8 @@ namespace Async {
namespace Private {
struct InternalRethrow {
InternalRethrow(std::exception_ptr exc)
: exc(std::move(exc))
InternalRethrow(std::exception_ptr _exc)
: exc(std::move(_exc))
{ }
std::exception_ptr exc;
......@@ -166,9 +166,9 @@ namespace Async {
};
struct Core {
Core(State state, TypeId id)
: state(state)
, id(id)
Core(State _state, TypeId _id)
: state(_state)
, id(_id)
{ }
State state;
......@@ -253,9 +253,9 @@ namespace Async {
template<typename T>
struct Continuable : public Request {
Continuable(const std::shared_ptr<Core>& chain)
: chain_(chain)
, resolveCount_(0)
: resolveCount_(0)
, rejectCount_(0)
, chain_(chain)
{ }
void resolve(const std::shared_ptr<Core>& core) {
......@@ -839,9 +839,9 @@ namespace Async {
Deferred(Deferred&& other) = default;
Deferred& operator=(Deferred&& other) = default;
Deferred(Resolver resolver, Rejection reject)
: resolver(std::move(resolver))
, rejection(std::move(reject))
Deferred(Resolver _resolver, Rejection _reject)
: resolver(std::move(_resolver))
, rejection(std::move(_reject))
{ }
template<typename U>
......@@ -888,9 +888,9 @@ namespace Async {
Deferred(Deferred&& other) = default;
Deferred& operator=(Deferred&& other) = default;
Deferred(Resolver resolver, Rejection reject)
: resolver(std::move(resolver))
, rejection(std::move(reject))
Deferred(Resolver _resolver, Rejection _reject)
: resolver(std::move(_resolver))
, rejection(std::move(_reject))
{ }
void resolve() {
......@@ -898,8 +898,8 @@ namespace Async {
}
template<typename Exc>
void reject(Exc exc) {
rejection(std::move(exc));
void reject(Exc _exc) {
rejection(std::move(_exc));
}
private:
......@@ -1138,12 +1138,12 @@ namespace Async {
struct All {
struct Data {
Data(const size_t total, Resolver resolver, Rejection rejection)
: total(total)
Data(const size_t _total, Resolver _resolver, Rejection _rejection)
: total(_total)
, resolved(0)
, rejected(false)
, resolve(std::move(resolver))
, reject(std::move(rejection))
, resolve(std::move(_resolver))
, reject(std::move(_rejection))
{ }
const size_t total;
......@@ -1245,8 +1245,8 @@ namespace Async {
private:
template<typename T, size_t Index, typename Data>
struct WhenContinuation {
WhenContinuation(Data data)
: data(std::move(data))
WhenContinuation(Data _data)
: data(std::move(_data))
{ }
void operator()(const T& val) const {
......@@ -1258,8 +1258,8 @@ namespace Async {
template<size_t Index, typename Data>
struct WhenContinuation<void, Index, Data> {
WhenContinuation(Data data)
: data(std::move(data))
WhenContinuation(Data _data)
: data(std::move(_data))
{ }
void operator()() const {
......@@ -1341,9 +1341,9 @@ namespace Async {
>
struct WhenAllRange {
WhenAllRange(Resolver resolve, Rejection reject)
: resolve(std::move(resolve))
, reject(std::move(reject))
WhenAllRange(Resolver _resolve, Rejection _reject)
: resolve(std::move(_resolve))
, reject(std::move(_reject))
{ }
template<typename Iterator>
......@@ -1373,12 +1373,12 @@ namespace Async {
private:
struct Data {
Data(size_t total, Resolver resolver, Rejection rejection)
: total(total)
Data(size_t _total, Resolver _resolver, Rejection _rejection)
: total(_total)
, resolved(0)
, rejected(false)
, resolve(std::move(resolver))
, reject(std::move(rejection))
, resolve(std::move(_resolver))
, reject(std::move(_rejection))
{ }
const size_t total;
......@@ -1419,9 +1419,9 @@ namespace Async {
typedef std::shared_ptr<DataT<ValueType>> D;
WhenContinuation(const D& data, size_t index)
: data(data)
, index(index)
WhenContinuation(const D& _data, size_t _index)
: data(_data)
, index(_index)
{ }
void operator()(const ValueType& val) const {
......@@ -1444,8 +1444,8 @@ namespace Async {
typedef std::shared_ptr<DataT<void>> D;
WhenContinuation(const D& data, size_t)
: data(data)
WhenContinuation(const D& _data, size_t)
: data(_data)
{ }
void operator()() const {
......
......@@ -257,7 +257,7 @@ public:
friend class Client;
RequestBuilder& method(Method method);
RequestBuilder& resource(std::string val);
RequestBuilder& resource(const std::string& val);
RequestBuilder& params(const Uri::Query& query);
RequestBuilder& header(const std::shared_ptr<Header::Header>& header);
......@@ -321,11 +321,11 @@ public:
static Options options();
void init(const Options& options);
RequestBuilder get(std::string resource);
RequestBuilder post(std::string resource);
RequestBuilder put(std::string resource);
RequestBuilder patch(std::string resource);
RequestBuilder del(std::string resource);
RequestBuilder get(const std::string& resource);
RequestBuilder post(const std::string& resource);
RequestBuilder put(const std::string& resource);
RequestBuilder patch(const std::string& resource);
RequestBuilder del(const std::string& resource);
void shutdown();
......@@ -342,9 +342,9 @@ private:
typedef std::lock_guard<Lock> Guard;
Lock queuesLock;
std::unordered_map<std::string, MPMCQueue<Connection::RequestData *, 2048>> requestsQueues;
std::unordered_map<std::string, MPMCQueue<std::shared_ptr<Connection::RequestData>, 2048>> requestsQueues;
RequestBuilder prepareRequest(std::string resource, Http::Method method);
RequestBuilder prepareRequest(const std::string& resource, Http::Method method);
Async::Promise<Response> doRequest(
Http::Request req,
......
......@@ -49,6 +49,9 @@
#define unreachable() __builtin_unreachable()
// Until we require C++17 compiler with [[maybe_unused]]
#define UNUSED(x) (void)(x);
namespace Pistache {
namespace Const {
......
......@@ -44,8 +44,8 @@ public:
typedef std::unordered_map<std::string, Cookie> Storage;
struct iterator : std::iterator<std::bidirectional_iterator_tag, Cookie> {
iterator(const Storage::const_iterator& iterator)
: it_(iterator)
iterator(const Storage::const_iterator& _iterator)
: it_(_iterator)
{ }
Cookie operator*() const {
......@@ -78,6 +78,7 @@ public:
CookieJar();
void add(const Cookie& cookie);
void addFromRaw(const char *str, size_t len);
Cookie get(const std::string& name) const;
bool has(const std::string& name) const;
......
This diff is collapsed.
......@@ -379,8 +379,8 @@ public:
Description& basePath(std::string value);
template<typename... Schemes>
Description& schemes(Schemes... schemes) {
Scheme s[sizeof...(Schemes)] = { schemes... };
Description& schemes(Schemes... _schemes) {
Scheme s[sizeof...(Schemes)] = { _schemes... };
std::copy(std::begin(s), std::end(s), std::back_inserter(schemes_));
return *this;
}
......
......@@ -58,7 +58,7 @@ public:
Flags() : val(T::None) {
}
Flags(T val) : val(val)
Flags(T _val) : val(_val)
{
}
......
......@@ -9,7 +9,9 @@
#include <type_traits>
#include <stdexcept>
#include <array>
#include <vector>
#include <sstream>
#include <algorithm>
#include <sys/timerfd.h>
......@@ -87,7 +89,6 @@ protected:
};
namespace Uri {
typedef std::string Fragment;
class Query {
public:
......@@ -97,12 +98,36 @@ namespace Uri {
void add(std::string name, std::string value);
Optional<std::string> get(const std::string& name) const;
bool has(const std::string& name) const;
// Return empty string or "?key1=value1&key2=value2" if query exist
std::string as_str() const;
void clear() {
params.clear();
}
// \brief Return iterator to the beginning of the parameters map
std::unordered_map<std::string, std::string>::const_iterator
parameters_begin() const {
return params.begin();
}
// \brief Return iterator to the end of the parameters map
std::unordered_map<std::string, std::string>::const_iterator
parameters_end() const {
return params.begin();
}
// \brief returns all parameters given in the query
std::vector<std::string> parameters() const {
std::vector<std::string> keys;
std::transform(params.begin(), params.end(), std::back_inserter(keys),
[](const std::unordered_map<std::string, std::string>::value_type
&pair) {return pair.first;});
return keys;
}
private:
//first is key second is value
std::unordered_map<std::string, std::string> params;
};
} // namespace Uri
......@@ -231,18 +256,18 @@ public:
private:
Timeout(const Timeout& other)
: handler(other.handler)
, transport(other.transport)
, request(other.request)
, transport(other.transport)
, armed(other.armed)
, timerFd(other.timerFd)
{ }
Timeout(Tcp::Transport* transport,
Handler* handler,
Request request)
: handler(handler)
, transport(transport)
, request(std::move(request))
Timeout(Tcp::Transport* transport_,
Handler* handler_,
Request request_)
: handler(handler_)
, request(std::move(request_))
, transport(transport_)
, armed(false)
, timerFd(-1)
{
......@@ -509,17 +534,24 @@ public:
return timeout_;
}
std::shared_ptr<Tcp::Peer> peer() const {
if (peer_.expired())
throw std::runtime_error("Write failed: Broken pipe");
return peer_.lock();
}
// Unsafe API
DynamicStreamBuf *rdbuf() {
return &buf_;
}
DynamicStreamBuf *rdbuf(DynamicStreamBuf*) {
DynamicStreamBuf *rdbuf([[maybe_unused]] DynamicStreamBuf* other) {
UNUSED(other)
throw std::domain_error("Unimplemented");
}
ResponseWriter clone() const {
return ResponseWriter(*this);
}
......@@ -540,13 +572,6 @@ private:
, timeout_(other.timeout_)
{ }
std::shared_ptr<Tcp::Peer> peer() const {
if (peer_.expired())
throw std::runtime_error("Write failed: Broken pipe");
return peer_.lock();
}
template<typename Ptr>
void associatePeer(const Ptr& peer) {
if (peer_.use_count() > 0)
......@@ -577,6 +602,8 @@ namespace Private {
: message(request)
{ }
virtual ~Step() = default;
virtual State apply(StreamCursor& cursor) = 0;
void raise(const char* msg, Code code = Code::Bad_Request);
......@@ -611,11 +638,10 @@ namespace Private {
State apply(StreamCursor& cursor);
};
class BodyStep : public Step {
public:
BodyStep(Message* message)
: Step(message)
, chunk(message)
struct BodyStep : public Step {
BodyStep(Message* message_)
: Step(message_)
, chunk(message_)
, bytesRead(0)
{ }
......@@ -625,8 +651,8 @@ namespace Private {
struct Chunk {
enum Result { Complete, Incomplete, Final };
Chunk(Message* message)
: message(message)
Chunk(Message* message_)
: message(message_)
, bytesRead(0)
, size(-1)
{ }
......@@ -659,10 +685,12 @@ namespace Private {
{
}
ParserBase(const char*, size_t)
ParserBase(const char* data, size_t len)
: cursor(&buffer)
, currentStep(0)
{
UNUSED(data)
UNUSED(len)
}
ParserBase(const ParserBase& other) = delete;
......
......@@ -10,7 +10,6 @@
#include <ostream>
#include <stdexcept>
#include <chrono>
#include <ctime>
#include <functional>
namespace Pistache {
......@@ -176,7 +175,8 @@ private:
// 3.3.1 Full Date
class FullDate {
public:
FullDate();
using time_point = std::chrono::system_clock::time_point;
FullDate() { }
enum class Type {
RFC1123,
......@@ -184,18 +184,17 @@ public:
AscTime
};
FullDate(std::tm date)
FullDate(time_point date)
: date_(date)
{ }
std::tm date() const { return date_; }
time_point date() const { return date_; }
void write(std::ostream& os, Type type = Type::RFC1123) const;
static FullDate fromRaw(const char* str, size_t len);
static FullDate fromString(const std::string& str);
private:
std::tm date_;
time_point date_;
};
const char* methodString(Method method);
......
......@@ -326,7 +326,7 @@ public:
fullDate_(date)
{ }
void parseRaw(const char* str, size_t len);
void parse(const std::string &str);
void write(std::ostream& os) const;
FullDate fullDate() const { return fullDate_; }
......
......@@ -14,8 +14,8 @@ struct FlatMapIteratorAdapter {
typedef typename Map::mapped_type Value;
typedef typename Map::const_iterator const_iterator;
FlatMapIteratorAdapter(const_iterator it)
: it(it)
FlatMapIteratorAdapter(const_iterator _it)
: it(_it)
{ }
FlatMapIteratorAdapter operator++() {
......
......@@ -90,14 +90,14 @@ public:
}
T *post(T *newData) {
auto *ret = Mailbox<T>::post(newData);
auto *_ret = Mailbox<T>::post(newData);
if (isBound()) {
uint64_t val = 1;
TRY(write(event_fd, &val, sizeof val));
}
return ret;
return _ret;
}
T *clear() {
......
......@@ -36,6 +36,7 @@ namespace Mime {
SUB_TYPE(Javascript, "javascript") \
SUB_TYPE(Css , "css") \
\
SUB_TYPE(OctetStream, "octet-stream") \
SUB_TYPE(Json , "json") \
SUB_TYPE(FormUrlEncoded, "x-www-form-urlencoded") \
\
......
......@@ -227,8 +227,8 @@ public:
private:
const T* constData() const {
return const_cast<const T*>(reinterpret_cast<const T*>(bytes));
T *constData() const {
return const_cast<T *const>(reinterpret_cast<const T *const>(bytes));
}
T *data() const {
......@@ -262,14 +262,14 @@ private:
};
};
#define CheckSize(Type) \
#define PistacheCheckSize(Type) \
static_assert(sizeof(Optional<Type>) == sizeof(Type), "Size differs")
CheckSize(uint8_t);
CheckSize(uint16_t);
CheckSize(int);
CheckSize(void *);
CheckSize(std::string);
PistacheCheckSize(uint8_t);
PistacheCheckSize(uint16_t);
PistacheCheckSize(int);
PistacheCheckSize(void *);
PistacheCheckSize(std::string);
namespace details {
template<typename T>
......
......@@ -87,8 +87,8 @@ inline constexpr bool operator==(Tag lhs, Tag rhs) {
}
struct Event {
explicit Event(Tag tag) :
tag(tag)
explicit Event(Tag _tag) :
tag(_tag)
{ }
Flags<NotifyOn> flags;
......
......@@ -204,7 +204,7 @@ public:
};
virtual void onReady(const FdSet& fds) = 0;
virtual void registerPoller(Polling::Epoll&) { }
virtual void registerPoller(Polling::Epoll& poller) { UNUSED(poller) }
Reactor* reactor() const {
return reactor_;
......
......@@ -8,11 +8,15 @@
#include <string>
#include <tuple>
#include <unordered_map>
#include <memory>
#include <pistache/http.h>
#include <pistache/http_defs.h>
#include <pistache/flags.h>
#include "pistache/string_view.h"
namespace Pistache {
namespace Rest {
......@@ -20,8 +24,8 @@ class Description;
namespace details {
template<typename T> struct LexicalCast {
static T cast(const std::string& value) {
std::istringstream iss(value);
static T cast(const std::string_view& value) {
std::istringstream iss(std::string(value.data(), value.length()));
T out;
if (!(iss >> out))
throw std::runtime_error("Bad lexical cast");
......@@ -30,8 +34,8 @@ namespace details {
};
template<>
struct LexicalCast<std::string> {
static std::string cast(const std::string& value) {
struct LexicalCast<std::string_view> {
static std::string_view cast(const std::string_view& value) {
return value;
}
};
......@@ -39,7 +43,7 @@ namespace details {
class TypedParam {
public:
TypedParam(const std::string& name, const std::string& value)
TypedParam(const std::string_view& name, const std::string_view& value)
: name_(name)
, value_(value)
{ }
......@@ -49,97 +53,88 @@ public:
return details::LexicalCast<T>::cast(value_);
}
std::string name() const {
std::string_view name() const {
return name_;
}
private:
std::string name_;
std::string value_;
std::string_view name_;
std::string_view value_;
};
class Request;
struct Route {
enum class Result { Ok, Failure };
typedef std::function<Result (const Request&, Http::ResponseWriter)> Handler;
enum class Status { Match, NotFound };
Route(std::string resource, Http::Method method, Handler handler)
: resource_(std::move(resource))
, method_(method)
, handler_(std::move(handler))
, fragments_(Fragment::fromUrl(resource_))
{
}
enum class Result { Ok, Failure };
std::tuple<bool, std::vector<TypedParam>, std::vector<TypedParam>>
match(const Http::Request& req) const;
typedef std::function<Result(const Request, Http::ResponseWriter)> Handler;
std::tuple<bool, std::vector<TypedParam>, std::vector<TypedParam>>
match(const std::string& req) const;
explicit Route(Route::Handler handler) : handler_(std::move(handler)) { }
template<typename... Args>
void invokeHandler(Args&& ...args) const {
void invokeHandler(Args &&...args) const {
handler_(std::forward<Args>(args)...);
}
private:
struct Fragment {
explicit Fragment(std::string value);
Handler handler_;
};
bool match(const std::string& raw) const;
bool match(const Fragment& other) const;
namespace Private {
class RouterHandler;
}
bool isParameter() const;
bool isSplat() const;
bool isOptional() const;
class FragmentTreeNode {
private:
enum class FragmentType {
Fixed, Param, Optional, Splat
};
std::string value() const {
return value_;
}
/**
* Resource path are allocated on stack. To make them survive it is required to allocate them on heap.
* 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_;
static std::vector<Fragment> fromUrl(const std::string& url);
std::unordered_map<std::string_view, std::shared_ptr<FragmentTreeNode>> fixed_;
std::unordered_map<std::string_view, std::shared_ptr<FragmentTreeNode>> param_;
std::unordered_map<std::string_view, std::shared_ptr<FragmentTreeNode>> optional_;
std::shared_ptr<FragmentTreeNode> splat_;
std::shared_ptr<Route> route_;
private:
enum class Flag {
None = 0x0,
Fixed = 0x1,
Parameter = Fixed << 1,
Optional = Parameter << 1,
Splat = Optional << 1
};
static FragmentType getFragmentType(const std::string_view &fragment);
void init(std::string value);
std::tuple<std::shared_ptr<Route>, std::vector<TypedParam>, std::vector<TypedParam>>
findRoute(const std::string_view &path,
std::vector<TypedParam> &params,
std::vector<TypedParam> &splats) const;
void checkInvariant() const;
public:
FragmentTreeNode();
explicit FragmentTreeNode(const std::shared_ptr<char> &resourceReference);
Flags<Flag> flags;
std::string value_;
};
void addRoute(const std::string_view &path, const Route::Handler &handler,
const std::shared_ptr<char> &resourceReference);
std::string resource_;
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
bool removeRoute(const std::string_view &path);
/**
* Finds the correct route for the given path.
* \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
* is a null pointer).
*/
std::vector<Fragment> fragments_;
std::tuple<std::shared_ptr<Route>, std::vector<TypedParam>, std::vector<TypedParam>>
findRoute(const std::string_view &path) const;
};
namespace Private {
class RouterHandler;
}
class Router {
public:
enum class Status { Match, NotFound };
static Router fromDescription(const Rest::Description& desc);
std::shared_ptr<Private::RouterHandler>
......@@ -153,16 +148,22 @@ public:
void patch(std::string resource, Route::Handler handler);
void del(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 addNotFoundHandler(Route::Handler handler);
inline bool hasNotFoundHandler() { return notFoundHandler != nullptr; }
void invokeNotFoundHandler(const Http::Request &req, Http::ResponseWriter resp) const;
Status route(const Http::Request& request, Http::ResponseWriter response);
Route::Status route(const Http::Request& request, Http::ResponseWriter response);
private:
void addRoute(Http::Method method, std::string resource, Route::Handler handler);
std::unordered_map<Http::Method, std::vector<Route>> routes;
std::unordered_map<Http::Method, FragmentTreeNode> routes;
std::vector<Route::Handler> customHandlers;
Route::Handler notFoundHandler;
};
namespace Private {
......@@ -186,6 +187,7 @@ namespace Private {
class Request : public Http::Request {
public:
friend class FragmentTreeNode;
friend class Router;
bool hasParam(std::string name) const;
......@@ -213,6 +215,9 @@ namespace Routes {
void Patch(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 Remove(Router& router, Http::Method method, std::string resource);
void NotFound(Router& router, Route::Handler handler);
namespace details {
template <class... Args>
......@@ -228,11 +233,11 @@ namespace Routes {
template<typename... Args>
void static_checks() {
static_assert(sizeof...(Args) == 2, "Function should take 2 parameters");
#if 0
// typedef details::TypeList<Args...> Arguments;
// Disabled now as it
// 1/ does not compile
// 2/ might not be relevant
typedef details::TypeList<Args...> Arguments;
#if 0
static_assert(std::is_same<Arguments::At<0>::Type, const Rest::Request&>::value, "First argument should be a const Rest::Request&");
static_assert(std::is_same<typename Arguments::At<0>::Type, Http::Response>::value, "Second argument should be a Http::Response");
#endif
......
......@@ -202,7 +202,7 @@ void serializeDescription(Writer& writer, const Description& desc) {
writer.EndObject();
}
std::string rapidJson(const Description& desc) {
inline std::string rapidJson(const Description& desc) {
rapidjson::StringBuffer sb;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(sb);
serializeDescription(writer, desc);
......
......@@ -97,7 +97,7 @@ public:
}
bool feed(const char* data, size_t len) {
if (size + len >= N) {
if (size + len > N) {
return false;
}
......@@ -133,10 +133,10 @@ struct Buffer {
, isOwned(false)
{ }
Buffer(const char * const data, size_t len, bool own = false)
: data(data)
, len(len)
, isOwned(own)
Buffer(const char * const _data, size_t _len, bool _own = false)
: data(_data)
, len(_len)
, isOwned(_own)
{ }
Buffer detach(size_t fromIndex = 0) const {
......@@ -226,8 +226,8 @@ private:
class StreamCursor {
public:
StreamCursor(StreamBuf<char>* buf, size_t initialPos = 0)
: buf(buf)
StreamCursor(StreamBuf<char>* _buf, size_t initialPos = 0)
: buf(_buf)
{
advance(initialPos);
}
......@@ -235,8 +235,8 @@ public:
static constexpr int Eof = -1;
struct Token {
Token(StreamCursor& cursor)
: cursor(cursor)
Token(StreamCursor& _cursor)
: cursor(_cursor)
, position(cursor.buf->position())
, eback(cursor.buf->begptr())
, gptr(cursor.buf->curptr())
......@@ -270,8 +270,8 @@ public:
};
struct Revert {
Revert(StreamCursor& cursor)
: cursor(cursor)
Revert(StreamCursor& _cursor)
: cursor(_cursor)
, eback(cursor.buf->begptr())
, gptr(cursor.buf->curptr())
, egptr(cursor.buf->endptr())
......
#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
......@@ -147,7 +147,7 @@ private:
Fd fd;
U(Buffer buffer) : raw(buffer) { }
U(Fd fd) : fd(fd) { }
U(Fd fd_) : fd(fd_) { }
} u;
size_t size_= 0;
off_t offset_ = 0;
......@@ -155,11 +155,11 @@ private:
};
struct WriteEntry {
WriteEntry(Async::Deferred<ssize_t> deferred,
BufferHolder buffer, int flags = 0)
: deferred(std::move(deferred))
, buffer(std::move(buffer))
, flags(flags)
WriteEntry(Async::Deferred<ssize_t> deferred_,
BufferHolder buffer_, int flags_ = 0)
: deferred(std::move(deferred_))
, buffer(std::move(buffer_))
, flags(flags_)
, peerFd(-1)
{ }
......@@ -170,11 +170,11 @@ private:
};
struct TimerEntry {
TimerEntry(Fd fd, std::chrono::milliseconds value,
Async::Deferred<uint64_t> deferred)
: fd(fd)
, value(value)
, deferred(std::move(deferred))
TimerEntry(Fd fd_, std::chrono::milliseconds value_,
Async::Deferred<uint64_t> deferred_)
: fd(fd_)
, value(value_)
, deferred(std::move(deferred_))
{
active.store(true, std::memory_order_relaxed);
}
......@@ -201,8 +201,8 @@ private:
};
struct PeerEntry {
PeerEntry(std::shared_ptr<Peer> peer)
: peer(std::move(peer))
PeerEntry(std::shared_ptr<Peer> peer_)
: peer(std::move(peer_))
{ }
std::shared_ptr<Peer> peer;
......
/* version.h
Kip Warner, 29 May 2018
Version constants
*/
#pragma once
namespace Pistache {
namespace Version {
static constexpr int Major = @VERSION_MAJOR@;
static constexpr int Minor = @VERSION_MINOR@;
} // namespace Version
} // namespace Pistache
prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@
Name: libpistache
URL: http://pistache.io/
Description: An elegant C++ REST framework.
Version: @version@
Requires:
Libs: -L'${libdir}/' -lpistache -lpthread
Libs.private:
Cflags: -I'${includedir}/'
......@@ -9,7 +9,7 @@ set(SOURCE_FILES
${SERVER_SOURCE_FILES}
${CLIENT_SOURCE_FILES}
${INCLUDE_FILES}
)
../include/pistache/string_view.h)
add_library(pistache ${SOURCE_FILES})
set_target_properties(pistache PROPERTIES
......@@ -17,6 +17,7 @@ set_target_properties(pistache PROPERTIES
VERSION ${GENERIC_LIB_VERSION}
SOVERSION ${GENERIC_LIB_SOVERSION}
)
add_definitions(-DONLY_C_LOCALE=1)
target_include_directories(pistache PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
......
......@@ -355,7 +355,7 @@ Transport::handleIncoming(const std::shared_ptr<Connection>& connection) {
else {
totalBytes += bytes;
if (totalBytes >= Const::MaxBuffer) {
if (totalBytes > Const::MaxBuffer) {
std::cerr << "Too long packet" << std::endl;
break;
}
......@@ -463,43 +463,55 @@ Connection::handleResponsePacket(const char* buffer, size_t bytes) {
parser_.feed(buffer, bytes);
if (parser_.parse() == Private::State::Done) {
auto req = std::move(inflightRequests.front());
inflightRequests.pop_back();
if (!inflightRequests.empty()) {
auto req = std::move(inflightRequests.front());
inflightRequests.pop_back();
if (req.timer) {
req.timer->disarm();
timerPool_.releaseTimer(req.timer);
if (req.timer) {
req.timer->disarm();
timerPool_.releaseTimer(req.timer);
}
req.resolve(std::move(parser_.response));
if (req.onDone)
req.onDone();
}
req.resolve(std::move(parser_.response));
req.onDone();
parser_.reset();
}
}
void
Connection::handleError(const char* error) {
auto req = std::move(inflightRequests.front());
if (req.timer) {
req.timer->disarm();
timerPool_.releaseTimer(req.timer);
}
if (!inflightRequests.empty()) {
auto req = std::move(inflightRequests.front());
if (req.timer) {
req.timer->disarm();
timerPool_.releaseTimer(req.timer);
}
req.reject(Error(error));
req.onDone();
req.reject(Error(error));
if (req.onDone)
req.onDone();
}
}
void
Connection::handleTimeout() {
auto req = std::move(inflightRequests.front());
inflightRequests.pop_back();
if (!inflightRequests.empty()) {
auto req = std::move(inflightRequests.front());
inflightRequests.pop_back();
timerPool_.releaseTimer(req.timer);
timerPool_.releaseTimer(req.timer);
req.onDone();
/* @API: create a TimeoutException */
req.reject(std::runtime_error("Timeout"));
if (req.onDone)
req.onDone();
/* @API: create a TimeoutException */
req.reject(std::runtime_error("Timeout"));
}
}
Async::Promise<Response>
Connection::perform(
const Http::Request& request,
......@@ -540,6 +552,7 @@ struct MoveOnCopy {
MoveOnCopy& operator=(MoveOnCopy& other) {
val = std::move(other.val);
return *this;
}
MoveOnCopy(MoveOnCopy&& other) = default;
......@@ -697,8 +710,8 @@ RequestBuilder::method(Method method)
}
RequestBuilder&
RequestBuilder::resource(std::string val) {
request_.resource_ = std::move(val);
RequestBuilder::resource(const std::string& val) {
request_.resource_ = val;
return *this;
}
......@@ -756,15 +769,6 @@ Client::Client()
}
Client::~Client() {
for (auto& queues: requestsQueues) {
auto& q = queues.second;
for (;;) {
Connection::RequestData* d;
if (!q.dequeue(d)) break;
delete d;
}
}
}
Client::Options
......@@ -787,41 +791,41 @@ Client::shutdown() {
}
RequestBuilder
Client::get(std::string resource)
Client::get(const std::string& resource)
{
return prepareRequest(std::move(resource), Http::Method::Get);
return prepareRequest(resource, Http::Method::Get);
}
RequestBuilder
Client::post(std::string resource)
Client::post(const std::string& resource)
{
return prepareRequest(std::move(resource), Http::Method::Post);
return prepareRequest(resource, Http::Method::Post);
}
RequestBuilder
Client::put(std::string resource)
Client::put(const std::string& resource)
{
return prepareRequest(std::move(resource), Http::Method::Put);
return prepareRequest(resource, Http::Method::Put);
}
RequestBuilder
Client::patch(std::string resource)
Client::patch(const std::string& resource)
{
return prepareRequest(std::move(resource), Http::Method::Patch);
return prepareRequest(resource, Http::Method::Patch);
}
RequestBuilder
Client::del(std::string resource)
Client::del(const std::string& resource)
{
return prepareRequest(std::move(resource), Http::Method::Delete);
return prepareRequest(resource, Http::Method::Delete);
}
RequestBuilder
Client::prepareRequest(std::string resource, Http::Method method)
Client::prepareRequest(const std::string& resource, Http::Method method)
{
RequestBuilder builder(this);
builder
.resource(std::move(resource))
.resource(resource)
.method(method);
return builder;
......@@ -842,14 +846,10 @@ Client::doRequest(
return Async::Promise<Response>([=](Async::Resolver& resolve, Async::Rejection& reject) {
Guard guard(queuesLock);
std::unique_ptr<Connection::RequestData> data(
new Connection::RequestData(std::move(resolve), std::move(reject), request, timeout, nullptr));
auto data = std::make_shared<Connection::RequestData>(std::move(resolve), std::move(reject), request, timeout, nullptr);
auto& queue = requestsQueues[s.first];
if (!queue.enqueue(data.get()))
if (!queue.enqueue(data))
data->reject(std::runtime_error("Queue is full"));
else
data.release();
});
}
else {
......@@ -879,15 +879,14 @@ Client::processRequestQueue() {
Guard guard(queuesLock);
for (auto& queues: requestsQueues) {
const auto& domain = queues.first;
for (;;) {
const auto& domain = queues.first;
auto conn = pool.pickConnection(domain);
if (!conn)
break;
auto& queue = queues.second;
Connection::RequestData *data;
std::shared_ptr<Connection::RequestData> data;
if (!queue.dequeue(data)) {
pool.releaseConnection(conn);
break;
......@@ -901,8 +900,6 @@ Client::processRequestQueue() {
pool.releaseConnection(conn);
processRequestQueue();
});
delete data;
}
}
}
......
......@@ -36,7 +36,7 @@ namespace {
struct AttributeMatcher<Optional<std::string>> {
static void match(StreamCursor& cursor, Cookie* obj, Optional<std::string> Cookie::*attr) {
auto token = matchValue(cursor);
obj->*attr = Some(std::string(token.rawText(), token.size()));
obj->*attr = Some(token.text());
}
};
......@@ -73,7 +73,7 @@ namespace {
struct AttributeMatcher<Optional<FullDate>> {
static void match(StreamCursor& cursor, Cookie* obj, Optional<FullDate> Cookie::*attr) {
auto token = matchValue(cursor);
obj->*attr = Some(FullDate::fromRaw(token.rawText(), token.size()));
obj->*attr = Some(FullDate::fromString(token.text()));
}
};
......@@ -209,6 +209,35 @@ CookieJar::add(const Cookie& cookie) {
cookies.insert(std::make_pair(cookie.name, cookie));
}
void
CookieJar::addFromRaw(const char *str, size_t len) {
RawStreamBuf<> buf(const_cast<char *>(str), len);
StreamCursor cursor(&buf);
while (!cursor.eof()) {
StreamCursor::Token nameToken(cursor);
if (!match_until('=', cursor))
throw std::runtime_error("Invalid cookie, missing value");
auto name = nameToken.text();
if (!cursor.advance(1))
throw std::runtime_error("Invalid cookie, missing value");
StreamCursor::Token valueToken(cursor);
match_until(';', cursor);
auto value = valueToken.text();
Cookie cookie(std::move(name), std::move(value));
add(cookie);
cursor.advance(1);
skip_whitespaces(cursor);
}
}
Cookie
CookieJar::get(const std::string& name) const {
auto it = cookies.find(name);
......
......@@ -294,9 +294,7 @@ namespace Private {
}
if (name == "Cookie") {
message->cookies_.add(
Cookie::fromRaw(cursor.offset(start), cursor.diff(start))
);
message->cookies_.addFromRaw(cursor.offset(start), cursor.diff(start));
}
else if (Header::Registry::isRegistered(name)) {
......@@ -500,6 +498,18 @@ namespace Uri {
return Some(it->second);
}
std::string
Query::as_str() const {
std::string query_url;
for(const auto &e : params) {
query_url += "&" + e.first + "=" + e.second;
}
if(not query_url.empty()) {
query_url[0] = '?'; // replace first `&` with `?`
} else {/* query_url is empty */}
return query_url;
}
bool
Query::has(const std::string& name) const {
......@@ -668,13 +678,21 @@ serveFile(ResponseWriter& response, const char* fileName, const Mime::MediaType&
int fd = open(fileName, O_RDONLY);
if (fd == -1) {
std::string str_error(strerror(errno));
if(errno == ENOENT) {
throw HttpError(Http::Code::Not_Found, std::move(str_error));
}
//eles if TODO
/* @Improvement: maybe could we check for errno here and emit a different error
message
*/
throw HttpError(Http::Code::Not_Found, "");
else {
throw HttpError(Http::Code::Internal_Server_Error, std::move(str_error));
}
}
int res = ::fstat(fd, &sb);
close(fd); // Done with fd, close before error can be thrown
if (res == -1) {
throw HttpError(Code::Internal_Server_Error, "");
}
......
......@@ -9,25 +9,36 @@
#include <pistache/http_defs.h>
#include <pistache/common.h>
#include <pistache/date.h>
namespace Pistache {
namespace Http {
namespace {
bool parseRFC1123Date(std::tm& tm, const char* str, size_t) {
char *p = strptime(str, "%a, %d %b %Y %H:%M:%S %Z", &tm);
return p != NULL;
using time_point = FullDate::time_point;
bool parse_RFC_1123(const std::string& s, time_point &tp)
{
std::istringstream in{s};
in >> date::parse("%a, %d %b %Y %T %Z", tp);
return !in.fail();
}
bool parseRFC850Date(std::tm& tm, const char* str, size_t) {
char *p = strptime(str, "%A, %d-%b-%y %H:%M:%S %Z", &tm);
return p != NULL;
bool parse_RFC_850(const std::string& s, time_point &tp)
{
std::istringstream in{s};
in >> date::parse("%a, %d-%b-%y %T %Z", tp);
return !in.fail();
}
bool parseAscTimeDate(std::tm& tm, const char* str, size_t) {
char *p = strptime(str, "%a %b %d %H:%M:%S %Y", &tm);
return p != NULL;
bool parse_asctime(const std::string& s, time_point &tp)
{
std::istringstream in{s};
in >> date::parse("%a %b %d %T %Y", tp);
return !in.fail();
}
} // anonymous namespace
CacheDirective::CacheDirective(Directive directive)
......@@ -80,51 +91,35 @@ CacheDirective::init(Directive directive, std::chrono::seconds delta)
}
}
FullDate::FullDate() {
std::memset(&date_, 0, sizeof date_);
}
FullDate
FullDate::fromRaw(const char* str, size_t len)
{
// As per the RFC, implementation MUST support all three formats.
std::tm tm = {};
if (parseRFC1123Date(tm, str, len)) {
return FullDate(tm);
}
memset(&tm, 0, sizeof tm);
if (parseRFC850Date(tm, str, len)) {
return FullDate(tm);
}
memset(&tm, 0, sizeof tm);
FullDate::fromString(const std::string& str) {
if (parseAscTimeDate(tm, str, len)) {
return FullDate(tm);
}
FullDate::time_point tp;
if(parse_RFC_1123(str, tp))
return FullDate(tp);
else if(parse_RFC_850(str, tp))
return FullDate(tp);
else if(parse_asctime(str, tp))
return FullDate(tp);
throw std::runtime_error("Invalid Date format");
}
FullDate
FullDate::fromString(const std::string& str) {
return FullDate::fromRaw(str.c_str(), str.size());
}
void
FullDate::write(std::ostream& os, Type type) const
{
char buff[100];
std::memset(buff, 0, sizeof buff);
switch (type) {
case Type::RFC1123:
//os << std::put_time(&date_, "%a, %d %b %Y %H:%M:%S %Z");
if (std::strftime(buff, sizeof buff, "%a, %d %b %Y %H:%M:%S %Z", &date_))
os << buff;
date::to_stream(os, "%a, %d %b %Y %T %Z", date_);
break;
case Type::RFC850:
date::to_stream(os, "%a, %d-%b-%y %T %Z", date_);
break;
case Type::AscTime:
date::to_stream(os, "%a %b %d %T %Y", date_);
break;
default:
std::runtime_error("Invalid use of FullDate::write");
}
}
......
......@@ -207,6 +207,7 @@ CacheControl::write(std::ostream& os) const {
default:
return "";
}
return "";
};
auto hasDelta = [](CacheDirective directive) {
......@@ -278,7 +279,8 @@ Connection::write(std::ostream& os) const {
case ConnectionControl::KeepAlive:
os << "Keep-Alive";
break;
default:
case ConnectionControl::Ext:
os << "Ext";
break;
}
}
......@@ -302,8 +304,8 @@ ContentLength::write(std::ostream& os) const {
}
void
Date::parseRaw(const char* str, size_t len) {
fullDate_ = FullDate::fromRaw(str, len);
Date::parse(const std::string &str) {
fullDate_ = FullDate::fromString(str);
}
void
......@@ -428,21 +430,19 @@ AccessControlAllowOrigin::write(std::ostream& os) const {
void
EncodingHeader::parseRaw(const char* str, size_t len) {
// TODO: case-insensitive
//
if (!strncmp(str, "gzip", len)) {
if (!strncasecmp(str, "gzip", len)) {
encoding_ = Encoding::Gzip;
}
else if (!strncmp(str, "deflate", len)) {
else if (!strncasecmp(str, "deflate", len)) {
encoding_ = Encoding::Deflate;
}
else if (!strncmp(str, "compress", len)) {
else if (!strncasecmp(str, "compress", len)) {
encoding_ = Encoding::Compress;
}
else if (!strncmp(str, "identity", len)) {
else if (!strncasecmp(str, "identity", len)) {
encoding_ = Encoding::Identity;
}
else if (!strncmp(str, "chunked", len)) {
else if (!strncasecmp(str, "chunked", len)) {
encoding_ = Encoding::Chunked;
}
else {
......
......@@ -82,7 +82,9 @@ MediaType::fromFile(const char* fileName)
{ "bmp", Type::Image, Subtype::Bmp },
{ "txt", Type::Text, Subtype::Plain },
{ "md", Type::Text, Subtype::Plain }
{ "md", Type::Text, Subtype::Plain },
{ "bin", Type::Application, Subtype::OctetStream},
};
for (const auto& ext: KnownExtensions) {
......
......@@ -18,14 +18,14 @@ namespace Tcp {
using namespace std;
Peer::Peer()
: fd_(-1)
, transport_(nullptr)
: transport_(nullptr)
, fd_(-1)
{ }
Peer::Peer(const Address& addr)
: addr(addr)
: transport_(nullptr)
, addr(addr)
, fd_(-1)
, transport_(nullptr)
{ }
Address
......
......@@ -228,6 +228,13 @@ Transport::asyncWriteImpl(
totalWritten += bytesWritten;
if (totalWritten >= buffer.size()) {
cleanUp();
if (buffer.isFile()) {
// done with the file buffer, nothing else knows whether to
// close it with the way the code is written.
::close(buffer.fd());
}
deferred.resolve(totalWritten);
break;
}
......
This diff is collapsed.
......@@ -16,3 +16,4 @@ pistache_test(router_test)
pistache_test(cookie_test)
pistache_test(view_test)
pistache_test(http_parsing_test)
pistache_test(string_view_test)
#include "gtest/gtest.h"
#include <pistache/cookie.h>
#include <pistache/date.h>
using namespace Pistache;
using namespace Pistache::Http;
......@@ -46,12 +47,11 @@ TEST(cookie_test, attributes_test) {
ASSERT_EQ(c.value, "en-US");
auto expires = c.expires.getOrElse(FullDate());
auto date = expires.date();
ASSERT_EQ(date.tm_year, 121);
ASSERT_EQ(date.tm_mon, 5);
ASSERT_EQ(date.tm_mday, 9);
ASSERT_EQ(date.tm_hour, 10);
ASSERT_EQ(date.tm_min, 18);
ASSERT_EQ(date.tm_sec, 14);
using namespace std::chrono;
FullDate::time_point expected_time_point = date::sys_days(date::year{2021}/6/9)
+ hours(10) + minutes(18) + seconds(14);
ASSERT_EQ(date, expected_time_point);
});
parse("lang=en-US; Path=/; Domain=example.com;", [](const Cookie& c) {
......@@ -102,15 +102,10 @@ TEST(cookie_test, write_test) {
ASSERT_EQ(oss.str(), "lang=fr-FR; Path=/; Domain=example.com");
Cookie c2("lang", "en-US");
std::tm expires;
std::memset(&expires, 0, sizeof expires);
expires.tm_isdst = 1;
expires.tm_mon = 2;
expires.tm_year = 118;
expires.tm_mday = 16;
expires.tm_hour = 17;
expires.tm_min = 0;
expires.tm_sec = 0;
using namespace std::chrono;
FullDate::time_point expires = date::sys_days(date::year{118}/2/16) + hours(17);
c2.path = Some(std::string("/"));
c2.expires = Some(FullDate(expires));
......@@ -134,3 +129,24 @@ TEST(cookie_test, invalid_test) {
ASSERT_THROW(Cookie::fromString("lang=en-US; Max-Age=12ab"), std::invalid_argument);
}
void addCookies(const char* str, std::function<void (const CookieJar&)> testFunc) {
CookieJar jar;
jar.addFromRaw(str, strlen(str));
testFunc(jar);
}
TEST(cookie_test, cookiejar_test) {
addCookies("key1=value1", [](const CookieJar& jar) {
ASSERT_EQ(jar.get("key1").value, "value1");
});
addCookies("key2=value2; key3=value3; key4=; key5=foo=bar", [](const CookieJar& jar) {
ASSERT_EQ(jar.get("key2").value, "value2");
ASSERT_EQ(jar.get("key3").value, "value3");
ASSERT_EQ(jar.get("key4").value, "");
ASSERT_EQ(jar.get("key5").value, "foo=bar");
});
CookieJar jar;
ASSERT_THROW(jar.addFromRaw("key4", strlen("key4")), std::runtime_error);
}
#include "gtest/gtest.h"
#include <chrono>
#include <pistache/http_headers.h>
#include <pistache/date.h>
using namespace Pistache::Http;
......@@ -194,45 +196,30 @@ TEST(headers_test, connection) {
}
}
TEST(headers_test, date_test) {
/* RFC-1123 */
using namespace std::chrono;
FullDate::time_point expected_time_point = date::sys_days(date::year{1994}/11/6)
+ hours(8) + minutes(49) + seconds(37);
/* RFC-1123 */
Header::Date d1;
d1.parse("Sun, 06 Nov 1994 08:49:37 GMT");
auto fd1 = d1.fullDate();
auto dd1 = fd1.date();
ASSERT_EQ(dd1.tm_year, 94);
ASSERT_EQ(dd1.tm_mon, 10);
ASSERT_EQ(dd1.tm_mday, 6);
ASSERT_EQ(dd1.tm_hour, 8);
ASSERT_EQ(dd1.tm_min, 49);
ASSERT_EQ(dd1.tm_sec, 37);
auto dd1 = d1.fullDate().date();
ASSERT_EQ(expected_time_point, dd1);
/* RFC-850 */
Header::Date d2;
d2.parse("Sunday, 06-Nov-94 08:49:37 GMT");
auto fd2 = d2.fullDate();
auto dd2 = fd2.date();
ASSERT_EQ(dd2.tm_year, 94);
ASSERT_EQ(dd2.tm_mon, 10);
ASSERT_EQ(dd2.tm_mday, 6);
ASSERT_EQ(dd2.tm_hour, 8);
ASSERT_EQ(dd2.tm_min, 49);
ASSERT_EQ(dd2.tm_sec, 37);
auto dd2 = d2.fullDate().date();
ASSERT_EQ(dd2, expected_time_point);
/* ANSI C's asctime format */
Header::Date d3;
d3.parse("Sun Nov 6 08:49:37 1994");
auto fd3 = d3.fullDate();
auto dd3 = fd3.date();
ASSERT_EQ(dd3.tm_year, 94);
ASSERT_EQ(dd3.tm_mon, 10);
ASSERT_EQ(dd3.tm_mday, 6);
ASSERT_EQ(dd3.tm_hour, 8);
ASSERT_EQ(dd3.tm_min, 49);
ASSERT_EQ(dd3.tm_sec, 37);
auto dd3 = d3.fullDate().date();
ASSERT_EQ(dd3, expected_time_point);
}
......
......@@ -12,23 +12,25 @@
using namespace Pistache;
bool match(const Rest::Route& route, const std::string& req) {
return std::get<0>(route.match(req));
bool match(const Rest::FragmentTreeNode& routes, const std::string& req) {
std::shared_ptr<Rest::Route> route;
std::tie(route, std::ignore, std::ignore) = routes.findRoute({req.data(), req.size()});
return route != nullptr;
}
bool matchParams(
const Rest::Route& route, const std::string& req,
const Rest::FragmentTreeNode& routes, const std::string& req,
std::initializer_list<std::pair<std::string, std::string>> list)
{
bool ok;
std::shared_ptr<Rest::Route> route;
std::vector<Rest::TypedParam> params;
std::tie(ok, params, std::ignore) = route.match(req);
std::tie(route, params, std::ignore) = routes.findRoute({req.data(), req.size()});
if (!ok) return false;
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;
return param.name() == std::string_view(p.first.data(), p.first.size());
});
if (it == std::end(params)) {
std::cerr << "Did not find param '" << p.first << "'" << std::endl;
......@@ -45,14 +47,14 @@ bool matchParams(
}
bool matchSplat(
const Rest::Route& route, const std::string& req,
const Rest::FragmentTreeNode& routes, const std::string& req,
std::initializer_list<std::string> list)
{
bool ok;
std::shared_ptr<Rest::Route> route;
std::vector<Rest::TypedParam> splats;
std::tie(ok, std::ignore, splats) = route.match(req);
if (!ok) return false;
std::tie(route, std::ignore, splats) = routes.findRoute({req.data(), req.size()});
if (route == nullptr) return false;
if (list.size() != splats.size()) {
std::cerr << "Size mismatch (" << list.size() << " != " << splats.size() << ")"
......@@ -74,55 +76,57 @@ bool matchSplat(
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"));
Rest::FragmentTreeNode routes;
routes.addRoute(std::string_view("/v1/hello"), 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"));
routes.addRoute(std::string_view("/a/b/c"), 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", {
Rest::FragmentTreeNode routes;
routes.addRoute(std::string_view("/v1/hello/:name"), nullptr, nullptr);
ASSERT_TRUE(matchParams(routes, "/v1/hello/joe", {
{ ":name", "joe" }
}));
auto r2 = makeRoute("/greetings/:from/:to");
ASSERT_TRUE(matchParams(r2, "/greetings/foo/bar", {
routes.addRoute(std::string_view("/greetings/:from/:to"), 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", {
Rest::FragmentTreeNode routes;
routes.addRoute(std::string_view("/get/:key?"), nullptr, nullptr);
ASSERT_TRUE(match(routes, "/get"));
ASSERT_TRUE(match(routes, "/get/"));
ASSERT_TRUE(matchParams(routes, "/get/foo", {
{ ":key", "foo" }
}));
ASSERT_TRUE(matchParams(r1, "/get/foo/", {
ASSERT_TRUE(matchParams(routes, "/get/foo/", {
{ ":key", "foo" }
}));
ASSERT_FALSE(match(r1, "/get/foo/bar"));
ASSERT_FALSE(match(routes, "/get/foo/bar"));
}
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"));
Rest::FragmentTreeNode routes;
routes.addRoute(std::string_view("/say/*/to/*"), nullptr, nullptr);
ASSERT_TRUE(matchSplat(r1, "/say/hello/to/user", { "hello", "user" }));
ASSERT_TRUE(matchSplat(r1, "/say/hello/to/user/", { "hello", "user" }));
}
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" }));
}
\ 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