Skip to content
This repository has been archived by the owner on Aug 19, 2019. It is now read-only.

Allow supplying callbacks for specific endpoints in the API server. #93

Merged
merged 3 commits into from
Mar 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 75 additions & 41 deletions src/api_server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,54 +25,43 @@

namespace google {

MetadataApiServer::Handler::Handler(const Configuration& config,
const MetadataStore& store)
: config_(config), store_(store) {}
MetadataApiServer::Dispatcher::Dispatcher(
const HandlerMap& handlers, bool verbose)
: handlers_(handlers), verbose_(verbose) {}

void MetadataApiServer::Handler::operator()(const HttpServer::request& request,
std::shared_ptr<HttpServer::connection> conn) {
static const std::string kPrefix = "/monitoredResource/";
// The format for the local metadata API request is:
// {host}:{port}/monitoredResource/{id}
if (config_.VerboseLogging()) {
LOG(INFO) << "Handler called: " << request.method
void MetadataApiServer::Dispatcher::operator()(
const HttpServer::request& request,
std::shared_ptr<HttpServer::connection> conn) {
if (verbose_) {
LOG(INFO) << "Dispatcher called: " << request.method
<< " " << request.destination
<< " headers: " << request.headers
<< " body: " << request.body;
}
if (request.method == "GET" && request.destination.find(kPrefix) == 0) {
const std::string id = request.destination.substr(kPrefix.size());
try {
const MonitoredResource& resource = store_.LookupResource(id);
if (config_.VerboseLogging()) {
LOG(INFO) << "Found resource for " << id << ": " << resource;
}
conn->set_status(HttpServer::connection::ok);
conn->set_headers(std::map<std::string, std::string>({
{"Content-Type", "application/json"},
}));
conn->write(resource.ToJSON()->ToString());
} catch (const std::out_of_range& e) {
// TODO: This could be considered log spam.
// As we add more resource mappings, these will become less and less
// frequent, and could be promoted to ERROR.
if (config_.VerboseLogging()) {
LOG(WARNING) << "No matching resource for " << id;
}
conn->set_status(HttpServer::connection::not_found);
conn->set_headers(std::map<std::string, std::string>({
{"Content-Type", "application/json"},
}));
json::value json_response = json::object({
{"status_code", json::number(404)},
{"error", json::string("Not found")},
});
conn->write(json_response->ToString());
// Look for the longest match first. This means going backwards through
// the map, since strings are sorted in lexicographical order.
for (auto it = handlers_.crbegin(); it != handlers_.crend(); --it) {
const std::string& method = it->first.first;
const std::string& prefix = it->first.second;
#ifdef VERBOSE
LOG(DEBUG) << "Checking " << method << " " << prefix;
#endif
if (request.method != method || request.destination.find(prefix) != 0) {
#ifdef VERBOSE
LOG(DEBUG) << "No match; skipping " << method << " " << prefix;
#endif
continue;
}
#ifdef VERBOSE
LOG(DEBUG) << "Handler found for " << request.method
<< " " << request.destination;
#endif
const Handler& handler = it->second;
handler(request, conn);
}
}

void MetadataApiServer::Handler::log(const HttpServer::string_type& info) {
void MetadataApiServer::Dispatcher::log(const HttpServer::string_type& info) {
LOG(ERROR) << info;
}

Expand All @@ -81,9 +70,15 @@ MetadataApiServer::MetadataApiServer(const Configuration& config,
const MetadataStore& store,
int server_threads,
const std::string& host, int port)
: handler_(config, store),
: config_(config), store_(store), dispatcher_({
{{"GET", "/monitoredResource/"},
[=](const HttpServer::request& request,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to just pass HandleMonitoredResource, without doing so in a lambda? This would work in Javascript, but I know C++ is much stricter than javascript, I think anything is stricter than Javascript :-P

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because we also need to capture this. If there were no capture, you could indeed convert a function pointer into an std::function.

std::shared_ptr<HttpServer::connection> conn) {
HandleMonitoredResource(request, conn);
}},
}, config_.VerboseLogging()),
server_(
HttpServer::options(handler_)
HttpServer::options(dispatcher_)
.address(host)
.port(std::to_string(port))),
server_pool_()
Expand All @@ -99,4 +94,43 @@ MetadataApiServer::~MetadataApiServer() {
}
}

void MetadataApiServer::HandleMonitoredResource(
const HttpServer::request& request,
std::shared_ptr<HttpServer::connection> conn) {
// The format for the local metadata API request is:
// {host}:{port}/monitoredResource/{id}
static const std::string kPrefix = "/monitoredResource/";;
const std::string id = request.destination.substr(kPrefix.size());
if (config_.VerboseLogging()) {
LOG(INFO) << "Handler called for " << id;
}
try {
const MonitoredResource& resource = store_.LookupResource(id);
if (config_.VerboseLogging()) {
LOG(INFO) << "Found resource for " << id << ": " << resource;
}
conn->set_status(HttpServer::connection::ok);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you rebase off of master with the latest header updates and 404 response?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, done.

conn->set_headers(std::map<std::string, std::string>({
{"Content-Type", "application/json"},
}));
conn->write(resource.ToJSON()->ToString());
} catch (const std::out_of_range& e) {
// TODO: This could be considered log spam.
// As we add more resource mappings, these will become less and less
// frequent, and could be promoted to ERROR.
if (config_.VerboseLogging()) {
LOG(WARNING) << "No matching resource for " << id;
}
conn->set_status(HttpServer::connection::not_found);
conn->set_headers(std::map<std::string, std::string>({
{"Content-Type", "application/json"},
}));
json::value json_response = json::object({
{"status_code", json::number(404)},
{"error", json::string("Not found")},
});
conn->write(json_response->ToString());
}
}

}
30 changes: 23 additions & 7 deletions src/api_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@

#define BOOST_NETWORK_ENABLE_HTTPS
#include <boost/network/protocol/http/server.hpp>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <thread>
#include <utility>
#include <vector>

namespace http = boost::network::http;
Expand All @@ -40,20 +44,32 @@ class MetadataApiServer {
~MetadataApiServer();

private:
class Handler;
using HttpServer = http::server<Handler>;
class Handler {
class Dispatcher;
using HttpServer = http::server<Dispatcher>;
using Handler = std::function<void(const HttpServer::request&,
std::shared_ptr<HttpServer::connection>)>;

class Dispatcher {
public:
Handler(const Configuration& config, const MetadataStore& store);
using HandlerMap = std::map<std::pair<std::string, std::string>, Handler>;
Dispatcher(const HandlerMap& handlers, bool verbose);
void operator()(const HttpServer::request& request,
std::shared_ptr<HttpServer::connection> conn);
void log(const HttpServer::string_type& info);
private:
const Configuration& config_;
const MetadataStore& store_;
// A mapping from a method/prefix pair to the handler function.
// Order matters: later entries override earlier ones.
const HandlerMap handlers_;
bool verbose_;
};

Handler handler_;
// Handler functions.
void HandleMonitoredResource(const HttpServer::request& request,
std::shared_ptr<HttpServer::connection> conn);

const Configuration& config_;
const MetadataStore& store_;
Dispatcher dispatcher_;
HttpServer server_;
std::vector<std::thread> server_pool_;
};
Expand Down