Initial client API V2
This commit is contained in:
+11
-6
@@ -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)
|
||||
|
||||
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)
|
||||
|
||||
add_subdirectory(example/rest_book_server)
|
||||
add_subdirectory(example/rest_book_client)
|
||||
add_subdirectory(example/rest_book_async_client)
|
||||
# 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
-1
@@ -1,7 +1,7 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: Google
|
||||
AccessModifierOffset: -1
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
|
||||
+37
-17
@@ -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;
|
||||
|
||||
webcc::HttpClient client;
|
||||
HttpResponsePtr r;
|
||||
|
||||
if (client.Request(*request)) {
|
||||
std::cout << client.response_content() << std::endl;
|
||||
} else {
|
||||
PrintError(client);
|
||||
}
|
||||
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
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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,7 +1,13 @@
|
||||
# Unit test
|
||||
set(UT_SRCS
|
||||
url_test.cc
|
||||
)
|
||||
|
||||
if(WEBCC_ENABLE_REST)
|
||||
set(UT_SRCS ${UT_SRCS}
|
||||
rest_service_manager_test.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
set(UT_TARGET_NAME webcc_unittest)
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+35
-31
@@ -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(REST_SOURCES
|
||||
# rest_async_client.cc
|
||||
# rest_client.cc
|
||||
rest_request_handler.cc
|
||||
rest_service_manager.cc
|
||||
rest_service.cc
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
http_ssl_client.cc
|
||||
# rest_ssl_async_client.cc
|
||||
rest_ssl_client.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
|
||||
|
||||
+2
-9
@@ -1,18 +1,11 @@
|
||||
#include "webcc/globals.h"
|
||||
|
||||
#include "boost/algorithm/string.hpp"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
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";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const char* DescribeError(Error error) {
|
||||
switch (error) {
|
||||
case kHostResolveError:
|
||||
|
||||
+44
-39
@@ -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_
|
||||
|
||||
+1
-1
@@ -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,
|
||||
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> {
|
||||
typedef std::shared_ptr<HttpConnection> HttpConnectionPtr;
|
||||
|
||||
class HttpConnection : public std::enable_shared_from_this<HttpConnection> {
|
||||
public:
|
||||
HttpSession(boost::asio::ip::tcp::socket socket,
|
||||
HttpConnection(boost::asio::ip::tcp::socket socket,
|
||||
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.
|
||||
void Start();
|
||||
@@ -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_
|
||||
+35
-25
@@ -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_) {
|
||||
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) });
|
||||
}
|
||||
|
||||
// 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"
|
||||
bool HttpHeaderDict::Has(const std::string& key) const {
|
||||
for (const HttpHeader& h : headers_) {
|
||||
if (boost::iequals(h.first, key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// See: https://tools.ietf.org/html/rfc7231#section-3.1.1.1
|
||||
void HttpMessage::SetContentType(const std::string& media_type,
|
||||
const std::string& 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) {
|
||||
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_));
|
||||
|
||||
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
|
||||
|
||||
+55
-16
@@ -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) {}
|
||||
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
|
||||
@@ -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_
|
||||
|
||||
+27
-22
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
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_ += " ";
|
||||
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();
|
||||
|
||||
+28
-25
@@ -3,8 +3,10 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/http_message.h"
|
||||
#include "webcc/url.h"
|
||||
|
||||
namespace webcc {
|
||||
|
||||
@@ -17,26 +19,31 @@ class HttpRequest : public HttpMessage {
|
||||
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:
|
||||
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"
|
||||
|
||||
@@ -22,22 +22,24 @@ class HttpRequestHandler {
|
||||
|
||||
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:
|
||||
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,7 +96,7 @@ void HttpServer::DoAccept() {
|
||||
if (!ec) {
|
||||
LOG_INFO("Accepted a connection.");
|
||||
|
||||
std::make_shared<HttpSession>(std::move(socket),
|
||||
std::make_shared<HttpConnection>(std::move(socket),
|
||||
GetRequestHandler())->Start();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+4
-4
@@ -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),
|
||||
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
|
||||
|
||||
@@ -13,12 +13,13 @@ namespace webcc {
|
||||
class RestRequestHandler : public HttpRequestHandler {
|
||||
public:
|
||||
RestRequestHandler() = default;
|
||||
|
||||
~RestRequestHandler() override = default;
|
||||
|
||||
bool Bind(RestServicePtr service, const std::string& url, bool is_regex);
|
||||
|
||||
private:
|
||||
void HandleSession(HttpSessionPtr session) override;
|
||||
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) {
|
||||
|
||||
@@ -12,13 +12,14 @@ namespace webcc {
|
||||
class SoapRequestHandler : public HttpRequestHandler {
|
||||
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;
|
||||
@@ -27,7 +28,7 @@ class SoapRequestHandler : public HttpRequestHandler {
|
||||
bool Bind(SoapServicePtr service, const std::string& url);
|
||||
|
||||
private:
|
||||
void HandleSession(HttpSessionPtr session) override;
|
||||
void HandleConnection(HttpConnectionPtr connection) override;
|
||||
|
||||
SoapServicePtr GetServiceByUrl(const std::string& url);
|
||||
|
||||
|
||||
+65
-25
@@ -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);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
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);
|
||||
}
|
||||
|
||||
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_ = str.substr(0, pos);
|
||||
query_ = str.substr(pos + 1);
|
||||
path_ = tmp;
|
||||
}
|
||||
} else {
|
||||
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
|
||||
|
||||
+25
-17
@@ -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>
|
||||
@@ -60,32 +56,44 @@ class UrlQuery {
|
||||
class Url {
|
||||
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);
|
||||
|
||||
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_;
|
||||
}
|
||||
|
||||
const std::string& port() const {
|
||||
return port_;
|
||||
}
|
||||
|
||||
const std::string& path() const {
|
||||
return path_;
|
||||
}
|
||||
|
||||
void set_path(const std::string& path) {
|
||||
path_ = path;
|
||||
}
|
||||
|
||||
const std::string& query() const {
|
||||
return query_;
|
||||
}
|
||||
|
||||
void set_query(const std::string& query) {
|
||||
query_ = query;
|
||||
}
|
||||
|
||||
// Split a path into its hierarchical components.
|
||||
static std::vector<std::string> SplitPath(const std::string& path);
|
||||
// Add a parameter to the query string.
|
||||
void AddParameter(const std::string& key, const std::string& value);
|
||||
|
||||
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 query_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user