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()
endif()
# Automatically detect _WIN32_WINNT for Asio.
# See: https://stackoverflow.com/a/40217291
if(WIN32)
macro(get_WIN32_WINNT version)

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

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

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

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

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

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

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

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

@ -26,17 +26,17 @@ public:
HttpServer(unsigned short port, std::size_t workers);
virtual ~HttpServer();
virtual ~HttpServer() = default;
// Run the server's io_service loop.
void Run();
private:
// Initiate an asynchronous accept operation.
void DoAccept();
void AsyncAccept();
// Wait for a request to stop the server.
void DoAwaitStop();
void AsyncAwaitStop();
// Get the handler for incoming requests.
virtual HttpRequestHandler* GetRequestHandler() = 0;

@ -17,14 +17,11 @@ HttpSession::HttpSession(boost::asio::ip::tcp::socket socket,
, request_parser_(&request_) {
}
HttpSession::~HttpSession() {
}
void HttpSession::Start() {
DoRead();
AsyncRead();
}
void HttpSession::Stop() {
void HttpSession::Close() {
boost::system::error_code ec;
socket_.close(ec);
}
@ -35,33 +32,24 @@ void HttpSession::SetResponseContent(std::string&& content,
response_.SetContentType(content_type);
}
void HttpSession::SendResponse(int status) {
void HttpSession::SendResponse(HttpStatus::Enum status) {
response_.set_status(status);
DoWrite();
AsyncWrite();
}
void HttpSession::DoRead() {
void HttpSession::AsyncRead() {
socket_.async_read_some(boost::asio::buffer(buffer_),
std::bind(&HttpSession::HandleRead,
std::bind(&HttpSession::ReadHandler,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void HttpSession::DoWrite() {
boost::asio::async_write(socket_,
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) {
void HttpSession::ReadHandler(boost::system::error_code ec,
std::size_t length) {
if (ec) {
if (ec != boost::asio::error::operation_aborted) {
Stop();
Close();
}
return;
}
@ -71,13 +59,13 @@ void HttpSession::HandleRead(boost::system::error_code ec,
if (error != kNoError) {
// Bad request.
response_ = HttpResponse::Fault(HttpStatus::kBadRequest);
DoWrite();
AsyncWrite();
return;
}
if (!request_parser_.finished()) {
// Continue to read the request.
DoRead();
AsyncRead();
return;
}
@ -87,14 +75,23 @@ void HttpSession::HandleRead(boost::system::error_code ec,
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:
// 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
// ensured by Asio.
void HttpSession::HandleWrite(boost::system::error_code ec,
std::size_t length) {
void HttpSession::WriteHandler(boost::system::error_code ec,
std::size_t length) {
if (!ec) {
LOG_VERB("Response has been sent back.");
LOG_INFO("Response has been sent back.");
// Initiate graceful connection closure.
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());
if (ec != boost::asio::error::operation_aborted) {
Stop();
Close();
}
}
}

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

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

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

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

@ -28,8 +28,8 @@ protected:
// -1 means default timeout (normally 30s) will be used.
int timeout_seconds_ = -1;
Namespace soapenv_ns_; // SOAP envelope namespace.
Namespace service_ns_; // Namespace for your web service.
SoapNamespace soapenv_ns_; // SOAP envelope namespace.
SoapNamespace service_ns_; // Namespace for your web service.
// Request URL.
std::string url_;

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

@ -1,9 +1,43 @@
#include "webcc/url.h"
#include <algorithm>
#include <sstream>
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) {
std::size_t pos = str.find('?');
if (pos == std::string::npos) {
@ -44,33 +78,29 @@ static bool SplitKeyValue(const std::string& kv,
}
// 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;
Query result;
// Split into key value pairs separated by '&'.
std::size_t i = 0;
while (i != NPOS) {
std::size_t j = query.find_first_of('&', i);
std::size_t j = str.find_first_of('&', i);
std::string kv;
if (j == NPOS) {
kv = query.substr(i);
kv = str.substr(i);
i = NPOS;
} else {
kv = query.substr(i, j - i);
kv = str.substr(i, j - i);
i = j + 1;
}
std::string key;
std::string value;
if (SplitKeyValue(kv, &key, &value)) {
result[key] = value; // TODO: Move
query->Add(std::move(key), std::move(value));
}
}
return result;
}
} // namespace webcc

@ -11,8 +11,37 @@
#include <string>
#include <vector>
#include "webcc/common.h"
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 {
public:
typedef std::map<std::string, std::string> Query;
@ -40,8 +69,8 @@ public:
// Split a path into its hierarchical components.
static std::vector<std::string> SplitPath(const std::string& path);
// Split query string into key-value map.
static Query SplitQuery(const std::string& query);
// Split query string into key-value parameters.
static void SplitQuery(const std::string& str, UrlQuery* query);
private:
std::string path_;

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

Loading…
Cancel
Save