Extract the route related code to new class Router and add UT.
This commit is contained in:
@@ -0,0 +1,77 @@
|
|||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "webcc/router.h"
|
||||||
|
#include "webcc/response_builder.h"
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class MyView : public webcc::View {
|
||||||
|
public:
|
||||||
|
webcc::ResponsePtr Handle(webcc::RequestPtr request) override {
|
||||||
|
return webcc::ResponseBuilder{}.OK()();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TEST(RouterTest, URL_RegexBasic) {
|
||||||
|
webcc::Router router;
|
||||||
|
|
||||||
|
router.Route(webcc::R("/instance/(\\d+)"),
|
||||||
|
std::make_shared<MyView>());
|
||||||
|
|
||||||
|
std::string url = "/instance/12345";
|
||||||
|
webcc::UrlArgs args;
|
||||||
|
|
||||||
|
webcc::ViewPtr view = router.FindView("GET", url, &args);
|
||||||
|
|
||||||
|
EXPECT_TRUE(!!view);
|
||||||
|
|
||||||
|
EXPECT_EQ(1, args.size());
|
||||||
|
EXPECT_EQ("12345", args[0]);
|
||||||
|
|
||||||
|
url = "/instance/abcde";
|
||||||
|
args.clear();
|
||||||
|
view = router.FindView("GET", url, &args);
|
||||||
|
|
||||||
|
EXPECT_TRUE(!view);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RouterTest, URL_RegexMultiple) {
|
||||||
|
webcc::Router router;
|
||||||
|
|
||||||
|
router.Route(webcc::R("/study/(\\d+)/series/(\\d+)/instance/(\\d+)"),
|
||||||
|
std::make_shared<MyView>());
|
||||||
|
|
||||||
|
std::string url = "/study/1/series/2/instance/3";
|
||||||
|
webcc::UrlArgs args;
|
||||||
|
|
||||||
|
webcc::ViewPtr view = router.FindView("GET", url, &args);
|
||||||
|
|
||||||
|
EXPECT_TRUE(!!view);
|
||||||
|
|
||||||
|
EXPECT_EQ(3, args.size());
|
||||||
|
EXPECT_EQ("1", args[0]);
|
||||||
|
EXPECT_EQ("2", args[1]);
|
||||||
|
EXPECT_EQ("3", args[2]);
|
||||||
|
|
||||||
|
url = "/study/a/series/b/instance/c";
|
||||||
|
args.clear();
|
||||||
|
view = router.FindView("GET", url, &args);
|
||||||
|
|
||||||
|
EXPECT_TRUE(!view);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RouterTest, URL_NonRegex) {
|
||||||
|
webcc::Router router;
|
||||||
|
|
||||||
|
router.Route("/instances", std::make_shared<MyView>());
|
||||||
|
|
||||||
|
std::string url = "/instances";
|
||||||
|
webcc::UrlArgs args;
|
||||||
|
|
||||||
|
webcc::ViewPtr view = router.FindView("GET", url, &args);
|
||||||
|
|
||||||
|
EXPECT_TRUE(!!view);
|
||||||
|
EXPECT_TRUE(args.empty());
|
||||||
|
}
|
||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
#include "webcc/router.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "boost/algorithm/string.hpp"
|
||||||
|
|
||||||
|
#include "webcc/logger.h"
|
||||||
|
|
||||||
|
namespace webcc {
|
||||||
|
|
||||||
|
bool Router::Route(const std::string& url, ViewPtr view,
|
||||||
|
const Strings& methods) {
|
||||||
|
assert(view);
|
||||||
|
|
||||||
|
// TODO: More error check
|
||||||
|
|
||||||
|
routes_.push_back({ url, {}, view, methods });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Router::Route(const UrlRegex& regex_url, ViewPtr view,
|
||||||
|
const Strings& methods) {
|
||||||
|
assert(view);
|
||||||
|
|
||||||
|
// TODO: More error check
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
routes_.push_back({ "", regex_url(), view, methods });
|
||||||
|
|
||||||
|
} catch (const std::regex_error& e) {
|
||||||
|
LOG_ERRO("Not a valid regular expression: %s", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewPtr Router::FindView(const std::string& method, const std::string& url,
|
||||||
|
UrlArgs* args) {
|
||||||
|
assert(args != nullptr);
|
||||||
|
|
||||||
|
for (auto& route : routes_) {
|
||||||
|
if (std::find(route.methods.begin(), route.methods.end(), method) ==
|
||||||
|
route.methods.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.url.empty()) {
|
||||||
|
std::smatch match;
|
||||||
|
|
||||||
|
if (std::regex_match(url, match, route.url_regex)) {
|
||||||
|
// Any sub-matches?
|
||||||
|
// Start from 1 because match[0] is the whole string itself.
|
||||||
|
for (size_t i = 1; i < match.size(); ++i) {
|
||||||
|
args->push_back(match[i].str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return route.view;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (boost::iequals(route.url, url)) {
|
||||||
|
return route.view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ViewPtr();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Router::MatchView(const std::string& method, const std::string& url,
|
||||||
|
bool* stream) {
|
||||||
|
assert(stream != nullptr);
|
||||||
|
*stream = false;
|
||||||
|
|
||||||
|
for (auto& route : routes_) {
|
||||||
|
if (std::find(route.methods.begin(), route.methods.end(), method) ==
|
||||||
|
route.methods.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.url.empty()) {
|
||||||
|
std::smatch match;
|
||||||
|
|
||||||
|
if (std::regex_match(url, match, route.url_regex)) {
|
||||||
|
*stream = route.view->Stream(method);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (boost::iequals(route.url, url)) {
|
||||||
|
*stream = route.view->Stream(method);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webcc
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#ifndef WEBCC_ROUTER_H_
|
||||||
|
#define WEBCC_ROUTER_H_
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "webcc/globals.h"
|
||||||
|
#include "webcc/view.h"
|
||||||
|
|
||||||
|
namespace webcc {
|
||||||
|
|
||||||
|
class Router {
|
||||||
|
public:
|
||||||
|
virtual ~Router() = default;
|
||||||
|
|
||||||
|
// Route a URL to a view.
|
||||||
|
// The URL should start with "/". E.g., "/instances".
|
||||||
|
bool Route(const std::string& url, ViewPtr view,
|
||||||
|
const Strings& methods = { "GET" });
|
||||||
|
|
||||||
|
// Route a URL (as regular expression) to a view.
|
||||||
|
// The URL should start with "/" and be a regular expression.
|
||||||
|
// E.g., "/instances/(\\d+)".
|
||||||
|
bool Route(const UrlRegex& regex_url, ViewPtr view,
|
||||||
|
const Strings& methods = { "GET" });
|
||||||
|
|
||||||
|
// Find the view by HTTP method and URL (path).
|
||||||
|
ViewPtr FindView(const std::string& method, const std::string& url,
|
||||||
|
UrlArgs* args);
|
||||||
|
|
||||||
|
// Match the view by HTTP method and URL (path).
|
||||||
|
// Return if a view is matched or not.
|
||||||
|
// If the view asks for data streaming, |stream| will be set to true.
|
||||||
|
bool MatchView(const std::string& method, const std::string& url,
|
||||||
|
bool* stream);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct RouteInfo {
|
||||||
|
std::string url;
|
||||||
|
std::regex url_regex;
|
||||||
|
ViewPtr view;
|
||||||
|
Strings methods;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Route table.
|
||||||
|
std::vector<RouteInfo> routes_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webcc
|
||||||
|
|
||||||
|
#endif // WEBCC_ROUTER_H_
|
||||||
+5
-88
@@ -1,10 +1,8 @@
|
|||||||
#include "webcc/server.h"
|
#include "webcc/server.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "boost/algorithm/string.hpp"
|
|
||||||
#include "boost/filesystem/fstream.hpp"
|
#include "boost/filesystem/fstream.hpp"
|
||||||
#include "boost/filesystem/operations.hpp"
|
#include "boost/filesystem/operations.hpp"
|
||||||
|
|
||||||
@@ -12,7 +10,6 @@
|
|||||||
#include "webcc/logger.h"
|
#include "webcc/logger.h"
|
||||||
#include "webcc/request.h"
|
#include "webcc/request.h"
|
||||||
#include "webcc/response.h"
|
#include "webcc/response.h"
|
||||||
#include "webcc/url.h"
|
|
||||||
#include "webcc/utility.h"
|
#include "webcc/utility.h"
|
||||||
|
|
||||||
namespace bfs = boost::filesystem;
|
namespace bfs = boost::filesystem;
|
||||||
@@ -27,35 +24,6 @@ Server::Server(std::uint16_t port, const Path& doc_root)
|
|||||||
AddSignals();
|
AddSignals();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Server::Route(const std::string& url, ViewPtr view,
|
|
||||||
const Strings& methods) {
|
|
||||||
assert(view);
|
|
||||||
|
|
||||||
// TODO: More error check
|
|
||||||
|
|
||||||
routes_.push_back({ url, {}, view, methods });
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Server::Route(const UrlRegex& regex_url, ViewPtr view,
|
|
||||||
const Strings& methods) {
|
|
||||||
assert(view);
|
|
||||||
|
|
||||||
// TODO: More error check
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
routes_.push_back({ "", regex_url(), view, methods });
|
|
||||||
|
|
||||||
} catch (const std::regex_error& e) {
|
|
||||||
LOG_ERRO("Not a valid regular expression: %s", e.what());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Server::Run(std::size_t workers, std::size_t loops) {
|
void Server::Run(std::size_t workers, std::size_t loops) {
|
||||||
assert(workers > 0);
|
assert(workers > 0);
|
||||||
|
|
||||||
@@ -195,7 +163,8 @@ void Server::AsyncAccept() {
|
|||||||
LOG_INFO("Accepted a connection.");
|
LOG_INFO("Accepted a connection.");
|
||||||
|
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
auto view_matcher = std::bind(&Server::MatchView, this, _1, _2, _3);
|
auto view_matcher = std::bind(&Server::MatchViewOrStatic, this, _1,
|
||||||
|
_2, _3);
|
||||||
|
|
||||||
auto connection = std::make_shared<Connection>(
|
auto connection = std::make_shared<Connection>(
|
||||||
std::move(socket), &pool_, &queue_, std::move(view_matcher));
|
std::move(socket), &pool_, &queue_, std::move(view_matcher));
|
||||||
@@ -321,63 +290,11 @@ void Server::Handle(ConnectionPtr connection) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewPtr Server::FindView(const std::string& method, const std::string& url,
|
bool Server::MatchViewOrStatic(const std::string& method,
|
||||||
UrlArgs* args) {
|
const std::string& url, bool* stream) {
|
||||||
assert(args != nullptr);
|
if (Router::MatchView(method, url, stream)) {
|
||||||
|
|
||||||
for (auto& route : routes_) {
|
|
||||||
if (std::find(route.methods.begin(), route.methods.end(), method) ==
|
|
||||||
route.methods.end()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.url.empty()) {
|
|
||||||
std::smatch match;
|
|
||||||
|
|
||||||
if (std::regex_match(url, match, route.url_regex)) {
|
|
||||||
// Any sub-matches?
|
|
||||||
// Start from 1 because match[0] is the whole string itself.
|
|
||||||
for (size_t i = 1; i < match.size(); ++i) {
|
|
||||||
args->push_back(match[i].str());
|
|
||||||
}
|
|
||||||
|
|
||||||
return route.view;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (boost::iequals(route.url, url)) {
|
|
||||||
return route.view;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ViewPtr();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Server::MatchView(const std::string& method, const std::string& url,
|
|
||||||
bool* stream) {
|
|
||||||
assert(stream != nullptr);
|
|
||||||
*stream = false;
|
|
||||||
|
|
||||||
for (auto& route : routes_) {
|
|
||||||
if (std::find(route.methods.begin(), route.methods.end(), method) ==
|
|
||||||
route.methods.end()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.url.empty()) {
|
|
||||||
std::smatch match;
|
|
||||||
|
|
||||||
if (std::regex_match(url, match, route.url_regex)) {
|
|
||||||
*stream = route.view->Stream(method);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (boost::iequals(route.url, url)) {
|
|
||||||
*stream = route.view->Stream(method);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to match a static file.
|
// Try to match a static file.
|
||||||
if (method == methods::kGet && !doc_root_.empty()) {
|
if (method == methods::kGet && !doc_root_.empty()) {
|
||||||
|
|||||||
+3
-28
@@ -12,12 +12,12 @@
|
|||||||
#include "webcc/connection.h"
|
#include "webcc/connection.h"
|
||||||
#include "webcc/connection_pool.h"
|
#include "webcc/connection_pool.h"
|
||||||
#include "webcc/queue.h"
|
#include "webcc/queue.h"
|
||||||
|
#include "webcc/router.h"
|
||||||
#include "webcc/url.h"
|
#include "webcc/url.h"
|
||||||
#include "webcc/view.h"
|
|
||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
class Server {
|
class Server : public Router {
|
||||||
public:
|
public:
|
||||||
explicit Server(std::uint16_t port, const Path& doc_root = {});
|
explicit Server(std::uint16_t port, const Path& doc_root = {});
|
||||||
|
|
||||||
@@ -31,17 +31,6 @@ public:
|
|||||||
file_chunk_size_ = file_chunk_size;
|
file_chunk_size_ = file_chunk_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route a URL to a view.
|
|
||||||
// The URL should start with "/". E.g., "/instances".
|
|
||||||
bool Route(const std::string& url, ViewPtr view,
|
|
||||||
const Strings& methods = { "GET" });
|
|
||||||
|
|
||||||
// Route a URL (as regular expression) to a view.
|
|
||||||
// The URL should start with "/" and be a regular expression.
|
|
||||||
// E.g., "/instances/(\\d+)".
|
|
||||||
bool Route(const UrlRegex& regex_url, ViewPtr view,
|
|
||||||
const Strings& methods = { "GET" });
|
|
||||||
|
|
||||||
// Start and run the server.
|
// Start and run the server.
|
||||||
// This method is blocking so will not return until Stop() is called (from
|
// This method is blocking so will not return until Stop() is called (from
|
||||||
// another thread) or a signal like SIGINT is caught.
|
// another thread) or a signal like SIGINT is caught.
|
||||||
@@ -93,27 +82,16 @@ private:
|
|||||||
// request comes, this connection will be put back to the queue again.
|
// request comes, this connection will be put back to the queue again.
|
||||||
virtual void Handle(ConnectionPtr connection);
|
virtual void Handle(ConnectionPtr connection);
|
||||||
|
|
||||||
// Find the view by HTTP method and URL (path).
|
|
||||||
ViewPtr FindView(const std::string& method, const std::string& url,
|
|
||||||
UrlArgs* args);
|
|
||||||
|
|
||||||
// Match the view by HTTP method and URL (path).
|
// Match the view by HTTP method and URL (path).
|
||||||
// Return if a view or static file is matched or not.
|
// Return if a view or static file is matched or not.
|
||||||
// If the view asks for data streaming, |stream| will be set to true.
|
// If the view asks for data streaming, |stream| will be set to true.
|
||||||
bool MatchView(const std::string& method, const std::string& url,
|
bool MatchViewOrStatic(const std::string& method, const std::string& url,
|
||||||
bool* stream);
|
bool* stream);
|
||||||
|
|
||||||
// Serve static files from the doc root.
|
// Serve static files from the doc root.
|
||||||
ResponsePtr ServeStatic(RequestPtr request);
|
ResponsePtr ServeStatic(RequestPtr request);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct RouteInfo {
|
|
||||||
std::string url;
|
|
||||||
std::regex url_regex;
|
|
||||||
ViewPtr view;
|
|
||||||
Strings methods;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Port number.
|
// Port number.
|
||||||
std::uint16_t port_;
|
std::uint16_t port_;
|
||||||
|
|
||||||
@@ -147,9 +125,6 @@ private:
|
|||||||
|
|
||||||
// The queue with connection waiting for the workers to process.
|
// The queue with connection waiting for the workers to process.
|
||||||
Queue<ConnectionPtr> queue_;
|
Queue<ConnectionPtr> queue_;
|
||||||
|
|
||||||
// Route table.
|
|
||||||
std::vector<RouteInfo> routes_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|||||||
Reference in New Issue
Block a user