Commit 282f6c89 authored by Ian Roddis's avatar Ian Roddis

Merge branch 'master' of http://github.com/oktal/pistache into cors_support

parents 37e4dfa5 d59eee7b
......@@ -53,6 +53,7 @@ endif()
@ONLY
)
if(PISTACHE_INSTALL)
# Install header...
install (
FILES
......@@ -60,6 +61,7 @@ endif()
DESTINATION
include/pistache/
)
endif()
# Configure the pkg-config metadata...
......@@ -77,6 +79,7 @@ endif()
@ONLY
)
if(PISTACHE_INSTALL)
# Install pkg-config metadata into standard location within the prefix...
install (
FILES
......@@ -84,4 +87,5 @@ endif()
DESTINATION
lib/pkgconfig/
)
endif()
......@@ -59,6 +59,7 @@ namespace Const {
static constexpr size_t MaxBacklog = 128;
static constexpr size_t MaxEvents = 1024;
static constexpr size_t MaxBuffer = 4096;
static constexpr size_t DefaultWorkers = 1;
// Defined from CMakeLists.txt in project root
static constexpr size_t DefaultMaxPayload = 4096;
......
......@@ -26,6 +26,7 @@
#include <pistache/peer.h>
#include <pistache/tcp.h>
#include <pistache/transport.h>
#include <pistache/view.h>
namespace Pistache {
namespace Http {
......@@ -692,16 +693,7 @@ namespace Private {
ParserBase()
: cursor(&buffer)
, currentStep(0)
{
}
ParserBase(const char* data, size_t len)
: cursor(&buffer)
, currentStep(0)
{
UNUSED(data)
UNUSED(len)
}
{ }
ParserBase(const ParserBase& other) = delete;
ParserBase(ParserBase&& other) = default;
......@@ -810,5 +802,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
......@@ -118,6 +118,8 @@ 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,
HTTP_METHODS
......
......@@ -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();
......
......@@ -42,7 +42,7 @@ public:
Listener();
~Listener();
Listener(const Address& address);
explicit Listener(const Address& address);
void init(
size_t workers,
Flags<Options> options = Options::None,
......@@ -80,7 +80,7 @@ private:
std::shared_ptr<Transport> transport_;
std::shared_ptr<Handler> handler_;
std::shared_ptr<Aio::Reactor> reactor_;
Aio::Reactor reactor_;
Aio::Reactor::Key transportKey;
void handleNewConnection();
......
......@@ -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") \
......
......@@ -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>
......@@ -592,8 +579,7 @@ Connection::performImpl(
transport_->asyncSendRequest(shared_from_this(), timer, buffer).then(
[=](ssize_t bytes) mutable {
UNUSED(bytes)
[=](ssize_t) mutable {
inflightRequests.push_back(RequestEntry(std::move(resolveMover), std::move(rejectMover), std::move(timer), std::move(onDone)));
},
[=](std::exception_ptr e) { rejectCloneMover.val(e); });
......@@ -872,7 +858,7 @@ Client::doRequest(
}
if (!conn->isConnected()) {
conn->connect(httpAddr(s.first));
conn->connect(helpers::httpAddr(s.first));
}
return conn->perform(request, timeout, [=]() {
......
......@@ -740,8 +740,7 @@ serveFile(ResponseWriter& response, const char* fileName, const Mime::MediaType&
auto sockFd = peer->fd();
auto buffer = buf->buffer();
return transport->asyncWrite(sockFd, buffer, MSG_MORE).then([=](ssize_t bytes) {
UNUSED(bytes)
return transport->asyncWrite(sockFd, buffer, MSG_MORE).then([=](ssize_t) {
return transport->asyncWrite(sockFd, FileBuffer(fileName));
}, Async::Throw);
......
......@@ -346,7 +346,7 @@ Host::parse(const std::string& data) {
port_ = p;
} else {
host_ = data;
port_ = 80;
port_ = HTTP_STANDARD_PORT;
}
}
......
......@@ -30,6 +30,9 @@ FileBuffer::FileBuffer(const std::string& fileName)
void
FileBuffer::init(const char* fileName)
{
if (!fileName) {
throw std::runtime_error("Missing fileName");
}
int fd = open(fileName, O_RDONLY);
if (fd == -1) {
throw std::runtime_error("Could not open file");
......
......@@ -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>
......@@ -73,16 +74,27 @@ void setSocketOptions(Fd fd, Flags<Options> options) {
}
Listener::Listener()
: listen_fd(-1)
: addr_()
, listen_fd(-1)
, backlog_(Const::MaxBacklog)
, reactor_(Aio::Reactor::create())
, shutdownFd()
, poller()
, options_()
, workers_(Const::DefaultWorkers)
, reactor_()
, transportKey()
{ }
Listener::Listener(const Address& address)
: addr_(address)
, listen_fd(-1)
, backlog_(Const::MaxBacklog)
, reactor_(Aio::Reactor::create())
, shutdownFd()
, poller()
, options_()
, workers_(Const::DefaultWorkers)
, reactor_()
, transportKey()
{
}
......@@ -160,7 +172,8 @@ Listener::bind(const Address& address) {
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;
......@@ -174,6 +187,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));
......@@ -182,20 +201,20 @@ Listener::bind(const Address& address) {
transport_.reset(new Transport(handler_));
reactor_->init(Aio::AsyncContext(workers_));
transportKey = reactor_->addHandler(transport_);
reactor_.init(Aio::AsyncContext(workers_));
transportKey = reactor_.addHandler(transport_);
return true;
}
bool
Listener::isBound() const {
return g_listen_fd != -1;
return listen_fd != -1;
}
void
Listener::run() {
reactor_->run();
reactor_.run();
for (;;) {
std::vector<Polling::Event> events;
......@@ -230,12 +249,12 @@ Listener::runThreaded() {
void
Listener::shutdown() {
if (shutdownFd.isBound()) shutdownFd.notify();
reactor_->shutdown();
reactor_.shutdown();
}
Async::Promise<Listener::Load>
Listener::requestLoad(const Listener::Load& old) {
auto handlers = reactor_->handlers(transportKey);
auto handlers = reactor_.handlers(transportKey);
std::vector<Async::Promise<rusage>> loads;
for (const auto& handler: handlers) {
......@@ -313,7 +332,7 @@ Listener::handleNewConnection() {
void
Listener::dispatchPeer(const std::shared_ptr<Peer>& peer) {
auto handlers = reactor_->handlers(transportKey);
auto handlers = reactor_.handlers(transportKey);
auto idx = peer->fd() % handlers.size();
auto transport = std::static_pointer_cast<Transport>(handlers[idx]);
......
......@@ -18,4 +18,6 @@ pistache_test(view_test)
pistache_test(http_parsing_test)
pistache_test(http_client_test)
pistache_test(net_test)
pistache_test(listener_test)
pistache_test(payload_test)
pistache_test(rest_server_test)
This diff is collapsed.
#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. Abort test.\n";
}
Pistache::Port port(port_nb);
Pistache::Address address(Pistache::Ipv4::any(), port);
Pistache::Tcp::Listener listener;
Pistache::Flags<Pistache::Tcp::Options> options;
listener.init(1, options);
listener.setHandler(Pistache::Http::make_handler<DummyHandler>());
listener.bind(address);
ASSERT_TRUE(true);
}
// Listener should not crash if an additional member is added to the listener class. This test
// is there to prevent regression for PR 303
TEST(listener_test, listener_uses_default) {
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. Abort 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. Abort test.\n";
}
Pistache::Port port(port_nb);
Pistache::Address address(Pistache::Ipv4::any(), port);
Pistache::Tcp::Listener listener;
Pistache::Flags<Pistache::Tcp::Options> options;
listener.init(1, options);
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";
}
}
/*
Mathieu Stefani, 07 février 2016
Example of a REST endpoint with routing
*/
#include "gtest/gtest.h"
#include <pistache/http.h>
#include <pistache/router.h>
#include <pistache/endpoint.h>
#include "httplib.h"
using namespace std;
using namespace Pistache;
class StatsEndpoint {
public:
StatsEndpoint(Address addr)
: httpEndpoint(std::make_shared<Http::Endpoint>(addr))
{ }
void init(size_t thr = 2) {
auto opts = Http::Endpoint::options()
.threads(thr)
.flags(Tcp::Options::InstallSignalHandler);
httpEndpoint->init(opts);
setupRoutes();
}
void start() {
httpEndpoint->setHandler(router.handler());
httpEndpoint->serveThreaded();
}
void shutdown() {
httpEndpoint->shutdown();
}
private:
void setupRoutes() {
using namespace Rest;
Routes::Get(router, "/read/function1", Routes::bind(&StatsEndpoint::doAuth, this));
}
void doAuth(const Rest::Request& request, Http::ResponseWriter response) {
std::thread worker([](Http::ResponseWriter writer) {
writer.send(Http::Code::Ok, "1");
}, std::move(response));
worker.detach();
}
std::shared_ptr<Http::Endpoint> httpEndpoint;
Rest::Router router;
};
TEST(rest_server_test, basic_test) {
Port port(9090);
int thr = 1;
Address addr(Ipv4::any(), port);
StatsEndpoint stats(addr);
stats.init(thr);
stats.start();
cout << "Cores = " << hardware_concurrency() << endl;
cout << "Using " << thr << " threads" << endl;
httplib::Client client("localhost", 9090);
auto res = client.Get("/read/function1");
ASSERT_EQ(res->status, 200);
ASSERT_EQ(res->body, "1");
stats.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