Skip to content
This repository has been archived by the owner on Jun 23, 2022. It is now read-only.

refactor(http): replace service/method with simple HTTP path #594

Merged
merged 15 commits into from
Aug 31, 2020
Merged
Changes from 1 commit
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
Next Next commit
refactor(http): replace service/method with simple HTTP path
neverchanje committed Aug 15, 2020
commit 08352a025c97d7177c50d7d6dad34f278ee14917
66 changes: 12 additions & 54 deletions include/dsn/http/http_server.h
Original file line number Diff line number Diff line change
@@ -21,8 +21,7 @@ struct http_request
{
static error_with<http_request> parse(dsn::message_ex *m);

// http://ip:port/<service>/<method>
std::pair<std::string, std::string> service_method;
std::string path;
// <args_name, args_val>
std::unordered_map<std::string, std::string> query_args;
blob body;
@@ -51,48 +50,24 @@ struct http_response
message_ptr to_message(message_ex *req) const;
};

typedef std::function<void(const http_request &req, http_response &resp)> http_callback;

// Defines the structure of an HTTP call.
struct http_call
{
std::string path;
std::string help;
http_callback callback;
};

class http_service
{
public:
typedef std::function<void(const http_request &req, http_response &resp)> http_callback;

virtual ~http_service() = default;

virtual std::string path() const = 0;

void register_handler(std::string path, http_callback cb, std::string help)
{
_cb_map.emplace(std::move(path), std::make_pair(std::move(cb), std::move(help)));
}

void call(const http_request &req, http_response &resp)
{
auto it = _cb_map.find(req.service_method.second);
if (it != _cb_map.end()) {
it->second.first(req, resp);
} else {
resp.status_code = http_status_code::not_found;
resp.body = std::string("method not found for \"") + req.service_method.second + "\"";
}
}

struct method_help_entry
{
std::string name;
std::string help;
};
std::vector<method_help_entry> get_help() const
{
std::vector<method_help_entry> ret;
ret.reserve(_cb_map.size());
for (const auto &method : _cb_map) {
ret.push_back({method.first, method.second.second});
}
return ret;
}

private:
std::map<std::string, std::pair<http_callback, std::string>> _cb_map;
void register_handler(std::string path, http_callback cb, std::string help);
};

class http_server : public serverlet<http_server>
@@ -106,23 +81,6 @@ class http_server : public serverlet<http_server>

void serve(message_ex *msg);

struct service_method_help_entry
{
std::string name;
std::string method;
std::string help;
};
std::vector<service_method_help_entry> get_help() const
{
std::vector<service_method_help_entry> ret;
for (const auto &service : _service_map) {
for (const auto &method : service.second->get_help()) {
ret.push_back({service.first, method.name, method.help});
}
}
return ret;
}

private:
std::map<std::string, std::unique_ptr<http_service>> _service_map;
};
67 changes: 67 additions & 0 deletions src/http/http_call_registry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

#pragma once

#include <dsn/http/http_server.h>
#include <dsn/utility/errors.h>

namespace dsn {

// A singleton registry for all the HTTP calls
class http_call_registry : public utils::singleton<http_call_registry>
neverchanje marked this conversation as resolved.
Show resolved Hide resolved
{
public:
std::shared_ptr<http_call> find(const std::string &path) const
{
std::lock_guard<std::mutex> guard(_mu);
auto it = _call_map.find(path);
if (it == _call_map.end()) {
return nullptr;
}
return it->second;
}

void remove(const std::string &path)
{
std::lock_guard<std::mutex> guard(_mu);
_call_map.erase(path);
}

void add(std::shared_ptr<http_call> call)
neverchanje marked this conversation as resolved.
Show resolved Hide resolved
{
std::lock_guard<std::mutex> guard(_mu);
_call_map[call->path] = call;
acelyc111 marked this conversation as resolved.
Show resolved Hide resolved
}

std::vector<std::shared_ptr<http_call>> list_all_calls() const
{
std::lock_guard<std::mutex> guard(_mu);

std::vector<std::shared_ptr<http_call>> ret;
for (const auto &kv : _call_map) {
ret.push_back(kv.second);
}
return ret;
}

private:
mutable std::mutex _mu;
std::map<std::string, std::shared_ptr<http_call>> _call_map;
};

} // namespace dsn
44 changes: 19 additions & 25 deletions src/http/http_server.cpp
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
#include "pprof_http_service.h"
#include "perf_counter_http_service.h"
#include "uri_decoder.h"
#include "http_call_registry.h"

namespace dsn {

@@ -34,6 +35,18 @@ namespace dsn {
}
}

void http_service::register_handler(std::string path, http_callback cb, std::string help)
{
auto call = std::make_shared<http_call>();
call->path = this->path();
if (!path.empty()) {
call->path += "/" + std::move(path);
}
call->callback = std::move(cb);
call->help = std::move(help);
http_call_registry::instance().add(std::move(call));
}

http_server::http_server(bool start /*default=true*/) : serverlet<http_server>("http_server")
{
if (!start) {
@@ -45,7 +58,7 @@ http_server::http_server(bool start /*default=true*/) : serverlet<http_server>("
tools::register_message_header_parser<http_message_parser>(NET_HDR_HTTP, {"GET ", "POST"});

// add builtin services
add_service(new root_http_service(this));
add_service(new root_http_service());

#ifdef DSN_ENABLE_GPERF
add_service(new pprof_http_service());
@@ -63,12 +76,12 @@ void http_server::serve(message_ex *msg)
resp.body = fmt::format("failed to parse request: {}", res.get_error());
} else {
const http_request &req = res.get_value();
auto it = _service_map.find(req.service_method.first);
if (it != _service_map.end()) {
it->second->call(req, resp);
std::shared_ptr<http_call> call = http_call_registry::instance().find(req.path);
if (call != nullptr) {
call->callback(req, resp);
} else {
resp.status_code = http_status_code::not_found;
resp.body = fmt::format("service not found for \"{}\"", req.service_method.first);
resp.body = fmt::format("service not found for \"{}\"", req.path);
}
}

@@ -131,26 +144,7 @@ void http_server::add_service(http_service *service)
if (!unresolved_path.empty() && *unresolved_path.crbegin() == '\0') {
unresolved_path.pop_back();
}
std::vector<std::string> args;
boost::split(args, unresolved_path, boost::is_any_of("/"));
std::vector<std::string> real_args;
for (std::string &arg : args) {
if (!arg.empty()) {
real_args.emplace_back(std::move(arg));
}
}
if (real_args.size() == 0) {
ret.service_method = {"", ""};
} else if (real_args.size() == 1) {
ret.service_method = {std::move(real_args[0]), ""};
} else {
std::string method = std::move(real_args[1]);
for (int i = 2; i < real_args.size(); i++) {
method += '/';
method += real_args[i];
}
ret.service_method = {std::move(real_args[0]), std::move(method)};
}
ret.path = std::move(unresolved_path);

// find if there are method args (<ip>:<port>/<service>/<method>?<arg>=<val>&<arg>=<val>)
if (!unresolved_query.empty()) {
15 changes: 6 additions & 9 deletions src/http/root_http_service.h
Original file line number Diff line number Diff line change
@@ -6,12 +6,14 @@
#include <dsn/utility/output_utils.h>
#include <string>

#include "http_call_registry.h"

namespace dsn {

class root_http_service : public http_service
{
public:
explicit root_http_service(http_server *server) : _server(server)
explicit root_http_service()
{
// url: ip:port/
register_handler("",
@@ -28,19 +30,14 @@ class root_http_service : public http_service
{
utils::table_printer tp;
std::ostringstream oss;
auto help_entries = _server->get_help();
for (const auto &ent : help_entries) {
tp.add_row_name_and_data(std::string("/") + ent.name + (ent.method.empty() ? "" : "/") +
ent.method,
ent.help);
auto calls = http_call_registry::instance().list_all_calls();
for (const auto &call : calls) {
tp.add_row_name_and_data(std::string("/") + call->path, call->help);
}
tp.output(oss, utils::table_printer::output_format::kJsonCompact);
resp.body = oss.str();
resp.status_code = http_status_code::ok;
}

private:
http_server *_server;
};

} // namespace dsn
26 changes: 12 additions & 14 deletions src/http/test/http_server_test.cpp
Original file line number Diff line number Diff line change
@@ -18,17 +18,17 @@ TEST(http_server, parse_url)
std::string url;

error_code err;
std::pair<std::string, std::string> result;
std::string path;
} tests[] = {
{"http://127.0.0.1:34601", ERR_OK, {"", ""}},
{"http://127.0.0.1:34601/", ERR_OK, {"", ""}},
{"http://127.0.0.1:34601///", ERR_OK, {"", ""}},
{"http://127.0.0.1:34601/threads", ERR_OK, {"threads", ""}},
{"http://127.0.0.1:34601/threads/", ERR_OK, {"threads", ""}},
{"http://127.0.0.1:34601//pprof/heap/", ERR_OK, {"pprof", "heap"}},
{"http://127.0.0.1:34601//pprof///heap", ERR_OK, {"pprof", "heap"}},
{"http://127.0.0.1:34601/pprof/heap/arg/", ERR_OK, {"pprof", "heap/arg"}},
{"http://127.0.0.1:34601/pprof///heap///arg/", ERR_OK, {"pprof", "heap/arg"}},
{"http://127.0.0.1:34601", ERR_OK, ""},
{"http://127.0.0.1:34601/", ERR_OK, "/"},
{"http://127.0.0.1:34601///", ERR_OK, "///"},
{"http://127.0.0.1:34601/threads", ERR_OK, "/threads"},
{"http://127.0.0.1:34601/threads/?detail", ERR_OK, "/threads/"},
{"http://127.0.0.1:34601//pprof/heap/", ERR_OK, "//pprof/heap/"},
{"http://127.0.0.1:34601//pprof///heap?detailed=true", ERR_OK, "//pprof///heap"},
{"http://127.0.0.1:34601/pprof/heap/arg/", ERR_OK, "/pprof/heap/arg/"},
{"http://127.0.0.1:34601/pprof///heap///arg/", ERR_OK, "/pprof///heap///arg/"},
};

for (auto tt : tests) {
@@ -38,7 +38,7 @@ TEST(http_server, parse_url)

auto res = http_request::parse(m.get());
if (res.is_ok()) {
ASSERT_EQ(res.get_value().service_method, tt.result) << tt.url;
ASSERT_EQ(res.get_value().path, tt.path) << tt.url;
} else {
ASSERT_EQ(res.get_error().code(), tt.err);
}
@@ -48,9 +48,8 @@ TEST(http_server, parse_url)
TEST(root_http_service_test, get_help)
{
http_server server(false);
auto root = new root_http_service(&server);
auto root = new root_http_service();
server.add_service(root);
ASSERT_EQ(server.get_help().size(), 1);

http_request req;
http_response resp;
@@ -60,7 +59,6 @@ TEST(root_http_service_test, get_help)

auto ver = new version_http_service();
server.add_service(ver);
ASSERT_EQ(server.get_help().size(), 2);
root->default_handler(req, resp);
ASSERT_EQ(resp.body, "{\"/\":\"ip:port/\",\"/version\":\"ip:port/version\"}\n");
}
4 changes: 1 addition & 3 deletions src/meta/meta_http_service.cpp
Original file line number Diff line number Diff line change
@@ -619,9 +619,7 @@ bool meta_http_service::redirect_if_not_primary(const http_request &req, http_re
if (_service->_failure_detector->get_leader(&leader))
return true;
// set redirect response
const std::string &service_name = req.service_method.first;
const std::string &method_name = req.service_method.second;
resp.location = "http://" + leader.to_std_string() + '/' + service_name + '/' + method_name;
resp.location = "http://" + leader.to_std_string() + '/' + req.path;
if (!req.query_args.empty()) {
resp.location += '?';
for (const auto &i : req.query_args) {