Initial client API V2

master
Chunting Gu 6 years ago
parent b38b7e6d38
commit 9b8304f06c

@ -8,6 +8,7 @@ endif()
project(webcc) project(webcc)
option(WEBCC_ENABLE_REST "Enable REST support?" ON)
option(WEBCC_ENABLE_SOAP "Enable SOAP support (need pugixml)?" ON) option(WEBCC_ENABLE_SOAP "Enable SOAP support (need pugixml)?" ON)
option(WEBCC_ENABLE_SSL "Enable SSL/HTTPS support (need OpenSSL)?" OFF) option(WEBCC_ENABLE_SSL "Enable SSL/HTTPS support (need OpenSSL)?" OFF)
option(WEBCC_ENABLE_UNITTEST "Build unit test?" ON) option(WEBCC_ENABLE_UNITTEST "Build unit test?" ON)
@ -121,17 +122,18 @@ add_subdirectory(webcc)
if(WEBCC_ENABLE_EXAMPLES) if(WEBCC_ENABLE_EXAMPLES)
add_subdirectory(example/http_client) add_subdirectory(example/http_client)
add_subdirectory(example/http_async_client) # add_subdirectory(example/http_async_client)
if(WEBCC_ENABLE_REST)
# For including jsoncpp as "json/json.h". # For including jsoncpp as "json/json.h".
include_directories(${THIRD_PARTY_DIR}/src/jsoncpp) include_directories(${THIRD_PARTY_DIR}/src/jsoncpp)
# REST examples need jsoncpp to parse and create JSON. # REST examples need jsoncpp to parse and create JSON.
add_subdirectory(${THIRD_PARTY_DIR}/src/jsoncpp) add_subdirectory(${THIRD_PARTY_DIR}/src/jsoncpp)
add_subdirectory(example/rest_book_server) add_subdirectory(example/rest_book_server)
add_subdirectory(example/rest_book_client) # add_subdirectory(example/rest_book_client)
add_subdirectory(example/rest_book_async_client) # add_subdirectory(example/rest_book_async_client)
endif()
if(WEBCC_ENABLE_SOAP) if(WEBCC_ENABLE_SOAP)
add_subdirectory(example/soap_calc_server) add_subdirectory(example/soap_calc_server)
@ -143,8 +145,11 @@ if(WEBCC_ENABLE_EXAMPLES)
if(WEBCC_ENABLE_SSL) if(WEBCC_ENABLE_SSL)
add_subdirectory(example/http_ssl_client) add_subdirectory(example/http_ssl_client)
add_subdirectory(example/http_ssl_async_client) # add_subdirectory(example/http_ssl_async_client)
add_subdirectory(example/github_rest_client)
if(WEBCC_ENABLE_REST)
# add_subdirectory(example/github_rest_client)
endif()
endif() endif()
endif() endif()

@ -1,7 +1,7 @@
--- ---
Language: Cpp Language: Cpp
# BasedOnStyle: Google # BasedOnStyle: Google
AccessModifierOffset: -1 AccessModifierOffset: -2
AlignAfterOpenBracket: Align AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false AlignConsecutiveDeclarations: false

@ -1,29 +1,49 @@
#include <iostream> #include <iostream>
#include "webcc/http_client.h" #include "webcc/http_client_session.h" // TEST
#include "webcc/logger.h" #include "webcc/logger.h"
static void PrintError(const webcc::HttpClient& client) {
std::cout << webcc::DescribeError(client.error());
if (client.timed_out()) {
std::cout << " (timed out)";
}
std::cout << std::endl;
}
int main() { int main() {
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
auto request = webcc::HttpRequest::New(webcc::kHttpGet, "/get", using namespace webcc;
"httpbin.org");
webcc::HttpClient client; HttpResponsePtr r;
if (client.Request(*request)) { HttpClientSession session;
std::cout << client.response_content() << std::endl;
} else { #if 0
PrintError(client); r = session.Request(HttpRequestArgs("GET")
} .url("http://httpbin.org/get") // Moved
.parameters({ "key1", "value1", "key2", "value2" }) // Moved
.headers({ "Accept", "application/json" }) // Moved
.buffer_size(1000));
std::cout << r->content() << std::endl;
// If you want to create the args object firstly, there'll be an extra call
// to its move constructor.
// - constructor: HttpRequestArgs("GET")
// - move constructor: auto args = ...
auto args = HttpRequestArgs("GET")
.url("http://httpbin.org/get")
.parameters({ "key1", "value1", "key2", "value2" })
.headers({ "Accept", "application/json" })
.buffer_size(1000);
r = session.Request(std::move(args));
r = session.Get("http://httpbin.org/get",
{ "key1", "value1", "key2", "value2" },
{ "Accept", "application/json" },
HttpRequestArgs().buffer_size(1000));
#endif
r = session.Post("http://httpbin.org/post", "{ 'key': 'value' }", true,
{ "Accept", "application/json" },
HttpRequestArgs().buffer_size(1000));
std::cout << r->content() << std::endl;
return 0; return 0;
} }

@ -4,25 +4,21 @@
#include "webcc/logger.h" #include "webcc/logger.h"
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
std::string host;
std::string url; std::string url;
if (argc != 3) { if (argc != 3) {
host = "www.boost.org"; url = "www.boost.org/LICENSE_1_0.txt";
url = "/LICENSE_1_0.txt";
} else { } else {
host = argv[1]; url = argv[1];
url = argv[2];
} }
std::cout << "Host: " << host << std::endl;
std::cout << "URL: " << url << std::endl; std::cout << "URL: " << url << std::endl;
std::cout << std::endl; std::cout << std::endl;
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
// Leave port to default value. // Leave port to default value.
webcc::HttpRequest request(webcc::kHttpGet, url, host); webcc::HttpRequest request(webcc::http::kGet, url);
request.Prepare(); request.Prepare();
@ -30,7 +26,7 @@ int main(int argc, char* argv[]) {
// See HttpSslClient::Request() for more details. // See HttpSslClient::Request() for more details.
bool ssl_verify = false; bool ssl_verify = false;
webcc::HttpSslClient client(2000, ssl_verify); webcc::HttpSslClient client(ssl_verify, 2000);
if (client.Request(request)) { if (client.Request(request)) {
//std::cout << client.response()->content() << std::endl; //std::cout << client.response()->content() << std::endl;

@ -1,7 +1,13 @@
# Unit test # Unit test
set(UT_SRCS set(UT_SRCS
url_test.cc
)
if(WEBCC_ENABLE_REST)
set(UT_SRCS ${UT_SRCS}
rest_service_manager_test.cc rest_service_manager_test.cc
) )
endif()
set(UT_TARGET_NAME webcc_unittest) set(UT_TARGET_NAME webcc_unittest)

@ -1,6 +1,9 @@
#include "webcc/rest_service_manager.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "webcc/rest_service_manager.h"
// -----------------------------------------------------------------------------
class TestRestService : public webcc::RestService { class TestRestService : public webcc::RestService {
public: public:
void Handle(const webcc::RestRequest& request, void Handle(const webcc::RestRequest& request,
@ -9,6 +12,8 @@ class TestRestService : public webcc::RestService {
} }
}; };
// -----------------------------------------------------------------------------
TEST(RestServiceManager, URL_RegexBasic) { TEST(RestServiceManager, URL_RegexBasic) {
webcc::RestServiceManager service_manager; webcc::RestServiceManager service_manager;

@ -15,27 +15,23 @@ include(GNUInstallDirs)
set(HEADERS set(HEADERS
globals.h globals.h
http_async_client_base.h # http_async_client_base.h
http_async_client.h # http_async_client.h
http_client_base.h http_client_base.h
http_client.h http_client.h
http_session.h http_client_session.h
http_connection.h
http_message.h http_message.h
http_parser.h http_parser.h
http_request.h http_request.h
http_request_args.h
http_request_handler.h http_request_handler.h
http_request_parser.h http_request_parser.h
http_response.h http_response.h
http_response_parser.h http_response_parser.h
http_server.h http_server.h
http_ssl_async_client.h # http_ssl_async_client.h
queue.h queue.h
rest_async_client.h
rest_client.h
rest_request_handler.h
rest_server.h
rest_service.h
rest_service_manager.h
url.h url.h
utility.h utility.h
version.h version.h
@ -43,11 +39,12 @@ set(HEADERS
set(SOURCES set(SOURCES
globals.cc globals.cc
http_async_client_base.cc # http_async_client_base.cc
http_async_client.cc # http_async_client.cc
http_client_base.cc http_client_base.cc
http_client.cc http_client.cc
http_session.cc http_client_session.cc
http_connection.cc
http_message.cc http_message.cc
http_parser.cc http_parser.cc
http_request.cc http_request.cc
@ -56,34 +53,41 @@ set(SOURCES
http_response.cc http_response.cc
http_response_parser.cc http_response_parser.cc
http_server.cc http_server.cc
http_ssl_async_client.cc # http_ssl_async_client.cc
logger.cc logger.cc
rest_async_client.cc
rest_client.cc
rest_request_handler.cc
rest_service_manager.cc
rest_service.cc
url.cc url.cc
utility.cc utility.cc
) )
if(WEBCC_ENABLE_SSL) if(WEBCC_ENABLE_REST)
set(HEADERS ${HEADERS} set(REST_HEADERS
http_ssl_client.h # rest_async_client.h
# rest_ssl_async_client.h # rest_client.h
rest_ssl_client.h rest_request_handler.h
rest_server.h
rest_service.h
rest_service_manager.h
) )
set(REST_SOURCES
set(SOURCES ${SOURCES} # rest_async_client.cc
http_ssl_client.cc # rest_client.cc
# rest_ssl_async_client.cc rest_request_handler.cc
rest_ssl_client.cc rest_service_manager.cc
rest_service.cc
) )
set(HEADERS ${HEADERS} ${REST_HEADERS})
set(SOURCES ${SOURCES} ${REST_SOURCES})
endif()
if(WEBCC_ENABLE_SSL)
set(HEADERS ${HEADERS} http_ssl_client.h)
set(SOURCES ${SOURCES} http_ssl_client.cc)
endif() endif()
if(WEBCC_ENABLE_SOAP) if(WEBCC_ENABLE_SOAP)
set(SOAP_HEADERS set(SOAP_HEADERS
soap_async_client.h # soap_async_client.h
soap_client.h soap_client.h
soap_globals.h soap_globals.h
soap_message.h soap_message.h
@ -97,7 +101,7 @@ if(WEBCC_ENABLE_SOAP)
) )
set(SOAP_SOURCES set(SOAP_SOURCES
soap_async_client.cc # soap_async_client.cc
soap_client.cc soap_client.cc
soap_globals.cc soap_globals.cc
soap_message.cc soap_message.cc

@ -1,15 +1,8 @@
#include "webcc/globals.h" #include "webcc/globals.h"
namespace webcc { #include "boost/algorithm/string.hpp"
// -----------------------------------------------------------------------------
const std::string kHttpHead = "HEAD"; namespace webcc {
const std::string kHttpGet = "GET";
const std::string kHttpPost = "POST";
const std::string kHttpPatch = "PATCH";
const std::string kHttpPut = "PUT";
const std::string kHttpDelete = "DELETE";
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------

@ -1,7 +1,9 @@
#ifndef WEBCC_GLOBALS_H_ #ifndef WEBCC_GLOBALS_H_
#define WEBCC_GLOBALS_H_ #define WEBCC_GLOBALS_H_
#include <cassert>
#include <string> #include <string>
#include <vector>
#include "webcc/version.h" #include "webcc/version.h"
@ -25,6 +27,9 @@
TypeName(const TypeName&) = delete; \ TypeName(const TypeName&) = delete; \
TypeName& operator=(const TypeName&) = delete; TypeName& operator=(const TypeName&) = delete;
// Default user agent.
#define USER_AGENT "Webcc/" WEBCC_VERSION
namespace webcc { namespace webcc {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -45,9 +50,47 @@ const int kMaxReadSeconds = 30;
// when dumped/logged. // when dumped/logged.
const std::size_t kMaxDumpSize = 2048; const std::size_t kMaxDumpSize = 2048;
// Default ports.
const char* const kPort80 = "80";
const char* const kPort443 = "443";
// Client side error codes.
enum Error {
kNoError = 0, // i.e., OK
kHostResolveError,
kEndpointConnectError,
kHandshakeError, // HTTPS handshake
kSocketReadError,
kSocketWriteError,
// HTTP error.
// E.g., failed to parse HTTP response (invalid content length, etc.).
kHttpError,
// Server error.
// E.g., HTTP status 500 + SOAP Fault element.
kServerError,
// XML parsing error.
kXmlError,
};
// Return a descriptive message for the given error code.
const char* DescribeError(Error error);
// HTTP headers. // HTTP headers.
namespace http { namespace http {
// HTTP methods (verbs) in string.
// Don't use enum to avoid converting back and forth.
const char* const kHead = "HEAD";
const char* const kGet = "GET";
const char* const kPost = "POST";
const char* const kPatch = "PATCH";
const char* const kPut = "PUT";
const char* const kDelete = "DELETE";
// HTTP response status. // HTTP response status.
// This is not a full list. // This is not a full list.
// Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes // Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
@ -94,6 +137,7 @@ const char* const kTextXml = "text/xml";
} // namespace media_types } // namespace media_types
// TODO: Rename to encodings?
namespace charsets { namespace charsets {
const char* const kUtf8 = "utf-8"; const char* const kUtf8 = "utf-8";
@ -102,45 +146,6 @@ const char* const kUtf8 = "utf-8";
} // namespace http } // namespace http
// Default ports.
const char* const kHttpPort = "80";
const char* const kHttpSslPort = "443";
// HTTP methods (verbs) in string ("HEAD", "GET", etc.).
// NOTE: Don't use enum to avoid converting back and forth.
// TODO: Add enum.
extern const std::string kHttpHead;
extern const std::string kHttpGet;
extern const std::string kHttpPost;
extern const std::string kHttpPatch;
extern const std::string kHttpPut;
extern const std::string kHttpDelete;
// Client side error codes.
enum Error {
kNoError = 0, // i.e., OK
kHostResolveError,
kEndpointConnectError,
kHandshakeError, // HTTPS handshake
kSocketReadError,
kSocketWriteError,
// HTTP error.
// E.g., failed to parse HTTP response (invalid content length, etc.).
kHttpError,
// Server error.
// E.g., HTTP status 500 + SOAP Fault element.
kServerError,
// XML parsing error.
kXmlError,
};
// Return a descriptive message for the given error code.
const char* DescribeError(Error error);
} // namespace webcc } // namespace webcc
#endif // WEBCC_GLOBALS_H_ #endif // WEBCC_GLOBALS_H_

@ -14,7 +14,7 @@ class HttpClient : public HttpClientBase {
private: private:
Error Connect(const HttpRequest& request) final { Error Connect(const HttpRequest& request) final {
return DoConnect(request, kHttpPort); return DoConnect(request, kPort80);
} }
void SocketConnect(const Endpoints& endpoints, void SocketConnect(const Endpoints& endpoints,

@ -0,0 +1,80 @@
#include "webcc/http_client_session.h"
#include "webcc/http_client.h"
#include "webcc/http_request.h"
#include "webcc/url.h"
namespace webcc {
HttpClientSession::HttpClientSession() {
InitHeaders();
}
HttpResponsePtr HttpClientSession::Request(HttpRequestArgs&& args) {
assert(args.parameters_.size() % 2 == 0);
assert(args.headers_.size() % 2 == 0);
HttpRequest request(args.method_, args.url_, args.parameters_);
if (!args.data_.empty()) {
request.SetContent(std::move(args.data_), true);
// TODO: charset/encoding
if (args.json_) {
request.SetContentType(http::media_types::kApplicationJson, "");
}
}
// Apply the session-level headers.
for (const HttpHeader& h : headers_.data()) {
request.SetHeader(h.first, h.second);
}
// Apply the request-level headers.
// This will overwrite the session-level headers.
for (std::size_t i = 1; i < args.headers_.size(); i += 2) {
request.SetHeader(std::move(args.headers_[i - 1]),
std::move(args.headers_[i]));
}
request.Prepare();
HttpClient client;
if (!client.Request(request, args.buffer_size_)) {
return HttpResponsePtr{};
}
return client.response();
}
HttpResponsePtr HttpClientSession::Get(const std::string& url,
std::vector<std::string>&& parameters,
std::vector<std::string>&& headers,
HttpRequestArgs&& args) {
return Request(args.method(http::kGet).url(url)
.parameters(std::move(parameters))
.headers(std::move(headers)));
}
HttpResponsePtr HttpClientSession::Post(const std::string& url,
std::string&& data, bool json,
std::vector<std::string>&& headers,
HttpRequestArgs&& args) {
return Request(args.method(http::kPost).url(url).data(std::move(data))
.json(json).headers(std::move(headers)));
}
void HttpClientSession::InitHeaders() {
// NOTE: C++11 requires a space between literal and string macro.
headers_.Add(http::headers::kUserAgent, USER_AGENT);
// TODO: Support gzip, deflate
headers_.Add(http::headers::kAcceptEncoding, "identity");
headers_.Add(http::headers::kAccept, "*/*");
// TODO: Support Keep-Alive connection.
//headers_.Add(http::headers::kConnection, "close");
}
} // namespace webcc

@ -0,0 +1,43 @@
#ifndef WEBCC_HTTP_CLIENT_SESSION_H_
#define WEBCC_HTTP_CLIENT_SESSION_H_
#include <string>
#include <vector>
#include "webcc/http_request_args.h"
#include "webcc/http_response.h"
namespace webcc {
class HttpClientSession {
public:
HttpClientSession();
~HttpClientSession() = default;
void AddHeader(const std::string& key, const std::string& value) {
headers_.Add(key, value);
}
HttpResponsePtr Request(HttpRequestArgs&& args);
HttpResponsePtr Get(const std::string& url,
std::vector<std::string>&& parameters = {},
std::vector<std::string>&& headers = {},
HttpRequestArgs&& args = HttpRequestArgs());
HttpResponsePtr Post(const std::string& url,
std::string&& data, bool json,
std::vector<std::string>&& headers = {},
HttpRequestArgs&& args = HttpRequestArgs());
private:
void InitHeaders();
// Headers to be sent on each request sent from this session.
HttpHeaderDict headers_;
};
} // namespace webcc
#endif // WEBCC_HTTP_CLIENT_SESSION_H_

@ -1,4 +1,4 @@
#include "webcc/http_session.h" #include "webcc/http_connection.h"
#include <utility> // for move() #include <utility> // for move()
@ -11,18 +11,18 @@ using boost::asio::ip::tcp;
namespace webcc { namespace webcc {
HttpSession::HttpSession(tcp::socket socket, HttpRequestHandler* handler) HttpConnection::HttpConnection(tcp::socket socket, HttpRequestHandler* handler)
: socket_(std::move(socket)), : socket_(std::move(socket)),
buffer_(kBufferSize), buffer_(kBufferSize),
request_handler_(handler), request_handler_(handler),
request_parser_(&request_) { request_parser_(&request_) {
} }
void HttpSession::Start() { void HttpConnection::Start() {
DoRead(); DoRead();
} }
void HttpSession::Close() { void HttpConnection::Close() {
LOG_INFO("Close socket..."); LOG_INFO("Close socket...");
boost::system::error_code ec; boost::system::error_code ec;
@ -32,27 +32,27 @@ void HttpSession::Close() {
} }
} }
void HttpSession::SetResponseContent(std::string&& content, void HttpConnection::SetResponseContent(std::string&& content,
const std::string& media_type, const std::string& media_type,
const std::string& charset) { const std::string& charset) {
response_.SetContent(std::move(content), true); response_.SetContent(std::move(content), true);
response_.SetContentType(media_type, charset); response_.SetContentType(media_type, charset);
} }
void HttpSession::SendResponse(http::Status status) { void HttpConnection::SendResponse(http::Status status) {
response_.set_status(status); response_.set_status(status);
response_.Prepare(); response_.Prepare();
DoWrite(); DoWrite();
} }
void HttpSession::DoRead() { void HttpConnection::DoRead() {
socket_.async_read_some(boost::asio::buffer(buffer_), socket_.async_read_some(boost::asio::buffer(buffer_),
std::bind(&HttpSession::OnRead, shared_from_this(), std::bind(&HttpConnection::OnRead, shared_from_this(),
std::placeholders::_1, std::placeholders::_1,
std::placeholders::_2)); std::placeholders::_2));
} }
void HttpSession::OnRead(boost::system::error_code ec, std::size_t length) { void HttpConnection::OnRead(boost::system::error_code ec, std::size_t length) {
if (ec) { if (ec) {
LOG_ERRO("Socket read error (%s).", ec.message().c_str()); LOG_ERRO("Socket read error (%s).", ec.message().c_str());
if (ec != boost::asio::error::operation_aborted) { if (ec != boost::asio::error::operation_aborted) {
@ -77,16 +77,16 @@ void HttpSession::OnRead(boost::system::error_code ec, std::size_t length) {
LOG_VERB("HTTP request:\n%s", request_.Dump(4, "> ").c_str()); LOG_VERB("HTTP request:\n%s", request_.Dump(4, "> ").c_str());
// Enqueue this session. // Enqueue this connection.
// Some worker thread will handle it later. // Some worker thread will handle it later.
request_handler_->Enqueue(shared_from_this()); request_handler_->Enqueue(shared_from_this());
} }
void HttpSession::DoWrite() { void HttpConnection::DoWrite() {
LOG_VERB("HTTP response:\n%s", response_.Dump(4, "> ").c_str()); LOG_VERB("HTTP response:\n%s", response_.Dump(4, "> ").c_str());
boost::asio::async_write(socket_, response_.ToBuffers(), boost::asio::async_write(socket_, response_.ToBuffers(),
std::bind(&HttpSession::OnWrite, shared_from_this(), std::bind(&HttpConnection::OnWrite, shared_from_this(),
std::placeholders::_1, std::placeholders::_1,
std::placeholders::_2)); std::placeholders::_2));
} }
@ -95,7 +95,7 @@ void HttpSession::DoWrite() {
// 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 AsyncWrite() is invoked by worker threads. // io_context.run), even though AsyncWrite() is invoked by worker threads.
// This is ensured by Asio. // This is ensured by Asio.
void HttpSession::OnWrite(boost::system::error_code ec, std::size_t length) { void HttpConnection::OnWrite(boost::system::error_code ec, std::size_t length) {
if (ec) { if (ec) {
LOG_ERRO("Socket write error (%s).", ec.message().c_str()); LOG_ERRO("Socket write error (%s).", ec.message().c_str());
@ -112,7 +112,7 @@ void HttpSession::OnWrite(boost::system::error_code ec, std::size_t length) {
// Socket close VS. shutdown: // Socket close VS. shutdown:
// https://stackoverflow.com/questions/4160347/close-vs-shutdown-socket // https://stackoverflow.com/questions/4160347/close-vs-shutdown-socket
void HttpSession::Shutdown() { void HttpConnection::Shutdown() {
LOG_INFO("Shutdown socket..."); LOG_INFO("Shutdown socket...");
// Initiate graceful connection closure. // Initiate graceful connection closure.

@ -1,5 +1,5 @@
#ifndef WEBCC_HTTP_SESSION_H_ #ifndef WEBCC_HTTP_CONNECTION_H_
#define WEBCC_HTTP_SESSION_H_ #define WEBCC_HTTP_CONNECTION_H_
#include <memory> #include <memory>
#include <string> #include <string>
@ -14,18 +14,23 @@
namespace webcc { namespace webcc {
class HttpConnection;
class HttpRequestHandler; class HttpRequestHandler;
class HttpSession : public std::enable_shared_from_this<HttpSession> { typedef std::shared_ptr<HttpConnection> HttpConnectionPtr;
class HttpConnection : public std::enable_shared_from_this<HttpConnection> {
public: public:
HttpSession(boost::asio::ip::tcp::socket socket, HttpConnection(boost::asio::ip::tcp::socket socket,
HttpRequestHandler* handler); HttpRequestHandler* handler);
~HttpSession() = default; ~HttpConnection() = default;
WEBCC_DELETE_COPY_ASSIGN(HttpSession); WEBCC_DELETE_COPY_ASSIGN(HttpConnection);
const HttpRequest& request() const { return request_; } const HttpRequest& request() const {
return request_;
}
// Start to read and process the client request. // Start to read and process the client request.
void Start(); void Start();
@ -69,8 +74,6 @@ class HttpSession : public std::enable_shared_from_this<HttpSession> {
HttpResponse response_; HttpResponse response_;
}; };
typedef std::shared_ptr<HttpSession> HttpSessionPtr;
} // namespace webcc } // namespace webcc
#endif // WEBCC_HTTP_SESSION_H_ #endif // WEBCC_HTTP_CONNECTION_H_

@ -17,39 +17,54 @@ const char CRLF[] = { '\r', '\n' };
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void HttpMessage::SetHeader(const std::string& name, const std::string& value) { std::ostream& operator<<(std::ostream& os, const HttpMessage& message) {
message.Dump(os);
return os;
}
// -----------------------------------------------------------------------------
void HttpHeaderDict::Add(const std::string& key, const std::string& value) {
for (HttpHeader& h : headers_) { for (HttpHeader& h : headers_) {
if (boost::iequals(h.name, name)) { if (boost::iequals(h.first, key)) {
h.value = value; h.second = value;
return; return;
} }
} }
headers_.push_back({ name, value }); headers_.push_back({ key, value });
} }
void HttpMessage::SetHeader(std::string&& name, std::string&& value) { void HttpHeaderDict::Add(std::string&& key, std::string&& value) {
for (HttpHeader& h : headers_) { for (HttpHeader& h : headers_) {
if (boost::iequals(h.name, name)) { if (boost::iequals(h.first, key)) {
h.value = std::move(value); h.second = std::move(value);
return; return;
} }
} }
headers_.push_back({ std::move(name), std::move(value) }); headers_.push_back({ std::move(key), std::move(value) });
}
bool HttpHeaderDict::Has(const std::string& key) const {
for (const HttpHeader& h : headers_) {
if (boost::iequals(h.first, key)) {
return true;
}
}
return false;
} }
// NOTE: // -----------------------------------------------------------------------------
// According to HTTP 1.1 RFC7231, the following examples are all equivalent,
// but the first is preferred for consistency:
// text/html;charset=utf-8
// text/html;charset=UTF-8
// Text/HTML;Charset="utf-8"
// text/html; charset="utf-8"
// See: https://tools.ietf.org/html/rfc7231#section-3.1.1.1 // See: https://tools.ietf.org/html/rfc7231#section-3.1.1.1
void HttpMessage::SetContentType(const std::string& media_type, void HttpMessage::SetContentType(const std::string& media_type,
const std::string& charset) { const std::string& charset) {
if (charset.empty()) {
SetHeader(http::headers::kContentType, media_type);
} else {
SetHeader(http::headers::kContentType, SetHeader(http::headers::kContentType,
media_type + ";charset=" + charset); media_type + ";charset=" + charset);
} }
}
void HttpMessage::SetContent(std::string&& content, bool set_length) { void HttpMessage::SetContent(std::string&& content, bool set_length) {
content_ = std::move(content); content_ = std::move(content);
@ -72,10 +87,10 @@ std::vector<boost::asio::const_buffer> HttpMessage::ToBuffers() const {
buffers.push_back(boost::asio::buffer(start_line_)); buffers.push_back(boost::asio::buffer(start_line_));
for (const HttpHeader& h : headers_) { for (const HttpHeader& h : headers_.data()) {
buffers.push_back(boost::asio::buffer(h.name)); buffers.push_back(boost::asio::buffer(h.first));
buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR)); buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR));
buffers.push_back(boost::asio::buffer(h.value)); buffers.push_back(boost::asio::buffer(h.second));
buffers.push_back(boost::asio::buffer(misc_strings::CRLF)); buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
} }
@ -98,8 +113,8 @@ void HttpMessage::Dump(std::ostream& os, std::size_t indent,
os << indent_str << start_line_; os << indent_str << start_line_;
for (const HttpHeader& h : headers_) { for (const HttpHeader& h : headers_.data()) {
os << indent_str << h.name << ": " << h.value << std::endl; os << indent_str << h.first << ": " << h.second << std::endl;
} }
os << indent_str << std::endl; os << indent_str << std::endl;
@ -144,9 +159,4 @@ std::string HttpMessage::Dump(std::size_t indent,
return ss.str(); return ss.str();
} }
std::ostream& operator<<(std::ostream& os, const HttpMessage& message) {
message.Dump(os);
return os;
}
} // namespace webcc } // namespace webcc

@ -12,35 +12,76 @@
namespace webcc { namespace webcc {
struct HttpHeader { // -----------------------------------------------------------------------------
std::string name;
std::string value; typedef std::pair<std::string, std::string> HttpHeader;
class HttpMessage;
std::ostream& operator<<(std::ostream& os, const HttpMessage& message);
// -----------------------------------------------------------------------------
class HttpHeaderDict {
public:
std::size_t size() const {
return headers_.size();
}
void Add(const std::string& key, const std::string& value);
void Add(std::string&& key, std::string&& value);
bool Has(const std::string& key) const;
const HttpHeader& Get(std::size_t i) const {
assert(i < size());
return headers_[i];
}
const std::vector<HttpHeader>& data() const {
return headers_;
}
private:
std::vector<HttpHeader> headers_;
}; };
// -----------------------------------------------------------------------------
// Base class for HTTP request and response messages. // Base class for HTTP request and response messages.
class HttpMessage { class HttpMessage {
public: public:
HttpMessage() : content_length_(kInvalidLength) {} HttpMessage() : content_length_(kInvalidLength) {
}
virtual ~HttpMessage() = default; virtual ~HttpMessage() = default;
const std::string& start_line() const { return start_line_; } const std::string& start_line() const {
return start_line_;
}
void set_start_line(const std::string& start_line) { void set_start_line(const std::string& start_line) {
start_line_ = start_line; start_line_ = start_line;
} }
std::size_t content_length() const { return content_length_; } std::size_t content_length() const {
return content_length_;
}
const std::string& content() const { return content_; } const std::string& content() const {
return content_;
}
// TODO: Rename to AddHeader. void SetHeader(const std::string& key, const std::string& value) {
void SetHeader(const std::string& name, const std::string& value); headers_.Add(key, value);
}
// TODO: Remove void SetHeader(std::string&& key, std::string&& value) {
void SetHeader(std::string&& name, std::string&& value); headers_.Add(std::move(key), std::move(value));
}
// E.g., "application/json; charset=utf-8" // E.g., "text/html", "application/json; charset=utf-8", etc.
void SetContentType(const std::string& media_type, void SetContentType(const std::string& media_type,
const std::string& charset); const std::string& charset);
@ -51,7 +92,7 @@ class HttpMessage {
// Make the message (e.g., update start line). // Make the message (e.g., update start line).
// Must be called before ToBuffers()! // Must be called before ToBuffers()!
virtual void Prepare() = 0; virtual bool Prepare() = 0;
// Convert the message into a vector of buffers. The buffers do not own the // Convert the message into a vector of buffers. The buffers do not own the
// underlying memory blocks, therefore the message object must remain valid // underlying memory blocks, therefore the message object must remain valid
@ -77,13 +118,11 @@ class HttpMessage {
std::size_t content_length_; std::size_t content_length_;
std::vector<HttpHeader> headers_; HttpHeaderDict headers_;
std::string content_; std::string content_;
}; };
std::ostream& operator<<(std::ostream& os, const HttpMessage& message);
} // namespace webcc } // namespace webcc
#endif // WEBCC_HTTP_MESSAGE_H_ #endif // WEBCC_HTTP_MESSAGE_H_

@ -1,46 +1,51 @@
#include "webcc/http_request.h" #include "webcc/http_request.h"
#include "webcc/logger.h"
namespace webcc { namespace webcc {
HttpRequest::HttpRequest(const std::string& method, HttpRequest::HttpRequest(const std::string& method,
const std::string& url, const std::string& url,
const std::string& host, const std::vector<std::string>& parameters)
const std::string& port) : method_(method), url_(url) {
: method_(method), url_(url), host_(host), port_(port) { assert(parameters.size() % 2 == 0);
for (std::size_t i = 1; i < parameters.size(); i += 2) {
url_.AddParameter(parameters[i - 1], parameters[i]);
}
}
bool HttpRequest::Prepare() {
if (url_.host().empty()) {
LOG_ERRO("Invalid request: host is missing.");
return false;
}
std::string target = "/" + url_.path();
if (!url_.query().empty()) {
target += "?";
target += url_.query();
} }
void HttpRequest::Prepare() {
start_line_ = method_; start_line_ = method_;
start_line_ += " "; start_line_ += " ";
start_line_ += url_; start_line_ += target;
start_line_ += " HTTP/1.1"; start_line_ += " HTTP/1.1";
start_line_ += CRLF; start_line_ += CRLF;
if (port_.empty()) { if (url_.port().empty()) {
SetHeader(http::headers::kHost, host_); SetHeader(http::headers::kHost, url_.host());
} else { } else {
SetHeader(http::headers::kHost, host_ + ":" + port_); SetHeader(http::headers::kHost, url_.host() + ":" + url_.port());
} }
// TODO: Support Keep-Alive connection. return true;
//SetHeader(http::headers::kConnection, "close");
// TODO: Support gzip, deflate
SetHeader(http::headers::kAcceptEncoding, "identity");
// NOTE: C++11 requires a space between literal and string macro.
SetHeader(http::headers::kUserAgent, "Webcc/" WEBCC_VERSION);
} }
// static
HttpRequestPtr HttpRequest::New(const std::string& method, HttpRequestPtr HttpRequest::New(const std::string& method,
const std::string& url, const std::string& url,
const std::string& host, const std::vector<std::string>& parameters,
const std::string& port,
bool prepare) { bool prepare) {
HttpRequestPtr request{ HttpRequestPtr request{ new HttpRequest{ method, url, parameters } };
new HttpRequest{ method, url, host, port }
};
if (prepare) { if (prepare) {
request->Prepare(); request->Prepare();

@ -3,8 +3,10 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector>
#include "webcc/http_message.h" #include "webcc/http_message.h"
#include "webcc/url.h"
namespace webcc { namespace webcc {
@ -17,26 +19,31 @@ class HttpRequest : public HttpMessage {
public: public:
HttpRequest() = default; HttpRequest() = default;
// The |host| is a descriptive name (e.g., www.google.com) or a numeric IP // TODO: Move parameters
// address (127.0.0.1).
// The |port| is a numeric number (e.g., 9000). The default value (80 for HTTP
// or 443 for HTTPS) will be used to connect to server if it's empty.
HttpRequest(const std::string& method, HttpRequest(const std::string& method,
const std::string& url, const std::string& url,
const std::string& host, const std::vector<std::string>& parameters = {});
const std::string& port = "");
~HttpRequest() override = default; ~HttpRequest() override = default;
const std::string& method() const { return method_; } const std::string& method() const {
return method_;
}
const std::string& url() const { return url_; } const Url& url() const {
return url_;
}
const std::string& host() const { return host_; } const std::string& host() const {
const std::string& port() const { return port_; } return url_.host();
}
const std::string& port() const {
return url_.port();
}
std::string port(const std::string& default_port) const { std::string port(const std::string& default_port) const {
return port_.empty() ? default_port : port_; return port().empty() ? default_port : port();
} }
// Shortcut to set `Accept` header. // Shortcut to set `Accept` header.
@ -51,30 +58,26 @@ class HttpRequest : public HttpMessage {
// Prepare payload. // Prepare payload.
// Compose start line, set Host header, etc. // Compose start line, set Host header, etc.
void Prepare() override; bool Prepare() override;
// TODO: Re-place
static HttpRequestPtr New(const std::string& method, static HttpRequestPtr New(const std::string& method,
const std::string& url, const std::string& url,
const std::string& host, const std::vector<std::string>& parameters = {},
const std::string& port = "",
bool prepare = true); bool prepare = true);
private: private:
friend class HttpRequestParser; friend class HttpRequestParser;
void set_method(const std::string& method) { method_ = method; } void set_method(const std::string& method) {
void set_url(const std::string& url) { url_ = url; } method_ = method;
}
void set_url(const std::string& url) {
url_.Init(url);
}
// HTTP method.
std::string method_; std::string method_;
Url url_;
// Request URL.
// A complete URL naming the requested resource, or the path component of
// the URL.
std::string url_;
std::string host_;
std::string port_;
}; };
} // namespace webcc } // namespace webcc

@ -0,0 +1,140 @@
#ifndef WEBCC_HTTP_REQUEST_ARGS_H_
#define WEBCC_HTTP_REQUEST_ARGS_H_
#include <string>
#include <utility>
#include <vector>
#include "webcc/globals.h"
#include "webcc/logger.h"
namespace webcc {
class HttpClientSession;
// Args maker for HttpClientSession.
// Use method chaining to simulate Named Parameters.
class HttpRequestArgs {
public:
explicit HttpRequestArgs(const std::string& method = "")
: method_(method), json_(false), buffer_size_(0) {
LOG_VERB("HttpRequestArgs()");
}
HttpRequestArgs(const HttpRequestArgs&) = default;
HttpRequestArgs& operator=(const HttpRequestArgs&) = default;
#if WEBCC_DEFAULT_MOVE_COPY_ASSIGN
HttpRequestArgs(HttpRequestArgs&&) = default;
HttpRequestArgs& operator=(HttpRequestArgs&&) = default;
#else
HttpRequestArgs(HttpRequestArgs&& rhs)
: method_(std::move(rhs.method_)),
url_(std::move(rhs.url_)),
parameters_(std::move(rhs.parameters_)),
data_(std::move(rhs.data_)),
json_(rhs.json_),
headers_(std::move(rhs.headers_)),
buffer_size_(rhs.buffer_size_) {
LOG_VERB("HttpRequestArgs(&&)");
}
HttpRequestArgs& operator=(HttpRequestArgs&& rhs) {
if (&rhs != this) {
method_ = std::move(rhs.method_);
url_ = std::move(rhs.url_);
parameters_ = std::move(rhs.parameters_);
data_ = std::move(rhs.data_);
json_ = rhs.json_;
headers_ = std::move(rhs.headers_);
buffer_size_ = buffer_size_;
}
LOG_VERB("HttpRequestArgs& operator=(&&)");
return *this;
}
#endif // WEBCC_DEFAULT_MOVE_COPY_ASSIGN
HttpRequestArgs&& method(const std::string& method) {
method_ = method;
return std::move(*this);
}
HttpRequestArgs&& url(const std::string& url) {
url_ = url;
return std::move(*this);
}
HttpRequestArgs&& url(std::string&& url) {
url_ = std::move(url);
return std::move(*this);
}
HttpRequestArgs&& parameters(const std::vector<std::string>& parameters) {
parameters_ = parameters;
return std::move(*this);
}
HttpRequestArgs&& parameters(std::vector<std::string>&& parameters) {
parameters_ = std::move(parameters);
return std::move(*this);
}
HttpRequestArgs&& data(const std::string& data) {
data_ = data;
return std::move(*this);
}
HttpRequestArgs&& data(std::string&& data) {
data_ = std::move(data);
return std::move(*this);
}
HttpRequestArgs&& json(bool json = true) {
json_ = json;
return std::move(*this);
}
HttpRequestArgs&& headers(const std::vector<std::string>& headers) {
headers_ = headers;
return std::move(*this);
}
HttpRequestArgs&& headers(std::vector<std::string>&& headers) {
headers_ = std::move(headers);
return std::move(*this);
}
HttpRequestArgs&& buffer_size(std::size_t buffer_size) {
buffer_size_ = buffer_size;
return std::move(*this);
}
private:
friend class HttpClientSession;
std::string method_;
std::string url_;
std::vector<std::string> parameters_;
// Data to send in the body of the request.
std::string data_;
// Is the data to send a JSON string?
bool json_;
std::vector<std::string> headers_;
// Size of the buffer to read response.
// Leave it to 0 for using default value.
std::size_t buffer_size_;
};
} // namespace webcc
#endif // WEBCC_HTTP_REQUEST_ARGS_H_

@ -9,8 +9,8 @@
namespace webcc { namespace webcc {
void HttpRequestHandler::Enqueue(HttpSessionPtr session) { void HttpRequestHandler::Enqueue(HttpConnectionPtr connection) {
queue_.Push(session); queue_.Push(connection);
} }
void HttpRequestHandler::Start(std::size_t count) { void HttpRequestHandler::Start(std::size_t count) {
@ -24,14 +24,14 @@ void HttpRequestHandler::Start(std::size_t count) {
void HttpRequestHandler::Stop() { void HttpRequestHandler::Stop() {
LOG_INFO("Stopping workers..."); LOG_INFO("Stopping workers...");
// Close pending sessions. // Close pending connections.
for (HttpSessionPtr s = queue_.Pop(); s; s = queue_.Pop()) { for (HttpConnectionPtr s = queue_.Pop(); s; s = queue_.Pop()) {
LOG_INFO("Closing pending session..."); LOG_INFO("Closing pending connection...");
s->Close(); s->Close();
} }
// Enqueue a null session to trigger the first worker to stop. // Enqueue a null connection to trigger the first worker to stop.
queue_.Push(HttpSessionPtr()); queue_.Push(HttpConnectionPtr());
for (auto& worker : workers_) { for (auto& worker : workers_) {
if (worker.joinable()) { if (worker.joinable()) {
@ -46,19 +46,19 @@ void HttpRequestHandler::WorkerRoutine() {
LOG_INFO("Worker is running."); LOG_INFO("Worker is running.");
for (;;) { for (;;) {
HttpSessionPtr session = queue_.PopOrWait(); HttpConnectionPtr connection = queue_.PopOrWait();
if (!session) { if (!connection) {
LOG_INFO("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(HttpConnectionPtr());
// Stop the worker. // Stop the worker.
break; break;
} }
HandleSession(session); HandleConnection(connection);
} }
} }

@ -5,7 +5,7 @@
#include <thread> #include <thread>
#include <vector> #include <vector>
#include "webcc/http_session.h" #include "webcc/http_connection.h"
#include "webcc/queue.h" #include "webcc/queue.h"
#include "webcc/soap_service.h" #include "webcc/soap_service.h"
@ -22,22 +22,24 @@ class HttpRequestHandler {
WEBCC_DELETE_COPY_ASSIGN(HttpRequestHandler); WEBCC_DELETE_COPY_ASSIGN(HttpRequestHandler);
// Put the session into the queue. // Put the connection into the queue.
void Enqueue(HttpSessionPtr session); void Enqueue(HttpConnectionPtr connection);
// Start worker threads. // Start worker threads.
void Start(std::size_t count); void Start(std::size_t count);
// Close pending sessions and stop worker threads. // Close pending connections and stop worker threads.
void Stop(); void Stop();
private: private:
void WorkerRoutine(); void WorkerRoutine();
// Called by the worker routine. // Called by the worker routine.
virtual void HandleSession(HttpSessionPtr session) = 0; virtual void HandleConnection(HttpConnectionPtr connection) = 0;
private:
Queue<HttpConnectionPtr> queue_;
Queue<HttpSessionPtr> queue_;
std::vector<std::thread> workers_; std::vector<std::thread> workers_;
}; };

@ -57,13 +57,13 @@ const std::string& ToString(int status) {
} // namespace status_strings } // namespace status_strings
void HttpResponse::Prepare() { bool HttpResponse::Prepare() {
start_line_ = status_strings::ToString(status_); start_line_ = status_strings::ToString(status_);
// NOTE: C++11 requires a space between literal and string macro. SetHeader("Server", USER_AGENT);
SetHeader("Server", "Webcc/" WEBCC_VERSION);
SetHeader("Date", GetHttpDateTimestamp()); SetHeader("Date", GetHttpDateTimestamp());
return true;
} }
HttpResponse HttpResponse::Fault(http::Status status) { HttpResponse HttpResponse::Fault(http::Status status) {

@ -19,7 +19,7 @@ class HttpResponse : public HttpMessage {
void set_status(int status) { status_ = status; } void set_status(int status) { status_ = status; }
// Set start line according to status code. // Set start line according to status code.
void Prepare() override; bool Prepare() override;
// Get a fault response when HTTP status is not OK. // Get a fault response when HTTP status is not OK.
// TODO: Avoid copy. // TODO: Avoid copy.

@ -12,15 +12,15 @@ HttpResponseParser::HttpResponseParser(HttpResponse* response)
} }
bool HttpResponseParser::ParseStartLine(const std::string& line) { bool HttpResponseParser::ParseStartLine(const std::string& line) {
std::vector<std::string> splitted; std::vector<std::string> parts;
boost::split(splitted, line, boost::is_any_of(" "), boost::token_compress_on); boost::split(parts, line, boost::is_any_of(" "), boost::token_compress_on);
if (splitted.size() < 3) { if (parts.size() < 3) {
LOG_ERRO("Invalid HTTP response status line: %s", line.c_str()); LOG_ERRO("Invalid HTTP response status line: %s", line.c_str());
return false; return false;
} }
std::string& status_str = splitted[1]; std::string& status_str = parts[1];
try { try {
response_->set_status(std::stoi(status_str)); response_->set_status(std::stoi(status_str));

@ -96,7 +96,7 @@ void HttpServer::DoAccept() {
if (!ec) { if (!ec) {
LOG_INFO("Accepted a connection."); LOG_INFO("Accepted a connection.");
std::make_shared<HttpSession>(std::move(socket), std::make_shared<HttpConnection>(std::move(socket),
GetRequestHandler())->Start(); GetRequestHandler())->Start();
} }

@ -8,7 +8,7 @@
#include "boost/asio/signal_set.hpp" #include "boost/asio/signal_set.hpp"
#include "webcc/globals.h" #include "webcc/globals.h"
#include "webcc/http_session.h" #include "webcc/http_connection.h"
namespace webcc { namespace webcc {

@ -20,7 +20,7 @@ HttpSslClient::HttpSslClient(bool ssl_verify, std::size_t buffer_size)
} }
Error HttpSslClient::Connect(const HttpRequest& request) { Error HttpSslClient::Connect(const HttpRequest& request) {
Error error = DoConnect(request, kHttpSslPort); Error error = DoConnect(request, kPort443);
if (error != kNoError) { if (error != kNoError) {
return error; return error;

@ -35,22 +35,22 @@ class RestClient {
// instead for the HTTP status code. // instead for the HTTP status code.
inline bool Get(const std::string& url, std::size_t buffer_size = 0) { inline bool Get(const std::string& url, std::size_t buffer_size = 0) {
return Request(kHttpGet, url, "", buffer_size); return Request("GET", url, "", buffer_size);
} }
inline bool Post(const std::string& url, std::string&& content, inline bool Post(const std::string& url, std::string&& content,
std::size_t buffer_size = 0) { std::size_t buffer_size = 0) {
return Request(kHttpPost, url, std::move(content), buffer_size); return Request("POST", url, std::move(content), buffer_size);
} }
inline bool Put(const std::string& url, std::string&& content, inline bool Put(const std::string& url, std::string&& content,
std::size_t buffer_size = 0) { std::size_t buffer_size = 0) {
return Request(kHttpPut, url, std::move(content), buffer_size); return Request("PUT", url, std::move(content), buffer_size);
} }
inline bool Patch(const std::string& url, std::string&& content, inline bool Patch(const std::string& url, std::string&& content,
std::size_t buffer_size = 0) { std::size_t buffer_size = 0) {
return Request(kHttpPatch, url, std::move(content), buffer_size); return Request("PATCH", url, std::move(content), buffer_size);
} }
inline bool Delete(const std::string& url, std::size_t buffer_size = 0) { inline bool Delete(const std::string& url, std::size_t buffer_size = 0) {

@ -13,13 +13,14 @@ bool RestRequestHandler::Bind(RestServicePtr service, const std::string& url,
return service_manager_.AddService(service, url, is_regex); return service_manager_.AddService(service, url, is_regex);
} }
void RestRequestHandler::HandleSession(HttpSessionPtr session) { void RestRequestHandler::HandleConnection(HttpConnectionPtr connection) {
const HttpRequest& http_request = session->request(); const HttpRequest& http_request = connection->request();
Url url(http_request.url(), /*decode*/true); // TODO
const Url& url = http_request.url();
if (!url.IsPathValid()) { if (url.path().empty()) {
session->SendResponse(http::Status::kBadRequest); connection->SendResponse(http::Status::kBadRequest);
return; return;
} }
@ -33,7 +34,7 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) {
if (!service) { if (!service) {
LOG_WARN("No service matches the URL path: %s", url.path().c_str()); LOG_WARN("No service matches the URL path: %s", url.path().c_str());
session->SendResponse(http::Status::kNotFound); connection->SendResponse(http::Status::kNotFound);
return; return;
} }
@ -42,13 +43,13 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) {
service->Handle(rest_request, &rest_response); service->Handle(rest_request, &rest_response);
if (!rest_response.content.empty()) { if (!rest_response.content.empty()) {
session->SetResponseContent(std::move(rest_response.content), connection->SetResponseContent(std::move(rest_response.content),
http::media_types::kApplicationJson, http::media_types::kApplicationJson,
http::charsets::kUtf8); http::charsets::kUtf8);
} }
// Send response back to client. // Send response back to client.
session->SendResponse(rest_response.status); connection->SendResponse(rest_response.status);
} }
} // namespace webcc } // namespace webcc

@ -13,12 +13,13 @@ namespace webcc {
class RestRequestHandler : public HttpRequestHandler { class RestRequestHandler : public HttpRequestHandler {
public: public:
RestRequestHandler() = default; RestRequestHandler() = default;
~RestRequestHandler() override = default; ~RestRequestHandler() override = default;
bool Bind(RestServicePtr service, const std::string& url, bool is_regex); bool Bind(RestServicePtr service, const std::string& url, bool is_regex);
private: private:
void HandleSession(HttpSessionPtr session) override; void HandleConnection(HttpConnectionPtr connection) override;
RestServiceManager service_manager_; RestServiceManager service_manager_;
}; };

@ -8,9 +8,9 @@ namespace webcc {
void RestListService::Handle(const RestRequest& request, void RestListService::Handle(const RestRequest& request,
RestResponse* response) { RestResponse* response) {
if (request.method == kHttpGet) { if (request.method == http::kGet) {
Get(UrlQuery(request.url_query_str), response); Get(UrlQuery(request.url_query_str), response);
} else if (request.method == kHttpPost) { } else if (request.method == http::kPost) {
Post(request.content, response); Post(request.content, response);
} else { } else {
LOG_ERRO("RestListService doesn't support '%s' method.", LOG_ERRO("RestListService doesn't support '%s' method.",
@ -22,13 +22,13 @@ void RestListService::Handle(const RestRequest& request,
void RestDetailService::Handle(const RestRequest& request, void RestDetailService::Handle(const RestRequest& request,
RestResponse* response) { RestResponse* response) {
if (request.method == kHttpGet) { if (request.method == http::kGet) {
Get(request.url_sub_matches, UrlQuery(request.url_query_str), response); Get(request.url_sub_matches, UrlQuery(request.url_query_str), response);
} else if (request.method == kHttpPut) { } else if (request.method == http::kPut) {
Put(request.url_sub_matches, request.content, response); Put(request.url_sub_matches, request.content, response);
} else if (request.method == kHttpPatch) { } else if (request.method == http::kPatch) {
Patch(request.url_sub_matches, request.content, response); Patch(request.url_sub_matches, request.content, response);
} else if (request.method == kHttpDelete) { } else if (request.method == http::kDelete) {
Delete(request.url_sub_matches, response); Delete(request.url_sub_matches, response);
} else { } else {
LOG_ERRO("RestDetailService doesn't support '%s' method.", LOG_ERRO("RestDetailService doesn't support '%s' method.",

@ -63,7 +63,7 @@ protected:
// Default: "utf-8". // Default: "utf-8".
std::string content_charset_; std::string content_charset_;
// Default headers for all session requests. // Default headers for each request sent from this session.
std::map<std::string, std::string> headers_; std::map<std::string, std::string> headers_;
private: private:
@ -96,7 +96,7 @@ public:
inline bool Post(const std::string& url, std::string&& content, inline bool Post(const std::string& url, std::string&& content,
const SSMap& headers = {}, std::size_t buffer_size = 0) { const SSMap& headers = {}, std::size_t buffer_size = 0) {
return Request(kHttpPost, url, std::move(content), headers, buffer_size); return Request(Post, url, std::move(content), headers, buffer_size);
} }
inline bool Put(const std::string& url, std::string&& content, inline bool Put(const std::string& url, std::string&& content,

@ -48,7 +48,14 @@ bool SoapClient::Request(const std::string& operation,
std::string http_content; std::string http_content;
soap_request.ToXml(format_raw_, indent_str_, &http_content); soap_request.ToXml(format_raw_, indent_str_, &http_content);
HttpRequest http_request(kHttpPost, url_, host_, port_); // TODO
std::string url = host_;
url += url_;
if (!port_.empty()) {
url += ":" + port_;
}
HttpRequest http_request(http::kPost, url);
http_request.SetContent(std::move(http_content), true); http_request.SetContent(std::move(http_content), true);

@ -53,13 +53,11 @@ class SoapParameter {
#else #else
// Use "= default" if drop the support of VS 2013.
SoapParameter(SoapParameter&& rhs) SoapParameter(SoapParameter&& rhs)
: key_(std::move(rhs.key_)), value_(std::move(rhs.value_)), : key_(std::move(rhs.key_)), value_(std::move(rhs.value_)),
as_cdata_(rhs.as_cdata_) { as_cdata_(rhs.as_cdata_) {
} }
// Use "= default" if drop the support of VS 2013.
SoapParameter& operator=(SoapParameter&& rhs) { SoapParameter& operator=(SoapParameter&& rhs) {
if (&rhs != this) { if (&rhs != this) {
key_ = std::move(rhs.key_); key_ = std::move(rhs.key_);

@ -15,17 +15,17 @@ bool SoapRequestHandler::Bind(SoapServicePtr service, const std::string& url) {
return true; return true;
} }
void SoapRequestHandler::HandleSession(HttpSessionPtr session) { void SoapRequestHandler::HandleConnection(HttpConnectionPtr connection) {
SoapServicePtr service = GetServiceByUrl(session->request().url()); SoapServicePtr service = GetServiceByUrl(connection->request().url().path());
if (!service) { if (!service) {
session->SendResponse(http::Status::kBadRequest); connection->SendResponse(http::Status::kBadRequest);
return; return;
} }
// Parse the SOAP request XML. // Parse the SOAP request XML.
SoapRequest soap_request; SoapRequest soap_request;
if (!soap_request.FromXml(session->request().content())) { if (!soap_request.FromXml(connection->request().content())) {
session->SendResponse(http::Status::kBadRequest); connection->SendResponse(http::Status::kBadRequest);
return; return;
} }
@ -40,7 +40,7 @@ void SoapRequestHandler::HandleSession(HttpSessionPtr session) {
} }
if (!service->Handle(soap_request, &soap_response)) { if (!service->Handle(soap_request, &soap_response)) {
session->SendResponse(http::Status::kBadRequest); connection->SendResponse(http::Status::kBadRequest);
return; return;
} }
@ -48,16 +48,16 @@ void SoapRequestHandler::HandleSession(HttpSessionPtr session) {
soap_response.ToXml(format_raw_, indent_str_, &content); soap_response.ToXml(format_raw_, indent_str_, &content);
if (soap_version_ == kSoapV11) { if (soap_version_ == kSoapV11) {
session->SetResponseContent(std::move(content), connection->SetResponseContent(std::move(content),
http::media_types::kTextXml, http::media_types::kTextXml,
http::charsets::kUtf8); http::charsets::kUtf8);
} else { } else {
session->SetResponseContent(std::move(content), connection->SetResponseContent(std::move(content),
http::media_types::kApplicationSoapXml, http::media_types::kApplicationSoapXml,
http::charsets::kUtf8); http::charsets::kUtf8);
} }
session->SendResponse(http::Status::kOK); connection->SendResponse(http::Status::kOK);
} }
SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) { SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) {

@ -12,13 +12,14 @@ namespace webcc {
class SoapRequestHandler : public HttpRequestHandler { class SoapRequestHandler : public HttpRequestHandler {
public: public:
explicit SoapRequestHandler(SoapVersion soap_version) explicit SoapRequestHandler(SoapVersion soap_version)
: soap_version_(soap_version), : soap_version_(soap_version), format_raw_(true) {
format_raw_(true) {
} }
~SoapRequestHandler() override = default; ~SoapRequestHandler() override = default;
void set_format_raw(bool format_raw) { format_raw_ = format_raw; } void set_format_raw(bool format_raw) {
format_raw_ = format_raw;
}
void set_indent_str(const std::string& indent_str) { void set_indent_str(const std::string& indent_str) {
indent_str_ = indent_str; indent_str_ = indent_str;
@ -27,7 +28,7 @@ class SoapRequestHandler : public HttpRequestHandler {
bool Bind(SoapServicePtr service, const std::string& url); bool Bind(SoapServicePtr service, const std::string& url);
private: private:
void HandleSession(HttpSessionPtr session) override; void HandleConnection(HttpConnectionPtr connection) override;
SoapServicePtr GetServiceByUrl(const std::string& url); SoapServicePtr GetServiceByUrl(const std::string& url);

@ -4,6 +4,8 @@
#include <functional> #include <functional>
#include <sstream> #include <sstream>
#include "boost/algorithm/string.hpp"
namespace webcc { namespace webcc {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -244,48 +246,86 @@ UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
Url::Url(const std::string& str, bool decode) { Url::Url(const std::string& str, bool decode) {
Init(str, decode);
}
void Url::Init(const std::string& str, bool decode, bool clear) {
if (clear) {
Clear();
}
if (!decode || str.find('%') == std::string::npos) { if (!decode || str.find('%') == std::string::npos) {
Init(str); Parse(str);
return; return;
} }
std::string decoded; std::string decoded;
if (Decode(str, &decoded)) { if (Decode(str, &decoded)) {
Init(decoded); Parse(decoded);
} else { } else {
// TODO(Adam): Exception? // TODO: Exception?
Init(str); Parse(str);
} }
} }
bool Url::IsPathValid() const { void Url::AddParameter(const std::string& key, const std::string& value) {
// URL path must be absolute. if (!query_.empty()) {
if (path_.empty() || path_[0] != '/') { query_ += "&";
return false;
} }
return true; query_ += key + "=" + value;
} }
std::vector<std::string> Url::SplitPath(const std::string& path) { void Url::Parse(const std::string& str) {
std::vector<std::string> results; std::string tmp = boost::trim_left_copy(str);
std::stringstream iss(path);
std::string s; std::size_t pos = std::string::npos;
while (std::getline(iss, s, '/')) {
if (!s.empty()) { pos = tmp.find("://");
results.push_back(s); if (pos != std::string::npos) {
} scheme_ = tmp.substr(0, pos);
tmp = tmp.substr(pos + 3);
} }
return results;
pos = tmp.find('/');
if (pos != std::string::npos) {
host_ = tmp.substr(0, pos);
tmp = tmp.substr(pos + 1);
pos = tmp.find('?');
if (pos != std::string::npos) {
path_ = tmp.substr(0, pos);
query_ = tmp.substr(pos + 1);
} else {
path_ = tmp;
} }
} else {
path_ = "";
void Url::Init(const std::string& str) { pos = tmp.find('?');
std::size_t pos = str.find('?'); if (pos != std::string::npos) {
if (pos == std::string::npos) { host_ = tmp.substr(0, pos);
path_ = str; query_ = tmp.substr(pos + 1);
} else { } else {
path_ = str.substr(0, pos); host_ = tmp;
query_ = str.substr(pos + 1); }
} }
if (!host_.empty()) {
pos = host_.find(':');
if (pos != std::string::npos) {
port_ = host_.substr(pos + 1);
host_ = host_.substr(0, pos);
}
}
}
void Url::Clear() {
scheme_.clear();
host_.clear();
port_.clear();
path_.clear();
query_.clear();
} }
} // namespace webcc } // namespace webcc

@ -2,10 +2,6 @@
#define WEBCC_URL_H_ #define WEBCC_URL_H_
// A simplified implementation of URL (or URI). // A simplified implementation of URL (or URI).
// The URL should start with "/".
// The parameters (separated by ";") are not supported.
// Example:
// /inventory-check.cgi?item=12731&color=blue&size=large
#include <string> #include <string>
#include <utility> #include <utility>
@ -60,32 +56,44 @@ class UrlQuery {
class Url { class Url {
public: public:
Url() = default; Url() = default;
Url(const std::string& str, bool decode);
bool IsPathValid() const; // TODO: decode/encode/encoded?
explicit Url(const std::string& str, bool decode = true);
const std::string& path() const { void Init(const std::string& str, bool decode = true, bool clear = true);
return path_;
const std::string& scheme() const {
return scheme_;
} }
void set_path(const std::string& path) { const std::string& host() const {
path_ = path; return host_;
} }
const std::string& query() const { const std::string& port() const {
return query_; return port_;
}
const std::string& path() const {
return path_;
} }
void set_query(const std::string& query) { const std::string& query() const {
query_ = query; return query_;
} }
// Split a path into its hierarchical components. // Add a parameter to the query string.
static std::vector<std::string> SplitPath(const std::string& path); void AddParameter(const std::string& key, const std::string& value);
private: private:
void Init(const std::string& str); void Parse(const std::string& str);
void Clear();
// TODO: Support auth & fragment.
std::string scheme_;
std::string host_;
std::string port_;
std::string path_; std::string path_;
std::string query_; std::string query_;
}; };

Loading…
Cancel
Save