Add a base class for http async clients; refine examples.
parent
9ecec5de0b
commit
43eaf90621
@ -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;
|
||||
}
|
@ -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_
|
@ -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_
|
Loading…
Reference in New Issue