Commit 0d199c80 authored by Mathieu Stefani's avatar Mathieu Stefani

Add a special TransportImpl for http Endpoint that periodically checks and...

Add a special TransportImpl for http Endpoint that periodically checks and disconnects idle peers with an HTTP 408 response code
parent 29ff10ff
......@@ -4,6 +4,8 @@
#include <cstdint>
#include <limits>
#include <chrono>
// Allow compile-time overload
namespace Pistache {
namespace Const {
......@@ -18,6 +20,8 @@ static constexpr size_t DefaultTimerPoolSize = 128;
static constexpr size_t DefaultMaxRequestSize = 4096;
static constexpr size_t DefaultMaxResponseSize =
std::numeric_limits<uint32_t>::max();
static constexpr auto DefaultHeaderTimeout = std::chrono::seconds(60);
static constexpr auto DefaultBodyTimeout = std::chrono::seconds(60);
static constexpr size_t ChunkSize = 1024;
static constexpr uint16_t HTTP_STANDARD_PORT = 80;
......
......@@ -8,8 +8,11 @@
#include <pistache/http.h>
#include <pistache/listener.h>
#include <pistache/transport.h>
#include <pistache/net.h>
#include <chrono>
namespace Pistache {
namespace Http {
......@@ -20,26 +23,55 @@ public:
Options &threads(int val);
Options &threadsName(const std::string &val);
Options &flags(Flags<Tcp::Options> flags);
Options &flags(Tcp::Options tcp_opts) {
flags(Flags<Tcp::Options>(tcp_opts));
return *this;
}
Options &backlog(int val);
Options &maxRequestSize(size_t val);
Options &maxResponseSize(size_t val);
template<typename Duration>
Options &headerTimeout(Duration timeout)
{
headerTimeout_ = std::chrono::duration_cast<std::chrono::milliseconds>(timeout);
return *this;
}
template<typename Duration>
Options& bodyTimeout(Duration timeout)
{
bodyTimeout_ = std::chrono::duration_cast<std::chrono::milliseconds>(timeout);
return *this;
}
Options &logger(PISTACHE_STRING_LOGGER_T logger);
[[deprecated("Replaced by maxRequestSize(val)")]] Options &
maxPayload(size_t val);
private:
// Thread options
int threads_;
std::string threadsName_;
// TCP flags
Flags<Tcp::Options> flags_;
// Backlog size
int backlog_;
// Size options
size_t maxRequestSize_;
size_t maxResponseSize_;
// Timeout options
std::chrono::milliseconds headerTimeout_;
std::chrono::milliseconds bodyTimeout_;
PISTACHE_STRING_LOGGER_T logger_;
Options();
};
......@@ -151,8 +183,8 @@ private:
std::shared_ptr<Handler> handler_;
Tcp::Listener listener;
size_t maxRequestSize_ = Const::DefaultMaxRequestSize;
size_t maxResponseSize_ = Const::DefaultMaxResponseSize;
Options options_;
PISTACHE_STRING_LOGGER_T logger_ = PISTACHE_NULL_STRING_LOGGER;
};
......
......@@ -8,6 +8,7 @@
#include <algorithm>
#include <array>
#include <chrono>
#include <memory>
#include <sstream>
#include <stdexcept>
......@@ -21,6 +22,7 @@
#include <pistache/cookie.h>
#include <pistache/http_defs.h>
#include <pistache/http_headers.h>
#include <pistache/meta.h>
#include <pistache/mime.h>
#include <pistache/net.h>
#include <pistache/stream.h>
......@@ -353,6 +355,10 @@ public:
friend class Private::ResponseLineStep;
ResponseWriter(Tcp::Transport *transport, Request request, Handler *handler,
std::weak_ptr<Tcp::Peer> peer);
//
// C++11: std::weak_ptr move constructor is C++14 only so the default
// version of move constructor / assignement operator does not work and we
......@@ -416,9 +422,6 @@ public:
ResponseWriter clone() const;
private:
ResponseWriter(Tcp::Transport *transport, Request request, Handler *handler,
std::weak_ptr<Tcp::Peer> peer);
ResponseWriter(const ResponseWriter &other);
Async::Promise<ssize_t> sendImpl(Code code, const char *data,
......@@ -442,12 +445,14 @@ serveFile(ResponseWriter &writer, const std::string &fileName,
namespace Private {
enum class State { Again, Next, Done };
using StepId = uint64_t;
struct Step {
explicit Step(Message *request);
virtual ~Step() = default;
virtual StepId id() const = 0;
virtual State apply(StreamCursor &cursor) = 0;
static void raise(const char *msg, Code code = Code::Bad_Request);
......@@ -458,30 +463,42 @@ protected:
class RequestLineStep : public Step {
public:
static constexpr StepId Id = Meta::Hash::fnv1a("RequestLine");
explicit RequestLineStep(Request *request) : Step(request) {}
StepId id() const override { return Id; }
State apply(StreamCursor &cursor) override;
};
class ResponseLineStep : public Step {
public:
static constexpr StepId Id = Meta::Hash::fnv1a("ResponseLine");
explicit ResponseLineStep(Response *response) : Step(response) {}
StepId id() const override { return Id; }
State apply(StreamCursor &cursor) override;
};
class HeadersStep : public Step {
public:
static constexpr StepId Id = Meta::Hash::fnv1a("Headers");
explicit HeadersStep(Message *request) : Step(request) {}
StepId id() const override { return Id; }
State apply(StreamCursor &cursor) override;
};
class BodyStep : public Step {
public:
static constexpr auto Id = Meta::Hash::fnv1a("Headers");
explicit BodyStep(Message *message_)
: Step(message_), chunk(message_), bytesRead(0) {}
StepId id() const override { return Id; }
State apply(StreamCursor &cursor) override;
private:
......@@ -517,6 +534,8 @@ private:
class ParserBase {
public:
static constexpr size_t StepsCount = 3;
explicit ParserBase(size_t maxDataSize);
ParserBase(const ParserBase &) = delete;
......@@ -530,12 +549,18 @@ public:
virtual void reset();
State parse();
protected:
static constexpr size_t StepsCount = 3;
Step* step();
std::chrono::steady_clock::time_point time() const
{
return time_;
}
protected:
std::array<std::unique_ptr<Step>, StepsCount> allSteps;
size_t currentStep = 0;
std::chrono::steady_clock::time_point time_;
private:
ArrayStreamBuf<char> buffer;
StreamCursor cursor;
......@@ -544,7 +569,6 @@ private:
template <typename Message> class ParserImpl;
template <> class ParserImpl<Http::Request> : public ParserBase {
public:
explicit ParserImpl(size_t maxDataSize);
......@@ -577,6 +601,30 @@ public:
void setMaxResponseSize(size_t value);
size_t getMaxResponseSize() const;
template<typename Duration>
void setHeaderTimeout(Duration timeout)
{
headerTimeout_ = std::chrono::duration_cast<std::chrono::milliseconds>(timeout);
}
template<typename Duration>
void setBodyTimeout(Duration timeout)
{
bodyTimeout_ = std::chrono::duration_cast<std::chrono::milliseconds>(timeout);
}
std::chrono::milliseconds getHeaderTimeout() const
{
return headerTimeout_;
}
std::chrono::milliseconds getBodyTimeout() const
{
return bodyTimeout_;
}
static RequestParser &getParser(const std::shared_ptr<Tcp::Peer> &peer);
virtual ~Handler() override {}
private:
......@@ -584,11 +632,12 @@ private:
void onDisconnection(const std::shared_ptr<Tcp::Peer> &peer) override;
void onInput(const char *buffer, size_t len,
const std::shared_ptr<Tcp::Peer> &peer) override;
RequestParser &getParser(const std::shared_ptr<Tcp::Peer> &peer) const;
private:
size_t maxRequestSize_ = Const::DefaultMaxRequestSize;
size_t maxResponseSize_ = Const::DefaultMaxResponseSize;
std::chrono::milliseconds headerTimeout_ = Const::DefaultHeaderTimeout;
std::chrono::milliseconds bodyTimeout_ = Const::DefaultBodyTimeout;
};
template <typename H, typename... Args>
......
......@@ -45,7 +45,9 @@ public:
TimePoint tick;
};
Listener() = default;
using TransportFactory = std::function<std::shared_ptr<Transport> ()>;
Listener();
~Listener();
explicit Listener(const Address &address);
......@@ -54,6 +56,8 @@ public:
const std::string &workersName = "",
int backlog = Const::MaxBacklog,
PISTACHE_STRING_LOGGER_T logger = PISTACHE_NULL_STRING_LOGGER);
void setTransportFactory(TransportFactory factory);
void setHandler(const std::shared_ptr<Handler> &handler);
void bind();
......@@ -96,6 +100,10 @@ private:
Aio::Reactor reactor_;
Aio::Reactor::Key transportKey;
TransportFactory transportFactory_;
TransportFactory defaultTransportFactory() const;
void handleNewConnection();
int acceptConnection(struct sockaddr_in &peer_addr) const;
void dispatchPeer(const std::shared_ptr<Peer> &peer);
......
......@@ -43,9 +43,9 @@ public:
void *ssl() const;
void putData(std::string name, std::shared_ptr<Http::Parser> data);
std::shared_ptr<Http::Parser> getData(std::string name) const;
std::shared_ptr<Http::Parser> tryGetData(std::string name) const;
void putData(std::string name, std::shared_ptr<void> data);
std::shared_ptr<void> getData(std::string name) const;
std::shared_ptr<void> tryGetData(std::string name) const;
Async::Promise<ssize_t> send(const RawBuffer &buffer, int flags = 0);
......@@ -61,7 +61,7 @@ private:
Address addr;
std::string hostname_;
std::unordered_map<std::string, std::shared_ptr<Http::Parser>> data_;
std::unordered_map<std::string, std::shared_ptr<void>> data_;
void *ssl_ = nullptr;
};
......
......@@ -13,6 +13,7 @@ namespace Pistache {
/* In a sense, a Prototype is just a class that provides a clone() method */
template <typename Class> struct Prototype {
public:
virtual ~Prototype() {}
virtual std::shared_ptr<Class> clone() const = 0;
};
......@@ -20,9 +21,7 @@ template <typename Class> struct Prototype {
} // namespace Pistache
#define PROTOTYPE_OF(Base, Class) \
private: \
public: \
std::shared_ptr<Base> clone() const override { \
return std::make_shared<Class>(*this); \
} \
\
public:
......@@ -31,7 +31,7 @@ enum class Options : uint64_t {
DECLARE_FLAGS_OPERATORS(Options)
class Handler : private Prototype<Handler> {
class Handler : public Prototype<Handler> {
public:
friend class Transport;
......
......@@ -27,15 +27,16 @@ class Handler;
class Transport : public Aio::Handler {
public:
explicit Transport(const std::shared_ptr<Tcp::Handler> &handler);
Transport(const Transport &) = delete;
Transport &operator=(const Transport &) = delete;
void init(const std::shared_ptr<Tcp::Handler> &handler);
void registerPoller(Polling::Epoll &poller) override;
virtual void registerPoller(Polling::Epoll &poller) override;
void handleNewPeer(const std::shared_ptr<Peer> &peer);
void onReady(const Aio::FdSet &fds) override;
virtual void onReady(const Aio::FdSet &fds) override;
template <typename Buf>
Async::Promise<ssize_t> asyncWrite(Fd fd, const Buf &buffer, int flags = 0) {
......@@ -167,13 +168,17 @@ private:
std::unordered_map<Fd, TimerEntry> timers;
PollableQueue<PeerEntry> peersQueue;
std::unordered_map<Fd, std::shared_ptr<Peer>> peers;
Async::Deferred<rusage> loadRequest_;
NotifyFd notifier;
std::shared_ptr<Tcp::Handler> handler_;
protected:
void removePeer(const std::shared_ptr<Peer>& peer);
std::unordered_map<Fd, std::shared_ptr<Peer>> peers;
private:
bool isPeerFd(Fd fd) const;
bool isTimerFd(Fd fd) const;
bool isPeerFd(Polling::Tag tag) const;
......
......@@ -247,7 +247,7 @@ State ResponseLineStep::apply(StreamCursor &cursor) {
char *end;
auto code = strtol(codeToken.rawText(), &end, 10);
if (*end != ' ')
raise("Failed to parsed return code");
raise("Failed to parse return code");
response->code_ = static_cast<Http::Code>(code);
if (!cursor.advance(1))
......@@ -484,6 +484,7 @@ State ParserBase::parse() {
}
bool ParserBase::feed(const char *data, size_t len) {
time_ = std::chrono::steady_clock::now();
return buffer.feed(data, len);
}
......@@ -492,6 +493,12 @@ void ParserBase::reset() {
cursor.reset();
currentStep = 0;
time_ = std::chrono::steady_clock::time_point(std::chrono::steady_clock::duration(0));
}
Step* ParserBase::step()
{
return allSteps[currentStep].get();
}
} // namespace Private
......@@ -966,8 +973,11 @@ void Handler::onConnection(const std::shared_ptr<Tcp::Peer> &peer) {
void Handler::onDisconnection(const std::shared_ptr<Tcp::Peer> & /*peer*/) {}
void Handler::onTimeout(const Request & /*request*/,
ResponseWriter /*response*/) {}
void Handler::onTimeout(const Request& /*request*/,
ResponseWriter response)
{
response.send(Code::Request_Timeout);
}
Timeout::~Timeout() { disarm(); }
......@@ -990,7 +1000,6 @@ void Timeout::onTimeout(uint64_t numWakeup) {
return;
ResponseWriter response(transport, request, handler, peer);
handler->onTimeout(request, std::move(response));
}
......@@ -1003,8 +1012,8 @@ void Handler::setMaxResponseSize(size_t value) { maxResponseSize_ = value; }
size_t Handler::getMaxResponseSize() const { return maxResponseSize_; }
RequestParser &
Handler::getParser(const std::shared_ptr<Tcp::Peer> &peer) const {
return static_cast<RequestParser &>(*peer->getData(ParserData));
Handler::getParser(const std::shared_ptr<Tcp::Peer> &peer) {
return *std::static_pointer_cast<RequestParser>(peer->getData(ParserData));
}
} // namespace Http
......
......@@ -75,7 +75,7 @@ int Peer::fd() const {
return fd_;
}
void Peer::putData(std::string name, std::shared_ptr<Http::Parser> data) {
void Peer::putData(std::string name, std::shared_ptr<void> data) {
auto it = data_.find(name);
if (it != std::end(data_)) {
throw std::runtime_error("The data already exists");
......@@ -84,7 +84,7 @@ void Peer::putData(std::string name, std::shared_ptr<Http::Parser> data) {
data_.insert(std::make_pair(std::move(name), std::move(data)));
}
std::shared_ptr<Http::Parser> Peer::getData(std::string name) const {
std::shared_ptr<void> Peer::getData(std::string name) const {
auto data = tryGetData(std::move(name));
if (data == nullptr) {
throw std::runtime_error("The data does not exist");
......@@ -93,7 +93,7 @@ std::shared_ptr<Http::Parser> Peer::getData(std::string name) const {
return data;
}
std::shared_ptr<Http::Parser> Peer::tryGetData(std::string(name)) const {
std::shared_ptr<void> Peer::tryGetData(std::string(name)) const {
auto it = data_.find(name);
if (it == std::end(data_))
return nullptr;
......
......@@ -66,7 +66,6 @@ public:
Reactor::Key addHandler(const std::shared_ptr<Handler> &handler,
bool setKey = true) override {
handler->registerPoller(poller);
handler->reactor_ = reactor_;
......
......@@ -160,6 +160,11 @@ void Transport::handleIncoming(const std::shared_ptr<Peer> &peer) {
void Transport::handlePeerDisconnection(const std::shared_ptr<Peer> &peer) {
handler_->onDisconnection(peer);
removePeer(peer);
}
void Transport::removePeer(const std::shared_ptr<Peer>& peer)
{
int fd = peer->fd();
auto it = peers.find(fd);
if (it == std::end(peers))
......
......@@ -9,14 +9,150 @@
#include <pistache/peer.h>
#include <pistache/tcp.h>
#include <array>
#include <chrono>
namespace Pistache {
namespace Http {
class TransportImpl : public Tcp::Transport
{
public:
using Base = Tcp::Transport;
explicit TransportImpl(const std::shared_ptr<Tcp::Handler>& handler);
void registerPoller(Polling::Epoll& poller) override;
void onReady(const Aio::FdSet& fds) override;
void setHeaderTimeout(std::chrono::milliseconds timeout);
void setBodyTimeout(std::chrono::milliseconds timeout);
std::shared_ptr<Aio::Handler> clone() const override;
private:
std::shared_ptr<Tcp::Handler> handler_;
std::chrono::milliseconds headerTimeout_;
std::chrono::milliseconds bodyTimeout_;
int timerFd;
void checkIdlePeers();
};
TransportImpl::TransportImpl(const std::shared_ptr<Tcp::Handler>& handler)
: Tcp::Transport(handler)
, handler_(handler)
{ }
void TransportImpl::registerPoller(Polling::Epoll& poller)
{
Base::registerPoller(poller);
timerFd = TRY_RET(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK));
static constexpr auto TimerInterval = std::chrono::milliseconds(500);
static constexpr auto TimerIntervalNs = std::chrono::duration_cast<std::chrono::nanoseconds>(TimerInterval);
static_assert(
TimerInterval < std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::seconds(1)),
"Timer frequency should be less than 1 second"
);
itimerspec spec;
spec.it_value.tv_sec = 0;
spec.it_value.tv_nsec = TimerIntervalNs.count();
spec.it_interval.tv_sec = 0;
spec.it_interval.tv_nsec = TimerIntervalNs.count();
TRY(timerfd_settime(timerFd, 0, &spec, 0));
Polling::Tag tag(timerFd);
poller.addFd(timerFd, Flags<Polling::NotifyOn>(Polling::NotifyOn::Read), Polling::Tag(timerFd));
}
void TransportImpl::onReady(const Aio::FdSet& fds)
{
bool handled = false;
for (const auto& entry: fds)
{
if (entry.getTag() == Polling::Tag(timerFd))
{
uint64_t wakeups;
::read(timerFd, &wakeups, sizeof wakeups);
checkIdlePeers();
handled = true;
}
}
if (!handled)
Base::onReady(fds);
}
void TransportImpl::setHeaderTimeout(std::chrono::milliseconds timeout)
{
headerTimeout_ = timeout;
}
void TransportImpl::setBodyTimeout(std::chrono::milliseconds timeout)
{
bodyTimeout_ = timeout;
}
void TransportImpl::checkIdlePeers()
{
std::vector<std::shared_ptr<Tcp::Peer>> idlePeers;
for (const auto& peerPair: peers)
{
const auto& peer = peerPair.second;
auto& parser = Http::Handler::getParser(peer);
auto time = parser.time();
auto now = std::chrono::steady_clock::now();
auto elapsed = now - time;
auto* step = parser.step();
if (step->id() == Private::RequestLineStep::Id)
{
if (elapsed > headerTimeout_ || elapsed > bodyTimeout_)
idlePeers.push_back(peer);
}
else if (step->id() == Private::HeadersStep::Id)
{
if (elapsed > bodyTimeout_)
idlePeers.push_back(peer);
}
}
for (const auto& idlePeer: idlePeers)
{
Http::Request request;
ResponseWriter response(this, request, static_cast<Http::Handler *>(handler_.get()), idlePeer);
response.send(Http::Code::Request_Timeout).then([=](ssize_t) {
removePeer(idlePeer);
}, [=](std::exception_ptr) {
removePeer(idlePeer);
});
}
}
std::shared_ptr<Aio::Handler> TransportImpl::clone() const
{
auto transport = std::make_shared<TransportImpl>(handler_->clone());
transport->setHeaderTimeout(headerTimeout_);
transport->setBodyTimeout(bodyTimeout_);
return transport;
}
Endpoint::Options::Options()
: threads_(1), flags_(), backlog_(Const::MaxBacklog),
maxRequestSize_(Const::DefaultMaxRequestSize),
maxResponseSize_(Const::DefaultMaxResponseSize),
logger_(PISTACHE_NULL_STRING_LOGGER) {}
: threads_(1), flags_(), backlog_(Const::MaxBacklog)
, maxRequestSize_(Const::DefaultMaxRequestSize)
, maxResponseSize_(Const::DefaultMaxResponseSize)
, headerTimeout_(Const::DefaultHeaderTimeout)
, bodyTimeout_(Const::DefaultBodyTimeout)
, logger_(PISTACHE_NULL_STRING_LOGGER)
{}
Endpoint::Options &Endpoint::Options::threads(int val) {
threads_ = val;
......@@ -63,15 +199,25 @@ Endpoint::Endpoint(const Address &addr) : listener(addr) {}
void Endpoint::init(const Endpoint::Options &options) {
listener.init(options.threads_, options.flags_, options.threadsName_);
maxRequestSize_ = options.maxRequestSize_;
maxResponseSize_ = options.maxResponseSize_;
listener.setTransportFactory([&] {
if (!handler_)
throw std::runtime_error("Must call setHandler()");
auto transport = std::make_shared<TransportImpl>(handler_);
transport->setHeaderTimeout(options.headerTimeout_);
transport->setBodyTimeout(options.bodyTimeout_);
return transport;
});
options_ = options;
logger_ = options.logger_;
}
void Endpoint::setHandler(const std::shared_ptr<Handler> &handler) {
handler_ = handler;
handler_->setMaxRequestSize(maxRequestSize_);
handler_->setMaxResponseSize(maxResponseSize_);
handler_->setMaxRequestSize(options_.maxRequestSize_);
handler_->setMaxResponseSize(options_.maxResponseSize_);
}
void Endpoint::bind() { listener.bind(); }
......
......@@ -148,7 +148,14 @@ void setSocketOptions(Fd fd, Flags<Options> options) {
}
}
Listener::Listener(const Address &address) : addr_(address) {}
Listener::Listener()
: transportFactory_(defaultTransportFactory())
{}
Listener::Listener(const Address &address)
: addr_(address)
, transportFactory_(defaultTransportFactory())
{}
Listener::~Listener() {
if (isBound())
......@@ -177,6 +184,11 @@ void Listener::init(size_t workers, Flags<Options> options,
logger_ = logger;
}
void Listener::setTransportFactory(TransportFactory factory)
{
transportFactory_ = std::move(factory);
}
void Listener::setHandler(const std::shared_ptr<Handler> &handler) {
handler_ = handler;
}
......@@ -200,8 +212,6 @@ void Listener::pinWorker(size_t worker, const CpuSet &set) {
void Listener::bind() { bind(addr_); }
void Listener::bind(const Address &address) {
if (!handler_)
throw std::runtime_error("Call setHandler before calling bind()");
addr_ = address;
struct addrinfo hints;
......@@ -248,7 +258,7 @@ void Listener::bind(const Address &address) {
Polling::Tag(fd));
listen_fd = fd;
auto transport = std::make_shared<Transport>(handler_);
auto transport = transportFactory_();
reactor_.init(Aio::AsyncContext(workers_, workersName_));
transportKey = reactor_.addHandler(transport);
......@@ -447,6 +457,16 @@ void Listener::dispatchPeer(const std::shared_ptr<Peer> &peer) {
transport->handleNewPeer(peer);
}
Listener::TransportFactory Listener::defaultTransportFactory() const
{
return [&] {
if (!handler_)
throw std::runtime_error("setHandler() has not been called");
return std::make_shared<Transport>(handler_);
};
}
#ifdef PISTACHE_USE_SSL
void Listener::setupSSLAuth(const std::string &ca_file,
......
......@@ -436,9 +436,46 @@ TEST(http_server_test, response_size_captured) {
TEST(http_server_test, client_request_header_timeout_raises_http_408) {
Pistache::Address address("localhost", Pistache::Port(0));
auto timeout = std::chrono::seconds(2);
Http::Endpoint server(address);
auto flags = Tcp::Options::ReuseAddr;
auto opts = Http::Endpoint::options()
.flags(flags)
.headerTimeout(timeout);
server.init(opts);
server.setHandler(Http::make_handler<PingHandler>());
server.serveThreaded();
auto port = server.getPort();
auto addr = "localhost:" + port.toString();
std::cout << "Server address: " << addr << "\n";
char recvBuf[1024];
std::memset(recvBuf, 0, sizeof(recvBuf));
size_t bytes;
TcpClient client;
ASSERT_TRUE(client.connect(Pistache::Address("localhost", port))) << client.lastError();
ASSERT_TRUE(client.receive(recvBuf, sizeof(recvBuf), &bytes, std::chrono::seconds(5))) << client.lastError();
server.shutdown();
}
TEST(http_server_test, client_request_body_timeout_raises_http_408) {
Pistache::Address address("localhost", Pistache::Port(0));
auto headerTimeout = std::chrono::seconds(1);
auto bodyTimeout = std::chrono::seconds(1);
Http::Endpoint server(address);
auto flags = Tcp::Options::ReuseAddr;
auto opts = Http::Endpoint::options().flags(flags);
auto opts = Http::Endpoint::options()
.flags(flags)
.headerTimeout(headerTimeout)
.bodyTimeout(bodyTimeout);
server.init(opts);
server.setHandler(Http::make_handler<PingHandler>());
server.serveThreaded();
......@@ -448,16 +485,24 @@ TEST(http_server_test, client_request_header_timeout_raises_http_408) {
std::cout << "Server address: " << addr << "\n";
std::string reqStr = "GET /ping HTTP/1.1\r\n";
std::string headerStr = "Host: localhost\r\nUser-Agent: test\r\n";
char recvBuf[1024];
std::memset(recvBuf, 0, sizeof(recvBuf));
size_t bytes;
TcpClient client;
EXPECT_TRUE(client.connect(Pistache::Address("localhost", port))) << client.lastError();
EXPECT_TRUE(client.send(reqStr)) << client.lastError();
ASSERT_TRUE(client.connect(Pistache::Address("localhost", port))) << client.lastError();
ASSERT_TRUE(client.send(reqStr)) << client.lastError();
EXPECT_TRUE(client.receive(recvBuf, sizeof(recvBuf), &bytes, std::chrono::seconds(5))) << client.lastError();
std::this_thread::sleep_for(headerTimeout / 2);
ASSERT_TRUE(client.send(headerStr)) << client.lastError();
static constexpr const char* ExpectedResponseLine = "HTTP/1.1 408 Request Timeout";
ASSERT_TRUE(client.receive(recvBuf, sizeof(recvBuf), &bytes, std::chrono::seconds(5))) << client.lastError();
ASSERT_TRUE(!strncmp(recvBuf, ExpectedResponseLine, strlen(ExpectedResponseLine)));
server.shutdown();
}
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