mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2025-01-20 20:47:15 +01:00
1198 lines
40 KiB
C++
1198 lines
40 KiB
C++
/*
|
|
* Copyright (c) 2015, Peter Thorson. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of the WebSocket++ Project nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#ifndef WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP
|
|
#define WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP
|
|
|
|
#include <websocketpp/transport/asio/base.hpp>
|
|
|
|
#include <websocketpp/transport/base/connection.hpp>
|
|
|
|
#include <websocketpp/logger/levels.hpp>
|
|
#include <websocketpp/http/constants.hpp>
|
|
|
|
#include <websocketpp/base64/base64.hpp>
|
|
#include <websocketpp/error.hpp>
|
|
#include <websocketpp/uri.hpp>
|
|
|
|
#include <websocketpp/common/asio.hpp>
|
|
#include <websocketpp/common/chrono.hpp>
|
|
#include <websocketpp/common/cpp11.hpp>
|
|
#include <websocketpp/common/memory.hpp>
|
|
#include <websocketpp/common/functional.hpp>
|
|
#include <websocketpp/common/connection_hdl.hpp>
|
|
|
|
#include <istream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace websocketpp {
|
|
namespace transport {
|
|
namespace asio {
|
|
|
|
typedef lib::function<void(connection_hdl)> tcp_init_handler;
|
|
|
|
/// Asio based connection transport component
|
|
/**
|
|
* transport::asio::connection implements a connection transport component using
|
|
* Asio that works with the transport::asio::endpoint endpoint transport
|
|
* component.
|
|
*/
|
|
template <typename config>
|
|
class connection : public config::socket_type::socket_con_type {
|
|
public:
|
|
/// Type of this connection transport component
|
|
typedef connection<config> type;
|
|
/// Type of a shared pointer to this connection transport component
|
|
typedef lib::shared_ptr<type> ptr;
|
|
|
|
/// Type of the socket connection component
|
|
typedef typename config::socket_type::socket_con_type socket_con_type;
|
|
/// Type of a shared pointer to the socket connection component
|
|
typedef typename socket_con_type::ptr socket_con_ptr;
|
|
/// Type of this transport's access logging policy
|
|
typedef typename config::alog_type alog_type;
|
|
/// Type of this transport's error logging policy
|
|
typedef typename config::elog_type elog_type;
|
|
|
|
typedef typename config::request_type request_type;
|
|
typedef typename request_type::ptr request_ptr;
|
|
typedef typename config::response_type response_type;
|
|
typedef typename response_type::ptr response_ptr;
|
|
|
|
/// Type of a pointer to the Asio io_service being used
|
|
typedef lib::asio::io_service * io_service_ptr;
|
|
/// Type of a pointer to the Asio io_service::strand being used
|
|
typedef lib::shared_ptr<lib::asio::io_service::strand> strand_ptr;
|
|
/// Type of a pointer to the Asio timer class
|
|
typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr;
|
|
|
|
// connection is friends with its associated endpoint to allow the endpoint
|
|
// to call private/protected utility methods that we don't want to expose
|
|
// to the public api.
|
|
friend class endpoint<config>;
|
|
|
|
// generate and manage our own io_service
|
|
explicit connection(bool is_server, const lib::shared_ptr<alog_type> & alog, const lib::shared_ptr<elog_type> & elog)
|
|
: m_is_server(is_server)
|
|
, m_alog(alog)
|
|
, m_elog(elog)
|
|
{
|
|
m_alog->write(log::alevel::devel,"asio con transport constructor");
|
|
}
|
|
|
|
/// Get a shared pointer to this component
|
|
ptr get_shared() {
|
|
return lib::static_pointer_cast<type>(socket_con_type::get_shared());
|
|
}
|
|
|
|
bool is_secure() const {
|
|
return socket_con_type::is_secure();
|
|
}
|
|
|
|
/// Set uri hook
|
|
/**
|
|
* Called by the endpoint as a connection is being established to provide
|
|
* the uri being connected to to the transport layer.
|
|
*
|
|
* This transport policy doesn't use the uri except to forward it to the
|
|
* socket layer.
|
|
*
|
|
* @since 0.6.0
|
|
*
|
|
* @param u The uri to set
|
|
*/
|
|
void set_uri(uri_ptr u) {
|
|
socket_con_type::set_uri(u);
|
|
}
|
|
|
|
/// Sets the tcp pre init handler
|
|
/**
|
|
* The tcp pre init handler is called after the raw tcp connection has been
|
|
* established but before any additional wrappers (proxy connects, TLS
|
|
* handshakes, etc) have been performed.
|
|
*
|
|
* @since 0.3.0
|
|
*
|
|
* @param h The handler to call on tcp pre init.
|
|
*/
|
|
void set_tcp_pre_init_handler(tcp_init_handler h) {
|
|
m_tcp_pre_init_handler = h;
|
|
}
|
|
|
|
/// Sets the tcp pre init handler (deprecated)
|
|
/**
|
|
* The tcp pre init handler is called after the raw tcp connection has been
|
|
* established but before any additional wrappers (proxy connects, TLS
|
|
* handshakes, etc) have been performed.
|
|
*
|
|
* @deprecated Use set_tcp_pre_init_handler instead
|
|
*
|
|
* @param h The handler to call on tcp pre init.
|
|
*/
|
|
void set_tcp_init_handler(tcp_init_handler h) {
|
|
set_tcp_pre_init_handler(h);
|
|
}
|
|
|
|
/// Sets the tcp post init handler
|
|
/**
|
|
* The tcp post init handler is called after the tcp connection has been
|
|
* established and all additional wrappers (proxy connects, TLS handshakes,
|
|
* etc have been performed. This is fired before any bytes are read or any
|
|
* WebSocket specific handshake logic has been performed.
|
|
*
|
|
* @since 0.3.0
|
|
*
|
|
* @param h The handler to call on tcp post init.
|
|
*/
|
|
void set_tcp_post_init_handler(tcp_init_handler h) {
|
|
m_tcp_post_init_handler = h;
|
|
}
|
|
|
|
/// Set the proxy to connect through (exception free)
|
|
/**
|
|
* The URI passed should be a complete URI including scheme. For example:
|
|
* http://proxy.example.com:8080/
|
|
*
|
|
* The proxy must be set up as an explicit (CONNECT) proxy allowed to
|
|
* connect to the port you specify. Traffic to the proxy is not encrypted.
|
|
*
|
|
* @param uri The full URI of the proxy to connect to.
|
|
*
|
|
* @param ec A status value
|
|
*/
|
|
void set_proxy(std::string const & uri, lib::error_code & ec) {
|
|
// TODO: return errors for illegal URIs here?
|
|
// TODO: should https urls be illegal for the moment?
|
|
m_proxy = uri;
|
|
m_proxy_data = lib::make_shared<proxy_data>();
|
|
ec = lib::error_code();
|
|
}
|
|
|
|
/// Set the proxy to connect through (exception)
|
|
void set_proxy(std::string const & uri) {
|
|
lib::error_code ec;
|
|
set_proxy(uri,ec);
|
|
if (ec) { throw exception(ec); }
|
|
}
|
|
|
|
/// Set the basic auth credentials to use (exception free)
|
|
/**
|
|
* The URI passed should be a complete URI including scheme. For example:
|
|
* http://proxy.example.com:8080/
|
|
*
|
|
* The proxy must be set up as an explicit proxy
|
|
*
|
|
* @param username The username to send
|
|
*
|
|
* @param password The password to send
|
|
*
|
|
* @param ec A status value
|
|
*/
|
|
void set_proxy_basic_auth(std::string const & username, std::string const &
|
|
password, lib::error_code & ec)
|
|
{
|
|
if (!m_proxy_data) {
|
|
ec = make_error_code(websocketpp::error::invalid_state);
|
|
return;
|
|
}
|
|
|
|
// TODO: username can't contain ':'
|
|
std::string val = "Basic "+base64_encode(username + ":" + password);
|
|
m_proxy_data->req.replace_header("Proxy-Authorization",val);
|
|
ec = lib::error_code();
|
|
}
|
|
|
|
/// Set the basic auth credentials to use (exception)
|
|
void set_proxy_basic_auth(std::string const & username, std::string const &
|
|
password)
|
|
{
|
|
lib::error_code ec;
|
|
set_proxy_basic_auth(username,password,ec);
|
|
if (ec) { throw exception(ec); }
|
|
}
|
|
|
|
/// Set the proxy timeout duration (exception free)
|
|
/**
|
|
* Duration is in milliseconds. Default value is based on the transport
|
|
* config
|
|
*
|
|
* @param duration The number of milliseconds to wait before aborting the
|
|
* proxy connection.
|
|
*
|
|
* @param ec A status value
|
|
*/
|
|
void set_proxy_timeout(long duration, lib::error_code & ec) {
|
|
if (!m_proxy_data) {
|
|
ec = make_error_code(websocketpp::error::invalid_state);
|
|
return;
|
|
}
|
|
|
|
m_proxy_data->timeout_proxy = duration;
|
|
ec = lib::error_code();
|
|
}
|
|
|
|
/// Set the proxy timeout duration (exception)
|
|
void set_proxy_timeout(long duration) {
|
|
lib::error_code ec;
|
|
set_proxy_timeout(duration,ec);
|
|
if (ec) { throw exception(ec); }
|
|
}
|
|
|
|
std::string const & get_proxy() const {
|
|
return m_proxy;
|
|
}
|
|
|
|
/// Get the remote endpoint address
|
|
/**
|
|
* The iostream transport has no information about the ultimate remote
|
|
* endpoint. It will return the string "iostream transport". To indicate
|
|
* this.
|
|
*
|
|
* TODO: allow user settable remote endpoint addresses if this seems useful
|
|
*
|
|
* @return A string identifying the address of the remote endpoint
|
|
*/
|
|
std::string get_remote_endpoint() const {
|
|
lib::error_code ec;
|
|
|
|
std::string ret = socket_con_type::get_remote_endpoint(ec);
|
|
|
|
if (ec) {
|
|
m_elog->write(log::elevel::info,ret);
|
|
return "Unknown";
|
|
} else {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/// Get the connection handle
|
|
connection_hdl get_handle() const {
|
|
return m_connection_hdl;
|
|
}
|
|
|
|
/// Call back a function after a period of time.
|
|
/**
|
|
* Sets a timer that calls back a function after the specified period of
|
|
* milliseconds. Returns a handle that can be used to cancel the timer.
|
|
* A cancelled timer will return the error code error::operation_aborted
|
|
* A timer that expired will return no error.
|
|
*
|
|
* @param duration Length of time to wait in milliseconds
|
|
*
|
|
* @param callback The function to call back when the timer has expired
|
|
*
|
|
* @return A handle that can be used to cancel the timer if it is no longer
|
|
* needed.
|
|
*/
|
|
timer_ptr set_timer(long duration, timer_handler callback) {
|
|
timer_ptr new_timer(
|
|
new lib::asio::steady_timer(
|
|
*m_io_service,
|
|
lib::asio::milliseconds(duration))
|
|
);
|
|
|
|
if (config::enable_multithreading) {
|
|
new_timer->async_wait(m_strand->wrap(lib::bind(
|
|
&type::handle_timer, get_shared(),
|
|
new_timer,
|
|
callback,
|
|
lib::placeholders::_1
|
|
)));
|
|
} else {
|
|
new_timer->async_wait(lib::bind(
|
|
&type::handle_timer, get_shared(),
|
|
new_timer,
|
|
callback,
|
|
lib::placeholders::_1
|
|
));
|
|
}
|
|
|
|
return new_timer;
|
|
}
|
|
|
|
/// Timer callback
|
|
/**
|
|
* The timer pointer is included to ensure the timer isn't destroyed until
|
|
* after it has expired.
|
|
*
|
|
* TODO: candidate for protected status
|
|
*
|
|
* @param post_timer Pointer to the timer in question
|
|
* @param callback The function to call back
|
|
* @param ec The status code
|
|
*/
|
|
void handle_timer(timer_ptr, timer_handler callback,
|
|
lib::asio::error_code const & ec)
|
|
{
|
|
if (ec) {
|
|
if (ec == lib::asio::error::operation_aborted) {
|
|
callback(make_error_code(transport::error::operation_aborted));
|
|
} else {
|
|
log_err(log::elevel::info,"asio handle_timer",ec);
|
|
callback(make_error_code(error::pass_through));
|
|
}
|
|
} else {
|
|
callback(lib::error_code());
|
|
}
|
|
}
|
|
|
|
/// Get a pointer to this connection's strand
|
|
strand_ptr get_strand() {
|
|
return m_strand;
|
|
}
|
|
|
|
/// Get the internal transport error code for a closed/failed connection
|
|
/**
|
|
* Retrieves a machine readable detailed error code indicating the reason
|
|
* that the connection was closed or failed. Valid only after the close or
|
|
* fail handler is called.
|
|
*
|
|
* Primarily used if you are using mismatched asio / system_error
|
|
* implementations such as `boost::asio` with `std::system_error`. In these
|
|
* cases the transport error type is different than the library error type
|
|
* and some WebSocket++ functions that return transport errors via the
|
|
* library error code type will be coerced into a catch all `pass_through`
|
|
* or `tls_error` error. This method will return the original machine
|
|
* readable transport error in the native type.
|
|
*
|
|
* @since 0.7.0
|
|
*
|
|
* @return Error code indicating the reason the connection was closed or
|
|
* failed
|
|
*/
|
|
lib::asio::error_code get_transport_ec() const {
|
|
return m_tec;
|
|
}
|
|
|
|
/// Initialize transport for reading
|
|
/**
|
|
* init_asio is called once immediately after construction to initialize
|
|
* Asio components to the io_service
|
|
*
|
|
* The transport initialization sequence consists of the following steps:
|
|
* - Pre-init: the underlying socket is initialized to the point where
|
|
* bytes may be written. No bytes are actually written in this stage
|
|
* - Proxy negotiation: if a proxy is set, a request is made to it to start
|
|
* a tunnel to the final destination. This stage ends when the proxy is
|
|
* ready to forward the
|
|
* next byte to the remote endpoint.
|
|
* - Post-init: Perform any i/o with the remote endpoint, such as setting up
|
|
* tunnels for encryption. This stage ends when the connection is ready to
|
|
* read or write the WebSocket handshakes. At this point the original
|
|
* callback function is called.
|
|
*/
|
|
protected:
|
|
void init(init_handler callback) {
|
|
if (m_alog->static_test(log::alevel::devel)) {
|
|
m_alog->write(log::alevel::devel,"asio connection init");
|
|
}
|
|
|
|
// TODO: pre-init timeout. Right now no implemented socket policies
|
|
// actually have an asyncronous pre-init
|
|
|
|
socket_con_type::pre_init(
|
|
lib::bind(
|
|
&type::handle_pre_init,
|
|
get_shared(),
|
|
callback,
|
|
lib::placeholders::_1
|
|
)
|
|
);
|
|
}
|
|
|
|
/// initialize the proxy buffers and http parsers
|
|
/**
|
|
*
|
|
* @param authority The address of the server we want the proxy to tunnel to
|
|
* in the format of a URI authority (host:port)
|
|
*
|
|
* @return Status code indicating what errors occurred, if any
|
|
*/
|
|
lib::error_code proxy_init(std::string const & authority) {
|
|
if (!m_proxy_data) {
|
|
return websocketpp::error::make_error_code(
|
|
websocketpp::error::invalid_state);
|
|
}
|
|
m_proxy_data->req.set_version("HTTP/1.1");
|
|
m_proxy_data->req.set_method("CONNECT");
|
|
|
|
m_proxy_data->req.set_uri(authority);
|
|
m_proxy_data->req.replace_header("Host",authority);
|
|
|
|
return lib::error_code();
|
|
}
|
|
|
|
/// Finish constructing the transport
|
|
/**
|
|
* init_asio is called once immediately after construction to initialize
|
|
* Asio components to the io_service.
|
|
*
|
|
* @param io_service A pointer to the io_service to register with this
|
|
* connection
|
|
*
|
|
* @return Status code for the success or failure of the initialization
|
|
*/
|
|
lib::error_code init_asio (io_service_ptr io_service) {
|
|
m_io_service = io_service;
|
|
|
|
if (config::enable_multithreading) {
|
|
m_strand.reset(new lib::asio::io_service::strand(*io_service));
|
|
}
|
|
|
|
lib::error_code ec = socket_con_type::init_asio(io_service, m_strand,
|
|
m_is_server);
|
|
|
|
return ec;
|
|
}
|
|
|
|
void handle_pre_init(init_handler callback, lib::error_code const & ec) {
|
|
if (m_alog->static_test(log::alevel::devel)) {
|
|
m_alog->write(log::alevel::devel,"asio connection handle pre_init");
|
|
}
|
|
|
|
if (m_tcp_pre_init_handler) {
|
|
m_tcp_pre_init_handler(m_connection_hdl);
|
|
}
|
|
|
|
if (ec) {
|
|
callback(ec);
|
|
}
|
|
|
|
// If we have a proxy set issue a proxy connect, otherwise skip to
|
|
// post_init
|
|
if (!m_proxy.empty()) {
|
|
proxy_write(callback);
|
|
} else {
|
|
post_init(callback);
|
|
}
|
|
}
|
|
|
|
void post_init(init_handler callback) {
|
|
if (m_alog->static_test(log::alevel::devel)) {
|
|
m_alog->write(log::alevel::devel,"asio connection post_init");
|
|
}
|
|
|
|
timer_ptr post_timer;
|
|
|
|
if (config::timeout_socket_post_init > 0) {
|
|
post_timer = set_timer(
|
|
config::timeout_socket_post_init,
|
|
lib::bind(
|
|
&type::handle_post_init_timeout,
|
|
get_shared(),
|
|
post_timer,
|
|
callback,
|
|
lib::placeholders::_1
|
|
)
|
|
);
|
|
}
|
|
|
|
socket_con_type::post_init(
|
|
lib::bind(
|
|
&type::handle_post_init,
|
|
get_shared(),
|
|
post_timer,
|
|
callback,
|
|
lib::placeholders::_1
|
|
)
|
|
);
|
|
}
|
|
|
|
/// Post init timeout callback
|
|
/**
|
|
* The timer pointer is included to ensure the timer isn't destroyed until
|
|
* after it has expired.
|
|
*
|
|
* @param post_timer Pointer to the timer in question
|
|
* @param callback The function to call back
|
|
* @param ec The status code
|
|
*/
|
|
void handle_post_init_timeout(timer_ptr, init_handler callback,
|
|
lib::error_code const & ec)
|
|
{
|
|
lib::error_code ret_ec;
|
|
|
|
if (ec) {
|
|
if (ec == transport::error::operation_aborted) {
|
|
m_alog->write(log::alevel::devel,
|
|
"asio post init timer cancelled");
|
|
return;
|
|
}
|
|
|
|
log_err(log::elevel::devel,"asio handle_post_init_timeout",ec);
|
|
ret_ec = ec;
|
|
} else {
|
|
if (socket_con_type::get_ec()) {
|
|
ret_ec = socket_con_type::get_ec();
|
|
} else {
|
|
ret_ec = make_error_code(transport::error::timeout);
|
|
}
|
|
}
|
|
|
|
m_alog->write(log::alevel::devel, "Asio transport post-init timed out");
|
|
cancel_socket_checked();
|
|
callback(ret_ec);
|
|
}
|
|
|
|
/// Post init timeout callback
|
|
/**
|
|
* The timer pointer is included to ensure the timer isn't destroyed until
|
|
* after it has expired.
|
|
*
|
|
* @param post_timer Pointer to the timer in question
|
|
* @param callback The function to call back
|
|
* @param ec The status code
|
|
*/
|
|
void handle_post_init(timer_ptr post_timer, init_handler callback,
|
|
lib::error_code const & ec)
|
|
{
|
|
if (ec == transport::error::operation_aborted ||
|
|
(post_timer && lib::asio::is_neg(post_timer->expires_from_now())))
|
|
{
|
|
m_alog->write(log::alevel::devel,"post_init cancelled");
|
|
return;
|
|
}
|
|
|
|
if (post_timer) {
|
|
post_timer->cancel();
|
|
}
|
|
|
|
if (m_alog->static_test(log::alevel::devel)) {
|
|
m_alog->write(log::alevel::devel,"asio connection handle_post_init");
|
|
}
|
|
|
|
if (m_tcp_post_init_handler) {
|
|
m_tcp_post_init_handler(m_connection_hdl);
|
|
}
|
|
|
|
callback(ec);
|
|
}
|
|
|
|
void proxy_write(init_handler callback) {
|
|
if (m_alog->static_test(log::alevel::devel)) {
|
|
m_alog->write(log::alevel::devel,"asio connection proxy_write");
|
|
}
|
|
|
|
if (!m_proxy_data) {
|
|
m_elog->write(log::elevel::library,
|
|
"assertion failed: !m_proxy_data in asio::connection::proxy_write");
|
|
callback(make_error_code(error::general));
|
|
return;
|
|
}
|
|
|
|
m_proxy_data->write_buf = m_proxy_data->req.raw();
|
|
|
|
m_bufs.push_back(lib::asio::buffer(m_proxy_data->write_buf.data(),
|
|
m_proxy_data->write_buf.size()));
|
|
|
|
m_alog->write(log::alevel::devel,m_proxy_data->write_buf);
|
|
|
|
// Set a timer so we don't wait forever for the proxy to respond
|
|
m_proxy_data->timer = this->set_timer(
|
|
m_proxy_data->timeout_proxy,
|
|
lib::bind(
|
|
&type::handle_proxy_timeout,
|
|
get_shared(),
|
|
callback,
|
|
lib::placeholders::_1
|
|
)
|
|
);
|
|
|
|
// Send proxy request
|
|
if (config::enable_multithreading) {
|
|
lib::asio::async_write(
|
|
socket_con_type::get_next_layer(),
|
|
m_bufs,
|
|
m_strand->wrap(lib::bind(
|
|
&type::handle_proxy_write, get_shared(),
|
|
callback,
|
|
lib::placeholders::_1
|
|
))
|
|
);
|
|
} else {
|
|
lib::asio::async_write(
|
|
socket_con_type::get_next_layer(),
|
|
m_bufs,
|
|
lib::bind(
|
|
&type::handle_proxy_write, get_shared(),
|
|
callback,
|
|
lib::placeholders::_1
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
void handle_proxy_timeout(init_handler callback, lib::error_code const & ec)
|
|
{
|
|
if (ec == transport::error::operation_aborted) {
|
|
m_alog->write(log::alevel::devel,
|
|
"asio handle_proxy_write timer cancelled");
|
|
return;
|
|
} else if (ec) {
|
|
log_err(log::elevel::devel,"asio handle_proxy_write",ec);
|
|
callback(ec);
|
|
} else {
|
|
m_alog->write(log::alevel::devel,
|
|
"asio handle_proxy_write timer expired");
|
|
cancel_socket_checked();
|
|
callback(make_error_code(transport::error::timeout));
|
|
}
|
|
}
|
|
|
|
void handle_proxy_write(init_handler callback,
|
|
lib::asio::error_code const & ec)
|
|
{
|
|
if (m_alog->static_test(log::alevel::devel)) {
|
|
m_alog->write(log::alevel::devel,
|
|
"asio connection handle_proxy_write");
|
|
}
|
|
|
|
m_bufs.clear();
|
|
|
|
// Timer expired or the operation was aborted for some reason.
|
|
// Whatever aborted it will be issuing the callback so we are safe to
|
|
// return
|
|
if (ec == lib::asio::error::operation_aborted ||
|
|
lib::asio::is_neg(m_proxy_data->timer->expires_from_now()))
|
|
{
|
|
m_elog->write(log::elevel::devel,"write operation aborted");
|
|
return;
|
|
}
|
|
|
|
if (ec) {
|
|
log_err(log::elevel::info,"asio handle_proxy_write",ec);
|
|
m_proxy_data->timer->cancel();
|
|
callback(make_error_code(error::pass_through));
|
|
return;
|
|
}
|
|
|
|
proxy_read(callback);
|
|
}
|
|
|
|
void proxy_read(init_handler callback) {
|
|
if (m_alog->static_test(log::alevel::devel)) {
|
|
m_alog->write(log::alevel::devel,"asio connection proxy_read");
|
|
}
|
|
|
|
if (!m_proxy_data) {
|
|
m_elog->write(log::elevel::library,
|
|
"assertion failed: !m_proxy_data in asio::connection::proxy_read");
|
|
m_proxy_data->timer->cancel();
|
|
callback(make_error_code(error::general));
|
|
return;
|
|
}
|
|
|
|
if (config::enable_multithreading) {
|
|
lib::asio::async_read_until(
|
|
socket_con_type::get_next_layer(),
|
|
m_proxy_data->read_buf,
|
|
"\r\n\r\n",
|
|
m_strand->wrap(lib::bind(
|
|
&type::handle_proxy_read, get_shared(),
|
|
callback,
|
|
lib::placeholders::_1, lib::placeholders::_2
|
|
))
|
|
);
|
|
} else {
|
|
lib::asio::async_read_until(
|
|
socket_con_type::get_next_layer(),
|
|
m_proxy_data->read_buf,
|
|
"\r\n\r\n",
|
|
lib::bind(
|
|
&type::handle_proxy_read, get_shared(),
|
|
callback,
|
|
lib::placeholders::_1, lib::placeholders::_2
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Proxy read callback
|
|
/**
|
|
* @param init_handler The function to call back
|
|
* @param ec The status code
|
|
* @param bytes_transferred The number of bytes read
|
|
*/
|
|
void handle_proxy_read(init_handler callback,
|
|
lib::asio::error_code const & ec, size_t)
|
|
{
|
|
if (m_alog->static_test(log::alevel::devel)) {
|
|
m_alog->write(log::alevel::devel,
|
|
"asio connection handle_proxy_read");
|
|
}
|
|
|
|
// Timer expired or the operation was aborted for some reason.
|
|
// Whatever aborted it will be issuing the callback so we are safe to
|
|
// return
|
|
if (ec == lib::asio::error::operation_aborted ||
|
|
lib::asio::is_neg(m_proxy_data->timer->expires_from_now()))
|
|
{
|
|
m_elog->write(log::elevel::devel,"read operation aborted");
|
|
return;
|
|
}
|
|
|
|
// At this point there is no need to wait for the timer anymore
|
|
m_proxy_data->timer->cancel();
|
|
|
|
if (ec) {
|
|
m_elog->write(log::elevel::info,
|
|
"asio handle_proxy_read error: "+ec.message());
|
|
callback(make_error_code(error::pass_through));
|
|
} else {
|
|
if (!m_proxy_data) {
|
|
m_elog->write(log::elevel::library,
|
|
"assertion failed: !m_proxy_data in asio::connection::handle_proxy_read");
|
|
callback(make_error_code(error::general));
|
|
return;
|
|
}
|
|
|
|
std::istream input(&m_proxy_data->read_buf);
|
|
|
|
m_proxy_data->res.consume(input);
|
|
|
|
if (!m_proxy_data->res.headers_ready()) {
|
|
// we read until the headers were done in theory but apparently
|
|
// they aren't. Internal endpoint error.
|
|
callback(make_error_code(error::general));
|
|
return;
|
|
}
|
|
|
|
m_alog->write(log::alevel::devel,m_proxy_data->res.raw());
|
|
|
|
if (m_proxy_data->res.get_status_code() != http::status_code::ok) {
|
|
// got an error response back
|
|
// TODO: expose this error in a programmatically accessible way?
|
|
// if so, see below for an option on how to do this.
|
|
std::stringstream s;
|
|
s << "Proxy connection error: "
|
|
<< m_proxy_data->res.get_status_code()
|
|
<< " ("
|
|
<< m_proxy_data->res.get_status_msg()
|
|
<< ")";
|
|
m_elog->write(log::elevel::info,s.str());
|
|
callback(make_error_code(error::proxy_failed));
|
|
return;
|
|
}
|
|
|
|
// we have successfully established a connection to the proxy, now
|
|
// we can continue and the proxy will transparently forward the
|
|
// WebSocket connection.
|
|
|
|
// TODO: decide if we want an on_proxy callback that would allow
|
|
// access to the proxy response.
|
|
|
|
// free the proxy buffers and req/res objects as they aren't needed
|
|
// anymore
|
|
m_proxy_data.reset();
|
|
|
|
// Continue with post proxy initialization
|
|
post_init(callback);
|
|
}
|
|
}
|
|
|
|
/// read at least num_bytes bytes into buf and then call handler.
|
|
void async_read_at_least(size_t num_bytes, char *buf, size_t len,
|
|
read_handler handler)
|
|
{
|
|
if (m_alog->static_test(log::alevel::devel)) {
|
|
std::stringstream s;
|
|
s << "asio async_read_at_least: " << num_bytes;
|
|
m_alog->write(log::alevel::devel,s.str());
|
|
}
|
|
|
|
// TODO: safety vs speed ?
|
|
// maybe move into an if devel block
|
|
/*if (num_bytes > len) {
|
|
m_elog->write(log::elevel::devel,
|
|
"asio async_read_at_least error::invalid_num_bytes");
|
|
handler(make_error_code(transport::error::invalid_num_bytes),
|
|
size_t(0));
|
|
return;
|
|
}*/
|
|
|
|
if (config::enable_multithreading) {
|
|
lib::asio::async_read(
|
|
socket_con_type::get_socket(),
|
|
lib::asio::buffer(buf,len),
|
|
lib::asio::transfer_at_least(num_bytes),
|
|
m_strand->wrap(make_custom_alloc_handler(
|
|
m_read_handler_allocator,
|
|
lib::bind(
|
|
&type::handle_async_read, get_shared(),
|
|
handler,
|
|
lib::placeholders::_1, lib::placeholders::_2
|
|
)
|
|
))
|
|
);
|
|
} else {
|
|
lib::asio::async_read(
|
|
socket_con_type::get_socket(),
|
|
lib::asio::buffer(buf,len),
|
|
lib::asio::transfer_at_least(num_bytes),
|
|
make_custom_alloc_handler(
|
|
m_read_handler_allocator,
|
|
lib::bind(
|
|
&type::handle_async_read, get_shared(),
|
|
handler,
|
|
lib::placeholders::_1, lib::placeholders::_2
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
void handle_async_read(read_handler handler, lib::asio::error_code const & ec,
|
|
size_t bytes_transferred)
|
|
{
|
|
m_alog->write(log::alevel::devel, "asio con handle_async_read");
|
|
|
|
// translate asio error codes into more lib::error_codes
|
|
lib::error_code tec;
|
|
if (ec == lib::asio::error::eof) {
|
|
tec = make_error_code(transport::error::eof);
|
|
} else if (ec) {
|
|
// We don't know much more about the error at this point. As our
|
|
// socket/security policy if it knows more:
|
|
tec = socket_con_type::translate_ec(ec);
|
|
m_tec = ec;
|
|
|
|
if (tec == transport::error::tls_error ||
|
|
tec == transport::error::pass_through)
|
|
{
|
|
// These are aggregate/catch all errors. Log some human readable
|
|
// information to the info channel to give library users some
|
|
// more details about why the upstream method may have failed.
|
|
log_err(log::elevel::info,"asio async_read_at_least",ec);
|
|
}
|
|
}
|
|
if (handler) {
|
|
handler(tec,bytes_transferred);
|
|
} else {
|
|
// This can happen in cases where the connection is terminated while
|
|
// the transport is waiting on a read.
|
|
m_alog->write(log::alevel::devel,
|
|
"handle_async_read called with null read handler");
|
|
}
|
|
}
|
|
|
|
/// Initiate a potentially asyncronous write of the given buffer
|
|
void async_write(const char* buf, size_t len, write_handler handler) {
|
|
m_bufs.push_back(lib::asio::buffer(buf,len));
|
|
|
|
if (config::enable_multithreading) {
|
|
lib::asio::async_write(
|
|
socket_con_type::get_socket(),
|
|
m_bufs,
|
|
m_strand->wrap(make_custom_alloc_handler(
|
|
m_write_handler_allocator,
|
|
lib::bind(
|
|
&type::handle_async_write, get_shared(),
|
|
handler,
|
|
lib::placeholders::_1, lib::placeholders::_2
|
|
)
|
|
))
|
|
);
|
|
} else {
|
|
lib::asio::async_write(
|
|
socket_con_type::get_socket(),
|
|
m_bufs,
|
|
make_custom_alloc_handler(
|
|
m_write_handler_allocator,
|
|
lib::bind(
|
|
&type::handle_async_write, get_shared(),
|
|
handler,
|
|
lib::placeholders::_1, lib::placeholders::_2
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Initiate a potentially asyncronous write of the given buffers
|
|
void async_write(std::vector<buffer> const & bufs, write_handler handler) {
|
|
std::vector<buffer>::const_iterator it;
|
|
|
|
for (it = bufs.begin(); it != bufs.end(); ++it) {
|
|
m_bufs.push_back(lib::asio::buffer((*it).buf,(*it).len));
|
|
}
|
|
|
|
if (config::enable_multithreading) {
|
|
lib::asio::async_write(
|
|
socket_con_type::get_socket(),
|
|
m_bufs,
|
|
m_strand->wrap(make_custom_alloc_handler(
|
|
m_write_handler_allocator,
|
|
lib::bind(
|
|
&type::handle_async_write, get_shared(),
|
|
handler,
|
|
lib::placeholders::_1, lib::placeholders::_2
|
|
)
|
|
))
|
|
);
|
|
} else {
|
|
lib::asio::async_write(
|
|
socket_con_type::get_socket(),
|
|
m_bufs,
|
|
make_custom_alloc_handler(
|
|
m_write_handler_allocator,
|
|
lib::bind(
|
|
&type::handle_async_write, get_shared(),
|
|
handler,
|
|
lib::placeholders::_1, lib::placeholders::_2
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Async write callback
|
|
/**
|
|
* @param ec The status code
|
|
* @param bytes_transferred The number of bytes read
|
|
*/
|
|
void handle_async_write(write_handler handler, lib::asio::error_code const & ec, size_t) {
|
|
m_bufs.clear();
|
|
lib::error_code tec;
|
|
if (ec) {
|
|
log_err(log::elevel::info,"asio async_write",ec);
|
|
tec = make_error_code(transport::error::pass_through);
|
|
}
|
|
if (handler) {
|
|
handler(tec);
|
|
} else {
|
|
// This can happen in cases where the connection is terminated while
|
|
// the transport is waiting on a read.
|
|
m_alog->write(log::alevel::devel,
|
|
"handle_async_write called with null write handler");
|
|
}
|
|
}
|
|
|
|
/// Set Connection Handle
|
|
/**
|
|
* See common/connection_hdl.hpp for information
|
|
*
|
|
* @param hdl A connection_hdl that the transport will use to refer
|
|
* to itself
|
|
*/
|
|
void set_handle(connection_hdl hdl) {
|
|
m_connection_hdl = hdl;
|
|
socket_con_type::set_handle(hdl);
|
|
}
|
|
|
|
/// Trigger the on_interrupt handler
|
|
/**
|
|
* This needs to be thread safe
|
|
*/
|
|
lib::error_code interrupt(interrupt_handler handler) {
|
|
if (config::enable_multithreading) {
|
|
m_io_service->post(m_strand->wrap(handler));
|
|
} else {
|
|
m_io_service->post(handler);
|
|
}
|
|
return lib::error_code();
|
|
}
|
|
|
|
lib::error_code dispatch(dispatch_handler handler) {
|
|
if (config::enable_multithreading) {
|
|
m_io_service->post(m_strand->wrap(handler));
|
|
} else {
|
|
m_io_service->post(handler);
|
|
}
|
|
return lib::error_code();
|
|
}
|
|
|
|
/*void handle_interrupt(interrupt_handler handler) {
|
|
handler();
|
|
}*/
|
|
|
|
/// close and clean up the underlying socket
|
|
void async_shutdown(shutdown_handler callback) {
|
|
if (m_alog->static_test(log::alevel::devel)) {
|
|
m_alog->write(log::alevel::devel,"asio connection async_shutdown");
|
|
}
|
|
|
|
timer_ptr shutdown_timer;
|
|
shutdown_timer = set_timer(
|
|
config::timeout_socket_shutdown,
|
|
lib::bind(
|
|
&type::handle_async_shutdown_timeout,
|
|
get_shared(),
|
|
shutdown_timer,
|
|
callback,
|
|
lib::placeholders::_1
|
|
)
|
|
);
|
|
|
|
socket_con_type::async_shutdown(
|
|
lib::bind(
|
|
&type::handle_async_shutdown,
|
|
get_shared(),
|
|
shutdown_timer,
|
|
callback,
|
|
lib::placeholders::_1
|
|
)
|
|
);
|
|
}
|
|
|
|
/// Async shutdown timeout handler
|
|
/**
|
|
* @param shutdown_timer A pointer to the timer to keep it in scope
|
|
* @param callback The function to call back
|
|
* @param ec The status code
|
|
*/
|
|
void handle_async_shutdown_timeout(timer_ptr, init_handler callback,
|
|
lib::error_code const & ec)
|
|
{
|
|
lib::error_code ret_ec;
|
|
|
|
if (ec) {
|
|
if (ec == transport::error::operation_aborted) {
|
|
m_alog->write(log::alevel::devel,
|
|
"asio socket shutdown timer cancelled");
|
|
return;
|
|
}
|
|
|
|
log_err(log::elevel::devel,"asio handle_async_shutdown_timeout",ec);
|
|
ret_ec = ec;
|
|
} else {
|
|
ret_ec = make_error_code(transport::error::timeout);
|
|
}
|
|
|
|
m_alog->write(log::alevel::devel,
|
|
"Asio transport socket shutdown timed out");
|
|
cancel_socket_checked();
|
|
callback(ret_ec);
|
|
}
|
|
|
|
void handle_async_shutdown(timer_ptr shutdown_timer, shutdown_handler
|
|
callback, lib::asio::error_code const & ec)
|
|
{
|
|
if (ec == lib::asio::error::operation_aborted ||
|
|
lib::asio::is_neg(shutdown_timer->expires_from_now()))
|
|
{
|
|
m_alog->write(log::alevel::devel,"async_shutdown cancelled");
|
|
return;
|
|
}
|
|
|
|
shutdown_timer->cancel();
|
|
|
|
lib::error_code tec;
|
|
if (ec) {
|
|
if (ec == lib::asio::error::not_connected) {
|
|
// The socket was already closed when we tried to close it. This
|
|
// happens periodically (usually if a read or write fails
|
|
// earlier and if it is a real error will be caught at another
|
|
// level of the stack.
|
|
} else {
|
|
// We don't know anything more about this error, give our
|
|
// socket/security policy a crack at it.
|
|
tec = socket_con_type::translate_ec(ec);
|
|
m_tec = ec;
|
|
|
|
// all other errors are effectively pass through errors of
|
|
// some sort so print some detail on the info channel for
|
|
// library users to look up if needed.
|
|
log_err(log::elevel::info,"asio async_shutdown",ec);
|
|
}
|
|
} else {
|
|
if (m_alog->static_test(log::alevel::devel)) {
|
|
m_alog->write(log::alevel::devel,
|
|
"asio con handle_async_shutdown");
|
|
}
|
|
}
|
|
callback(tec);
|
|
}
|
|
|
|
/// Cancel the underlying socket and log any errors
|
|
void cancel_socket_checked() {
|
|
lib::asio::error_code cec = socket_con_type::cancel_socket();
|
|
if (cec) {
|
|
if (cec == lib::asio::error::operation_not_supported) {
|
|
// cancel not supported on this OS, ignore and log at dev level
|
|
m_alog->write(log::alevel::devel, "socket cancel not supported");
|
|
} else {
|
|
log_err(log::elevel::warn, "socket cancel failed", cec);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
/// Convenience method for logging the code and message for an error_code
|
|
template <typename error_type>
|
|
void log_err(log::level l, const char * msg, const error_type & ec) {
|
|
std::stringstream s;
|
|
s << msg << " error: " << ec << " (" << ec.message() << ")";
|
|
m_elog->write(l,s.str());
|
|
}
|
|
|
|
// static settings
|
|
const bool m_is_server;
|
|
lib::shared_ptr<alog_type> m_alog;
|
|
lib::shared_ptr<elog_type> m_elog;
|
|
|
|
struct proxy_data {
|
|
proxy_data() : timeout_proxy(config::timeout_proxy) {}
|
|
|
|
request_type req;
|
|
response_type res;
|
|
std::string write_buf;
|
|
lib::asio::streambuf read_buf;
|
|
long timeout_proxy;
|
|
timer_ptr timer;
|
|
};
|
|
|
|
std::string m_proxy;
|
|
lib::shared_ptr<proxy_data> m_proxy_data;
|
|
|
|
// transport resources
|
|
io_service_ptr m_io_service;
|
|
strand_ptr m_strand;
|
|
connection_hdl m_connection_hdl;
|
|
|
|
std::vector<lib::asio::const_buffer> m_bufs;
|
|
|
|
/// Detailed internal error code
|
|
lib::asio::error_code m_tec;
|
|
|
|
// Handlers
|
|
tcp_init_handler m_tcp_pre_init_handler;
|
|
tcp_init_handler m_tcp_post_init_handler;
|
|
|
|
handler_allocator m_read_handler_allocator;
|
|
handler_allocator m_write_handler_allocator;
|
|
};
|
|
|
|
|
|
} // namespace asio
|
|
} // namespace transport
|
|
} // namespace websocketpp
|
|
|
|
#endif // WEBSOCKETPP_TRANSPORT_ASIO_CON_HPP
|