Refactoring according Google C++ Style.

master
Adam Gu 7 years ago
parent 189a246221
commit 1bab2da8f4

@ -1,78 +1,50 @@
#include <iostream>
#include "boost/algorithm/string.hpp"
#include "json/json.h" // jsoncpp
#include "json/json.h"
#include "webcc/logger.h"
#include "webcc/http_client.h"
#include "webcc/http_request.h"
#include "webcc/http_response.h"
#include "webcc/rest_client.h"
////////////////////////////////////////////////////////////////////////////////
class BookClientBase {
public:
BookClientBase(const std::string& host, const std::string& port)
: host_(host), port_(port) {
}
bool Request(const std::string& method,
const std::string& url,
const std::string& content,
webcc::HttpResponse* http_response) {
webcc::HttpRequest http_request;
http_request.set_method(method);
http_request.set_url(url);
http_request.SetHost(host_, port_);
if (!content.empty()) { // TODO
http_request.SetContent(content);
}
http_request.Build();
webcc::HttpClient http_client;
webcc::Error error = http_client.MakeRequest(http_request, http_response);
return error == webcc::kNoError;
}
protected:
std::string host_;
std::string port_;
};
// Write a JSON object to string.
std::string JsonToString(const Json::Value& json) {
Json::StreamWriterBuilder builder;
return Json::writeString(builder, json);
}
////////////////////////////////////////////////////////////////////////////////
class BookListClient : public BookClientBase {
class BookListClient {
public:
BookListClient(const std::string& host, const std::string& port)
: BookClientBase(host, port) {
: rest_client_(host, port) {
}
bool ListBooks() {
webcc::HttpResponse http_response;
if (!Request(webcc::kHttpGet, "/books", "", &http_response)) {
if (!rest_client_.Get("/books", &http_response)) {
return false;
}
std::cout << "result:\n" << http_response.content() << std::endl;
return true;
}
bool CreateBook(const std::string& id,
const std::string& title,
double price) {
Json::Value root(Json::objectValue);
root["id"] = id;
root["title"] = title;
root["price"] = price;
Json::StreamWriterBuilder builder;
std::string book_json = Json::writeString(builder, root);
Json::Value json(Json::objectValue);
json["id"] = id;
json["title"] = title;
json["price"] = price;
webcc::HttpResponse http_response;
if (!Request(webcc::kHttpPost, "/books", book_json, &http_response)) {
if (!rest_client_.Post("/books", JsonToString(json), &http_response)) {
return false;
}
@ -80,58 +52,58 @@ public:
return true;
}
private:
webcc::RestClient rest_client_;
};
////////////////////////////////////////////////////////////////////////////////
class BookDetailClient : public BookClientBase {
class BookDetailClient {
public:
BookDetailClient(const std::string& host, const std::string& port)
: BookClientBase(host, port) {
: rest_client_(host, port) {
}
bool GetBook(const std::string& id) {
webcc::HttpResponse http_response;
if (!Request(webcc::kHttpGet, "/book/" + id, "", &http_response)) {
if (!rest_client_.Get("/book/" + id, &http_response)) {
return false;
}
std::cout << http_response.content() << std::endl;
return true;
}
bool UpdateBook(const std::string& id,
const std::string& title,
double price) {
Json::Value root(Json::objectValue);
// root["id"] = id; // NOTE: ID is already in the URL.
root["title"] = title;
root["price"] = price;
Json::StreamWriterBuilder builder;
std::string book_json = Json::writeString(builder, root);
// NOTE: ID is already in the URL.
Json::Value json(Json::objectValue);
json["title"] = title;
json["price"] = price;
webcc::HttpResponse http_response;
if (!Request(webcc::kHttpPost, "/book/" + id, book_json, &http_response)) {
if (!rest_client_.Post("/book/" + id, JsonToString(json), &http_response)) {
return false;
}
std::cout << http_response.status() << std::endl;
return true;
}
bool DeleteBook(const std::string& id) {
webcc::HttpResponse http_response;
if (!Request(webcc::kHttpDelete, "/book/" + id, "", &http_response)) {
if (!rest_client_.Delete("/book/" + id, &http_response)) {
return false;
}
std::cout << http_response.content() << std::endl;
std::cout << http_response.status() << std::endl;
return true;
}
private:
webcc::RestClient rest_client_;
};
////////////////////////////////////////////////////////////////////////////////
@ -147,13 +119,8 @@ void Help(const char* argv0) {
std::string GetUserInput() {
char input[256];
// std::size_t length = 0;
// do {
std::cout << ">> ";
std::cin.getline(input, 256);
// length = strlen(input);
// } while (length == 0);
std::cout << ">> ";
std::cin.getline(input, 256);
return input;
}

@ -144,6 +144,7 @@ bool BookListService::Post(const std::string& request_content,
////////////////////////////////////////////////////////////////////////////////
bool BookDetailService::Get(const std::vector<std::string>& url_sub_matches,
const webcc::UrlQuery& query,
std::string* response_content) {
if (url_sub_matches.size() != 1) {
return false;

@ -32,6 +32,7 @@ class BookListService : public webcc::RestListService {
class BookDetailService : public webcc::RestDetailService {
protected:
bool Get(const std::vector<std::string>& url_sub_matches,
const webcc::UrlQuery& query,
std::string* response_content) final;
bool Patch(const std::vector<std::string>& url_sub_matches,

@ -2,8 +2,8 @@
add_definitions(-DBOOST_ASIO_NO_DEPRECATED)
set(SRCS
common.cc
common.h
globals.cc
globals.h
http_client.cc
http_client.h
http_message.cc
@ -27,7 +27,12 @@ set(SRCS
logger.cc
logger.h
queue.h
rest_server.cc
rest_client.cc
rest_client.h
rest_request_handler.cc
rest_request_handler.h
rest_service_manager.cc
rest_service_manager.h
rest_server.h
rest_service.cc
rest_service.h

@ -1,4 +1,4 @@
#include "webcc/common.h"
#include "webcc/globals.h"
namespace webcc {
@ -63,13 +63,6 @@ const char* GetErrorMessage(Error error) {
////////////////////////////////////////////////////////////////////////////////
const SoapNamespace kSoapEnvNamespace{
"soap",
"http://schemas.xmlsoap.org/soap/envelope/"
};
////////////////////////////////////////////////////////////////////////////////
Parameter::Parameter(const std::string& key, const char* value)
: key_(key), value_(value) {
}
@ -110,4 +103,8 @@ Parameter& Parameter::operator=(Parameter&& rhs) {
return *this;
}
std::string Parameter::ToString() const {
return key_ + "=" + value_;
}
} // namespace webcc

@ -1,17 +1,47 @@
#ifndef WEBCC_COMMON_H_
#define WEBCC_COMMON_H_
// Common definitions.
#ifndef WEBCC_GLOBALS_H_
#define WEBCC_GLOBALS_H_
#include <string>
#include <vector>
namespace webcc {
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// Macros
// Explicitly declare the assignment operator as deleted.
#define DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete;
// Explicitly declare the copy constructor and assignment operator as deleted.
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
DISALLOW_ASSIGN(TypeName)
// Explicitly declare all implicit constructors as deleted, namely the
// default constructor, copy constructor and operator= functions.
// This is especially useful for classes containing only static methods.
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
TypeName() = delete; \
DISALLOW_COPY_AND_ASSIGN(TypeName)
// Disallow copying a type, but provide default construction, move construction
// and move assignment. Especially useful for move-only structs.
#define MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS(TypeName) \
TypeName() = default; \
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(TypeName)
// Disallow copying a type, and only provide move construction and move
// assignment. Especially useful for move-only structs.
#define MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(TypeName) \
TypeName(TypeName&&) = default; \
TypeName& operator=(TypeName&&) = default; \
DISALLOW_COPY_AND_ASSIGN(TypeName)
// -----------------------------------------------------------------------------
// Constants
// Buffer size for sending HTTP request and receiving HTTP response.
// TODO: Configurable for client and server separately.
// TODO: Configurable
const std::size_t kBufferSize = 1024;
const std::size_t kInvalidLength = static_cast<std::size_t>(-1);
@ -24,8 +54,6 @@ extern const std::string kHost;
extern const std::string kTextXmlUtf8;
extern const std::string kTextJsonUtf8;
////////////////////////////////////////////////////////////////////////////////
// HTTP methods (verbs) in string ("HEAD", "GET", etc.).
// NOTE: Don't use enum to avoid converting back and forth.
extern const std::string kHttpHead;
@ -54,11 +82,9 @@ struct HttpStatus {
};
};
////////////////////////////////////////////////////////////////////////////////
// Error codes.
enum Error {
kNoError = 0, // OK
kNoError = 0,
kHostResolveError,
kEndpointConnectError,
@ -83,31 +109,14 @@ enum Error {
// Return a descriptive message for the given error code.
const char* GetErrorMessage(Error error);
////////////////////////////////////////////////////////////////////////////////
// XML namespace name/url pair.
// E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" }
class SoapNamespace {
public:
std::string name;
std::string url;
bool IsValid() const {
return !name.empty() && !url.empty();
}
};
// CSoap's default namespace for SOAP Envelope.
extern const SoapNamespace kSoapEnvNamespace;
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// Key-value parameter.
class Parameter {
public:
Parameter() = default;
Parameter(const Parameter& rhs) = default;
Parameter& operator=(const Parameter& rhs) = default;
Parameter(const Parameter&) = default;
Parameter& operator=(const Parameter&) = default;
Parameter(const std::string& key, const char* value);
Parameter(const std::string& key, const std::string& value);
@ -138,6 +147,9 @@ public:
return value_.c_str();
}
// Return "key=value" string.
std::string ToString() const;
private:
std::string key_;
std::string value_;
@ -145,4 +157,4 @@ private:
} // namespace webcc
#endif // WEBCC_COMMON_H_
#endif // WEBCC_GLOBALS_H_

@ -0,0 +1,169 @@
#include "webcc/http_async_client.h"
#if WEBCC_DEBUG_OUTPUT
#include <iostream>
#endif
#if 0
#include "boost/asio.hpp"
#else
#include "boost/asio/connect.hpp"
//#include "boost/asio/ip/tcp.hpp"
#include "boost/asio/read.hpp"
#include "boost/asio/write.hpp"
#endif
#include "webcc/http_response_parser.h"
#include "webcc/http_request.h"
#include "webcc/http_response.h"
using boost::asio::ip::tcp;
namespace webcc {
HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context)
: socket_(io_context) {
resolver_.reset(new tcp::resolver(io_context));
response_.reset(new HttpResponse());
parser_.reset(new HttpResponseParser(response_.get()));
}
Error HttpAsyncClient::SendRequest(std::shared_ptr<HttpRequest> request,
HttpResponseHandler response_handler) {
request_ = request;
std::string port = request->port();
if (port.empty()) {
port = "80";
}
auto handler = std::bind(&HttpAsyncClient::HandleResolve,
this,
std::placeholders::_1,
std::placeholders::_2);
resolver_->async_resolve(tcp::v4(), request->host(), port, handler);
return kNoError;
}
void HttpAsyncClient::HandleResolve(boost::system::error_code ec,
tcp::resolver::results_type results) {
if (ec) {
std::cerr << "Resolve: " << ec.message() << std::endl;
// return kHostResolveError;
} else {
endpoints_ = results;
DoConnect(endpoints_.begin());
}
}
void HttpAsyncClient::DoConnect(tcp::resolver::results_type::iterator endpoint_it) {
if (endpoint_it != endpoints_.end()) {
socket_.async_connect(endpoint_it->endpoint(),
std::bind(&HttpAsyncClient::HandleConnect,
this,
std::placeholders::_1,
endpoint_it));
}
}
void HttpAsyncClient::HandleConnect(boost::system::error_code ec,
tcp::resolver::results_type::iterator endpoint_it) {
if (ec) {
// Will be here if the end point is v6.
std::cout << "Connect error: " << ec.message() << std::endl;
// return kEndpointConnectError;
socket_.close();
// Try the next available endpoint.
DoConnect(++endpoint_it);
} else {
DoWrite();
}
}
// Send HTTP request.
void HttpAsyncClient::DoWrite() {
boost::asio::async_write(socket_,
request_->ToBuffers(),
std::bind(&HttpAsyncClient::HandleWrite,
this,
std::placeholders::_1));
}
void HttpAsyncClient::HandleWrite(boost::system::error_code ec) {
if (ec) {
//return kSocketWriteError;
return;
}
DoRead();
}
void HttpAsyncClient::DoRead() {
socket_.async_read_some(boost::asio::buffer(buffer_),
std::bind(&HttpAsyncClient::HandleRead,
this,
std::placeholders::_1,
std::placeholders::_2));
}
void HttpAsyncClient::HandleRead(boost::system::error_code ec,
std::size_t length) {
if (ec || length == 0) {
//return kSocketReadError;
return;
}
#if WEBCC_DEBUG_OUTPUT
// NOTE: the content XML might not be well formated.
std::cout.write(buffer_.data(), length);
#endif
// Parse the response piece just read.
// If the content has been fully received, next time flag "finished_"
// will be set.
Error error = parser_->Parse(buffer_.data(), length);
if (error != kNoError) {
//return error;
}
if (parser_->finished()) {
return;
}
// Read and parse HTTP response.
// NOTE:
// We must stop trying to read once all content has been received,
// because some servers will block extra call to read_some().
//while (!parser_.finished()) {
// size_t length = socket_.read_some(boost::asio::buffer(buffer_), ec);
//if (length == 0 || ec) {
// return kSocketReadError;
//}
// Parse the response piece just read.
// If the content has been fully received, next time flag "finished_"
// will be set.
//Error error = parser_.Parse(buffer_.data(), length);
//if (error != kNoError) {
// return error;
//}
//}
//#if WEBCC_DEBUG_OUTPUT
// std::cout << std::endl;
// std::cout << "--- RESPONSE (PARSED) ---" << std::endl;
// std::cout << *response << std::endl;
//#endif
}
} // namespace webcc

@ -0,0 +1,62 @@
#ifndef WEBCC_HTTP_ASYNC_CLIENT_H_
#define WEBCC_HTTP_ASYNC_CLIENT_H_
#include <array>
#include "boost/smart_ptr/scoped_ptr.hpp"
#include "boost/asio/io_context.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "webcc/common.h"
#include "webcc/http_response_parser.h"
namespace webcc {
class HttpRequest;
class HttpResponse;
typedef void(*HttpResponseHandler)(std::shared_ptr<HttpResponse>);
class HttpAsyncClient {
public:
HttpAsyncClient(boost::asio::io_context& io_context);
Error SendRequest(std::shared_ptr<HttpRequest> request,
HttpResponseHandler response_handler);
private:
void HandleResolve(boost::system::error_code ec,
boost::asio::ip::tcp::resolver::results_type results);
void DoConnect(boost::asio::ip::tcp::resolver::results_type::iterator endpoint_it);
void HandleConnect(boost::system::error_code ec,
boost::asio::ip::tcp::resolver::results_type::iterator endpoint_it);
void DoWrite();
void HandleWrite(boost::system::error_code ec);
void DoRead();
void HandleRead(boost::system::error_code ec, std::size_t length);
private:
boost::asio::ip::tcp::socket socket_;
std::shared_ptr<HttpRequest> request_;
std::unique_ptr<boost::asio::ip::tcp::resolver> resolver_;
boost::asio::ip::tcp::resolver::results_type endpoints_;
std::array<char, kBufferSize> buffer_;
std::unique_ptr<HttpResponseParser> parser_;
std::shared_ptr<HttpResponse> response_;
};
} // namespace webcc
#endif // WEBCC_HTTP_ASYNC_CLIENT_H_

@ -3,14 +3,9 @@
#include "boost/date_time/posix_time/posix_time.hpp"
#include "boost/lambda/bind.hpp"
#include "boost/lambda/lambda.hpp"
#if 0
#include "boost/asio.hpp"
#else
#include "boost/asio/connect.hpp"
#include "boost/asio/read.hpp"
#include "boost/asio/write.hpp"
#endif
#include "webcc/logger.h"
#include "webcc/http_request.h"

@ -5,10 +5,10 @@
#include <memory>
#include "boost/asio/deadline_timer.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "boost/asio/io_context.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "webcc/common.h"
#include "webcc/globals.h"
#include "webcc/http_response_parser.h"
namespace webcc {

@ -3,7 +3,8 @@
#include <cassert>
#include <string>
#include "webcc/common.h"
#include "webcc/globals.h"
namespace webcc {
@ -16,9 +17,6 @@ public:
// Base class for HTTP request and response messages.
class HttpMessage {
public:
HttpMessage() = default;
HttpMessage(const HttpMessage&) = default;
HttpMessage& operator=(const HttpMessage&) = default;
virtual ~HttpMessage() = default;
const std::string& start_line() const {

@ -2,7 +2,8 @@
#define WEBCC_HTTP_PARSER_H_
#include <string>
#include "webcc/common.h"
#include "webcc/globals.h"
namespace webcc {
@ -15,9 +16,6 @@ public:
virtual ~HttpParser() = default;
HttpParser(const HttpParser&) = delete;
HttpParser& operator=(const HttpParser&) = delete;
bool finished() const {
return finished_;
}
@ -53,6 +51,8 @@ protected:
bool content_length_parsed_;
bool header_parsed_;
bool finished_;
DISALLOW_COPY_AND_ASSIGN(HttpParser);
};
} // namespace webcc

@ -2,7 +2,9 @@
#define WEBCC_HTTP_REQUEST_H_
#include <string>
#include "boost/asio/buffer.hpp" // for const_buffer
#include "webcc/http_message.h"
namespace webcc {
@ -12,10 +14,6 @@ class HttpRequest;
std::ostream& operator<<(std::ostream& os, const HttpRequest& request);
class HttpRequest : public HttpMessage {
friend std::ostream& operator<<(std::ostream& os,
const HttpRequest& request);
public:
HttpRequest() = default;
HttpRequest(const HttpRequest&) = default;
@ -47,8 +45,9 @@ public:
return port_;
}
// \param host Descriptive host name or numeric IP address.
// \param port Numeric port number, "80" will be used if it's empty.
// Set host name and port number.
// The |host| is a descriptive name or a numeric IP address. The |port| is
// a numeric number (e.g., "9000") and "80" will be used if it's empty.
void SetHost(const std::string& host, const std::string& port);
// Compose start line, etc.
@ -64,6 +63,8 @@ public:
std::string Dump() const;
private:
friend std::ostream& operator<<(std::ostream& os, const HttpRequest& request);
// HTTP method.
std::string method_;

@ -2,10 +2,10 @@
#include <sstream>
#include "webcc/logger.h"
#include "webcc/common.h"
#include "webcc/globals.h"
#include "webcc/http_request.h"
#include "webcc/http_response.h"
#include "webcc/logger.h"
namespace webcc {

@ -18,13 +18,8 @@ class HttpResponse;
// The common handler for all incoming requests.
class HttpRequestHandler {
public:
HttpRequestHandler(const HttpRequestHandler&) = delete;
HttpRequestHandler& operator=(const HttpRequestHandler&) = delete;
HttpRequestHandler() = default;
virtual ~HttpRequestHandler() {
}
virtual ~HttpRequestHandler() = default;
// Put the session into the queue.
void Enqueue(HttpSessionPtr session);
@ -41,9 +36,10 @@ private:
// Called by the worker routine.
virtual void HandleSession(HttpSessionPtr session) = 0;
private:
Queue<HttpSessionPtr> queue_;
boost::thread_group workers_;
DISALLOW_COPY_AND_ASSIGN(HttpRequestHandler);
};
} // namespace webcc

@ -16,7 +16,6 @@ public:
private:
Error ParseStartLine(const std::string& line) override;
private:
HttpRequest* request_;
};

@ -1,4 +1,5 @@
#include "webcc/http_response.h"
#include <iostream>
#include <sstream>

@ -2,7 +2,9 @@
#define WEBCC_HTTP_RESPONSE_H_
#include <string>
#include "boost/asio/buffer.hpp" // for const_buffer
#include "webcc/http_message.h"
namespace webcc {
@ -12,9 +14,6 @@ class HttpResponse;
std::ostream& operator<<(std::ostream& os, const HttpResponse& response);
class HttpResponse : public HttpMessage {
friend std::ostream& operator<<(std::ostream& os,
const HttpResponse& response);
public:
HttpResponse() = default;
~HttpResponse() override = default;
@ -39,6 +38,9 @@ public:
static HttpResponse Fault(HttpStatus::Enum status);
private:
friend std::ostream& operator<<(std::ostream& os,
const HttpResponse& response);
int status_ = HttpStatus::kOK;
};

@ -1,5 +1,7 @@
#include "webcc/http_response_parser.h"
#include "boost/lexical_cast.hpp"
#include "webcc/http_response.h"
namespace webcc {

@ -1,9 +1,9 @@
#include "webcc/http_server.h"
#include <signal.h>
#include <csignal>
#include "webcc/logger.h"
#include "webcc/http_request_handler.h"
#include "webcc/logger.h"
#include "webcc/soap_service.h"
#include "webcc/utility.h"

@ -4,12 +4,11 @@
#include <string>
#include <vector>
#include "boost/scoped_ptr.hpp"
#include "boost/thread/thread.hpp"
#include "boost/asio/io_context.hpp"
#include "boost/asio/signal_set.hpp"
#include "boost/asio/ip/tcp.hpp"
#include "boost/asio/signal_set.hpp"
#include "boost/scoped_ptr.hpp"
#include "boost/thread/thread.hpp"
#include "webcc/http_session.h"

@ -5,8 +5,8 @@
#include "boost/asio/write.hpp"
#include "boost/date_time/posix_time/posix_time.hpp"
#include "webcc/logger.h"
#include "webcc/http_request_handler.h"
#include "webcc/logger.h"
namespace webcc {

@ -6,7 +6,7 @@
#include "boost/asio/ip/tcp.hpp" // for ip::tcp::socket
#include "webcc/common.h"
#include "webcc/globals.h"
#include "webcc/http_request.h"
#include "webcc/http_request_parser.h"
#include "webcc/http_response.h"

@ -15,11 +15,11 @@ namespace webcc {
template <typename T>
class Queue {
public:
Queue(const Queue& rhs) = delete;
Queue& operator=(const Queue& rhs) = delete;
Queue() = default;
Queue(const Queue&) = delete;
Queue& operator=(const Queue&) = delete;
T PopOrWait() {
boost::unique_lock<boost::mutex> lock(mutex_);

@ -0,0 +1,31 @@
#include "webcc/rest_client.h"
#include "webcc/http_client.h"
#include "webcc/http_request.h"
#include "webcc/http_response.h"
namespace webcc {
bool RestClient::Request(const std::string& method,
const std::string& url,
const std::string& content,
HttpResponse* response) {
HttpRequest request;
request.set_method(method);
request.set_url(url);
request.SetHost(host_, port_);
if (!content.empty()) {
request.SetContent(content);
}
request.Build();
HttpClient http_client;
Error error = http_client.MakeRequest(request, response);
return error == kNoError;
}
} // namespace webcc

@ -0,0 +1,56 @@
#ifndef WEBCC_REST_CLIENT_H_
#define WEBCC_REST_CLIENT_H_
#include <string>
#include "webcc/globals.h"
namespace webcc {
class HttpResponse;
class RestClient {
public:
RestClient(const std::string& host, const std::string& port)
: host_(host), port_(port) {
}
bool Get(const std::string& url, HttpResponse* response) {
return Request(kHttpGet, url, "", response);
}
bool Post(const std::string& url,
const std::string& content,
HttpResponse* response) {
return Request(kHttpPost, url, content, response);
}
bool Put(const std::string& url,
const std::string& content,
HttpResponse* response) {
return Request(kHttpPut, url, content, response);
}
bool Patch(const std::string& url,
const std::string& content,
HttpResponse* response) {
return Request(kHttpPatch, url, content, response);
}
bool Delete(const std::string& url, HttpResponse* response) {
return Request(kHttpDelete, url, "", response);
}
private:
bool Request(const std::string& method,
const std::string& url,
const std::string& content,
HttpResponse* response);
std::string host_;
std::string port_;
};
} // namespace webcc
#endif // WEBCC_REST_CLIENT_H_

@ -0,0 +1,50 @@
#include "webcc/rest_request_handler.h"
#include "webcc/logger.h"
#include "webcc/url.h"
namespace webcc {
bool RestRequestHandler::RegisterService(RestServicePtr service,
const std::string& url) {
return service_manager_.AddService(service, url);
}
void RestRequestHandler::HandleSession(HttpSessionPtr session) {
Url url(session->request().url(), true);
if (!url.IsValid()) {
session->SendResponse(HttpStatus::kBadRequest);
return;
}
std::vector<std::string> sub_matches;
RestServicePtr service = service_manager_.GetService(url.path(),
&sub_matches);
if (!service) {
LOG_WARN("No service matches the URL: %s", url.path().c_str());
session->SendResponse(HttpStatus::kBadRequest);
return;
}
// TODO: Only for GET?
UrlQuery query;
Url::SplitQuery(url.query(), &query);
std::string content;
bool ok = service->Handle(session->request().method(),
sub_matches,
query,
session->request().content(),
&content);
if (!ok) {
// TODO: Could be other than kBadRequest.
session->SendResponse(HttpStatus::kBadRequest);
return;
}
session->SetResponseContent(std::move(content), kTextJsonUtf8);
session->SendResponse(HttpStatus::kOK);
}
} // namespace webcc

@ -0,0 +1,29 @@
#ifndef WEBCC_REST_REQUEST_HANDLER_H_
#define WEBCC_REST_REQUEST_HANDLER_H_
// HTTP server handling REST requests.
#include "webcc/http_request_handler.h"
#include "webcc/rest_service_manager.h"
namespace webcc {
class RestRequestHandler : public HttpRequestHandler {
public:
~RestRequestHandler() override = default;
// Register a REST service to the given URL path.
// 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:
void HandleSession(HttpSessionPtr session) override;
private:
RestServiceManager service_manager_;
};
} // namespace webcc
#endif // WEBCC_REST_REQUEST_HANDLER_H_

@ -1,114 +0,0 @@
#include "webcc/rest_server.h"
#include "webcc/logger.h"
#include "webcc/url.h"
namespace webcc {
////////////////////////////////////////////////////////////////////////////////
bool RestServiceManager::AddService(RestServicePtr service,
const std::string& url) {
assert(service);
ServiceItem item(service, url);
std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase;
try {
// Compile the regex.
item.url_regex.assign(url, flags);
service_items_.push_back(item);
return true;
} catch (std::regex_error& e) {
LOG_ERRO("URL is not a valid regular expression: %s", e.what());
}
return false;
}
RestServicePtr RestServiceManager::GetService(
const std::string& url,
std::vector<std::string>* sub_matches) {
assert(sub_matches != NULL);
for (ServiceItem& item : service_items_) {
std::smatch match;
if (std::regex_match(url, match, item.url_regex)) {
// Any sub-matches?
// NOTE: Start from 1 because match[0] is the whole string itself.
for (size_t i = 1; i < match.size(); ++i) {
sub_matches->push_back(match[i].str());
}
return item.service;
}
}
return RestServicePtr();
}
////////////////////////////////////////////////////////////////////////////////
bool RestRequestHandler::RegisterService(RestServicePtr service,
const std::string& url) {
return service_manager_.AddService(service, url);
}
void RestRequestHandler::HandleSession(HttpSessionPtr session) {
Url url(session->request().url());
if (!url.IsValid()) {
session->SendResponse(HttpStatus::kBadRequest);
return;
}
std::vector<std::string> sub_matches;
RestServicePtr service = service_manager_.GetService(url.path(), &sub_matches);
if (!service) {
LOG_WARN("No service matches the URL: %s", url.path().c_str());
session->SendResponse(HttpStatus::kBadRequest);
return;
}
// TODO: Only for GET?
UrlQuery query;
Url::SplitQuery(url.query(), &query);
std::string content;
bool ok = service->Handle(session->request().method(),
sub_matches,
query,
session->request().content(),
&content);
if (!ok) {
// TODO: Could be other than kBadRequest.
session->SendResponse(HttpStatus::kBadRequest);
return;
}
session->SetResponseContent(std::move(content), kTextJsonUtf8);
session->SendResponse(HttpStatus::kOK);
}
////////////////////////////////////////////////////////////////////////////////
RestServer::RestServer(unsigned short port, std::size_t workers)
: HttpServer(port, workers)
, request_handler_(new RestRequestHandler()) {
}
RestServer::~RestServer() {
delete request_handler_;
}
bool RestServer::RegisterService(RestServicePtr service,
const std::string& url) {
return request_handler_->RegisterService(service, url);
}
} // namespace webcc

@ -3,106 +3,36 @@
// HTTP server handling REST requests.
#include <regex>
#include <string>
#include <vector>
#include "webcc/http_request_handler.h"
#include "webcc/http_server.h"
#include "webcc/rest_request_handler.h"
#include "webcc/rest_service.h"
namespace webcc {
class Url;
////////////////////////////////////////////////////////////////////////////////
class RestServiceManager {
public:
RestServiceManager() = default;
RestServiceManager(const RestServiceManager&) = delete;
RestServiceManager& operator=(const RestServiceManager&) = delete;
// Add a service and bind it with the given URL.
// The URL should start with "/" and could be a regular expression or not.
// E.g., "/instances". "/instances/(\\d+)"
bool AddService(RestServicePtr service, const std::string& url);
// Parameter 'sub_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 sub-match of "12345".
RestServicePtr GetService(const std::string& url,
std::vector<std::string>* sub_matches);
private:
class ServiceItem {
public:
ServiceItem(RestServicePtr _service, const std::string& _url)
: service(_service), url(_url) {
}
ServiceItem(const ServiceItem& rhs) = default;
ServiceItem& operator=(const ServiceItem& rhs) = default;
ServiceItem(ServiceItem&& rhs)
: url(std::move(rhs.url))
, url_regex(std::move(rhs.url_regex))
, service(rhs.service) { // No move
}
RestServicePtr service;
// URL string, e.g., "/instances/(\\d+)".
std::string url;
// Compiled regex for URL string.
std::regex url_regex;
};
std::vector<ServiceItem> service_items_;
};
////////////////////////////////////////////////////////////////////////////////
class RestRequestHandler : public HttpRequestHandler {
public:
RestRequestHandler() = default;
// Register a REST service to the given URL path.
// 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:
void HandleSession(HttpSessionPtr session) override;
private:
RestServiceManager service_manager_;
};
////////////////////////////////////////////////////////////////////////////////
class RestServer : public HttpServer {
public:
RestServer(unsigned short port, std::size_t workers);
RestServer(unsigned short port, std::size_t workers)
: HttpServer(port, workers) {
}
~RestServer() override;
~RestServer() override = default;
// Register a REST service to the given URL path.
// The URL should start with "/" and could be a regular expression or not.
// E.g., "/instances". "/instances/(\\d+)"
// NOTE: Registering to the same URL multiple times is allowed, but only the
// last one takes effect.
bool RegisterService(RestServicePtr service, const std::string& url);
bool RegisterService(RestServicePtr service, const std::string& url) {
return request_handler_.RegisterService(service, url);
}
private:
HttpRequestHandler* GetRequestHandler() override {
return request_handler_;
return &request_handler_;
}
private:
RestRequestHandler* request_handler_;
RestRequestHandler request_handler_;
};
} // namespace webcc

@ -1,4 +1,5 @@
#include "webcc/rest_service.h"
#include "webcc/logger.h"
namespace webcc {
@ -29,7 +30,7 @@ bool RestDetailService::Handle(const std::string& http_method,
const std::string& request_content,
std::string* response_content) {
if (http_method == kHttpGet) {
return Get(url_sub_matches, response_content);
return Get(url_sub_matches, query, response_content);
}
if (http_method == kHttpPut) {

@ -13,7 +13,7 @@
#include <string>
#include <vector>
#include "webcc/common.h"
#include "webcc/globals.h"
namespace webcc {
@ -28,12 +28,9 @@ public:
}
// Handle REST request, output the response.
// \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.
// \param request_content Request JSON.
// \param response_content Output response JSON.
// The regex sub-matches of the URL (usually resource IDs) were stored in
// |url_sub_matches|. The |query| part of the URL is normally only for GET
// request. Both the request and response contents are JSON strings.
virtual bool Handle(const std::string& http_method,
const std::vector<std::string>& url_sub_matches,
const UrlQuery& query,
@ -79,6 +76,7 @@ class RestDetailService : public RestService {
protected:
virtual bool Get(const std::vector<std::string>& url_sub_matches,
const UrlQuery& query,
std::string* response_content) {
return false;
}

@ -0,0 +1,55 @@
#include "webcc/rest_service_manager.h"
#include <cassert>
#include "webcc/logger.h"
namespace webcc {
bool RestServiceManager::AddService(RestServicePtr service,
const std::string& url) {
assert(service);
ServiceItem item(service, url);
std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase;
try {
// Compile the regex.
item.url_regex.assign(url, flags);
service_items_.push_back(item);
return true;
} catch (std::regex_error& e) {
LOG_ERRO("URL is not a valid regular expression: %s", e.what());
}
return false;
}
RestServicePtr RestServiceManager::GetService(
const std::string& url,
std::vector<std::string>* sub_matches) {
assert(sub_matches != NULL);
for (ServiceItem& item : service_items_) {
std::smatch match;
if (std::regex_match(url, match, item.url_regex)) {
// Any sub-matches?
// NOTE: Start from 1 because match[0] is the whole string itself.
for (size_t i = 1; i < match.size(); ++i) {
sub_matches->push_back(match[i].str());
}
return item.service;
}
}
return RestServicePtr();
}
} // namespace webcc

@ -0,0 +1,59 @@
#ifndef WEBCC_REST_SERVICE_MANAGER_H_
#define WEBCC_REST_SERVICE_MANAGER_H_
#include <regex>
#include <vector>
#include "webcc/rest_service.h"
namespace webcc {
class RestServiceManager {
public:
RestServiceManager() = default;
// Add a service and bind it with the given URL.
// The |url| should start with "/" and could be a regular expression or not.
// E.g., "/instances". "/instances/(\\d+)"
bool AddService(RestServicePtr service, const std::string& url);
// The |sub_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 sub-match of "12345".
RestServicePtr GetService(const std::string& url,
std::vector<std::string>* sub_matches);
private:
class ServiceItem {
public:
ServiceItem(RestServicePtr _service, const std::string& _url)
: service(_service), url(_url) {
}
ServiceItem(const ServiceItem& rhs) = default;
ServiceItem& operator=(const ServiceItem& rhs) = default;
ServiceItem(ServiceItem&& rhs)
: url(std::move(rhs.url)),
url_regex(std::move(rhs.url_regex)),
service(rhs.service) { // No move
}
RestServicePtr service;
// URL string, e.g., "/instances/(\\d+)".
std::string url;
// Compiled regex for URL string.
std::regex url_regex;
};
std::vector<ServiceItem> service_items_;
DISALLOW_COPY_AND_ASSIGN(RestServiceManager);
};
} // namespace webcc
#endif // WEBCC_REST_SERVICE_MANAGER_H_

@ -4,7 +4,8 @@
#include <string>
#include <vector>
#include "webcc/common.h"
#include "webcc/globals.h"
#include "webcc/soap_message.h"
namespace webcc {

@ -1,10 +1,16 @@
#include "webcc/soap_message.h"
#include <cassert>
#include "webcc/soap_xml.h"
namespace webcc {
const SoapNamespace kSoapEnvNamespace{
"soap",
"http://schemas.xmlsoap.org/soap/envelope/"
};
void SoapMessage::ToXml(std::string* xml_string) {
assert(soapenv_ns_.IsValid() &&
service_ns_.IsValid() &&

@ -2,14 +2,33 @@
#define WEBCC_SOAP_MESSAGE_H_
#include <string>
#include "pugixml/pugixml.hpp"
#include "webcc/common.h"
#include "webcc/globals.h"
namespace webcc {
// XML namespace name/url pair.
// E.g., { "soap", "http://schemas.xmlsoap.org/soap/envelope/" }
class SoapNamespace {
public:
std::string name;
std::string url;
bool IsValid() const {
return !name.empty() && !url.empty();
}
};
// CSoap's default namespace for SOAP Envelope.
extern const SoapNamespace kSoapEnvNamespace;
// Base class for SOAP request and response.
class SoapMessage {
public:
virtual ~SoapMessage() {}
// E.g., set as kSoapEnvNamespace.
void set_soapenv_ns(const SoapNamespace& soapenv_ns) {
soapenv_ns_ = soapenv_ns;
@ -34,8 +53,6 @@ public:
bool FromXml(const std::string& xml_string);
protected:
SoapMessage() = default;
// Convert to SOAP body XML.
virtual void ToXmlBody(pugi::xml_node xbody) = 0;

@ -1,4 +1,5 @@
#include "webcc/soap_request.h"
#include "webcc/soap_xml.h"
namespace webcc {

@ -2,6 +2,7 @@
#define WEBCC_SOAP_REQUEST_H_
#include <vector>
#include "webcc/soap_message.h"
namespace webcc {

@ -1,4 +1,4 @@
#include "webcc/soap_server.h"
#include "webcc/soap_request_handler.h"
#include "webcc/logger.h"
#include "webcc/soap_request.h"
@ -6,8 +6,6 @@
namespace webcc {
////////////////////////////////////////////////////////////////////////////////
bool SoapRequestHandler::RegisterService(SoapServicePtr service,
const std::string& url) {
assert(service);
@ -55,20 +53,4 @@ SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) {
return SoapServicePtr();
}
////////////////////////////////////////////////////////////////////////////////
SoapServer::SoapServer(unsigned short port, std::size_t workers)
: HttpServer(port, workers)
, request_handler_(new SoapRequestHandler()) {
}
SoapServer::~SoapServer() {
delete request_handler_;
}
bool SoapServer::RegisterService(SoapServicePtr service,
const std::string& url) {
return request_handler_->RegisterService(service, url);
}
} // namespace webcc

@ -0,0 +1,32 @@
#ifndef WEBCC_SOAP_REQUEST_HANDLER_H_
#define WEBCC_SOAP_REQUEST_HANDLER_H_
#include <map>
#include "webcc/http_request_handler.h"
namespace webcc {
class SoapRequestHandler : public HttpRequestHandler {
public:
SoapRequestHandler() = default;
~SoapRequestHandler() override = default;
// Register a SOAP service to the given URL path.
// 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:
void HandleSession(HttpSessionPtr session) override;
SoapServicePtr GetServiceByUrl(const std::string& url);
typedef std::map<std::string, SoapServicePtr> UrlServiceMap;
UrlServiceMap url_service_map_;
};
} // namespace webcc
#endif // WEBCC_SOAP_REQUEST_HANDLER_H_

@ -3,53 +3,29 @@
// HTTP server handling SOAP requests.
#include <map>
#include <string>
#include "webcc/http_request_handler.h"
#include "webcc/soap_request_handler.h"
#include "webcc/http_server.h"
namespace webcc {
////////////////////////////////////////////////////////////////////////////////
class SoapRequestHandler : public HttpRequestHandler {
public:
SoapRequestHandler() = default;
// Register a SOAP service to the given URL path.
// \url URL path, must start with "/". E.g., "/calculator".
// NOTE: 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:
void HandleSession(HttpSessionPtr session) override;
SoapServicePtr GetServiceByUrl(const std::string& url);
private:
typedef std::map<std::string, SoapServicePtr> UrlServiceMap;
UrlServiceMap url_service_map_;
};
////////////////////////////////////////////////////////////////////////////////
class SoapServer : public HttpServer {
public:
SoapServer(unsigned short port, std::size_t workers);
SoapServer(unsigned short port, std::size_t workers)
: HttpServer(port, workers) {
}
~SoapServer() override;
~SoapServer() override = default;
bool RegisterService(SoapServicePtr service, const std::string& url);
bool RegisterService(SoapServicePtr service, const std::string& url) {
return request_handler_.RegisterService(service, url);
}
private:
HttpRequestHandler* GetRequestHandler() override {
return request_handler_;
return &request_handler_;
}
private:
SoapRequestHandler* request_handler_;
SoapRequestHandler request_handler_;
};
} // namespace webcc

@ -2,7 +2,8 @@
#define WEBCC_SOAP_SERVICE_H_
#include <memory>
#include "webcc/common.h"
#include "webcc/globals.h"
namespace webcc {
@ -12,8 +13,7 @@ class SoapResponse;
// Base class for your SOAP service.
class SoapService {
public:
virtual ~SoapService() {
}
virtual ~SoapService() = default;
// Handle SOAP request, output the response.
virtual bool Handle(const SoapRequest& soap_request,

@ -3,8 +3,7 @@
namespace webcc {
namespace soap_xml {
void SplitName(const pugi::xml_node& xnode,
std::string* prefix,
void SplitName(const pugi::xml_node& xnode, std::string* prefix,
std::string* name) {
std::string full_name = xnode.name();
@ -39,14 +38,12 @@ std::string GetNameNoPrefix(const pugi::xml_node& xnode) {
return name;
}
pugi::xml_node AddChild(pugi::xml_node& xnode,
const std::string& ns,
pugi::xml_node AddChild(pugi::xml_node& xnode, const std::string& ns,
const std::string& name) {
return xnode.append_child((ns + ":" + name).c_str());
}
pugi::xml_node GetChild(pugi::xml_node& xnode,
const std::string& ns,
pugi::xml_node GetChild(pugi::xml_node& xnode, const std::string& ns,
const std::string& name) {
return xnode.child((ns + ":" + name).c_str());
}
@ -72,28 +69,23 @@ pugi::xml_node GetChildNoNS(pugi::xml_node& xnode, const std::string& name) {
return pugi::xml_node();
}
void AddAttr(pugi::xml_node& xnode,
const std::string& ns,
const std::string& name,
const std::string& value) {
void AddAttr(pugi::xml_node& xnode, const std::string& ns,
const std::string& name, const std::string& value) {
std::string ns_name = ns + ":" + name;
xnode.append_attribute(ns_name.c_str()) = value.c_str();
}
void AddNSAttr(pugi::xml_node& xnode,
const std::string& ns_name,
void AddNSAttr(pugi::xml_node& xnode, const std::string& ns_name,
const std::string& ns_url) {
AddAttr(xnode, "xmlns", ns_name, ns_url);
}
std::string GetNSAttr(pugi::xml_node& xnode,
const std::string& ns_name) {
std::string GetNSAttr(pugi::xml_node& xnode, const std::string& ns_name) {
std::string attr_name = "xmlns:" + ns_name;
return xnode.attribute(attr_name.c_str()).as_string();
}
bool PrettyPrint(std::ostream& os,
const std::string& xml_string,
bool PrettyPrint(std::ostream& os, const std::string& xml_string,
const char* indent) {
pugi::xml_document xdoc;
if (!xdoc.load_string(xml_string.c_str())) {

@ -1,19 +1,19 @@
#ifndef WEBCC_SOAP_XML_H_
#define WEBCC_SOAP_XML_H_
// XML utilities.
// XML helpers for SOAP messages.
#include <string>
#include "pugixml/pugixml.hpp"
namespace webcc {
namespace soap_xml {
// Split the node name into namespace prefix and real name.
// E.g., if the node name is "soapenv:Envelope", it will be splited to
// E.g., if the node name is "soapenv:Envelope", it will be splitted to
// "soapenv" and "Envelope".
void SplitName(const pugi::xml_node& xnode,
std::string* prefix = NULL,
void SplitName(const pugi::xml_node& xnode, std::string* prefix = NULL,
std::string* name = NULL);
// Get the namespace prefix from node name.
@ -26,38 +26,31 @@ std::string GetNameNoPrefix(const pugi::xml_node& xnode);
// Add a child with the given name which is prefixed by a namespace.
// E.g., AppendChild(xnode, "soapenv", "Envelope") will append a child with
// name "soapenv:Envelope".
pugi::xml_node AddChild(pugi::xml_node& xnode,
const std::string& ns,
pugi::xml_node AddChild(pugi::xml_node& xnode, const std::string& ns,
const std::string& name);
pugi::xml_node GetChild(pugi::xml_node& xnode,
const std::string& ns,
pugi::xml_node GetChild(pugi::xml_node& xnode, const std::string& ns,
const std::string& name);
// TODO: Remove
pugi::xml_node GetChildNoNS(pugi::xml_node& xnode,
const std::string& name);
pugi::xml_node GetChildNoNS(pugi::xml_node& xnode, const std::string& name);
// Add an attribute with the given name which is prefixed by a namespace.
void AddAttr(pugi::xml_node& xnode,
const std::string& ns,
const std::string& name,
const std::string& value);
void AddAttr(pugi::xml_node& xnode, const std::string& ns,
const std::string& name, const std::string& value);
// Append "xmlns" attribute.
// E.g., if the namespace is
// { "soapenv", "http://schemas.xmlsoap.org/soap/envelope/" }
// the attribute added will be
// xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
void AddNSAttr(pugi::xml_node& xnode,
const std::string& ns_name,
void AddNSAttr(pugi::xml_node& xnode, const std::string& ns_name,
const std::string& ns_url);
// Get namespace attribute value.
// E.g., if the given namespace name is "soapenv", the value of
// attribute "xmlns:soapenv" will be returned.
std::string GetNSAttr(pugi::xml_node& xnode,
const std::string& ns_name);
std::string GetNSAttr(pugi::xml_node& xnode, const std::string& ns_name);
// An XML writer writing to a referenced string.
// Example:
@ -68,12 +61,11 @@ std::string GetNSAttr(pugi::xml_node& xnode,
// xdoc.save(writer, "\t", pugi::format_default, pugi::encoding_utf8);
class XmlStrRefWriter : public pugi::xml_writer {
public:
explicit XmlStrRefWriter(std::string* result)
: result_(result) {
explicit XmlStrRefWriter(std::string* result) : result_(result) {
result_->clear();
}
virtual void write(const void* data, size_t size) override {
void write(const void* data, size_t size) override {
result_->append(static_cast<const char*>(data), size);
}
@ -82,8 +74,7 @@ private:
};
// Print the XML string to output stream in pretty format.
bool PrettyPrint(std::ostream& os,
const std::string& xml_string,
bool PrettyPrint(std::ostream& os, const std::string& xml_string,
const char* indent = "\t");
} // namespace soap_xml

@ -6,13 +6,78 @@
namespace webcc {
////////////////////////////////////////////////////////////////////////////////
// Helper functions to decode URL string.
// Convert a hex character digit to a decimal character value.
static bool HexToDecimal(char hex, int* decimal) {
if (hex >= '0' && hex <= '9') {
*decimal = hex - '0';
} else if (hex >= 'A' && hex <= 'F') {
*decimal = 10 + (hex - 'A');
} else if (hex >= 'a' && hex <= 'f') {
*decimal = 10 + (hex - 'a');
} else {
return false;
}
return true;
}
static bool Decode(const std::string& encoded, std::string* raw) {
for (auto iter = encoded.begin(); iter != encoded.end(); ++iter) {
if (*iter == '%') {
if (++iter == encoded.end()) {
// Invalid URI string, two hexadecimal digits must follow '%'.
return false;
}
int h_decimal = 0;
if (!HexToDecimal(*iter, &h_decimal)) {
return false;
}
if (++iter == encoded.end()) {
// Invalid URI string, two hexadecimal digits must follow '%'.
return false;
}
int l_decimal = 0;
if (!HexToDecimal(*iter, &l_decimal)) {
return false;
}
raw->push_back(static_cast<char>((h_decimal << 4) + l_decimal));
} else if (*iter > 127 || *iter < 0) {
// Invalid encoded URI string, must be entirely ASCII.
return false;
} else {
raw->push_back(*iter);
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
UrlQuery::UrlQuery(const std::map<std::string, std::string>& map) {
for (auto& pair : map) {
Add(pair.first, pair.second);
}
}
void UrlQuery::Add(std::string&& key, std::string&& value) {
if (!HasKey(key)) {
if (!Has(key)) {
parameters_.push_back({ std::move(key), std::move(value) });
}
}
void UrlQuery::Add(const std::string& key, const std::string& value) {
if (!Has(key)) {
parameters_.push_back({ key, value });
}
}
void UrlQuery::Remove(const std::string& key) {
auto it = Find(key);
if (it != parameters_.end()) {
@ -20,16 +85,31 @@ void UrlQuery::Remove(const std::string& key) {
}
}
const std::string& UrlQuery::GetValue(const std::string& key) const {
static const std::string kEmptyValue;
const std::string& UrlQuery::Get(const std::string& key) const {
auto it = Find(key);
if (it != parameters_.end()) {
return it->value();
}
static const std::string kEmptyValue;
return kEmptyValue;
}
std::string UrlQuery::ToString() const {
if (parameters_.empty()) {
return "";
}
std::string str = parameters_[0].ToString();
for (std::size_t i = 1; i < parameters_.size(); ++i) {
str += "&";
str += parameters_[i].ToString();
}
return str;
}
UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const {
return std::find_if(parameters_.begin(),
parameters_.end(),
@ -38,13 +118,18 @@ UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const {
////////////////////////////////////////////////////////////////////////////////
Url::Url(const std::string& str) {
std::size_t pos = str.find('?');
if (pos == std::string::npos) {
path_ = str;
Url::Url(const std::string& str, bool decode) {
if (!decode || str.find('%') == std::string::npos) {
Init(str);
return;
}
std::string decoded;
if (Decode(str, &decoded)) {
Init(decoded);
} else {
path_ = str.substr(0, pos);
query_ = str.substr(pos + 1);
// TODO: Exception?
Init(str);
}
}
@ -103,4 +188,14 @@ void Url::SplitQuery(const std::string& str, UrlQuery* query) {
}
}
void Url::Init(const std::string& str) {
std::size_t pos = str.find('?');
if (pos == std::string::npos) {
path_ = str;
} else {
path_ = str.substr(0, pos);
query_ = str.substr(pos + 1);
}
}
} // namespace webcc

@ -11,27 +11,44 @@
#include <string>
#include <vector>
#include "webcc/common.h"
#include "webcc/globals.h"
namespace webcc {
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// URL query parameters.
class UrlQuery {
public:
typedef std::vector<Parameter> Parameters;
UrlQuery() = default;
// Construct from key-value pairs.
explicit UrlQuery(const std::map<std::string, std::string>& map);
void Add(const std::string& key, const std::string& value);
void Add(std::string&& key, std::string&& value);
void Remove(const std::string& key);
const std::string& GetValue(const std::string& key) const;
// Get a value by key.
// Return empty string if the key doesn't exist.
const std::string& Get(const std::string& key) const;
bool HasKey(const std::string& key) const {
bool Has(const std::string& key) const {
return Find(key) != parameters_.end();
}
bool IsEmpty() const {
return parameters_.empty();
}
// Return key-value pairs concatenated by '&'.
// E.g., "item=12731&color=blue&size=large".
std::string ToString() const;
private:
typedef Parameters::const_iterator ConstIterator;
ConstIterator Find(const std::string& key) const;
@ -40,13 +57,12 @@ private:
Parameters parameters_;
};
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
class Url {
public:
typedef std::map<std::string, std::string> Query;
Url(const std::string& str);
Url() = default;
Url(const std::string& str, bool decode);
bool IsValid() const;
@ -73,6 +89,8 @@ public:
static void SplitQuery(const std::string& str, UrlQuery* query);
private:
void Init(const std::string& str);
std::string path_;
std::string query_;
};

@ -1,4 +1,5 @@
#include "webcc/utility.h"
#include <iostream>
using tcp = boost::asio::ip::tcp;

@ -1,10 +1,8 @@
#include "webcc/rest_server.h"
#include "webcc/rest_service_manager.h"
#include "gtest/gtest.h"
using namespace webcc;
////////////////////////////////////////////////////////////////////////////////
class TestRestService : public RestService {
public:
bool Handle(const std::string& http_method,
@ -16,8 +14,6 @@ public:
}
};
////////////////////////////////////////////////////////////////////////////////
TEST(RestServiceManager, URL_RegexBasic) {
RestServiceManager service_manager;

Loading…
Cancel
Save