Skip to content

Commit

Permalink
Merge pull request eBay#100 from raakella1/trf
Browse files Browse the repository at this point in the history
Trf support for http server
  • Loading branch information
raakella1 authored Aug 30, 2024
2 parents b713df8 + 371899f commit afdb876
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 23 deletions.
2 changes: 1 addition & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class IOMgrConan(ConanFile):
name = "iomgr"
version = "11.3.10"
version = "11.3.11"

homepage = "https://github.com/eBay/IOManager"
description = "Asynchronous event manager"
Expand Down
14 changes: 14 additions & 0 deletions src/include/iomgr/http_server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,25 @@ ENUM(url_type, uint8_t,
safe, // Can be called from any host
regular);

struct http_route {
Pistache::Http::Method method;
std::string resource;
Pistache::Rest::Route::Handler handler;
iomgr::url_type type{iomgr::url_type::regular};
};

class HttpServer {
public:
HttpServer();
HttpServer(std::string const& ssl_cert, std::string const& ssl_key);

// All the routes should be setup before calling start()
void start();
void restart(std::string const& ssl_cert, std::string const& ssl_key);

void setup_route(Pistache::Http::Method method, std::string resource, Pistache::Rest::Route::Handler handler,
url_type const& type = url_type::regular);
void setup_routes(std::vector< http_route > const& routes);

void stop();

Expand All @@ -35,10 +44,13 @@ class HttpServer {
bool is_localaddr_url(std::string const& url) const;
bool is_safe_url(std::string const& url) const;
bool is_secure_zone() const;
bool auth_verify(Pistache::Http::Request& request, Pistache::Http::ResponseWriter& response) const;

private:
void get_local_ips();
bool is_local_addr(std::string const& addr) const;
void init(std::string const& ssl_cert, std::string const& ssl_key);
void setup_route(http_route const& route, bool restart);

private:
std::unique_ptr< Pistache::Http::Endpoint > m_http_endpoint;
Expand All @@ -48,6 +60,8 @@ class HttpServer {
std::unordered_set< std::string > m_safelist;
std::unordered_set< std::string > m_localhost_list;
std::unordered_set< std::string > m_local_ips;
std::vector< http_route > m_http_routes;
std::mutex m_mutex;
};

using url_t = iomgr::url_type;
Expand Down
73 changes: 69 additions & 4 deletions src/lib/http_server.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,41 @@
#include "iomgr/http_server.hpp"
#include "iomgr_config.hpp"
#include "iomgr/io_environment.hpp"
#include <sisl/logging/logging.h>
#include <ifaddrs.h>
#include <arpa/inet.h>

namespace iomgr {

static Pistache::Http::Code to_pistache_code(sisl::token_state_ptr const status) {
switch (status->code) {
case sisl::VerifyCode::OK:
return Pistache::Http::Code::Ok;
case sisl::VerifyCode::UNAUTH:
return Pistache::Http::Code::Unauthorized;
case sisl::VerifyCode::FORBIDDEN:
return Pistache::Http::Code::Forbidden;
default:
break;
}
return Pistache::Http::Code::Precondition_Failed;
}

HttpServer::HttpServer(std::string const& ssl_cert, std::string const& ssl_key) :
m_secure_zone(!ssl_cert.empty() && !ssl_key.empty()) {

if (!m_secure_zone && (!ssl_cert.empty() || !ssl_key.empty())) {
LOGERROR("one of ssl cert {}, ssl_ky: {} is empty!", ssl_cert, ssl_key);
return;
}
init(ssl_cert, ssl_key);
get_local_ips();
}

HttpServer::HttpServer() : HttpServer("", "") {}

void HttpServer::init(std::string const& ssl_cert, std::string const& ssl_key) {
m_http_endpoint.reset();
Pistache::Address addr(Pistache::Ipv4::any(), Pistache::Port(IM_DYNAMIC_CONFIG(io_env.http_port)));
m_http_endpoint = std::make_unique< Pistache::Http::Endpoint >(addr);
auto flags = Pistache::Tcp::Options::ReuseAddr;
Expand All @@ -23,11 +46,8 @@ HttpServer::HttpServer(std::string const& ssl_cert, std::string const& ssl_key)
.flags(flags);
m_http_endpoint->init(opts);
setup_ssl(ssl_cert, ssl_key);
get_local_ips();
}

HttpServer::HttpServer() : HttpServer("", "") {}

void HttpServer::start() {
// setup auth middleware
m_router.addMiddleware(Pistache::Rest::Routes::middleware(&HttpServer::do_auth, this));
Expand All @@ -38,6 +58,19 @@ void HttpServer::start() {
m_server_running = true;
}

void HttpServer::restart(std::string const& ssl_cert, std::string const& ssl_key) {
std::unique_lock< std::mutex > lock(m_mutex);
m_server_running = false;
init(ssl_cert, ssl_key);
m_localhost_list.clear();
m_safelist.clear();
m_router = Pistache::Rest::Router();
for (auto& route : m_http_routes) {
setup_route(route, true);
}
start();
}

void HttpServer::setup_route(Pistache::Http::Method method, std::string resource,
Pistache::Rest::Route::Handler handler, url_type const& type) {
DEBUG_ASSERT(!m_server_running, "Initiated route setup after server started");
Expand All @@ -55,11 +88,23 @@ void HttpServer::setup_route(Pistache::Http::Method method, std::string resource
}
}

void HttpServer::setup_route(http_route const& route, bool restart) {
if (!restart) { m_http_routes.push_back(route); }
setup_route(route.method, std::move(route.resource), std::move(route.handler), route.type);
}

void HttpServer::setup_routes(std::vector< http_route > const& routes) {
for (auto& route : routes) {
setup_route(route, false);
}
}

bool HttpServer::do_auth(Pistache::Http::Request& request, Pistache::Http::ResponseWriter& response) {
if (is_safe_url(request.resource())) { return true; }
if (is_localaddr_url(request.resource()) || m_secure_zone) { return is_local_addr(request.address().host()); }
if (is_localaddr_url(request.resource())) { return is_local_addr(request.address().host()); }

// add additional auth rules here
if (ioenvironment.get_token_verifier()) { return auth_verify(request, response); }
return true;
}

Expand Down Expand Up @@ -106,4 +151,24 @@ bool HttpServer::is_safe_url(std::string const& url) const { return m_safelist.c

bool HttpServer::is_local_addr(std::string const& addr) const { return m_local_ips.count(addr) > 0; }

bool HttpServer::auth_verify(Pistache::Http::Request& request, Pistache::Http::ResponseWriter& response) const {
// tryGet never throws
auto opt_token = request.headers().tryGet< Pistache::Http::Header::Authorization >();
if (!opt_token) {
response.send(Pistache::Http::Code::Unauthorized, "missing auth token in request header");
return false;
}

if (!opt_token->hasMethod< Pistache::Http::Header::Authorization::Method::Bearer >()) {
response.send(Pistache::Http::Code::Unauthorized, "require bearer token in request header");
return false;
}

auto const prefix_len = std::string{"Bearer "}.length();
auto ret_state = ioenvironment.get_token_verifier()->verify(opt_token->value().substr(prefix_len));
if (ret_state->code == sisl::VerifyCode::OK) { return true; }
response.send(to_pistache_code(ret_state), ret_state->msg);
return false;
}

} // namespace iomgr
8 changes: 2 additions & 6 deletions src/lib/io_environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,10 @@ IOEnvironment::~IOEnvironment() {
}

void IOEnvironment::restart_http_server(std::string const& ssl_cert, std::string const& ssl_key) {
m_http_server.reset();
with_http_server(ssl_cert, ssl_key);
m_http_server->restart(ssl_cert, ssl_key);
}

void IOEnvironment::restart_http_server() {
m_http_server.reset();
with_http_server();
}
void IOEnvironment::restart_http_server() { restart_http_server("", ""); }

IOEnvironment& IOEnvironment::with_http_server() { return with_http_server("", ""); }

Expand Down
49 changes: 37 additions & 12 deletions src/test/test_http_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,15 @@ class HTTPServerTest : public ::testing::Test {

virtual void SetUp() override {
m_server = std::make_unique< iomgr::HttpServer >();
// s_is_shutdown = false;
m_server->setup_route(Http::Method::Get, "/api/v1/sayHello", Routes::bind(&HTTPServerTest::say_hello, this));
m_server->setup_route(Http::Method::Get, "/api/v1/yourNamePlease",
Routes::bind(&HTTPServerTest::say_name, this));
m_server->setup_route(Http::Method::Post, "/api/v1/postResource/",
Routes::bind(&HTTPServerTest::post_resource, this));
m_server->setup_route(Http::Method::Get, "/api/v1/getResource",
Routes::bind(&HTTPServerTest::get_resource, this));
m_server->setup_route(Http::Method::Put, "/api/v1/putResource",
Routes::bind(&HTTPServerTest::put_resource, this));
m_server->setup_route(Http::Method::Delete, "/api/v1/deleteResource",
Routes::bind(&HTTPServerTest::delete_resource, this));
std::vector< iomgr::http_route > routes = {
{Http::Method::Get, "/api/v1/sayHello", Routes::bind(&HTTPServerTest::say_hello, this)},
{Http::Method::Get, "/api/v1/yourNamePlease", Routes::bind(&HTTPServerTest::say_name, this)},
{Http::Method::Post, "/api/v1/postResource/", Routes::bind(&HTTPServerTest::post_resource, this)},
{Http::Method::Get, "/api/v1/getResource", Routes::bind(&HTTPServerTest::get_resource, this)},
{Http::Method::Put, "/api/v1/putResource", Routes::bind(&HTTPServerTest::put_resource, this)},
{Http::Method::Delete, "/api/v1/deleteResource", Routes::bind(&HTTPServerTest::delete_resource, this)},
};
m_server->setup_routes(routes);
m_server->start();
}

Expand Down Expand Up @@ -141,6 +138,34 @@ TEST_F(HTTPServerTest, ParallelTestWithoutWait) {
// exit while server processing
}

TEST_F(HTTPServerTest, RestartTest) {
m_server->restart("", "");
const cpr::Url url{"http://127.0.0.1:5000/api/v1/sayHello"};
auto resp{cpr::Get(url)};
EXPECT_EQ(resp.status_code, cpr::status::HTTP_OK);
EXPECT_EQ(resp.text, "Hello client from async_http server\n");

static const cpr::Url url1{"http://127.0.0.1:5000/api/v1/getResource"};
resp = cpr::Get(url1);
EXPECT_EQ(resp.status_code, cpr::status::HTTP_OK);
EXPECT_EQ(resp.text, "get");

const cpr::Url url2{"http://127.0.0.1:5000/api/v1/postResource"};
resp = cpr::Post(url2);
EXPECT_EQ(resp.status_code, cpr::status::HTTP_OK);
EXPECT_EQ(resp.text, "post");

const cpr::Url url3{"http://127.0.0.1:5000/api/v1/putResource"};
resp = cpr::Put(url3);
EXPECT_EQ(resp.status_code, cpr::status::HTTP_OK);
EXPECT_EQ(resp.text, "put");

const cpr::Url url4{"http://127.0.0.1:5000/api/v1/deleteResource"};
resp = cpr::Delete(url4);
EXPECT_EQ(resp.status_code, cpr::status::HTTP_OK);
EXPECT_EQ(resp.text, "delete");
}

class HTTPServerParamsTest : public HTTPServerTest {
protected:
void SetUp() override {
Expand Down

0 comments on commit afdb876

Please sign in to comment.