Use hashmap for storing routes instead of an array.

The use of array index makes rolling updates tricky since requests might be forwarded to an instance running an older/newer version having a different route index.
This commit is contained in:
kishorenc 2020-03-26 20:07:47 +05:30
parent 88192820de
commit 75a1fe1e1d
6 changed files with 44 additions and 38 deletions

View File

@ -6,6 +6,7 @@
#include <vector>
#include <future>
#include "json.hpp"
#include "string_utils.h"
#define H2O_USE_LIBUV 0
extern "C" {
@ -96,26 +97,25 @@ struct http_res {
enum class ROUTE_CODES {
NOT_FOUND = -1,
RETURN_EARLY = -2,
ALREADY_HANDLED = -2,
};
struct http_req {
h2o_req_t* _req;
std::string http_method;
int route_index;
int route_hash;
std::map<std::string, std::string> params;
std::string body;
http_req(): route_index(-1) {}
http_req(): route_hash(-1) {}
http_req(h2o_req_t* _req, const std::string & http_method, size_t route_index,
const std::map<std::string, std::string> & params,
std::string body): _req(_req), http_method(http_method), route_index(route_index),
params(params), body(body) {}
http_req(h2o_req_t* _req, const std::string & http_method, size_t route_hash,
const std::map<std::string, std::string> & params, std::string body):
_req(_req), http_method(http_method), route_hash(route_hash), params(params), body(body) {}
void deserialize(const std::string& serialized_content) {
nlohmann::json content = nlohmann::json::parse(serialized_content);
route_index = content["route_index"];
route_hash = content["route_hash"];
body = content["body"];
for (nlohmann::json::iterator it = content["params"].begin(); it != content["params"].end(); ++it) {
@ -127,7 +127,7 @@ struct http_req {
std::string serialize() const {
nlohmann::json content;
content["route_index"] = route_index;
content["route_hash"] = route_hash;
content["params"] = params;
content["body"] = body;
@ -149,6 +149,12 @@ struct route_path {
inline bool operator< (const route_path& rhs) const {
return true;
}
uint64_t route_hash() {
std::string path = StringUtils::join(path_parts, "/");
std::string method_path = http_method + path;
return StringUtils::hash_wy(method_path.c_str(), method_path.size());
}
};
struct h2o_custom_generator_t {

View File

@ -34,7 +34,7 @@ private:
std::string version;
std::vector<route_path> routes; // TODO: must be a hashmap?
std::unordered_map<uint32_t, route_path> routes;
const std::string listen_address;

View File

@ -46,7 +46,7 @@ struct StringUtils {
}
}
static std::string join(std::vector<std::string> vec, std::string delimiter, size_t start_index = 0) {
static std::string join(std::vector<std::string> vec, const std::string& delimiter, size_t start_index = 0) {
std::stringstream ss;
for(size_t i = start_index; i < vec.size(); i++) {
if(i != start_index) {

View File

@ -613,21 +613,16 @@ bool async_write_request(void *data) {
AsyncIndexArg* index_arg = static_cast<AsyncIndexArg*>(data);
std::unique_ptr<AsyncIndexArg> index_arg_guard(index_arg);
if(index_arg->req->route_index == static_cast<int>(ROUTE_CODES::NOT_FOUND)) {
if(index_arg->req->route_hash == static_cast<int>(ROUTE_CODES::NOT_FOUND)) {
// route not found
return false;
} else if(index_arg->req->route_index == static_cast<int>(ROUTE_CODES::RETURN_EARLY)) {
// respond without calling internal route
server->send_response(index_arg->req, index_arg->res);
return true;
index_arg->res->send_400("Not found.");
} else if(index_arg->req->route_hash != static_cast<int>(ROUTE_CODES::ALREADY_HANDLED)) {
// call the underlying http handler
route_path* found_rpath = nullptr;
server->get_route(index_arg->req->route_hash, &found_rpath);
found_rpath->handler(*index_arg->req, *index_arg->res);
}
route_path* found_rpath;
server->get_route(index_arg->req->route_index, &found_rpath);
// call the underlying http handler
found_rpath->handler(*index_arg->req, *index_arg->res);
if(index_arg->req->_req != nullptr) {
// we have to return a response to the client
server->send_response(index_arg->req, index_arg->res);

View File

@ -24,6 +24,8 @@ HttpServer::HttpServer(const std::string & version, const std::string & listen_a
hostconf = h2o_config_register_host(&config, h2o_iovec_init(H2O_STRLIT("default")), 65535);
register_handler(hostconf, "/", catch_all_handler);
listener_socket = nullptr; // initialized later
signal(SIGPIPE, SIG_IGN);
h2o_context_init(&ctx, h2o_evloop_create(), &config);
@ -161,8 +163,10 @@ void HttpServer::clear_timeouts(const std::vector<h2o_timeout_t*> & timeouts) {
}
void HttpServer::stop() {
h2o_socket_read_stop(listener_socket);
h2o_socket_close(listener_socket);
if(listener_socket != nullptr) {
h2o_socket_read_stop(listener_socket);
h2o_socket_close(listener_socket);
}
// this will break the event loop
exit_loop = true;
@ -209,8 +213,9 @@ std::map<std::string, std::string> HttpServer::parse_query(const std::string& qu
}
int HttpServer::find_route(const std::vector<std::string> & path_parts, const std::string & http_method, route_path** found_rpath) {
for(size_t i = 0; i < routes.size(); i++) {
const route_path & rpath = routes[i];
for (const auto& index_route : routes) {
const route_path & rpath = index_route.second;
if(rpath.path_parts.size() != path_parts.size() || rpath.http_method != http_method) {
continue;
}
@ -228,7 +233,7 @@ int HttpServer::find_route(const std::vector<std::string> & path_parts, const st
if(found) {
*found_rpath = const_cast<route_path *>(&rpath);
return i;
return index_route.first;
}
}
@ -304,9 +309,9 @@ int HttpServer::catch_all_handler(h2o_handler_t *_self, h2o_req_t *req) {
}
route_path *rpath = nullptr;
int route_index = self->http_server->find_route(path_parts, http_method, &rpath);
int route_hash = self->http_server->find_route(path_parts, http_method, &rpath);
if(route_index != -1) {
if(route_hash != -1) {
bool authenticated = self->http_server->auth_handler(*rpath, auth_key_from_header);
if(!authenticated) {
return send_401_unauthorized(req);
@ -320,7 +325,7 @@ int HttpServer::catch_all_handler(h2o_handler_t *_self, h2o_req_t *req) {
}
}
http_req* request = new http_req(req, http_method, route_index, query_map, req_body);
http_req* request = new http_req(req, http_method, route_hash, query_map, req_body);
http_res* response = new http_res();
// for writes, we defer to replication_state
@ -391,28 +396,28 @@ void HttpServer::get(const std::string & path, bool (*handler)(http_req &, http_
std::vector<std::string> path_parts;
StringUtils::split(path, path_parts, "/");
route_path rpath = {"GET", path_parts, handler, async};
routes.push_back(rpath);
routes.emplace(rpath.route_hash(), rpath);
}
void HttpServer::post(const std::string & path, bool (*handler)(http_req &, http_res &), bool async) {
std::vector<std::string> path_parts;
StringUtils::split(path, path_parts, "/");
route_path rpath = {"POST", path_parts, handler, async};
routes.push_back(rpath);
routes.emplace(rpath.route_hash(), rpath);
}
void HttpServer::put(const std::string & path, bool (*handler)(http_req &, http_res &), bool async) {
std::vector<std::string> path_parts;
StringUtils::split(path, path_parts, "/");
route_path rpath = {"PUT", path_parts, handler, async};
routes.push_back(rpath);
routes.emplace(rpath.route_hash(), rpath);
}
void HttpServer::del(const std::string & path, bool (*handler)(http_req &, http_res &), bool async) {
std::vector<std::string> path_parts;
StringUtils::split(path, path_parts, "/");
route_path rpath = {"DELETE", path_parts, handler, async};
routes.push_back(rpath);
routes.emplace(rpath.route_hash(), rpath);
}
void HttpServer::on(const std::string & message, bool (*handler)(void*)) {
@ -456,7 +461,7 @@ ReplicationState* HttpServer::get_replication_state() const {
}
void HttpServer::get_route(size_t index, route_path** found_rpath) {
if(index >= 0 && index < routes.size()) {
if(routes.count(index) > 0) {
*found_rpath = &routes[index];
}
}

View File

@ -100,7 +100,7 @@ void ReplicationState::write(http_req* request, http_res* response) {
LOG(ERROR) << "Rejecting write: could not find a leader.";
response->send_500("Could not find a leader.");
auto replication_arg = new AsyncIndexArg{request, response, nullptr};
replication_arg->req->route_index = static_cast<int>(ROUTE_CODES::RETURN_EARLY);
replication_arg->req->route_hash = static_cast<int>(ROUTE_CODES::ALREADY_HANDLED);
return message_dispatcher->send_message(REPLICATION_MSG, replication_arg);
}
@ -131,7 +131,7 @@ void ReplicationState::write(http_req* request, http_res* response) {
}
auto replication_arg = new AsyncIndexArg{request, response, nullptr};
replication_arg->req->route_index = static_cast<int>(ROUTE_CODES::RETURN_EARLY);
replication_arg->req->route_hash = static_cast<int>(ROUTE_CODES::ALREADY_HANDLED);
message_dispatcher->send_message(REPLICATION_MSG, replication_arg);
});