Refine logger; add UrlQuery for query parameters.
This commit is contained in:
@@ -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]);
|
||||
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ const char* GetErrorMessage(Error error) {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const Namespace kSoapEnvNamespace{
|
||||
const SoapNamespace kSoapEnvNamespace{
|
||||
"soap",
|
||||
"http://schemas.xmlsoap.org/soap/envelope/"
|
||||
};
|
||||
|
||||
+3
-5
@@ -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;
|
||||
|
||||
+24
-27
@@ -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.
|
||||
|
||||
+2
-2
@@ -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_;
|
||||
};
|
||||
|
||||
+39
-9
@@ -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
|
||||
|
||||
+31
-2
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user