Unverified Commit 2cd8b9ef authored by mszy's avatar mszy Committed by GitHub

Merge branch 'master' into patch-1

parents 80a198a1 b6e13d3c
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")
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)
# 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)
if(COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
......@@ -97,3 +88,4 @@ if(PISTACHE_INSTALL)
lib/pkgconfig/
)
endif()
......@@ -52,6 +52,13 @@ 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.
Some other CMAKE defines:
| Option | Default | Description |
|---------------------------|-------------|-------------------------------------------------------------|
| PISTACHE_BUILD_EXAMPLES | False | Build all of the example apps |
| PISTACHE_BUILD_TESTS | False | Build all of the unit tests |
# Example
## Hello World (server)
......
/*
/*
Mathieu Stefani, 15 février 2016
Example of custom headers registering
*/
#include <pistache/net.h>
#include <pistache/http_headers.h>
#include <sys/types.h>
// Quiet a warning about "minor" and "major" being doubly defined.
#ifdef major
#undef major
#endif
#ifdef minor
#undef minor
#endif
using namespace Pistache;
using namespace Pistache::Http;
......@@ -15,8 +24,8 @@ public:
NAME("X-Protocol-Version");
XProtocolVersion()
: min(0)
, maj(0)
: maj(0)
, min(0)
{ }
XProtocolVersion(uint32_t major, uint32_t minor)
......@@ -54,8 +63,8 @@ public:
}
private:
uint32_t min;
uint32_t maj;
uint32_t min;
};
int main() {
......
/*
/*
Mathieu Stefani, 13 février 2016
Example of an hello world server
*/
......@@ -15,7 +15,8 @@ public:
HTTP_PROTOTYPE(HelloHandler)
void onRequest(const Http::Request& request, Http::ResponseWriter response) {
response.send(Http::Code::Ok, "Hello World");
UNUSED(request);
response.send(Pistache::Http::Code::Ok, "Hello World\n");
}
};
......
/* http_server.cc
Mathieu Stefani, 07 février 2016
Example of an http server
*/
......@@ -121,6 +121,16 @@ class MyHandler : public Http::Handler {
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") {
throw std::runtime_error("Exception thrown in the handler");
}
......@@ -129,7 +139,7 @@ class MyHandler : public Http::Handler {
}
else if (req.resource() == "/static") {
if (req.method() == Http::Method::Get) {
Http::serveFile(response, "README.md").then([](ssize_t bytes) {;
Http::serveFile(response, "README.md").then([](ssize_t bytes) {
std::cout << "Sent " << bytes << " bytes" << std::endl;
}, Async::NoExcept);
}
......@@ -140,6 +150,7 @@ class MyHandler : public Http::Handler {
}
void onTimeout(const Http::Request& req, Http::ResponseWriter response) {
UNUSED(req);
response
.send(Http::Code::Request_Timeout, "Timeout")
.then([=](ssize_t) { }, PrintException());
......@@ -160,7 +171,6 @@ int main(int argc, char *argv[]) {
}
Address addr(Ipv4::any(), port);
static constexpr size_t Workers = 4;
cout << "Cores = " << hardware_concurrency() << endl;
cout << "Using " << thr << " threads" << endl;
......
/* async.h
Mathieu Stefani, 05 novembre 2015
This header brings a Promise<T> class inspired by the Promises/A+
specification for asynchronous operations
*/
......@@ -17,6 +17,7 @@
#include <pistache/optional.h>
#include <pistache/typeid.h>
#include <pistache/common.h>
namespace Pistache {
namespace Async {
......@@ -46,6 +47,7 @@ namespace Async {
class BadAnyCast : public std::bad_cast {
public:
virtual const char* what() const noexcept { return "Bad any cast"; }
virtual ~BadAnyCast() { }
};
enum class State {
......@@ -163,6 +165,7 @@ namespace Async {
public:
virtual void resolve(const std::shared_ptr<Core>& core) = 0;
virtual void reject(const std::shared_ptr<Core>& core) = 0;
virtual ~Request() {}
};
struct Core {
......@@ -207,6 +210,7 @@ namespace Async {
state = State::Fulfilled;
}
virtual ~Core() { }
};
template<typename T>
......@@ -290,6 +294,8 @@ namespace Async {
virtual void doResolve(const std::shared_ptr<CoreT<T>>& core) = 0;
virtual void doReject(const std::shared_ptr<CoreT<T>>& core) = 0;
virtual ~Continuable() { }
size_t resolveCount_;
size_t rejectCount_;
std::shared_ptr<Core> chain_;
......@@ -387,6 +393,7 @@ namespace Async {
"Can not attach a non-void continuation to a void-Promise");
void doResolve(const std::shared_ptr<CoreT<void>>& core) {
UNUSED(core)
finishResolve(resolve_());
}
......@@ -464,6 +471,7 @@ namespace Async {
"Can not attach a non-void continuation to a void-Promise");
void doResolve(const std::shared_ptr<CoreT<void>>& core) {
UNUSED(core)
resolve_();
}
......@@ -571,6 +579,7 @@ namespace Async {
{ }
void doResolve(const std::shared_ptr<CoreT<void>>& core) {
UNUSED(core)
auto promise = resolve_();
finishResolve(promise);
}
......@@ -722,7 +731,7 @@ namespace Async {
Resolver& operator=(const Resolver& other) = delete;
Resolver(Resolver&& other) = default;
Resolver& operator=(Resolver&& other) = default;
Resolver& operator=(Resolver&& other) = default;
template<typename Arg>
bool operator()(Arg&& arg) const {
......@@ -855,7 +864,7 @@ namespace Async {
}
template<typename... Args>
void emplaceResolve(Args&& ...args) {
void emplaceResolve(Args&& ...) {
}
template<typename Exc>
......@@ -932,7 +941,7 @@ namespace Async {
-> decltype(std::declval<Func>()(Deferred<T>()), void()) {
func(Deferred<T>(std::move(resolver), std::move(rejection)));
}
};
}
template<typename T>
class Promise : public PromiseBase
......@@ -947,7 +956,7 @@ namespace Async {
: core_(std::make_shared<Core>())
, resolver_(core_)
, rejection_(core_)
{
{
details::callAsync<T>(func, resolver_, rejection_);
}
......@@ -957,8 +966,9 @@ namespace Async {
Promise(Promise<T>&& other) = default;
Promise& operator=(Promise<T>&& other) = default;
~Promise()
virtual ~Promise()
{
}
template<typename U>
......
/*
/*
Mathieu Stefani, 29 janvier 2016
The Http client
*/
......@@ -51,7 +51,7 @@ struct Connection : public std::enable_shared_from_this<Connection> {
: resolve(std::move(resolve))
, reject(std::move(reject))
, request(request)
, timeout(timeout)
, timeout(timeout)
, onDone(std::move(onDone))
{ }
Async::Resolver resolve;
......@@ -136,8 +136,8 @@ private:
Private::Parser<Http::Response> parser_;
};
struct ConnectionPool {
class ConnectionPool {
public:
void init(size_t maxConnsPerHost);
std::shared_ptr<Connection> pickConnection(const std::string& domain);
......@@ -165,6 +165,9 @@ public:
PROTOTYPE_OF(Aio::Handler, Transport)
Transport() {}
Transport(const Transport &rhs) { UNUSED(rhs); }
typedef std::function<void()> OnResponseParsed;
void onReady(const Aio::FdSet& fds);
......@@ -271,7 +274,8 @@ public:
}
RequestBuilder& cookie(const Cookie& cookie);
RequestBuilder& body(std::string val);
RequestBuilder& body(const std::string& val);
RequestBuilder& body(std::string&& val);
RequestBuilder& timeout(std::chrono::milliseconds value);
......
/* common.h
Mathieu Stefani, 12 August 2015
A collection of macro / utilities / constants
*/
......@@ -52,12 +52,16 @@
// Until we require C++17 compiler with [[maybe_unused]]
#define UNUSED(x) (void)(x);
// Allow compile-time overload
namespace Pistache {
namespace Const {
static constexpr int MaxBacklog = 128;
static constexpr int MaxEvents = 1024;
static constexpr int MaxBuffer = 4096;
static constexpr int ChunkSize = 1024;
static constexpr size_t MaxBacklog = 128;
static constexpr size_t MaxEvents = 1024;
static constexpr size_t MaxBuffer = 4096;
// Defined from CMakeLists.txt in project root
static constexpr size_t DefaultMaxPayload = 4096;
static constexpr size_t ChunkSize = 1024;
} // namespace Const
} // namespace Pistache
/*
/*
Mathieu Stefani, 22 janvier 2016
An Http endpoint
*/
......@@ -21,11 +21,13 @@ public:
Options& threads(int val);
Options& flags(Flags<Tcp::Options> flags);
Options& backlog(int val);
Options& maxPayload(size_t val);
private:
int threads_;
Flags<Tcp::Options> flags_;
int backlog_;
size_t maxPayload_;
Options();
};
Endpoint();
......@@ -67,7 +69,6 @@ private:
listener.setHandler(handler_);
if (listener.bind()) {
const auto& addr = listener.address();
CALL_MEMBER_FN(listener, method)();
}
#undef CALL_MEMBER_FN
......
......@@ -12,6 +12,7 @@
#include <vector>
#include <sstream>
#include <algorithm>
#include <memory>
#include <sys/timerfd.h>
......@@ -25,6 +26,7 @@
#include <pistache/peer.h>
#include <pistache/tcp.h>
#include <pistache/transport.h>
#include <pistache/view.h>
namespace Pistache {
namespace Http {
......@@ -40,7 +42,7 @@ namespace details {
static constexpr bool value =
std::is_same<decltype(test<P>(nullptr)), prototype_tag>::value;
};
};
}
#define HTTP_PROTOTYPE(Class) \
PROTOTYPE_OF(Pistache::Tcp::Handler, Class) \
......@@ -48,7 +50,7 @@ namespace details {
namespace Private {
class ParserBase;
template<typename T> struct Parser;
template<typename T> class Parser;
class RequestLineStep;
class ResponseLineStep;
class HeadersStep;
......@@ -100,7 +102,7 @@ namespace Uri {
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();
}
......@@ -114,7 +116,7 @@ namespace Uri {
// \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();
return params.end();
}
// \brief returns all parameters given in the query
......@@ -204,22 +206,22 @@ public:
Timeout(Timeout&& other)
: handler(other.handler)
, request(std::move(other.request))
, peer(std::move(other.peer))
, transport(other.transport)
, armed(other.armed)
, timerFd(other.timerFd)
, peer(std::move(other.peer))
{
other.timerFd = -1;
}
Timeout& operator=(Timeout&& other) {
handler = other.handler;
request = std::move(other.request);
peer = std::move(other.peer);
transport = other.transport;
request = std::move(other.request);
armed = other.armed;
timerFd = other.timerFd;
other.timerFd = -1;
peer = std::move(other.peer);
return *this;
}
......@@ -282,12 +284,10 @@ private:
Handler* handler;
Request request;
std::weak_ptr<Tcp::Peer> peer;
Tcp::Transport* transport;
bool armed;
Fd timerFd;
std::weak_ptr<Tcp::Peer> peer;
};
class ResponseStream : public Message {
......@@ -316,6 +316,14 @@ public:
friend
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 {
return headers_;
}
......@@ -549,7 +557,7 @@ public:
return &buf_;
}
DynamicStreamBuf *rdbuf([[maybe_unused]] DynamicStreamBuf* other) {
DynamicStreamBuf *rdbuf(DynamicStreamBuf* other) {
UNUSED(other)
throw std::domain_error("Unimplemented");
}
......@@ -613,7 +621,8 @@ namespace Private {
Message *message;
};
struct RequestLineStep : public Step {
class RequestLineStep : public Step {
public:
RequestLineStep(Request* request)
: Step(request)
{ }
......@@ -621,7 +630,8 @@ namespace Private {
State apply(StreamCursor& cursor);
};
struct ResponseLineStep : public Step {
class ResponseLineStep : public Step {
public:
ResponseLineStep(Response* response)
: Step(response)
{ }
......@@ -629,7 +639,8 @@ namespace Private {
State apply(StreamCursor& cursor);
};
struct HeadersStep : public Step {
class HeadersStep : public Step {
public:
HeadersStep(Message* request)
: Step(request)
{ }
......@@ -637,7 +648,8 @@ namespace Private {
State apply(StreamCursor& cursor);
};
struct BodyStep : public Step {
class BodyStep : public Step {
public:
BodyStep(Message* message_)
: Step(message_)
, chunk(message_)
......@@ -676,7 +688,8 @@ namespace Private {
size_t bytesRead;
};
struct ParserBase {
class ParserBase {
public:
ParserBase()
: cursor(&buffer)
, currentStep(0)
......@@ -697,9 +710,11 @@ namespace Private {
bool feed(const char* data, size_t len);
virtual void reset();
virtual ~ParserBase() { }
State parse();
ArrayStreamBuf<Const::MaxBuffer> buffer;
ArrayStreamBuf<char> buffer;
StreamCursor cursor;
protected:
......@@ -710,9 +725,12 @@ namespace Private {
};
template<typename Message> struct Parser;
template<typename Message> class Parser;
template<> class Parser<Http::Request> : public ParserBase {
public:
template<> struct Parser<Http::Request> : public ParserBase {
Parser()
: ParserBase()
{
......@@ -743,7 +761,8 @@ namespace Private {
Request request;
};
template<> struct Parser<Http::Response> : public ParserBase {
template<> class Parser<Http::Response> : public ParserBase {
public:
Parser()
: ParserBase()
{
......@@ -778,6 +797,8 @@ public:
virtual void onTimeout(const Request& request, ResponseWriter response);
virtual ~Handler() { }
private:
Private::Parser<Http::Request>& getParser(const std::shared_ptr<Tcp::Peer>& peer) const;
};
......@@ -790,5 +811,19 @@ std::shared_ptr<H> make_handler(Args&& ...args) {
return std::make_shared<H>(std::forward<Args>(args)...);
}
namespace helpers
{
inline Address httpAddr(const StringView& view) {
auto const str = view.toString();
auto const pos = str.find(':');
if (pos == std::string::npos) {
return Address(std::move(str), HTTP_STANDARD_PORT);
}
auto const host = str.substr(0, pos);
auto const port = std::stoi(str.substr(pos + 1));
return Address(std::move(host), port);
}
} // namespace helpers
} // namespace Http
} // namespace Pistache
/* http_defs.h
Mathieu Stefani, 01 September 2015
Various http definitions
*/
......@@ -118,8 +118,10 @@ namespace Http {
CHARSET(Utf32-LE, "utf-32le") \
CHARSET(Unicode-11, "unicode-1-1")
const uint16_t HTTP_STANDARD_PORT = 80;
enum class Method {
#define METHOD(m, _) m,
#define METHOD(m, _) m,
HTTP_METHODS
#undef METHOD
};
......@@ -165,10 +167,10 @@ private:
Directive directive_;
// Poor way of representing tagged unions in C++
union {
struct { uint64_t maxAge; };
struct { uint64_t sMaxAge; };
struct { uint64_t maxStale; };
struct { uint64_t minFresh; };
uint64_t maxAge;
uint64_t sMaxAge;
uint64_t maxStale;
uint64_t minFresh;
} data;
};
......
......@@ -186,6 +186,32 @@ private:
std::string uri_;
};
class AccessControlAllowHeaders : public Header {
public:
NAME("Access-Control-Allow-Headers")
AccessControlAllowHeaders() { }
explicit AccessControlAllowHeaders(const char* val)
: val_(val)
{ }
explicit AccessControlAllowHeaders(const std::string& val)
: val_(val)
{ }
void parse(const std::string& data);
void write(std::ostream& os) const;
void setUri(std::string val) {
val_ = std::move(val);
}
std::string val() const { return val_; }
private:
std::string val_;
};
class CacheControl : public Header {
public:
NAME("Cache-Control")
......
......@@ -104,6 +104,10 @@ public:
std::vector<std::shared_ptr<Header>> list() const;
const std::unordered_map<std::string, Raw>& rawList() const {
return rawHeaders;
}
bool remove(const std::string& name);
void clear();
......
......@@ -39,6 +39,7 @@ namespace Mime {
SUB_TYPE(OctetStream, "octet-stream") \
SUB_TYPE(Json , "json") \
SUB_TYPE(FormUrlEncoded, "x-www-form-urlencoded") \
SUB_TYPE(FormData, "form-data") \
\
SUB_TYPE(Png, "png") \
SUB_TYPE(Gif, "gif") \
......
/* net.h
Mathieu Stefani, 12 August 2015
Network utility classes
*/
......@@ -13,6 +13,8 @@
#include <sys/socket.h>
#include <pistache/common.h>
#ifndef _KERNEL_FASTOPEN
#define _KERNEL_FASTOPEN
......@@ -32,7 +34,11 @@ public:
bool isReserved() const;
bool isUsed() const;
std::string toString() const;
static constexpr uint16_t min() {
return std::numeric_limits<uint16_t>::min();
}
static constexpr uint16_t max() {
return std::numeric_limits<uint16_t>::max();
}
......@@ -69,13 +75,13 @@ public:
Address &operator=(const Address& other) = default;
Address &operator=(Address&& other) = default;
static Address fromUnix(struct sockaddr *addr);
static Address fromUnix(struct sockaddr *addr);
std::string host() const;
Port port() const;
private:
void init(std::string addr);
void init(const std::string& addr);
std::string host_;
Port port_;
};
......@@ -112,7 +118,8 @@ struct Size<const char*> {
template<size_t N>
struct Size<char[N]> {
constexpr size_t operator()(const char (&arr)[N]) const {
constexpr size_t operator()(const char (&)[N]) const {
// We omit the \0
return N - 1;
}
......
/* optional.h
Mathieu Stefani, 27 August 2015
An algebraic data type that can either represent Some Value or None.
This type is the equivalent of the Haskell's Maybe type
*/
......@@ -34,7 +34,7 @@ namespace types {
class None { };
namespace impl {
template<typename Func> struct callable_trait :
template<typename Func> struct callable_trait :
public callable_trait<decltype(&Func::operator())> { };
template<typename Class, typename Ret, typename... Args>
......@@ -54,7 +54,7 @@ namespace types {
};
}
template<typename Func,
template<typename Func,
bool IsBindExp = std::is_bind_expression<Func>::value>
struct callable_trait;
......@@ -87,7 +87,7 @@ inline types::None None() {
return types::None();
}
template<typename T,
template<typename T,
typename CleanT = typename std::remove_reference<T>::type>
inline types::Some<CleanT> Some(T &&value) {
return types::Some<CleanT>(std::forward<T>(value));
......@@ -112,14 +112,14 @@ public:
}
}
Optional(Optional<T> &&other)
noexcept(types::is_nothrow_move_constructible<T>::value)
Optional(Optional<T> &&other)
noexcept(types::is_nothrow_move_constructible<T>::value)
{
*this = std::move(other);
}
template<typename U>
Optional(types::Some<U> some) {
Optional(types::Some<U> some) {
static_assert(std::is_same<T, U>::value || std::is_convertible<U, T>::value,
"Types mismatch");
from_some_helper(std::move(some), types::is_move_constructible<U>());
......@@ -165,7 +165,7 @@ public:
}
Optional<T> &operator=(Optional<T> &&other)
Optional<T> &operator=(Optional<T> &&other)
noexcept(types::is_nothrow_move_constructible<T>::value)
{
if (other.data()) {
......@@ -284,7 +284,7 @@ namespace details {
template<typename T, typename Func>
void do_static_checks(std::false_type) {
static_assert(types::callable_trait<Func>::Arity == 1,
static_assert(types::callable_trait<Func>::Arity == 1,
"The function must take exactly 1 argument");
typedef typename types::callable_trait<Func>::template Arg<0>::Type ArgType;
......@@ -338,7 +338,7 @@ optionally_do(const Optional<T> &option, Func func) {
template<typename T, typename Func>
auto
optionally_map(const Optional<T> &option, Func func)
optionally_map(const Optional<T> &option, Func func)
-> Optional<typename types::callable_trait<Func>::ReturnType>
{
details::static_checks<T, Func>();
......@@ -351,7 +351,7 @@ optionally_map(const Optional<T> &option, Func func)
template<typename T, typename Func>
auto
optionally_fmap(const Optional<T> &option, Func func)
optionally_fmap(const Optional<T> &option, Func func)
-> Optional<typename details::RemoveOptional<typename types::callable_trait<Func>::ReturnType>::Type>
{
details::static_checks<T, Func>();
......@@ -369,7 +369,7 @@ template<typename T, typename Func>
Optional<T> optionally_filter(const Optional<T> &option, Func func) {
details::static_checks<T, Func>();
typedef typename types::callable_trait<Func>::ReturnType ReturnType;
static_assert(std::is_same<ReturnType, bool>::value ||
static_assert(std::is_same<ReturnType, bool>::value ||
std::is_convertible<ReturnType, bool>::value,
"The predicate must return a boolean value");
if (!option.isEmpty() && func(option.get())) {
......
/* os.h
Mathieu Stefani, 13 August 2015
Operating system specific functions
*/
......@@ -20,7 +20,7 @@ namespace Pistache {
typedef int Fd;
int hardware_concurrency();
uint hardware_concurrency();
bool make_non_blocking(int fd);
class CpuSet {
......@@ -65,7 +65,7 @@ enum class NotifyOn {
Shutdown = Read << 3
};
DECLARE_FLAGS_OPERATORS(NotifyOn);
DECLARE_FLAGS_OPERATORS(NotifyOn)
struct Tag {
friend class Epoll;
......
/* peer.h
Mathieu Stefani, 12 August 2015
A class representing a TCP Peer
*/
......@@ -58,10 +58,10 @@ private:
Transport* transport() const;
Transport* transport_;
Address addr;
std::string hostname_;
Fd fd_;
std::string hostname_;
std::unordered_map<std::string, std::shared_ptr<void>> data_;
};
......
......@@ -22,6 +22,6 @@ struct Prototype {
#define PROTOTYPE_OF(Base, Class) \
private: \
std::shared_ptr<Base> clone() const { \
return std::make_shared<Class>(); \
return std::make_shared<Class>(*this); \
} \
public:
/*
/*
Mathieu Stefani, 15 juin 2016
A lightweight implementation of the Reactor design-pattern.
The main goal of this component is to provide an solid abstraction
......@@ -151,7 +151,7 @@ public:
Polling::Mode mode = Polling::Mode::Level);
void modifyFd(
const Key& key, Fd fd, Polling::NotifyOn interest, Polling::Tag tag,
const Key& key, Fd fd, Polling::NotifyOn interest, Polling::Tag tag,
Polling::Mode mode = Polling::Mode::Level);
void runOnce();
......@@ -218,6 +218,8 @@ public:
return key_;
};
virtual ~Handler() { }
private:
Reactor* reactor_;
Context context_;
......
/* router.h
Mathieu Stefani, 05 janvier 2016
Simple HTTP Rest Router
*/
......@@ -8,15 +8,11 @@
#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 {
......@@ -24,8 +20,8 @@ class Description;
namespace details {
template<typename T> struct LexicalCast {
static T cast(const std::string_view& value) {
std::istringstream iss(std::string(value.data(), value.length()));
static T cast(const std::string& value) {
std::istringstream iss(value);
T out;
if (!(iss >> out))
throw std::runtime_error("Bad lexical cast");
......@@ -34,8 +30,8 @@ namespace details {
};
template<>
struct LexicalCast<std::string_view> {
static std::string_view cast(const std::string_view& value) {
struct LexicalCast<std::string> {
static std::string cast(const std::string& value) {
return value;
}
};
......@@ -43,7 +39,7 @@ namespace details {
class TypedParam {
public:
TypedParam(const std::string_view& name, const std::string_view& value)
TypedParam(const std::string& name, const std::string& value)
: name_(name)
, value_(value)
{ }
......@@ -53,88 +49,96 @@ public:
return details::LexicalCast<T>::cast(value_);
}
std::string_view name() const {
std::string name() const {
return name_;
}
private:
std::string_view name_;
std::string_view value_;
std::string name_;
std::string value_;
};
class Request;
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))
, handler_(std::move(handler))
, fragments_(Fragment::fromUrl(resource_))
{
UNUSED(method)
}
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>
void invokeHandler(Args &&...args) const {
void invokeHandler(Args&& ...args) const {
handler_(std::forward<Args>(args)...);
}
Handler handler_;
};
namespace Private {
class RouterHandler;
}
class FragmentTreeNode {
private:
enum class FragmentType {
Fixed, Param, Optional, Splat
};
struct Fragment {
explicit Fragment(std::string 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_;
bool match(const std::string& raw) const;
bool match(const Fragment& other) const;
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_;
bool isParameter() const;
bool isSplat() const;
bool isOptional() const;
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>>
findRoute(const std::string_view &path,
std::vector<TypedParam> &params,
std::vector<TypedParam> &splats) const;
static std::vector<Fragment> fromUrl(const std::string& url);
public:
FragmentTreeNode();
explicit FragmentTreeNode(const std::shared_ptr<char> &resourceReference);
private:
enum class Flag {
None = 0x0,
Fixed = 0x1,
Parameter = Fixed << 1,
Optional = Parameter << 1,
Splat = Optional << 1
};
void addRoute(const std::string_view &path, const Route::Handler &handler,
const std::shared_ptr<char> &resourceReference);
void init(std::string value);
bool removeRoute(const std::string_view &path);
void checkInvariant() const;
/**
* 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).
Flags<Flag> flags;
std::string value_;
};
std::string resource_;
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>>
findRoute(const std::string_view &path) const;
std::vector<Fragment> fragments_;
};
namespace Private {
class RouterHandler;
}
class Router {
public:
enum class Status { Match, NotFound };
static Router fromDescription(const Rest::Description& desc);
std::shared_ptr<Private::RouterHandler>
......@@ -148,18 +152,18 @@ 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;
Route::Status route(const Http::Request& request, Http::ResponseWriter response);
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, FragmentTreeNode> routes;
std::unordered_map<Http::Method, std::vector<Route>> routes;
std::vector<Route::Handler> customHandlers;
......@@ -178,7 +182,7 @@ namespace Private {
private:
std::shared_ptr<Tcp::Handler> clone() const {
return std::make_shared<RouterHandler>(router);
return std::make_shared<RouterHandler>(*this);
}
Rest::Router router;
......@@ -187,11 +191,10 @@ namespace Private {
class Request : public Http::Request {
public:
friend class FragmentTreeNode;
friend class Router;
bool hasParam(std::string name) const;
TypedParam param(std::string name) const;
bool hasParam(const std::string& name) const;
TypedParam param(const std::string& name) const;
TypedParam splatAt(size_t index) const;
std::vector<TypedParam> splat() const;
......@@ -215,7 +218,6 @@ 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);
......
......@@ -76,56 +76,50 @@ public:
};
template<size_t N, typename CharT = char>
// Make the buffer dynamic
template<typename CharT = char>
class ArrayStreamBuf : public StreamBuf<CharT> {
public:
typedef StreamBuf<CharT> Base;
static size_t maxSize;
ArrayStreamBuf()
: size(0)
{
memset(bytes, 0, N);
Base::setg(bytes, bytes, bytes + N);
bytes.clear();
Base::setg(bytes.data(), bytes.data(), bytes.data() + bytes.size());
}
template<size_t M>
ArrayStreamBuf(char (&arr)[M]) {
static_assert(M <= N, "Source array exceeds maximum capacity");
memcpy(bytes, arr, M);
size = M;
Base::setg(bytes, bytes, bytes + M);
bytes.clear();
std::copy(arr, arr + M, std::back_inserter(bytes));
Base::setg(bytes.data(), bytes.data(), bytes.data() + bytes.size());
}
bool feed(const char* data, size_t len) {
if (size + len > N) {
return false;
}
memcpy(bytes + size, data, len);
CharT *cur = nullptr;
if (this->gptr()) {
cur = this->gptr();
} else {
cur = bytes + size;
}
Base::setg(bytes, cur, bytes + size + len);
size += len;
if (bytes.size() + len > maxSize) { return false; }
// persist current offset
size_t readOffset = static_cast<size_t>(this->gptr() - this->eback());
std::copy(data, data + len, std::back_inserter(bytes));
Base::setg(bytes.data()
, bytes.data() + readOffset
, bytes.data() + bytes.size());
return true;
}
void reset() {
memset(bytes, 0, N);
size = 0;
Base::setg(bytes, bytes, bytes);
std::vector<CharT> nbytes;
bytes.swap(nbytes);
Base::setg(bytes.data(), bytes.data(), bytes.data() + bytes.size());
}
private:
char bytes[N];
size_t size;
std::vector<CharT> bytes;
};
template<typename CharT>
size_t ArrayStreamBuf<CharT>::maxSize = Const::DefaultMaxPayload;
struct Buffer {
Buffer()
: data(nullptr)
......
#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
......@@ -11,6 +11,7 @@
#include <pistache/flags.h>
#include <pistache/prototype.h>
#include <pistache/common.h>
namespace Pistache {
namespace Tcp {
......@@ -43,16 +44,16 @@ public:
virtual void onConnection(const std::shared_ptr<Tcp::Peer>& peer);
virtual void onDisconnection(const std::shared_ptr<Tcp::Peer>& peer);
private:
void associateTransport(Transport* transport);
Transport *transport_;
protected:
Transport *transport() {
if (!transport_)
throw std::logic_error("Orphaned handler");
return transport_;
}
private:
void associateTransport(Transport* transport);
Transport *transport_;
};
} // namespace Tcp
......
/*
/*
Mathieu Stefani, 26 janvier 2016
Transport TCP layer
*/
......
/* view.h
Mathieu Stefani, 19 janvier 2016
A non-owning range of contiguous memory that is represented by
a pointer to the beginning of the memory and the size.
*/
......@@ -242,7 +242,7 @@ namespace impl {
return StringView(str.data(), size);
}
};
};
}
template<typename Cont>
View<typename impl::ViewBuilder<Cont>::Type> make_view(const Cont& cont)
......
......@@ -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
......
/*
/*
Mathieu Stefani, 29 janvier 2016
Implementation of the Http client
*/
......@@ -10,6 +10,7 @@
#include <netdb.h>
#include <pistache/client.h>
#include <pistache/http.h>
#include <pistache/stream.h>
......@@ -19,20 +20,6 @@ using namespace Polling;
namespace Http {
namespace {
Address httpAddr(const StringView& view) {
auto str = view.toString();
auto pos = str.find(':');
if (pos == std::string::npos) {
return Address(std::move(str), 80);
}
auto host = str.substr(0, pos);
auto port = std::stoi(str.substr(pos + 1));
return Address(std::move(host), port);
}
}
static constexpr const char* UA = "pistache/0.1";
std::pair<StringView, StringView>
......@@ -254,7 +241,7 @@ Transport::asyncSendRequestImpl(
ssize_t totalWritten = 0;
for (;;) {
ssize_t bytesWritten = 0;
auto len = buffer.len - totalWritten;
ssize_t len = buffer.len - totalWritten;
auto ptr = buffer.data + totalWritten;
bytesWritten = ::send(fd, ptr, len, 0);
if (bytesWritten < 0) {
......@@ -355,8 +342,8 @@ Transport::handleIncoming(const std::shared_ptr<Connection>& connection) {
else {
totalBytes += bytes;
if (totalBytes > Const::MaxBuffer) {
std::cerr << "Too long packet" << std::endl;
if (static_cast<size_t>(totalBytes) > Const::MaxBuffer) {
std::cerr << "Client: Too long packet" << std::endl;
break;
}
}
......@@ -382,18 +369,11 @@ Connection::connect(Address addr)
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* Stream socket */
hints.ai_flags = 0;
hints.ai_protocol = 0;
auto host = addr.host();
/* We rely on the fact that a string literal is an lvalue const char[N] */
static constexpr size_t MaxPortLen = sizeof("65535");
char port[MaxPortLen];
std::fill(port, port + MaxPortLen, 0);
std::snprintf(port, MaxPortLen, "%d", static_cast<uint16_t>(addr.port()));
hints.ai_protocol = 0;
TRY(::getaddrinfo(host.c_str(), port, &hints, &addrs));
const auto& host = addr.host();
const auto& port = addr.port().toString();
TRY(::getaddrinfo(host.c_str(), port.c_str(), &hints, &addrs));
int sfd = -1;
......@@ -407,7 +387,7 @@ Connection::connect(Address addr)
fd = sfd;
transport_->asyncConnect(shared_from_this(), addr->ai_addr, addr->ai_addrlen)
.then([=]() {
.then([=]() {
socklen_t len = sizeof(saddr);
getsockname(sfd, (struct sockaddr *)&saddr, &len);
connectionState_ = Connected;
......@@ -476,7 +456,7 @@ Connection::handleResponsePacket(const char* buffer, size_t bytes) {
if (req.onDone)
req.onDone();
}
parser_.reset();
}
}
......@@ -600,6 +580,7 @@ Connection::performImpl(
transport_->asyncSendRequest(shared_from_this(), timer, buffer).then(
[=](ssize_t bytes) mutable {
UNUSED(bytes)
inflightRequests.push_back(RequestEntry(std::move(resolveMover), std::move(rejectMover), std::move(timer), std::move(onDone)));
},
[=](std::exception_ptr e) { rejectCloneMover.val(e); });
......@@ -695,11 +676,13 @@ ConnectionPool::idleConnections(const std::string& domain) const {
size_t
ConnectionPool::availableConnections(const std::string& domain) const {
UNUSED(domain)
return 0;
}
void
ConnectionPool::closeIdleConnections(const std::string& domain) {
UNUSED(domain)
}
RequestBuilder&
......@@ -734,14 +717,26 @@ RequestBuilder::cookie(const Cookie& cookie) {
}
RequestBuilder&
RequestBuilder::body(std::string val) {
RequestBuilder::body(const std::string& val) {
request_.body_ = val;
return *this;
}
RequestBuilder&
RequestBuilder::body(std::string&& val) {
request_.body_ = std::move(val);
return *this;
}
RequestBuilder&
RequestBuilder::timeout(std::chrono::milliseconds timeout) {
timeout_ = timeout;
return *this;
}
Async::Promise<Response>
RequestBuilder::send() {
return client_->doRequest(std::move(request_), timeout_);
return client_->doRequest(request_, timeout_);
}
Client::Options&
......@@ -864,7 +859,7 @@ Client::doRequest(
}
if (!conn->isConnected()) {
conn->connect(httpAddr(s.first));
conn->connect(helpers::httpAddr(s.first));
}
return conn->perform(request, timeout, [=]() {
......
/*
/*
Mathieu Stefani, 16 janvier 2016
Cookie implementation
*/
......@@ -65,6 +65,7 @@ namespace {
template<>
struct AttributeMatcher<bool> {
static void match(StreamCursor& cursor, Cookie* obj, bool Cookie::*attr) {
UNUSED(cursor)
obj->*attr = true;
}
};
......@@ -129,7 +130,6 @@ Cookie::fromRaw(const char* str, size_t len)
#define STR(str) str, sizeof(str) - 1
int c;
do {
skip_whitespaces(cursor);
......@@ -237,7 +237,7 @@ CookieJar::addFromRaw(const char *str, size_t len) {
cursor.advance(1);
skip_whitespaces(cursor);
}
}
}
Cookie
CookieJar::get(const std::string& name) const {
......
/* http.cc
Mathieu Stefani, 13 August 2015
Http layer implementation
*/
......@@ -402,7 +402,7 @@ namespace Private {
message->body_.reserve(size);
StreamCursor::Token chunkData(cursor);
const size_t available = cursor.remaining();
const ssize_t available = cursor.remaining();
if (available < size) {
cursor.advance(available);
......@@ -498,8 +498,8 @@ namespace Uri {
return Some(it->second);
}
std::string
std::string
Query::as_str() const {
std::string query_url;
for(const auto &e : params) {
......@@ -682,7 +682,7 @@ serveFile(ResponseWriter& response, const char* fileName, const Mime::MediaType&
if(errno == ENOENT) {
throw HttpError(Http::Code::Not_Found, std::move(str_error));
}
//eles if TODO
//eles if TODO
/* @Improvement: maybe could we check for errno here and emit a different error
message
*/
......@@ -741,6 +741,7 @@ serveFile(ResponseWriter& response, const char* fileName, const Mime::MediaType&
auto buffer = buf->buffer();
return transport->asyncWrite(sockFd, buffer, MSG_MORE).then([=](ssize_t bytes) {
UNUSED(bytes)
return transport->asyncWrite(sockFd, FileBuffer(fileName));
}, Async::Throw);
......@@ -788,14 +789,18 @@ Handler::onConnection(const std::shared_ptr<Tcp::Peer>& peer) {
void
Handler::onDisconnection(const shared_ptr<Tcp::Peer>& peer) {
UNUSED(peer)
}
void
Handler::onTimeout(const Request& request, ResponseWriter response) {
UNUSED(request)
UNUSED(response)
}
void
Timeout::onTimeout(uint64_t numWakeup) {
UNUSED(numWakeup)
if (!peer.lock()) return;
ResponseWriter response(transport, request, handler);
......
/* http_defs.cc
Mathieu Stefani, 01 September 2015
Implementation of http definitions
*/
......@@ -15,8 +15,8 @@ namespace Pistache {
namespace Http {
namespace {
using time_point = FullDate::time_point;
using time_point = FullDate::time_point;
bool parse_RFC_1123(const std::string& s, time_point &tp)
{
std::istringstream in{s};
......@@ -37,7 +37,7 @@ namespace {
in >> date::parse("%a %b %d %T %Y", tp);
return !in.fail();
}
} // anonymous namespace
CacheDirective::CacheDirective(Directive directive)
......@@ -62,9 +62,9 @@ CacheDirective::delta() const
return std::chrono::seconds(data.maxStale);
case MinFresh:
return std::chrono::seconds(data.minFresh);
default:
throw std::domain_error("Invalid operation on cache directive");
}
throw std::domain_error("Invalid operation on cache directive");
}
void
......@@ -84,12 +84,14 @@ CacheDirective::init(Directive directive, std::chrono::seconds delta)
case MinFresh:
data.minFresh = delta.count();
break;
default:
break;
}
}
FullDate
FullDate::fromString(const std::string& str) {
FullDate::time_point tp;
if(parse_RFC_1123(str, tp))
return FullDate(tp);
......@@ -97,7 +99,7 @@ FullDate::fromString(const std::string& str) {
return FullDate(tp);
else if(parse_asctime(str, tp))
return FullDate(tp);
throw std::runtime_error("Invalid Date format");
}
......
......@@ -51,6 +51,8 @@ Header::parseRaw(const char *str, size_t len) {
void
Allow::parseRaw(const char* str, size_t len) {
UNUSED(str)
UNUSED(len)
}
void
......@@ -176,7 +178,7 @@ void
CacheControl::write(std::ostream& os) const {
using Http::CacheDirective;
auto directiveString = [](CacheDirective directive) -> const char* const {
auto directiveString = [](CacheDirective directive) -> const char* {
switch (directive.directive()) {
case CacheDirective::NoCache:
return "no-cache";
......@@ -204,6 +206,8 @@ CacheControl::write(std::ostream& os) const {
return "s-maxage";
case CacheDirective::Ext:
return "";
default:
return "";
}
return "";
};
......@@ -308,6 +312,7 @@ Date::parse(const std::string &str) {
void
Date::write(std::ostream& os) const {
UNUSED(os)
}
void
......@@ -341,7 +346,7 @@ Host::parse(const std::string& data) {
port_ = p;
} else {
host_ = data;
port_ = 80;
port_ = HTTP_STANDARD_PORT;
}
}
......@@ -414,6 +419,7 @@ Accept::parseRaw(const char *str, size_t len) {
void
Accept::write(std::ostream& os) const {
UNUSED(os)
}
void
......@@ -426,6 +432,16 @@ AccessControlAllowOrigin::write(std::ostream& os) const {
os << uri_;
}
void
AccessControlAllowHeaders::parse(const std::string& data) {
val_ = data;
}
void
AccessControlAllowHeaders::write(std::ostream& os) const {
os << val_;
}
void
EncodingHeader::parseRaw(const char* str, size_t len) {
if (!strncasecmp(str, "gzip", len)) {
......@@ -470,6 +486,7 @@ Server::Server(const char* token)
void
Server::parse(const std::string& data)
{
UNUSED(data)
}
void
......
......@@ -26,6 +26,7 @@ namespace {
RegisterHeader(Accept);
RegisterHeader(AccessControlAllowOrigin);
RegisterHeader(AccessControlAllowHeaders);
RegisterHeader(Allow);
RegisterHeader(CacheControl);
RegisterHeader(Connection);
......
/* mime.cc
Mathieu Stefani, 29 August 2015
Implementaton of MIME Type parsing
*/
......@@ -110,7 +110,7 @@ MediaType::parseRaw(const char* str, size_t len) {
raw_ = string(str, len);
Mime::Type top;
Mime::Type top = Type::None;
// The reason we are using a do { } while (0); syntax construct here is to emulate
// if / else-if. Since we are using items-list macros to compare the strings,
......
......@@ -33,6 +33,11 @@ Port::isUsed() const {
return false;
}
std::string
Port::toString() const {
return std::to_string(port);
}
Ipv4::Ipv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
: a(a)
, b(b)
......@@ -104,22 +109,25 @@ Address::port() const {
}
void
Address::init(std::string addr) {
auto pos = addr.find(':');
Address::init(const std::string& addr) {
const auto pos = addr.find(':');
if (pos == std::string::npos)
throw std::invalid_argument("Invalid address");
std::string host = addr.substr(0, pos);
char *end;
host_ = addr.substr(0, pos);
if (host_ == "*") {
host_ = "0.0.0.0";
}
char *end;
const std::string portPart = addr.substr(pos + 1);
if (portPart.empty())
throw std::invalid_argument("Invalid port");
long port = strtol(portPart.c_str(), &end, 10);
if (*end != 0 || port > Port::max())
if (*end != 0 || port < Port::min() || port > Port::max())
throw std::invalid_argument("Invalid port");
host_ = std::move(host);
port_ = port;
port_ = static_cast<uint16_t>(port);
}
Error::Error(const char* message)
......
/* os.cc
Mathieu Stefani, 13 August 2015
*/
#include <fstream>
#include <iterator>
#include <algorithm>
#include <thread>
#include <unistd.h>
#include <fcntl.h>
......@@ -19,22 +20,15 @@ using namespace std;
namespace Pistache {
int hardware_concurrency() {
std::ifstream cpuinfo("/proc/cpuinfo");
if (cpuinfo) {
return std::count(std::istream_iterator<std::string>(cpuinfo),
std::istream_iterator<std::string>(),
std::string("processor"));
}
return sysconf(_SC_NPROCESSORS_ONLN);
uint hardware_concurrency() {
return std::thread::hardware_concurrency();
}
bool make_non_blocking(int sfd)
{
int flags = fcntl (sfd, F_GETFL, 0);
if (flags == -1) return false;
if (flags == -1) return false;
flags |= O_NONBLOCK;
int ret = fcntl (sfd, F_SETFL, flags);
......@@ -139,7 +133,7 @@ CpuSet::toPosix() const {
}
return cpu_set;
};
}
namespace Polling {
......
/* peer.cc
Mathieu Stefani, 12 August 2015
*/
#include <iostream>
......
/*
/*
Mathieu Stefani, 15 juin 2016
Implementation of the Reactor
*/
#include <pistache/reactor.h>
#include <array>
namespace Pistache {
namespace Aio {
struct Reactor::Impl {
class Reactor::Impl {
public:
Impl(Reactor* reactor)
: reactor_(reactor)
{ }
......@@ -55,8 +56,8 @@ struct Reactor::Impl {
/* Synchronous implementation of the reactor that polls in the context
* of the same thread
*/
struct SyncImpl : public Reactor::Impl {
class SyncImpl : public Reactor::Impl {
public:
SyncImpl(Reactor* reactor)
: Reactor::Impl(reactor)
, handlers_()
......@@ -269,7 +270,7 @@ private:
// to shift the value to retrieve the fd if there is only one handler as
// all the bits will already be set to 0.
auto encodedValue = (index << HandlerShift) | value;
return Polling::Tag(value);
return Polling::Tag(encodedValue);
}
static std::pair<size_t, uint64_t> decodeTag(const Polling::Tag& tag) {
......@@ -325,7 +326,8 @@ private:
* 32 bits to retrieve the index of the handler.
*/
struct AsyncImpl : public Reactor::Impl {
class AsyncImpl : public Reactor::Impl {
public:
static constexpr uint32_t KeyMarker = 0xBADB0B;
......
/* stream.cc
Mathieu Stefani, 05 September 2015
*/
#include <iostream>
......@@ -71,7 +71,7 @@ DynamicStreamBuf::reserve(size_t size)
bool
StreamCursor::advance(size_t count) {
if (count > buf->in_avail())
if (static_cast<ssize_t>(count) > buf->in_avail())
return false;
for (size_t i = 0; i < count; ++i) {
......
/* tcp.cc
Mathieu Stefani, 05 novembre 2015
TCP
*/
......@@ -12,7 +12,8 @@ namespace Tcp {
Handler::Handler()
: transport_(nullptr)
{ }
{
}
Handler::~Handler()
{ }
......@@ -24,10 +25,12 @@ Handler::associateTransport(Transport* transport) {
void
Handler::onConnection(const std::shared_ptr<Tcp::Peer>& peer) {
UNUSED(peer)
}
void
Handler::onDisconnection(const std::shared_ptr<Tcp::Peer>& peer) {
UNUSED(peer)
}
} // namespace Tcp
......
......@@ -74,7 +74,6 @@ Transport::onReady(const Aio::FdSet& fds) {
else if (entry.isReadable()) {
auto tag = entry.getTag();
auto val = tag.value();
if (isPeerFd(tag)) {
auto& peer = getPeer(tag);
handleIncoming(peer);
......@@ -150,11 +149,7 @@ Transport::handleIncoming(const std::shared_ptr<Peer>& peer) {
}
else {
totalBytes += bytes;
if (totalBytes >= Const::MaxBuffer) {
std::cerr << "Too long packet" << std::endl;
break;
}
handler_->onInput(buffer, bytes, peer);
}
}
}
......@@ -169,6 +164,7 @@ Transport::handlePeerDisconnection(const std::shared_ptr<Peer>& peer) {
throw std::runtime_error("Could not find peer to erase");
peers.erase(it);
toWrite.erase(fd);
close(fd);
}
......@@ -193,14 +189,14 @@ Transport::asyncWriteImpl(
toWrite.erase(fd);
};
ssize_t totalWritten = buffer.offset();
size_t totalWritten = buffer.offset();
for (;;) {
ssize_t bytesWritten = 0;
auto len = buffer.size() - totalWritten;
if (buffer.isRaw()) {
auto raw = buffer.raw();
auto ptr = raw.data + totalWritten;
bytesWritten = ::send(fd, ptr, len, flags);
bytesWritten = ::send(fd, ptr, len, flags | MSG_NOSIGNAL);
} else {
auto file = buffer.fd();
off_t offset = totalWritten;
......@@ -236,7 +232,9 @@ Transport::asyncWriteImpl(
::close(buffer.fd());
}
deferred.resolve(totalWritten);
// Cast to match the type of defered template
// to avoid a BadType exception
deferred.resolve(static_cast<ssize_t>(totalWritten));
break;
}
}
......
......@@ -34,6 +34,12 @@ Endpoint::Options::backlog(int val) {
return *this;
}
Endpoint::Options&
Endpoint::Options::maxPayload(size_t val) {
maxPayload_ = val;
return *this;
}
Endpoint::Endpoint()
{ }
......@@ -44,6 +50,7 @@ Endpoint::Endpoint(const Address& addr)
void
Endpoint::init(const Endpoint::Options& options) {
listener.init(options.threads_, options.flags_);
ArrayStreamBuf<char>::maxSize = options.maxPayload_;
}
void
......
/* listener.cc
Mathieu Stefani, 12 August 2015
*/
#include <iostream>
......@@ -8,7 +8,7 @@
#include <cstring>
#include <sys/socket.h>
#include <unistd.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
......@@ -18,6 +18,7 @@
#include <signal.h>
#include <sys/timerfd.h>
#include <sys/sendfile.h>
#include <cerrno>
#include <pistache/listener.h>
#include <pistache/peer.h>
......@@ -121,6 +122,8 @@ Listener::setHandler(const std::shared_ptr<Handler>& handler) {
void
Listener::pinWorker(size_t worker, const CpuSet& set)
{
UNUSED(worker)
UNUSED(set)
#if 0
if (ioGroup.empty()) {
throw std::domain_error("Invalid operation, did you call init() before ?");
......@@ -147,28 +150,19 @@ Listener::bind(const Address& address) {
struct addrinfo hints;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
hints.ai_protocol = 0;
auto host = addr_.host();
if (host == "*") {
host = "0.0.0.0";
}
/* We rely on the fact that a string literal is an lvalue const char[N] */
static constexpr size_t MaxPortLen = sizeof("65535");
char port[MaxPortLen];
std::fill(port, port + MaxPortLen, 0);
std::snprintf(port, MaxPortLen, "%d", static_cast<uint16_t>(addr_.port()));
const auto& host = addr_.host();
const auto& port = addr_.port().toString();
struct addrinfo *addrs;
TRY(::getaddrinfo(host.c_str(), port, &hints, &addrs));
TRY(::getaddrinfo(host.c_str(), port.c_str(), &hints, &addrs));
int fd = -1;
for (struct addrinfo *addr = addrs; addr; addr = addr->ai_next) {
addrinfo *addr;
for (addr = addrs; addr; addr = addr->ai_next) {
fd = ::socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (fd < 0) continue;
......@@ -182,6 +176,12 @@ Listener::bind(const Address& address) {
TRY(::listen(fd, backlog_));
break;
}
// At this point, it is still possible that we couldn't bind any socket. If it is the case, the previous
// loop would have exited naturally and addr will be null.
if (addr == nullptr) {
throw std::runtime_error(strerror(errno));
}
make_non_blocking(fd);
poller.addFd(fd, Polling::NotifyOn::Read, Polling::Tag(fd));
......@@ -198,7 +198,7 @@ Listener::bind(const Address& address) {
bool
Listener::isBound() const {
return g_listen_fd != -1;
return listen_fd != -1;
}
void
......@@ -220,7 +220,7 @@ Listener::run() {
else {
if (event.flags.hasFlag(Polling::NotifyOn::Read)) {
auto fd = event.tag.value();
if (fd == listen_fd)
if (static_cast<ssize_t>(fd) == listen_fd)
handleNewConnection();
}
}
......
This diff is collapsed.
......@@ -16,4 +16,7 @@ pistache_test(router_test)
pistache_test(cookie_test)
pistache_test(view_test)
pistache_test(http_parsing_test)
pistache_test(string_view_test)
pistache_test(http_client_test)
pistache_test(net_test)
pistache_test(listener_test)
pistache_test(payload_test)
......@@ -15,6 +15,7 @@ Async::Promise<int> doAsync(int N)
{
Async::Promise<int> promise(
[=](Async::Resolver& resolve, Async::Rejection& reject) {
UNUSED(reject)
std::thread thr([=](Async::Resolver resolve) mutable {
std::this_thread::sleep_for(std::chrono::seconds(1));
resolve(N * 2);
......@@ -31,6 +32,7 @@ Async::Promise<T> doAsyncTimed(std::chrono::seconds time, T val, Func func)
{
Async::Promise<T> promise(
[=](Async::Resolver& resolve, Async::Rejection& reject) {
UNUSED(reject)
std::thread thr([=](Async::Resolver resolve) mutable {
std::this_thread::sleep_for(time);
resolve(func(val));
......@@ -46,6 +48,7 @@ Async::Promise<T> doAsyncTimed(std::chrono::seconds time, T val, Func func)
TEST(async_test, basic_test) {
Async::Promise<int> p1(
[](Async::Resolver& resolv, Async::Rejection& reject) {
UNUSED(reject)
resolv(10);
});
......@@ -66,6 +69,7 @@ TEST(async_test, basic_test) {
Async::Promise<int> p3(
[](Async::Resolver& resolv, Async::Rejection& reject) {
UNUSED(resolv)
reject(std::runtime_error("Because I decided"));
});
......@@ -87,6 +91,7 @@ TEST(async_test, basic_test) {
TEST(async_test, error_test) {
Async::Promise<int> p1(
[](Async::Resolver& resolve, Async::Rejection& reject) {
UNUSED(reject)
ASSERT_THROW(resolve(10.5), Async::BadType);
});
}
......@@ -94,8 +99,9 @@ TEST(async_test, error_test) {
TEST(async_test, void_promise) {
Async::Promise<void> p1(
[](Async::Resolver& resolve, Async::Rejection& reject) {
UNUSED(reject)
resolve();
});
});
ASSERT_TRUE(p1.isFulfilled());
......@@ -108,11 +114,13 @@ TEST(async_test, void_promise) {
Async::Promise<int> p2(
[](Async::Resolver& resolve, Async::Rejection& reject) {
UNUSED(reject)
ASSERT_THROW(resolve(), Async::Error);
});
Async::Promise<void> p3(
[](Async::Resolver& resolve, Async::Rejection& reject) {
UNUSED(reject)
ASSERT_THROW(resolve(10), Async::Error);
});
}
......@@ -120,6 +128,7 @@ TEST(async_test, void_promise) {
TEST(async_test, chain_test) {
Async::Promise<int> p1(
[](Async::Resolver& resolve, Async::Rejection& reject) {
UNUSED(reject)
resolve(10);
});
......@@ -130,6 +139,7 @@ TEST(async_test, chain_test) {
Async::Promise<int> p2(
[](Async::Resolver& resolve, Async::Rejection& reject) {
UNUSED(reject)
resolve(10);
});
......@@ -142,13 +152,15 @@ TEST(async_test, chain_test) {
Async::Promise<Test> p3(
[](Async::Resolver& resolve, Async::Rejection& reject) {
UNUSED(reject)
resolve(Test::Foo);
});
p3
.then([](Test result) {
return Async::Promise<std::string>(
[=](Async::Resolver& resolve, Async::Rejection&) {
[=](Async::Resolver& resolve, Async::Rejection& reject) {
UNUSED(reject)
switch (result) {
case Test::Foo:
resolve(std::string("Foo"));
......@@ -163,6 +175,7 @@ TEST(async_test, chain_test) {
Async::Promise<Test> p4(
[](Async::Resolver& resolve, Async::Rejection& reject) {
UNUSED(reject)
resolve(Test::Bar);
});
......@@ -178,11 +191,12 @@ TEST(async_test, chain_test) {
case Test::Bar:
reject(std::runtime_error("Invalid"));
}
});
});
},
Async::NoExcept)
.then(
[](std::string str) {
UNUSED(str)
ASSERT_TRUE(false);
},
[](std::exception_ptr exc) {
......@@ -227,7 +241,7 @@ TEST(async_test, when_all) {
Async::whenAll(std::begin(vec), std::end(vec)).then([&](const std::vector<int>& results) {
resolved = true;
ASSERT_EQ(results.size(), 2);
ASSERT_EQ(results.size(), 2U);
ASSERT_EQ(results[0], 10);
ASSERT_EQ(results[1], 123);
},
......@@ -291,6 +305,7 @@ TEST(async_test, when_any) {
TEST(async_test, rethrow_test) {
auto p1 = Async::Promise<void>([](Async::Resolver& resolve, Async::Rejection& reject) {
UNUSED(resolve)
reject(std::runtime_error("Because"));
});
......@@ -375,10 +390,9 @@ private:
{
}
int seq;
Async::Resolver resolve;
Async::Rejection reject;
int seq;
};
std::atomic<bool> shutdown;
......@@ -417,6 +431,7 @@ TEST(async_test, stress_multithreaded_test) {
for (size_t i = 0; i < Ops; ++i) {
auto &wrk = workers[wrkIndex];
wrk->doWork(i).then([&](int seq) {
UNUSED(seq)
++resolved;
}, Async::NoExcept);
......
......@@ -119,7 +119,7 @@ TEST(cookie_test, write_test) {
c3.write(oss);
ASSERT_EQ(oss.str(), "lang=en-US; Secure; Scope=Private");
};
}
TEST(cookie_test, invalid_test) {
ASSERT_THROW(Cookie::fromString("lang"), std::runtime_error);
......
......@@ -12,7 +12,7 @@ TEST(headers_test, accept) {
{
const auto& media = a1.media();
ASSERT_EQ(media.size(), 1);
ASSERT_EQ(media.size(), 1U);
const auto& mime = media[0];
ASSERT_EQ(mime, MIME(Audio, Star));
......@@ -24,7 +24,7 @@ TEST(headers_test, accept) {
{
const auto& media = a2.media();
ASSERT_EQ(media.size(), 4);
ASSERT_EQ(media.size(), 4U);
const auto &m1 = media[0];
ASSERT_EQ(m1, MIME(Text, Star));
......@@ -44,7 +44,7 @@ TEST(headers_test, accept) {
{
const auto& media = a3.media();
ASSERT_EQ(media.size(), 5);
ASSERT_EQ(media.size(), 5U);
ASSERT_EQ(media[0], MIME(Text, Star));
ASSERT_EQ(media[0].q().getOrElse(Mime::Q(0)), Mime::Q(30));
......@@ -98,7 +98,7 @@ TEST(headers_test, cache_control) {
cc.parse(str);
auto directives = cc.directives();
ASSERT_EQ(directives.size(), 1);
ASSERT_EQ(directives.size(), 1U);
ASSERT_EQ(directives[0].directive(), expected);
};
......@@ -108,7 +108,7 @@ TEST(headers_test, cache_control) {
cc.parse(str);
auto directives = cc.directives();
ASSERT_EQ(directives.size(), 1);
ASSERT_EQ(directives.size(), 1U);
ASSERT_EQ(directives[0].directive(), expected);
ASSERT_EQ(directives[0].delta(), std::chrono::seconds(delta));
......@@ -128,7 +128,7 @@ TEST(headers_test, cache_control) {
Header::CacheControl cc1;
cc1.parse("private, max-age=600");
auto d1 = cc1.directives();
ASSERT_EQ(d1.size(), 2);
ASSERT_EQ(d1.size(), 2U);
ASSERT_EQ(d1[0].directive(), CacheDirective::Private);
ASSERT_EQ(d1[1].directive(), CacheDirective::MaxAge);
ASSERT_EQ(d1[1].delta(), std::chrono::seconds(600));
......@@ -136,7 +136,7 @@ TEST(headers_test, cache_control) {
Header::CacheControl cc2;
cc2.parse("public, s-maxage=200, proxy-revalidate");
auto d2 = cc2.directives();
ASSERT_EQ(d2.size(), 3);
ASSERT_EQ(d2.size(), 3U);
ASSERT_EQ(d2[0].directive(), CacheDirective::Public);
ASSERT_EQ(d2[1].directive(), CacheDirective::SMaxAge);
ASSERT_EQ(d2[1].delta(), std::chrono::seconds(200));
......@@ -167,7 +167,7 @@ TEST(headers_test, content_length) {
Header::ContentLength cl;
cl.parse("3495");
ASSERT_EQ(cl.value(), 3495);
ASSERT_EQ(cl.value(), 3495U);
}
TEST(headers_test, connection) {
......@@ -197,30 +197,43 @@ TEST(headers_test, connection) {
}
TEST(headers_test, date_test) {
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 */
/* RFC-1123 */
Header::Date d1;
d1.parse("Sun, 06 Nov 1994 08:49:37 GMT");
auto dd1 = d1.fullDate().date();
ASSERT_EQ(expected_time_point, dd1);
}
TEST(headers_test, date_test_rfc_850) {
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-850 */
Header::Date d2;
d2.parse("Sunday, 06-Nov-94 08:49:37 GMT");
auto dd2 = d2.fullDate().date();
ASSERT_EQ(dd2, expected_time_point);
}
TEST(headers_test, date_test_asctime) {
using namespace std::chrono;
FullDate::time_point expected_time_point = date::sys_days(date::year{1994}/11/6)
+ hours(8) + minutes(49) + seconds(37);
/* ANSI C's asctime format */
Header::Date d3;
d3.parse("Sun Nov 6 08:49:37 1994");
auto dd3 = d3.fullDate().date();
ASSERT_EQ(dd3, expected_time_point);
}
TEST(headers_test, host) {
......@@ -265,3 +278,11 @@ TEST(headers_test, access_control_allow_origin_test)
allowOrigin.parse("http://foo.bar");
ASSERT_EQ(allowOrigin.uri(), "http://foo.bar");
}
TEST(headers_test, access_control_allow_headers_test)
{
Header::AccessControlAllowHeaders allowHeaders;
allowHeaders.parse("Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
ASSERT_EQ(allowHeaders.val(), "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
}
#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)
{
UNUSED(request)
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
#include "gtest/gtest.h"
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <pistache/listener.h>
#include <pistache/http.h>
class SocketWrapper {
public:
explicit SocketWrapper(int fd): fd_(fd) {}
~SocketWrapper() { close(fd_);}
uint16_t port() {
sockaddr_in sin;
socklen_t len = sizeof(sin);
uint16_t port = 0;
if (getsockname(fd_, (struct sockaddr *)&sin, &len) == -1) {
perror("getsockname");
} else {
port = ntohs(sin.sin_port);
}
return port;
}
private:
int fd_;
};
// Just there for show.
class DummyHandler : public Pistache::Http::Handler {
public:
HTTP_PROTOTYPE(DummyHandler)
void onRequest(const Pistache::Http::Request& request, Pistache::Http::ResponseWriter response) override {
UNUSED(request);
response.send(Pistache::Http::Code::Ok, "I am a dummy handler\n");
}
};
/*
* Will try to get a free port by binding port 0.
*/
SocketWrapper bind_free_port() {
int sockfd; // listen on sock_fd, new connection on new_fd
addrinfo hints, *servinfo, *p;
int yes=1;
int rv;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP
if ((rv = getaddrinfo(nullptr, "0", &hints, &servinfo)) != 0) {
std::cerr << "getaddrinfo: " << gai_strerror(rv) << "\n";
exit(1);
}
// loop through all the results and bind to the first we can
for(p = servinfo; p != nullptr; p = p->ai_next) {
if ((sockfd = socket(p->ai_family, p->ai_socktype,
p->ai_protocol)) == -1) {
perror("server: socket");
continue;
}
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
sizeof(int)) == -1) {
perror("setsockopt");
exit(1);
}
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
close(sockfd);
perror("server: bind");
continue;
}
break;
}
freeaddrinfo(servinfo); // all done with this structure
if (p == nullptr) {
fprintf(stderr, "server: failed to bind\n");
exit(1);
}
return SocketWrapper(sockfd);
}
TEST(listener_test, listener_bind_port_free) {
uint16_t port_nb;
// This is just done to get the value of a free port. The socket will be closed
// after the closing curly bracket and the port will be free again (SO_REUSEADDR option).
// In theory, it is possible that some application grab this port before we bind it again...
{
SocketWrapper s = bind_free_port();
port_nb = s.port();
}
if (port_nb == 0) {
FAIL() << "Could not find a free port. Abord test.\n";
}
Pistache::Port port(port_nb);
Pistache::Address address(Pistache::Ipv4::any(), port);
Pistache::Tcp::Listener listener;
listener.setHandler(Pistache::Http::make_handler<DummyHandler>());
listener.bind(address);
ASSERT_TRUE(true);
}
TEST(listener_test, listener_bind_port_not_free_throw_runtime) {
SocketWrapper s = bind_free_port();
uint16_t port_nb = s.port();
if (port_nb == 0) {
FAIL() << "Could not find a free port. Abord test.\n";
}
Pistache::Port port(port_nb);
Pistache::Address address(Pistache::Ipv4::any(), port);
Pistache::Tcp::Listener listener;
listener.setHandler(Pistache::Http::make_handler<DummyHandler>());
try {
listener.bind(address);
FAIL() << "Expected std::runtime_error while binding, got nothing";
} catch (std::runtime_error const & err) {
std::cout << err.what() << std::endl;
ASSERT_STREQ("Address already in use", err.what());
} catch ( ... ) {
FAIL() << "Expected std::runtime_error";
}
}
#include "gtest/gtest.h"
#include <pistache/net.h>
#include <stdexcept>
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace Pistache;
TEST(net_test, port_creation)
{
Port port1(3000);
ASSERT_FALSE(port1.isReserved());
uint16_t value1 = port1;
ASSERT_EQ(value1, 3000);
ASSERT_EQ(port1.toString(), "3000");
Port port2(80);
ASSERT_TRUE(port2.isReserved());
uint16_t value2 = port2;
ASSERT_EQ(value2, 80);
ASSERT_EQ(port2.toString(), "80");
}
TEST(net_test, address_creation)
{
Address address1("127.0.0.1:8080");
ASSERT_EQ(address1.host(), "127.0.0.1");
ASSERT_EQ(address1.port(), 8080);
std::string addr = "127.0.0.1";
Address address2(addr, Port(8080));
ASSERT_EQ(address2.host(), "127.0.0.1");
ASSERT_EQ(address2.port(), 8080);
Address address3(Ipv4(127, 0, 0, 1), Port(8080));
ASSERT_EQ(address3.host(), "127.0.0.1");
ASSERT_EQ(address3.port(), 8080);
Address address4(Ipv4::any(), Port(8080));
ASSERT_EQ(address4.host(), "0.0.0.0");
ASSERT_EQ(address4.port(), 8080);
Address address5("*:8080");
ASSERT_EQ(address4.host(), "0.0.0.0");
ASSERT_EQ(address4.port(), 8080);
}
TEST(net_test, invalid_address)
{
ASSERT_THROW(Address("127.0.0.1"), std::invalid_argument);
ASSERT_THROW(Address("127.0.0.1:9999999"), std::invalid_argument);
ASSERT_THROW(Address("127.0.0.1:"), std::invalid_argument);
ASSERT_THROW(Address("127.0.0.1:-10"), std::invalid_argument);
}
\ No newline at end of file
#include "gtest/gtest.h"
#include <pistache/http.h>
#include <pistache/description.h>
#include <pistache/client.h>
#include <pistache/endpoint.h>
using namespace std;
using namespace Pistache;
struct TestSet {
TestSet()
: bytes(0)
, expectedCode(Http::Code::Ok)
, actualCode(Http::Code::Ok)
{ }
TestSet(size_t b, Http::Code c)
: bytes(b)
, expectedCode(c)
, actualCode(Http::Code::Ok)
{ }
size_t bytes;
Http::Code expectedCode;
Http::Code actualCode;
};
typedef std::vector<TestSet> PayloadTestSets;
static const uint16_t PORT = 9080;
void testPayloads(Http::Client & client, std::string url, PayloadTestSets & testPayloads) {
// Client tests to make sure the payload is enforced
std::mutex resultsetMutex;
PayloadTestSets test_results;
std::vector<Async::Promise<Http::Response> > responses;
for (auto & t : testPayloads) {
std::string payload(t.bytes, 'A');
auto response = client.post(url).body(payload).send();
response.then([t,&test_results,&resultsetMutex](Http::Response rsp) {
TestSet res(t);
res.actualCode = rsp.code();
{
std::unique_lock<std::mutex> lock(resultsetMutex);
test_results.push_back(res);
}
}, 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::milliseconds(500));
for (auto & result : test_results) {
ASSERT_EQ(result.expectedCode, result.actualCode);
}
}
void handleEcho(const Rest::Request&req, Http::ResponseWriter response) {
UNUSED(req);
response.send(Http::Code::Ok, "", MIME(Text, Plain));
}
TEST(payload, from_description)
{
Http::Client client;
auto client_opts = Http::Client::options()
.threads(3)
.maxConnectionsPerHost(3);
client.init(client_opts);
Address addr(Ipv4::any(), 9080);
const size_t threads = 20;
const size_t maxPayload = 1024; // very small
auto pid = fork();
if ( pid == 0) {
std::shared_ptr<Http::Endpoint> endpoint;
Rest::Description desc("Rest Description Test", "v1");
Rest::Router router;
desc
.route(desc.post("/"))
.bind(&handleEcho)
.response(Http::Code::Ok, "Response to the /ready call");
router.initFromDescription(desc);
auto flags = Tcp::Options::InstallSignalHandler | Tcp::Options::ReuseAddr;
auto opts = Http::Endpoint::options()
.threads(threads)
.flags(flags)
.maxPayload(maxPayload);
;
endpoint = std::make_shared<Pistache::Http::Endpoint>(addr);
endpoint->init(opts);
endpoint->setHandler(router.handler());
endpoint->serve();
endpoint->shutdown();
return;
}
std::this_thread::sleep_for(std::chrono::milliseconds(150));
PayloadTestSets payloads{
{800, Http::Code::Ok}
, {1024, Http::Code::Request_Entity_Too_Large}
,{2048, Http::Code::Request_Entity_Too_Large}
};
testPayloads(client, "127.0.0.1:" + std::to_string(PORT), payloads);
kill(pid, SIGTERM);
int r;
waitpid(pid, &r, 0);
client.shutdown();
}
TEST(payload, manual_construction) {
class MyHandler : public Http::Handler {
public:
HTTP_PROTOTYPE(MyHandler)
void onRequest(
const Http::Request& req,
Http::ResponseWriter response)
{
UNUSED(req);
response.send(Http::Code::Ok, "All good");
}
private:
tag placeholder;
};
Http::Client client;
auto client_opts = Http::Client::options()
.threads(3)
.maxConnectionsPerHost(3);
client.init(client_opts);
Port port(PORT);
Address addr(Ipv4::any(), port);
int threads = 20;
auto flags = Tcp::Options::InstallSignalHandler | Tcp::Options::ReuseAddr;
size_t maxPayload = 2048;
auto pid = fork();
if (pid == 0) {
auto endpoint = std::make_shared<Http::Endpoint>(addr);
auto opts = Http::Endpoint::options()
.threads(threads)
.flags(flags)
.maxPayload(maxPayload);
;
endpoint->init(opts);
endpoint->setHandler(Http::make_handler<MyHandler>());
endpoint->serve();
endpoint->shutdown();
return;
}
PayloadTestSets payloads{
{1024, Http::Code::Ok}
, {1800, Http::Code::Ok}
, {2048, Http::Code::Request_Entity_Too_Large}
, {4096, Http::Code::Request_Entity_Too_Large}
};
testPayloads(client, "127.0.0.1:" + std::to_string(PORT), payloads);
// Cleanup
kill(pid, SIGTERM);
int r;
waitpid(pid, &r, 0);
client.shutdown();
}
......@@ -12,25 +12,23 @@
using namespace Pistache;
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 match(const Rest::Route& route, const std::string& req) {
return std::get<0>(route.match(req));
}
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::shared_ptr<Rest::Route> route;
bool ok;
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) {
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)) {
std::cerr << "Did not find param '" << p.first << "'" << std::endl;
......@@ -47,14 +45,14 @@ bool matchParams(
}
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::shared_ptr<Rest::Route> route;
bool ok;
std::vector<Rest::TypedParam> splats;
std::tie(route, std::ignore, splats) = routes.findRoute({req.data(), req.size()});
if (route == nullptr) return false;
std::tie(ok, std::ignore, splats) = route.match(req);
if (!ok) return false;
if (list.size() != splats.size()) {
std::cerr << "Size mismatch (" << list.size() << " != " << splats.size() << ")"
......@@ -76,57 +74,55 @@ bool matchSplat(
return true;
}
TEST(router_test, test_fixed_routes) {
Rest::FragmentTreeNode routes;
routes.addRoute(std::string_view("/v1/hello"), nullptr, nullptr);
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);
}
ASSERT_TRUE(match(routes, "/v1/hello"));
ASSERT_FALSE(match(routes, "/v2/hello"));
ASSERT_FALSE(match(routes, "/v1/hell0"));
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"));
routes.addRoute(std::string_view("/a/b/c"), nullptr, nullptr);
ASSERT_TRUE(match(routes, "/a/b/c"));
auto r2 = makeRoute("/a/b/c");
ASSERT_TRUE(match(r2, "/a/b/c"));
}
TEST(router_test, test_parameters) {
Rest::FragmentTreeNode routes;
routes.addRoute(std::string_view("/v1/hello/:name"), nullptr, nullptr);
ASSERT_TRUE(matchParams(routes, "/v1/hello/joe", {
auto r1 = makeRoute("/v1/hello/:name");
ASSERT_TRUE(matchParams(r1, "/v1/hello/joe", {
{ ":name", "joe" }
}));
routes.addRoute(std::string_view("/greetings/:from/:to"), nullptr, nullptr);
ASSERT_TRUE(matchParams(routes, "/greetings/foo/bar", {
auto r2 = makeRoute("/greetings/:from/:to");
ASSERT_TRUE(matchParams(r2, "/greetings/foo/bar", {
{ ":from", "foo" },
{ ":to" , "bar" }
}));
}
TEST(router_test, test_optional) {
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", {
auto r1 = makeRoute("/get/:key?");
ASSERT_TRUE(match(r1, "/get"));
ASSERT_TRUE(match(r1, "/get/"));
ASSERT_TRUE(matchParams(r1, "/get/foo", {
{ ":key", "foo" }
}));
ASSERT_TRUE(matchParams(routes, "/get/foo/", {
ASSERT_TRUE(matchParams(r1, "/get/foo/", {
{ ":key", "foo" }
}));
ASSERT_FALSE(match(routes, "/get/foo/bar"));
ASSERT_FALSE(match(r1, "/get/foo/bar"));
}
TEST(router_test, test_splat) {
Rest::FragmentTreeNode routes;
routes.addRoute(std::string_view("/say/*/to/*"), nullptr, nullptr);
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"));
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
ASSERT_TRUE(matchSplat(r1, "/say/hello/to/user", { "hello", "user" }));
ASSERT_TRUE(matchSplat(r1, "/say/hello/to/user/", { "hello", "user" }));
}
#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());
}
......@@ -6,23 +6,23 @@ using namespace Pistache;
template<typename T>
std::vector<T> make_vec(std::initializer_list<T> list) {
return std::vector<T>(list);
}
}
TEST(view_test, test_vector) {
auto vec1 = make_vec({1, 2, 3, 4});
auto v1 = make_view(vec1);
ASSERT_EQ(v1.size(), 4);
ASSERT_EQ(v1.size(), 4U);
ASSERT_EQ(v1[0], 1);
ASSERT_EQ(v1[3], 4);
auto v2(v1);
ASSERT_EQ(v2.size(), 4);
ASSERT_EQ(v2.size(), 4U);
ASSERT_EQ(v2[0], 1);
ASSERT_EQ(v2[3], 4);
auto vec2 = make_vec({2, 4, 6, 8, 10});
auto v3 = make_view(vec2, 4);
ASSERT_EQ(v3.size(), 4);
ASSERT_EQ(v3.size(), 4U);
ASSERT_EQ(v3[0], 2);
ASSERT_EQ(v3[3], 8);
ASSERT_THROW(v3.at(4), std::invalid_argument);
......@@ -43,22 +43,22 @@ TEST(view_test, test_vector) {
}
TEST(view_test, test_array) {
std::array<int, 4> arr1 { 4, 5, 6, 7 };
std::array<int, 4> arr1 {{ 4, 5, 6, 7 }};
auto v1 = make_view(arr1);
ASSERT_EQ(v1.size(), 4);
ASSERT_EQ(v1.size(), 4U);
ASSERT_EQ(v1[0], 4);
ASSERT_EQ(v1[3], 7);
auto v2 = make_view(arr1, 2);
ASSERT_EQ(v2.size(), 2);
ASSERT_EQ(v2.size(), 2U);
ASSERT_EQ(v2[1], 5);
ASSERT_THROW(v2.at(3), std::invalid_argument);
std::array<int, 4> arr2 { 6, 8, 1, 2 };
std::array<int, 4> arr2 {{ 6, 8, 1, 2 }};
ASSERT_NE(make_view(arr2), v1);
std::array<int, 4> arr3 { 4, 5, 6, 7 };
std::array<int, 4> arr3 {{ 4, 5, 6, 7 }};
ASSERT_EQ(v1, make_view(arr3));
}
......@@ -66,7 +66,7 @@ TEST(view_test, string_test) {
std::string s1("Hello");
auto v1 = make_view(s1);
ASSERT_EQ(v1.size(), 5);
ASSERT_EQ(v1.size(), 5U);
ASSERT_EQ(v1[0], 'H');
ASSERT_EQ(v1[4], 'o');
ASSERT_EQ(v1, "Hello");
......
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