diff --git a/conanfile.py b/conanfile.py index 8895a9c..56ccd9b 100644 --- a/conanfile.py +++ b/conanfile.py @@ -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" diff --git a/src/include/iomgr/http_server.hpp b/src/include/iomgr/http_server.hpp index a2564a2..5d50953 100644 --- a/src/include/iomgr/http_server.hpp +++ b/src/include/iomgr/http_server.hpp @@ -15,6 +15,13 @@ 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(); @@ -22,9 +29,11 @@ class HttpServer { // 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(); @@ -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; @@ -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; diff --git a/src/lib/http_server.cpp b/src/lib/http_server.cpp index 26619bc..1848792 100644 --- a/src/lib/http_server.cpp +++ b/src/lib/http_server.cpp @@ -1,11 +1,26 @@ #include "iomgr/http_server.hpp" #include "iomgr_config.hpp" +#include "iomgr/io_environment.hpp" #include #include #include 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()) { @@ -13,6 +28,14 @@ HttpServer::HttpServer(std::string const& ssl_cert, std::string const& ssl_key) 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; @@ -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)); @@ -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"); @@ -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; } @@ -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 \ No newline at end of file diff --git a/src/lib/io_environment.cpp b/src/lib/io_environment.cpp index 2b29ded..00908df 100644 --- a/src/lib/io_environment.cpp +++ b/src/lib/io_environment.cpp @@ -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("", ""); } diff --git a/src/test/test_http_server.cpp b/src/test/test_http_server.cpp index 87dc75f..2d8412c 100644 --- a/src/test/test_http_server.cpp +++ b/src/test/test_http_server.cpp @@ -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(); } @@ -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 {