Rework the server to remove rest_xxx classes.

master
Chunting Gu 6 years ago
parent 43aadee885
commit 4140b4f94c

@ -2,12 +2,11 @@
#include <string>
#include "webcc/logger.h"
#include "webcc/rest_server.h"
#include "webcc/rest_service.h"
#include "webcc/server.h"
// -----------------------------------------------------------------------------
class FileUploadService : public webcc::RestService {
class FileUploadService : public webcc::Service {
public:
void Handle(const webcc::RestRequest& request,
webcc::RestResponse* response) override {
@ -48,7 +47,8 @@ int main(int argc, char* argv[]) {
std::size_t workers = 2;
try {
webcc::RestServer server(port, workers);
// TODO: doc root
webcc::Server server(port, workers);
server.Bind(std::make_shared<FileUploadService>(), "/upload", false);

@ -7,8 +7,7 @@
#include "json/json.h"
#include "webcc/logger.h"
#include "webcc/rest_server.h"
#include "webcc/rest_service.h"
#include "webcc/server.h"
#include "examples/common/book.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:
explicit BookListService(int sleep_seconds)
: sleep_seconds_(sleep_seconds) {
@ -58,7 +57,7 @@ private:
// The URL is like '/books/{BookID}', and the 'url_matches' parameter
// contains the matched book ID.
class BookDetailService : public webcc::RestDetailService {
class BookDetailService : public webcc::DetailService {
public:
explicit BookDetailService(int sleep_seconds)
: sleep_seconds_(sleep_seconds) {
@ -227,7 +226,8 @@ int main(int argc, char* argv[]) {
std::size_t workers = 2;
try {
webcc::RestServer server(port, workers);
// TODO: doc root
webcc::Server server(port, workers);
server.Bind(std::make_shared<BookListService>(sleep_seconds),
"/books", false);

@ -3,7 +3,7 @@
set(UT_SRCS
base64_unittest.cc
request_parser_unittest.cc
rest_service_manager_unittest.cc
service_manager_unittest.cc
url_unittest.cc
utility_unittest.cc
)

@ -1,10 +1,10 @@
#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:
void Handle(const webcc::RestRequest& request,
webcc::RestResponse* response) override {
@ -14,16 +14,16 @@ public:
// -----------------------------------------------------------------------------
TEST(RestServiceManagerTest, URL_RegexBasic) {
webcc::RestServiceManager service_manager;
TEST(ServiceManagerTest, URL_RegexBasic) {
webcc::ServiceManager service_manager;
service_manager.AddService(std::make_shared<MyRestService>(),
service_manager.AddService(std::make_shared<MyService>(),
"/instance/(\\d+)", true);
std::vector<std::string> matches;
std::string url = "/instance/12345";
webcc::RestServicePtr service = service_manager.GetService(url, &matches);
webcc::ServicePtr service = service_manager.GetService(url, &matches);
EXPECT_TRUE(!!service);
@ -38,16 +38,16 @@ TEST(RestServiceManagerTest, URL_RegexBasic) {
}
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+)",
true);
std::vector<std::string> matches;
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);
@ -64,14 +64,14 @@ TEST(RestServiceManagerTest, URL_RegexMultiple) {
}
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);
std::vector<std::string> matches;
std::string url = "/instances";
webcc::RestServicePtr service = service_manager.GetService(url, &matches);
webcc::ServicePtr service = service_manager.GetService(url, &matches);
EXPECT_TRUE(!!service);
EXPECT_EQ(0, matches.size());

@ -214,7 +214,8 @@ FormPart::FormPart(const std::string& name, const Path& path,
// Determine media type from file extension.
if (media_type_.empty()) {
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 {
ConnectionPool::ConnectionPool() {
}
void ConnectionPool::Start(ConnectionPtr c) {
LOG_VERB("Starting connection...");
connections_.insert(c);

@ -9,11 +9,11 @@ namespace webcc {
class ConnectionPool {
public:
ConnectionPool() = default;
ConnectionPool(const ConnectionPool&) = delete;
ConnectionPool& operator=(const ConnectionPool&) = delete;
ConnectionPool();
// Add a connection to the pool and start it.
void Start(ConnectionPtr c);
@ -24,7 +24,6 @@ public:
void CloseAll();
private:
/// The managed connections.
std::set<ConnectionPtr> connections_;
};

@ -1,7 +1,8 @@
#include "webcc/globals.h"
#include <iostream>
#include <map>
#include "boost/algorithm/string.hpp"
namespace webcc {
@ -9,37 +10,32 @@ namespace webcc {
namespace media_types {
// TODO: Add more.
static void InitMap(std::map<std::string, std::string>& map) {
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.
std::string FromExtension(const std::string& extension,
bool default_to_plain_text) {
static std::map<std::string, std::string> s_map;
if (s_map.empty()) {
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 "";
}
std::string FromExtension(const std::string& ext) {
using boost::iequals;
if (iequals(ext, ".htm")) { return "text/html"; }
if (iequals(ext, ".html")) { return "text/html"; }
if (iequals(ext, ".php")) { return "text/html"; }
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"; }
return "application/text";
}
} // namespace media_types

@ -131,8 +131,7 @@ const char* const kTextPlain = "text/plain";
const char* const kTextXml = "text/xml";
// Get media type from file extension.
std::string FromExtension(const std::string& extension,
bool default_to_plain_text = true);
std::string FromExtension(const std::string& ext);
} // namespace media_types

@ -248,7 +248,7 @@ void Log(int level, const char* file, int line, const char* format, ...) {
va_list args;
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(),
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 (level < WEBCC_WARN) {
fprintf(stderr, "%s%s, %s, %7s, %25s, %4d, ",
fprintf(stderr, "%s%s, %s, %7s, %20s, %4d, ",
TerminalReset(),
timestamp.c_str(), kLevelNames[level], thread_id.c_str(),
file, line);
} else {
fprintf(stderr, "%s%s%s, %s, %7s, %25s, %4d, ",
fprintf(stderr, "%s%s%s, %s, %7s, %20s, %4d, ",
TerminalReset(),
level == WEBCC_WARN ? TerminalYellow() : TerminalRed(),
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());
} else {
fprintf(stderr, "%s, %s, %7s, %25s, %4d, ",
fprintf(stderr, "%s, %s, %7s, %20s, %4d, ",
timestamp.c_str(), kLevelNames[level], thread_id.c_str(),
file, line);

@ -44,22 +44,11 @@ void Request::Prepare() {
Payload data_payload;
using boost::asio::buffer;
for (auto& part : form_parts_) {
// Boundary
data_payload.push_back(buffer(misc_strings::DOUBLE_DASHES));
data_payload.push_back(buffer(boundary_));
data_payload.push_back(buffer(misc_strings::CRLF));
AddBoundary(data_payload);
part->Prepare(&data_payload);
}
// 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));
AddBoundary(data_payload, true);
// Update Content-Length header.
std::size_t content_length = 0;
@ -84,7 +73,7 @@ void Request::CreateStartLine() {
throw Error{ Error::kSyntaxError, "Host is missing" };
}
std::string target = "/" + url_.path();
std::string target = url_.path();
if (!url_.query().empty()) {
target += "?";
target += url_.query();
@ -96,4 +85,15 @@ void Request::CreateStartLine() {
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

@ -73,6 +73,9 @@ public:
private:
void CreateStartLine();
// Add boundary to the payload for multipart form data.
void AddBoundary(Payload& payload, bool end = false);
private:
std::string method_;

@ -1,14 +1,29 @@
#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/response.h"
#include "webcc/logger.h"
#include "webcc/url.h"
#if WEBCC_ENABLE_GZIP
#include "webcc/gzip.h"
#endif
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) {
queue_.Push(connection);
}
@ -45,7 +60,7 @@ void RequestHandler::WorkerRoutine() {
LOG_INFO("Worker is running.");
for (;;) {
ConnectionPtr connection = queue_.PopOrWait();
auto connection = queue_.PopOrWait();
if (!connection) {
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

@ -7,21 +7,22 @@
#include "webcc/connection.h"
#include "webcc/queue.h"
#include "webcc/service_manager.h"
namespace webcc {
class Request;
class Response;
// The common handler for all incoming requests.
class RequestHandler {
public:
RequestHandler() = default;
explicit RequestHandler(const std::string& doc_root);
virtual ~RequestHandler() = default;
RequestHandler(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.
void Enqueue(ConnectionPtr connection);
@ -34,13 +35,28 @@ public:
private:
void WorkerRoutine();
// Called by the worker routine.
virtual void HandleConnection(ConnectionPtr connection) = 0;
// Handle a connection (or more precisely, the request inside it).
// 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:
// The directory containing the files to be served.
std::string doc_root_;
Queue<ConnectionPtr> queue_;
std::vector<std::thread> workers_;
ServiceManager service_manager_;
};
} // 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,8 +11,10 @@ using tcp = boost::asio::ip::tcp;
namespace webcc {
Server::Server(std::uint16_t port, std::size_t workers)
: acceptor_(io_context_), signals_(io_context_), workers_(workers) {
Server::Server(std::uint16_t port, std::size_t workers,
const std::string& doc_root)
: acceptor_(io_context_), signals_(io_context_), workers_(workers),
request_handler_(doc_root) {
RegisterSignals();
boost::system::error_code ec;
@ -50,9 +52,11 @@ Server::Server(std::uint16_t port, std::size_t workers)
}
}
void Server::Run() {
assert(GetRequestHandler() != nullptr);
bool Server::Bind(ServicePtr service, const std::string& url, bool is_regex) {
return request_handler_.Bind(service, url, is_regex);
}
void Server::Run() {
if (!acceptor_.is_open()) {
LOG_ERRO("Server is NOT going to run.");
return;
@ -65,7 +69,7 @@ void Server::Run() {
DoAccept();
// Start worker threads.
GetRequestHandler()->Start(workers_);
request_handler_.Start(workers_);
// The io_context::run() call will block until all asynchronous operations
// 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.");
auto connection = std::make_shared<Connection>(
std::move(socket), &pool_, GetRequestHandler());
std::move(socket), &pool_, &request_handler_);
pool_.Start(connection);
}
@ -116,7 +120,7 @@ void Server::DoAwaitStop() {
acceptor_.close();
// Stop worker threads.
GetRequestHandler()->Stop();
request_handler_.Stop();
// Close all connections.
pool_.CloseAll();

@ -7,25 +7,35 @@
#include "boost/asio/ip/tcp.hpp"
#include "boost/asio/signal_set.hpp"
#include "webcc/globals.h"
#include "webcc/connection.h"
#include "webcc/connection_pool.h"
#include "webcc/request_handler.h"
#include "webcc/service.h"
namespace webcc {
class RequestHandler;
// HTTP server accepts TCP connections from TCP clients.
// NOTE: Only support IPv4.
class Server {
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;
Server(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.
void Run();
@ -40,7 +50,7 @@ private:
void DoAwaitStop();
// Get the handler for incoming requests.
virtual RequestHandler* GetRequestHandler() = 0;
//virtual RequestHandler* GetRequestHandler();
// The io_context used to perform asynchronous operations.
boost::asio::io_context io_context_;
@ -51,11 +61,14 @@ private:
// The connection pool which owns all live connections.
ConnectionPool pool_;
// The signal_set is used to register for process termination notifications.
// The signals for processing termination notifications.
boost::asio::signal_set signals_;
// The number of worker threads.
std::size_t workers_;
// The handler for incoming requests.
RequestHandler request_handler_;
};
} // namespace webcc

@ -1,4 +1,4 @@
#include "webcc/rest_service.h"
#include "webcc/service.h"
#include "webcc/logger.h"
@ -6,8 +6,7 @@ namespace webcc {
// -----------------------------------------------------------------------------
void RestListService::Handle(const RestRequest& request,
RestResponse* response) {
void ListService::Handle(const RestRequest& request, RestResponse* response) {
const std::string& method = request.http->method();
if (method == methods::kGet) {
@ -17,14 +16,13 @@ void RestListService::Handle(const RestRequest& request,
Post(request.http->content(), response);
} 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,
RestResponse* response) {
void DetailService::Handle(const RestRequest& request, RestResponse* response) {
const std::string& method = request.http->method();
if (method == methods::kGet) {
@ -40,7 +38,7 @@ void RestDetailService::Handle(const RestRequest& request,
Delete(request.url_matches, response);
} 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_
#define WEBCC_REST_SERVICE_H_
#ifndef WEBCC_SERVICE_H_
#define WEBCC_SERVICE_H_
// NOTE:
// The design of RestListService and RestDetailService is very similar to
@ -32,6 +32,7 @@ struct RestRequest {
UrlMatches url_matches;
};
// TODO: Add ResponseBuilder instead.
struct RestResponse {
Status status;
@ -43,20 +44,20 @@ struct RestResponse {
// -----------------------------------------------------------------------------
// Base class for your REST service.
class RestService {
// Base class for your service.
class Service {
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;
};
using RestServicePtr = std::shared_ptr<RestService>;
using ServicePtr = std::shared_ptr<Service>;
// -----------------------------------------------------------------------------
class RestListService : public RestService {
class ListService : public Service {
public:
void Handle(const RestRequest& request, RestResponse* response) override;
@ -71,7 +72,7 @@ protected:
// -----------------------------------------------------------------------------
class RestDetailService : public RestService {
class DetailService : public Service {
public:
void Handle(const RestRequest& request, RestResponse* response) override;
@ -98,4 +99,4 @@ protected:
} // 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>
@ -6,15 +6,14 @@
namespace webcc {
bool RestServiceManager::AddService(RestServicePtr service,
const std::string& url,
bool is_regex) {
bool ServiceManager::AddService(ServicePtr service, const std::string& url,
bool is_regex) {
assert(service);
ServiceItem item(service, url, is_regex);
Item item(service, url, is_regex);
if (!is_regex) {
service_items_.push_back(std::move(item));
items_.push_back(std::move(item));
return true;
}
@ -23,7 +22,7 @@ bool RestServiceManager::AddService(RestServicePtr service,
try {
// Compile the regex.
item.url_regex.assign(url, flags);
service_items_.push_back(std::move(item));
items_.push_back(std::move(item));
return true;
} catch (const std::regex_error& e) {
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,
UrlMatches* matches) {
ServicePtr ServiceManager::GetService(const std::string& url,
UrlMatches* matches) {
assert(matches != nullptr);
for (ServiceItem& item : service_items_) {
for (Item& item : items_) {
if (item.is_regex) {
std::smatch match;
@ -55,7 +54,7 @@ RestServicePtr RestServiceManager::GetService(const std::string& url,
}
}
return RestServicePtr();
return ServicePtr();
}
} // namespace webcc

@ -1,54 +1,52 @@
#ifndef WEBCC_REST_SERVICE_MANAGER_H_
#define WEBCC_REST_SERVICE_MANAGER_H_
#ifndef WEBCC_SERVICE_MANAGER_H_
#define WEBCC_SERVICE_MANAGER_H_
#include <regex> // NOLINT
#include <string>
#include <utility> // for move()
#include <vector>
#include "webcc/rest_service.h"
#include "webcc/service.h"
namespace webcc {
class RestServiceManager {
class ServiceManager {
public:
RestServiceManager() = default;
ServiceManager() = default;
RestServiceManager(const RestServiceManager&) = delete;
RestServiceManager& operator=(const RestServiceManager&) = delete;
ServiceManager(const ServiceManager&) = delete;
ServiceManager& operator=(const ServiceManager&) = delete;
// Add a service and bind it with the given URL.
// The |url| should start with "/" and will be treated as a regular expression
// if |regex| is true.
// Examples: "/instances", "/instances/(\\d+)".
bool AddService(RestServicePtr service, const std::string& url,
bool is_regex);
bool AddService(ServicePtr service, const std::string& url, bool is_regex);
// The |matches| is only available when the |url| bound to the service is a
// regular expression and has sub-expressions.
// E.g., the URL bound to the service is "/instances/(\\d+)", now match
// "/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:
class ServiceItem {
class Item {
public:
ServiceItem(RestServicePtr _service, const std::string& _url,
bool _is_regex)
Item(ServicePtr _service, const std::string& _url, bool _is_regex)
: service(_service), url(_url), is_regex(_is_regex) {
}
ServiceItem(const ServiceItem&) = default;
ServiceItem& operator=(const ServiceItem&) = default;
Item(const Item&) = default;
Item& operator=(const Item&) = default;
ServiceItem(ServiceItem&& rhs)
Item(Item&& rhs)
: service(rhs.service),
url(std::move(rhs.url)),
is_regex(rhs.is_regex),
url_regex(std::move(rhs.url_regex)) {
}
RestServicePtr service;
ServicePtr service;
// URL string, e.g., "/instances/(\\d+)".
std::string url;
@ -60,9 +58,9 @@ private:
std::regex url_regex;
};
std::vector<ServiceItem> service_items_;
std::vector<Item> items_;
};
} // namespace webcc
#endif // WEBCC_REST_SERVICE_MANAGER_H_
#endif // WEBCC_SERVICE_MANAGER_H_

@ -278,44 +278,44 @@ void Url::AddQuery(const std::string& key, const std::string& value) {
void Url::Parse(const std::string& 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("://");
if (pos != std::string::npos) {
scheme_ = tmp.substr(0, pos);
tmp = tmp.substr(pos + 3);
p = tmp.find("://");
if (p != std::string::npos) {
scheme_ = tmp.substr(0, p);
tmp = tmp.substr(p + 3);
}
pos = tmp.find('/');
if (pos != std::string::npos) {
host_ = tmp.substr(0, pos);
p = tmp.find('/');
if (p != std::string::npos) {
host_ = tmp.substr(0, p);
tmp = tmp.substr(pos + 1);
tmp = tmp.substr(p);
pos = tmp.find('?');
if (pos != std::string::npos) {
path_ = tmp.substr(0, pos);
query_ = tmp.substr(pos + 1);
p = tmp.find('?');
if (p != std::string::npos) {
path_ = tmp.substr(0, p);
query_ = tmp.substr(p + 1);
} else {
path_ = tmp;
}
} else {
path_ = "";
pos = tmp.find('?');
if (pos != std::string::npos) {
host_ = tmp.substr(0, pos);
query_ = tmp.substr(pos + 1);
p = tmp.find('?');
if (p != std::string::npos) {
host_ = tmp.substr(0, p);
query_ = tmp.substr(p + 1);
} else {
host_ = tmp;
}
}
if (!host_.empty()) {
pos = host_.find(':');
if (pos != std::string::npos) {
port_ = host_.substr(pos + 1);
host_ = host_.substr(0, pos);
p = host_.find(':');
if (p != std::string::npos) {
port_ = host_.substr(p + 1);
host_ = host_.substr(0, p);
}
}
}

Loading…
Cancel
Save