Commit 5464de5e authored by octal's avatar octal

First draft of a brand new HTTP routing API

parent 6d32e4a2
......@@ -2,10 +2,13 @@
#include "peer.h"
#include "http.h"
#include "http_headers.h"
#include "router.h"
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
using namespace Net;
struct ExceptionPrinter {
void operator()(std::exception_ptr exc) const {
......@@ -136,6 +139,101 @@ private:
}
};
class StatsEndpoint {
public:
StatsEndpoint(Net::Address addr)
: httpEndpoint(addr)
{ }
void init() {
auto opts = Net::Http::Endpoint::options()
.threads(1)
.flags(Net::Tcp::Options::InstallSignalHandler);
httpEndpoint.init(opts);
setupRoutes();
}
void start() {
httpEndpoint.setHandler(router.handler());
httpEndpoint.serve();
}
void shutdown() {
httpEndpoint.shutdown();
}
private:
void setupRoutes() {
using namespace Net::Rest;
Routes::Post(router, "/record/:name", &StatsEndpoint::doRecordMetric, this);
Routes::Get(router, "/value/:name", &StatsEndpoint::doGetMetric, this);
}
void doRecordMetric(const Rest::Request& request, Net::Http::Response response) {
auto name = request.param(":name").as<std::string>();
auto it = std::find_if(metrics.begin(), metrics.end(), [&](const Metric& metric) {
return metric.name() == name;
});
if (it == std::end(metrics)) {
metrics.push_back(Metric(std::move(name), 1));
response.send(Http::Code::Created);
}
else {
auto &metric = *it;
metric.incr(1);
response.send(Http::Code::Ok, std::to_string(metric.value()));
}
}
void doGetMetric(const Rest::Request& request, Net::Http::Response response) {
auto name = request.param(":name").as<std::string>();
auto it = std::find_if(metrics.begin(), metrics.end(), [&](const Metric& metric) {
return metric.name() == name;
});
if (it == std::end(metrics)) {
response.send(Http::Code::Not_Found);
}
const auto& metric = *it;
response.send(Http::Code::Ok, std::to_string(metric.value()));
}
class Metric {
public:
Metric(std::string name, int initialValue = 1)
: name_(std::move(name))
, value_(initialValue)
{ }
int incr(int n = 1) {
int old = value_;
value_ += n;
return old;
}
int value() const {
return value_;
}
std::string name() const {
return name_;
}
private:
std::string name_;
int value_;
};
std::vector<Metric> metrics;
Net::Http::Endpoint httpEndpoint;
Rest::Router router;
};
int main(int argc, char *argv[]) {
Net::Port port(9080);
......@@ -154,20 +252,11 @@ int main(int argc, char *argv[]) {
cout << "Cores = " << hardware_concurrency() << endl;
cout << "Using " << thr << " threads" << endl;
auto server = std::make_shared<Net::Http::Endpoint>(addr);
auto opts = Net::Http::Endpoint::options()
.threads(thr)
.flags(Net::Tcp::Options::InstallSignalHandler);
server->init(opts);
server->setHandler(std::make_shared<MyHandler>());
StatsEndpoint stats(addr);
LoadMonitor monitor(server);
monitor.setInterval(std::chrono::seconds(5));
monitor.start();
stats.init();
stats.start();
server->serve();
std::cout << "Shutdowning server" << std::endl;
server->shutdown();
monitor.shutdown();
stats.shutdown();
}
......@@ -12,6 +12,7 @@ set(SOURCE_FILES
http_defs.cc
mime.cc
stream.cc
router.cc
)
add_library(net ${SOURCE_FILES})
......
......@@ -531,6 +531,9 @@ public:
void init(const Options& options);
void bind();
void bind(const Address& addr);
void setHandler(const std::shared_ptr<Handler>& handler);
void serve();
void serveThreaded();
......
......@@ -10,6 +10,7 @@
#include <stdexcept>
#include <chrono>
#include <ctime>
#include <functional>
namespace Net {
......@@ -184,3 +185,14 @@ private:
} // namespace Http
} // namespace Net
namespace std {
template<>
struct hash<Net::Http::Method> {
size_t operator()(Net::Http::Method method) const {
return std::hash<int>()(static_cast<int>(method));
}
};
} // namespace std
/* router.cc
Mathieu Stefani, 05 janvier 2016
Rest routing implementation
*/
#include "router.h"
namespace Net {
namespace Rest {
Request::Request(
const Http::Request& request,
std::vector<TypedParam>&& params)
: Http::Request(request)
{
for (auto&& param: params) {
params_.insert(std::make_pair(param.name(), std::move(param)));
}
}
TypedParam
Request::param(std::string name) const {
auto it = params_.find(std::move(name));
if (it == std::end(params_)) {
throw std::runtime_error("Unknown parameter");
}
return it->second;
}
std::pair<bool, std::vector<TypedParam>>
Router::Route::match(const Http::Request& req) const
{
auto reqParts = splitUrl(req.resource());
if (reqParts.size() != parts_.size()) return std::make_pair(false, std::vector<TypedParam>());
auto isUrlParameter = [](const std::string& str) {
return str.size() >= 1 && str[0] == ':';
};
std::vector<TypedParam> extractedParams;
for (std::vector<std::string>::size_type i = 0; i < reqParts.size(); ++i) {
if (!isUrlParameter(parts_[i])) {
if (reqParts[i] != parts_[i]) return std::make_pair(false, std::vector<TypedParam>());
continue;
}
TypedParam param(parts_[i], reqParts[i]);
extractedParams.push_back(std::move(param));
}
return make_pair(true, std::move(extractedParams));
}
std::vector<std::string>
Router::Route::splitUrl(const std::string& resource) const {
std::istringstream iss(resource);
std::string p;
std::vector<std::string> parts;
while (std::getline(iss, p, '/')) {
parts.push_back(std::move(p));
}
return parts;
}
Router::HttpHandler::HttpHandler(
const std::unordered_map<Http::Method, std::vector<Router::Route>>& routes)
: routes(routes)
{
}
void
Router::HttpHandler::onRequest(
const Http::Request& req,
Http::Response response,
Http::Timeout timeout)
{
auto& r = routes[req.method()];
for (const auto& route: r) {
bool match;
std::vector<TypedParam> params;
std::tie(match, params) = route.match(req);
if (match) {
route.invokeHandler(Request(req, std::move(params)), std::move(response));
}
}
}
void
Router::get(std::string resource, Router::Handler handler) {
addRoute(Http::Method::Get, std::move(resource), std::move(handler));
}
void
Router::post(std::string resource, Router::Handler handler) {
addRoute(Http::Method::Post, std::move(resource), std::move(handler));
}
void
Router::put(std::string resource, Router::Handler handler) {
addRoute(Http::Method::Put, std::move(resource), std::move(handler));
}
void
Router::del(std::string resource, Router::Handler handler) {
addRoute(Http::Method::Delete, std::move(resource), std::move(handler));
}
void
Router::addRoute(Http::Method method, std::string resource, Router::Handler handler)
{
auto& r = routes[method];
r.push_back(Route(std::move(resource), method, std::move(handler)));
}
namespace Routes {
void Get(Router& router, std::string resource, Router::Handler handler) {
router.get(std::move(resource), std::move(handler));
}
void Post(Router& router, std::string resource, Router::Handler handler) {
router.post(std::move(resource), std::move(handler));
}
void Put(Router& router, std::string resource, Router::Handler handler) {
router.put(std::move(resource), std::move(handler));
}
void Delete(Router& router, std::string resource, Router::Handler handler) {
router.del(std::move(resource), std::move(handler));
}
} // namespace Routing
} // namespace Rest
} // namespace Net
/* router.h
Mathieu Stefani, 05 janvier 2016
Simple HTTP Rest Router
*/
#pragma once
#include <string>
#include "http.h"
#include "http_defs.h"
namespace Net {
namespace Rest {
class TypedParam {
public:
TypedParam(const std::string& name, const std::string& value)
: name_(name)
, value_(value)
{ }
template<typename T>
T as() const {
/* @FixMe: use some sort of lexical casting here */
return value_;
}
std::string name() const {
return name_;
}
private:
std::string name_;
std::string value_;
};
class Request : public Http::Request {
public:
explicit Request(
const Http::Request& request,
std::vector<TypedParam>&& params);
TypedParam param(std::string name) const;
private:
std::unordered_map<std::string, TypedParam> params_;
};
class Router {
public:
typedef std::function<void (const Request&, Net::Http::Response)> Handler;
struct Route {
Route(std::string resource, Http::Method method, Handler handler)
: resource_(std::move(resource))
, method_(method)
, handler_(std::move(handler))
, parts_(splitUrl(resource_))
{
}
std::pair<bool, std::vector<TypedParam>>
match(const Http::Request& req) const;
template<typename... Args>
void invokeHandler(Args&& ...args) const {
handler_(std::forward<Args>(args)...);
}
private:
std::vector<std::string> splitUrl(const std::string& resource) const;
std::string resource_;
Net::Http::Method method_;
Handler handler_;
/* @Performance: since we know that resource_ will live as long as the vector underneath,
* we would benefit from std::experimental::string_view to store parts of the resource.
*
* 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::vector<std::string> parts_;
};
class HttpHandler : public Net::Http::Handler {
public:
HttpHandler(const std::unordered_map<Http::Method, std::vector<Route>>& routes);
void onRequest(
const Http::Request& req,
Http::Response response,
Http::Timeout timeout);
private:
std::unordered_map<Http::Method, std::vector<Route>> routes;
};
std::shared_ptr<HttpHandler>
handler() const {
return std::make_shared<HttpHandler>(routes);
}
void get(std::string resource, Handler handler);
void post(std::string resource, Handler handler);
void put(std::string resource, Handler handler);
void del(std::string resource, Handler handler);
private:
void addRoute(Http::Method method, std::string resource, Handler handler);
std::unordered_map<Http::Method, std::vector<Route>> routes;
};
namespace Routes {
void Get(Router& router, std::string resource, Router::Handler handler);
void Post(Router& router, std::string resource, Router::Handler handler);
void Put(Router& router, std::string resource, Router::Handler handler);
void Delete(Router& router, std::string resource, Router::Handler handler);
template<typename Handler, typename Obj>
void Get(Router& router, std::string resource, Handler handler, Obj obj) {
Get(router, std::move(resource), std::bind(handler, obj, std::placeholders::_1, std::placeholders::_2));
}
template<typename Handler, typename Obj>
void Post(Router& router, std::string resource, Handler handler, Obj obj) {
Post(router, std::move(resource), std::bind(handler, obj, std::placeholders::_1, std::placeholders::_2));
}
} // namespace Routing
} // namespace Rest
} // namespace Net
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