Allow to set buffer size for client; don't auto adjust reading buffer size; refine the global definitions.
This commit is contained in:
@@ -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";
|
||||||
|
|||||||
+53
-23
@@ -46,33 +46,13 @@ 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;
|
|
||||||
|
|
||||||
// Default ports.
|
|
||||||
extern const std::string kHttpPort;
|
|
||||||
extern const std::string kHttpSslPort;
|
|
||||||
|
|
||||||
// HTTP methods (verbs) in string ("HEAD", "GET", etc.).
|
|
||||||
// NOTE: Don't use enum to avoid converting back and forth.
|
|
||||||
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;
|
|
||||||
|
|
||||||
// HTTP response status.
|
// HTTP response status.
|
||||||
// This is not a full list.
|
// This is not a full list.
|
||||||
// Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
|
// Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
|
||||||
// NTOE: Don't use enum class because we want to convert to/from int easily.
|
// NTOE: Don't use enum class because we want to convert to/from int easily.
|
||||||
struct HttpStatus {
|
enum Status {
|
||||||
enum Enum {
|
|
||||||
kOK = 200,
|
kOK = 200,
|
||||||
kCreated = 201,
|
kCreated = 201,
|
||||||
kAccepted = 202,
|
kAccepted = 202,
|
||||||
@@ -83,9 +63,59 @@ struct HttpStatus {
|
|||||||
kInternalServerError = 500,
|
kInternalServerError = 500,
|
||||||
kNotImplemented = 501,
|
kNotImplemented = 501,
|
||||||
kServiceUnavailable = 503,
|
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.
|
||||||
|
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.
|
// Client side error codes.
|
||||||
enum Error {
|
enum Error {
|
||||||
kNoError = 0, // i.e., OK
|
kNoError = 0, // i.e., OK
|
||||||
|
|||||||
+84
-54
@@ -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;
|
DoStop();
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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_) {
|
||||||
|
|
||||||
if (ec == boost::asio::error::operation_aborted) {
|
|
||||||
LOG_VERB("Deadline timer canceled.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_VERB("OnDeadline.");
|
||||||
|
|
||||||
|
// 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.");
|
LOG_WARN("HTTP client timed out.");
|
||||||
timed_out_ = true;
|
timed_out_ = true;
|
||||||
|
|
||||||
Stop();
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the actor back to sleep.
|
||||||
|
DoWaitDeadline();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
+20
-12
@@ -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_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+10
-13
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-3
@@ -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());
|
||||||
|
|||||||
+4
-10
@@ -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);
|
||||||
|
|||||||
+16
-3
@@ -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
|
||||||
|
|||||||
+17
-12
@@ -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_;
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -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_; }
|
||||||
|
|
||||||
|
|||||||
+17
-14
@@ -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
|
||||||
|
|||||||
+18
-17
@@ -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
|
||||||
|
|||||||
+12
-16
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-12
@@ -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 {
|
||||||
@@ -77,7 +81,7 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+10
-17
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-13
@@ -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_;
|
||||||
|
|||||||
+24
-25
@@ -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,10 +49,10 @@ 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);
|
||||||
|
|
||||||
@@ -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_;
|
||||||
|
|
||||||
|
|||||||
+16
-20
@@ -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
|
||||||
|
|||||||
+7
-4
@@ -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;
|
||||||
|
|||||||
+7
-15
@@ -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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+21
-3
@@ -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_
|
||||||
|
|||||||
Reference in New Issue
Block a user