/*
* COPYRIGHT (C) 2017-2021, zhllxt
*
* author : zhllxt
* email : 37792738@qq.com
*
* Distributed under the GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007
* (See accompanying file LICENSE or see )
*/
#if defined(ASIO2_USE_SSL)
#ifndef __ASIO2_HTTPS_CLIENT_HPP__
#define __ASIO2_HTTPS_CLIENT_HPP__
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
#pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
#include
#include
#include
#include
namespace asio2::detail
{
ASIO2_CLASS_FORWARD_DECLARE_BASE;
ASIO2_CLASS_FORWARD_DECLARE_TCP_BASE;
ASIO2_CLASS_FORWARD_DECLARE_TCP_CLIENT;
template
class https_client_impl_t
: public ssl_context_cp
, public http_client_impl_t
, public ssl_stream_cp
{
ASIO2_CLASS_FRIEND_DECLARE_BASE;
ASIO2_CLASS_FRIEND_DECLARE_TCP_BASE;
ASIO2_CLASS_FRIEND_DECLARE_TCP_CLIENT;
public:
using super = http_client_impl_t ;
using self = https_client_impl_t;
using body_type = typename args_t::body_t;
using buffer_type = typename args_t::buffer_t;
using ssl_context_comp = ssl_context_cp;
using ssl_stream_comp = ssl_stream_cp ;
using super::send;
using super::async_send;
public:
/**
* @constructor
*/
template
explicit https_client_impl_t(
asio::ssl::context::method method = asio::ssl::context::sslv23,
Args&&... args
)
: ssl_context_comp(method)
, super(std::forward(args)...)
, ssl_stream_comp(this->io_, *this, asio::ssl::stream_base::client)
{
}
/**
* @destructor
*/
~https_client_impl_t()
{
this->stop();
}
/**
* @function : get the stream object refrence
*/
inline typename ssl_stream_comp::stream_type & stream()
{
ASIO2_ASSERT(bool(this->ssl_stream_));
return (*(this->ssl_stream_));
}
public:
template
static inline http::response_t execute(const asio::ssl::context& ctx, String&& host, StrOrInt&& port,
http::request_t& req, std::chrono::duration timeout, error_code& ec)
{
http::parser parser;
try
{
// set default result to unknown
parser.get().result(http::status::unknown);
parser.eager(true);
// First assign default value timed_out to ec
ec = asio::error::timed_out;
// The io_context is required for all I/O
asio::io_context ioc;
// These objects perform our I/O
asio::ip::tcp::resolver resolver{ ioc };
asio::ip::tcp::socket socket{ ioc };
asio::ssl::stream stream(socket, const_cast(ctx));
// This buffer is used for reading and must be persisted
Buffer buffer;
// Look up the domain name
resolver.async_resolve(std::forward(host), to_string(std::forward(port)),
[&](const error_code& ec1, const asio::ip::tcp::resolver::results_type& endpoints) mutable
{
if (ec1) { ec = ec1; return; }
// Make the connection on the IP address we get from a lookup
asio::async_connect(socket, endpoints,
[&](const error_code & ec2, const asio::ip::tcp::endpoint&) mutable
{
if (ec2) { ec = ec2; return; }
stream.async_handshake(asio::ssl::stream_base::client,
[&](const error_code& ec3) mutable
{
if (ec3) { ec = ec3; return; }
http::async_write(stream, req, [&](const error_code& ec4, std::size_t) mutable
{
// can't use stream.shutdown(),in some case the shutdowm will blocking forever.
if (ec4) { ec = ec4; stream.async_shutdown([](const error_code&) {}); return; }
// Then start asynchronous reading
http::async_read(stream, buffer, parser,
[&](const error_code& ec5, std::size_t) mutable
{
// Reading completed, assign the read the result to ec
// If the code does not execute into here, the ec value
// is the default value timed_out.
ec = ec5;
stream.async_shutdown([](const error_code&) mutable {});
});
});
});
});
});
// timedout run
ioc.run_for(timeout);
error_code ec_ignore{};
// Gracefully close the socket
socket.shutdown(asio::ip::tcp::socket::shutdown_both, ec_ignore);
socket.close(ec_ignore);
}
catch (system_error & e)
{
ec = e.code();
}
return parser.release();
}
template
static inline http::response_t execute(const asio::ssl::context& ctx, String&& host, StrOrInt&& port,
http::request_t& req, std::chrono::duration timeout)
{
error_code ec;
http::response_t rep = execute(ctx,
std::forward(host), std::forward(port), req, timeout, ec);
asio::detail::throw_error(ec);
return rep;
}
template
static inline http::response_t execute(String&& host, StrOrInt&& port,
http::request_t& req, error_code& ec)
{
ec.clear();
return execute(asio::ssl::context{ asio::ssl::context::sslv23 },
std::forward(host), std::forward(port),
req, std::chrono::milliseconds(http_execute_timeout), ec);
}
template
static inline http::response_t execute(String&& host, StrOrInt&& port,
http::request_t& req)
{
error_code ec;
http::response_t rep = execute(
std::forward(host), std::forward(port), req, ec);
asio::detail::throw_error(ec);
return rep;
}
/**
* @function : blocking execute the http request until it is returned on success or failure
* You need to encode the "url"(by url_encode) before calling this function
*/
template
static inline http::response_t execute(std::string_view url, error_code& ec)
{
return execute(url, std::chrono::milliseconds(http_execute_timeout), ec);
}
/**
* @function : blocking execute the http request until it is returned on success or failure
* You need to encode the "url"(by url_encode) before calling this function
*/
template
static inline http::response_t execute(std::string_view url)
{
error_code ec;
http::response_t rep = execute(url, ec);
asio::detail::throw_error(ec);
return rep;
}
/**
* @function : blocking execute the http request until it is returned on success or failure
* You need to encode the "url"(by url_encode) before calling this function
*/
template
static inline http::response_t execute(std::string_view url,
std::chrono::duration timeout, error_code& ec)
{
return execute(asio::ssl::context{ asio::ssl::context::sslv23 },
url, timeout, ec);
}
/**
* @function : blocking execute the http request until it is returned on success or failure
* You need to encode the "url"(by url_encode) before calling this function
*/
template
static inline http::response_t execute(std::string_view url,
std::chrono::duration timeout)
{
error_code ec;
http::response_t rep = execute(url, timeout, ec);
asio::detail::throw_error(ec);
return rep;
}
/**
* @function : blocking execute the http request until it is returned on success or failure
* You need to encode the "url"(by url_encode) before calling this function
*/
template
static inline http::response_t execute(const asio::ssl::context& ctx, std::string_view url,
std::chrono::duration timeout, error_code& ec)
{
ec.clear();
http::request_t req = http::make_request(url, ec);
if (ec) return http::response_t{ http::status::unknown, 11};
std::string_view host = http::url_to_host(url);
std::string_view port = http::url_to_port(url);
return execute(ctx,
host, port, req, timeout, ec);
}
/**
* @function : blocking execute the http request until it is returned on success or failure
* You need to encode the "url"(by url_encode) before calling this function
*/
template
static inline http::response_t execute(const asio::ssl::context& ctx, std::string_view url,
std::chrono::duration timeout)
{
error_code ec;
http::response_t rep = execute(ctx, url, timeout, ec);
asio::detail::throw_error(ec);
return rep;
}
/**
* @function : blocking execute the http request until it is returned on success or failure
* You need to encode the "target"(by url_encode) before calling this function
*/
template
static inline http::response_t execute(String&& host, StrOrInt&& port,
std::string_view target, error_code& ec)
{
return execute(
std::forward(host), std::forward(port),
target, std::chrono::milliseconds(http_execute_timeout), ec);
}
/**
* @function : blocking execute the http request until it is returned on success or failure
* You need to encode the "target"(by url_encode) before calling this function
*/
template
static inline http::response_t execute(String&& host, StrOrInt&& port,
std::string_view target)
{
error_code ec;
http::response_t rep = execute(
std::forward(host), std::forward(port), target, ec);
asio::detail::throw_error(ec);
return rep;
}
/**
* @function : blocking execute the http request until it is returned on success or failure
* You need to encode the "target"(by url_encode) before calling this function
*/
template
static inline http::response_t execute(String&& host, StrOrInt&& port,
std::string_view target, std::chrono::duration timeout, error_code& ec)
{
using host_type = std::remove_cv_t>;
using port_type = std::remove_cv_t>;
ec.clear();
http::request_t req = http::make_request<
const host_type&, const port_type&, Body, Fields>(
const_cast(host), const_cast(port),
target);
return execute(asio::ssl::context{ asio::ssl::context::sslv23 },
std::forward(host), std::forward(port),
req, timeout, ec);
}
/**
* @function : blocking execute the http request until it is returned on success or failure
* You need to encode the "target"(by url_encode) before calling this function
*/
template
static inline http::response_t execute(String&& host, StrOrInt&& port,
std::string_view target, std::chrono::duration timeout)
{
error_code ec;
http::response_t rep = execute(
std::forward(host), std::forward(port), target, timeout, ec);
asio::detail::throw_error(ec);
return rep;
}
// ----------------------------------------------------------------------------------------
public:
/**
* @function : bind ssl handshake listener
* @param : fun - a user defined callback function
* Function signature : void(asio2::error_code ec)
*/
template
inline derived_t & bind_handshake(F&& fun, C&&... obj)
{
this->listener_.bind(event_type::handshake,
observer_t(std::forward(fun), std::forward(obj)...));
return (this->derived());
}
protected:
template
inline void _do_init(condition_wrap condition)
{
super::_do_init(condition);
this->derived()._ssl_init(condition, this->socket_, *this);
}
inline void _handle_disconnect(const error_code& ec, std::shared_ptr this_ptr)
{
this->derived()._rdc_stop();
this->derived()._ssl_stop(this_ptr, [this, ec, this_ptr]() mutable
{
super::_handle_disconnect(ec, std::move(this_ptr));
});
}
template
inline void _handle_connect(const error_code & ec, std::shared_ptr this_ptr,
condition_wrap condition)
{
set_last_error(ec);
if (ec)
return this->derived()._done_connect(ec, std::move(this_ptr), std::move(condition));
this->derived()._ssl_start(this_ptr, condition, this->socket_, *this);
this->derived()._post_handshake(std::move(this_ptr), std::move(condition));
}
inline void _fire_handshake(std::shared_ptr& this_ptr, error_code ec)
{
// the _fire_handshake must be executed in the thread 0.
ASIO2_ASSERT(this->derived().io().strand().running_in_this_thread());
detail::ignore_unused(this_ptr);
this->listener_.notify(event_type::handshake, ec);
}
protected:
};
}
namespace asio2
{
template
class https_client_t : public detail::https_client_impl_t
{
public:
using detail::https_client_impl_t::https_client_impl_t;
};
class https_client : public https_client_t
{
public:
using https_client_t::https_client_t;
};
}
#include
#endif // !__ASIO2_HTTPS_CLIENT_HPP__
#endif