Add a base class for http async clients; refine examples.
This commit is contained in:
+3
-4
@@ -120,8 +120,8 @@ endif()
|
|||||||
add_subdirectory(webcc)
|
add_subdirectory(webcc)
|
||||||
|
|
||||||
if(WEBCC_ENABLE_EXAMPLES)
|
if(WEBCC_ENABLE_EXAMPLES)
|
||||||
add_subdirectory(example/http_hello_client)
|
add_subdirectory(example/http_client)
|
||||||
add_subdirectory(example/http_hello_async_client)
|
add_subdirectory(example/http_async_client)
|
||||||
|
|
||||||
# For including jsoncpp as "json/json.h".
|
# For including jsoncpp as "json/json.h".
|
||||||
include_directories(${THIRD_PARTY_DIR}/src/jsoncpp)
|
include_directories(${THIRD_PARTY_DIR}/src/jsoncpp)
|
||||||
@@ -143,10 +143,9 @@ if(WEBCC_ENABLE_EXAMPLES)
|
|||||||
|
|
||||||
if(WEBCC_ENABLE_SSL)
|
if(WEBCC_ENABLE_SSL)
|
||||||
add_subdirectory(example/http_ssl_client)
|
add_subdirectory(example/http_ssl_client)
|
||||||
|
add_subdirectory(example/http_ssl_async_client)
|
||||||
add_subdirectory(example/github_rest_client)
|
add_subdirectory(example/github_rest_client)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(example/http_bin_client)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WEBCC_ENABLE_UNITTEST)
|
if(WEBCC_ENABLE_UNITTEST)
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
HttpBin (http://httpbin.org/) client example.
|
||||||
|
|
||||||
|
You request to different endpoints, and it returns information about what was in the request.
|
||||||
|
|
||||||
|
E.g., request:
|
||||||
|
```plain
|
||||||
|
GET /get HTTP/1.1
|
||||||
|
Host: httpbin.org:80
|
||||||
|
User-Agent: Webcc/0.1.0
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
```plain
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Connection: keep-alive
|
||||||
|
Server: gunicorn/19.9.0
|
||||||
|
Content-Type: application/json
|
||||||
|
Content-Length: 191
|
||||||
|
Access-Control-Allow-Origin: *
|
||||||
|
Access-Control-Allow-Credentials: true
|
||||||
|
Via: 1.1 vegur
|
||||||
|
|
||||||
|
{
|
||||||
|
"args": {},
|
||||||
|
"headers": {
|
||||||
|
"Connection": "close",
|
||||||
|
"Host": "httpbin.org",
|
||||||
|
"User-Agent": "Webcc/0.1.0"
|
||||||
|
},
|
||||||
|
"origin": "198.55.94.81",
|
||||||
|
"url": "http://httpbin.org/get"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, the request information is returned in JSON format.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
add_executable(http_async_client main.cc)
|
||||||
|
|
||||||
|
target_link_libraries(http_async_client webcc ${Boost_LIBRARIES})
|
||||||
|
target_link_libraries(http_async_client "${CMAKE_THREAD_LIBS_INIT}")
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "boost/asio/io_context.hpp"
|
||||||
|
|
||||||
|
#include "webcc/http_async_client.h"
|
||||||
|
#include "webcc/logger.h"
|
||||||
|
|
||||||
|
// TODO: The program blocks during read response.
|
||||||
|
// Only HttpBin.org has this issue.
|
||||||
|
|
||||||
|
static void Test(boost::asio::io_context& io_context) {
|
||||||
|
auto request = webcc::HttpRequest::Make(webcc::kHttpGet, "/get",
|
||||||
|
"httpbin.org");
|
||||||
|
|
||||||
|
webcc::HttpAsyncClientPtr client{
|
||||||
|
new webcc::HttpAsyncClient(io_context)
|
||||||
|
};
|
||||||
|
|
||||||
|
client->SetTimeout(3);
|
||||||
|
|
||||||
|
// Response callback.
|
||||||
|
auto callback = [](webcc::HttpResponsePtr response, webcc::Error error,
|
||||||
|
bool timed_out) {
|
||||||
|
if (error == webcc::kNoError) {
|
||||||
|
std::cout << response->content() << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << DescribeError(error);
|
||||||
|
if (timed_out) {
|
||||||
|
std::cout << " (timed out)";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
client->Request(request, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
|
||||||
|
|
||||||
|
boost::asio::io_context io_context;
|
||||||
|
|
||||||
|
//Test(io_context);
|
||||||
|
Test(io_context);
|
||||||
|
|
||||||
|
io_context.run();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
add_executable(http_bin_client main.cc)
|
|
||||||
|
|
||||||
target_link_libraries(http_bin_client webcc ${Boost_LIBRARIES})
|
|
||||||
target_link_libraries(http_bin_client "${CMAKE_THREAD_LIBS_INIT}")
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
// HttpBin (http://httpbin.org/) client example.
|
|
||||||
//
|
|
||||||
// You request to different endpoints, and it returns information about what
|
|
||||||
// was in the request.
|
|
||||||
//
|
|
||||||
// E.g., request:
|
|
||||||
// > GET /get HTTP/1.1
|
|
||||||
// > Host: httpbin.org:80
|
|
||||||
// > User-Agent: Webcc/0.1.0
|
|
||||||
// >
|
|
||||||
// Response:
|
|
||||||
// > HTTP/1.1 200 OK
|
|
||||||
// > Connection: keep-alive
|
|
||||||
// > Server: gunicorn/19.9.0
|
|
||||||
// > Content-Type: application/json
|
|
||||||
// > Content-Length: 191
|
|
||||||
// > Access-Control-Allow-Origin: *
|
|
||||||
// > Access-Control-Allow-Credentials: true
|
|
||||||
// > Via: 1.1 vegur
|
|
||||||
// >
|
|
||||||
// > {
|
|
||||||
// > "args": {},
|
|
||||||
// > "headers": {
|
|
||||||
// > "Connection": "close",
|
|
||||||
// > "Host": "httpbin.org",
|
|
||||||
// > "User-Agent": "Webcc/0.1.0"
|
|
||||||
// > },
|
|
||||||
// > "origin": "198.55.94.81",
|
|
||||||
// > "url": "http://httpbin.org/get"
|
|
||||||
// > }
|
|
||||||
// >
|
|
||||||
// As you can see, the request information is returned in JSON format.
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "webcc/http_client.h"
|
|
||||||
#include "webcc/logger.h"
|
|
||||||
|
|
||||||
void Test() {
|
|
||||||
webcc::HttpRequest request(webcc::kHttpGet, "/get", "httpbin.org"/*, "80"*/);
|
|
||||||
request.Make();
|
|
||||||
|
|
||||||
webcc::HttpClient client;
|
|
||||||
if (client.Request(request)) {
|
|
||||||
std::cout << client.response()->content() << std::endl;
|
|
||||||
} else {
|
|
||||||
std::cout << webcc::DescribeError(client.error());
|
|
||||||
if (client.timed_out()) {
|
|
||||||
std::cout << " (timed out)";
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
|
|
||||||
|
|
||||||
Test();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
add_executable(http_client main.cc)
|
||||||
|
|
||||||
|
target_link_libraries(http_client webcc ${Boost_LIBRARIES})
|
||||||
|
target_link_libraries(http_client "${CMAKE_THREAD_LIBS_INIT}")
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "webcc/http_client.h"
|
||||||
|
#include "webcc/logger.h"
|
||||||
|
|
||||||
|
static void PrintError(const webcc::HttpClient& client) {
|
||||||
|
std::cout << webcc::DescribeError(client.error());
|
||||||
|
if (client.timed_out()) {
|
||||||
|
std::cout << " (timed out)";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
|
||||||
|
|
||||||
|
auto request = webcc::HttpRequest::Make(webcc::kHttpGet, "/get",
|
||||||
|
"httpbin.org");
|
||||||
|
|
||||||
|
webcc::HttpClient client;
|
||||||
|
|
||||||
|
if (client.Request(*request)) {
|
||||||
|
std::cout << client.response_content() << std::endl;
|
||||||
|
} else {
|
||||||
|
PrintError(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
add_executable(http_hello_async_client main.cc)
|
|
||||||
|
|
||||||
target_link_libraries(http_hello_async_client webcc ${Boost_LIBRARIES})
|
|
||||||
target_link_libraries(http_hello_async_client "${CMAKE_THREAD_LIBS_INIT}")
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "boost/asio/io_context.hpp"
|
|
||||||
|
|
||||||
#include "webcc/http_async_client.h"
|
|
||||||
#include "webcc/logger.h"
|
|
||||||
|
|
||||||
// In order to test this client, create a file index.html whose content is
|
|
||||||
// simply "Hello, World!", then start a HTTP server with Python 3:
|
|
||||||
// $ python -m http.server
|
|
||||||
// The default port number should be 8000.
|
|
||||||
|
|
||||||
void Test(boost::asio::io_context& io_context) {
|
|
||||||
webcc::HttpRequestPtr request = std::make_shared<webcc::HttpRequest>(
|
|
||||||
webcc::kHttpGet, "/index.html", "localhost", "8000");
|
|
||||||
|
|
||||||
request->Make();
|
|
||||||
|
|
||||||
webcc::HttpAsyncClientPtr client(new webcc::HttpAsyncClient(io_context));
|
|
||||||
|
|
||||||
// Response handler.
|
|
||||||
auto handler = [](webcc::HttpResponsePtr response, webcc::Error error,
|
|
||||||
bool timed_out) {
|
|
||||||
if (error == webcc::kNoError) {
|
|
||||||
std::cout << response->content() << std::endl;
|
|
||||||
} else {
|
|
||||||
std::cout << webcc::DescribeError(error);
|
|
||||||
if (timed_out) {
|
|
||||||
std::cout << " (timed out)";
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
client->Request(request, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
|
|
||||||
|
|
||||||
boost::asio::io_context io_context;
|
|
||||||
|
|
||||||
Test(io_context);
|
|
||||||
Test(io_context);
|
|
||||||
Test(io_context);
|
|
||||||
|
|
||||||
io_context.run();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
add_executable(http_hello_client main.cc)
|
|
||||||
|
|
||||||
target_link_libraries(http_hello_client webcc ${Boost_LIBRARIES})
|
|
||||||
target_link_libraries(http_hello_client "${CMAKE_THREAD_LIBS_INIT}")
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "webcc/http_client.h"
|
|
||||||
#include "webcc/logger.h"
|
|
||||||
|
|
||||||
// In order to test this client, create a file index.html whose content is
|
|
||||||
// simply "Hello, World!", then start a HTTP server with Python 3:
|
|
||||||
// $ python -m http.server
|
|
||||||
// The default port number should be 8000.
|
|
||||||
|
|
||||||
void Test() {
|
|
||||||
webcc::HttpRequest request(webcc::kHttpGet, "/index.html", "localhost",
|
|
||||||
"8000");
|
|
||||||
request.Make();
|
|
||||||
|
|
||||||
webcc::HttpClient client;
|
|
||||||
if (client.Request(request)) {
|
|
||||||
std::cout << client.response()->content() << std::endl;
|
|
||||||
} else {
|
|
||||||
std::cout << webcc::DescribeError(client.error());
|
|
||||||
if (client.timed_out()) {
|
|
||||||
std::cout << " (timed out)";
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
|
|
||||||
|
|
||||||
Test();
|
|
||||||
Test();
|
|
||||||
Test();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
add_executable(http_ssl_async_client main.cc)
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
set(SSL_LIBS ${OPENSSL_LIBRARIES})
|
||||||
|
if(WIN32)
|
||||||
|
set(SSL_LIBS ${SSL_LIBS} crypt32)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(http_ssl_async_client webcc ${Boost_LIBRARIES})
|
||||||
|
target_link_libraries(http_ssl_async_client "${CMAKE_THREAD_LIBS_INIT}")
|
||||||
|
target_link_libraries(http_ssl_async_client ${SSL_LIBS})
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "boost/asio/io_context.hpp"
|
||||||
|
|
||||||
|
#include "webcc/http_ssl_async_client.h"
|
||||||
|
#include "webcc/logger.h"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
std::string host;
|
||||||
|
std::string url;
|
||||||
|
|
||||||
|
if (argc != 3) {
|
||||||
|
host = "www.boost.org";
|
||||||
|
url = "/LICENSE_1_0.txt";
|
||||||
|
} else {
|
||||||
|
host = argv[1];
|
||||||
|
url = argv[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Host: " << host << std::endl;
|
||||||
|
std::cout << "URL: " << url << std::endl;
|
||||||
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
|
||||||
|
|
||||||
|
boost::asio::io_context io_context;
|
||||||
|
|
||||||
|
// Leave port to default value.
|
||||||
|
auto request = webcc::HttpRequest::Make(webcc::kHttpGet, url, host);
|
||||||
|
|
||||||
|
// Verify the certificate of the peer or not.
|
||||||
|
// See HttpSslClient::Request() for more details.
|
||||||
|
bool ssl_verify = false;
|
||||||
|
|
||||||
|
webcc::HttpSslAsyncClientPtr client{
|
||||||
|
new webcc::HttpSslAsyncClient{ io_context, 2000, ssl_verify }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Response callback.
|
||||||
|
auto callback = [](webcc::HttpResponsePtr response, webcc::Error error,
|
||||||
|
bool timed_out) {
|
||||||
|
if (error == webcc::kNoError) {
|
||||||
|
std::cout << response->content() << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << DescribeError(error);
|
||||||
|
if (timed_out) {
|
||||||
|
std::cout << " (timed out)";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
client->Request(request, callback);
|
||||||
|
|
||||||
|
io_context.run();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ int main(int argc, char* argv[]) {
|
|||||||
// Leave port to default value.
|
// Leave port to default value.
|
||||||
webcc::HttpRequest request(webcc::kHttpGet, url, host);
|
webcc::HttpRequest request(webcc::kHttpGet, url, host);
|
||||||
|
|
||||||
request.Make();
|
request.Prepare();
|
||||||
|
|
||||||
// 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.
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ include(GNUInstallDirs)
|
|||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
globals.h
|
globals.h
|
||||||
|
http_async_client_base.h
|
||||||
http_async_client.h
|
http_async_client.h
|
||||||
http_client_base.h
|
http_client_base.h
|
||||||
http_client.h
|
http_client.h
|
||||||
@@ -27,7 +28,7 @@ set(HEADERS
|
|||||||
http_response.h
|
http_response.h
|
||||||
http_response_parser.h
|
http_response_parser.h
|
||||||
http_server.h
|
http_server.h
|
||||||
logger.h
|
http_ssl_async_client.h
|
||||||
queue.h
|
queue.h
|
||||||
rest_async_client.h
|
rest_async_client.h
|
||||||
rest_client.h
|
rest_client.h
|
||||||
@@ -42,6 +43,7 @@ set(HEADERS
|
|||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
globals.cc
|
globals.cc
|
||||||
|
http_async_client_base.cc
|
||||||
http_async_client.cc
|
http_async_client.cc
|
||||||
http_client_base.cc
|
http_client_base.cc
|
||||||
http_client.cc
|
http_client.cc
|
||||||
@@ -54,6 +56,7 @@ set(SOURCES
|
|||||||
http_response.cc
|
http_response.cc
|
||||||
http_response_parser.cc
|
http_response_parser.cc
|
||||||
http_server.cc
|
http_server.cc
|
||||||
|
http_ssl_async_client.cc
|
||||||
logger.cc
|
logger.cc
|
||||||
rest_async_client.cc
|
rest_async_client.cc
|
||||||
rest_client.cc
|
rest_client.cc
|
||||||
|
|||||||
+11
-208
@@ -11,222 +11,25 @@ namespace webcc {
|
|||||||
|
|
||||||
HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context,
|
HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context,
|
||||||
std::size_t buffer_size)
|
std::size_t buffer_size)
|
||||||
: socket_(io_context),
|
: HttpAsyncClientBase(io_context, buffer_size),
|
||||||
resolver_(io_context),
|
socket_(io_context) {
|
||||||
buffer_(buffer_size == 0 ? kBufferSize : buffer_size),
|
|
||||||
deadline_(io_context),
|
|
||||||
timeout_seconds_(kMaxReadSeconds),
|
|
||||||
stopped_(false),
|
|
||||||
timed_out_(false) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpAsyncClient::SetTimeout(int seconds) {
|
void HttpAsyncClient::SocketAsyncConnect(const Endpoints& endpoints,
|
||||||
if (seconds > 0) {
|
ConnectHandler&& handler) {
|
||||||
timeout_seconds_ = seconds;
|
boost::asio::async_connect(socket_, endpoints, std::move(handler));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpAsyncClient::Request(std::shared_ptr<HttpRequest> request,
|
void HttpAsyncClient::SocketAsyncWrite(WriteHandler&& handler) {
|
||||||
HttpResponseCallback response_callback) {
|
boost::asio::async_write(socket_, request_->ToBuffers(), std::move(handler));
|
||||||
assert(request);
|
|
||||||
assert(response_callback);
|
|
||||||
|
|
||||||
response_.reset(new HttpResponse());
|
|
||||||
response_parser_.reset(new HttpResponseParser(response_.get()));
|
|
||||||
|
|
||||||
stopped_ = false;
|
|
||||||
timed_out_ = false;
|
|
||||||
|
|
||||||
LOG_VERB("HTTP request:\n%s", request->Dump(4, "> ").c_str());
|
|
||||||
|
|
||||||
request_ = request;
|
|
||||||
response_callback_ = response_callback;
|
|
||||||
|
|
||||||
resolver_.async_resolve(tcp::v4(), request->host(), request->port(kHttpPort),
|
|
||||||
std::bind(&HttpAsyncClient::OnResolve,
|
|
||||||
shared_from_this(),
|
|
||||||
std::placeholders::_1,
|
|
||||||
std::placeholders::_2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpAsyncClient::Stop() {
|
void HttpAsyncClient::SocketAsyncReadSome(ReadHandler&& handler) {
|
||||||
LOG_INFO("The user asks to cancel the request.");
|
socket_.async_read_some(boost::asio::buffer(buffer_), std::move(handler));
|
||||||
|
|
||||||
DoStop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpAsyncClient::OnResolve(boost::system::error_code ec,
|
void HttpAsyncClient::SocketClose(boost::system::error_code* ec) {
|
||||||
tcp::resolver::results_type endpoints) {
|
socket_.close(*ec);
|
||||||
if (ec) {
|
|
||||||
LOG_ERRO("Host resolve error (%s): %s, %s.", ec.message().c_str(),
|
|
||||||
request_->host().c_str(), request_->port().c_str());
|
|
||||||
response_callback_(response_, kHostResolveError, timed_out_);
|
|
||||||
} else {
|
|
||||||
LOG_VERB("Connect to server...");
|
|
||||||
|
|
||||||
// ConnectHandler: void(boost::system::error_code, tcp::endpoint)
|
|
||||||
boost::asio::async_connect(socket_, endpoints,
|
|
||||||
std::bind(&HttpAsyncClient::OnConnect,
|
|
||||||
shared_from_this(),
|
|
||||||
std::placeholders::_1,
|
|
||||||
std::placeholders::_2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpAsyncClient::OnConnect(boost::system::error_code ec,
|
|
||||||
tcp::endpoint endpoint) {
|
|
||||||
if (ec) {
|
|
||||||
LOG_ERRO("Socket connect error (%s).", ec.message().c_str());
|
|
||||||
DoStop();
|
|
||||||
response_callback_(response_, kEndpointConnectError, timed_out_);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_VERB("Socket connected.");
|
|
||||||
|
|
||||||
// Even though the connect operation notionally succeeded, the user could
|
|
||||||
// have stopped the operation by calling Stop(). And if we started the
|
|
||||||
// deadline timer, it could also be stopped due to timeout.
|
|
||||||
if (stopped_) {
|
|
||||||
// TODO: Use some other error.
|
|
||||||
response_callback_(response_, kEndpointConnectError, timed_out_);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connection established.
|
|
||||||
DoWrite();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpAsyncClient::DoWrite() {
|
|
||||||
// NOTE:
|
|
||||||
// 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_,
|
|
||||||
request_->ToBuffers(),
|
|
||||||
std::bind(&HttpAsyncClient::OnWrite,
|
|
||||||
shared_from_this(),
|
|
||||||
std::placeholders::_1));
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpAsyncClient::OnWrite(boost::system::error_code ec) {
|
|
||||||
if (stopped_) {
|
|
||||||
// TODO: Use some other error.
|
|
||||||
response_callback_(response_, kSocketWriteError, timed_out_);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ec) {
|
|
||||||
LOG_ERRO("Socket write error (%s).", ec.message().c_str());
|
|
||||||
DoStop();
|
|
||||||
response_callback_(response_, kSocketWriteError, timed_out_);
|
|
||||||
} else {
|
|
||||||
LOG_INFO("Request sent.");
|
|
||||||
LOG_VERB("Read response (timeout: %ds)...", timeout_seconds_);
|
|
||||||
|
|
||||||
deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_));
|
|
||||||
DoWaitDeadline();
|
|
||||||
|
|
||||||
DoRead();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpAsyncClient::DoRead() {
|
|
||||||
socket_.async_read_some(boost::asio::buffer(buffer_),
|
|
||||||
std::bind(&HttpAsyncClient::OnRead,
|
|
||||||
shared_from_this(),
|
|
||||||
std::placeholders::_1,
|
|
||||||
std::placeholders::_2));
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpAsyncClient::OnRead(boost::system::error_code ec,
|
|
||||||
std::size_t length) {
|
|
||||||
LOG_VERB("Socket async read handler.");
|
|
||||||
|
|
||||||
if (ec || length == 0) {
|
|
||||||
DoStop();
|
|
||||||
LOG_ERRO("Socket read error (%s).", ec.message().c_str());
|
|
||||||
response_callback_(response_, kSocketReadError, timed_out_);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INFO("Read data, length: %u.", length);
|
|
||||||
|
|
||||||
// Parse the response piece just read.
|
|
||||||
// If the content has been fully received, |finished()| will be true.
|
|
||||||
if (!response_parser_->Parse(buffer_.data(), length)) {
|
|
||||||
DoStop();
|
|
||||||
LOG_ERRO("Failed to parse HTTP response.");
|
|
||||||
response_callback_(response_, kHttpError, timed_out_);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response_parser_->finished()) {
|
|
||||||
DoStop();
|
|
||||||
|
|
||||||
LOG_INFO("Finished to read and parse HTTP response.");
|
|
||||||
LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str());
|
|
||||||
|
|
||||||
response_callback_(response_, kNoError, timed_out_);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stopped_) {
|
|
||||||
DoRead();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpAsyncClient::DoWaitDeadline() {
|
|
||||||
deadline_.async_wait(std::bind(&HttpAsyncClient::OnDeadline,
|
|
||||||
shared_from_this(), std::placeholders::_1));
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpAsyncClient::OnDeadline(boost::system::error_code ec) {
|
|
||||||
if (stopped_) {
|
|
||||||
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.");
|
|
||||||
timed_out_ = true;
|
|
||||||
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
|
||||||
|
|||||||
+15
-69
@@ -1,91 +1,37 @@
|
|||||||
#ifndef WEBCC_HTTP_ASYNC_CLIENT_H_
|
#ifndef WEBCC_HTTP_ASYNC_CLIENT_H_
|
||||||
#define WEBCC_HTTP_ASYNC_CLIENT_H_
|
#define WEBCC_HTTP_ASYNC_CLIENT_H_
|
||||||
|
|
||||||
#include <functional>
|
#include "webcc/http_async_client_base.h"
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "boost/asio/deadline_timer.hpp"
|
|
||||||
#include "boost/asio/io_context.hpp"
|
|
||||||
#include "boost/asio/ip/tcp.hpp"
|
|
||||||
|
|
||||||
#include "webcc/globals.h"
|
|
||||||
#include "webcc/http_request.h"
|
|
||||||
#include "webcc/http_response.h"
|
|
||||||
#include "webcc/http_response_parser.h"
|
|
||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
// Response callback.
|
// HTTP asynchronous client.
|
||||||
typedef std::function<void(HttpResponsePtr, Error, bool)> HttpResponseCallback;
|
class HttpAsyncClient : public HttpAsyncClientBase {
|
||||||
|
|
||||||
// HTTP client session in asynchronous mode.
|
|
||||||
// A request will return without waiting for the response, the callback handler
|
|
||||||
// will be invoked when the response is received or timeout occurs.
|
|
||||||
// Don't use the same HttpAsyncClient object in multiple threads.
|
|
||||||
class HttpAsyncClient : public std::enable_shared_from_this<HttpAsyncClient> {
|
|
||||||
public:
|
public:
|
||||||
// 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,
|
explicit HttpAsyncClient(boost::asio::io_context& io_context,
|
||||||
std::size_t buffer_size = 0);
|
std::size_t buffer_size = 0);
|
||||||
|
|
||||||
WEBCC_DELETE_COPY_ASSIGN(HttpAsyncClient);
|
~HttpAsyncClient() = default;
|
||||||
|
|
||||||
// Set the timeout seconds for reading response.
|
|
||||||
// The |seconds| is only effective when greater than 0.
|
|
||||||
void SetTimeout(int seconds);
|
|
||||||
|
|
||||||
// Asynchronously connect to the server, send the request, read the response,
|
|
||||||
// and call the |response_callback| when all these finish.
|
|
||||||
void Request(HttpRequestPtr request, HttpResponseCallback response_callback);
|
|
||||||
|
|
||||||
// Called by the user to cancel the request.
|
|
||||||
void Stop();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using tcp = boost::asio::ip::tcp;
|
void Resolve() final {
|
||||||
|
DoResolve(kHttpPort);
|
||||||
|
}
|
||||||
|
|
||||||
void OnResolve(boost::system::error_code ec,
|
void OnConnected() final {
|
||||||
tcp::resolver::results_type results);
|
DoWrite();
|
||||||
|
}
|
||||||
|
|
||||||
void OnConnect(boost::system::error_code ec, tcp::endpoint endpoint);
|
void SocketAsyncConnect(const Endpoints& endpoints,
|
||||||
|
ConnectHandler&& handler) final;
|
||||||
|
|
||||||
void DoWrite();
|
void SocketAsyncWrite(WriteHandler&& handler) final;
|
||||||
void OnWrite(boost::system::error_code ec);
|
|
||||||
|
|
||||||
void DoRead();
|
void SocketAsyncReadSome(ReadHandler&& handler) final;
|
||||||
void OnRead(boost::system::error_code ec, std::size_t length);
|
|
||||||
|
|
||||||
void DoWaitDeadline();
|
void SocketClose(boost::system::error_code* ec) final;
|
||||||
void OnDeadline(boost::system::error_code ec);
|
|
||||||
|
|
||||||
// Terminate all the actors to shut down the connection.
|
|
||||||
void DoStop();
|
|
||||||
|
|
||||||
tcp::resolver resolver_;
|
|
||||||
tcp::socket socket_;
|
tcp::socket socket_;
|
||||||
|
|
||||||
std::shared_ptr<HttpRequest> request_;
|
|
||||||
std::vector<char> buffer_;
|
|
||||||
|
|
||||||
HttpResponsePtr response_;
|
|
||||||
std::unique_ptr<HttpResponseParser> response_parser_;
|
|
||||||
HttpResponseCallback response_callback_;
|
|
||||||
|
|
||||||
// Timer for the timeout control.
|
|
||||||
boost::asio::deadline_timer deadline_;
|
|
||||||
|
|
||||||
// Maximum seconds to wait before the client cancels the operation.
|
|
||||||
// Only for receiving response from server.
|
|
||||||
int timeout_seconds_;
|
|
||||||
|
|
||||||
// Request stopped due to timeout or socket error.
|
|
||||||
bool stopped_;
|
|
||||||
|
|
||||||
// If the error was caused by timeout or not.
|
|
||||||
// Will be passed to the response handler/callback.
|
|
||||||
bool timed_out_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<HttpAsyncClient> HttpAsyncClientPtr;
|
typedef std::shared_ptr<HttpAsyncClient> HttpAsyncClientPtr;
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
#include "webcc/http_async_client_base.h"
|
||||||
|
|
||||||
|
#include "boost/asio/connect.hpp"
|
||||||
|
#include "boost/asio/read.hpp"
|
||||||
|
#include "boost/asio/write.hpp"
|
||||||
|
|
||||||
|
#include "webcc/logger.h"
|
||||||
|
#include "webcc/utility.h"
|
||||||
|
|
||||||
|
namespace webcc {
|
||||||
|
|
||||||
|
HttpAsyncClientBase::HttpAsyncClientBase(boost::asio::io_context& io_context,
|
||||||
|
std::size_t buffer_size)
|
||||||
|
: resolver_(io_context),
|
||||||
|
buffer_(buffer_size == 0 ? kBufferSize : buffer_size),
|
||||||
|
deadline_(io_context),
|
||||||
|
timeout_seconds_(kMaxReadSeconds),
|
||||||
|
stopped_(false),
|
||||||
|
timed_out_(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::SetTimeout(int seconds) {
|
||||||
|
if (seconds > 0) {
|
||||||
|
timeout_seconds_ = seconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::Request(std::shared_ptr<HttpRequest> request,
|
||||||
|
HttpResponseCallback response_callback) {
|
||||||
|
assert(request);
|
||||||
|
assert(response_callback);
|
||||||
|
|
||||||
|
response_.reset(new HttpResponse());
|
||||||
|
response_parser_.reset(new HttpResponseParser(response_.get()));
|
||||||
|
|
||||||
|
stopped_ = false;
|
||||||
|
timed_out_ = false;
|
||||||
|
|
||||||
|
LOG_VERB("HTTP request:\n%s", request->Dump(4, "> ").c_str());
|
||||||
|
|
||||||
|
request_ = request;
|
||||||
|
response_callback_ = response_callback;
|
||||||
|
|
||||||
|
DoResolve(kHttpSslPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::Stop() {
|
||||||
|
LOG_INFO("The user asks to cancel the request.");
|
||||||
|
|
||||||
|
DoStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::DoResolve(const std::string& default_port) {
|
||||||
|
auto handler = std::bind(&HttpAsyncClientBase::OnResolve, shared_from_this(),
|
||||||
|
std::placeholders::_1, std::placeholders::_2);
|
||||||
|
|
||||||
|
resolver_.async_resolve(tcp::v4(), request_->host(),
|
||||||
|
request_->port(default_port), handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::OnResolve(boost::system::error_code ec,
|
||||||
|
tcp::resolver::results_type endpoints) {
|
||||||
|
if (ec) {
|
||||||
|
LOG_ERRO("Host resolve error (%s): %s, %s.", ec.message().c_str(),
|
||||||
|
request_->host().c_str(), request_->port().c_str());
|
||||||
|
|
||||||
|
response_callback_(response_, kHostResolveError, timed_out_);
|
||||||
|
} else {
|
||||||
|
LOG_VERB("Host resolved.");
|
||||||
|
|
||||||
|
DoConnect(endpoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::DoConnect(const Endpoints& endpoints) {
|
||||||
|
auto handler = std::bind(&HttpAsyncClientBase::OnConnect,
|
||||||
|
shared_from_this(),
|
||||||
|
std::placeholders::_1, std::placeholders::_2);
|
||||||
|
|
||||||
|
SocketAsyncConnect(endpoints, std::move(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::OnConnect(boost::system::error_code ec,
|
||||||
|
tcp::endpoint endpoint) {
|
||||||
|
if (ec) {
|
||||||
|
LOG_ERRO("Socket connect error (%s).", ec.message().c_str());
|
||||||
|
DoStop();
|
||||||
|
response_callback_(response_, kEndpointConnectError, timed_out_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_VERB("Socket connected.");
|
||||||
|
|
||||||
|
// Even though the connect operation notionally succeeded, the user could
|
||||||
|
// have stopped the operation by calling Stop(). And if we started the
|
||||||
|
// deadline timer, it could also be stopped due to timeout.
|
||||||
|
if (stopped_) {
|
||||||
|
// TODO: Use some other error.
|
||||||
|
response_callback_(response_, kEndpointConnectError, timed_out_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection established.
|
||||||
|
OnConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::DoWrite() {
|
||||||
|
// NOTE:
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
SocketAsyncWrite(std::bind(&HttpAsyncClientBase::OnWrite, shared_from_this(),
|
||||||
|
std::placeholders::_1, std::placeholders::_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::OnWrite(boost::system::error_code ec,
|
||||||
|
std::size_t /*length*/) {
|
||||||
|
if (stopped_) {
|
||||||
|
// TODO: Use some other error.
|
||||||
|
response_callback_(response_, kSocketWriteError, timed_out_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ec) {
|
||||||
|
LOG_ERRO("Socket write error (%s).", ec.message().c_str());
|
||||||
|
DoStop();
|
||||||
|
response_callback_(response_, kSocketWriteError, timed_out_);
|
||||||
|
} else {
|
||||||
|
LOG_INFO("Request sent.");
|
||||||
|
LOG_VERB("Read response (timeout: %ds)...", timeout_seconds_);
|
||||||
|
|
||||||
|
deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_));
|
||||||
|
DoWaitDeadline();
|
||||||
|
|
||||||
|
DoRead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::DoRead() {
|
||||||
|
auto handler = std::bind(&HttpAsyncClientBase::OnRead, shared_from_this(),
|
||||||
|
std::placeholders::_1, std::placeholders::_2);
|
||||||
|
SocketAsyncReadSome(std::move(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::OnRead(boost::system::error_code ec,
|
||||||
|
std::size_t length) {
|
||||||
|
LOG_VERB("Socket async read handler.");
|
||||||
|
|
||||||
|
if (ec || length == 0) {
|
||||||
|
DoStop();
|
||||||
|
LOG_ERRO("Socket read error (%s).", ec.message().c_str());
|
||||||
|
response_callback_(response_, kSocketReadError, timed_out_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Read data, length: %u.", length);
|
||||||
|
|
||||||
|
// Parse the response piece just read.
|
||||||
|
// If the content has been fully received, |finished()| will be true.
|
||||||
|
if (!response_parser_->Parse(buffer_.data(), length)) {
|
||||||
|
DoStop();
|
||||||
|
LOG_ERRO("Failed to parse HTTP response.");
|
||||||
|
response_callback_(response_, kHttpError, timed_out_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response_parser_->finished()) {
|
||||||
|
DoStop();
|
||||||
|
|
||||||
|
LOG_INFO("Finished to read and parse HTTP response.");
|
||||||
|
LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str());
|
||||||
|
|
||||||
|
response_callback_(response_, kNoError, timed_out_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stopped_) {
|
||||||
|
DoRead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::DoWaitDeadline() {
|
||||||
|
deadline_.async_wait(std::bind(&HttpAsyncClientBase::OnDeadline,
|
||||||
|
shared_from_this(), std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::OnDeadline(boost::system::error_code ec) {
|
||||||
|
if (stopped_) {
|
||||||
|
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.");
|
||||||
|
timed_out_ = true;
|
||||||
|
Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the actor back to sleep.
|
||||||
|
DoWaitDeadline();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpAsyncClientBase::DoStop() {
|
||||||
|
if (stopped_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopped_ = true;
|
||||||
|
|
||||||
|
LOG_INFO("Close socket...");
|
||||||
|
|
||||||
|
boost::system::error_code ec;
|
||||||
|
SocketClose(&ec);
|
||||||
|
|
||||||
|
if (ec) {
|
||||||
|
LOG_ERRO("Socket close error (%s).", ec.message().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Cancel deadline timer...");
|
||||||
|
deadline_.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webcc
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
#ifndef WEBCC_HTTP_ASYNC_CLIENT_BASE_H_
|
||||||
|
#define WEBCC_HTTP_ASYNC_CLIENT_BASE_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "boost/asio/deadline_timer.hpp"
|
||||||
|
#include "boost/asio/io_context.hpp"
|
||||||
|
#include "boost/asio/ip/tcp.hpp"
|
||||||
|
|
||||||
|
#include "webcc/globals.h"
|
||||||
|
#include "webcc/http_request.h"
|
||||||
|
#include "webcc/http_response.h"
|
||||||
|
#include "webcc/http_response_parser.h"
|
||||||
|
|
||||||
|
namespace webcc {
|
||||||
|
|
||||||
|
// Response callback.
|
||||||
|
typedef std::function<void(HttpResponsePtr, Error, bool)> HttpResponseCallback;
|
||||||
|
|
||||||
|
// HTTP client session in asynchronous mode.
|
||||||
|
// A request will return without waiting for the response, the callback handler
|
||||||
|
// will be invoked when the response is received or timeout occurs.
|
||||||
|
// Don't use the same HttpAsyncClient object in multiple threads.
|
||||||
|
class HttpAsyncClientBase
|
||||||
|
: public std::enable_shared_from_this<HttpAsyncClientBase> {
|
||||||
|
public:
|
||||||
|
// The |buffer_size| is the bytes of the buffer for reading response.
|
||||||
|
// 0 means default value (e.g., 1024) will be used.
|
||||||
|
explicit HttpAsyncClientBase(boost::asio::io_context& io_context,
|
||||||
|
std::size_t buffer_size = 0);
|
||||||
|
|
||||||
|
WEBCC_DELETE_COPY_ASSIGN(HttpAsyncClientBase);
|
||||||
|
|
||||||
|
// Set the timeout seconds for reading response.
|
||||||
|
// The |seconds| is only effective when greater than 0.
|
||||||
|
void SetTimeout(int seconds);
|
||||||
|
|
||||||
|
// Asynchronously connect to the server, send the request, read the response,
|
||||||
|
// and call the |response_callback| when all these finish.
|
||||||
|
void Request(HttpRequestPtr request, HttpResponseCallback response_callback);
|
||||||
|
|
||||||
|
// Called by the user to cancel the request.
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using tcp = boost::asio::ip::tcp;
|
||||||
|
|
||||||
|
typedef tcp::resolver::results_type Endpoints;
|
||||||
|
|
||||||
|
typedef std::function<void(boost::system::error_code, std::size_t)>
|
||||||
|
ReadHandler;
|
||||||
|
|
||||||
|
typedef std::function<void(boost::system::error_code, tcp::endpoint)>
|
||||||
|
ConnectHandler;
|
||||||
|
|
||||||
|
typedef std::function<void(boost::system::error_code, std::size_t)>
|
||||||
|
WriteHandler;
|
||||||
|
|
||||||
|
// To enable_shared_from_this for both parent and derived.
|
||||||
|
// See https://stackoverflow.com/q/657155/6825348
|
||||||
|
template <typename Derived>
|
||||||
|
std::shared_ptr<Derived> shared_from_base() {
|
||||||
|
return std::static_pointer_cast<Derived>(shared_from_this());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void Resolve() = 0;
|
||||||
|
|
||||||
|
void DoResolve(const std::string& default_port);
|
||||||
|
void OnResolve(boost::system::error_code ec,
|
||||||
|
tcp::resolver::results_type results);
|
||||||
|
|
||||||
|
void DoConnect(const Endpoints& endpoints);
|
||||||
|
void OnConnect(boost::system::error_code ec, tcp::endpoint endpoint);
|
||||||
|
|
||||||
|
virtual void OnConnected() = 0;
|
||||||
|
|
||||||
|
void DoWrite();
|
||||||
|
void OnWrite(boost::system::error_code ec, std::size_t length);
|
||||||
|
|
||||||
|
void DoRead();
|
||||||
|
void OnRead(boost::system::error_code ec, std::size_t length);
|
||||||
|
|
||||||
|
void DoWaitDeadline();
|
||||||
|
void OnDeadline(boost::system::error_code ec);
|
||||||
|
|
||||||
|
// Terminate all the actors to shut down the connection.
|
||||||
|
void DoStop();
|
||||||
|
|
||||||
|
virtual void SocketAsyncConnect(const Endpoints& endpoints,
|
||||||
|
ConnectHandler&& handler) = 0;
|
||||||
|
|
||||||
|
virtual void SocketAsyncWrite(WriteHandler&& handler) = 0;
|
||||||
|
|
||||||
|
virtual void SocketAsyncReadSome(ReadHandler&& handler) = 0;
|
||||||
|
|
||||||
|
virtual void SocketClose(boost::system::error_code* ec) = 0;
|
||||||
|
|
||||||
|
tcp::resolver resolver_;
|
||||||
|
|
||||||
|
HttpRequestPtr request_;
|
||||||
|
|
||||||
|
std::vector<char> buffer_;
|
||||||
|
|
||||||
|
HttpResponsePtr response_;
|
||||||
|
std::unique_ptr<HttpResponseParser> response_parser_;
|
||||||
|
HttpResponseCallback response_callback_;
|
||||||
|
|
||||||
|
// Timer for the timeout control.
|
||||||
|
boost::asio::deadline_timer deadline_;
|
||||||
|
|
||||||
|
// Maximum seconds to wait before the client cancels the operation.
|
||||||
|
// Only for receiving response from server.
|
||||||
|
int timeout_seconds_;
|
||||||
|
|
||||||
|
// Request stopped due to timeout or socket error.
|
||||||
|
bool stopped_;
|
||||||
|
|
||||||
|
// If the error was caused by timeout or not.
|
||||||
|
// Will be passed to the response handler/callback.
|
||||||
|
bool timed_out_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webcc
|
||||||
|
|
||||||
|
#endif // WEBCC_HTTP_ASYNC_CLIENT_BASE_H_
|
||||||
@@ -20,9 +20,8 @@ void HttpClient::SocketWrite(const HttpRequest& request,
|
|||||||
boost::asio::write(socket_, request.ToBuffers(), *ec);
|
boost::asio::write(socket_, request.ToBuffers(), *ec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpClient::SocketAsyncReadSome(std::vector<char>& buffer,
|
void HttpClient::SocketAsyncReadSome(ReadHandler&& handler) {
|
||||||
ReadHandler handler) {
|
socket_.async_read_some(boost::asio::buffer(buffer_), std::move(handler));
|
||||||
socket_.async_read_some(boost::asio::buffer(buffer), handler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpClient::SocketClose(boost::system::error_code* ec) {
|
void HttpClient::SocketClose(boost::system::error_code* ec) {
|
||||||
|
|||||||
+2
-7
@@ -5,17 +5,13 @@
|
|||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
// HTTP client in synchronous mode.
|
// HTTP synchronous client.
|
||||||
// A request will not return until the response is received or timeout occurs.
|
|
||||||
// Don't use the same HttpClient object in multiple threads.
|
|
||||||
class HttpClient : public HttpClientBase {
|
class HttpClient : public HttpClientBase {
|
||||||
public:
|
public:
|
||||||
explicit HttpClient(std::size_t buffer_size = 0);
|
explicit HttpClient(std::size_t buffer_size = 0);
|
||||||
|
|
||||||
~HttpClient() = default;
|
~HttpClient() = default;
|
||||||
|
|
||||||
WEBCC_DELETE_COPY_ASSIGN(HttpClient);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Error Connect(const HttpRequest& request) final {
|
Error Connect(const HttpRequest& request) final {
|
||||||
return DoConnect(request, kHttpPort);
|
return DoConnect(request, kHttpPort);
|
||||||
@@ -27,8 +23,7 @@ class HttpClient : public HttpClientBase {
|
|||||||
void SocketWrite(const HttpRequest& request,
|
void SocketWrite(const HttpRequest& request,
|
||||||
boost::system::error_code* ec) final;
|
boost::system::error_code* ec) final;
|
||||||
|
|
||||||
void SocketAsyncReadSome(std::vector<char>& buffer,
|
void SocketAsyncReadSome(ReadHandler&& handler) final;
|
||||||
ReadHandler handler) final;
|
|
||||||
|
|
||||||
void SocketClose(boost::system::error_code* ec) final;
|
void SocketClose(boost::system::error_code* ec) final;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
#include "webcc/http_client_base.h"
|
#include "webcc/http_client_base.h"
|
||||||
|
|
||||||
#include "boost/asio/connect.hpp"
|
|
||||||
#include "boost/asio/read.hpp"
|
|
||||||
#include "boost/asio/write.hpp"
|
|
||||||
#include "boost/date_time/posix_time/posix_time.hpp"
|
#include "boost/date_time/posix_time/posix_time.hpp"
|
||||||
|
|
||||||
#include "webcc/logger.h"
|
#include "webcc/logger.h"
|
||||||
@@ -44,7 +41,7 @@ bool HttpClientBase::Request(const HttpRequest& request,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((error_ = SendReqeust(request)) != kNoError) {
|
if ((error_ = WriteReqeust(request)) != kNoError) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +84,7 @@ Error HttpClientBase::DoConnect(const HttpRequest& request,
|
|||||||
return kNoError;
|
return kNoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
Error HttpClientBase::SendReqeust(const HttpRequest& request) {
|
Error HttpClientBase::WriteReqeust(const HttpRequest& request) {
|
||||||
LOG_VERB("HTTP request:\n%s", request.Dump(4, "> ").c_str());
|
LOG_VERB("HTTP request:\n%s", request.Dump(4, "> ").c_str());
|
||||||
|
|
||||||
// NOTE:
|
// NOTE:
|
||||||
@@ -130,8 +127,8 @@ Error HttpClientBase::ReadResponse() {
|
|||||||
void HttpClientBase::DoReadResponse(Error* error) {
|
void HttpClientBase::DoReadResponse(Error* error) {
|
||||||
boost::system::error_code ec = boost::asio::error::would_block;
|
boost::system::error_code ec = boost::asio::error::would_block;
|
||||||
|
|
||||||
auto read_handler = [this, &ec, error](boost::system::error_code inner_ec,
|
auto handler = [this, &ec, error](boost::system::error_code inner_ec,
|
||||||
std::size_t length) {
|
std::size_t length) {
|
||||||
ec = inner_ec;
|
ec = inner_ec;
|
||||||
|
|
||||||
LOG_VERB("Socket async read handler.");
|
LOG_VERB("Socket async read handler.");
|
||||||
@@ -168,7 +165,7 @@ void HttpClientBase::DoReadResponse(Error* error) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SocketAsyncReadSome(buffer_, read_handler);
|
SocketAsyncReadSome(std::move(handler));
|
||||||
|
|
||||||
// Block until the asynchronous operation has completed.
|
// Block until the asynchronous operation has completed.
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
|
// The base class of synchronous HTTP clients.
|
||||||
|
// In synchronous mode, a request won't return until the response is received
|
||||||
|
// or timeout occurs.
|
||||||
|
// Please don't use the same client object in multiple threads.
|
||||||
class HttpClientBase {
|
class HttpClientBase {
|
||||||
public:
|
public:
|
||||||
// The |buffer_size| is the bytes of the buffer for reading response.
|
// The |buffer_size| is the bytes of the buffer for reading response.
|
||||||
@@ -38,6 +42,16 @@ class HttpClientBase {
|
|||||||
|
|
||||||
HttpResponsePtr response() const { return response_; }
|
HttpResponsePtr response() const { return response_; }
|
||||||
|
|
||||||
|
int response_status() const {
|
||||||
|
assert(response_);
|
||||||
|
return response_->status();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& response_content() const {
|
||||||
|
assert(response_);
|
||||||
|
return response_->content();
|
||||||
|
}
|
||||||
|
|
||||||
bool timed_out() const { return timed_out_; }
|
bool timed_out() const { return timed_out_; }
|
||||||
|
|
||||||
Error error() const { return error_; }
|
Error error() const { return error_; }
|
||||||
@@ -48,12 +62,11 @@ class HttpClientBase {
|
|||||||
typedef std::function<void(boost::system::error_code, std::size_t)>
|
typedef std::function<void(boost::system::error_code, std::size_t)>
|
||||||
ReadHandler;
|
ReadHandler;
|
||||||
|
|
||||||
|
virtual Error Connect(const HttpRequest& request) = 0;
|
||||||
|
|
||||||
Error DoConnect(const HttpRequest& request, const std::string& default_port);
|
Error DoConnect(const HttpRequest& request, const std::string& default_port);
|
||||||
|
|
||||||
boost::asio::io_context io_context_;
|
Error WriteReqeust(const HttpRequest& request);
|
||||||
|
|
||||||
private:
|
|
||||||
Error SendReqeust(const HttpRequest& request);
|
|
||||||
|
|
||||||
Error ReadResponse();
|
Error ReadResponse();
|
||||||
|
|
||||||
@@ -64,19 +77,18 @@ class HttpClientBase {
|
|||||||
|
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
virtual Error Connect(const HttpRequest& request) = 0;
|
|
||||||
|
|
||||||
virtual void SocketConnect(const Endpoints& endpoints,
|
virtual void SocketConnect(const Endpoints& endpoints,
|
||||||
boost::system::error_code* ec) = 0;
|
boost::system::error_code* ec) = 0;
|
||||||
|
|
||||||
virtual void SocketWrite(const HttpRequest& request,
|
virtual void SocketWrite(const HttpRequest& request,
|
||||||
boost::system::error_code* ec) = 0;
|
boost::system::error_code* ec) = 0;
|
||||||
|
|
||||||
virtual void SocketAsyncReadSome(std::vector<char>& buffer,
|
virtual void SocketAsyncReadSome(ReadHandler&& handler) = 0;
|
||||||
ReadHandler handler) = 0;
|
|
||||||
|
|
||||||
virtual void SocketClose(boost::system::error_code* ec) = 0;
|
virtual void SocketClose(boost::system::error_code* ec) = 0;
|
||||||
|
|
||||||
|
boost::asio::io_context io_context_;
|
||||||
|
|
||||||
std::vector<char> buffer_;
|
std::vector<char> buffer_;
|
||||||
|
|
||||||
HttpResponsePtr response_;
|
HttpResponsePtr response_;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class HttpMessage {
|
|||||||
|
|
||||||
// Make the message (e.g., update start line).
|
// Make the message (e.g., update start line).
|
||||||
// Must be called before ToBuffers()!
|
// Must be called before ToBuffers()!
|
||||||
virtual void Make() = 0;
|
virtual void Prepare() = 0;
|
||||||
|
|
||||||
// Convert the message into a vector of buffers. The buffers do not own the
|
// Convert the message into a vector of buffers. The buffers do not own the
|
||||||
// underlying memory blocks, therefore the message object must remain valid
|
// underlying memory blocks, therefore the message object must remain valid
|
||||||
|
|||||||
+18
-1
@@ -9,7 +9,7 @@ HttpRequest::HttpRequest(const std::string& method,
|
|||||||
: method_(method), url_(url), host_(host), port_(port) {
|
: method_(method), url_(url), host_(host), port_(port) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequest::Make() {
|
void HttpRequest::Prepare() {
|
||||||
start_line_ = method_;
|
start_line_ = method_;
|
||||||
start_line_ += " ";
|
start_line_ += " ";
|
||||||
start_line_ += url_;
|
start_line_ += url_;
|
||||||
@@ -32,4 +32,21 @@ void HttpRequest::Make() {
|
|||||||
SetHeader(http::headers::kUserAgent, "Webcc/" WEBCC_VERSION);
|
SetHeader(http::headers::kUserAgent, "Webcc/" WEBCC_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
HttpRequestPtr HttpRequest::Make(const std::string& method,
|
||||||
|
const std::string& url,
|
||||||
|
const std::string& host,
|
||||||
|
const std::string& port,
|
||||||
|
bool prepare) {
|
||||||
|
HttpRequestPtr request{
|
||||||
|
new HttpRequest{ method, url, host, port }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (prepare) {
|
||||||
|
request->Prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|||||||
+11
-3
@@ -8,8 +8,11 @@
|
|||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
|
class HttpRequest;
|
||||||
class HttpRequestParser;
|
class HttpRequestParser;
|
||||||
|
|
||||||
|
typedef std::shared_ptr<HttpRequest> HttpRequestPtr;
|
||||||
|
|
||||||
class HttpRequest : public HttpMessage {
|
class HttpRequest : public HttpMessage {
|
||||||
public:
|
public:
|
||||||
HttpRequest() = default;
|
HttpRequest() = default;
|
||||||
@@ -36,8 +39,15 @@ class HttpRequest : public HttpMessage {
|
|||||||
return port_.empty() ? default_port : port_;
|
return port_.empty() ? default_port : port_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare payload.
|
||||||
// Compose start line, set Host header, etc.
|
// Compose start line, set Host header, etc.
|
||||||
void Make() override;
|
void Prepare() override;
|
||||||
|
|
||||||
|
static HttpRequestPtr Make(const std::string& method,
|
||||||
|
const std::string& url,
|
||||||
|
const std::string& host,
|
||||||
|
const std::string& port = "",
|
||||||
|
bool prepare = true);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class HttpRequestParser;
|
friend class HttpRequestParser;
|
||||||
@@ -57,8 +67,6 @@ class HttpRequest : public HttpMessage {
|
|||||||
std::string port_;
|
std::string port_;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<HttpRequest> HttpRequestPtr;
|
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|
||||||
#endif // WEBCC_HTTP_REQUEST_H_
|
#endif // WEBCC_HTTP_REQUEST_H_
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const std::string& ToString(int status) {
|
|||||||
|
|
||||||
} // namespace status_strings
|
} // namespace status_strings
|
||||||
|
|
||||||
void HttpResponse::Make() {
|
void HttpResponse::Prepare() {
|
||||||
start_line_ = status_strings::ToString(status_);
|
start_line_ = status_strings::ToString(status_);
|
||||||
|
|
||||||
// NOTE: C++11 requires a space between literal and string macro.
|
// NOTE: C++11 requires a space between literal and string macro.
|
||||||
@@ -72,7 +72,7 @@ HttpResponse HttpResponse::Fault(http::Status status) {
|
|||||||
HttpResponse response;
|
HttpResponse response;
|
||||||
response.set_status(status);
|
response.set_status(status);
|
||||||
|
|
||||||
response.Make();
|
response.Prepare();
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class HttpResponse : public HttpMessage {
|
|||||||
void set_status(int status) { status_ = status; }
|
void set_status(int status) { status_ = status; }
|
||||||
|
|
||||||
// Set start line according to status code.
|
// Set start line according to status code.
|
||||||
void Make() override;
|
void Prepare() override;
|
||||||
|
|
||||||
// Get a fault response when HTTP status is not OK.
|
// Get a fault response when HTTP status is not OK.
|
||||||
// TODO: Avoid copy.
|
// TODO: Avoid copy.
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ void HttpSession::SetResponseContent(std::string&& content,
|
|||||||
|
|
||||||
void HttpSession::SendResponse(http::Status status) {
|
void HttpSession::SendResponse(http::Status status) {
|
||||||
response_.set_status(status);
|
response_.set_status(status);
|
||||||
response_.Make();
|
response_.Prepare();
|
||||||
DoWrite();
|
DoWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#include "webcc/http_ssl_async_client.h"
|
||||||
|
|
||||||
|
#include "boost/asio/connect.hpp"
|
||||||
|
|
||||||
|
#include "webcc/logger.h"
|
||||||
|
|
||||||
|
using tcp = boost::asio::ip::tcp;
|
||||||
|
namespace ssl = boost::asio::ssl;
|
||||||
|
|
||||||
|
namespace webcc {
|
||||||
|
|
||||||
|
HttpSslAsyncClient::HttpSslAsyncClient(boost::asio::io_context& io_context,
|
||||||
|
std::size_t buffer_size,
|
||||||
|
bool ssl_verify)
|
||||||
|
: HttpAsyncClientBase(io_context, buffer_size),
|
||||||
|
ssl_context_(ssl::context::sslv23),
|
||||||
|
ssl_socket_(io_context, ssl_context_),
|
||||||
|
ssl_verify_(ssl_verify) {
|
||||||
|
// Use the default paths for finding CA certificates.
|
||||||
|
ssl_context_.set_default_verify_paths();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpSslAsyncClient::Resolve() {
|
||||||
|
DoResolve(kHttpSslPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpSslAsyncClient::DoHandshake() {
|
||||||
|
if (ssl_verify_) {
|
||||||
|
ssl_socket_.set_verify_mode(ssl::verify_peer);
|
||||||
|
} else {
|
||||||
|
ssl_socket_.set_verify_mode(ssl::verify_none);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssl_socket_.set_verify_callback(ssl::rfc2818_verification(request_->host()));
|
||||||
|
|
||||||
|
ssl_socket_.async_handshake(ssl::stream_base::client,
|
||||||
|
std::bind(&HttpSslAsyncClient::OnHandshake,
|
||||||
|
shared_from_this(),
|
||||||
|
std::placeholders::_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpSslAsyncClient::OnHandshake(boost::system::error_code ec) {
|
||||||
|
if (ec) {
|
||||||
|
LOG_ERRO("Handshake error (%s).", ec.message().c_str());
|
||||||
|
response_callback_(response_, kHandshakeError, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DoWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpSslAsyncClient::SocketAsyncConnect(const Endpoints& endpoints,
|
||||||
|
ConnectHandler&& handler) {
|
||||||
|
boost::asio::async_connect(ssl_socket_.lowest_layer(), endpoints,
|
||||||
|
std::move(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpSslAsyncClient::SocketAsyncWrite(WriteHandler&& handler) {
|
||||||
|
boost::asio::async_write(ssl_socket_, request_->ToBuffers(),
|
||||||
|
std::move(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpSslAsyncClient::SocketAsyncReadSome(ReadHandler&& handler) {
|
||||||
|
ssl_socket_.async_read_some(boost::asio::buffer(buffer_), std::move(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpSslAsyncClient::SocketClose(boost::system::error_code* ec) {
|
||||||
|
ssl_socket_.lowest_layer().close(*ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webcc
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#ifndef WEBCC_HTTP_SSL_ASYNC_CLIENT_H_
|
||||||
|
#define WEBCC_HTTP_SSL_ASYNC_CLIENT_H_
|
||||||
|
|
||||||
|
#include "webcc/http_async_client_base.h"
|
||||||
|
|
||||||
|
#include "boost/asio/ssl.hpp"
|
||||||
|
|
||||||
|
namespace webcc {
|
||||||
|
|
||||||
|
// HTTP SSL (a.k.a., HTTPS) asynchronous client.
|
||||||
|
class HttpSslAsyncClient : public HttpAsyncClientBase {
|
||||||
|
public:
|
||||||
|
// SSL verification (|ssl_verify|) needs CA certificates to be found
|
||||||
|
// in the default verify paths of OpenSSL. On Windows, it means you need to
|
||||||
|
// set environment variable SSL_CERT_FILE properly.
|
||||||
|
explicit HttpSslAsyncClient(boost::asio::io_context& io_context,
|
||||||
|
std::size_t buffer_size = 0,
|
||||||
|
bool ssl_verify = true);
|
||||||
|
|
||||||
|
~HttpSslAsyncClient() = default;
|
||||||
|
|
||||||
|
// See https://stackoverflow.com/q/657155/6825348
|
||||||
|
std::shared_ptr<HttpSslAsyncClient> shared_from_this() {
|
||||||
|
return shared_from_base<HttpSslAsyncClient>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Resolve() final;
|
||||||
|
|
||||||
|
// Override to do handshake after connected.
|
||||||
|
void OnConnected() final {
|
||||||
|
DoHandshake();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoHandshake();
|
||||||
|
void OnHandshake(boost::system::error_code ec);
|
||||||
|
|
||||||
|
void SocketAsyncConnect(const Endpoints& endpoints,
|
||||||
|
ConnectHandler&& handler) final;
|
||||||
|
|
||||||
|
void SocketAsyncWrite(WriteHandler&& handler) final;
|
||||||
|
|
||||||
|
void SocketAsyncReadSome(ReadHandler&& handler) final;
|
||||||
|
|
||||||
|
void SocketClose(boost::system::error_code* ec) final;
|
||||||
|
|
||||||
|
boost::asio::ssl::context ssl_context_;
|
||||||
|
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket_;
|
||||||
|
|
||||||
|
// Verify the certificate of the peer (remote server) or not.
|
||||||
|
bool ssl_verify_;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::shared_ptr<HttpSslAsyncClient> HttpSslAsyncClientPtr;
|
||||||
|
|
||||||
|
} // namespace webcc
|
||||||
|
|
||||||
|
#endif // WEBCC_HTTP_SSL_ASYNC_CLIENT_H_
|
||||||
@@ -61,9 +61,8 @@ void HttpSslClient::SocketWrite(const HttpRequest& request,
|
|||||||
boost::asio::write(ssl_socket_, request.ToBuffers(), *ec);
|
boost::asio::write(ssl_socket_, request.ToBuffers(), *ec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSslClient::SocketAsyncReadSome(std::vector<char>& buffer,
|
void HttpSslClient::SocketAsyncReadSome(ReadHandler&& handler) {
|
||||||
ReadHandler handler) {
|
ssl_socket_.async_read_some(boost::asio::buffer(buffer_), std::move(handler));
|
||||||
ssl_socket_.async_read_some(boost::asio::buffer(buffer), handler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSslClient::SocketClose(boost::system::error_code* ec) {
|
void HttpSslClient::SocketClose(boost::system::error_code* ec) {
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
// HTTP SSL (a.k.a., HTTPS) client session in synchronous mode.
|
// HTTP SSL (a.k.a., HTTPS) synchronous client.
|
||||||
// A request will not return until the response is received or timeout occurs.
|
|
||||||
// Don't use the same HttpSslClient object in multiple threads.
|
|
||||||
class HttpSslClient : public HttpClientBase {
|
class HttpSslClient : public HttpClientBase {
|
||||||
public:
|
public:
|
||||||
// SSL verification (|ssl_verify|) needs CA certificates to be found
|
// SSL verification (|ssl_verify|) needs CA certificates to be found
|
||||||
@@ -19,8 +17,6 @@ class HttpSslClient : public HttpClientBase {
|
|||||||
|
|
||||||
~HttpSslClient() = default;
|
~HttpSslClient() = default;
|
||||||
|
|
||||||
WEBCC_DELETE_COPY_ASSIGN(HttpSslClient);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Error Handshake(const std::string& host);
|
Error Handshake(const std::string& host);
|
||||||
|
|
||||||
@@ -33,8 +29,7 @@ class HttpSslClient : public HttpClientBase {
|
|||||||
void SocketWrite(const HttpRequest& request,
|
void SocketWrite(const HttpRequest& request,
|
||||||
boost::system::error_code* ec) final;
|
boost::system::error_code* ec) final;
|
||||||
|
|
||||||
void SocketAsyncReadSome(std::vector<char>& buffer,
|
void SocketAsyncReadSome(ReadHandler&& handler) final;
|
||||||
ReadHandler handler) final;
|
|
||||||
|
|
||||||
void SocketClose(boost::system::error_code* ec) final;
|
void SocketClose(boost::system::error_code* ec) final;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -168,7 +168,7 @@ void LogWrite(int level, const char* file, int line, const char* format, ...) {
|
|||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
|
|
||||||
fprintf(stderr, "%s, %s, %7s, %24s, %4d, ",
|
fprintf(stderr, "%s, %s, %7s, %25s, %4d, ",
|
||||||
timestamp.c_str(), kLevelNames[level], thread_id.c_str(),
|
timestamp.c_str(), kLevelNames[level], thread_id.c_str(),
|
||||||
file, line);
|
file, line);
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ void RestAsyncClient::Request(const std::string& method,
|
|||||||
http::charsets::kUtf8);
|
http::charsets::kUtf8);
|
||||||
}
|
}
|
||||||
|
|
||||||
http_request->Make();
|
http_request->Prepare();
|
||||||
|
|
||||||
HttpAsyncClientPtr http_async_client{
|
HttpAsyncClientPtr http_async_client{
|
||||||
new HttpAsyncClient(io_context_, buffer_size_)
|
new HttpAsyncClient(io_context_, buffer_size_)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ bool RestClient::Request(const std::string& method, const std::string& url,
|
|||||||
http::charsets::kUtf8);
|
http::charsets::kUtf8);
|
||||||
}
|
}
|
||||||
|
|
||||||
http_request.Make();
|
http_request.Prepare();
|
||||||
|
|
||||||
if (!http_client_.Request(http_request, buffer_size)) {
|
if (!http_client_.Request(http_request, buffer_size)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+3
-3
@@ -16,9 +16,9 @@ class RestClient {
|
|||||||
public:
|
public:
|
||||||
// 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);
|
std::size_t buffer_size = 0);
|
||||||
|
|
||||||
~RestClient() = default;
|
~RestClient() = default;
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ bool RestSslClient::Request(const std::string& method, const std::string& url,
|
|||||||
http::charsets::kUtf8);
|
http::charsets::kUtf8);
|
||||||
}
|
}
|
||||||
|
|
||||||
http_request.Make();
|
http_request.Prepare();
|
||||||
|
|
||||||
if (!http_client_.Request(http_request, buffer_size)) {
|
if (!http_client_.Request(http_request, buffer_size)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ void SoapAsyncClient::Request(const std::string& operation,
|
|||||||
}
|
}
|
||||||
|
|
||||||
http_request->SetHeader(kSoapAction, operation);
|
http_request->SetHeader(kSoapAction, operation);
|
||||||
http_request->Make();
|
http_request->Prepare();
|
||||||
|
|
||||||
HttpAsyncClientPtr http_async_client{
|
HttpAsyncClientPtr http_async_client{
|
||||||
new HttpAsyncClient(io_context_, buffer_size_)
|
new HttpAsyncClient(io_context_, buffer_size_)
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ bool SoapClient::Request(const std::string& operation,
|
|||||||
|
|
||||||
http_request.SetHeader(kSoapAction, operation);
|
http_request.SetHeader(kSoapAction, operation);
|
||||||
|
|
||||||
http_request.Make();
|
http_request.Prepare();
|
||||||
|
|
||||||
if (!http_client_.Request(http_request, buffer_size)) {
|
if (!http_client_.Request(http_request, buffer_size)) {
|
||||||
error_ = http_client_.error();
|
error_ = http_client_.error();
|
||||||
|
|||||||
+3
-5
@@ -22,8 +22,7 @@ void AdjustHostPort(std::string& host, std::string& port) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintEndpoint(std::ostream& ostream,
|
void PrintEndpoint(std::ostream& ostream, const TcpEndpoint& endpoint) {
|
||||||
const boost::asio::ip::tcp::endpoint& endpoint) {
|
|
||||||
ostream << endpoint;
|
ostream << endpoint;
|
||||||
if (endpoint.protocol() == tcp::v4()) {
|
if (endpoint.protocol() == tcp::v4()) {
|
||||||
ostream << ", v4";
|
ostream << ", v4";
|
||||||
@@ -32,8 +31,7 @@ void PrintEndpoint(std::ostream& ostream,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintEndpoints(std::ostream& ostream,
|
void PrintEndpoints(std::ostream& ostream, const TcpEndpoints& endpoints) {
|
||||||
const tcp::resolver::results_type& endpoints) {
|
|
||||||
ostream << "Endpoints: " << endpoints.size() << std::endl;
|
ostream << "Endpoints: " << endpoints.size() << std::endl;
|
||||||
tcp::resolver::results_type::iterator it = endpoints.begin();
|
tcp::resolver::results_type::iterator it = endpoints.begin();
|
||||||
for (; it != endpoints.end(); ++it) {
|
for (; it != endpoints.end(); ++it) {
|
||||||
@@ -43,7 +41,7 @@ void PrintEndpoints(std::ostream& ostream,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint) {
|
std::string EndpointToString(const TcpEndpoint& endpoint) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
PrintEndpoint(ss, endpoint);
|
PrintEndpoint(ss, endpoint);
|
||||||
return ss.str();
|
return ss.str();
|
||||||
|
|||||||
+6
-6
@@ -12,14 +12,14 @@ namespace webcc {
|
|||||||
// If |port| is empty, try to extract it from |host| (separated by ':').
|
// If |port| is empty, try to extract it from |host| (separated by ':').
|
||||||
void AdjustHostPort(std::string& host, std::string& port);
|
void AdjustHostPort(std::string& host, std::string& port);
|
||||||
|
|
||||||
void PrintEndpoint(std::ostream& ostream,
|
typedef boost::asio::ip::tcp::endpoint TcpEndpoint;
|
||||||
const boost::asio::ip::tcp::endpoint& endpoint);
|
typedef boost::asio::ip::tcp::resolver::results_type TcpEndpoints;
|
||||||
|
|
||||||
void PrintEndpoints(
|
void PrintEndpoint(std::ostream& ostream, const TcpEndpoint& endpoint);
|
||||||
std::ostream& ostream,
|
|
||||||
const boost::asio::ip::tcp::resolver::results_type& endpoints);
|
|
||||||
|
|
||||||
std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint);
|
void PrintEndpoints(std::ostream& ostream, const TcpEndpoints& endpoints);
|
||||||
|
|
||||||
|
std::string EndpointToString(const TcpEndpoint& endpoint);
|
||||||
|
|
||||||
// Get the timestamp for HTTP Date header field.
|
// Get the timestamp for HTTP Date header field.
|
||||||
// E.g., Wed, 21 Oct 2015 07:28:00 GMT
|
// E.g., Wed, 21 Oct 2015 07:28:00 GMT
|
||||||
|
|||||||
Reference in New Issue
Block a user