Refine logger; add UrlQuery for query parameters.

master
Adam Gu 7 years ago
parent 2496862fa0
commit b6ebba771b

@ -19,6 +19,7 @@ if(WEBCC_BUILD_UNITTEST)
enable_testing() enable_testing()
endif() endif()
# Automatically detect _WIN32_WINNT for Asio.
# See: https://stackoverflow.com/a/40217291 # See: https://stackoverflow.com/a/40217291
if(WIN32) if(WIN32)
macro(get_WIN32_WINNT version) macro(get_WIN32_WINNT version)

@ -1,6 +1,7 @@
#include "calc_client.h" #include "calc_client.h"
#include <iostream> #include <iostream>
#include "boost/lexical_cast.hpp" #include "boost/lexical_cast.hpp"
#include "webcc/logger.h"
// Set to 0 to test our own calculator server created with webcc. // Set to 0 to test our own calculator server created with webcc.
#define ACCESS_PARASOFT 0 #define ACCESS_PARASOFT 0
@ -71,8 +72,9 @@ bool CalcClient::Calc(const std::string& operation,
// Error handling if any. // Error handling if any.
if (error != webcc::kNoError) { if (error != webcc::kNoError) {
std::cerr << "Error: " << error; LOG_ERRO("Operation '%s' failed: %s",
std::cerr << ", " << webcc::GetErrorMessage(error) << std::endl; operation.c_str(),
webcc::GetErrorMessage(error));
return false; return false;
} }

@ -1,7 +1,10 @@
#include <iostream> #include <iostream>
#include "webcc/logger.h"
#include "calc_client.h" #include "calc_client.h"
int main() { int main() {
LOG_INIT(webcc::INFO, 0);
CalcClient calc; CalcClient calc;
double x = 1.0; double x = 1.0;

@ -15,7 +15,7 @@ int main(int argc, char* argv[]) {
return 1; return 1;
} }
LOG_INIT(webcc::VERB, 0); LOG_INIT(webcc::INFO, 0);
unsigned short port = std::atoi(argv[1]); unsigned short port = std::atoi(argv[1]);

@ -63,7 +63,7 @@ const char* GetErrorMessage(Error error) {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
const Namespace kSoapEnvNamespace{ const SoapNamespace kSoapEnvNamespace{
"soap", "soap",
"http://schemas.xmlsoap.org/soap/envelope/" "http://schemas.xmlsoap.org/soap/envelope/"
}; };

@ -87,8 +87,7 @@ const char* GetErrorMessage(Error error);
// 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/" }
// TODO: Rename (add soap prefix) class SoapNamespace {
class Namespace {
public: public:
std::string name; std::string name;
std::string url; std::string url;
@ -99,12 +98,11 @@ public:
}; };
// CSoap's default namespace for SOAP Envelope. // CSoap's default namespace for SOAP Envelope.
extern const Namespace kSoapEnvNamespace; extern const SoapNamespace kSoapEnvNamespace;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Parameter in the SOAP request envelope. // Key-value parameter.
// TODO: Rename (add soap prefix)
class Parameter { class Parameter {
public: public:
Parameter() = default; Parameter() = default;

@ -19,7 +19,6 @@ class HttpResponse;
class HttpClient { class HttpClient {
public: public:
HttpClient(); HttpClient();
~HttpClient() = default; ~HttpClient() = default;
HttpClient(const HttpClient&) = delete; HttpClient(const HttpClient&) = delete;

@ -22,12 +22,12 @@ void HttpRequestHandler::Start(std::size_t count) {
} }
void HttpRequestHandler::Stop() { void HttpRequestHandler::Stop() {
LOG_VERB("Stopping workers..."); LOG_INFO("Stopping workers...");
// Close pending sessions. // Close pending sessions.
for (HttpSessionPtr conn = queue_.Pop(); conn; conn = queue_.Pop()) { for (HttpSessionPtr conn = queue_.Pop(); conn; conn = queue_.Pop()) {
LOG_VERB("Closing pending session..."); LOG_INFO("Closing pending session...");
conn->Stop(); conn->Close();
} }
// Enqueue a null session to trigger the first worker to stop. // Enqueue a null session to trigger the first worker to stop.
@ -35,17 +35,17 @@ void HttpRequestHandler::Stop() {
workers_.join_all(); workers_.join_all();
LOG_VERB("All workers have been stopped."); LOG_INFO("All workers have been stopped.");
} }
void HttpRequestHandler::WorkerRoutine() { void HttpRequestHandler::WorkerRoutine() {
LOG_VERB("Worker is running."); LOG_INFO("Worker is running.");
for (;;) { for (;;) {
HttpSessionPtr session = queue_.PopOrWait(); HttpSessionPtr session = queue_.PopOrWait();
if (!session) { if (!session) {
LOG_VERB("Worker is going to stop."); LOG_INFO("Worker is going to stop.");
// For stopping next worker. // For stopping next worker.
queue_.Push(HttpSessionPtr()); queue_.Push(HttpSessionPtr());

@ -25,7 +25,7 @@ HttpServer::HttpServer(unsigned short port, std::size_t workers)
signals_.add(SIGQUIT); signals_.add(SIGQUIT);
#endif #endif
DoAwaitStop(); AsyncAwaitStop();
// NOTE: // NOTE:
// "reuse_addr=true" means option SO_REUSEADDR will be set. // "reuse_addr=true" means option SO_REUSEADDR will be set.
@ -37,16 +37,13 @@ HttpServer::HttpServer(unsigned short port, std::size_t workers)
tcp::endpoint(tcp::v4(), port), tcp::endpoint(tcp::v4(), port),
true)); // reuse_addr true)); // reuse_addr
DoAccept(); AsyncAccept();
}
HttpServer::~HttpServer() {
} }
void HttpServer::Run() { void HttpServer::Run() {
assert(GetRequestHandler() != NULL); assert(GetRequestHandler() != NULL);
LOG_VERB("Server is going to run..."); LOG_INFO("Server is going to run...");
// Start worker threads. // Start worker threads.
GetRequestHandler()->Start(workers_); GetRequestHandler()->Start(workers_);
@ -58,7 +55,7 @@ void HttpServer::Run() {
io_context_.run(); io_context_.run();
} }
void HttpServer::DoAccept() { void HttpServer::AsyncAccept() {
acceptor_->async_accept( acceptor_->async_accept(
[this](boost::system::error_code ec, tcp::socket socket) { [this](boost::system::error_code ec, tcp::socket socket) {
// Check whether the server was stopped by a signal before this // Check whether the server was stopped by a signal before this
@ -68,17 +65,19 @@ void HttpServer::DoAccept() {
} }
if (!ec) { if (!ec) {
LOG_INFO("Accepted a connection.");
HttpSessionPtr session{ HttpSessionPtr session{
new HttpSession(std::move(socket), GetRequestHandler()) new HttpSession(std::move(socket), GetRequestHandler())
}; };
session->Start(); session->Start();
} }
DoAccept(); AsyncAccept();
}); });
} }
void HttpServer::DoAwaitStop() { void HttpServer::AsyncAwaitStop() {
signals_.async_wait( signals_.async_wait(
[this](boost::system::error_code, int /*signo*/) { [this](boost::system::error_code, int /*signo*/) {
// The server is stopped by canceling all outstanding asynchronous // The server is stopped by canceling all outstanding asynchronous

@ -26,17 +26,17 @@ public:
HttpServer(unsigned short port, std::size_t workers); HttpServer(unsigned short port, std::size_t workers);
virtual ~HttpServer(); virtual ~HttpServer() = default;
// 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 DoAccept(); void AsyncAccept();
// Wait for a request to stop the server. // Wait for a request to stop the server.
void DoAwaitStop(); void AsyncAwaitStop();
// Get the handler for incoming requests. // Get the handler for incoming requests.
virtual HttpRequestHandler* GetRequestHandler() = 0; virtual HttpRequestHandler* GetRequestHandler() = 0;

@ -17,14 +17,11 @@ HttpSession::HttpSession(boost::asio::ip::tcp::socket socket,
, request_parser_(&request_) { , request_parser_(&request_) {
} }
HttpSession::~HttpSession() {
}
void HttpSession::Start() { void HttpSession::Start() {
DoRead(); AsyncRead();
} }
void HttpSession::Stop() { void HttpSession::Close() {
boost::system::error_code ec; boost::system::error_code ec;
socket_.close(ec); socket_.close(ec);
} }
@ -35,33 +32,24 @@ void HttpSession::SetResponseContent(std::string&& content,
response_.SetContentType(content_type); response_.SetContentType(content_type);
} }
void HttpSession::SendResponse(int status) { void HttpSession::SendResponse(HttpStatus::Enum status) {
response_.set_status(status); response_.set_status(status);
DoWrite(); AsyncWrite();
} }
void HttpSession::DoRead() { void HttpSession::AsyncRead() {
socket_.async_read_some(boost::asio::buffer(buffer_), socket_.async_read_some(boost::asio::buffer(buffer_),
std::bind(&HttpSession::HandleRead, std::bind(&HttpSession::ReadHandler,
shared_from_this(), shared_from_this(),
std::placeholders::_1, std::placeholders::_1,
std::placeholders::_2)); std::placeholders::_2));
} }
void HttpSession::DoWrite() { void HttpSession::ReadHandler(boost::system::error_code ec,
boost::asio::async_write(socket_, std::size_t length) {
response_.ToBuffers(),
std::bind(&HttpSession::HandleWrite,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void HttpSession::HandleRead(boost::system::error_code ec,
std::size_t length) {
if (ec) { if (ec) {
if (ec != boost::asio::error::operation_aborted) { if (ec != boost::asio::error::operation_aborted) {
Stop(); Close();
} }
return; return;
} }
@ -71,13 +59,13 @@ void HttpSession::HandleRead(boost::system::error_code ec,
if (error != kNoError) { if (error != kNoError) {
// Bad request. // Bad request.
response_ = HttpResponse::Fault(HttpStatus::kBadRequest); response_ = HttpResponse::Fault(HttpStatus::kBadRequest);
DoWrite(); AsyncWrite();
return; return;
} }
if (!request_parser_.finished()) { if (!request_parser_.finished()) {
// Continue to read the request. // Continue to read the request.
DoRead(); AsyncRead();
return; return;
} }
@ -87,14 +75,23 @@ void HttpSession::HandleRead(boost::system::error_code ec,
request_handler_->Enqueue(shared_from_this()); request_handler_->Enqueue(shared_from_this());
} }
void HttpSession::AsyncWrite() {
boost::asio::async_write(socket_,
response_.ToBuffers(),
std::bind(&HttpSession::WriteHandler,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
// NOTE: // NOTE:
// This write handler will be called from main thread (the thread calling // This write handler will be called from main thread (the thread calling
// io_context.run), even though DoWrite() is invoked by worker threads. This is // io_context.run), even though DoWrite() is invoked by worker threads. This is
// ensured by Asio. // ensured by Asio.
void HttpSession::HandleWrite(boost::system::error_code ec, void HttpSession::WriteHandler(boost::system::error_code ec,
std::size_t length) { std::size_t length) {
if (!ec) { if (!ec) {
LOG_VERB("Response has been sent back."); LOG_INFO("Response has been sent back.");
// Initiate graceful connection closure. // Initiate graceful connection closure.
boost::system::error_code ec; boost::system::error_code ec;
@ -104,7 +101,7 @@ void HttpSession::HandleWrite(boost::system::error_code ec,
LOG_ERRO("Sending response error: %s", ec.message().c_str()); LOG_ERRO("Sending response error: %s", ec.message().c_str());
if (ec != boost::asio::error::operation_aborted) { if (ec != boost::asio::error::operation_aborted) {
Stop(); Close();
} }
} }
} }

@ -23,29 +23,30 @@ public:
HttpSession(boost::asio::ip::tcp::socket socket, HttpSession(boost::asio::ip::tcp::socket socket,
HttpRequestHandler* handler); HttpRequestHandler* handler);
~HttpSession(); ~HttpSession() = default;
const HttpRequest& request() const { const HttpRequest& request() const {
return request_; return request_;
} }
// Start to read and process the client request.
void Start(); void Start();
void Stop(); // Close the socket.
void Close();
void SetResponseContent(std::string&& content, void SetResponseContent(std::string&& content,
const std::string& content_type); const std::string& content_type);
// Write response back to the client with the given HTTP status. // Send response to client with the given status.
void SendResponse(int status); void SendResponse(HttpStatus::Enum status);
private: private:
void DoRead(); void AsyncRead();
void ReadHandler(boost::system::error_code ec, std::size_t length);
void DoWrite(); void AsyncWrite();
void WriteHandler(boost::system::error_code ec, std::size_t length);
void HandleRead(boost::system::error_code ec, std::size_t length);
void HandleWrite(boost::system::error_code ec, std::size_t length);
private: private:
// Socket for the connection. // Socket for the connection.

@ -3,10 +3,10 @@
// Simple console logger. // Simple console logger.
namespace webcc {
#if WEBCC_ENABLE_LOG #if WEBCC_ENABLE_LOG
namespace webcc {
enum LogLevel { enum LogLevel {
VERB = 0, VERB = 0,
INFO, INFO,

@ -76,7 +76,8 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) {
} }
// TODO: Only for GET? // TODO: Only for GET?
Url::Query query = Url::SplitQuery(url.query()); UrlQuery query;
Url::SplitQuery(url.query(), &query);
std::string content; std::string content;
bool ok = service->Handle(session->request().method(), bool ok = service->Handle(session->request().method(),

@ -10,6 +10,8 @@
namespace webcc { namespace webcc {
class UrlQuery;
// Base class for your REST service. // Base class for your REST service.
class RestService { class RestService {
public: public:
@ -20,12 +22,12 @@ public:
// \param http_method GET, POST, etc. // \param http_method GET, POST, etc.
// \param url_sub_matches The regex sub-matches in the URL, // \param url_sub_matches The regex sub-matches in the URL,
// usually resource ID. // usually resource ID.
// \param query Query parameters in the URL, key value pairs. // \param query Query parameters in the URL.
// \param request_content Request JSON. // \param request_content Request JSON.
// \param response_content Output response JSON. // \param response_content Output response JSON.
virtual bool Handle(const std::string& http_method, virtual bool Handle(const std::string& http_method,
const std::vector<std::string>& url_sub_matches, const std::vector<std::string>& url_sub_matches,
const std::map<std::string, std::string>& query, const UrlQuery& query,
const std::string& request_content, const std::string& request_content,
std::string* response_content) = 0; std::string* response_content) = 0;
}; };

@ -28,8 +28,8 @@ 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;
Namespace soapenv_ns_; // SOAP envelope namespace. SoapNamespace soapenv_ns_; // SOAP envelope namespace.
Namespace service_ns_; // Namespace for your web service. SoapNamespace service_ns_; // Namespace for your web service.
// Request URL. // Request URL.
std::string url_; std::string url_;

@ -11,11 +11,11 @@ namespace webcc {
class SoapMessage { class SoapMessage {
public: public:
// E.g., set as kSoapEnvNamespace. // E.g., set as kSoapEnvNamespace.
void set_soapenv_ns(const Namespace& soapenv_ns) { void set_soapenv_ns(const SoapNamespace& soapenv_ns) {
soapenv_ns_ = soapenv_ns; soapenv_ns_ = soapenv_ns;
} }
void set_service_ns(const Namespace& service_ns) { void set_service_ns(const SoapNamespace& service_ns) {
service_ns_ = service_ns; service_ns_ = service_ns;
} }
@ -34,8 +34,7 @@ public:
bool FromXml(const std::string& xml_string); bool FromXml(const std::string& xml_string);
protected: protected:
SoapMessage() { SoapMessage() = default;
}
// 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;
@ -44,8 +43,8 @@ protected:
virtual bool FromXmlBody(pugi::xml_node xbody) = 0; virtual bool FromXmlBody(pugi::xml_node xbody) = 0;
protected: protected:
Namespace soapenv_ns_; // SOAP envelope namespace. SoapNamespace soapenv_ns_; // SOAP envelope namespace.
Namespace service_ns_; // Namespace for your web service. SoapNamespace service_ns_; // Namespace for your web service.
std::string operation_; std::string operation_;
}; };

@ -1,9 +1,43 @@
#include "webcc/url.h" #include "webcc/url.h"
#include <algorithm>
#include <sstream> #include <sstream>
namespace webcc { namespace webcc {
////////////////////////////////////////////////////////////////////////////////
void UrlQuery::Add(std::string&& key, std::string&& value) {
if (!HasKey(key)) {
parameters_.push_back({ std::move(key), std::move(value) });
}
}
void UrlQuery::Remove(const std::string& key) {
auto it = Find(key);
if (it != parameters_.end()) {
parameters_.erase(it);
}
}
const std::string& UrlQuery::GetValue(const std::string& key) const {
static const std::string kEmptyValue;
auto it = Find(key);
if (it != parameters_.end()) {
return it->value();
}
return kEmptyValue;
}
UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const {
return std::find_if(parameters_.begin(),
parameters_.end(),
[&key](const Parameter& p) { return p.key() == key; });
}
////////////////////////////////////////////////////////////////////////////////
Url::Url(const std::string& str) { Url::Url(const std::string& str) {
std::size_t pos = str.find('?'); std::size_t pos = str.find('?');
if (pos == std::string::npos) { if (pos == std::string::npos) {
@ -44,33 +78,29 @@ static bool SplitKeyValue(const std::string& kv,
} }
// static // static
Url::Query Url::SplitQuery(const std::string& query) { void Url::SplitQuery(const std::string& str, UrlQuery* query) {
const std::size_t NPOS = std::string::npos; const std::size_t NPOS = std::string::npos;
Query result;
// Split into key value pairs separated by '&'. // Split into key value pairs separated by '&'.
std::size_t i = 0; std::size_t i = 0;
while (i != NPOS) { while (i != NPOS) {
std::size_t j = query.find_first_of('&', i); std::size_t j = str.find_first_of('&', i);
std::string kv; std::string kv;
if (j == NPOS) { if (j == NPOS) {
kv = query.substr(i); kv = str.substr(i);
i = NPOS; i = NPOS;
} else { } else {
kv = query.substr(i, j - i); kv = str.substr(i, j - i);
i = j + 1; i = j + 1;
} }
std::string key; std::string key;
std::string value; std::string value;
if (SplitKeyValue(kv, &key, &value)) { if (SplitKeyValue(kv, &key, &value)) {
result[key] = value; // TODO: Move query->Add(std::move(key), std::move(value));
} }
} }
return result;
} }
} // namespace webcc } // namespace webcc

@ -11,8 +11,37 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "webcc/common.h"
namespace webcc { namespace webcc {
////////////////////////////////////////////////////////////////////////////////
// URL query parameters.
class UrlQuery {
public:
typedef std::vector<Parameter> Parameters;
void Add(std::string&& key, std::string&& value);
void Remove(const std::string& key);
const std::string& GetValue(const std::string& key) const;
bool HasKey(const std::string& key) const {
return Find(key) != parameters_.end();
}
private:
typedef Parameters::const_iterator ConstIterator;
ConstIterator Find(const std::string& key) const;
private:
Parameters parameters_;
};
////////////////////////////////////////////////////////////////////////////////
class Url { class Url {
public: public:
typedef std::map<std::string, std::string> Query; typedef std::map<std::string, std::string> Query;
@ -40,8 +69,8 @@ public:
// Split a path into its hierarchical components. // Split a path into its hierarchical components.
static std::vector<std::string> SplitPath(const std::string& path); static std::vector<std::string> SplitPath(const std::string& path);
// Split query string into key-value map. // Split query string into key-value parameters.
static Query SplitQuery(const std::string& query); static void SplitQuery(const std::string& str, UrlQuery* query);
private: private:
std::string path_; std::string path_;

@ -7,7 +7,6 @@ namespace webcc {
// Print the resolved endpoints. // Print the resolved endpoints.
// NOTE: Endpoint is one word, don't use "end point". // NOTE: Endpoint is one word, don't use "end point".
// TODO
void DumpEndpoints(tcp::resolver::results_type& endpoints) { void DumpEndpoints(tcp::resolver::results_type& endpoints) {
std::cout << "Endpoints: " << endpoints.size() << std::endl; std::cout << "Endpoints: " << endpoints.size() << std::endl;

Loading…
Cancel
Save