Allow to set buffer size for client; don't auto adjust reading buffer size; refine the global definitions.

master
Chunting Gu 7 years ago
parent ae9d4efcf1
commit 3e60c76da1

@ -5,6 +5,11 @@
#include "webcc/rest_ssl_client.h" #include "webcc/rest_ssl_client.h"
#include "webcc/logger.h" #include "webcc/logger.h"
const bool kSslVerify = false;
#define PRINT_CONTENT 0
static Json::Value StringToJson(const std::string& str) { static Json::Value StringToJson(const std::string& str) {
Json::Value json; Json::Value json;
@ -18,10 +23,11 @@ static Json::Value StringToJson(const std::string& str) {
return json; return json;
} }
void Test() { void ListPublicEvents() {
webcc::RestSslClient client("api.github.com"); webcc::RestSslClient client("api.github.com", "", kSslVerify);
if (client.Get("/events")) { if (client.Get("/events")) {
#if PRINT_CONTENT
Json::Value json = StringToJson(client.response()->content()); Json::Value json = StringToJson(client.response()->content());
// Pretty print the JSON. // Pretty print the JSON.
@ -33,6 +39,33 @@ void Test() {
writer->write(json, &std::cout); writer->write(json, &std::cout);
std::cout << std::endl; std::cout << std::endl;
#endif // PRINT_CONTENT
} else {
std::cout << webcc::DescribeError(client.error());
if (client.timed_out()) {
std::cout << " (timed out)";
}
std::cout << std::endl;
}
}
void ListUserFollowers() {
webcc::RestSslClient client("api.github.com", "", kSslVerify);
if (client.Get("/users/sprinfall/followers")) {
#if PRINT_CONTENT
Json::Value json = StringToJson(client.response()->content());
// Pretty print the JSON.
Json::StreamWriterBuilder builder;
builder["indentation"] = " ";
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
writer->write(json, &std::cout);
std::cout << std::endl;
#endif // PRINT_CONTENT
} else { } else {
std::cout << webcc::DescribeError(client.error()); std::cout << webcc::DescribeError(client.error());
if (client.timed_out()) { if (client.timed_out()) {
@ -45,7 +78,9 @@ void Test() {
int main() { int main() {
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
Test(); //ListPublicEvents();
ListUserFollowers();
return 0; return 0;
} }

@ -37,10 +37,7 @@
#include "webcc/logger.h" #include "webcc/logger.h"
void Test() { void Test() {
webcc::HttpRequest request; webcc::HttpRequest request(webcc::kHttpGet, "/get", "httpbin.org"/*, "80"*/);
request.set_method(webcc::kHttpGet);
request.set_url("/get");
request.set_host("httpbin.org"/*, "80"*/);
request.Make(); request.Make();
webcc::HttpClient client; webcc::HttpClient client;

@ -11,11 +11,9 @@
// The default port number should be 8000. // The default port number should be 8000.
void Test(boost::asio::io_context& io_context) { void Test(boost::asio::io_context& io_context) {
webcc::HttpRequestPtr request(new webcc::HttpRequest()); webcc::HttpRequestPtr request = std::make_shared<webcc::HttpRequest>(
webcc::kHttpGet, "/index.html", "localhost", "8000");
request->set_method(webcc::kHttpGet);
request->set_url("/index.html");
request->set_host("localhost", "8000");
request->Make(); request->Make();
webcc::HttpAsyncClientPtr client(new webcc::HttpAsyncClient(io_context)); webcc::HttpAsyncClientPtr client(new webcc::HttpAsyncClient(io_context));

@ -9,10 +9,8 @@
// The default port number should be 8000. // The default port number should be 8000.
void Test() { void Test() {
webcc::HttpRequest request; webcc::HttpRequest request(webcc::kHttpGet, "/index.html", "localhost",
request.set_method(webcc::kHttpGet); "8000");
request.set_url("/index.html");
request.set_host("localhost", "8000");
request.Make(); request.Make();
webcc::HttpClient client; webcc::HttpClient client;

@ -21,20 +21,19 @@ int main(int argc, char* argv[]) {
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
webcc::HttpRequest request; // Leave port to default value.
request.set_method(webcc::kHttpGet); webcc::HttpRequest request(webcc::kHttpGet, url, host);
request.set_url(url);
request.set_host(host); // Leave port to default value.
request.Make(); request.Make();
// Verify the certificate of the peer or not. // Verify the certificate of the peer or not.
// See HttpSslClient::Request() for more details. // See HttpSslClient::Request() for more details.
bool ssl_verify = false; bool ssl_verify = false;
webcc::HttpSslClient client(ssl_verify); webcc::HttpSslClient client(2000, ssl_verify);
if (client.Request(request)) { if (client.Request(request)) {
std::cout << client.response()->content() << std::endl; //std::cout << client.response()->content() << std::endl;
} else { } else {
std::cout << webcc::DescribeError(client.error()); std::cout << webcc::DescribeError(client.error());
if (client.timed_out()) { if (client.timed_out()) {

@ -16,7 +16,7 @@ class BookClientBase {
const std::string& host, const std::string& port, const std::string& host, const std::string& port,
int timeout_seconds) int timeout_seconds)
: rest_client_(io_context, host, port) { : rest_client_(io_context, host, port) {
rest_client_.set_timeout_seconds(timeout_seconds); rest_client_.SetTimeout(timeout_seconds);
} }
virtual ~BookClientBase() = default; virtual ~BookClientBase() = default;
@ -58,10 +58,10 @@ class BookListClient : public BookClientBase {
: BookClientBase(io_context, host, port, timeout_seconds) { : BookClientBase(io_context, host, port, timeout_seconds) {
} }
void ListBooks(webcc::HttpResponseHandler handler) { void ListBooks(webcc::HttpResponseCallback callback) {
std::cout << "ListBooks" << std::endl; std::cout << "ListBooks" << std::endl;
rest_client_.Get("/books", handler); rest_client_.Get("/books", callback);
} }
void CreateBook(const std::string& title, double price, void CreateBook(const std::string& title, double price,
@ -96,7 +96,7 @@ class BookDetailClient : public BookClientBase {
: BookClientBase(io_context, host, port, timeout_seconds) { : BookClientBase(io_context, host, port, timeout_seconds) {
} }
void GetBook(const std::string& id, webcc::HttpResponseHandler handler) { void GetBook(const std::string& id, webcc::HttpResponseCallback callback) {
std::cout << "GetBook: " << id << std::endl; std::cout << "GetBook: " << id << std::endl;
auto rsp_callback = [](webcc::HttpResponsePtr response) { auto rsp_callback = [](webcc::HttpResponsePtr response) {
@ -105,13 +105,13 @@ class BookDetailClient : public BookClientBase {
//id_callback(rsp_json["id"].asString()); //id_callback(rsp_json["id"].asString());
}; };
rest_client_.Get("/books/" + id, handler); rest_client_.Get("/books/" + id, callback);
} }
void UpdateBook(const std::string& id, void UpdateBook(const std::string& id,
const std::string& title, const std::string& title,
double price, double price,
webcc::HttpResponseHandler handler) { webcc::HttpResponseCallback callback) {
std::cout << "UpdateBook: " << id << " " << title << " " << price std::cout << "UpdateBook: " << id << " " << title << " " << price
<< std::endl; << std::endl;
@ -120,13 +120,13 @@ class BookDetailClient : public BookClientBase {
json["title"] = title; json["title"] = title;
json["price"] = price; json["price"] = price;
rest_client_.Put("/books/" + id, JsonToString(json), handler); rest_client_.Put("/books/" + id, JsonToString(json), callback);
} }
void DeleteBook(const std::string& id, webcc::HttpResponseHandler handler) { void DeleteBook(const std::string& id, webcc::HttpResponseCallback callback) {
std::cout << "DeleteBook: " << id << std::endl; std::cout << "DeleteBook: " << id << std::endl;
rest_client_.Delete("/books/" + id, handler); rest_client_.Delete("/books/" + id, callback);
} }
}; };

@ -40,7 +40,7 @@ class BookClientBase {
} }
// Check HTTP response status. // Check HTTP response status.
bool CheckStatus(webcc::HttpStatus::Enum expected_status) { bool CheckStatus(webcc::http::Status expected_status) {
int status = rest_client_.response_status(); int status = rest_client_.response_status();
if (status != expected_status) { if (status != expected_status) {
LOG_ERRO("HTTP status error (actual: %d, expected: %d).", LOG_ERRO("HTTP status error (actual: %d, expected: %d).",
@ -69,7 +69,7 @@ class BookListClient : public BookClientBase {
return false; return false;
} }
if (!CheckStatus(webcc::HttpStatus::kOK)) { if (!CheckStatus(webcc::http::Status::kOK)) {
// Response HTTP status error. // Response HTTP status error.
return false; return false;
} }
@ -97,7 +97,7 @@ class BookListClient : public BookClientBase {
return false; return false;
} }
if (!CheckStatus(webcc::HttpStatus::kCreated)) { if (!CheckStatus(webcc::http::Status::kCreated)) {
return false; return false;
} }
@ -123,7 +123,7 @@ class BookDetailClient : public BookClientBase {
return false; return false;
} }
if (!CheckStatus(webcc::HttpStatus::kOK)) { if (!CheckStatus(webcc::http::Status::kOK)) {
return false; return false;
} }
@ -141,7 +141,7 @@ class BookDetailClient : public BookClientBase {
return false; return false;
} }
if (!CheckStatus(webcc::HttpStatus::kOK)) { if (!CheckStatus(webcc::http::Status::kOK)) {
return false; return false;
} }
@ -154,7 +154,7 @@ class BookDetailClient : public BookClientBase {
return false; return false;
} }
if (!CheckStatus(webcc::HttpStatus::kOK)) { if (!CheckStatus(webcc::http::Status::kOK)) {
return false; return false;
} }

@ -35,7 +35,7 @@ void BookListService::Get(const webcc::UrlQuery& /*query*/,
} }
response->content = JsonToString(json); response->content = JsonToString(json);
response->status = webcc::HttpStatus::kOK; response->status = webcc::http::Status::kOK;
} }
void BookListService::Post(const std::string& request_content, void BookListService::Post(const std::string& request_content,
@ -50,10 +50,10 @@ void BookListService::Post(const std::string& request_content,
json["id"] = id; json["id"] = id;
response->content = JsonToString(json); response->content = JsonToString(json);
response->status = webcc::HttpStatus::kCreated; response->status = webcc::http::Status::kCreated;
} else { } else {
// Invalid JSON // Invalid JSON
response->status = webcc::HttpStatus::kBadRequest; response->status = webcc::http::Status::kBadRequest;
} }
} }
@ -67,7 +67,7 @@ void BookDetailService::Get(const webcc::UrlSubMatches& url_sub_matches,
if (url_sub_matches.size() != 1) { if (url_sub_matches.size() != 1) {
// Using kNotFound means the resource specified by the URL cannot be found. // Using kNotFound means the resource specified by the URL cannot be found.
// kBadRequest could be another choice. // kBadRequest could be another choice.
response->status = webcc::HttpStatus::kNotFound; response->status = webcc::http::Status::kNotFound;
return; return;
} }
@ -75,12 +75,12 @@ void BookDetailService::Get(const webcc::UrlSubMatches& url_sub_matches,
const Book& book = g_book_store.GetBook(book_id); const Book& book = g_book_store.GetBook(book_id);
if (book.IsNull()) { if (book.IsNull()) {
response->status = webcc::HttpStatus::kNotFound; response->status = webcc::http::Status::kNotFound;
return; return;
} }
response->content = BookToJsonString(book); response->content = BookToJsonString(book);
response->status = webcc::HttpStatus::kOK; response->status = webcc::http::Status::kOK;
} }
void BookDetailService::Put(const webcc::UrlSubMatches& url_sub_matches, void BookDetailService::Put(const webcc::UrlSubMatches& url_sub_matches,
@ -89,7 +89,7 @@ void BookDetailService::Put(const webcc::UrlSubMatches& url_sub_matches,
Sleep(sleep_seconds_); Sleep(sleep_seconds_);
if (url_sub_matches.size() != 1) { if (url_sub_matches.size() != 1) {
response->status = webcc::HttpStatus::kNotFound; response->status = webcc::http::Status::kNotFound;
return; return;
} }
@ -97,14 +97,14 @@ void BookDetailService::Put(const webcc::UrlSubMatches& url_sub_matches,
Book book; Book book;
if (!JsonStringToBook(request_content, &book)) { if (!JsonStringToBook(request_content, &book)) {
response->status = webcc::HttpStatus::kBadRequest; response->status = webcc::http::Status::kBadRequest;
return; return;
} }
book.id = book_id; book.id = book_id;
g_book_store.UpdateBook(book); g_book_store.UpdateBook(book);
response->status = webcc::HttpStatus::kOK; response->status = webcc::http::Status::kOK;
} }
void BookDetailService::Delete(const webcc::UrlSubMatches& url_sub_matches, void BookDetailService::Delete(const webcc::UrlSubMatches& url_sub_matches,
@ -112,16 +112,16 @@ void BookDetailService::Delete(const webcc::UrlSubMatches& url_sub_matches,
Sleep(sleep_seconds_); Sleep(sleep_seconds_);
if (url_sub_matches.size() != 1) { if (url_sub_matches.size() != 1) {
response->status = webcc::HttpStatus::kNotFound; response->status = webcc::http::Status::kNotFound;
return; return;
} }
const std::string& book_id = url_sub_matches[0]; const std::string& book_id = url_sub_matches[0];
if (!g_book_store.DeleteBook(book_id)) { if (!g_book_store.DeleteBook(book_id)) {
response->status = webcc::HttpStatus::kNotFound; response->status = webcc::http::Status::kNotFound;
return; return;
} }
response->status = webcc::HttpStatus::kOK; response->status = webcc::http::Status::kOK;
} }

@ -104,7 +104,7 @@ bool BookClient::Call1(const std::string& operation,
bool BookClient::Call(const std::string& operation, bool BookClient::Call(const std::string& operation,
std::vector<webcc::SoapParameter>&& parameters, std::vector<webcc::SoapParameter>&& parameters,
std::string* result_str) { std::string* result_str) {
if (!soap_client_.Request(operation, std::move(parameters), kResult, if (!soap_client_.Request(operation, std::move(parameters), kResult, 0,
result_str)) { result_str)) {
PrintError(); PrintError();
return false; return false;

@ -55,7 +55,7 @@ class CalcClient {
}; };
std::string result_str; std::string result_str;
if (!soap_client_.Request(operation, std::move(parameters), kResultName, if (!soap_client_.Request(operation, std::move(parameters), kResultName, 0,
&result_str)) { &result_str)) {
PrintError(); PrintError();
return false; return false;

@ -56,7 +56,7 @@ class CalcClient {
}; };
std::string result_str; std::string result_str;
if (!soap_client_.Request(operation, std::move(parameters), kResultName, if (!soap_client_.Request(operation, std::move(parameters), kResultName, 0,
&result_str)) { &result_str)) {
PrintError(); PrintError();
return false; return false;

@ -5,7 +5,7 @@ class TestRestService : public webcc::RestService {
public: public:
void Handle(const webcc::RestRequest& request, void Handle(const webcc::RestRequest& request,
webcc::RestResponse* response) final { webcc::RestResponse* response) final {
response->status = webcc::HttpStatus::kOK; response->status = webcc::http::Status::kOK;
} }
}; };

@ -65,11 +65,13 @@ set(SOURCES
if(WEBCC_ENABLE_SSL) if(WEBCC_ENABLE_SSL)
set(HEADERS ${HEADERS} set(HEADERS ${HEADERS}
http_ssl_client.h http_ssl_client.h
# rest_ssl_async_client.h
rest_ssl_client.h rest_ssl_client.h
) )
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
http_ssl_client.cc http_ssl_client.cc
# rest_ssl_async_client.cc
rest_ssl_client.cc rest_ssl_client.cc
) )
endif() endif()

@ -4,19 +4,6 @@ namespace webcc {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// NOTE: Field names are case-insensitive.
// See https://stackoverflow.com/a/5259004 for more details.
const std::string kHost = "Host";
const std::string kContentType = "Content-Type";
const std::string kContentLength = "Content-Length";
const std::string kTransferEncoding = "Transfer-Encoding";
const std::string kUserAgent = "User-Agent";
const std::string kAppJsonUtf8 = "application/json; charset=utf-8";
const std::string kHttpPort = "80";
const std::string kHttpSslPort = "443";
const std::string kHttpHead = "HEAD"; const std::string kHttpHead = "HEAD";
const std::string kHttpGet = "GET"; const std::string kHttpGet = "GET";
const std::string kHttpPost = "POST"; const std::string kHttpPost = "POST";

@ -46,20 +46,69 @@ const int kMaxReadSeconds = 30;
const std::size_t kMaxDumpSize = 2048; const std::size_t kMaxDumpSize = 2048;
// HTTP headers. // HTTP headers.
extern const std::string kHost; namespace http {
extern const std::string kContentType;
extern const std::string kContentLength;
extern const std::string kTransferEncoding;
extern const std::string kUserAgent;
extern const std::string kAppJsonUtf8; // HTTP response status.
// This is not a full list.
// Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
// NTOE: Don't use enum class because we want to convert to/from int easily.
enum Status {
kOK = 200,
kCreated = 201,
kAccepted = 202,
kNoContent = 204,
kNotModified = 304,
kBadRequest = 400,
kNotFound = 404,
kInternalServerError = 500,
kNotImplemented = 501,
kServiceUnavailable = 503,
};
namespace headers {
// NOTE: Field names are case-insensitive.
// See https://stackoverflow.com/a/5259004 for more details.
const char* const kHost = "Host";
const char* const kContentType = "Content-Type";
const char* const kContentLength = "Content-Length";
const char* const kConnection = "Connection";
const char* const kTransferEncoding = "Transfer-Encoding";
const char* const kAccept = "Accept";
const char* const kAcceptEncoding = "Accept-Encoding";
const char* const kUserAgent = "User-Agent";
} // namespace headers
namespace media_types {
// NOTE:
// According to www.w3.org when placing SOAP messages in HTTP bodies, the HTTP
// Content-type header must be chosen as "application/soap+xml" [RFC 3902].
// But in practice, many web servers cannot understand it.
// See: https://www.w3.org/TR/2007/REC-soap12-part0-20070427/#L26854
const char* const kApplicationJson = "application/json";
const char* const kApplicationSoapXml = "application/soap+xml";
const char* const kTextXml = "text/xml";
} // namespace media_types
namespace charsets {
const char* const kUtf8 = "utf-8";
} // namespace charsets
} // namespace http
// Default ports. // Default ports.
extern const std::string kHttpPort; const char* const kHttpPort = "80";
extern const std::string kHttpSslPort; const char* const kHttpSslPort = "443";
// HTTP methods (verbs) in string ("HEAD", "GET", etc.). // HTTP methods (verbs) in string ("HEAD", "GET", etc.).
// NOTE: Don't use enum to avoid converting back and forth. // NOTE: Don't use enum to avoid converting back and forth.
// TODO: Add enum.
extern const std::string kHttpHead; extern const std::string kHttpHead;
extern const std::string kHttpGet; extern const std::string kHttpGet;
extern const std::string kHttpPost; extern const std::string kHttpPost;
@ -67,25 +116,6 @@ extern const std::string kHttpPatch;
extern const std::string kHttpPut; extern const std::string kHttpPut;
extern const std::string kHttpDelete; extern const std::string kHttpDelete;
// HTTP response status.
// This is not a full list.
// Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
// NTOE: Don't use enum class because we want to convert to/from int easily.
struct HttpStatus {
enum Enum {
kOK = 200,
kCreated = 201,
kAccepted = 202,
kNoContent = 204,
kNotModified = 304,
kBadRequest = 400,
kNotFound = 404,
kInternalServerError = 500,
kNotImplemented = 501,
kServiceUnavailable = 503,
};
};
// Client side error codes. // Client side error codes.
enum Error { enum Error {
kNoError = 0, // i.e., OK kNoError = 0, // i.e., OK

@ -9,20 +9,27 @@
namespace webcc { namespace webcc {
HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context) HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context,
std::size_t buffer_size)
: socket_(io_context), : socket_(io_context),
resolver_(io_context), resolver_(io_context),
buffer_(kBufferSize), buffer_(buffer_size == 0 ? kBufferSize : buffer_size),
deadline_(io_context), deadline_(io_context),
timeout_seconds_(kMaxReadSeconds), timeout_seconds_(kMaxReadSeconds),
stopped_(false), stopped_(false),
timed_out_(false) { timed_out_(false) {
} }
void HttpAsyncClient::SetTimeout(int seconds) {
if (seconds > 0) {
timeout_seconds_ = seconds;
}
}
void HttpAsyncClient::Request(std::shared_ptr<HttpRequest> request, void HttpAsyncClient::Request(std::shared_ptr<HttpRequest> request,
HttpResponseHandler response_handler) { HttpResponseCallback response_callback) {
assert(request); assert(request);
assert(response_handler); assert(response_callback);
response_.reset(new HttpResponse()); response_.reset(new HttpResponse());
response_parser_.reset(new HttpResponseParser(response_.get())); response_parser_.reset(new HttpResponseParser(response_.get()));
@ -33,7 +40,7 @@ void HttpAsyncClient::Request(std::shared_ptr<HttpRequest> request,
LOG_VERB("HTTP request:\n%s", request->Dump(4, "> ").c_str()); LOG_VERB("HTTP request:\n%s", request->Dump(4, "> ").c_str());
request_ = request; request_ = request;
response_handler_ = response_handler; response_callback_ = response_callback;
resolver_.async_resolve(tcp::v4(), request->host(), request->port(kHttpPort), resolver_.async_resolve(tcp::v4(), request->host(), request->port(kHttpPort),
std::bind(&HttpAsyncClient::OnResolve, std::bind(&HttpAsyncClient::OnResolve,
@ -43,31 +50,20 @@ void HttpAsyncClient::Request(std::shared_ptr<HttpRequest> request,
} }
void HttpAsyncClient::Stop() { void HttpAsyncClient::Stop() {
if (stopped_) { LOG_INFO("The user asks to cancel the request.");
return;
}
stopped_ = true;
LOG_INFO("Close socket...");
boost::system::error_code ec; DoStop();
socket_.close(ec);
if (ec) {
LOG_ERRO("Socket close error (%s).", ec.message().c_str());
}
LOG_INFO("Cancel deadline timer...");
deadline_.cancel();
} }
void HttpAsyncClient::OnResolve(boost::system::error_code ec, void HttpAsyncClient::OnResolve(boost::system::error_code ec,
tcp::resolver::results_type endpoints) { tcp::resolver::results_type endpoints) {
if (ec) { if (ec) {
LOG_ERRO("Can't resolve host (%s): %s, %s", ec.message().c_str(), LOG_ERRO("Host resolve error (%s): %s, %s.", ec.message().c_str(),
request_->host().c_str(), request_->port().c_str()); request_->host().c_str(), request_->port().c_str());
response_handler_(response_, kHostResolveError, timed_out_); response_callback_(response_, kHostResolveError, timed_out_);
} else { } else {
LOG_VERB("Connect to server...");
// ConnectHandler: void(boost::system::error_code, tcp::endpoint) // ConnectHandler: void(boost::system::error_code, tcp::endpoint)
boost::asio::async_connect(socket_, endpoints, boost::asio::async_connect(socket_, endpoints,
std::bind(&HttpAsyncClient::OnConnect, std::bind(&HttpAsyncClient::OnConnect,
@ -81,19 +77,19 @@ void HttpAsyncClient::OnConnect(boost::system::error_code ec,
tcp::endpoint endpoint) { tcp::endpoint endpoint) {
if (ec) { if (ec) {
LOG_ERRO("Socket connect error (%s).", ec.message().c_str()); LOG_ERRO("Socket connect error (%s).", ec.message().c_str());
Stop(); DoStop();
response_handler_(response_, kEndpointConnectError, timed_out_); response_callback_(response_, kEndpointConnectError, timed_out_);
return; return;
} }
LOG_VERB("Socket connected."); LOG_VERB("Socket connected.");
// The deadline actor may have had a chance to run and close our socket, even // Even though the connect operation notionally succeeded, the user could
// though the connect operation notionally succeeded. // have stopped the operation by calling Stop(). And if we started the
// deadline timer, it could also be stopped due to timeout.
if (stopped_) { if (stopped_) {
// |timed_out_| should be true in this case. // TODO: Use some other error.
LOG_ERRO("Socket connect timed out."); response_callback_(response_, kEndpointConnectError, timed_out_);
response_handler_(response_, kEndpointConnectError, timed_out_);
return; return;
} }
@ -102,9 +98,10 @@ void HttpAsyncClient::OnConnect(boost::system::error_code ec,
} }
void HttpAsyncClient::DoWrite() { void HttpAsyncClient::DoWrite() {
if (stopped_) { // NOTE:
return; // It doesn't make much sense to set a timeout for socket write.
} // I find that it's almost impossible to simulate a situation in the server
// side to test this timeout.
boost::asio::async_write(socket_, boost::asio::async_write(socket_,
request_->ToBuffers(), request_->ToBuffers(),
@ -115,13 +112,19 @@ void HttpAsyncClient::DoWrite() {
void HttpAsyncClient::OnWrite(boost::system::error_code ec) { void HttpAsyncClient::OnWrite(boost::system::error_code ec) {
if (stopped_) { if (stopped_) {
// TODO: Use some other error.
response_callback_(response_, kSocketWriteError, timed_out_);
return; return;
} }
if (ec) { if (ec) {
Stop(); LOG_ERRO("Socket write error (%s).", ec.message().c_str());
response_handler_(response_, kSocketWriteError, timed_out_); DoStop();
response_callback_(response_, kSocketWriteError, timed_out_);
} else { } else {
LOG_INFO("Request sent.");
LOG_VERB("Read response (timeout: %ds)...", timeout_seconds_);
deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_)); deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_));
DoWaitDeadline(); DoWaitDeadline();
@ -142,36 +145,30 @@ void HttpAsyncClient::OnRead(boost::system::error_code ec,
LOG_VERB("Socket async read handler."); LOG_VERB("Socket async read handler.");
if (ec || length == 0) { if (ec || length == 0) {
Stop(); DoStop();
LOG_ERRO("Socket read error (%s).", ec.message().c_str()); LOG_ERRO("Socket read error (%s).", ec.message().c_str());
response_handler_(response_, kSocketReadError, timed_out_); response_callback_(response_, kSocketReadError, timed_out_);
return; return;
} }
LOG_INFO("Read data, length: %d.", length); LOG_INFO("Read data, length: %u.", length);
bool content_length_parsed = response_parser_->content_length_parsed();
// Parse the response piece just read. // Parse the response piece just read.
// If the content has been fully received, |finished()| will be true. // If the content has been fully received, |finished()| will be true.
if (!response_parser_->Parse(buffer_.data(), length)) { if (!response_parser_->Parse(buffer_.data(), length)) {
Stop(); DoStop();
LOG_ERRO("Failed to parse HTTP response."); LOG_ERRO("Failed to parse HTTP response.");
response_handler_(response_, kHttpError, timed_out_); response_callback_(response_, kHttpError, timed_out_);
return; return;
} }
if (!content_length_parsed &&
response_parser_->content_length_parsed()) {
// Content length just has been parsed.
AdjustBufferSize(response_parser_->content_length(), &buffer_);
}
if (response_parser_->finished()) { if (response_parser_->finished()) {
DoStop();
LOG_INFO("Finished to read and parse HTTP response."); LOG_INFO("Finished to read and parse HTTP response.");
LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str()); LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str());
Stop();
response_handler_(response_, kNoError, timed_out_); response_callback_(response_, kNoError, timed_out_);
return; return;
} }
@ -186,17 +183,50 @@ void HttpAsyncClient::DoWaitDeadline() {
} }
void HttpAsyncClient::OnDeadline(boost::system::error_code ec) { void HttpAsyncClient::OnDeadline(boost::system::error_code ec) {
LOG_VERB("Deadline handler."); if (stopped_) {
return;
}
if (ec == boost::asio::error::operation_aborted) { LOG_VERB("OnDeadline.");
LOG_VERB("Deadline timer canceled.");
// NOTE: Can't check this:
// if (ec == boost::asio::error::operation_aborted) {
// LOG_VERB("Deadline timer canceled.");
// return;
// }
if (deadline_.expires_at() <=
boost::asio::deadline_timer::traits_type::now()) {
// The deadline has passed.
// The socket is closed so that any outstanding asynchronous operations
// are canceled.
LOG_WARN("HTTP client timed out.");
timed_out_ = true;
Stop();
return; return;
} }
LOG_WARN("HTTP client timed out."); // Put the actor back to sleep.
timed_out_ = true; DoWaitDeadline();
}
Stop(); void HttpAsyncClient::DoStop() {
if (stopped_) {
return;
}
stopped_ = true;
LOG_INFO("Close socket...");
boost::system::error_code ec;
socket_.close(ec);
if (ec) {
LOG_ERRO("Socket close error (%s).", ec.message().c_str());
}
LOG_INFO("Cancel deadline timer...");
deadline_.cancel();
} }
} // namespace webcc } // namespace webcc

@ -16,8 +16,8 @@
namespace webcc { namespace webcc {
// Response handler/callback. // Response callback.
typedef std::function<void(HttpResponsePtr, Error, bool)> HttpResponseHandler; typedef std::function<void(HttpResponsePtr, Error, bool)> HttpResponseCallback;
// HTTP client session in asynchronous mode. // HTTP client session in asynchronous mode.
// A request will return without waiting for the response, the callback handler // A request will return without waiting for the response, the callback handler
@ -25,21 +25,22 @@ typedef std::function<void(HttpResponsePtr, Error, bool)> HttpResponseHandler;
// Don't use the same HttpAsyncClient object in multiple threads. // Don't use the same HttpAsyncClient object in multiple threads.
class HttpAsyncClient : public std::enable_shared_from_this<HttpAsyncClient> { class HttpAsyncClient : public std::enable_shared_from_this<HttpAsyncClient> {
public: public:
explicit HttpAsyncClient(boost::asio::io_context& io_context); // The |buffer_size| is the bytes of the buffer for reading response.
// 0 means default value (e.g., 1024) will be used.
explicit HttpAsyncClient(boost::asio::io_context& io_context,
std::size_t buffer_size = 0);
WEBCC_DELETE_COPY_ASSIGN(HttpAsyncClient); WEBCC_DELETE_COPY_ASSIGN(HttpAsyncClient);
void set_timeout_seconds(int timeout_seconds) { // Set the timeout seconds for reading response.
timeout_seconds_ = timeout_seconds; // The |seconds| is only effective when greater than 0.
} void SetTimeout(int seconds);
// Asynchronously connect to the server, send the request, read the response, // Asynchronously connect to the server, send the request, read the response,
// and call the |response_handler| when all these finish. // and call the |response_callback| when all these finish.
void Request(HttpRequestPtr request, HttpResponseHandler response_handler); void Request(HttpRequestPtr request, HttpResponseCallback response_callback);
// Terminate all the actors to shut down the connection. It may be called by // Called by the user to cancel the request.
// the user of the client class, or by the class itself in response to
// graceful termination or an unrecoverable error.
void Stop(); void Stop();
private: private:
@ -59,6 +60,9 @@ class HttpAsyncClient : public std::enable_shared_from_this<HttpAsyncClient> {
void DoWaitDeadline(); void DoWaitDeadline();
void OnDeadline(boost::system::error_code ec); void OnDeadline(boost::system::error_code ec);
// Terminate all the actors to shut down the connection.
void DoStop();
tcp::resolver resolver_; tcp::resolver resolver_;
tcp::socket socket_; tcp::socket socket_;
@ -67,7 +71,7 @@ class HttpAsyncClient : public std::enable_shared_from_this<HttpAsyncClient> {
HttpResponsePtr response_; HttpResponsePtr response_;
std::unique_ptr<HttpResponseParser> response_parser_; std::unique_ptr<HttpResponseParser> response_parser_;
HttpResponseHandler response_handler_; HttpResponseCallback response_callback_;
// Timer for the timeout control. // Timer for the timeout control.
boost::asio::deadline_timer deadline_; boost::asio::deadline_timer deadline_;
@ -76,7 +80,11 @@ class HttpAsyncClient : public std::enable_shared_from_this<HttpAsyncClient> {
// Only for receiving response from server. // Only for receiving response from server.
int timeout_seconds_; int timeout_seconds_;
// Request stopped due to timeout or socket error.
bool stopped_; bool stopped_;
// If the error was caused by timeout or not.
// Will be passed to the response handler/callback.
bool timed_out_; bool timed_out_;
}; };

@ -14,9 +14,9 @@ using boost::asio::ip::tcp;
namespace webcc { namespace webcc {
HttpClient::HttpClient() HttpClient::HttpClient(std::size_t buffer_size)
: socket_(io_context_), : socket_(io_context_),
buffer_(kBufferSize), buffer_(buffer_size == 0 ? kBufferSize : buffer_size),
deadline_(io_context_), deadline_(io_context_),
timeout_seconds_(kMaxReadSeconds), timeout_seconds_(kMaxReadSeconds),
stopped_(false), stopped_(false),
@ -30,7 +30,7 @@ void HttpClient::SetTimeout(int seconds) {
} }
} }
bool HttpClient::Request(const HttpRequest& request) { bool HttpClient::Request(const HttpRequest& request, std::size_t buffer_size) {
io_context_.restart(); io_context_.restart();
response_.reset(new HttpResponse()); response_.reset(new HttpResponse());
@ -40,6 +40,8 @@ bool HttpClient::Request(const HttpRequest& request) {
timed_out_ = false; timed_out_ = false;
error_ = kNoError; error_ = kNoError;
BufferResizer buffer_resizer(&buffer_, buffer_size);
if ((error_ = Connect(request)) != kNoError) { if ((error_ = Connect(request)) != kNoError) {
return false; return false;
} }
@ -147,8 +149,6 @@ void HttpClient::DoReadResponse(Error* error) {
LOG_INFO("Read data, length: %u.", length); LOG_INFO("Read data, length: %u.", length);
bool content_length_parsed = response_parser_->content_length_parsed();
// Parse the response piece just read. // Parse the response piece just read.
if (!response_parser_->Parse(buffer_.data(), length)) { if (!response_parser_->Parse(buffer_.data(), length)) {
Stop(); Stop();
@ -157,17 +157,14 @@ void HttpClient::DoReadResponse(Error* error) {
return; return;
} }
if (!content_length_parsed &&
response_parser_->content_length_parsed()) {
// Content length just has been parsed.
AdjustBufferSize(response_parser_->content_length(), &buffer_);
}
if (response_parser_->finished()) { if (response_parser_->finished()) {
// Stop trying to read once all content has been received, // Stop trying to read once all content has been received, because
// because some servers will block extra call to read_some(). // some servers will block extra call to read_some().
Stop(); Stop();
LOG_INFO("Finished to read and parse HTTP response."); LOG_INFO("Finished to read and parse HTTP response.");
LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str());
return; return;
} }
@ -195,10 +192,10 @@ void HttpClient::OnDeadline(boost::system::error_code ec) {
LOG_VERB("OnDeadline."); LOG_VERB("OnDeadline.");
// NOTE: Can't check this: // NOTE: Can't check this:
// if (ec == boost::asio::error::operation_aborted) { // if (ec == boost::asio::error::operation_aborted) {
// LOG_VERB("Deadline timer canceled."); // LOG_VERB("Deadline timer canceled.");
// return; // return;
// } // }
if (deadline_.expires_at() <= if (deadline_.expires_at() <=
boost::asio::deadline_timer::traits_type::now()) { boost::asio::deadline_timer::traits_type::now()) {

@ -21,7 +21,10 @@ namespace webcc {
// Don't use the same HttpClient object in multiple threads. // Don't use the same HttpClient object in multiple threads.
class HttpClient { class HttpClient {
public: public:
HttpClient(); // The |buffer_size| is the bytes of the buffer for reading response.
// 0 means default value (e.g., 1024) will be used.
explicit HttpClient(std::size_t buffer_size = 0);
~HttpClient() = default; ~HttpClient() = default;
WEBCC_DELETE_COPY_ASSIGN(HttpClient); WEBCC_DELETE_COPY_ASSIGN(HttpClient);
@ -31,7 +34,9 @@ class HttpClient {
void SetTimeout(int seconds); void SetTimeout(int seconds);
// Connect to server, send request, wait until response is received. // Connect to server, send request, wait until response is received.
bool Request(const HttpRequest& request); // Set |buffer_size| to non-zero to use a different buffer size for this
// specific request.
bool Request(const HttpRequest& request, std::size_t buffer_size = 0);
HttpResponsePtr response() const { return response_; } HttpResponsePtr response() const { return response_; }
@ -54,7 +59,6 @@ class HttpClient {
void Stop(); void Stop();
boost::asio::io_context io_context_; boost::asio::io_context io_context_;
boost::asio::ip::tcp::socket socket_; boost::asio::ip::tcp::socket socket_;
std::vector<char> buffer_; std::vector<char> buffer_;
@ -62,12 +66,14 @@ class HttpClient {
HttpResponsePtr response_; HttpResponsePtr response_;
std::unique_ptr<HttpResponseParser> response_parser_; std::unique_ptr<HttpResponseParser> response_parser_;
// Timer for the timeout control.
boost::asio::deadline_timer deadline_; boost::asio::deadline_timer deadline_;
// Maximum seconds to wait before the client cancels the operation. // Maximum seconds to wait before the client cancels the operation.
// Only for reading response from server. // Only for reading response from server.
int timeout_seconds_; int timeout_seconds_;
// Request stopped due to timeout or socket error.
bool stopped_; bool stopped_;
// If the error was caused by timeout or not. // If the error was caused by timeout or not.

@ -37,6 +37,27 @@ void HttpMessage::SetHeader(std::string&& name, std::string&& value) {
headers_.push_back({ std::move(name), std::move(value) }); headers_.push_back({ std::move(name), 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"
// 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);
}
void HttpMessage::SetContent(std::string&& content, bool set_length) {
content_ = std::move(content);
if (set_length) {
SetContentLength(content_.size());
}
}
// ATTENTION: The buffers don't hold the memory! // ATTENTION: The buffers don't hold the memory!
std::vector<boost::asio::const_buffer> HttpMessage::ToBuffers() const { std::vector<boost::asio::const_buffer> HttpMessage::ToBuffers() const {
assert(!start_line_.empty()); assert(!start_line_.empty());

@ -39,16 +39,10 @@ class HttpMessage {
void SetHeader(std::string&& name, std::string&& value); void SetHeader(std::string&& name, std::string&& value);
// E.g., "application/json; charset=utf-8" // E.g., "application/json; charset=utf-8"
void SetContentType(const std::string& content_type) { void SetContentType(const std::string& media_type,
SetHeader(kContentType, content_type); const std::string& charset);
}
void SetContent(std::string&& content, bool set_length) { void SetContent(std::string&& content, bool set_length);
content_ = std::move(content);
if (set_length) {
SetContentLength(content_.size());
}
}
// Make the message (e.g., update start line). // Make the message (e.g., update start line).
// Must be called before ToBuffers()! // Must be called before ToBuffers()!
@ -70,7 +64,7 @@ class HttpMessage {
protected: protected:
void SetContentLength(std::size_t content_length) { void SetContentLength(std::size_t content_length) {
content_length_ = content_length; content_length_ = content_length;
SetHeader(kContentLength, std::to_string(content_length)); SetHeader(http::headers::kContentLength, std::to_string(content_length));
} }
// Start line with trailing CRLF. // Start line with trailing CRLF.

@ -132,7 +132,7 @@ bool HttpParser::ParseHeaderLine(const std::string& line) {
do { do {
if (!chunked_ && !content_length_parsed_) { if (!chunked_ && !content_length_parsed_) {
if (boost::iequals(name, kContentLength)) { if (boost::iequals(name, http::headers::kContentLength)) {
content_length_parsed_ = true; content_length_parsed_ = true;
if (!StringToSizeT(value, 10, &content_length_)) { if (!StringToSizeT(value, 10, &content_length_)) {
@ -156,7 +156,7 @@ bool HttpParser::ParseHeaderLine(const std::string& line) {
// TODO: Replace `!chunked_` with <TransferEncodingParsed>. // TODO: Replace `!chunked_` with <TransferEncodingParsed>.
if (!chunked_ && !content_length_parsed_) { if (!chunked_ && !content_length_parsed_) {
if (boost::iequals(name, kTransferEncoding)) { if (boost::iequals(name, http::headers::kTransferEncoding)) {
if (value == "chunked") { if (value == "chunked") {
// The content is chunked. // The content is chunked.
chunked_ = true; chunked_ = true;

@ -20,7 +20,6 @@ class HttpParser {
bool finished() const { return finished_; } bool finished() const { return finished_; }
bool content_length_parsed() const { return content_length_parsed_; }
std::size_t content_length() const { return content_length_; } std::size_t content_length() const { return content_length_; }
bool Parse(const char* data, std::size_t length); bool Parse(const char* data, std::size_t length);

@ -2,6 +2,13 @@
namespace webcc { 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) {
}
void HttpRequest::Make() { void HttpRequest::Make() {
start_line_ = method_; start_line_ = method_;
start_line_ += " "; start_line_ += " ";
@ -10,13 +17,19 @@ void HttpRequest::Make() {
start_line_ += CRLF; start_line_ += CRLF;
if (port_.empty()) { if (port_.empty()) {
SetHeader(kHost, host_); SetHeader(http::headers::kHost, host_);
} else { } else {
SetHeader(kHost, host_ + ":" + port_); SetHeader(http::headers::kHost, host_ + ":" + 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. // NOTE: C++11 requires a space between literal and string macro.
SetHeader(kUserAgent, "Webcc/" WEBCC_VERSION); SetHeader(http::headers::kUserAgent, "Webcc/" WEBCC_VERSION);
} }
} // namespace webcc } // namespace webcc

@ -8,16 +8,26 @@
namespace webcc { namespace webcc {
class HttpRequestParser;
class HttpRequest : public HttpMessage { class HttpRequest : public HttpMessage {
public: public:
HttpRequest() = default; HttpRequest() = default;
// The |host| is a descriptive name (e.g., www.google.com) or a numeric IP
// address (127.0.0.1).
// The |port| is a numeric number (e.g., 9000). The default value (80 for HTTP
// or 443 for HTTPS) will be used to connect to server if it's empty.
HttpRequest(const std::string& method,
const std::string& url,
const std::string& host,
const std::string& port = "");
~HttpRequest() override = default; ~HttpRequest() override = default;
const std::string& method() const { return method_; } const std::string& method() const { return method_; }
void set_method(const std::string& method) { method_ = method; }
const std::string& url() const { return url_; } const std::string& url() const { return url_; }
void set_url(const std::string& url) { url_ = url; }
const std::string& host() const { return host_; } const std::string& host() const { return host_; }
const std::string& port() const { return port_; } const std::string& port() const { return port_; }
@ -26,20 +36,15 @@ class HttpRequest : public HttpMessage {
return port_.empty() ? default_port : port_; return port_.empty() ? default_port : port_;
} }
// Set host name and port number.
// 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.
void set_host(const std::string& host, const std::string& port = "") {
host_ = host;
port_ = port;
}
// Compose start line, set Host header, etc. // Compose start line, set Host header, etc.
void Make() override; void Make() override;
private: private:
friend class HttpRequestParser;
void set_method(const std::string& method) { method_ = method; }
void set_url(const std::string& url) { url_ = url; }
// HTTP method. // HTTP method.
std::string method_; std::string method_;

@ -20,34 +20,34 @@ const std::string SERVICE_UNAVAILABLE = "HTTP/1.1 503 Service Unavailable\r\n";
const std::string& ToString(int status) { const std::string& ToString(int status) {
switch (status) { switch (status) {
case HttpStatus::kOK: case http::Status::kOK:
return OK; return OK;
case HttpStatus::kCreated: case http::Status::kCreated:
return CREATED; return CREATED;
case HttpStatus::kAccepted: case http::Status::kAccepted:
return ACCEPTED; return ACCEPTED;
case HttpStatus::kNoContent: case http::Status::kNoContent:
return NO_CONTENT; return NO_CONTENT;
case HttpStatus::kNotModified: case http::Status::kNotModified:
return NOT_MODIFIED; return NOT_MODIFIED;
case HttpStatus::kBadRequest: case http::Status::kBadRequest:
return BAD_REQUEST; return BAD_REQUEST;
case HttpStatus::kNotFound: case http::Status::kNotFound:
return NOT_FOUND; return NOT_FOUND;
case HttpStatus::kInternalServerError: case http::Status::kInternalServerError:
return INTERNAL_SERVER_ERROR; return INTERNAL_SERVER_ERROR;
case HttpStatus::kNotImplemented: case http::Status::kNotImplemented:
return NOT_IMPLEMENTED; return NOT_IMPLEMENTED;
case HttpStatus::kServiceUnavailable: case http::Status::kServiceUnavailable:
return SERVICE_UNAVAILABLE; return SERVICE_UNAVAILABLE;
default: default:
@ -66,8 +66,8 @@ void HttpResponse::Make() {
SetHeader("Date", GetHttpDateTimestamp()); SetHeader("Date", GetHttpDateTimestamp());
} }
HttpResponse HttpResponse::Fault(HttpStatus::Enum status) { HttpResponse HttpResponse::Fault(http::Status status) {
assert(status != HttpStatus::kOK); assert(status != http::Status::kOK);
HttpResponse response; HttpResponse response;
response.set_status(status); response.set_status(status);

@ -10,7 +10,7 @@ namespace webcc {
class HttpResponse : public HttpMessage { class HttpResponse : public HttpMessage {
public: public:
HttpResponse() : status_(HttpStatus::kOK) {} HttpResponse() : status_(http::Status::kOK) {}
~HttpResponse() override = default; ~HttpResponse() override = default;
@ -23,7 +23,7 @@ class HttpResponse : public HttpMessage {
// Get a fault response when HTTP status is not OK. // Get a fault response when HTTP status is not OK.
// TODO: Avoid copy. // TODO: Avoid copy.
static HttpResponse Fault(HttpStatus::Enum status); static HttpResponse Fault(http::Status status);
private: private:
int status_; int status_;

@ -33,12 +33,13 @@ void HttpSession::Close() {
} }
void HttpSession::SetResponseContent(std::string&& content, void HttpSession::SetResponseContent(std::string&& content,
const std::string& type) { const std::string& media_type,
const std::string& charset) {
response_.SetContent(std::move(content), true); response_.SetContent(std::move(content), true);
response_.SetContentType(type); response_.SetContentType(media_type, charset);
} }
void HttpSession::SendResponse(HttpStatus::Enum status) { void HttpSession::SendResponse(http::Status status) {
response_.set_status(status); response_.set_status(status);
response_.Make(); response_.Make();
DoWrite(); DoWrite();
@ -63,7 +64,7 @@ void HttpSession::OnRead(boost::system::error_code ec, std::size_t length) {
if (!request_parser_.Parse(buffer_.data(), length)) { if (!request_parser_.Parse(buffer_.data(), length)) {
// Bad request. // Bad request.
LOG_ERRO("Failed to parse HTTP request."); LOG_ERRO("Failed to parse HTTP request.");
response_ = HttpResponse::Fault(HttpStatus::kBadRequest); response_ = HttpResponse::Fault(http::Status::kBadRequest);
DoWrite(); DoWrite();
return; return;
} }

@ -33,10 +33,12 @@ class HttpSession : public std::enable_shared_from_this<HttpSession> {
// Close the socket. // Close the socket.
void Close(); void Close();
void SetResponseContent(std::string&& content, const std::string& type); void SetResponseContent(std::string&& content,
const std::string& media_type,
const std::string& charset);
// Send response to client with the given status. // Send response to client with the given status.
void SendResponse(HttpStatus::Enum status); void SendResponse(http::Status status);
private: private:
void DoRead(); void DoRead();

@ -15,10 +15,10 @@ namespace ssl = boost::asio::ssl;
namespace webcc { namespace webcc {
HttpSslClient::HttpSslClient(bool ssl_verify) HttpSslClient::HttpSslClient(std::size_t buffer_size, bool ssl_verify)
: ssl_context_(ssl::context::sslv23), : ssl_context_(ssl::context::sslv23),
ssl_socket_(io_context_, ssl_context_), ssl_socket_(io_context_, ssl_context_),
buffer_(kBufferSize), buffer_(buffer_size == 0 ? kBufferSize : buffer_size),
deadline_(io_context_), deadline_(io_context_),
ssl_verify_(ssl_verify), ssl_verify_(ssl_verify),
timeout_seconds_(kMaxReadSeconds), timeout_seconds_(kMaxReadSeconds),
@ -35,7 +35,8 @@ void HttpSslClient::SetTimeout(int seconds) {
} }
} }
bool HttpSslClient::Request(const HttpRequest& request) { bool HttpSslClient::Request(const HttpRequest& request,
std::size_t buffer_size) {
io_context_.restart(); io_context_.restart();
response_.reset(new HttpResponse()); response_.reset(new HttpResponse());
@ -45,6 +46,8 @@ bool HttpSslClient::Request(const HttpRequest& request) {
timed_out_ = false; timed_out_ = false;
error_ = kNoError; error_ = kNoError;
BufferResizer buffer_resizer(&buffer_, buffer_size);
if ((error_ = Connect(request)) != kNoError) { if ((error_ = Connect(request)) != kNoError) {
return false; return false;
} }
@ -178,8 +181,6 @@ void HttpSslClient::DoReadResponse(Error* error) {
LOG_INFO("Read data, length: %u.", length); LOG_INFO("Read data, length: %u.", length);
bool content_length_parsed = response_parser_->content_length_parsed();
// Parse the response piece just read. // Parse the response piece just read.
if (!response_parser_->Parse(buffer_.data(), length)) { if (!response_parser_->Parse(buffer_.data(), length)) {
Stop(); Stop();
@ -188,12 +189,6 @@ void HttpSslClient::DoReadResponse(Error* error) {
return; return;
} }
if (!content_length_parsed &&
response_parser_->content_length_parsed()) {
// Content length just has been parsed.
AdjustBufferSize(response_parser_->content_length(), &buffer_);
}
if (response_parser_->finished()) { if (response_parser_->finished()) {
// Stop trying to read once all content has been received, // Stop trying to read once all content has been received,
// because some servers will block extra call to read_some(). // because some servers will block extra call to read_some().

@ -23,11 +23,12 @@ namespace webcc {
// Don't use the same HttpClient object in multiple threads. // Don't use the same HttpClient object in multiple threads.
class HttpSslClient { class HttpSslClient {
public: public:
// NOTE: // The |buffer_size| is the bytes of the buffer for reading response.
// SSL verification (ssl_verify=true) needs CA certificates to be found // 0 means default value (e.g., 1024) will be used.
// SSL verification (|ssl_verify|) needs CA certificates to be found
// in the default verify paths of OpenSSL. On Windows, it means you need to // in the default verify paths of OpenSSL. On Windows, it means you need to
// set environment variable SSL_CERT_FILE properly. // set environment variable SSL_CERT_FILE properly.
HttpSslClient(bool ssl_verify = true); explicit HttpSslClient(std::size_t buffer_size = 0, bool ssl_verify = true);
~HttpSslClient() = default; ~HttpSslClient() = default;
@ -38,7 +39,9 @@ class HttpSslClient {
void SetTimeout(int seconds); void SetTimeout(int seconds);
// Connect to server, send request, wait until response is received. // Connect to server, send request, wait until response is received.
bool Request(const HttpRequest& request); // Set |buffer_size| to non-zero to use a different buffer size for this
// specific request.
bool Request(const HttpRequest& request, std::size_t buffer_size = 0);
HttpResponsePtr response() const { return response_; } HttpResponsePtr response() const { return response_; }

@ -4,34 +4,37 @@ namespace webcc {
RestAsyncClient::RestAsyncClient(boost::asio::io_context& io_context, RestAsyncClient::RestAsyncClient(boost::asio::io_context& io_context,
const std::string& host, const std::string& host,
const std::string& port) const std::string& port,
: io_context_(io_context), host_(host), port_(port), timeout_seconds_(0) { std::size_t buffer_size)
: io_context_(io_context),
host_(host), port_(port),
timeout_seconds_(0),
buffer_size_(buffer_size) {
} }
void RestAsyncClient::Request(const std::string& method, void RestAsyncClient::Request(const std::string& method,
const std::string& url, const std::string& url,
std::string&& content, std::string&& content,
HttpResponseHandler response_handler) { HttpResponseCallback callback) {
HttpRequestPtr request(new webcc::HttpRequest()); HttpRequestPtr http_request(new HttpRequest(method, url, host_, port_));
request->set_method(method);
request->set_url(url);
request->set_host(host_, port_);
if (!content.empty()) { if (!content.empty()) {
request->SetContent(std::move(content), true); http_request->SetContent(std::move(content), true);
request->SetContentType(kAppJsonUtf8); http_request->SetContentType(http::media_types::kApplicationJson,
http::charsets::kUtf8);
} }
request->Make(); http_request->Make();
HttpAsyncClientPtr http_client(new HttpAsyncClient(io_context_)); HttpAsyncClientPtr http_async_client{
new HttpAsyncClient(io_context_, buffer_size_)
};
if (timeout_seconds_ > 0) { if (timeout_seconds_ > 0) {
http_client->set_timeout_seconds(timeout_seconds_); http_async_client->SetTimeout(timeout_seconds_);
} }
http_client->Request(request, response_handler); http_async_client->Request(http_request, callback);
} }
} // namespace webcc } // namespace webcc

@ -10,41 +10,40 @@ namespace webcc {
class RestAsyncClient { class RestAsyncClient {
public: public:
RestAsyncClient(boost::asio::io_context& io_context, // NOLINT RestAsyncClient(boost::asio::io_context& io_context,
const std::string& host, const std::string& port); const std::string& host, const std::string& port,
std::size_t buffer_size = 0);
void set_timeout_seconds(int timeout_seconds) { void SetTimeout(int seconds) {
timeout_seconds_ = timeout_seconds; timeout_seconds_ = seconds;
} }
void Get(const std::string& url, void Get(const std::string& url, HttpResponseCallback callback) {
HttpResponseHandler response_handler) { Request(kHttpGet, url, "", callback);
Request(kHttpGet, url, "", response_handler);
} }
void Post(const std::string& url, std::string&& content, void Post(const std::string& url, std::string&& content,
HttpResponseHandler response_handler) { HttpResponseCallback callback) {
Request(kHttpPost, url, std::move(content), response_handler); Request(kHttpPost, url, std::move(content), callback);
} }
void Put(const std::string& url, std::string&& content, void Put(const std::string& url, std::string&& content,
HttpResponseHandler response_handler) { HttpResponseCallback callback) {
Request(kHttpPut, url, std::move(content), response_handler); Request(kHttpPut, url, std::move(content), callback);
} }
void Patch(const std::string& url, std::string&& content, void Patch(const std::string& url, std::string&& content,
HttpResponseHandler response_handler) { HttpResponseCallback callback) {
Request(kHttpPatch, url, std::move(content), response_handler); Request(kHttpPatch, url, std::move(content), callback);
} }
void Delete(const std::string& url, void Delete(const std::string& url, HttpResponseCallback callback) {
HttpResponseHandler response_handler) { Request(kHttpDelete, url, "", callback);
Request(kHttpDelete, url, "", response_handler);
} }
private: private:
void Request(const std::string& method, const std::string& url, void Request(const std::string& method, const std::string& url,
std::string&& content, HttpResponseHandler response_handler); std::string&& content, HttpResponseCallback callback);
boost::asio::io_context& io_context_; boost::asio::io_context& io_context_;
@ -53,6 +52,8 @@ class RestAsyncClient {
// Timeout in seconds; only effective when > 0. // Timeout in seconds; only effective when > 0.
int timeout_seconds_; int timeout_seconds_;
std::size_t buffer_size_;
}; };
} // namespace webcc } // namespace webcc

@ -1,34 +1,30 @@
#include "webcc/rest_client.h" #include "webcc/rest_client.h"
#include "webcc/utility.h"
namespace webcc { namespace webcc {
RestClient::RestClient(const std::string& host, const std::string& port) RestClient::RestClient(const std::string& host, const std::string& port,
: host_(host), port_(port) { std::size_t buffer_size)
if (port_.empty()) { : host_(host), port_(port), http_client_(buffer_size) {
std::size_t i = host_.find_last_of(':'); AdjustHostPort(host_, port_);
if (i != std::string::npos) {
port_ = host_.substr(i + 1);
host_ = host_.substr(0, i);
}
}
} }
bool RestClient::Request(const std::string& method, const std::string& url, bool RestClient::Request(const std::string& method, const std::string& url,
std::string&& content) { std::string&& content, std::size_t buffer_size) {
HttpRequest http_request; HttpRequest http_request(method, url, host_, port_);
http_request.set_method(method); http_request.SetHeader(http::headers::kAccept,
http_request.set_url(url); http::media_types::kApplicationJson);
http_request.set_host(host_, port_);
if (!content.empty()) { if (!content.empty()) {
http_request.SetContent(std::move(content), true); http_request.SetContent(std::move(content), true);
http_request.SetContentType(kAppJsonUtf8); http_request.SetContentType(http::media_types::kApplicationJson,
http::charsets::kUtf8);
} }
http_request.Make(); http_request.Make();
if (!http_client_.Request(http_request)) { if (!http_client_.Request(http_request, buffer_size)) {
return false; return false;
} }

@ -17,7 +17,8 @@ class RestClient {
// If |port| is empty, |host| will be checked to see if it contains port or // If |port| is empty, |host| will be checked to see if it contains port or
// not (separated by ':'). // not (separated by ':').
explicit RestClient(const std::string& host, explicit RestClient(const std::string& host,
const std::string& port = ""); const std::string& port = "",
std::size_t buffer_size = 0);
~RestClient() = default; ~RestClient() = default;
@ -33,24 +34,27 @@ class RestClient {
// timed_out() for more information if it's failed. Check response_status() // timed_out() for more information if it's failed. Check response_status()
// instead for the HTTP status code. // instead for the HTTP status code.
inline bool Get(const std::string& url) { inline bool Get(const std::string& url, std::size_t buffer_size = 0) {
return Request(kHttpGet, url, ""); return Request(kHttpGet, url, "", buffer_size);
} }
inline bool Post(const std::string& url, std::string&& content) { inline bool Post(const std::string& url, std::string&& content,
return Request(kHttpPost, url, std::move(content)); std::size_t buffer_size = 0) {
return Request(kHttpPost, url, std::move(content), buffer_size);
} }
inline bool Put(const std::string& url, std::string&& content) { inline bool Put(const std::string& url, std::string&& content,
return Request(kHttpPut, url, std::move(content)); std::size_t buffer_size = 0) {
return Request(kHttpPut, url, std::move(content), buffer_size);
} }
inline bool Patch(const std::string& url, std::string&& content) { inline bool Patch(const std::string& url, std::string&& content,
return Request(kHttpPatch, url, std::move(content)); std::size_t buffer_size = 0) {
return Request(kHttpPatch, url, std::move(content), buffer_size);
} }
inline bool Delete(const std::string& url) { inline bool Delete(const std::string& url, std::size_t buffer_size = 0) {
return Request(kHttpDelete, url, ""); return Request(kHttpDelete, url, "", buffer_size);
} }
HttpResponsePtr response() const { HttpResponsePtr response() const {
@ -76,8 +80,8 @@ class RestClient {
} }
private: private:
bool Request(const std::string& method, const std::string& url, bool Request(const std::string& method, const std::string& url,
std::string&& content); std::string&& content, std::size_t buffer_size);
std::string host_; std::string host_;
std::string port_; std::string port_;

@ -19,7 +19,7 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) {
Url url(http_request.url(), /*decode*/true); Url url(http_request.url(), /*decode*/true);
if (!url.IsPathValid()) { if (!url.IsPathValid()) {
session->SendResponse(HttpStatus::kBadRequest); session->SendResponse(http::Status::kBadRequest);
return; return;
} }
@ -33,7 +33,7 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) {
if (!service) { if (!service) {
LOG_WARN("No service matches the URL path: %s", url.path().c_str()); LOG_WARN("No service matches the URL path: %s", url.path().c_str());
session->SendResponse(HttpStatus::kNotFound); session->SendResponse(http::Status::kNotFound);
return; return;
} }
@ -42,7 +42,8 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) {
if (!rest_response.content.empty()) { if (!rest_response.content.empty()) {
session->SetResponseContent(std::move(rest_response.content), session->SetResponseContent(std::move(rest_response.content),
kAppJsonUtf8); http::media_types::kApplicationJson,
http::charsets::kUtf8);
} }
// Send response back to client. // Send response back to client.

@ -38,7 +38,7 @@ struct RestRequest {
}; };
struct RestResponse { struct RestResponse {
HttpStatus::Enum status; http::Status status;
std::string content; std::string content;
}; };

@ -1,35 +1,28 @@
#include "webcc/rest_ssl_client.h" #include "webcc/rest_ssl_client.h"
#include "webcc/utility.h"
namespace webcc { namespace webcc {
RestSslClient::RestSslClient(const std::string& host, const std::string& port, RestSslClient::RestSslClient(const std::string& host, const std::string& port,
bool ssl_verify) std::size_t buffer_size, bool ssl_verify)
: host_(host), port_(port), http_client_(ssl_verify) { : host_(host), port_(port),
if (port_.empty()) { http_client_(buffer_size, ssl_verify) {
std::size_t i = host_.find_last_of(':'); AdjustHostPort(host_, port_);
if (i != std::string::npos) {
port_ = host_.substr(i + 1);
host_ = host_.substr(0, i);
}
}
} }
bool RestSslClient::Request(const std::string& method, const std::string& url, bool RestSslClient::Request(const std::string& method, const std::string& url,
std::string&& content) { std::string&& content, std::size_t buffer_size) {
HttpRequest http_request; HttpRequest http_request(method, url, host_, port_);
http_request.set_method(method);
http_request.set_url(url);
http_request.set_host(host_, port_);
if (!content.empty()) { if (!content.empty()) {
http_request.SetContent(std::move(content), true); http_request.SetContent(std::move(content), true);
http_request.SetContentType(kAppJsonUtf8); http_request.SetContentType(http::media_types::kApplicationJson,
http::charsets::kUtf8);
} }
http_request.Make(); http_request.Make();
if (!http_client_.Request(http_request)) { if (!http_client_.Request(http_request, buffer_size)) {
return false; return false;
} }

@ -18,6 +18,7 @@ class RestSslClient {
// not (separated by ':'). // not (separated by ':').
explicit RestSslClient(const std::string& host, explicit RestSslClient(const std::string& host,
const std::string& port = "", const std::string& port = "",
std::size_t buffer_size = 0,
bool ssl_verify = true); bool ssl_verify = true);
~RestSslClient() = default; ~RestSslClient() = default;
@ -34,24 +35,27 @@ class RestSslClient {
// timed_out() for more information if it's failed. Check response_status() // timed_out() for more information if it's failed. Check response_status()
// instead for the HTTP status code. // instead for the HTTP status code.
inline bool Get(const std::string& url) { inline bool Get(const std::string& url, std::size_t buffer_size = 0) {
return Request(kHttpGet, url, ""); return Request(kHttpGet, url, "", buffer_size);
} }
inline bool Post(const std::string& url, std::string&& content) { inline bool Post(const std::string& url, std::string&& content,
return Request(kHttpPost, url, std::move(content)); std::size_t buffer_size = 0) {
return Request(kHttpPost, url, std::move(content), buffer_size);
} }
inline bool Put(const std::string& url, std::string&& content) { inline bool Put(const std::string& url, std::string&& content,
return Request(kHttpPut, url, std::move(content)); std::size_t buffer_size = 0) {
return Request(kHttpPut, url, std::move(content), buffer_size);
} }
inline bool Patch(const std::string& url, std::string&& content) { inline bool Patch(const std::string& url, std::string&& content,
return Request(kHttpPatch, url, std::move(content)); std::size_t buffer_size = 0) {
return Request(kHttpPatch, url, std::move(content), buffer_size);
} }
inline bool Delete(const std::string& url) { inline bool Delete(const std::string& url, std::size_t buffer_size = 0) {
return Request(kHttpDelete, url, ""); return Request(kHttpDelete, url, "", buffer_size);
} }
HttpResponsePtr response() const { HttpResponsePtr response() const {
@ -77,9 +81,8 @@ class RestSslClient {
} }
private: private:
bool Request(const std::string& method, bool Request(const std::string& method, const std::string& url,
const std::string& url, std::string&& content, std::size_t buffer_size);
std::string&& content);
std::string host_; std::string host_;
std::string port_; std::string port_;

@ -3,33 +3,29 @@
#include <cassert> #include <cassert>
#include <utility> // for move() #include <utility> // for move()
#include "webcc/http_async_client.h"
#include "webcc/soap_globals.h" #include "webcc/soap_globals.h"
#include "webcc/soap_request.h" #include "webcc/soap_request.h"
#include "webcc/soap_response.h" #include "webcc/soap_response.h"
#include "webcc/utility.h"
namespace webcc { namespace webcc {
SoapAsyncClient::SoapAsyncClient(boost::asio::io_context& io_context, SoapAsyncClient::SoapAsyncClient(boost::asio::io_context& io_context,
const std::string& host, const std::string& host,
const std::string& port, const std::string& port,
SoapVersion soap_version) SoapVersion soap_version,
std::size_t buffer_size)
: io_context_(io_context), : io_context_(io_context),
host_(host), port_(port), host_(host), port_(port),
soap_version_(soap_version), soap_version_(soap_version),
buffer_size_(buffer_size),
format_raw_(true), timeout_seconds_(0) { format_raw_(true), timeout_seconds_(0) {
if (port_.empty()) { AdjustHostPort(host_, port_);
std::size_t i = host_.find_last_of(':');
if (i != std::string::npos) {
port_ = host_.substr(i + 1);
host_ = host_.substr(0, i);
}
}
} }
void SoapAsyncClient::Request(const std::string& operation, void SoapAsyncClient::Request(const std::string& operation,
std::vector<SoapParameter>&& parameters, std::vector<SoapParameter>&& parameters,
SoapResponseHandler soap_response_handler) { SoapResponseCallback soap_response_callback) {
assert(service_ns_.IsValid()); assert(service_ns_.IsValid());
assert(!url_.empty() && !host_.empty()); assert(!url_.empty() && !host_.empty());
assert(!result_name_.empty()); assert(!result_name_.empty());
@ -54,50 +50,53 @@ void SoapAsyncClient::Request(const std::string& operation,
std::string http_content; std::string http_content;
soap_request.ToXml(format_raw_, indent_str_, &http_content); soap_request.ToXml(format_raw_, indent_str_, &http_content);
HttpRequestPtr http_request; HttpRequestPtr http_request(new HttpRequest(kHttpPost, url_, host_, port_));
http_request->set_method(kHttpPost);
http_request->set_url(url_);
http_request->SetContent(std::move(http_content), true); http_request->SetContent(std::move(http_content), true);
if (soap_version_ == kSoapV11) { if (soap_version_ == kSoapV11) {
http_request->SetContentType(kTextXmlUtf8); http_request->SetContentType(http::media_types::kTextXml,
http::charsets::kUtf8);
} else { } else {
http_request->SetContentType(kAppSoapXmlUtf8); http_request->SetContentType(http::media_types::kApplicationSoapXml,
http::charsets::kUtf8);
} }
http_request->set_host(host_, port_);
http_request->SetHeader(kSoapAction, operation); http_request->SetHeader(kSoapAction, operation);
http_request->Make(); http_request->Make();
HttpAsyncClientPtr http_client(new HttpAsyncClient(io_context_)); HttpAsyncClientPtr http_async_client{
new HttpAsyncClient(io_context_, buffer_size_)
};
if (timeout_seconds_ > 0) { if (timeout_seconds_ > 0) {
http_client->set_timeout_seconds(timeout_seconds_); http_async_client->SetTimeout(timeout_seconds_);
} }
auto http_response_handler = std::bind(&SoapAsyncClient::ResponseHandler, auto http_response_callback = std::bind(&SoapAsyncClient::OnHttpResponse,
this, soap_response_handler, this, soap_response_callback,
std::placeholders::_1, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_2,
std::placeholders::_3); std::placeholders::_3);
http_client->Request(http_request, http_response_handler); http_async_client->Request(http_request, http_response_callback);
} }
void SoapAsyncClient::ResponseHandler(SoapResponseHandler soap_response_handler, void SoapAsyncClient::OnHttpResponse(SoapResponseCallback soap_response_callback,
HttpResponsePtr http_response, HttpResponsePtr http_response,
Error error, bool timed_out) { Error error, bool timed_out) {
if (error != kNoError) { if (error != kNoError) {
soap_response_handler("", error, timed_out); soap_response_callback("", error, timed_out);
} else { } else {
SoapResponse soap_response; SoapResponse soap_response;
// TODO
//soap_response.set_result_name(result_name_); //soap_response.set_result_name(result_name_);
if (!soap_response.FromXml(http_response->content())) { if (!soap_response.FromXml(http_response->content())) {
soap_response_handler("", kXmlError, false); soap_response_callback("", kXmlError, false);
} else { } else {
//soap_response_handler(soap_response.result_moved(), kNoError, false); // TODO
//soap_response_callback(soap_response.result_moved(), kNoError, false);
} }
} }
} }

@ -11,8 +11,8 @@
namespace webcc { namespace webcc {
// Response handler/callback. // Response callback.
typedef std::function<void(std::string, Error, bool)> SoapResponseHandler; typedef std::function<void(std::string, Error, bool)> SoapResponseCallback;
class SoapAsyncClient { class SoapAsyncClient {
public: public:
@ -20,7 +20,8 @@ class SoapAsyncClient {
// not (separated by ':'). // not (separated by ':').
SoapAsyncClient(boost::asio::io_context& io_context, // NOLINT SoapAsyncClient(boost::asio::io_context& io_context, // NOLINT
const std::string& host, const std::string& port = "", const std::string& host, const std::string& port = "",
SoapVersion soap_version = kSoapV12); SoapVersion soap_version = kSoapV12,
std::size_t buffer_size = 0);
~SoapAsyncClient() = default; ~SoapAsyncClient() = default;
@ -48,12 +49,12 @@ class SoapAsyncClient {
void Request(const std::string& operation, void Request(const std::string& operation,
std::vector<SoapParameter>&& parameters, std::vector<SoapParameter>&& parameters,
SoapResponseHandler soap_response_handler); SoapResponseCallback soap_response_callback);
private: private:
void ResponseHandler(SoapResponseHandler soap_response_handler, void OnHttpResponse(SoapResponseCallback soap_response_callback,
HttpResponsePtr http_response, HttpResponsePtr http_response,
Error error, bool timed_out); Error error, bool timed_out);
boost::asio::io_context& io_context_; boost::asio::io_context& io_context_;
@ -62,6 +63,8 @@ class SoapAsyncClient {
SoapVersion soap_version_; SoapVersion soap_version_;
std::size_t buffer_size_;
// Request URL. // Request URL.
std::string url_; std::string url_;

@ -6,27 +6,22 @@
#include "boost/algorithm/string.hpp" #include "boost/algorithm/string.hpp"
#include "webcc/soap_request.h" #include "webcc/soap_request.h"
#include "webcc/soap_response.h" #include "webcc/utility.h"
namespace webcc { namespace webcc {
SoapClient::SoapClient(const std::string& host, const std::string& port, SoapClient::SoapClient(const std::string& host, const std::string& port,
SoapVersion soap_version) SoapVersion soap_version, std::size_t buffer_size)
: host_(host), port_(port), : host_(host), port_(port), soap_version_(soap_version),
soap_version_(soap_version), http_client_(buffer_size),
format_raw_(true), error_(kNoError) { format_raw_(true), error_(kNoError) {
if (port_.empty()) { AdjustHostPort(host_, port_);
std::size_t i = host_.find_last_of(':');
if (i != std::string::npos) {
port_ = host_.substr(i + 1);
host_ = host_.substr(0, i);
}
}
} }
bool SoapClient::Request(const std::string& operation, bool SoapClient::Request(const std::string& operation,
std::vector<SoapParameter>&& parameters, std::vector<SoapParameter>&& parameters,
SoapResponse::Parser parser) { SoapResponse::Parser parser,
std::size_t buffer_size) {
assert(service_ns_.IsValid()); assert(service_ns_.IsValid());
assert(!url_.empty() && !host_.empty()); assert(!url_.empty() && !host_.empty());
@ -53,23 +48,23 @@ bool SoapClient::Request(const std::string& operation,
std::string http_content; std::string http_content;
soap_request.ToXml(format_raw_, indent_str_, &http_content); soap_request.ToXml(format_raw_, indent_str_, &http_content);
HttpRequest http_request; HttpRequest http_request(kHttpPost, url_, host_, port_);
http_request.set_method(kHttpPost);
http_request.set_url(url_);
http_request.SetContent(std::move(http_content), true); http_request.SetContent(std::move(http_content), true);
if (soap_version_ == kSoapV11) { if (soap_version_ == kSoapV11) {
http_request.SetContentType(kTextXmlUtf8); http_request.SetContentType(http::media_types::kTextXml,
http::charsets::kUtf8);
} else { } else {
http_request.SetContentType(kAppSoapXmlUtf8); http_request.SetContentType(http::media_types::kApplicationSoapXml,
http::charsets::kUtf8);
} }
http_request.set_host(host_, port_);
http_request.SetHeader(kSoapAction, operation); http_request.SetHeader(kSoapAction, operation);
http_request.Make(); http_request.Make();
if (!http_client_.Request(http_request)) { if (!http_client_.Request(http_request, buffer_size)) {
error_ = http_client_.error(); error_ = http_client_.error();
return false; return false;
} }
@ -95,6 +90,7 @@ bool SoapClient::Request(const std::string& operation,
bool SoapClient::Request(const std::string& operation, bool SoapClient::Request(const std::string& operation,
std::vector<SoapParameter>&& parameters, std::vector<SoapParameter>&& parameters,
const std::string& result_name, const std::string& result_name,
std::size_t buffer_size,
std::string* result) { std::string* result) {
auto parser = [result, &result_name](pugi::xml_node xnode) { auto parser = [result, &result_name](pugi::xml_node xnode) {
if (boost::iequals(soap_xml::GetNameNoPrefix(xnode), result_name)) { if (boost::iequals(soap_xml::GetNameNoPrefix(xnode), result_name)) {
@ -103,7 +99,7 @@ bool SoapClient::Request(const std::string& operation,
return false; // Stop next call. return false; // Stop next call.
}; };
return Request(operation, std::move(parameters), parser); return Request(operation, std::move(parameters), parser, buffer_size);
} }
} // namespace webcc } // namespace webcc

@ -16,7 +16,8 @@ class SoapClient {
// If |port| is empty, |host| will be checked to see if it contains port or // If |port| is empty, |host| will be checked to see if it contains port or
// not (separated by ':'). // not (separated by ':').
explicit SoapClient(const std::string& host, const std::string& port = "", explicit SoapClient(const std::string& host, const std::string& port = "",
SoapVersion soap_version = kSoapV12); SoapVersion soap_version = kSoapV12,
std::size_t buffer_size = 0);
~SoapClient() = default; ~SoapClient() = default;
@ -42,7 +43,8 @@ class SoapClient {
bool Request(const std::string& operation, bool Request(const std::string& operation,
std::vector<SoapParameter>&& parameters, std::vector<SoapParameter>&& parameters,
SoapResponse::Parser parser); SoapResponse::Parser parser,
std::size_t buffer_size = 0);
// Shortcut for responses with single result node. // Shortcut for responses with single result node.
// The name of the single result node is specified by |result_name|. // The name of the single result node is specified by |result_name|.
@ -50,6 +52,7 @@ class SoapClient {
bool Request(const std::string& operation, bool Request(const std::string& operation,
std::vector<SoapParameter>&& parameters, std::vector<SoapParameter>&& parameters,
const std::string& result_name, const std::string& result_name,
std::size_t buffer_size, // Pass 0 for using default size.
std::string* result); std::string* result);
// HTTP status code (200, 500, etc.) in the response. // HTTP status code (200, 500, etc.) in the response.
@ -72,6 +75,8 @@ class SoapClient {
SoapVersion soap_version_; SoapVersion soap_version_;
HttpClient http_client_;
// Request URL. // Request URL.
std::string url_; std::string url_;
@ -85,8 +90,6 @@ class SoapClient {
// Applicable when |format_raw_| is false. // Applicable when |format_raw_| is false.
std::string indent_str_; std::string indent_str_;
HttpClient http_client_;
Error error_; Error error_;
std::shared_ptr<SoapFault> fault_; std::shared_ptr<SoapFault> fault_;

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

@ -20,7 +20,7 @@ class SoapService {
SoapResponse* soap_response) = 0; SoapResponse* soap_response) = 0;
protected: protected:
HttpStatus::Enum http_status_ = HttpStatus::kOK; http::Status http_status_ = http::Status::kOK;
}; };
typedef std::shared_ptr<SoapService> SoapServicePtr; typedef std::shared_ptr<SoapService> SoapServicePtr;

@ -12,21 +12,13 @@ using tcp = boost::asio::ip::tcp;
namespace webcc { namespace webcc {
void AdjustBufferSize(std::size_t content_length, std::vector<char>* buffer) { void AdjustHostPort(std::string& host, std::string& port) {
const std::size_t kMaxTimes = 10; if (port.empty()) {
std::size_t i = host.find_last_of(':');
// According to test, a client never read more than 200000 bytes a time. if (i != std::string::npos) {
// So it doesn't make sense to set any larger size, e.g., 1MB. port = host.substr(i + 1);
const std::size_t kMaxBufferSize = 200000; host = host.substr(0, i);
}
LOG_INFO("Adjust buffer size according to content length.");
std::size_t min_buffer_size = content_length / kMaxTimes;
if (min_buffer_size > buffer->size()) {
buffer->resize(std::min(min_buffer_size, kMaxBufferSize));
LOG_INFO("Resize read buffer to %u.", buffer->size());
} else {
LOG_INFO("Keep the current buffer size: %u.", buffer->size());
} }
} }

@ -9,9 +9,8 @@
namespace webcc { namespace webcc {
// Adjust buffer size according to content length. // If |port| is empty, try to extract it from |host| (separated by ':').
// This is to avoid reading too many times. void AdjustHostPort(std::string& host, std::string& port);
void AdjustBufferSize(std::size_t content_length, std::vector<char>* buffer);
void PrintEndpoint(std::ostream& ostream, void PrintEndpoint(std::ostream& ostream,
const boost::asio::ip::tcp::endpoint& endpoint); const boost::asio::ip::tcp::endpoint& endpoint);
@ -27,6 +26,25 @@ std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint);
// See: https://tools.ietf.org/html/rfc7231#section-7.1.1.2 // See: https://tools.ietf.org/html/rfc7231#section-7.1.1.2
std::string GetHttpDateTimestamp(); std::string GetHttpDateTimestamp();
// Resize a buffer in ctor and restore its original size in dtor.
struct BufferResizer {
BufferResizer(std::vector<char>* buffer, std::size_t new_size)
: buffer_(buffer), old_size_(buffer->size()) {
if (new_size != 0 && new_size != old_size_) {
buffer_->resize(new_size);
}
}
~BufferResizer() {
if (buffer_->size() != old_size_) {
buffer_->resize(old_size_);
}
}
std::vector<char>* buffer_;
std::size_t old_size_;
};
} // namespace webcc } // namespace webcc
#endif // WEBCC_UTILITY_H_ #endif // WEBCC_UTILITY_H_

Loading…
Cancel
Save