Initial client API V2

master
Chunting Gu 6 years ago
parent b38b7e6d38
commit 9b8304f06c

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

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

@ -1,29 +1,49 @@
#include <iostream>
#include "webcc/http_client.h"
#include "webcc/http_client_session.h" // TEST
#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() {
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
auto request = webcc::HttpRequest::New(webcc::kHttpGet, "/get",
"httpbin.org");
using namespace webcc;
HttpResponsePtr r;
HttpClientSession session;
#if 0
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
webcc::HttpClient client;
r = session.Post("http://httpbin.org/post", "{ 'key': 'value' }", true,
{ "Accept", "application/json" },
HttpRequestArgs().buffer_size(1000));
if (client.Request(*request)) {
std::cout << client.response_content() << std::endl;
} else {
PrintError(client);
}
std::cout << r->content() << std::endl;
return 0;
}

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

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

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

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

@ -1,15 +1,8 @@
#include "webcc/globals.h"
namespace webcc {
// -----------------------------------------------------------------------------
#include "boost/algorithm/string.hpp"
const std::string kHttpHead = "HEAD";
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";
namespace webcc {
// -----------------------------------------------------------------------------

@ -1,7 +1,9 @@
#ifndef WEBCC_GLOBALS_H_
#define WEBCC_GLOBALS_H_
#include <cassert>
#include <string>
#include <vector>
#include "webcc/version.h"
@ -25,6 +27,9 @@
TypeName(const TypeName&) = delete; \
TypeName& operator=(const TypeName&) = delete;
// Default user agent.
#define USER_AGENT "Webcc/" WEBCC_VERSION
namespace webcc {
// -----------------------------------------------------------------------------
@ -45,9 +50,47 @@ const int kMaxReadSeconds = 30;
// when dumped/logged.
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.
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.
// This is not a full list.
// 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
// TODO: Rename to encodings?
namespace charsets {
const char* const kUtf8 = "utf-8";
@ -102,45 +146,6 @@ const char* const kUtf8 = "utf-8";
} // 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
#endif // WEBCC_GLOBALS_H_

@ -14,7 +14,7 @@ class HttpClient : public HttpClientBase {
private:
Error Connect(const HttpRequest& request) final {
return DoConnect(request, kHttpPort);
return DoConnect(request, kPort80);
}
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()
@ -11,18 +11,18 @@ using boost::asio::ip::tcp;
namespace webcc {
HttpSession::HttpSession(tcp::socket socket, HttpRequestHandler* handler)
HttpConnection::HttpConnection(tcp::socket socket, HttpRequestHandler* handler)
: socket_(std::move(socket)),
buffer_(kBufferSize),
request_handler_(handler),
request_parser_(&request_) {
}
void HttpSession::Start() {
void HttpConnection::Start() {
DoRead();
}
void HttpSession::Close() {
void HttpConnection::Close() {
LOG_INFO("Close socket...");
boost::system::error_code ec;
@ -32,27 +32,27 @@ void HttpSession::Close() {
}
}
void HttpSession::SetResponseContent(std::string&& content,
const std::string& media_type,
const std::string& charset) {
void HttpConnection::SetResponseContent(std::string&& content,
const std::string& media_type,
const std::string& charset) {
response_.SetContent(std::move(content), true);
response_.SetContentType(media_type, charset);
}
void HttpSession::SendResponse(http::Status status) {
void HttpConnection::SendResponse(http::Status status) {
response_.set_status(status);
response_.Prepare();
DoWrite();
}
void HttpSession::DoRead() {
void HttpConnection::DoRead() {
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::_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) {
LOG_ERRO("Socket read error (%s).", ec.message().c_str());
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());
// Enqueue this session.
// Enqueue this connection.
// Some worker thread will handle it later.
request_handler_->Enqueue(shared_from_this());
}
void HttpSession::DoWrite() {
void HttpConnection::DoWrite() {
LOG_VERB("HTTP response:\n%s", response_.Dump(4, "> ").c_str());
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::_2));
}
@ -95,7 +95,7 @@ void HttpSession::DoWrite() {
// This write handler will be called from main thread (the thread calling
// io_context.run), even though AsyncWrite() is invoked by worker threads.
// 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) {
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:
// https://stackoverflow.com/questions/4160347/close-vs-shutdown-socket
void HttpSession::Shutdown() {
void HttpConnection::Shutdown() {
LOG_INFO("Shutdown socket...");
// Initiate graceful connection closure.

@ -1,5 +1,5 @@
#ifndef WEBCC_HTTP_SESSION_H_
#define WEBCC_HTTP_SESSION_H_
#ifndef WEBCC_HTTP_CONNECTION_H_
#define WEBCC_HTTP_CONNECTION_H_
#include <memory>
#include <string>
@ -14,18 +14,23 @@
namespace webcc {
class HttpConnection;
class HttpRequestHandler;
class HttpSession : public std::enable_shared_from_this<HttpSession> {
public:
HttpSession(boost::asio::ip::tcp::socket socket,
HttpRequestHandler* handler);
typedef std::shared_ptr<HttpConnection> HttpConnectionPtr;
~HttpSession() = default;
class HttpConnection : public std::enable_shared_from_this<HttpConnection> {
public:
HttpConnection(boost::asio::ip::tcp::socket socket,
HttpRequestHandler* handler);
WEBCC_DELETE_COPY_ASSIGN(HttpSession);
~HttpConnection() = default;
const HttpRequest& request() const { return request_; }
WEBCC_DELETE_COPY_ASSIGN(HttpConnection);
const HttpRequest& request() const {
return request_;
}
// Start to read and process the client request.
void Start();
@ -40,7 +45,7 @@ class HttpSession : public std::enable_shared_from_this<HttpSession> {
// Send response to client with the given status.
void SendResponse(http::Status status);
private:
private:
void DoRead();
void OnRead(boost::system::error_code ec, std::size_t length);
@ -69,8 +74,6 @@ class HttpSession : public std::enable_shared_from_this<HttpSession> {
HttpResponse response_;
};
typedef std::shared_ptr<HttpSession> HttpSessionPtr;
} // namespace webcc
#endif // WEBCC_HTTP_SESSION_H_
#endif // WEBCC_HTTP_CONNECTION_H_

@ -17,38 +17,53 @@ 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_) {
if (boost::iequals(h.name, name)) {
h.value = value;
if (boost::iequals(h.first, key)) {
h.second = value;
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_) {
if (boost::iequals(h.name, name)) {
h.value = std::move(value);
if (boost::iequals(h.first, key)) {
h.second = std::move(value);
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
void HttpMessage::SetContentType(const std::string& media_type,
const std::string& charset) {
SetHeader(http::headers::kContentType,
media_type + ";charset=" + charset);
if (charset.empty()) {
SetHeader(http::headers::kContentType, media_type);
} else {
SetHeader(http::headers::kContentType,
media_type + ";charset=" + charset);
}
}
void HttpMessage::SetContent(std::string&& content, bool set_length) {
@ -72,10 +87,10 @@ std::vector<boost::asio::const_buffer> HttpMessage::ToBuffers() const {
buffers.push_back(boost::asio::buffer(start_line_));
for (const HttpHeader& h : headers_) {
buffers.push_back(boost::asio::buffer(h.name));
for (const HttpHeader& h : headers_.data()) {
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(h.value));
buffers.push_back(boost::asio::buffer(h.second));
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_;
for (const HttpHeader& h : headers_) {
os << indent_str << h.name << ": " << h.value << std::endl;
for (const HttpHeader& h : headers_.data()) {
os << indent_str << h.first << ": " << h.second << std::endl;
}
os << indent_str << std::endl;
@ -144,9 +159,4 @@ std::string HttpMessage::Dump(std::size_t indent,
return ss.str();
}
std::ostream& operator<<(std::ostream& os, const HttpMessage& message) {
message.Dump(os);
return os;
}
} // namespace webcc

@ -12,35 +12,76 @@
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.
class HttpMessage {
public:
HttpMessage() : content_length_(kInvalidLength) {}
public:
HttpMessage() : content_length_(kInvalidLength) {
}
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) {
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& name, const std::string& value);
void SetHeader(const std::string& key, const std::string& value) {
headers_.Add(key, value);
}
// TODO: Remove
void SetHeader(std::string&& name, std::string&& value);
void SetHeader(std::string&& key, 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,
const std::string& charset);
@ -51,7 +92,7 @@ class HttpMessage {
// Make the message (e.g., update start line).
// 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
// underlying memory blocks, therefore the message object must remain valid
@ -66,7 +107,7 @@ class HttpMessage {
std::string Dump(std::size_t indent = 0,
const std::string& prefix = "") const;
protected:
protected:
void SetContentLength(std::size_t content_length) {
content_length_ = content_length;
SetHeader(http::headers::kContentLength, std::to_string(content_length));
@ -77,13 +118,11 @@ class HttpMessage {
std::size_t content_length_;
std::vector<HttpHeader> headers_;
HttpHeaderDict headers_;
std::string content_;
};
std::ostream& operator<<(std::ostream& os, const HttpMessage& message);
} // namespace webcc
#endif // WEBCC_HTTP_MESSAGE_H_

@ -1,46 +1,51 @@
#include "webcc/http_request.h"
#include "webcc/logger.h"
namespace webcc {
HttpRequest::HttpRequest(const std::string& method,
const std::string& url,
const std::string& host,
const std::string& port)
: method_(method), url_(url), host_(host), port_(port) {
const std::vector<std::string>& parameters)
: method_(method), url_(url) {
assert(parameters.size() % 2 == 0);
for (std::size_t i = 1; i < parameters.size(); i += 2) {
url_.AddParameter(parameters[i - 1], parameters[i]);
}
}
void HttpRequest::Prepare() {
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();
}
start_line_ = method_;
start_line_ += " ";
start_line_ += url_;
start_line_ += target;
start_line_ += " HTTP/1.1";
start_line_ += CRLF;
if (port_.empty()) {
SetHeader(http::headers::kHost, host_);
if (url_.port().empty()) {
SetHeader(http::headers::kHost, url_.host());
} else {
SetHeader(http::headers::kHost, host_ + ":" + port_);
SetHeader(http::headers::kHost, url_.host() + ":" + url_.port());
}
// TODO: Support Keep-Alive connection.
//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);
return true;
}
// static
HttpRequestPtr HttpRequest::New(const std::string& method,
const std::string& url,
const std::string& host,
const std::string& port,
const std::vector<std::string>& parameters,
bool prepare) {
HttpRequestPtr request{
new HttpRequest{ method, url, host, port }
};
HttpRequestPtr request{ new HttpRequest{ method, url, parameters } };
if (prepare) {
request->Prepare();

@ -3,8 +3,10 @@
#include <memory>
#include <string>
#include <vector>
#include "webcc/http_message.h"
#include "webcc/url.h"
namespace webcc {
@ -14,29 +16,34 @@ class HttpRequestParser;
typedef std::shared_ptr<HttpRequest> HttpRequestPtr;
class HttpRequest : public HttpMessage {
public:
public:
HttpRequest() = default;
// The |host| is a descriptive name (e.g., www.google.com) or a numeric IP
// 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.
// TODO: Move parameters
HttpRequest(const std::string& method,
const std::string& url,
const std::string& host,
const std::string& port = "");
const std::vector<std::string>& parameters = {});
~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& port() const { return port_; }
const std::string& host() const {
return url_.host();
}
const std::string& port() const {
return url_.port();
}
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.
@ -51,30 +58,26 @@ class HttpRequest : public HttpMessage {
// Prepare payload.
// Compose start line, set Host header, etc.
void Prepare() override;
bool Prepare() override;
// TODO: Re-place
static HttpRequestPtr New(const std::string& method,
const std::string& url,
const std::string& host,
const std::string& port = "",
const std::vector<std::string>& parameters = {},
bool prepare = true);
private:
private:
friend class HttpRequestParser;
void set_method(const std::string& method) { method_ = method; }
void set_url(const std::string& url) { url_ = url; }
void set_method(const std::string& method) {
method_ = method;
}
void set_url(const std::string& url) {
url_.Init(url);
}
// HTTP method.
std::string method_;
// 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_;
Url url_;
};
} // 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 {
void HttpRequestHandler::Enqueue(HttpSessionPtr session) {
queue_.Push(session);
void HttpRequestHandler::Enqueue(HttpConnectionPtr connection) {
queue_.Push(connection);
}
void HttpRequestHandler::Start(std::size_t count) {
@ -24,14 +24,14 @@ void HttpRequestHandler::Start(std::size_t count) {
void HttpRequestHandler::Stop() {
LOG_INFO("Stopping workers...");
// Close pending sessions.
for (HttpSessionPtr s = queue_.Pop(); s; s = queue_.Pop()) {
LOG_INFO("Closing pending session...");
// Close pending connections.
for (HttpConnectionPtr s = queue_.Pop(); s; s = queue_.Pop()) {
LOG_INFO("Closing pending connection...");
s->Close();
}
// Enqueue a null session to trigger the first worker to stop.
queue_.Push(HttpSessionPtr());
// Enqueue a null connection to trigger the first worker to stop.
queue_.Push(HttpConnectionPtr());
for (auto& worker : workers_) {
if (worker.joinable()) {
@ -46,19 +46,19 @@ void HttpRequestHandler::WorkerRoutine() {
LOG_INFO("Worker is running.");
for (;;) {
HttpSessionPtr session = queue_.PopOrWait();
HttpConnectionPtr connection = queue_.PopOrWait();
if (!session) {
if (!connection) {
LOG_INFO("Worker is going to stop.");
// For stopping next worker.
queue_.Push(HttpSessionPtr());
queue_.Push(HttpConnectionPtr());
// Stop the worker.
break;
}
HandleSession(session);
HandleConnection(connection);
}
}

@ -5,7 +5,7 @@
#include <thread>
#include <vector>
#include "webcc/http_session.h"
#include "webcc/http_connection.h"
#include "webcc/queue.h"
#include "webcc/soap_service.h"
@ -16,28 +16,30 @@ class HttpResponse;
// The common handler for all incoming requests.
class HttpRequestHandler {
public:
public:
HttpRequestHandler() = default;
virtual ~HttpRequestHandler() = default;
WEBCC_DELETE_COPY_ASSIGN(HttpRequestHandler);
// Put the session into the queue.
void Enqueue(HttpSessionPtr session);
// Put the connection into the queue.
void Enqueue(HttpConnectionPtr connection);
// Start worker threads.
void Start(std::size_t count);
// Close pending sessions and stop worker threads.
// Close pending connections and stop worker threads.
void Stop();
private:
private:
void WorkerRoutine();
// 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_;
};

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

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

@ -12,15 +12,15 @@ HttpResponseParser::HttpResponseParser(HttpResponse* response)
}
bool HttpResponseParser::ParseStartLine(const std::string& line) {
std::vector<std::string> splitted;
boost::split(splitted, line, boost::is_any_of(" "), boost::token_compress_on);
std::vector<std::string> parts;
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());
return false;
}
std::string& status_str = splitted[1];
std::string& status_str = parts[1];
try {
response_->set_status(std::stoi(status_str));

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

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

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

@ -35,22 +35,22 @@ class RestClient {
// instead for the HTTP status code.
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,
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,
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,
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) {

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

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

@ -8,9 +8,9 @@ namespace webcc {
void RestListService::Handle(const RestRequest& request,
RestResponse* response) {
if (request.method == kHttpGet) {
if (request.method == http::kGet) {
Get(UrlQuery(request.url_query_str), response);
} else if (request.method == kHttpPost) {
} else if (request.method == http::kPost) {
Post(request.content, response);
} else {
LOG_ERRO("RestListService doesn't support '%s' method.",
@ -22,13 +22,13 @@ void RestListService::Handle(const RestRequest& request,
void RestDetailService::Handle(const RestRequest& request,
RestResponse* response) {
if (request.method == kHttpGet) {
if (request.method == http::kGet) {
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);
} else if (request.method == kHttpPatch) {
} else if (request.method == http::kPatch) {
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);
} else {
LOG_ERRO("RestDetailService doesn't support '%s' method.",

@ -63,7 +63,7 @@ protected:
// Default: "utf-8".
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_;
private:
@ -96,7 +96,7 @@ public:
inline bool Post(const std::string& url, std::string&& content,
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,

@ -48,7 +48,14 @@ bool SoapClient::Request(const std::string& operation,
std::string 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);

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

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

@ -10,15 +10,16 @@
namespace webcc {
class SoapRequestHandler : public HttpRequestHandler {
public:
public:
explicit SoapRequestHandler(SoapVersion soap_version)
: soap_version_(soap_version),
format_raw_(true) {
: soap_version_(soap_version), format_raw_(true) {
}
~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) {
indent_str_ = indent_str;
@ -26,8 +27,8 @@ class SoapRequestHandler : public HttpRequestHandler {
bool Bind(SoapServicePtr service, const std::string& url);
private:
void HandleSession(HttpSessionPtr session) override;
private:
void HandleConnection(HttpConnectionPtr connection) override;
SoapServicePtr GetServiceByUrl(const std::string& url);

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

@ -2,10 +2,6 @@
#define WEBCC_URL_H_
// 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 <utility>
@ -17,7 +13,7 @@ namespace webcc {
// URL query parameters.
class UrlQuery {
public:
public:
typedef std::pair<std::string, std::string> Parameter;
typedef std::vector<Parameter> Parameters;
@ -48,7 +44,7 @@ class UrlQuery {
// E.g., "item=12731&color=blue&size=large".
std::string ToString() const;
private:
private:
typedef Parameters::const_iterator ConstIterator;
ConstIterator Find(const std::string& key) const;
@ -58,34 +54,46 @@ class UrlQuery {
// -----------------------------------------------------------------------------
class Url {
public:
public:
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 {
return path_;
void Init(const std::string& str, bool decode = true, bool clear = true);
const std::string& scheme() const {
return scheme_;
}
const std::string& host() const {
return host_;
}
void set_path(const std::string& path) {
path_ = path;
const std::string& port() const {
return port_;
}
const std::string& path() const {
return path_;
}
const std::string& query() const {
return query_;
}
void set_query(const std::string& query) {
query_ = query;
}
// Add a parameter to the query string.
void AddParameter(const std::string& key, const std::string& value);
// Split a path into its hierarchical components.
static std::vector<std::string> SplitPath(const std::string& path);
private:
void Parse(const std::string& str);
private:
void Init(const std::string& str);
void Clear();
// TODO: Support auth & fragment.
std::string scheme_;
std::string host_;
std::string port_;
std::string path_;
std::string query_;
};

Loading…
Cancel
Save