Rename RegisterService to Bind and add is_regex parameter; indent public/protected/private by one space; refine rest example.

master
Adam Gu 7 years ago
parent 1bab2da8f4
commit cdc61b4bf2

@ -26,18 +26,23 @@ public:
} }
bool ListBooks() { bool ListBooks() {
std::cout << "ListBooks" << std::endl;
webcc::HttpResponse http_response; webcc::HttpResponse http_response;
if (!rest_client_.Get("/books", &http_response)) { if (!rest_client_.Get("/books", &http_response)) {
return false; return false;
} }
std::cout << "result:\n" << http_response.content() << std::endl; std::cout << http_response.content() << std::endl;
return true; return true;
} }
bool CreateBook(const std::string& id, bool CreateBook(const std::string& id,
const std::string& title, const std::string& title,
double price) { double price) {
std::cout << "CreateBook: " << id << " " << title << " " << price
<< std::endl;
Json::Value json(Json::objectValue); Json::Value json(Json::objectValue);
json["id"] = id; json["id"] = id;
json["title"] = title; json["title"] = title;
@ -66,6 +71,8 @@ public:
} }
bool GetBook(const std::string& id) { bool GetBook(const std::string& id) {
std::cout << "GetBook: " << id << std::endl;
webcc::HttpResponse http_response; webcc::HttpResponse http_response;
if (!rest_client_.Get("/book/" + id, &http_response)) { if (!rest_client_.Get("/book/" + id, &http_response)) {
return false; return false;
@ -78,13 +85,16 @@ public:
bool UpdateBook(const std::string& id, bool UpdateBook(const std::string& id,
const std::string& title, const std::string& title,
double price) { double price) {
std::cout << "UpdateBook: " << id << " " << title << " " << price
<< std::endl;
// NOTE: ID is already in the URL. // NOTE: ID is already in the URL.
Json::Value json(Json::objectValue); Json::Value json(Json::objectValue);
json["title"] = title; json["title"] = title;
json["price"] = price; json["price"] = price;
webcc::HttpResponse http_response; webcc::HttpResponse http_response;
if (!rest_client_.Post("/book/" + id, JsonToString(json), &http_response)) { if (!rest_client_.Put("/book/" + id, JsonToString(json), &http_response)) {
return false; return false;
} }
@ -93,6 +103,8 @@ public:
} }
bool DeleteBook(const std::string& id) { bool DeleteBook(const std::string& id) {
std::cout << "DeleteBook: " << id << std::endl;
webcc::HttpResponse http_response; webcc::HttpResponse http_response;
if (!rest_client_.Delete("/book/" + id, &http_response)) { if (!rest_client_.Delete("/book/" + id, &http_response)) {
return false; return false;
@ -108,119 +120,35 @@ private:
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
std::string g_host;
std::string g_port;
void Help(const char* argv0) { void Help(const char* argv0) {
std::cout << "Usage: " << argv0 << " <host> <port>" << std::endl; std::cout << "Usage: " << argv0 << " <host> <port>" << std::endl;
std::cout << " E.g.," << std::endl; std::cout << " E.g.," << std::endl;
std::cout << " " << argv0 << " localhost 8080" << std::endl; std::cout << " " << argv0 << " localhost 8080" << std::endl;
} }
std::string GetUserInput() {
char input[256];
std::cout << ">> ";
std::cin.getline(input, 256);
return input;
}
bool ParseJsonInput(const std::string& input, Json::Value* root) {
Json::CharReaderBuilder builder;
std::stringstream stream(input);
std::string errs;
if (Json::parseFromStream(builder, stream, root, &errs)) {
return true;
} else {
std::cerr << errs << std::endl;
return false;
}
}
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
if (argc != 3) { if (argc != 3) {
Help(argv[0]); Help(argv[0]);
return 1; return 1;
} }
LOG_INIT(webcc::VERB, 0); LOG_INIT(webcc::ERRO, 0);
g_host = argv[1];
g_port = argv[2];
// Type commands to execute actions.
// Commands: list, create, detail, update, delete and exit.
// Examples:
// >> list
// >> create 1 { "title": "1984", "price": 12.3 }
// >> detail 1
// >> update 1 { "title": "1Q84", "price": 32.1 }
// >> delete 1
// >> exit
// A very naive implementation of interaction mode.
while (true) {
std::string input = GetUserInput();
boost::trim(input);
std::string command;
std::size_t i = input.find(' ');
if (i == std::string::npos) {
command = input;
} else {
command = input.substr(0, i);
}
if (command == "exit") { std::string host = argv[1];
break; std::string port = argv[2];
}
if (command == "list") {
BookListClient client(g_host, g_port);
client.ListBooks();
continue;
}
++i;
std::size_t j = input.find(' ', i); BookListClient list_client(host, port);
std::string id = input.substr(i, j - i); BookDetailClient detail_client(host, port);
i = j + 1;
if (command == "create") {
std::string json = input.substr(i);
Json::Value root;
if (ParseJsonInput(json, &root)) {
BookListClient client(g_host, g_port);
client.CreateBook(id, root["title"].asString(), root["price"].asDouble());
}
continue;
}
if (command == "update") { list_client.ListBooks();
std::string json = input.substr(i); list_client.CreateBook("1", "1984", 12.3);
Json::Value root; detail_client.GetBook("1");
if (ParseJsonInput(json, &root)) { detail_client.UpdateBook("1", "1Q84", 32.1);
BookDetailClient client(g_host, g_port); detail_client.GetBook("1");
client.UpdateBook(id, root["title"].asString(), root["price"].asDouble()); detail_client.DeleteBook("1");
}
continue;
}
if (command == "detail") {
BookDetailClient client(g_host, g_port);
client.GetBook(id);
continue;
}
if (command == "delete") {
BookDetailClient client(g_host, g_port);
client.DeleteBook(id);
continue;
}
}
list_client.ListBooks();
return 0; return 0;
} }

@ -62,6 +62,7 @@ public:
if (it != books_.end()) { if (it != books_.end()) {
it->title = book.title; it->title = book.title;
it->price = book.price; it->price = book.price;
return true;
} }
return false; return false;
} }
@ -162,10 +163,10 @@ bool BookDetailService::Get(const std::vector<std::string>& url_sub_matches,
return false; return false;
} }
// Update a book partially. // Update a book.
bool BookDetailService::Patch(const std::vector<std::string>& url_sub_matches, bool BookDetailService::Put(const std::vector<std::string>& url_sub_matches,
const std::string& request_content, const std::string& request_content,
std::string* response_content) { std::string* response_content) {
if (url_sub_matches.size() != 1) { if (url_sub_matches.size() != 1) {
return false; return false;
} }

@ -35,9 +35,9 @@ class BookDetailService : public webcc::RestDetailService {
const webcc::UrlQuery& query, const webcc::UrlQuery& query,
std::string* response_content) final; std::string* response_content) final;
bool Patch(const std::vector<std::string>& url_sub_matches, bool Put(const std::vector<std::string>& url_sub_matches,
const std::string& request_content, const std::string& request_content,
std::string* response_content) final; std::string* response_content) final;
bool Delete(const std::vector<std::string>& url_sub_matches) final; bool Delete(const std::vector<std::string>& url_sub_matches) final;
}; };

@ -26,11 +26,9 @@ int main(int argc, char* argv[]) {
try { try {
webcc::RestServer server(port, workers); webcc::RestServer server(port, workers);
server.RegisterService(std::make_shared<BookListService>(), server.Bind(std::make_shared<BookListService>(), "/books", false);
"/books");
server.RegisterService(std::make_shared<BookDetailService>(), server.Bind(std::make_shared<BookDetailService>(), "/book/(\\d+)", true);
"/book/(\\d+)");
server.Run(); server.Run();

@ -24,8 +24,7 @@ int main(int argc, char* argv[]) {
try { try {
webcc::SoapServer server(port, workers); webcc::SoapServer server(port, workers);
server.RegisterService(std::make_shared<CalcService>(), server.Bind(std::make_shared<CalcService>(), "/calculator");
"/calculator");
server.Run(); server.Run();

@ -91,8 +91,8 @@ Parameter::Parameter(const std::string& key, bool value)
} }
Parameter::Parameter(Parameter&& rhs) Parameter::Parameter(Parameter&& rhs)
: key_(std::move(rhs.key_)) : key_(std::move(rhs.key_)),
, value_(std::move(rhs.value_)) { value_(std::move(rhs.value_)) {
} }
Parameter& Parameter::operator=(Parameter&& rhs) { Parameter& Parameter::operator=(Parameter&& rhs) {

@ -113,7 +113,7 @@ const char* GetErrorMessage(Error error);
// Key-value parameter. // Key-value parameter.
class Parameter { class Parameter {
public: public:
Parameter() = default; Parameter() = default;
Parameter(const Parameter&) = default; Parameter(const Parameter&) = default;
Parameter& operator=(const Parameter&) = default; Parameter& operator=(const Parameter&) = default;
@ -150,7 +150,7 @@ public:
// Return "key=value" string. // Return "key=value" string.
std::string ToString() const; std::string ToString() const;
private: private:
std::string key_; std::string key_;
std::string value_; std::string value_;
}; };

@ -36,8 +36,7 @@ HttpClient::HttpClient()
CheckDeadline(); CheckDeadline();
} }
Error HttpClient::MakeRequest(const HttpRequest& request, Error HttpClient::Request(const HttpRequest& request, HttpResponse* response) {
HttpResponse* response) {
assert(response != NULL); assert(response != NULL);
Error error = kNoError; Error error = kNoError;

@ -17,23 +17,19 @@ class HttpRequest;
class HttpResponse; class HttpResponse;
class HttpClient { class HttpClient {
public: public:
HttpClient(); HttpClient();
~HttpClient() = default; ~HttpClient() = default;
HttpClient(const HttpClient&) = delete;
HttpClient& operator=(const HttpClient&) = delete;
void set_timeout_seconds(int timeout_seconds) { void set_timeout_seconds(int timeout_seconds) {
timeout_seconds_ = timeout_seconds; timeout_seconds_ = timeout_seconds;
} }
// Make a HTTP request.
// Connect to the server, send the request, wait until the response is // Connect to the server, send the request, wait until the response is
// received. // received.
Error MakeRequest(const HttpRequest& request, HttpResponse* response); Error Request(const HttpRequest& request, HttpResponse* response);
private: private:
Error Connect(const HttpRequest& request); Error Connect(const HttpRequest& request);
Error SendReqeust(const HttpRequest& request); Error SendReqeust(const HttpRequest& request);
@ -42,7 +38,6 @@ private:
void CheckDeadline(); void CheckDeadline();
private:
boost::asio::io_context io_context_; boost::asio::io_context io_context_;
boost::asio::ip::tcp::socket socket_; boost::asio::ip::tcp::socket socket_;
@ -57,6 +52,8 @@ private:
// Timer for the timeout control. // Timer for the timeout control.
boost::asio::deadline_timer deadline_timer_; boost::asio::deadline_timer deadline_timer_;
DISALLOW_COPY_AND_ASSIGN(HttpClient);
}; };
} // namespace webcc } // namespace webcc

@ -9,14 +9,14 @@
namespace webcc { namespace webcc {
class HttpHeader { class HttpHeader {
public: public:
std::string name; std::string name;
std::string value; std::string value;
}; };
// Base class for HTTP request and response messages. // Base class for HTTP request and response messages.
class HttpMessage { class HttpMessage {
public: public:
virtual ~HttpMessage() = default; virtual ~HttpMessage() = default;
const std::string& start_line() const { const std::string& start_line() const {
@ -51,13 +51,12 @@ public:
SetContentLength(content_.size()); SetContentLength(content_.size());
} }
private: protected:
void SetContentLength(std::size_t content_length) { void SetContentLength(std::size_t content_length) {
content_length_ = content_length; content_length_ = content_length;
SetHeader(kContentLength, std::to_string(content_length)); SetHeader(kContentLength, std::to_string(content_length));
} }
protected:
// Start line with trailing "\r\n". // Start line with trailing "\r\n".
std::string start_line_; std::string start_line_;

@ -17,7 +17,7 @@ class HttpResponse;
// The common handler for all incoming requests. // The common handler for all incoming requests.
class HttpRequestHandler { class HttpRequestHandler {
public: public:
HttpRequestHandler() = default; HttpRequestHandler() = default;
virtual ~HttpRequestHandler() = default; virtual ~HttpRequestHandler() = default;
@ -30,7 +30,7 @@ public:
// Close pending sessions and stop worker threads. // Close pending sessions and stop worker threads.
void Stop(); void Stop();
private: private:
void WorkerRoutine(); void WorkerRoutine();
// Called by the worker routine. // Called by the worker routine.

@ -8,12 +8,12 @@ namespace webcc {
class HttpRequest; class HttpRequest;
class HttpRequestParser : public HttpParser { class HttpRequestParser : public HttpParser {
public: public:
explicit HttpRequestParser(HttpRequest* request); explicit HttpRequestParser(HttpRequest* request);
~HttpRequestParser() override = default; ~HttpRequestParser() override = default;
private: private:
Error ParseStartLine(const std::string& line) override; Error ParseStartLine(const std::string& line) override;
HttpRequest* request_; HttpRequest* request_;

@ -14,7 +14,7 @@ class HttpResponse;
std::ostream& operator<<(std::ostream& os, const HttpResponse& response); std::ostream& operator<<(std::ostream& os, const HttpResponse& response);
class HttpResponse : public HttpMessage { class HttpResponse : public HttpMessage {
public: public:
HttpResponse() = default; HttpResponse() = default;
~HttpResponse() override = default; ~HttpResponse() override = default;
@ -37,7 +37,7 @@ public:
// Get a fault response when HTTP status is not OK. // Get a fault response when HTTP status is not OK.
static HttpResponse Fault(HttpStatus::Enum status); static HttpResponse Fault(HttpStatus::Enum status);
private: private:
friend std::ostream& operator<<(std::ostream& os, friend std::ostream& operator<<(std::ostream& os,
const HttpResponse& response); const HttpResponse& response);

@ -8,16 +8,15 @@ namespace webcc {
class HttpResponse; class HttpResponse;
class HttpResponseParser : public HttpParser { class HttpResponseParser : public HttpParser {
public: public:
explicit HttpResponseParser(HttpResponse* response); explicit HttpResponseParser(HttpResponse* response);
~HttpResponseParser() override = default; ~HttpResponseParser() override = default;
private: private:
// Parse HTTP start line; E.g., "HTTP/1.1 200 OK". // Parse HTTP start line; E.g., "HTTP/1.1 200 OK".
Error ParseStartLine(const std::string& line) override; Error ParseStartLine(const std::string& line) override;
private:
// The result response message. // The result response message.
HttpResponse* response_; HttpResponse* response_;
}; };

@ -10,6 +10,7 @@
#include "boost/scoped_ptr.hpp" #include "boost/scoped_ptr.hpp"
#include "boost/thread/thread.hpp" #include "boost/thread/thread.hpp"
#include "webcc/globals.h"
#include "webcc/http_session.h" #include "webcc/http_session.h"
namespace webcc { namespace webcc {
@ -19,10 +20,7 @@ class HttpRequestHandler;
// 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 HttpServer { class HttpServer {
public: public:
HttpServer(const HttpServer&) = delete;
HttpServer& operator=(const HttpServer&) = delete;
HttpServer(unsigned short port, std::size_t workers); HttpServer(unsigned short port, std::size_t workers);
virtual ~HttpServer() = default; virtual ~HttpServer() = default;
@ -30,7 +28,7 @@ public:
// Run the server's io_service loop. // Run the server's io_service loop.
void Run(); void Run();
private: private:
// Initiate an asynchronous accept operation. // Initiate an asynchronous accept operation.
void AsyncAccept(); void AsyncAccept();
@ -40,7 +38,6 @@ private:
// Get the handler for incoming requests. // Get the handler for incoming requests.
virtual HttpRequestHandler* GetRequestHandler() = 0; virtual HttpRequestHandler* GetRequestHandler() = 0;
private:
// The number of worker threads. // The number of worker threads.
std::size_t workers_; std::size_t workers_;
@ -52,6 +49,8 @@ private:
// Acceptor used to listen for incoming connections. // Acceptor used to listen for incoming connections.
boost::scoped_ptr<boost::asio::ip::tcp::acceptor> acceptor_; boost::scoped_ptr<boost::asio::ip::tcp::acceptor> acceptor_;
DISALLOW_COPY_AND_ASSIGN(HttpServer);
}; };
} // namespace webcc } // namespace webcc

@ -16,7 +16,7 @@ namespace webcc {
class HttpRequestHandler; class HttpRequestHandler;
class HttpSession : public std::enable_shared_from_this<HttpSession> { class HttpSession : public std::enable_shared_from_this<HttpSession> {
public: public:
HttpSession(const HttpSession&) = delete; HttpSession(const HttpSession&) = delete;
HttpSession& operator=(const HttpSession&) = delete; HttpSession& operator=(const HttpSession&) = delete;
@ -41,14 +41,13 @@ public:
// Send response to client with the given status. // Send response to client with the given status.
void SendResponse(HttpStatus::Enum status); void SendResponse(HttpStatus::Enum status);
private: private:
void AsyncRead(); void AsyncRead();
void ReadHandler(boost::system::error_code ec, std::size_t length); void ReadHandler(boost::system::error_code ec, std::size_t length);
void AsyncWrite(); void AsyncWrite();
void WriteHandler(boost::system::error_code ec, std::size_t length); void WriteHandler(boost::system::error_code ec, std::size_t length);
private:
// Socket for the connection. // Socket for the connection.
boost::asio::ip::tcp::socket socket_; boost::asio::ip::tcp::socket socket_;

@ -14,7 +14,7 @@ namespace webcc {
template <typename T> template <typename T>
class Queue { class Queue {
public: public:
Queue() = default; Queue() = default;
Queue(const Queue&) = delete; Queue(const Queue&) = delete;
@ -51,7 +51,7 @@ public:
not_empty_cv_.notify_one(); not_empty_cv_.notify_one();
} }
private: private:
std::list<T> message_list_; std::list<T> message_list_;
boost::mutex mutex_; boost::mutex mutex_;
boost::condition_variable not_empty_cv_; boost::condition_variable not_empty_cv_;

@ -23,7 +23,7 @@ bool RestClient::Request(const std::string& method,
request.Build(); request.Build();
HttpClient http_client; HttpClient http_client;
Error error = http_client.MakeRequest(request, response); Error error = http_client.Request(request, response);
return error == kNoError; return error == kNoError;
} }

@ -10,7 +10,7 @@ namespace webcc {
class HttpResponse; class HttpResponse;
class RestClient { class RestClient {
public: public:
RestClient(const std::string& host, const std::string& port) RestClient(const std::string& host, const std::string& port)
: host_(host), port_(port) { : host_(host), port_(port) {
} }
@ -41,7 +41,7 @@ public:
return Request(kHttpDelete, url, "", response); return Request(kHttpDelete, url, "", response);
} }
private: private:
bool Request(const std::string& method, bool Request(const std::string& method,
const std::string& url, const std::string& url,
const std::string& content, const std::string& content,

@ -5,9 +5,10 @@
namespace webcc { namespace webcc {
bool RestRequestHandler::RegisterService(RestServicePtr service, bool RestRequestHandler::Bind(RestServicePtr service,
const std::string& url) { const std::string& url,
return service_manager_.AddService(service, url); bool is_regex) {
return service_manager_.AddService(service, url, is_regex);
} }
void RestRequestHandler::HandleSession(HttpSessionPtr session) { void RestRequestHandler::HandleSession(HttpSessionPtr session) {

@ -9,18 +9,14 @@
namespace webcc { namespace webcc {
class RestRequestHandler : public HttpRequestHandler { class RestRequestHandler : public HttpRequestHandler {
public: public:
~RestRequestHandler() override = default; ~RestRequestHandler() override = default;
// Register a REST service to the given URL path. bool Bind(RestServicePtr service, const std::string& url, bool is_regex);
// The URL should start with "/" and could be a regular expression or not.
// E.g., "/instances". "/instances/(\\d+)"
bool RegisterService(RestServicePtr service, const std::string& url);
private: private:
void HandleSession(HttpSessionPtr session) override; void HandleSession(HttpSessionPtr session) override;
private:
RestServiceManager service_manager_; RestServiceManager service_manager_;
}; };

@ -10,28 +10,30 @@
namespace webcc { namespace webcc {
class RestServer : public HttpServer { class RestServer : public HttpServer {
public: public:
RestServer(unsigned short port, std::size_t workers) RestServer(unsigned short port, std::size_t workers)
: HttpServer(port, workers) { : HttpServer(port, workers) {
} }
~RestServer() override = default; ~RestServer() override = default;
// Register a REST service to the given URL path. // Bind a REST service to the given URL path.
// The URL should start with "/" and could be a regular expression or not. // The URL should start with "/" and it will be treated as a regular
// E.g., "/instances". "/instances/(\\d+)" // expression if |is_regex| is true.
// NOTE: Registering to the same URL multiple times is allowed, but only the // Examples:
// last one takes effect. // - "/instances"
bool RegisterService(RestServicePtr service, const std::string& url) { // - "/instances/(\\d+)"
return request_handler_.RegisterService(service, url); // 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: private:
HttpRequestHandler* GetRequestHandler() override { HttpRequestHandler* GetRequestHandler() override {
return &request_handler_; return &request_handler_;
} }
private:
RestRequestHandler request_handler_; RestRequestHandler request_handler_;
}; };

@ -23,7 +23,7 @@ class UrlQuery;
// Base class for your REST service. // Base class for your REST service.
class RestService { class RestService {
public: public:
virtual ~RestService() { virtual ~RestService() {
} }

@ -7,10 +7,16 @@
namespace webcc { namespace webcc {
bool RestServiceManager::AddService(RestServicePtr service, bool RestServiceManager::AddService(RestServicePtr service,
const std::string& url) { const std::string& url,
bool is_regex) {
assert(service); assert(service);
ServiceItem item(service, url); ServiceItem item(service, url, is_regex);
if (!is_regex) {
service_items_.push_back(std::move(item));
return true;
}
std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase; std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase;
@ -18,15 +24,13 @@ bool RestServiceManager::AddService(RestServicePtr service,
// Compile the regex. // Compile the regex.
item.url_regex.assign(url, flags); item.url_regex.assign(url, flags);
service_items_.push_back(item); service_items_.push_back(std::move(item));
return true; return true;
} catch (std::regex_error& e) { } catch (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());
return false;
} }
return false;
} }
RestServicePtr RestServiceManager::GetService( RestServicePtr RestServiceManager::GetService(
@ -36,16 +40,22 @@ RestServicePtr RestServiceManager::GetService(
assert(sub_matches != NULL); assert(sub_matches != NULL);
for (ServiceItem& item : service_items_) { for (ServiceItem& item : service_items_) {
std::smatch match; if (item.is_regex) {
std::smatch match;
if (std::regex_match(url, match, item.url_regex)) { if (std::regex_match(url, match, item.url_regex)) {
// Any sub-matches? // Any sub-matches?
// NOTE: Start from 1 because match[0] is the whole string itself. // NOTE: Start from 1 because match[0] is the whole string itself.
for (size_t i = 1; i < match.size(); ++i) { for (size_t i = 1; i < match.size(); ++i) {
sub_matches->push_back(match[i].str()); sub_matches->push_back(match[i].str());
} }
return item.service; return item.service;
}
} else {
if (item.url == url) {
return item.service;
}
} }
} }

@ -9,13 +9,15 @@
namespace webcc { namespace webcc {
class RestServiceManager { class RestServiceManager {
public: public:
RestServiceManager() = default; RestServiceManager() = default;
// 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 could be a regular expression or not. // The |url| should start with "/" and will be treated as a regular expression
// E.g., "/instances". "/instances/(\\d+)" // if |regex| is true.
bool AddService(RestServicePtr service, const std::string& url); // Examples: "/instances", "/instances/(\\d+)".
bool AddService(RestServicePtr service, const std::string& url,
bool is_regex);
// The |sub_matches| is only available when the |url| bound to the // The |sub_matches| is only available when the |url| bound to the
// service is a regular expression and has sub-expressions. // service is a regular expression and has sub-expressions.
@ -24,20 +26,22 @@ public:
RestServicePtr GetService(const std::string& url, RestServicePtr GetService(const std::string& url,
std::vector<std::string>* sub_matches); std::vector<std::string>* sub_matches);
private: private:
class ServiceItem { class ServiceItem {
public: public:
ServiceItem(RestServicePtr _service, const std::string& _url) ServiceItem(RestServicePtr _service, const std::string& _url,
: service(_service), url(_url) { bool _is_regex)
: service(_service), url(_url), is_regex(_is_regex) {
} }
ServiceItem(const ServiceItem& rhs) = default; ServiceItem(const ServiceItem&) = default;
ServiceItem& operator=(const ServiceItem& rhs) = default; ServiceItem& operator=(const ServiceItem&) = default;
ServiceItem(ServiceItem&& rhs) ServiceItem(ServiceItem&& rhs)
: url(std::move(rhs.url)), : service(rhs.service),
url_regex(std::move(rhs.url_regex)), url(std::move(rhs.url)),
service(rhs.service) { // No move is_regex(rhs.is_regex),
url_regex(std::move(rhs.url_regex)) {
} }
RestServicePtr service; RestServicePtr service;
@ -45,6 +49,9 @@ private:
// URL string, e.g., "/instances/(\\d+)". // URL string, e.g., "/instances/(\\d+)".
std::string url; std::string url;
// If the URL is a regular expression or not.
bool is_regex;
// Compiled regex for URL string. // Compiled regex for URL string.
std::regex url_regex; std::regex url_regex;
}; };

@ -53,7 +53,7 @@ Error SoapClient::Call(const std::string& operation,
http_client.set_timeout_seconds(timeout_seconds_); http_client.set_timeout_seconds(timeout_seconds_);
} }
Error error = http_client.MakeRequest(http_request, &http_response); Error error = http_client.Request(http_request, &http_response);
if (error != kNoError) { if (error != kNoError) {
return error; return error;

@ -13,10 +13,10 @@ namespace webcc {
// Set URL, host, port, etc. in your sub-class before make the call. // Set URL, host, port, etc. in your sub-class before make the call.
// //
class SoapClient { class SoapClient {
public: public:
virtual ~SoapClient() = default; virtual ~SoapClient() = default;
protected: protected:
SoapClient() = default; SoapClient() = default;
// A generic wrapper to make a call. // A generic wrapper to make a call.
@ -25,7 +25,6 @@ protected:
std::vector<Parameter>&& parameters, std::vector<Parameter>&& parameters,
std::string* result); std::string* result);
protected:
// -1 means default timeout (normally 30s) will be used. // -1 means default timeout (normally 30s) will be used.
int timeout_seconds_ = -1; int timeout_seconds_ = -1;

@ -12,7 +12,7 @@ namespace webcc {
// XML namespace name/url pair. // XML namespace name/url pair.
// E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" } // E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" }
class SoapNamespace { class SoapNamespace {
public: public:
std::string name; std::string name;
std::string url; std::string url;
@ -26,7 +26,7 @@ extern const SoapNamespace kSoapEnvNamespace;
// Base class for SOAP request and response. // Base class for SOAP request and response.
class SoapMessage { class SoapMessage {
public: public:
virtual ~SoapMessage() {} virtual ~SoapMessage() {}
// E.g., set as kSoapEnvNamespace. // E.g., set as kSoapEnvNamespace.
@ -52,14 +52,13 @@ public:
// Parse from SOAP request XML. // Parse from SOAP request XML.
bool FromXml(const std::string& xml_string); bool FromXml(const std::string& xml_string);
protected: protected:
// Convert to SOAP body XML. // Convert to SOAP body XML.
virtual void ToXmlBody(pugi::xml_node xbody) = 0; virtual void ToXmlBody(pugi::xml_node xbody) = 0;
// Parse from SOAP body XML. // Parse from SOAP body XML.
virtual bool FromXmlBody(pugi::xml_node xbody) = 0; virtual bool FromXmlBody(pugi::xml_node xbody) = 0;
protected:
SoapNamespace soapenv_ns_; // SOAP envelope namespace. SoapNamespace soapenv_ns_; // SOAP envelope namespace.
SoapNamespace service_ns_; // Namespace for your web service. SoapNamespace service_ns_; // Namespace for your web service.

@ -11,7 +11,7 @@ namespace webcc {
// Used to compose the SOAP request envelope XML which will be sent as the HTTP // Used to compose the SOAP request envelope XML which will be sent as the HTTP
// request body. // request body.
class SoapRequest : public SoapMessage { class SoapRequest : public SoapMessage {
public: public:
void AddParameter(const Parameter& parameter); void AddParameter(const Parameter& parameter);
void AddParameter(Parameter&& parameter); void AddParameter(Parameter&& parameter);
@ -19,11 +19,11 @@ public:
// Get parameter value by key. // Get parameter value by key.
const std::string& GetParameter(const std::string& key) const; const std::string& GetParameter(const std::string& key) const;
protected: protected:
void ToXmlBody(pugi::xml_node xbody) override; void ToXmlBody(pugi::xml_node xbody) override;
bool FromXmlBody(pugi::xml_node xbody) override; bool FromXmlBody(pugi::xml_node xbody) override;
private: private:
std::vector<Parameter> parameters_; std::vector<Parameter> parameters_;
}; };

@ -6,8 +6,7 @@
namespace webcc { namespace webcc {
bool SoapRequestHandler::RegisterService(SoapServicePtr service, bool SoapRequestHandler::Bind(SoapServicePtr service, const std::string& url) {
const std::string& url) {
assert(service); assert(service);
url_service_map_[url] = service; url_service_map_[url] = service;

@ -8,17 +8,13 @@
namespace webcc { namespace webcc {
class SoapRequestHandler : public HttpRequestHandler { class SoapRequestHandler : public HttpRequestHandler {
public: public:
SoapRequestHandler() = default; SoapRequestHandler() = default;
~SoapRequestHandler() override = default; ~SoapRequestHandler() override = default;
// Register a SOAP service to the given URL path. bool Bind(SoapServicePtr service, const std::string& url);
// The |url| path must start with "/", e.g., "/calculator".
// Registering to the same URL multiple times is allowed, but only the last
// one takes effect.
bool RegisterService(SoapServicePtr service, const std::string& url);
private: private:
void HandleSession(HttpSessionPtr session) override; void HandleSession(HttpSessionPtr session) override;
SoapServicePtr GetServiceByUrl(const std::string& url); SoapServicePtr GetServiceByUrl(const std::string& url);

@ -7,7 +7,7 @@ namespace webcc {
// SOAP response. // SOAP response.
class SoapResponse : public SoapMessage { class SoapResponse : public SoapMessage {
public: public:
// Could be "Price" for an operation/method like "GetXyzPrice". // Could be "Price" for an operation/method like "GetXyzPrice".
// Really depend on the service. // Really depend on the service.
// Most services use a general name "Result". // Most services use a general name "Result".
@ -27,12 +27,12 @@ public:
result_ = std::move(result); result_ = std::move(result);
} }
protected: protected:
void ToXmlBody(pugi::xml_node xbody) override; void ToXmlBody(pugi::xml_node xbody) override;
bool FromXmlBody(pugi::xml_node xbody) override; bool FromXmlBody(pugi::xml_node xbody) override;
private: private:
// NOTE: // NOTE:
// Multiple results might be necessary. But for most cases, single result // Multiple results might be necessary. But for most cases, single result
// should be enough, because an API normally returns only one value. // should be enough, because an API normally returns only one value.

@ -9,18 +9,22 @@
namespace webcc { namespace webcc {
class SoapServer : public HttpServer { class SoapServer : public HttpServer {
public: public:
SoapServer(unsigned short port, std::size_t workers) SoapServer(unsigned short port, std::size_t workers)
: HttpServer(port, workers) { : HttpServer(port, workers) {
} }
~SoapServer() override = default; ~SoapServer() override = default;
bool RegisterService(SoapServicePtr service, const std::string& url) { // Bind a SOAP service to the given URL path.
return request_handler_.RegisterService(service, url); // The |url| path must start with "/", e.g., "/calculator".
// Binding to the same URL multiple times is allowed, but only the last
// one takes effect.
bool Bind(SoapServicePtr service, const std::string& url) {
return request_handler_.Bind(service, url);
} }
private: private:
HttpRequestHandler* GetRequestHandler() override { HttpRequestHandler* GetRequestHandler() override {
return &request_handler_; return &request_handler_;
} }

@ -12,14 +12,14 @@ class SoapResponse;
// Base class for your SOAP service. // Base class for your SOAP service.
class SoapService { class SoapService {
public: public:
virtual ~SoapService() = default; virtual ~SoapService() = default;
// Handle SOAP request, output the response. // Handle SOAP request, output the response.
virtual bool Handle(const SoapRequest& soap_request, virtual bool Handle(const SoapRequest& soap_request,
SoapResponse* soap_response) = 0; SoapResponse* soap_response) = 0;
protected: protected:
HttpStatus::Enum http_status_ = HttpStatus::kOK; HttpStatus::Enum http_status_ = HttpStatus::kOK;
}; };

@ -19,7 +19,7 @@ namespace webcc {
// URL query parameters. // URL query parameters.
class UrlQuery { class UrlQuery {
public: public:
typedef std::vector<Parameter> Parameters; typedef std::vector<Parameter> Parameters;
UrlQuery() = default; UrlQuery() = default;
@ -49,18 +49,17 @@ public:
// E.g., "item=12731&color=blue&size=large". // E.g., "item=12731&color=blue&size=large".
std::string ToString() const; std::string ToString() const;
private: private:
typedef Parameters::const_iterator ConstIterator; typedef Parameters::const_iterator ConstIterator;
ConstIterator Find(const std::string& key) const; ConstIterator Find(const std::string& key) const;
private:
Parameters parameters_; Parameters parameters_;
}; };
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
class Url { class Url {
public: public:
Url() = default; Url() = default;
Url(const std::string& str, bool decode); Url(const std::string& str, bool decode);
@ -88,7 +87,7 @@ public:
// Split query string into key-value parameters. // Split query string into key-value parameters.
static void SplitQuery(const std::string& str, UrlQuery* query); static void SplitQuery(const std::string& str, UrlQuery* query);
private: private:
void Init(const std::string& str); void Init(const std::string& str);
std::string path_; std::string path_;

@ -20,7 +20,7 @@ TEST(RestServiceManager, URL_RegexBasic) {
{ {
RestServicePtr service = std::make_shared<TestRestService>(); RestServicePtr service = std::make_shared<TestRestService>();
service_manager.AddService(service, "/instances/(\\d+)"); service_manager.AddService(service, "/instances/(\\d+)", true);
} }
std::vector<std::string> sub_matches; std::vector<std::string> sub_matches;
@ -39,3 +39,20 @@ TEST(RestServiceManager, URL_RegexBasic) {
EXPECT_FALSE(!!service); EXPECT_FALSE(!!service);
} }
TEST(RestServiceManager, URL_NonRegex) {
RestServiceManager service_manager;
{
RestServicePtr service = std::make_shared<TestRestService>();
service_manager.AddService(service, "/instances", false);
}
std::vector<std::string> sub_matches;
std::string url = "/instances";
RestServicePtr service = service_manager.GetService(url, &sub_matches);
EXPECT_TRUE(!!service);
EXPECT_EQ(0, sub_matches.size());
}

Loading…
Cancel
Save