Rework the server to remove rest_xxx classes.
This commit is contained in:
@@ -2,12 +2,11 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "webcc/logger.h"
|
#include "webcc/logger.h"
|
||||||
#include "webcc/rest_server.h"
|
#include "webcc/server.h"
|
||||||
#include "webcc/rest_service.h"
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
class FileUploadService : public webcc::RestService {
|
class FileUploadService : public webcc::Service {
|
||||||
public:
|
public:
|
||||||
void Handle(const webcc::RestRequest& request,
|
void Handle(const webcc::RestRequest& request,
|
||||||
webcc::RestResponse* response) override {
|
webcc::RestResponse* response) override {
|
||||||
@@ -48,7 +47,8 @@ int main(int argc, char* argv[]) {
|
|||||||
std::size_t workers = 2;
|
std::size_t workers = 2;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
webcc::RestServer server(port, workers);
|
// TODO: doc root
|
||||||
|
webcc::Server server(port, workers);
|
||||||
|
|
||||||
server.Bind(std::make_shared<FileUploadService>(), "/upload", false);
|
server.Bind(std::make_shared<FileUploadService>(), "/upload", false);
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
#include "json/json.h"
|
#include "json/json.h"
|
||||||
|
|
||||||
#include "webcc/logger.h"
|
#include "webcc/logger.h"
|
||||||
#include "webcc/rest_server.h"
|
#include "webcc/server.h"
|
||||||
#include "webcc/rest_service.h"
|
|
||||||
|
|
||||||
#include "examples/common/book.h"
|
#include "examples/common/book.h"
|
||||||
#include "examples/common/book_json.h"
|
#include "examples/common/book_json.h"
|
||||||
@@ -34,7 +33,7 @@ static void Sleep(int seconds) {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
class BookListService : public webcc::RestListService {
|
class BookListService : public webcc::ListService {
|
||||||
public:
|
public:
|
||||||
explicit BookListService(int sleep_seconds)
|
explicit BookListService(int sleep_seconds)
|
||||||
: sleep_seconds_(sleep_seconds) {
|
: sleep_seconds_(sleep_seconds) {
|
||||||
@@ -58,7 +57,7 @@ private:
|
|||||||
|
|
||||||
// The URL is like '/books/{BookID}', and the 'url_matches' parameter
|
// The URL is like '/books/{BookID}', and the 'url_matches' parameter
|
||||||
// contains the matched book ID.
|
// contains the matched book ID.
|
||||||
class BookDetailService : public webcc::RestDetailService {
|
class BookDetailService : public webcc::DetailService {
|
||||||
public:
|
public:
|
||||||
explicit BookDetailService(int sleep_seconds)
|
explicit BookDetailService(int sleep_seconds)
|
||||||
: sleep_seconds_(sleep_seconds) {
|
: sleep_seconds_(sleep_seconds) {
|
||||||
@@ -227,7 +226,8 @@ int main(int argc, char* argv[]) {
|
|||||||
std::size_t workers = 2;
|
std::size_t workers = 2;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
webcc::RestServer server(port, workers);
|
// TODO: doc root
|
||||||
|
webcc::Server server(port, workers);
|
||||||
|
|
||||||
server.Bind(std::make_shared<BookListService>(sleep_seconds),
|
server.Bind(std::make_shared<BookListService>(sleep_seconds),
|
||||||
"/books", false);
|
"/books", false);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
set(UT_SRCS
|
set(UT_SRCS
|
||||||
base64_unittest.cc
|
base64_unittest.cc
|
||||||
request_parser_unittest.cc
|
request_parser_unittest.cc
|
||||||
rest_service_manager_unittest.cc
|
service_manager_unittest.cc
|
||||||
url_unittest.cc
|
url_unittest.cc
|
||||||
utility_unittest.cc
|
utility_unittest.cc
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
#include "webcc/rest_service_manager.h"
|
#include "webcc/service_manager.h"
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
class MyRestService : public webcc::RestService {
|
class MyService : public webcc::Service {
|
||||||
public:
|
public:
|
||||||
void Handle(const webcc::RestRequest& request,
|
void Handle(const webcc::RestRequest& request,
|
||||||
webcc::RestResponse* response) override {
|
webcc::RestResponse* response) override {
|
||||||
@@ -14,16 +14,16 @@ public:
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
TEST(RestServiceManagerTest, URL_RegexBasic) {
|
TEST(ServiceManagerTest, URL_RegexBasic) {
|
||||||
webcc::RestServiceManager service_manager;
|
webcc::ServiceManager service_manager;
|
||||||
|
|
||||||
service_manager.AddService(std::make_shared<MyRestService>(),
|
service_manager.AddService(std::make_shared<MyService>(),
|
||||||
"/instance/(\\d+)", true);
|
"/instance/(\\d+)", true);
|
||||||
|
|
||||||
std::vector<std::string> matches;
|
std::vector<std::string> matches;
|
||||||
|
|
||||||
std::string url = "/instance/12345";
|
std::string url = "/instance/12345";
|
||||||
webcc::RestServicePtr service = service_manager.GetService(url, &matches);
|
webcc::ServicePtr service = service_manager.GetService(url, &matches);
|
||||||
|
|
||||||
EXPECT_TRUE(!!service);
|
EXPECT_TRUE(!!service);
|
||||||
|
|
||||||
@@ -38,16 +38,16 @@ TEST(RestServiceManagerTest, URL_RegexBasic) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RestServiceManagerTest, URL_RegexMultiple) {
|
TEST(RestServiceManagerTest, URL_RegexMultiple) {
|
||||||
webcc::RestServiceManager service_manager;
|
webcc::ServiceManager service_manager;
|
||||||
|
|
||||||
service_manager.AddService(std::make_shared<MyRestService>(),
|
service_manager.AddService(std::make_shared<MyService>(),
|
||||||
"/study/(\\d+)/series/(\\d+)/instance/(\\d+)",
|
"/study/(\\d+)/series/(\\d+)/instance/(\\d+)",
|
||||||
true);
|
true);
|
||||||
|
|
||||||
std::vector<std::string> matches;
|
std::vector<std::string> matches;
|
||||||
|
|
||||||
std::string url = "/study/1/series/2/instance/3";
|
std::string url = "/study/1/series/2/instance/3";
|
||||||
webcc::RestServicePtr service = service_manager.GetService(url, &matches);
|
webcc::ServicePtr service = service_manager.GetService(url, &matches);
|
||||||
|
|
||||||
EXPECT_TRUE(!!service);
|
EXPECT_TRUE(!!service);
|
||||||
|
|
||||||
@@ -64,14 +64,14 @@ TEST(RestServiceManagerTest, URL_RegexMultiple) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(RestServiceManagerTest, URL_NonRegex) {
|
TEST(RestServiceManagerTest, URL_NonRegex) {
|
||||||
webcc::RestServiceManager service_manager;
|
webcc::ServiceManager service_manager;
|
||||||
|
|
||||||
service_manager.AddService(std::make_shared<MyRestService>(), "/instances",
|
service_manager.AddService(std::make_shared<MyService>(), "/instances",
|
||||||
false);
|
false);
|
||||||
|
|
||||||
std::vector<std::string> matches;
|
std::vector<std::string> matches;
|
||||||
std::string url = "/instances";
|
std::string url = "/instances";
|
||||||
webcc::RestServicePtr service = service_manager.GetService(url, &matches);
|
webcc::ServicePtr service = service_manager.GetService(url, &matches);
|
||||||
|
|
||||||
EXPECT_TRUE(!!service);
|
EXPECT_TRUE(!!service);
|
||||||
EXPECT_EQ(0, matches.size());
|
EXPECT_EQ(0, matches.size());
|
||||||
+2
-1
@@ -214,7 +214,8 @@ FormPart::FormPart(const std::string& name, const Path& path,
|
|||||||
// Determine media type from file extension.
|
// Determine media type from file extension.
|
||||||
if (media_type_.empty()) {
|
if (media_type_.empty()) {
|
||||||
std::string extension = path.extension().string();
|
std::string extension = path.extension().string();
|
||||||
media_type_ = media_types::FromExtension(extension, false);
|
// TODO: Default to "application/text"?
|
||||||
|
media_type_ = media_types::FromExtension(extension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,6 @@
|
|||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
ConnectionPool::ConnectionPool() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConnectionPool::Start(ConnectionPtr c) {
|
void ConnectionPool::Start(ConnectionPtr c) {
|
||||||
LOG_VERB("Starting connection...");
|
LOG_VERB("Starting connection...");
|
||||||
connections_.insert(c);
|
connections_.insert(c);
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ namespace webcc {
|
|||||||
|
|
||||||
class ConnectionPool {
|
class ConnectionPool {
|
||||||
public:
|
public:
|
||||||
|
ConnectionPool() = default;
|
||||||
|
|
||||||
ConnectionPool(const ConnectionPool&) = delete;
|
ConnectionPool(const ConnectionPool&) = delete;
|
||||||
ConnectionPool& operator=(const ConnectionPool&) = delete;
|
ConnectionPool& operator=(const ConnectionPool&) = delete;
|
||||||
|
|
||||||
ConnectionPool();
|
|
||||||
|
|
||||||
// Add a connection to the pool and start it.
|
// Add a connection to the pool and start it.
|
||||||
void Start(ConnectionPtr c);
|
void Start(ConnectionPtr c);
|
||||||
|
|
||||||
@@ -24,7 +24,6 @@ public:
|
|||||||
void CloseAll();
|
void CloseAll();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// The managed connections.
|
|
||||||
std::set<ConnectionPtr> connections_;
|
std::set<ConnectionPtr> connections_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+26
-30
@@ -1,7 +1,8 @@
|
|||||||
#include "webcc/globals.h"
|
#include "webcc/globals.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <map>
|
|
||||||
|
#include "boost/algorithm/string.hpp"
|
||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
@@ -9,37 +10,32 @@ namespace webcc {
|
|||||||
|
|
||||||
namespace media_types {
|
namespace media_types {
|
||||||
|
|
||||||
// TODO: Add more.
|
std::string FromExtension(const std::string& ext) {
|
||||||
static void InitMap(std::map<std::string, std::string>& map) {
|
using boost::iequals;
|
||||||
map["gif"] = "image/gif";
|
|
||||||
map["htm"] = "text/html";
|
|
||||||
map["html"] = "text/html";
|
|
||||||
map["jpg"] = "image/jpeg";
|
|
||||||
map["jpeg"] = "image/jpeg";
|
|
||||||
map["png"] = "image/png";
|
|
||||||
map["txt"] = "text/plain";
|
|
||||||
map[""] = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Ignore case on Windows.
|
if (iequals(ext, ".htm")) { return "text/html"; }
|
||||||
std::string FromExtension(const std::string& extension,
|
if (iequals(ext, ".html")) { return "text/html"; }
|
||||||
bool default_to_plain_text) {
|
if (iequals(ext, ".php")) { return "text/html"; }
|
||||||
static std::map<std::string, std::string> s_map;
|
if (iequals(ext, ".css")) { return "text/css"; }
|
||||||
|
if (iequals(ext, ".txt")) { return "text/plain"; }
|
||||||
|
if (iequals(ext, ".js")) { return "application/javascript"; }
|
||||||
|
if (iequals(ext, ".json")) { return "application/json"; }
|
||||||
|
if (iequals(ext, ".xml")) { return "application/xml"; }
|
||||||
|
if (iequals(ext, ".swf")) { return "application/x-shockwave-flash"; }
|
||||||
|
if (iequals(ext, ".flv")) { return "video/x-flv"; }
|
||||||
|
if (iequals(ext, ".png")) { return "image/png"; }
|
||||||
|
if (iequals(ext, ".jpe")) { return "image/jpeg"; }
|
||||||
|
if (iequals(ext, ".jpeg")) { return "image/jpeg"; }
|
||||||
|
if (iequals(ext, ".jpg")) { return "image/jpeg"; }
|
||||||
|
if (iequals(ext, ".gif")) { return "image/gif"; }
|
||||||
|
if (iequals(ext, ".bmp")) { return "image/bmp"; }
|
||||||
|
if (iequals(ext, ".ico")) { return "image/vnd.microsoft.icon"; }
|
||||||
|
if (iequals(ext, ".tiff")) { return "image/tiff"; }
|
||||||
|
if (iequals(ext, ".tif")) { return "image/tiff"; }
|
||||||
|
if (iequals(ext, ".svg")) { return "image/svg+xml"; }
|
||||||
|
if (iequals(ext, ".svgz")) { return "image/svg+xml"; }
|
||||||
|
|
||||||
if (s_map.empty()) {
|
return "application/text";
|
||||||
InitMap(s_map);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = s_map.find(extension);
|
|
||||||
if (it != s_map.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (default_to_plain_text) {
|
|
||||||
return "text/plain";
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace media_types
|
} // namespace media_types
|
||||||
|
|||||||
+1
-2
@@ -131,8 +131,7 @@ const char* const kTextPlain = "text/plain";
|
|||||||
const char* const kTextXml = "text/xml";
|
const char* const kTextXml = "text/xml";
|
||||||
|
|
||||||
// Get media type from file extension.
|
// Get media type from file extension.
|
||||||
std::string FromExtension(const std::string& extension,
|
std::string FromExtension(const std::string& ext);
|
||||||
bool default_to_plain_text = true);
|
|
||||||
|
|
||||||
} // namespace media_types
|
} // namespace media_types
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -248,7 +248,7 @@ void Log(int level, const char* file, int line, const char* format, ...) {
|
|||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
|
|
||||||
fprintf(g_logger.file, "%s, %s, %7s, %24s, %4d, ",
|
fprintf(g_logger.file, "%s, %s, %7s, %20s, %4d, ",
|
||||||
timestamp.c_str(), kLevelNames[level], thread_id.c_str(),
|
timestamp.c_str(), kLevelNames[level], thread_id.c_str(),
|
||||||
file, line);
|
file, line);
|
||||||
|
|
||||||
@@ -271,12 +271,12 @@ void Log(int level, const char* file, int line, const char* format, ...) {
|
|||||||
|
|
||||||
if (g_colorlogtostderr && g_terminal_has_color) {
|
if (g_colorlogtostderr && g_terminal_has_color) {
|
||||||
if (level < WEBCC_WARN) {
|
if (level < WEBCC_WARN) {
|
||||||
fprintf(stderr, "%s%s, %s, %7s, %25s, %4d, ",
|
fprintf(stderr, "%s%s, %s, %7s, %20s, %4d, ",
|
||||||
TerminalReset(),
|
TerminalReset(),
|
||||||
timestamp.c_str(), kLevelNames[level], thread_id.c_str(),
|
timestamp.c_str(), kLevelNames[level], thread_id.c_str(),
|
||||||
file, line);
|
file, line);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "%s%s%s, %s, %7s, %25s, %4d, ",
|
fprintf(stderr, "%s%s%s, %s, %7s, %20s, %4d, ",
|
||||||
TerminalReset(),
|
TerminalReset(),
|
||||||
level == WEBCC_WARN ? TerminalYellow() : TerminalRed(),
|
level == WEBCC_WARN ? TerminalYellow() : TerminalRed(),
|
||||||
timestamp.c_str(), kLevelNames[level], thread_id.c_str(),
|
timestamp.c_str(), kLevelNames[level], thread_id.c_str(),
|
||||||
@@ -287,7 +287,7 @@ void Log(int level, const char* file, int line, const char* format, ...) {
|
|||||||
|
|
||||||
fprintf(stderr, "%s\n", TerminalReset());
|
fprintf(stderr, "%s\n", TerminalReset());
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "%s, %s, %7s, %25s, %4d, ",
|
fprintf(stderr, "%s, %s, %7s, %20s, %4d, ",
|
||||||
timestamp.c_str(), kLevelNames[level], thread_id.c_str(),
|
timestamp.c_str(), kLevelNames[level], thread_id.c_str(),
|
||||||
file, line);
|
file, line);
|
||||||
|
|
||||||
|
|||||||
+14
-14
@@ -44,22 +44,11 @@ void Request::Prepare() {
|
|||||||
|
|
||||||
Payload data_payload;
|
Payload data_payload;
|
||||||
|
|
||||||
using boost::asio::buffer;
|
|
||||||
|
|
||||||
for (auto& part : form_parts_) {
|
for (auto& part : form_parts_) {
|
||||||
// Boundary
|
AddBoundary(data_payload);
|
||||||
data_payload.push_back(buffer(misc_strings::DOUBLE_DASHES));
|
|
||||||
data_payload.push_back(buffer(boundary_));
|
|
||||||
data_payload.push_back(buffer(misc_strings::CRLF));
|
|
||||||
|
|
||||||
part->Prepare(&data_payload);
|
part->Prepare(&data_payload);
|
||||||
}
|
}
|
||||||
|
AddBoundary(data_payload, true);
|
||||||
// Boundary end
|
|
||||||
data_payload.push_back(buffer(misc_strings::DOUBLE_DASHES));
|
|
||||||
data_payload.push_back(buffer(boundary_));
|
|
||||||
data_payload.push_back(buffer(misc_strings::DOUBLE_DASHES));
|
|
||||||
data_payload.push_back(buffer(misc_strings::CRLF));
|
|
||||||
|
|
||||||
// Update Content-Length header.
|
// Update Content-Length header.
|
||||||
std::size_t content_length = 0;
|
std::size_t content_length = 0;
|
||||||
@@ -84,7 +73,7 @@ void Request::CreateStartLine() {
|
|||||||
throw Error{ Error::kSyntaxError, "Host is missing" };
|
throw Error{ Error::kSyntaxError, "Host is missing" };
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string target = "/" + url_.path();
|
std::string target = url_.path();
|
||||||
if (!url_.query().empty()) {
|
if (!url_.query().empty()) {
|
||||||
target += "?";
|
target += "?";
|
||||||
target += url_.query();
|
target += url_.query();
|
||||||
@@ -96,4 +85,15 @@ void Request::CreateStartLine() {
|
|||||||
start_line_ += " HTTP/1.1";
|
start_line_ += " HTTP/1.1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Request::AddBoundary(Payload& payload, bool end) {
|
||||||
|
using boost::asio::buffer;
|
||||||
|
|
||||||
|
payload.push_back(buffer(misc_strings::DOUBLE_DASHES));
|
||||||
|
payload.push_back(buffer(boundary_));
|
||||||
|
if (end) {
|
||||||
|
payload.push_back(buffer(misc_strings::DOUBLE_DASHES));
|
||||||
|
}
|
||||||
|
payload.push_back(buffer(misc_strings::CRLF));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
void CreateStartLine();
|
void CreateStartLine();
|
||||||
|
|
||||||
|
// Add boundary to the payload for multipart form data.
|
||||||
|
void AddBoundary(Payload& payload, bool end = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string method_;
|
std::string method_;
|
||||||
|
|
||||||
|
|||||||
+121
-4
@@ -1,14 +1,29 @@
|
|||||||
#include "webcc/request_handler.h"
|
#include "webcc/request_handler.h"
|
||||||
|
|
||||||
#include <sstream>
|
#include <fstream>
|
||||||
|
#include <utility> // for move()
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "webcc/globals.h"
|
#include "webcc/logger.h"
|
||||||
#include "webcc/request.h"
|
#include "webcc/request.h"
|
||||||
#include "webcc/response.h"
|
#include "webcc/response.h"
|
||||||
#include "webcc/logger.h"
|
#include "webcc/url.h"
|
||||||
|
|
||||||
|
#if WEBCC_ENABLE_GZIP
|
||||||
|
#include "webcc/gzip.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
|
RequestHandler::RequestHandler(const std::string& doc_root)
|
||||||
|
: doc_root_(doc_root) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RequestHandler::Bind(ServicePtr service, const std::string& url,
|
||||||
|
bool is_regex) {
|
||||||
|
return service_manager_.AddService(service, url, is_regex);
|
||||||
|
}
|
||||||
|
|
||||||
void RequestHandler::Enqueue(ConnectionPtr connection) {
|
void RequestHandler::Enqueue(ConnectionPtr connection) {
|
||||||
queue_.Push(connection);
|
queue_.Push(connection);
|
||||||
}
|
}
|
||||||
@@ -45,7 +60,7 @@ void RequestHandler::WorkerRoutine() {
|
|||||||
LOG_INFO("Worker is running.");
|
LOG_INFO("Worker is running.");
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
ConnectionPtr connection = queue_.PopOrWait();
|
auto connection = queue_.PopOrWait();
|
||||||
|
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
LOG_INFO("Worker is going to stop.");
|
LOG_INFO("Worker is going to stop.");
|
||||||
@@ -61,4 +76,106 @@ void RequestHandler::WorkerRoutine() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RequestHandler::HandleConnection(ConnectionPtr connection) {
|
||||||
|
auto request = connection->request();
|
||||||
|
|
||||||
|
const Url& url = request->url();
|
||||||
|
|
||||||
|
RestRequest rest_request{ request };
|
||||||
|
|
||||||
|
LOG_INFO("Request URL path: %s", url.path().c_str());
|
||||||
|
|
||||||
|
// Get service by URL path.
|
||||||
|
auto service = service_manager_.GetService(url.path(),
|
||||||
|
&rest_request.url_matches);
|
||||||
|
|
||||||
|
if (!service) {
|
||||||
|
LOG_WARN("No service matches the URL path: %s", url.path().c_str());
|
||||||
|
|
||||||
|
if (!ServeStatic(connection)) {
|
||||||
|
connection->SendResponse(Status::kNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RestResponse rest_response;
|
||||||
|
service->Handle(rest_request, &rest_response);
|
||||||
|
|
||||||
|
auto response = std::make_shared<Response>(rest_response.status);
|
||||||
|
|
||||||
|
if (!rest_response.content.empty()) {
|
||||||
|
if (!rest_response.media_type.empty()) {
|
||||||
|
response->SetContentType(rest_response.media_type, rest_response.charset);
|
||||||
|
}
|
||||||
|
SetContent(request, response, std::move(rest_response.content));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send response back to client.
|
||||||
|
connection->SendResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RequestHandler::ServeStatic(ConnectionPtr connection) {
|
||||||
|
auto request = connection->request();
|
||||||
|
std::string path = request->url().path();
|
||||||
|
|
||||||
|
// If path ends in slash (i.e. is a directory) then add "index.html".
|
||||||
|
if (path[path.size() - 1] == '/') {
|
||||||
|
path += "index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the file extension.
|
||||||
|
std::string extension;
|
||||||
|
std::size_t last_slash_pos = path.find_last_of("/");
|
||||||
|
std::size_t last_dot_pos = path.find_last_of(".");
|
||||||
|
if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos) {
|
||||||
|
extension = path.substr(last_dot_pos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the file to send back.
|
||||||
|
std::string full_path = doc_root_ + path;
|
||||||
|
std::ifstream ifs(full_path.c_str(), std::ios::in | std::ios::binary);
|
||||||
|
if (!ifs) {
|
||||||
|
// The file doesn't exist.
|
||||||
|
connection->SendResponse(Status::kNotFound);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill out the content to be sent to the client.
|
||||||
|
std::string content;
|
||||||
|
char buf[512];
|
||||||
|
while (ifs.read(buf, sizeof(buf)).gcount() > 0) {
|
||||||
|
content.append(buf, ifs.gcount());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto response = std::make_shared<Response>(Status::kOK);
|
||||||
|
|
||||||
|
if (!content.empty()) {
|
||||||
|
response->SetContentType(media_types::FromExtension(extension), "");
|
||||||
|
response->SetContent(std::move(content), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send response back to client.
|
||||||
|
connection->SendResponse(response);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequestHandler::SetContent(RequestPtr request, ResponsePtr response,
|
||||||
|
std::string&& content) {
|
||||||
|
#if WEBCC_ENABLE_GZIP
|
||||||
|
// Only support gzip (no deflate) for response compression.
|
||||||
|
if (content.size() > kGzipThreshold && request->AcceptEncodingGzip()) {
|
||||||
|
std::string compressed;
|
||||||
|
if (gzip::Compress(content, &compressed)) {
|
||||||
|
response->SetHeader(headers::kContentEncoding, "gzip");
|
||||||
|
response->SetContent(std::move(compressed), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // WEBCC_ENABLE_GZIP
|
||||||
|
|
||||||
|
response->SetContent(std::move(content), true);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|||||||
+22
-6
@@ -7,21 +7,22 @@
|
|||||||
|
|
||||||
#include "webcc/connection.h"
|
#include "webcc/connection.h"
|
||||||
#include "webcc/queue.h"
|
#include "webcc/queue.h"
|
||||||
|
#include "webcc/service_manager.h"
|
||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
class Request;
|
|
||||||
class Response;
|
|
||||||
|
|
||||||
// The common handler for all incoming requests.
|
// The common handler for all incoming requests.
|
||||||
class RequestHandler {
|
class RequestHandler {
|
||||||
public:
|
public:
|
||||||
RequestHandler() = default;
|
explicit RequestHandler(const std::string& doc_root);
|
||||||
|
|
||||||
virtual ~RequestHandler() = default;
|
virtual ~RequestHandler() = default;
|
||||||
|
|
||||||
RequestHandler(const RequestHandler&) = delete;
|
RequestHandler(const RequestHandler&) = delete;
|
||||||
RequestHandler& operator=(const RequestHandler&) = delete;
|
RequestHandler& operator=(const RequestHandler&) = delete;
|
||||||
|
|
||||||
|
bool Bind(ServicePtr service, const std::string& url, bool is_regex);
|
||||||
|
|
||||||
// Put the connection into the queue.
|
// Put the connection into the queue.
|
||||||
void Enqueue(ConnectionPtr connection);
|
void Enqueue(ConnectionPtr connection);
|
||||||
|
|
||||||
@@ -34,13 +35,28 @@ public:
|
|||||||
private:
|
private:
|
||||||
void WorkerRoutine();
|
void WorkerRoutine();
|
||||||
|
|
||||||
// Called by the worker routine.
|
// Handle a connection (or more precisely, the request inside it).
|
||||||
virtual void HandleConnection(ConnectionPtr connection) = 0;
|
// Get the request from the connection, process it, prepare the response,
|
||||||
|
// then send the response back to the client.
|
||||||
|
// The connection will keep alive if it's a persistent connection. When next
|
||||||
|
// request comes, this connection will be put back to the queue again.
|
||||||
|
virtual void HandleConnection(ConnectionPtr connection);
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
bool ServeStatic(ConnectionPtr connection);
|
||||||
|
|
||||||
|
void SetContent(RequestPtr request, ResponsePtr response,
|
||||||
|
std::string&& content);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// The directory containing the files to be served.
|
||||||
|
std::string doc_root_;
|
||||||
|
|
||||||
Queue<ConnectionPtr> queue_;
|
Queue<ConnectionPtr> queue_;
|
||||||
|
|
||||||
std::vector<std::thread> workers_;
|
std::vector<std::thread> workers_;
|
||||||
|
|
||||||
|
ServiceManager service_manager_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
#include "webcc/rest_request_handler.h"
|
|
||||||
|
|
||||||
#include <utility> // for move()
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "webcc/logger.h"
|
|
||||||
#include "webcc/url.h"
|
|
||||||
|
|
||||||
#if WEBCC_ENABLE_GZIP
|
|
||||||
#include "webcc/gzip.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace webcc {
|
|
||||||
|
|
||||||
bool RestRequestHandler::Bind(RestServicePtr service, const std::string& url,
|
|
||||||
bool is_regex) {
|
|
||||||
return service_manager_.AddService(service, url, is_regex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RestRequestHandler::HandleConnection(ConnectionPtr connection) {
|
|
||||||
RequestPtr request = connection->request();
|
|
||||||
|
|
||||||
const Url& url = request->url();
|
|
||||||
|
|
||||||
RestRequest rest_request{ request };
|
|
||||||
|
|
||||||
// Get service by URL path.
|
|
||||||
std::string path = "/" + url.path();
|
|
||||||
auto service = service_manager_.GetService(path, &rest_request.url_matches);
|
|
||||||
|
|
||||||
if (!service) {
|
|
||||||
LOG_WARN("No service matches the URL path: %s", url.path().c_str());
|
|
||||||
connection->SendResponse(Status::kNotFound);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RestResponse rest_response;
|
|
||||||
service->Handle(rest_request, &rest_response);
|
|
||||||
|
|
||||||
auto response = std::make_shared<Response>(rest_response.status);
|
|
||||||
|
|
||||||
if (!rest_response.content.empty()) {
|
|
||||||
if (!rest_response.media_type.empty()) {
|
|
||||||
response->SetContentType(rest_response.media_type, rest_response.charset);
|
|
||||||
}
|
|
||||||
SetContent(request, response, std::move(rest_response.content));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send response back to client.
|
|
||||||
connection->SendResponse(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RestRequestHandler::SetContent(RequestPtr request, ResponsePtr response,
|
|
||||||
std::string&& content) {
|
|
||||||
#if WEBCC_ENABLE_GZIP
|
|
||||||
// Only support gzip (no deflate) for response compression.
|
|
||||||
if (content.size() > kGzipThreshold && request->AcceptEncodingGzip()) {
|
|
||||||
std::string compressed;
|
|
||||||
if (gzip::Compress(content, &compressed)) {
|
|
||||||
response->SetHeader(headers::kContentEncoding, "gzip");
|
|
||||||
response->SetContent(std::move(compressed), true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // WEBCC_ENABLE_GZIP
|
|
||||||
|
|
||||||
response->SetContent(std::move(content), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace webcc
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
#ifndef WEBCC_REST_REQUEST_HANDLER_H_
|
|
||||||
#define WEBCC_REST_REQUEST_HANDLER_H_
|
|
||||||
|
|
||||||
// HTTP server handling REST requests.
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "webcc/request_handler.h"
|
|
||||||
#include "webcc/rest_service_manager.h"
|
|
||||||
|
|
||||||
namespace webcc {
|
|
||||||
|
|
||||||
class RestRequestHandler : public RequestHandler {
|
|
||||||
public:
|
|
||||||
RestRequestHandler() = default;
|
|
||||||
|
|
||||||
~RestRequestHandler() override = default;
|
|
||||||
|
|
||||||
bool Bind(RestServicePtr service, const std::string& url, bool is_regex);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void HandleConnection(ConnectionPtr connection) override;
|
|
||||||
|
|
||||||
void SetContent(RequestPtr request, ResponsePtr response,
|
|
||||||
std::string&& content);
|
|
||||||
|
|
||||||
private:
|
|
||||||
RestServiceManager service_manager_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace webcc
|
|
||||||
|
|
||||||
#endif // WEBCC_REST_REQUEST_HANDLER_H_
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#ifndef WEBCC_REST_SERVER_H_
|
|
||||||
#define WEBCC_REST_SERVER_H_
|
|
||||||
|
|
||||||
// HTTP server handling REST requests.
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "webcc/server.h"
|
|
||||||
#include "webcc/rest_request_handler.h"
|
|
||||||
#include "webcc/rest_service.h"
|
|
||||||
|
|
||||||
namespace webcc {
|
|
||||||
|
|
||||||
class RestServer : public Server {
|
|
||||||
public:
|
|
||||||
RestServer(std::uint16_t port, std::size_t workers)
|
|
||||||
: Server(port, workers) {
|
|
||||||
}
|
|
||||||
|
|
||||||
~RestServer() override = default;
|
|
||||||
|
|
||||||
// Bind a REST service to the given URL path.
|
|
||||||
// The URL should start with "/" and it will be treated as a regular
|
|
||||||
// expression if |is_regex| is true.
|
|
||||||
// Examples:
|
|
||||||
// - "/instances"
|
|
||||||
// - "/instances/(\\d+)"
|
|
||||||
// Binding to the same URL multiple times is allowed, but only the last one
|
|
||||||
// takes effect.
|
|
||||||
bool Bind(RestServicePtr service, const std::string& url, bool is_regex) {
|
|
||||||
return request_handler_.Bind(service, url, is_regex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
RequestHandler* GetRequestHandler() override {
|
|
||||||
return &request_handler_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
RestRequestHandler request_handler_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace webcc
|
|
||||||
|
|
||||||
#endif // WEBCC_REST_SERVER_H_
|
|
||||||
+11
-7
@@ -11,8 +11,10 @@ using tcp = boost::asio::ip::tcp;
|
|||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
Server::Server(std::uint16_t port, std::size_t workers)
|
Server::Server(std::uint16_t port, std::size_t workers,
|
||||||
: acceptor_(io_context_), signals_(io_context_), workers_(workers) {
|
const std::string& doc_root)
|
||||||
|
: acceptor_(io_context_), signals_(io_context_), workers_(workers),
|
||||||
|
request_handler_(doc_root) {
|
||||||
RegisterSignals();
|
RegisterSignals();
|
||||||
|
|
||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
@@ -50,9 +52,11 @@ Server::Server(std::uint16_t port, std::size_t workers)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::Run() {
|
bool Server::Bind(ServicePtr service, const std::string& url, bool is_regex) {
|
||||||
assert(GetRequestHandler() != nullptr);
|
return request_handler_.Bind(service, url, is_regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::Run() {
|
||||||
if (!acceptor_.is_open()) {
|
if (!acceptor_.is_open()) {
|
||||||
LOG_ERRO("Server is NOT going to run.");
|
LOG_ERRO("Server is NOT going to run.");
|
||||||
return;
|
return;
|
||||||
@@ -65,7 +69,7 @@ void Server::Run() {
|
|||||||
DoAccept();
|
DoAccept();
|
||||||
|
|
||||||
// Start worker threads.
|
// Start worker threads.
|
||||||
GetRequestHandler()->Start(workers_);
|
request_handler_.Start(workers_);
|
||||||
|
|
||||||
// The io_context::run() call will block until all asynchronous operations
|
// The io_context::run() call will block until all asynchronous operations
|
||||||
// have finished. While the server is running, there is always at least one
|
// have finished. While the server is running, there is always at least one
|
||||||
@@ -96,7 +100,7 @@ void Server::DoAccept() {
|
|||||||
LOG_INFO("Accepted a connection.");
|
LOG_INFO("Accepted a connection.");
|
||||||
|
|
||||||
auto connection = std::make_shared<Connection>(
|
auto connection = std::make_shared<Connection>(
|
||||||
std::move(socket), &pool_, GetRequestHandler());
|
std::move(socket), &pool_, &request_handler_);
|
||||||
|
|
||||||
pool_.Start(connection);
|
pool_.Start(connection);
|
||||||
}
|
}
|
||||||
@@ -116,7 +120,7 @@ void Server::DoAwaitStop() {
|
|||||||
acceptor_.close();
|
acceptor_.close();
|
||||||
|
|
||||||
// Stop worker threads.
|
// Stop worker threads.
|
||||||
GetRequestHandler()->Stop();
|
request_handler_.Stop();
|
||||||
|
|
||||||
// Close all connections.
|
// Close all connections.
|
||||||
pool_.CloseAll();
|
pool_.CloseAll();
|
||||||
|
|||||||
+19
-6
@@ -7,25 +7,35 @@
|
|||||||
#include "boost/asio/ip/tcp.hpp"
|
#include "boost/asio/ip/tcp.hpp"
|
||||||
#include "boost/asio/signal_set.hpp"
|
#include "boost/asio/signal_set.hpp"
|
||||||
|
|
||||||
#include "webcc/globals.h"
|
|
||||||
#include "webcc/connection.h"
|
#include "webcc/connection.h"
|
||||||
#include "webcc/connection_pool.h"
|
#include "webcc/connection_pool.h"
|
||||||
|
#include "webcc/request_handler.h"
|
||||||
|
#include "webcc/service.h"
|
||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
class RequestHandler;
|
|
||||||
|
|
||||||
// HTTP server accepts TCP connections from TCP clients.
|
// HTTP server accepts TCP connections from TCP clients.
|
||||||
// NOTE: Only support IPv4.
|
// NOTE: Only support IPv4.
|
||||||
class Server {
|
class Server {
|
||||||
public:
|
public:
|
||||||
Server(std::uint16_t port, std::size_t workers);
|
Server(std::uint16_t port, std::size_t workers,
|
||||||
|
const std::string& doc_root = "");
|
||||||
|
|
||||||
virtual ~Server() = default;
|
virtual ~Server() = default;
|
||||||
|
|
||||||
Server(const Server&) = delete;
|
Server(const Server&) = delete;
|
||||||
Server& operator=(const Server&) = delete;
|
Server& operator=(const Server&) = delete;
|
||||||
|
|
||||||
|
// Bind a service to the given URL path.
|
||||||
|
// The URL should start with "/" and it will be treated as a regular
|
||||||
|
// expression if |is_regex| is true.
|
||||||
|
// Examples:
|
||||||
|
// - "/instances"
|
||||||
|
// - "/instances/(\\d+)"
|
||||||
|
// Binding to the same URL multiple times is allowed, but only the last one
|
||||||
|
// takes effect.
|
||||||
|
bool Bind(ServicePtr service, const std::string& url, bool is_regex);
|
||||||
|
|
||||||
// Run the server's io_service loop.
|
// Run the server's io_service loop.
|
||||||
void Run();
|
void Run();
|
||||||
|
|
||||||
@@ -40,7 +50,7 @@ private:
|
|||||||
void DoAwaitStop();
|
void DoAwaitStop();
|
||||||
|
|
||||||
// Get the handler for incoming requests.
|
// Get the handler for incoming requests.
|
||||||
virtual RequestHandler* GetRequestHandler() = 0;
|
//virtual RequestHandler* GetRequestHandler();
|
||||||
|
|
||||||
// The io_context used to perform asynchronous operations.
|
// The io_context used to perform asynchronous operations.
|
||||||
boost::asio::io_context io_context_;
|
boost::asio::io_context io_context_;
|
||||||
@@ -51,11 +61,14 @@ private:
|
|||||||
// The connection pool which owns all live connections.
|
// The connection pool which owns all live connections.
|
||||||
ConnectionPool pool_;
|
ConnectionPool pool_;
|
||||||
|
|
||||||
// The signal_set is used to register for process termination notifications.
|
// The signals for processing termination notifications.
|
||||||
boost::asio::signal_set signals_;
|
boost::asio::signal_set signals_;
|
||||||
|
|
||||||
// The number of worker threads.
|
// The number of worker threads.
|
||||||
std::size_t workers_;
|
std::size_t workers_;
|
||||||
|
|
||||||
|
// The handler for incoming requests.
|
||||||
|
RequestHandler request_handler_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "webcc/rest_service.h"
|
#include "webcc/service.h"
|
||||||
|
|
||||||
#include "webcc/logger.h"
|
#include "webcc/logger.h"
|
||||||
|
|
||||||
@@ -6,8 +6,7 @@ namespace webcc {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
void RestListService::Handle(const RestRequest& request,
|
void ListService::Handle(const RestRequest& request, RestResponse* response) {
|
||||||
RestResponse* response) {
|
|
||||||
const std::string& method = request.http->method();
|
const std::string& method = request.http->method();
|
||||||
|
|
||||||
if (method == methods::kGet) {
|
if (method == methods::kGet) {
|
||||||
@@ -17,14 +16,13 @@ void RestListService::Handle(const RestRequest& request,
|
|||||||
Post(request.http->content(), response);
|
Post(request.http->content(), response);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
LOG_ERRO("RestListService doesn't support '%s' method.", method.c_str());
|
LOG_ERRO("ListService doesn't support '%s' method.", method.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
void RestDetailService::Handle(const RestRequest& request,
|
void DetailService::Handle(const RestRequest& request, RestResponse* response) {
|
||||||
RestResponse* response) {
|
|
||||||
const std::string& method = request.http->method();
|
const std::string& method = request.http->method();
|
||||||
|
|
||||||
if (method == methods::kGet) {
|
if (method == methods::kGet) {
|
||||||
@@ -40,7 +38,7 @@ void RestDetailService::Handle(const RestRequest& request,
|
|||||||
Delete(request.url_matches, response);
|
Delete(request.url_matches, response);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
LOG_ERRO("RestDetailService doesn't support '%s' method.", method.c_str());
|
LOG_ERRO("DetailService doesn't support '%s' method.", method.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef WEBCC_REST_SERVICE_H_
|
#ifndef WEBCC_SERVICE_H_
|
||||||
#define WEBCC_REST_SERVICE_H_
|
#define WEBCC_SERVICE_H_
|
||||||
|
|
||||||
// NOTE:
|
// NOTE:
|
||||||
// The design of RestListService and RestDetailService is very similar to
|
// The design of RestListService and RestDetailService is very similar to
|
||||||
@@ -32,6 +32,7 @@ struct RestRequest {
|
|||||||
UrlMatches url_matches;
|
UrlMatches url_matches;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Add ResponseBuilder instead.
|
||||||
struct RestResponse {
|
struct RestResponse {
|
||||||
Status status;
|
Status status;
|
||||||
|
|
||||||
@@ -43,20 +44,20 @@ struct RestResponse {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Base class for your REST service.
|
// Base class for your service.
|
||||||
class RestService {
|
class Service {
|
||||||
public:
|
public:
|
||||||
virtual ~RestService() = default;
|
virtual ~Service() = default;
|
||||||
|
|
||||||
// Handle REST request, output response.
|
// Handle request, output response.
|
||||||
virtual void Handle(const RestRequest& request, RestResponse* response) = 0;
|
virtual void Handle(const RestRequest& request, RestResponse* response) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
using RestServicePtr = std::shared_ptr<RestService>;
|
using ServicePtr = std::shared_ptr<Service>;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
class RestListService : public RestService {
|
class ListService : public Service {
|
||||||
public:
|
public:
|
||||||
void Handle(const RestRequest& request, RestResponse* response) override;
|
void Handle(const RestRequest& request, RestResponse* response) override;
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ protected:
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
class RestDetailService : public RestService {
|
class DetailService : public Service {
|
||||||
public:
|
public:
|
||||||
void Handle(const RestRequest& request, RestResponse* response) override;
|
void Handle(const RestRequest& request, RestResponse* response) override;
|
||||||
|
|
||||||
@@ -98,4 +99,4 @@ protected:
|
|||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|
||||||
#endif // WEBCC_REST_SERVICE_H_
|
#endif // WEBCC_SERVICE_H_
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "webcc/rest_service_manager.h"
|
#include "webcc/service_manager.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
@@ -6,15 +6,14 @@
|
|||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
bool RestServiceManager::AddService(RestServicePtr service,
|
bool ServiceManager::AddService(ServicePtr service, const std::string& url,
|
||||||
const std::string& url,
|
bool is_regex) {
|
||||||
bool is_regex) {
|
|
||||||
assert(service);
|
assert(service);
|
||||||
|
|
||||||
ServiceItem item(service, url, is_regex);
|
Item item(service, url, is_regex);
|
||||||
|
|
||||||
if (!is_regex) {
|
if (!is_regex) {
|
||||||
service_items_.push_back(std::move(item));
|
items_.push_back(std::move(item));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +22,7 @@ bool RestServiceManager::AddService(RestServicePtr service,
|
|||||||
try {
|
try {
|
||||||
// Compile the regex.
|
// Compile the regex.
|
||||||
item.url_regex.assign(url, flags);
|
item.url_regex.assign(url, flags);
|
||||||
service_items_.push_back(std::move(item));
|
items_.push_back(std::move(item));
|
||||||
return true;
|
return true;
|
||||||
} catch (const std::regex_error& e) {
|
} catch (const std::regex_error& e) {
|
||||||
LOG_ERRO("URL is not a valid regular expression: %s", e.what());
|
LOG_ERRO("URL is not a valid regular expression: %s", e.what());
|
||||||
@@ -31,11 +30,11 @@ bool RestServiceManager::AddService(RestServicePtr service,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RestServicePtr RestServiceManager::GetService(const std::string& url,
|
ServicePtr ServiceManager::GetService(const std::string& url,
|
||||||
UrlMatches* matches) {
|
UrlMatches* matches) {
|
||||||
assert(matches != nullptr);
|
assert(matches != nullptr);
|
||||||
|
|
||||||
for (ServiceItem& item : service_items_) {
|
for (Item& item : items_) {
|
||||||
if (item.is_regex) {
|
if (item.is_regex) {
|
||||||
std::smatch match;
|
std::smatch match;
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ RestServicePtr RestServiceManager::GetService(const std::string& url,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return RestServicePtr();
|
return ServicePtr();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
@@ -1,54 +1,52 @@
|
|||||||
#ifndef WEBCC_REST_SERVICE_MANAGER_H_
|
#ifndef WEBCC_SERVICE_MANAGER_H_
|
||||||
#define WEBCC_REST_SERVICE_MANAGER_H_
|
#define WEBCC_SERVICE_MANAGER_H_
|
||||||
|
|
||||||
#include <regex> // NOLINT
|
#include <regex> // NOLINT
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility> // for move()
|
#include <utility> // for move()
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "webcc/rest_service.h"
|
#include "webcc/service.h"
|
||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
class RestServiceManager {
|
class ServiceManager {
|
||||||
public:
|
public:
|
||||||
RestServiceManager() = default;
|
ServiceManager() = default;
|
||||||
|
|
||||||
RestServiceManager(const RestServiceManager&) = delete;
|
ServiceManager(const ServiceManager&) = delete;
|
||||||
RestServiceManager& operator=(const RestServiceManager&) = delete;
|
ServiceManager& operator=(const ServiceManager&) = delete;
|
||||||
|
|
||||||
// Add a service and bind it with the given URL.
|
// Add a service and bind it with the given URL.
|
||||||
// The |url| should start with "/" and will be treated as a regular expression
|
// The |url| should start with "/" and will be treated as a regular expression
|
||||||
// if |regex| is true.
|
// if |regex| is true.
|
||||||
// Examples: "/instances", "/instances/(\\d+)".
|
// Examples: "/instances", "/instances/(\\d+)".
|
||||||
bool AddService(RestServicePtr service, const std::string& url,
|
bool AddService(ServicePtr service, const std::string& url, bool is_regex);
|
||||||
bool is_regex);
|
|
||||||
|
|
||||||
// The |matches| is only available when the |url| bound to the service is a
|
// The |matches| is only available when the |url| bound to the service is a
|
||||||
// regular expression and has sub-expressions.
|
// regular expression and has sub-expressions.
|
||||||
// E.g., the URL bound to the service is "/instances/(\\d+)", now match
|
// E.g., the URL bound to the service is "/instances/(\\d+)", now match
|
||||||
// "/instances/12345" against it, you will get one match of "12345".
|
// "/instances/12345" against it, you will get one match of "12345".
|
||||||
RestServicePtr GetService(const std::string& url, UrlMatches* matches);
|
ServicePtr GetService(const std::string& url, UrlMatches* matches);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class ServiceItem {
|
class Item {
|
||||||
public:
|
public:
|
||||||
ServiceItem(RestServicePtr _service, const std::string& _url,
|
Item(ServicePtr _service, const std::string& _url, bool _is_regex)
|
||||||
bool _is_regex)
|
|
||||||
: service(_service), url(_url), is_regex(_is_regex) {
|
: service(_service), url(_url), is_regex(_is_regex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceItem(const ServiceItem&) = default;
|
Item(const Item&) = default;
|
||||||
ServiceItem& operator=(const ServiceItem&) = default;
|
Item& operator=(const Item&) = default;
|
||||||
|
|
||||||
ServiceItem(ServiceItem&& rhs)
|
Item(Item&& rhs)
|
||||||
: service(rhs.service),
|
: service(rhs.service),
|
||||||
url(std::move(rhs.url)),
|
url(std::move(rhs.url)),
|
||||||
is_regex(rhs.is_regex),
|
is_regex(rhs.is_regex),
|
||||||
url_regex(std::move(rhs.url_regex)) {
|
url_regex(std::move(rhs.url_regex)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
RestServicePtr service;
|
ServicePtr service;
|
||||||
|
|
||||||
// URL string, e.g., "/instances/(\\d+)".
|
// URL string, e.g., "/instances/(\\d+)".
|
||||||
std::string url;
|
std::string url;
|
||||||
@@ -60,9 +58,9 @@ private:
|
|||||||
std::regex url_regex;
|
std::regex url_regex;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<ServiceItem> service_items_;
|
std::vector<Item> items_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|
||||||
#endif // WEBCC_REST_SERVICE_MANAGER_H_
|
#endif // WEBCC_SERVICE_MANAGER_H_
|
||||||
+21
-21
@@ -278,44 +278,44 @@ void Url::AddQuery(const std::string& key, const std::string& value) {
|
|||||||
void Url::Parse(const std::string& str) {
|
void Url::Parse(const std::string& str) {
|
||||||
std::string tmp = boost::trim_left_copy(str);
|
std::string tmp = boost::trim_left_copy(str);
|
||||||
|
|
||||||
std::size_t pos = std::string::npos;
|
std::size_t p = std::string::npos;
|
||||||
|
|
||||||
pos = tmp.find("://");
|
p = tmp.find("://");
|
||||||
if (pos != std::string::npos) {
|
if (p != std::string::npos) {
|
||||||
scheme_ = tmp.substr(0, pos);
|
scheme_ = tmp.substr(0, p);
|
||||||
tmp = tmp.substr(pos + 3);
|
tmp = tmp.substr(p + 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
pos = tmp.find('/');
|
p = tmp.find('/');
|
||||||
if (pos != std::string::npos) {
|
if (p != std::string::npos) {
|
||||||
host_ = tmp.substr(0, pos);
|
host_ = tmp.substr(0, p);
|
||||||
|
|
||||||
tmp = tmp.substr(pos + 1);
|
tmp = tmp.substr(p);
|
||||||
|
|
||||||
pos = tmp.find('?');
|
p = tmp.find('?');
|
||||||
if (pos != std::string::npos) {
|
if (p != std::string::npos) {
|
||||||
path_ = tmp.substr(0, pos);
|
path_ = tmp.substr(0, p);
|
||||||
query_ = tmp.substr(pos + 1);
|
query_ = tmp.substr(p + 1);
|
||||||
} else {
|
} else {
|
||||||
path_ = tmp;
|
path_ = tmp;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
path_ = "";
|
path_ = "";
|
||||||
|
|
||||||
pos = tmp.find('?');
|
p = tmp.find('?');
|
||||||
if (pos != std::string::npos) {
|
if (p != std::string::npos) {
|
||||||
host_ = tmp.substr(0, pos);
|
host_ = tmp.substr(0, p);
|
||||||
query_ = tmp.substr(pos + 1);
|
query_ = tmp.substr(p + 1);
|
||||||
} else {
|
} else {
|
||||||
host_ = tmp;
|
host_ = tmp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!host_.empty()) {
|
if (!host_.empty()) {
|
||||||
pos = host_.find(':');
|
p = host_.find(':');
|
||||||
if (pos != std::string::npos) {
|
if (p != std::string::npos) {
|
||||||
port_ = host_.substr(pos + 1);
|
port_ = host_.substr(p + 1);
|
||||||
host_ = host_.substr(0, pos);
|
host_ = host_.substr(0, p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user