mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-20 10:35:50 +00:00
1606 lines
57 KiB
C++
1606 lines
57 KiB
C++
/*
|
|
* Copyright (c) 2011, 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_CONNECTION_HPP
|
|
#define WEBSOCKETPP_CONNECTION_HPP
|
|
|
|
#include "common.hpp"
|
|
#include "http/parser.hpp"
|
|
#include "logger/logger.hpp"
|
|
|
|
#include "roles/server.hpp"
|
|
|
|
#include "processors/hybi.hpp"
|
|
|
|
#include "messages/data.hpp"
|
|
#include "messages/control.hpp"
|
|
|
|
#include <boost/asio.hpp>
|
|
#include <boost/bind.hpp>
|
|
#include <boost/enable_shared_from_this.hpp>
|
|
|
|
#include <iostream> // temporary?
|
|
#include <vector>
|
|
#include <string>
|
|
#include <queue>
|
|
#include <set>
|
|
|
|
#ifdef min
|
|
#undef min
|
|
#endif // #ifdef min
|
|
|
|
namespace websocketpp {
|
|
|
|
class endpoint_base;
|
|
|
|
template <typename T>
|
|
struct connection_traits;
|
|
|
|
template <
|
|
typename endpoint,
|
|
template <class> class role,
|
|
template <class> class socket>
|
|
class connection
|
|
: public role< connection<endpoint,role,socket> >,
|
|
public socket< connection<endpoint,role,socket> >,
|
|
public boost::enable_shared_from_this< connection<endpoint,role,socket> >
|
|
{
|
|
public:
|
|
typedef connection_traits< connection<endpoint,role,socket> > traits;
|
|
|
|
// get types that we need from our traits class
|
|
typedef typename traits::type type;
|
|
typedef typename traits::ptr ptr;
|
|
typedef typename traits::role_type role_type;
|
|
typedef typename traits::socket_type socket_type;
|
|
|
|
typedef endpoint endpoint_type;
|
|
|
|
typedef typename endpoint_type::handler_ptr handler_ptr;
|
|
typedef typename endpoint_type::handler handler;
|
|
|
|
// friends (would require C++11) this would enable connection::start to be
|
|
// protected instead of public.
|
|
//friend typename endpoint_traits<endpoint_type>::role_type;
|
|
//friend typename endpoint_traits<endpoint_type>::socket_type;
|
|
//friend class role<endpoint>;
|
|
//friend class socket<endpoint>;
|
|
|
|
friend class role< connection<endpoint,role,socket> >;
|
|
friend class socket< connection<endpoint,role,socket> >;
|
|
|
|
enum write_state {
|
|
IDLE = 0,
|
|
WRITING = 1,
|
|
INTURRUPT = 2
|
|
};
|
|
|
|
enum read_state {
|
|
READING = 0,
|
|
WAITING = 1
|
|
};
|
|
|
|
connection(endpoint_type& e,handler_ptr h)
|
|
: role_type(e)
|
|
, socket_type(e)
|
|
, m_endpoint(e)
|
|
, m_alog(e.alog_ptr())
|
|
, m_elog(e.elog_ptr())
|
|
, m_handler(h)
|
|
, m_read_threshold(e.get_read_threshold())
|
|
, m_silent_close(e.get_silent_close())
|
|
, m_timer(e.endpoint_base::m_io_service,boost::posix_time::seconds(0))
|
|
, m_state(session::state::CONNECTING)
|
|
, m_protocol_error(false)
|
|
, m_write_buffer(0)
|
|
, m_write_state(IDLE)
|
|
, m_fail_code(fail::status::GOOD)
|
|
, m_local_close_code(close::status::ABNORMAL_CLOSE)
|
|
, m_remote_close_code(close::status::ABNORMAL_CLOSE)
|
|
, m_closed_by_me(false)
|
|
, m_failed_by_me(false)
|
|
, m_dropped_by_me(false)
|
|
, m_read_state(READING)
|
|
, m_strand(e.endpoint_base::m_io_service)
|
|
, m_detached(false)
|
|
{
|
|
socket_type::init();
|
|
|
|
// This should go away
|
|
m_control_message = message::control_ptr(new message::control());
|
|
}
|
|
|
|
/// Destroy the connection
|
|
~connection() {
|
|
try {
|
|
if (m_state != session::state::CLOSED) {
|
|
terminate(true);
|
|
}
|
|
} catch (...) {}
|
|
}
|
|
|
|
// copy/assignment constructors require C++11
|
|
// boost::noncopyable is being used in the meantime.
|
|
// connection(connection const&) = delete;
|
|
// connection& operator=(connection const&) = delete
|
|
|
|
/// Start the websocket connection async read loop
|
|
/**
|
|
* Begins the connection's async read loop. First any socket level
|
|
* initialization will happen (TLS handshake, etc) then the handshake and
|
|
* frame reads will start.
|
|
*
|
|
* Visibility: protected
|
|
* State: Should only be called once by the endpoint.
|
|
* Concurrency: safe as long as state is valid
|
|
*/
|
|
void start() {
|
|
// initialize the socket.
|
|
socket_type::async_init(
|
|
m_strand.wrap(boost::bind(
|
|
&type::handle_socket_init,
|
|
type::shared_from_this(),
|
|
boost::asio::placeholders::error
|
|
))
|
|
);
|
|
}
|
|
|
|
/// Return current connection state
|
|
/**
|
|
* Visibility: public
|
|
* State: valid always
|
|
* Concurrency: callable from anywhere
|
|
*
|
|
* @return Current connection state
|
|
*/
|
|
session::state::value get_state() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
return m_state;
|
|
}
|
|
|
|
/// Detaches the connection from its endpoint
|
|
/**
|
|
* Called by the m_endpoint's destructor. In state DETACHED m_endpoint is
|
|
* no longer avaliable. The connection may stick around if the end user
|
|
* application needs to read state from it (ie close reasons, etc) but no
|
|
* operations requring the endpoint can be performed.
|
|
*
|
|
* Visibility: protected
|
|
* State: Should only be called once by the endpoint.
|
|
* Concurrency: safe as long as state is valid
|
|
*/
|
|
void detach() {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
m_detached = true;
|
|
}
|
|
|
|
void send(const std::string& payload, frame::opcode::value op = frame::opcode::TEXT);
|
|
void send(message::data_ptr msg);
|
|
|
|
/// Close connection
|
|
/**
|
|
* Closes the websocket connection with the given status code and reason.
|
|
* From state OPEN a clean connection close is initiated. From any other
|
|
* state the socket will be closed and the connection cleaned up.
|
|
*
|
|
* There is no feedback directly from close. Feedback will be provided via
|
|
* the on_fail or on_close callbacks.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from OPEN, ignored otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @param code Close code to send
|
|
* @param reason Close reason to send
|
|
*/
|
|
void close(close::status::value code, const std::string& reason = "") {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
if (m_detached) {return;}
|
|
|
|
if (m_state == session::state::CONNECTING) {
|
|
m_endpoint.endpoint_base::m_io_service.post(
|
|
m_strand.wrap(boost::bind(
|
|
&type::terminate,
|
|
type::shared_from_this(),
|
|
true
|
|
))
|
|
);
|
|
} else if (m_state == session::state::OPEN) {
|
|
m_endpoint.endpoint_base::m_io_service.post(
|
|
m_strand.wrap(boost::bind(
|
|
&type::begin_close,
|
|
type::shared_from_this(),
|
|
code,
|
|
reason
|
|
))
|
|
);
|
|
} else {
|
|
// in CLOSING state we are already closing, nothing to do
|
|
// in CLOSED state we are already closed, nothing to do
|
|
}
|
|
}
|
|
|
|
/// Send Ping
|
|
/**
|
|
* Initiates a ping with the given payload.
|
|
*
|
|
* There is no feedback directly from ping. Feedback will be provided via
|
|
* the on_pong or on_pong_timeout callbacks.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from OPEN, ignored otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @param payload Payload to be used for the ping
|
|
*/
|
|
void ping(const std::string& payload) {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
if (m_state != session::state::OPEN) {return;}
|
|
if (m_detached || !m_processor) {return;}
|
|
|
|
// TODO: optimize control messages and handle case where
|
|
// endpoint is out of messages
|
|
message::data_ptr control = get_control_message2();
|
|
control->reset(frame::opcode::PING);
|
|
control->set_payload(payload);
|
|
m_processor->prepare_frame(control);
|
|
|
|
// Using strand post here rather than ioservice.post(strand.wrap)
|
|
// to ensure that messages are sent in order
|
|
m_strand.post(boost::bind(
|
|
&type::write_message,
|
|
type::shared_from_this(),
|
|
control
|
|
));
|
|
}
|
|
|
|
/// Send Pong
|
|
/**
|
|
* Initiates a pong with the given payload.
|
|
*
|
|
* There is no feedback from pong.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from OPEN, ignored otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @param payload Payload to be used for the pong
|
|
*/
|
|
void pong(const std::string& payload) {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
if (m_state != session::state::OPEN) {return;}
|
|
if (m_detached) {return;}
|
|
|
|
// TODO: optimize control messages and handle case where
|
|
// endpoint is out of messages
|
|
message::data_ptr control = get_control_message2();
|
|
control->reset(frame::opcode::PONG);
|
|
control->set_payload(payload);
|
|
m_processor->prepare_frame(control);
|
|
|
|
// Using strand post here rather than ioservice.post(strand.wrap)
|
|
// to ensure that messages are sent in order
|
|
m_strand.post(boost::bind(
|
|
&type::write_message,
|
|
type::shared_from_this(),
|
|
control
|
|
));
|
|
}
|
|
|
|
/// Return send buffer size (payload bytes)
|
|
/**
|
|
* Visibility: public
|
|
* State: Valid from any state.
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return The current number of bytes in the outgoing send buffer.
|
|
*/
|
|
uint64_t buffered_amount() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
return m_write_buffer;
|
|
}
|
|
|
|
/// Get library fail code
|
|
/**
|
|
* Returns the internal WS++ fail code. This code starts at a value of
|
|
* websocketpp::fail::status::GOOD and will be set to other values as errors
|
|
* occur. Some values are direct errors, others point to locations where
|
|
* more specific error information can be found. Key values:
|
|
*
|
|
* (all in websocketpp::fail::status:: ) namespace
|
|
* GOOD - No error has occurred yet
|
|
* SYSTEM - A system call failed, the system error code is avaliable via
|
|
* get_fail_system_code()
|
|
* WEBSOCKET - The WebSocket connection close handshake was performed, more
|
|
* information is avaliable via get_local_close_code()/reason()
|
|
* UNKNOWN - terminate was called without a more specific error being set
|
|
*
|
|
* Refer to common.hpp for the rest of the individual codes. Call
|
|
* get_fail_reason() for a human readable error.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from any
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return Fail code supplied by WebSocket++
|
|
*/
|
|
fail::status::value get_fail_code() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
return m_fail_code;
|
|
}
|
|
|
|
/// Get library failure reason
|
|
/**
|
|
* Returns a human readable library failure reason.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from any
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return Fail reason supplied by WebSocket++
|
|
*/
|
|
std::string get_fail_reason() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
return m_fail_reason;
|
|
}
|
|
|
|
/// Get system failure code
|
|
/**
|
|
* Returns the system error code that caused WS++ to fail the connection
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from any
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return Error code supplied by system
|
|
*/
|
|
boost::system::error_code get_system_fail_code() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
return m_fail_system;
|
|
}
|
|
|
|
/// Get WebSocket close code sent by WebSocket++
|
|
/**
|
|
* Returns the system error code that caused
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from CLOSED, an exception is thrown otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return Close code supplied by WebSocket++
|
|
*/
|
|
close::status::value get_local_close_code() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
if (m_state != session::state::CLOSED) {
|
|
throw exception("get_local_close_code called from state other than CLOSED",
|
|
error::INVALID_STATE);
|
|
}
|
|
|
|
return m_local_close_code;
|
|
}
|
|
|
|
/// Get Local Close Reason
|
|
/**
|
|
* Returns the close reason that WebSocket++ believes to be the reason the
|
|
* connection closed. This is almost certainly the value passed to the
|
|
* `close()` method.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from CLOSED, an exception is thrown otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return Close reason supplied by WebSocket++
|
|
*/
|
|
std::string get_local_close_reason() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
if (m_state != session::state::CLOSED) {
|
|
throw exception("get_local_close_reason called from state other than CLOSED",
|
|
error::INVALID_STATE);
|
|
}
|
|
|
|
return m_local_close_reason;
|
|
}
|
|
|
|
/// Get Remote Close Code
|
|
/**
|
|
* Returns the close reason that was received over the wire from the remote peer.
|
|
* This method may return values that are invalid on the wire such as 1005/No close
|
|
* code received, 1006 abnormal closure, or 1015 Bad TLS handshake.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from CLOSED, an exception is thrown otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return Close code supplied by remote endpoint
|
|
*/
|
|
close::status::value get_remote_close_code() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
if (m_state != session::state::CLOSED) {
|
|
throw exception("get_remote_close_code called from state other than CLOSED",
|
|
error::INVALID_STATE);
|
|
}
|
|
|
|
return m_remote_close_code;
|
|
}
|
|
|
|
/// Get Remote Close Reason
|
|
/**
|
|
* Returns the close reason that was received over the wire from the remote peer.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from CLOSED, an exception is thrown otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return Close reason supplied by remote endpoint
|
|
*/
|
|
std::string get_remote_close_reason() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
if (m_state != session::state::CLOSED) {
|
|
throw exception("get_remote_close_reason called from state other than CLOSED",
|
|
error::INVALID_STATE);
|
|
}
|
|
|
|
return m_remote_close_reason;
|
|
}
|
|
|
|
/// Get failed_by_me
|
|
/**
|
|
* Returns whether or not the connection ending sequence was initiated by this
|
|
* endpoint. Will return true when this endpoint chooses to close normally or when it
|
|
* discovers an error and chooses to close the connection (either forcibly or not).
|
|
* Will return false when the close handshake was initiated by the remote endpoint or
|
|
* if the TCP connection was dropped or broken prematurely.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from CLOSED, an exception is thrown otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return Whether or not the connection ending sequence was initiated by this endpoint
|
|
*/
|
|
bool get_failed_by_me() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
if (m_state != session::state::CLOSED) {
|
|
throw exception("get_failed_by_me called from state other than CLOSED",
|
|
error::INVALID_STATE);
|
|
}
|
|
|
|
return m_failed_by_me;
|
|
}
|
|
|
|
/// Get dropped_by_me
|
|
/**
|
|
* Returns whether or not the TCP connection was dropped by this endpoint.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from CLOSED, an exception is thrown otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return whether or not the TCP connection was dropped by this endpoint.
|
|
*/
|
|
bool get_dropped_by_me() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
if (m_state != session::state::CLOSED) {
|
|
throw exception("get_dropped_by_me called from state other than CLOSED",
|
|
error::INVALID_STATE);
|
|
}
|
|
|
|
return m_dropped_by_me;
|
|
}
|
|
|
|
/// Get closed_by_me
|
|
/**
|
|
* Returns whether or not the WebSocket closing handshake was initiated by this
|
|
* endpoint.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from CLOSED, an exception is thrown otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return Whether or not closing handshake was initiated by this endpoint
|
|
*/
|
|
bool get_closed_by_me() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
if (m_state != session::state::CLOSED) {
|
|
throw exception("get_closed_by_me called from state other than CLOSED",
|
|
error::INVALID_STATE);
|
|
}
|
|
|
|
return m_closed_by_me;
|
|
}
|
|
|
|
/// Get get_data_message
|
|
/**
|
|
* Returns a pointer to an outgoing message buffer. If there are no outgoing messages
|
|
* available, a NO_OUTGOING_MESSAGES exception is thrown. This happens when the
|
|
* endpoint has exhausted its resources dedicated to buffering outgoing messages. To
|
|
* deal with this error either increase available outgoing resources or throttle back
|
|
* the rate and size of outgoing messages.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from OPEN, an exception is thrown otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return A pointer to the message buffer
|
|
*/
|
|
message::data_ptr get_data_message() {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
if (m_detached) {
|
|
throw exception("get_data_message: Endpoint was destroyed",error::ENDPOINT_UNAVAILABLE);
|
|
}
|
|
|
|
if (m_state != session::state::OPEN && m_state != session::state::CLOSING) {
|
|
throw exception("get_data_message called from invalid state",
|
|
error::INVALID_STATE);
|
|
}
|
|
|
|
message::data_ptr msg = m_endpoint.get_data_message();
|
|
|
|
if (!msg) {
|
|
throw exception("No outgoing messages available",error::NO_OUTGOING_MESSAGES);
|
|
} else {
|
|
return msg;
|
|
}
|
|
}
|
|
|
|
|
|
message::data_ptr get_control_message2() {
|
|
return m_endpoint.get_control_message();
|
|
}
|
|
|
|
message::control_ptr get_control_message() {
|
|
return m_control_message;
|
|
}
|
|
|
|
/// Set connection handler
|
|
/**
|
|
* Sets the handler that will process callbacks for this connection. The switch is
|
|
* processed asynchronously so set_handler will return immediately. The existing
|
|
* handler will receive an on_unload callback immediately before the switch. After
|
|
* on_unload returns the original handler will not receive any more callbacks from
|
|
* this connection. The new handler will receive an on_load callback immediately after
|
|
* the switch and before any other callbacks are processed.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from any state
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @param new_handler the new handler to set
|
|
*/
|
|
void set_handler(handler_ptr new_handler) {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
if (m_detached) {return;}
|
|
|
|
if (!new_handler) {
|
|
elog()->at(log::elevel::FATAL) << "Tried to switch to a NULL handler."
|
|
<< log::endl;
|
|
terminate(true);
|
|
return;
|
|
}
|
|
|
|
// io_service::strand ordering guarantees are not sufficient here to
|
|
// make sure that this code is run before any messages are processed.
|
|
handler_ptr old_handler = m_handler;
|
|
|
|
old_handler->on_unload(type::shared_from_this(),new_handler);
|
|
m_handler = new_handler;
|
|
new_handler->on_load(type::shared_from_this(),old_handler);
|
|
|
|
/*m_endpoint.endpoint_base::m_io_service.post(
|
|
m_strand.wrap(boost::bind(
|
|
&type::set_handler_internal,
|
|
type::shared_from_this(),
|
|
new_handler
|
|
))
|
|
);*/
|
|
}
|
|
|
|
/// Set connection read threshold
|
|
/**
|
|
* Set the read threshold for this connection. See endpoint::set_read_threshold for
|
|
* more information about the read threshold.
|
|
*
|
|
* Visibility: public
|
|
* State: valid always
|
|
* Concurrency: callable from anywhere
|
|
*
|
|
* @param val Size of the threshold in bytes
|
|
*/
|
|
void set_read_threshold(size_t val) {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
m_read_threshold = val;
|
|
}
|
|
|
|
/// Get connection read threshold
|
|
/**
|
|
* Returns the connection read threshold. See set_read_threshold for more information
|
|
* about the read threshold.
|
|
*
|
|
* Visibility: public
|
|
* State: valid always
|
|
* Concurrency: callable from anywhere
|
|
*
|
|
* @return Size of the threshold in bytes
|
|
*/
|
|
size_t get_read_threshold() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
return m_read_threshold;
|
|
}
|
|
|
|
/// Set connection silent close setting
|
|
/**
|
|
* See endpoint::set_silent_close for more information.
|
|
*
|
|
* Visibility: public
|
|
* State: valid always
|
|
* Concurrency: callable from anywhere
|
|
*
|
|
* @param val New silent close value
|
|
*/
|
|
void set_silent_close(bool val) {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
m_silent_close = val;
|
|
}
|
|
|
|
/// Get connection silent close setting
|
|
/**
|
|
* Visibility: public
|
|
* State: valid always
|
|
* Concurrency: callable from anywhere
|
|
*
|
|
* @return Current silent close value
|
|
* @see set_silent_close()
|
|
*/
|
|
bool get_silent_close() const {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
return m_silent_close;
|
|
}
|
|
|
|
// TODO: deprecated. will change to get_rng?
|
|
int32_t gen() {
|
|
return 0;
|
|
}
|
|
|
|
/// Returns a reference to the endpoint's access logger.
|
|
/**
|
|
* Visibility: public
|
|
* State: Valid from any state
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return A reference to the endpoint's access logger
|
|
*/
|
|
typename endpoint::alogger_ptr alog() {
|
|
return m_alog;
|
|
}
|
|
|
|
/// Returns a reference to the endpoint's error logger.
|
|
/**
|
|
* Visibility: public
|
|
* State: Valid from any state
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return A reference to the endpoint's error logger
|
|
*/
|
|
typename endpoint::elogger_ptr elog() {
|
|
return m_elog;
|
|
}
|
|
|
|
/// Returns a pointer to the endpoint's handler.
|
|
/**
|
|
* Visibility: public
|
|
* State: Valid from any state
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return A pointer to the endpoint's default handler
|
|
*/
|
|
handler_ptr get_handler() {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
return m_handler;
|
|
}
|
|
|
|
/// Returns a reference to the connections strand object.
|
|
/**
|
|
* Visibility: public
|
|
* State: Valid from any state
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @return A reference to the connection's strand object
|
|
*/
|
|
boost::asio::strand& get_strand() {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
return m_strand;
|
|
}
|
|
public:
|
|
//protected: TODO: figure out why VCPP2010 doesn't like protected here
|
|
|
|
/// Socket initialization callback
|
|
/* This is the async return point for initializing the socket policy. After this point
|
|
* the socket is open and ready.
|
|
*
|
|
* Visibility: protected
|
|
* State: Valid only once per connection during the initialization sequence.
|
|
* Concurrency: Must be called within m_strand
|
|
*/
|
|
void handle_socket_init(const boost::system::error_code& error) {
|
|
if (error) {
|
|
elog()->at(log::elevel::RERROR)
|
|
<< "Socket initialization failed, error code: " << error
|
|
<< log::endl;
|
|
this->terminate(false);
|
|
return;
|
|
}
|
|
|
|
role_type::async_init();
|
|
}
|
|
public:
|
|
/// ASIO callback for async_read of more frame data
|
|
/**
|
|
* Callback after ASIO has read some data that needs to be sent to a frame processor
|
|
*
|
|
* TODO: think about how receiving a very large buffer would affect concurrency due to
|
|
* that handler running for an unusually long period of time? Is a maximum size
|
|
* necessary on m_buf?
|
|
* TODO: think about how terminate here works with the locks and concurrency
|
|
*
|
|
* Visibility: protected
|
|
* State: valid for states OPEN and CLOSING, ignored otherwise
|
|
* Concurrency: must be called via strand. Only one async_read should be outstanding
|
|
* at a time. Should only be called from inside handle_read_frame or by
|
|
* the role's init method.
|
|
*/
|
|
void handle_read_frame(const boost::system::error_code& error) {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
// check if state changed while we were waiting for a read.
|
|
if (m_state == session::state::CLOSED) {
|
|
alog()->at(log::alevel::DEVEL)
|
|
<< "handle read returning due to closed connection"
|
|
<< log::endl;
|
|
return;
|
|
}
|
|
if (m_state == session::state::CONNECTING) { return; }
|
|
|
|
if (error) {
|
|
if (error == boost::asio::error::eof) {
|
|
elog()->at(log::elevel::RERROR)
|
|
<< "Unexpected EOF from remote endpoint, terminating connection."
|
|
<< log::endl;
|
|
terminate(false);
|
|
return;
|
|
} else if (error == boost::asio::error::operation_aborted) {
|
|
// got unexpected abort (likely our server issued an abort on
|
|
// all connections on this io_service)
|
|
|
|
elog()->at(log::elevel::RERROR)
|
|
<< "Connection terminating due to aborted read: "
|
|
<< error << log::endl;
|
|
terminate(true);
|
|
return;
|
|
} else {
|
|
// Other unexpected error
|
|
|
|
elog()->at(log::elevel::RERROR)
|
|
<< "Connection terminating due to unknown error: " << error
|
|
<< log::endl;
|
|
terminate(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// process data from the buffer just read into
|
|
std::istream s(&m_buf);
|
|
|
|
while (m_state != session::state::CLOSED && m_buf.size() > 0) {
|
|
try {
|
|
m_processor->consume(s);
|
|
|
|
if (m_processor->ready()) {
|
|
if (m_processor->is_control()) {
|
|
process_control(m_processor->get_control_message());
|
|
} else {
|
|
process_data(m_processor->get_data_message());
|
|
}
|
|
m_processor->reset();
|
|
}
|
|
} catch (const processor::exception& e) {
|
|
if (m_processor->ready()) {
|
|
m_processor->reset();
|
|
}
|
|
|
|
switch(e.code()) {
|
|
// the protocol error flag is set by any processor exception
|
|
// that indicates that the composition of future bytes in
|
|
// the read stream cannot be reliably determined. Bytes will
|
|
// no longer be read after that point.
|
|
case processor::error::PROTOCOL_VIOLATION:
|
|
m_protocol_error = true;
|
|
begin_close(close::status::PROTOCOL_ERROR,e.what());
|
|
break;
|
|
case processor::error::PAYLOAD_VIOLATION:
|
|
m_protocol_error = true;
|
|
begin_close(close::status::INVALID_PAYLOAD,e.what());
|
|
break;
|
|
case processor::error::INTERNAL_ENDPOINT_ERROR:
|
|
m_protocol_error = true;
|
|
begin_close(close::status::INTERNAL_ENDPOINT_ERROR,e.what());
|
|
break;
|
|
case processor::error::SOFT_ERROR:
|
|
continue;
|
|
case processor::error::MESSAGE_TOO_BIG:
|
|
m_protocol_error = true;
|
|
begin_close(close::status::MESSAGE_TOO_BIG,e.what());
|
|
break;
|
|
case processor::error::OUT_OF_MESSAGES:
|
|
// we need to wait for a message to be returned by the
|
|
// client. We exit the read loop. handle_read_frame
|
|
// will be restarted by recycle()
|
|
//m_read_state = WAITING;
|
|
m_endpoint.wait(type::shared_from_this());
|
|
return;
|
|
default:
|
|
// Fatal error, forcibly end connection immediately.
|
|
elog()->at(log::elevel::DEVEL)
|
|
<< "Terminating connection due to unrecoverable processor exception: "
|
|
<< e.code() << " (" << e.what() << ")" << log::endl;
|
|
terminate(true);
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
// try and read more
|
|
if (m_state != session::state::CLOSED &&
|
|
m_processor &&
|
|
m_processor->get_bytes_needed() > 0 &&
|
|
!m_protocol_error)
|
|
{
|
|
// TODO: read timeout timer?
|
|
|
|
socket_type::get_socket().async_read(
|
|
m_buf,
|
|
boost::asio::transfer_at_least(std::min(
|
|
m_read_threshold,
|
|
static_cast<size_t>(m_processor->get_bytes_needed())
|
|
)),
|
|
m_strand.wrap(boost::bind(
|
|
&type::handle_read_frame,
|
|
type::shared_from_this(),
|
|
boost::asio::placeholders::error
|
|
))
|
|
);
|
|
}
|
|
}
|
|
public:
|
|
//protected: TODO: figure out why VCPP2010 doesn't like protected here
|
|
/// Internal Implementation for set_handler
|
|
/**
|
|
* Visibility: protected
|
|
* State: Valid for all states
|
|
* Concurrency: Must be called within m_strand
|
|
*
|
|
* @param new_handler The handler to switch to
|
|
*/
|
|
void set_handler_internal(handler_ptr new_handler) {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
if (!new_handler) {
|
|
elog()->at(log::elevel::FATAL)
|
|
<< "Terminating connection due to attempted switch to NULL handler."
|
|
<< log::endl;
|
|
// TODO: unserialized call to terminate?
|
|
terminate(true);
|
|
return;
|
|
}
|
|
|
|
handler_ptr old_handler = m_handler;
|
|
|
|
old_handler->on_unload(type::shared_from_this(),new_handler);
|
|
m_handler = new_handler;
|
|
new_handler->on_load(type::shared_from_this(),old_handler);
|
|
}
|
|
|
|
/// Dispatch a data message
|
|
/**
|
|
* Visibility: private
|
|
* State: no state checking, should only be called within handle_read_frame
|
|
* Concurrency: Must be called within m_stranded method
|
|
*/
|
|
void process_data(message::data_ptr msg) {
|
|
m_handler->on_message(type::shared_from_this(),msg);
|
|
}
|
|
|
|
/// Dispatch or handle a control message
|
|
/**
|
|
* Visibility: private
|
|
* State: no state checking, should only be called within handle_read_frame
|
|
* Concurrency: Must be called within m_stranded method
|
|
*/
|
|
void process_control(message::control_ptr msg) {
|
|
bool response;
|
|
switch (msg->get_opcode()) {
|
|
case frame::opcode::PING:
|
|
response = m_handler->on_ping(type::shared_from_this(),
|
|
msg->get_payload());
|
|
if (response) {
|
|
pong(msg->get_payload());
|
|
}
|
|
break;
|
|
case frame::opcode::PONG:
|
|
m_handler->on_pong(type::shared_from_this(),
|
|
msg->get_payload());
|
|
// TODO: disable ping response timer
|
|
|
|
break;
|
|
case frame::opcode::CLOSE:
|
|
m_remote_close_code = msg->get_close_code();
|
|
m_remote_close_reason = msg->get_close_reason();
|
|
|
|
// check that the codes we got over the wire are valid
|
|
|
|
if (m_state == session::state::OPEN) {
|
|
// other end is initiating
|
|
alog()->at(log::alevel::DEBUG_CLOSE)
|
|
<< "sending close ack" << log::endl;
|
|
|
|
// TODO:
|
|
send_close_ack();
|
|
} else if (m_state == session::state::CLOSING) {
|
|
// ack of our close
|
|
alog()->at(log::alevel::DEBUG_CLOSE)
|
|
<< "got close ack" << log::endl;
|
|
|
|
terminate(false);
|
|
// TODO: start terminate timer (if client)
|
|
}
|
|
break;
|
|
default:
|
|
throw processor::exception("Invalid Opcode",
|
|
processor::error::PROTOCOL_VIOLATION);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// Begins a clean close handshake
|
|
/**
|
|
* Initiates a close handshake by sending a close frame with the given code
|
|
* and reason.
|
|
*
|
|
* Visibility: protected
|
|
* State: Valid for OPEN, ignored otherwise.
|
|
* Concurrency: Must be called within m_strand
|
|
*
|
|
* @param code The code to send
|
|
* @param reason The reason to send
|
|
*/
|
|
void begin_close(close::status::value code, const std::string& reason) {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
alog()->at(log::alevel::DEBUG_CLOSE) << "begin_close called" << log::endl;
|
|
|
|
if (m_detached) {return;}
|
|
|
|
if (m_state != session::state::OPEN) {
|
|
elog()->at(log::elevel::WARN)
|
|
<< "Tried to disconnect a session that wasn't open" << log::endl;
|
|
return;
|
|
}
|
|
|
|
if (close::status::invalid(code)) {
|
|
elog()->at(log::elevel::WARN)
|
|
<< "Tried to close a connection with invalid close code: "
|
|
<< code << log::endl;
|
|
return;
|
|
} else if (close::status::reserved(code)) {
|
|
elog()->at(log::elevel::WARN)
|
|
<< "Tried to close a connection with reserved close code: "
|
|
<< code << log::endl;
|
|
return;
|
|
}
|
|
|
|
m_state = session::state::CLOSING;
|
|
|
|
m_closed_by_me = true;
|
|
|
|
if (m_silent_close) {
|
|
m_local_close_code = close::status::NO_STATUS;
|
|
m_local_close_reason = "";
|
|
|
|
// In case of protocol error in silent mode just drop the connection.
|
|
// This is allowed by the spec and improves robustness over sending an
|
|
// empty close frame that we would ignore the ack to.
|
|
if (m_protocol_error) {
|
|
terminate(false);
|
|
return;
|
|
}
|
|
} else {
|
|
m_local_close_code = code;
|
|
m_local_close_reason = reason;
|
|
}
|
|
|
|
// wait for close timer
|
|
// TODO: configurable value
|
|
register_timeout(5000,fail::status::WEBSOCKET,"Timeout on close handshake");
|
|
|
|
// TODO: optimize control messages and handle case where endpoint is
|
|
// out of messages
|
|
message::data_ptr msg = get_control_message2();
|
|
|
|
if (!msg) {
|
|
// Server endpoint is out of control messages. Force drop connection
|
|
elog()->at(log::elevel::RERROR)
|
|
<< "Request for control message failed (out of resources). Terminating connection."
|
|
<< log::endl;
|
|
terminate(true);
|
|
return;
|
|
}
|
|
|
|
msg->reset(frame::opcode::CLOSE);
|
|
m_processor->prepare_close_frame(msg,m_local_close_code,m_local_close_reason);
|
|
|
|
// Using strand post here rather than ioservice.post(strand.wrap)
|
|
// to ensure that messages are sent in order
|
|
m_strand.post(boost::bind(
|
|
&type::write_message,
|
|
type::shared_from_this(),
|
|
msg
|
|
));
|
|
}
|
|
|
|
/// send an acknowledgement close frame
|
|
/**
|
|
* Visibility: private
|
|
* State: no state checking, should only be called within process_control
|
|
* Concurrency: Must be called within m_stranded method
|
|
*/
|
|
void send_close_ack() {
|
|
alog()->at(log::alevel::DEBUG_CLOSE) << "send_close_ack called" << log::endl;
|
|
|
|
// echo close value unless there is a good reason not to.
|
|
if (m_silent_close) {
|
|
m_local_close_code = close::status::NO_STATUS;
|
|
m_local_close_reason = "";
|
|
} else if (m_remote_close_code == close::status::NO_STATUS) {
|
|
m_local_close_code = close::status::NORMAL;
|
|
m_local_close_reason = "";
|
|
} else if (m_remote_close_code == close::status::ABNORMAL_CLOSE) {
|
|
// TODO: can we possibly get here? This means send_close_ack was
|
|
// called after a connection ended without getting a close
|
|
// frame
|
|
throw "shouldn't be here";
|
|
} else if (close::status::invalid(m_remote_close_code)) {
|
|
// TODO: shouldn't be able to get here now either
|
|
m_local_close_code = close::status::PROTOCOL_ERROR;
|
|
m_local_close_reason = "Status code is invalid";
|
|
} else if (close::status::reserved(m_remote_close_code)) {
|
|
// TODO: shouldn't be able to get here now either
|
|
m_local_close_code = close::status::PROTOCOL_ERROR;
|
|
m_local_close_reason = "Status code is reserved";
|
|
} else {
|
|
m_local_close_code = m_remote_close_code;
|
|
m_local_close_reason = m_remote_close_reason;
|
|
}
|
|
|
|
// TODO: check whether we should cancel the current in flight write.
|
|
// if not canceled the close message will be sent as soon as the
|
|
// current write completes.
|
|
|
|
|
|
// TODO: optimize control messages and handle case where endpoint is
|
|
// out of messages
|
|
message::data_ptr msg = get_control_message2();
|
|
|
|
if (!msg) {
|
|
// server is out of resources, close connection.
|
|
elog()->at(log::elevel::RERROR)
|
|
<< "Request for control message failed (out of resources). Terminating connection."
|
|
<< log::endl;
|
|
terminate(true);
|
|
return;
|
|
}
|
|
|
|
msg->reset(frame::opcode::CLOSE);
|
|
m_processor->prepare_close_frame(msg,m_local_close_code,m_local_close_reason);
|
|
|
|
// Using strand post here rather than ioservice.post(strand.wrap)
|
|
// to ensure that messages are sent in order
|
|
m_strand.post(boost::bind(
|
|
&type::write_message,
|
|
type::shared_from_this(),
|
|
msg
|
|
));
|
|
|
|
//m_write_state = INTURRUPT;
|
|
}
|
|
|
|
/// Push message to write queue and start writer if it was idle
|
|
/**
|
|
* Visibility: protected (called only by asio dispatcher)
|
|
* State: Valid from OPEN and CLOSING, ignored otherwise
|
|
* Concurrency: Must be called within m_stranded method
|
|
*/
|
|
void write_message(message::data_ptr msg) {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
if (m_state != session::state::OPEN && m_state != session::state::CLOSING) {return;}
|
|
if (m_write_state == INTURRUPT) {return;}
|
|
|
|
m_write_buffer += msg->get_payload().size();
|
|
m_write_queue.push(msg);
|
|
|
|
write();
|
|
}
|
|
|
|
/// Begin async write of next message in list
|
|
/**
|
|
* Visibility: private
|
|
* State: Valid only as called from write_message or handle_write
|
|
* Concurrency: Must be called within m_stranded method
|
|
*/
|
|
void write() {
|
|
switch (m_write_state) {
|
|
case IDLE:
|
|
break;
|
|
case WRITING:
|
|
// already writing. write() will get called again by the write
|
|
// handler once it is ready.
|
|
return;
|
|
case INTURRUPT:
|
|
// clear the queue except for the last message
|
|
while (m_write_queue.size() > 1) {
|
|
m_write_buffer -= m_write_queue.front()->get_payload().size();
|
|
m_write_queue.pop();
|
|
}
|
|
break;
|
|
default:
|
|
// TODO: assert shouldn't be here
|
|
break;
|
|
}
|
|
|
|
if (!m_write_queue.empty()) {
|
|
if (m_write_state == IDLE) {
|
|
m_write_state = WRITING;
|
|
}
|
|
|
|
m_write_buf.push_back(boost::asio::buffer(m_write_queue.front()->get_header()));
|
|
m_write_buf.push_back(boost::asio::buffer(m_write_queue.front()->get_payload()));
|
|
|
|
//m_endpoint.alog().at(log::alevel::DEVEL) << "write header: " << zsutil::to_hex(m_write_queue.front()->get_header()) << log::endl;
|
|
|
|
socket_type::get_socket().async_write(
|
|
m_write_buf,
|
|
m_strand.wrap(boost::bind(
|
|
&type::handle_write,
|
|
type::shared_from_this(),
|
|
boost::asio::placeholders::error
|
|
))
|
|
);
|
|
} else {
|
|
// if we are in an inturrupted state and had nothing else to write
|
|
// it is safe to terminate the connection.
|
|
if (m_write_state == INTURRUPT) {
|
|
alog()->at(log::alevel::DEBUG_CLOSE)
|
|
<< "Exit after inturrupt" << log::endl;
|
|
terminate(false);
|
|
} else {
|
|
m_handler->on_send_empty(type::shared_from_this());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// async write callback
|
|
/**
|
|
* Visibility: protected (called only by asio dispatcher)
|
|
* State: Valid from OPEN and CLOSING, ignored otherwise
|
|
* Concurrency: Must be called within m_stranded method
|
|
*/
|
|
void handle_write(const boost::system::error_code& error) {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
|
|
|
|
if (error) {
|
|
if (m_state == session::state::CLOSED) {
|
|
alog()->at(log::alevel::DEBUG_CLOSE)
|
|
<< "handle_write error in CLOSED state. Ignoring."
|
|
<< log::endl;
|
|
return;
|
|
}
|
|
|
|
if (error == boost::asio::error::operation_aborted) {
|
|
// previous write was aborted
|
|
alog()->at(log::alevel::DEBUG_CLOSE)
|
|
<< "ASIO write was aborted. Exiting write loop."
|
|
<< log::endl;
|
|
//terminate(false);
|
|
return;
|
|
} else {
|
|
log_error("ASIO write failed with unknown error. Terminating connection.",error);
|
|
terminate(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (m_write_queue.size() == 0) {
|
|
alog()->at(log::alevel::DEBUG_CLOSE)
|
|
<< "handle_write called with empty queue" << log::endl;
|
|
return;
|
|
}
|
|
|
|
m_write_buffer -= m_write_queue.front()->get_payload().size();
|
|
m_write_buf.clear();
|
|
|
|
frame::opcode::value code = m_write_queue.front()->get_opcode();
|
|
|
|
m_write_queue.pop();
|
|
|
|
if (m_write_state == WRITING) {
|
|
m_write_state = IDLE;
|
|
}
|
|
|
|
|
|
|
|
|
|
if (code != frame::opcode::CLOSE) {
|
|
// only continue next write if the connection is still open
|
|
if (m_state == session::state::OPEN || m_state == session::state::CLOSING) {
|
|
write();
|
|
}
|
|
} else {
|
|
if (m_closed_by_me) {
|
|
alog()->at(log::alevel::DEBUG_CLOSE)
|
|
<< "Initial close frame sent" << log::endl;
|
|
// this is my close message
|
|
// no more writes allowed after this. will hang on to read their
|
|
// close response unless I just sent a protocol error close, in
|
|
// which case I assume the other end is too broken to return
|
|
// a meaningful response.
|
|
if (m_protocol_error) {
|
|
terminate(false);
|
|
}
|
|
} else {
|
|
// this is a close ack
|
|
// now that it has been written close the connection
|
|
|
|
if (m_endpoint.is_server()) {
|
|
// if I am a server close immediately
|
|
alog()->at(log::alevel::DEBUG_CLOSE)
|
|
<< "Close ack sent. Terminating immediately." << log::endl;
|
|
terminate(false);
|
|
} else {
|
|
// if I am a client give the server a moment to close.
|
|
alog()->at(log::alevel::DEBUG_CLOSE)
|
|
<< "Close ack sent. Termination queued." << log::endl;
|
|
// TODO: set timer here and close afterwards
|
|
terminate(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ends the connection by cleaning up based on current state
|
|
/** Terminate will review the outstanding resources and close each
|
|
* appropriately. Attached handlers will recieve an on_fail or on_close call
|
|
*
|
|
* TODO: should we protect against long running handlers?
|
|
*
|
|
* Visibility: protected
|
|
* State: Valid from any state except CLOSED.
|
|
* Concurrency: Must be called from within m_strand
|
|
*
|
|
* @param failed_by_me Whether or not to mark the connection as failed by me
|
|
*/
|
|
void terminate(bool failed_by_me) {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
|
|
alog()->at(log::alevel::DEVEL)
|
|
<< "terminate called from state: " << m_state << log::endl;
|
|
|
|
// If state is closed it either means terminate was called twice or
|
|
// something other than this library called it. In either case running
|
|
// it will only cause problems
|
|
if (m_state == session::state::CLOSED) {return;}
|
|
|
|
// TODO: ensure any other timers are accounted for here.
|
|
// cancel the close timeout
|
|
cancel_timeout();
|
|
|
|
// version -1 is an HTTP (non-websocket) connection.
|
|
if (role_type::get_version() != -1) {
|
|
// TODO: note, calling shutdown on the ssl socket for an HTTP
|
|
// connection seems to cause shutdown to block for a very long time.
|
|
// NOT calling it for a websocket connection causes the connection
|
|
// to time out. Behavior now is correct but I am not sure why.
|
|
m_dropped_by_me = socket_type::shutdown();
|
|
|
|
m_failed_by_me = failed_by_me;
|
|
|
|
session::state::value old_state = m_state;
|
|
m_state = session::state::CLOSED;
|
|
|
|
if (old_state == session::state::CONNECTING) {
|
|
m_handler->on_fail(type::shared_from_this());
|
|
|
|
if (m_fail_code == fail::status::GOOD) {
|
|
m_fail_code = fail::status::UNKNOWN;
|
|
m_fail_reason = "Terminate called in connecting state without more specific error.";
|
|
}
|
|
} else if (old_state == session::state::OPEN ||
|
|
old_state == session::state::CLOSING)
|
|
{
|
|
m_handler->on_close(type::shared_from_this());
|
|
|
|
if (m_fail_code == fail::status::GOOD) {
|
|
m_fail_code = fail::status::WEBSOCKET;
|
|
m_fail_reason = "Terminate called in open state without more specific error.";
|
|
}
|
|
}
|
|
|
|
log_close_result();
|
|
}
|
|
|
|
// finally remove this connection from the endpoint's list. This will
|
|
// remove the last shared pointer to the connection held by WS++. If we
|
|
// are DETACHED this has already been done and can't be done again.
|
|
if (!m_detached) {
|
|
alog()->at(log::alevel::DEVEL)
|
|
<< "terminate removing connection" << log::endl;
|
|
m_endpoint.remove_connection(type::shared_from_this());
|
|
|
|
/*m_endpoint.endpoint_base::m_io_service.post(
|
|
m_strand.wrap(boost::bind(
|
|
&type::remove_connection,
|
|
type::shared_from_this()
|
|
))
|
|
);*/
|
|
}
|
|
}
|
|
|
|
// This was an experiment in deferring the detachment of the connection
|
|
// until all handlers had been cleaned up. With the switch to a local logger
|
|
// this can probably be removed entirely.
|
|
void remove_connection() {
|
|
// finally remove this connection from the endpoint's list. This will
|
|
// remove the last shared pointer to the connection held by WS++. If we
|
|
// are DETACHED this has already been done and can't be done again.
|
|
/*if (!m_detached) {
|
|
alog()->at(log::alevel::DEVEL) << "terminate removing connection: start" << log::endl;
|
|
m_endpoint.remove_connection(type::shared_from_this());
|
|
}*/
|
|
}
|
|
|
|
// this is called when an async asio call encounters an error
|
|
void log_error(std::string msg,const boost::system::error_code& e) {
|
|
elog()->at(log::elevel::RERROR) << msg << "(" << e << ")" << log::endl;
|
|
}
|
|
|
|
void log_close_result() {
|
|
alog()->at(log::alevel::DISCONNECT)
|
|
//<< "Disconnect " << (m_was_clean ? "Clean" : "Unclean")
|
|
<< "Disconnect "
|
|
<< " close local:[" << m_local_close_code
|
|
<< (m_local_close_reason == "" ? "" : ","+m_local_close_reason)
|
|
<< "] remote:[" << m_remote_close_code
|
|
<< (m_remote_close_reason == "" ? "" : ","+m_remote_close_reason) << "]"
|
|
<< log::endl;
|
|
}
|
|
|
|
void register_timeout(size_t ms,fail::status::value s, std::string msg) {
|
|
m_timer.expires_from_now(boost::posix_time::milliseconds(ms));
|
|
m_timer.async_wait(
|
|
m_strand.wrap(boost::bind(
|
|
&type::fail_on_expire,
|
|
type::shared_from_this(),
|
|
boost::asio::placeholders::error,
|
|
s,
|
|
msg
|
|
))
|
|
);
|
|
}
|
|
|
|
void cancel_timeout() {
|
|
m_timer.cancel();
|
|
}
|
|
|
|
void fail_on_expire(const boost::system::error_code& error,
|
|
fail::status::value status,
|
|
std::string& msg)
|
|
{
|
|
if (error) {
|
|
if (error != boost::asio::error::operation_aborted) {
|
|
elog()->at(log::elevel::DEVEL)
|
|
<< "fail_on_expire timer ended in unknown error" << log::endl;
|
|
terminate(false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
m_fail_code = status;
|
|
m_fail_system = error;
|
|
m_fail_reason = msg;
|
|
|
|
alog()->at(log::alevel::DISCONNECT)
|
|
<< "fail_on_expire timer expired with message: " << msg << log::endl;
|
|
terminate(true);
|
|
}
|
|
|
|
boost::asio::streambuf& buffer() {
|
|
return m_buf;
|
|
}
|
|
public:
|
|
//protected: TODO: figure out why VCPP2010 doesn't like protected here
|
|
endpoint_type& m_endpoint;
|
|
typename endpoint::alogger_ptr m_alog;
|
|
typename endpoint::elogger_ptr m_elog;
|
|
|
|
// Overridable connection specific settings
|
|
handler_ptr m_handler; // object to dispatch callbacks to
|
|
size_t m_read_threshold; // maximum number of bytes to fetch in
|
|
// a single async read.
|
|
bool m_silent_close; // suppresses the return of detailed
|
|
// close codes.
|
|
|
|
// Network resources
|
|
boost::asio::streambuf m_buf;
|
|
boost::asio::deadline_timer m_timer;
|
|
|
|
// WebSocket connection state
|
|
session::state::value m_state;
|
|
bool m_protocol_error;
|
|
|
|
// stuff that actually does the work
|
|
processor::ptr m_processor;
|
|
|
|
// Write queue
|
|
std::vector<boost::asio::const_buffer> m_write_buf;
|
|
std::queue<message::data_ptr> m_write_queue;
|
|
uint64_t m_write_buffer;
|
|
write_state m_write_state;
|
|
|
|
// Close state
|
|
fail::status::value m_fail_code;
|
|
boost::system::error_code m_fail_system;
|
|
std::string m_fail_reason;
|
|
close::status::value m_local_close_code;
|
|
std::string m_local_close_reason;
|
|
close::status::value m_remote_close_code;
|
|
std::string m_remote_close_reason;
|
|
bool m_closed_by_me;
|
|
bool m_failed_by_me;
|
|
bool m_dropped_by_me;
|
|
|
|
// Read queue
|
|
read_state m_read_state;
|
|
message::control_ptr m_control_message;
|
|
|
|
// concurrency support
|
|
mutable boost::recursive_mutex m_lock;
|
|
boost::asio::strand m_strand;
|
|
bool m_detached; // TODO: this should be atomic
|
|
};
|
|
|
|
// connection related types that it and its policy classes need.
|
|
template <
|
|
typename endpoint,
|
|
template <class> class role,
|
|
template <class> class socket>
|
|
struct connection_traits< connection<endpoint,role,socket> > {
|
|
// type of the connection itself
|
|
typedef connection<endpoint,role,socket> type;
|
|
typedef boost::shared_ptr<type> ptr;
|
|
|
|
// types of the connection policies
|
|
typedef endpoint endpoint_type;
|
|
typedef role< type > role_type;
|
|
typedef socket< type > socket_type;
|
|
};
|
|
|
|
/// convenience overload for sending a one off message.
|
|
/**
|
|
* Creates a message, fills in payload, and queues a write as a message of
|
|
* type op. Default type is TEXT.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from OPEN, ignored otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @param payload Payload to write_state
|
|
* @param op opcode to send the message as
|
|
*/
|
|
template <typename endpoint,template <class> class role,template <class> class socket>
|
|
void
|
|
connection<endpoint,role,socket>::send(const std::string& payload,frame::opcode::value op)
|
|
{
|
|
{
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
if (m_state != session::state::OPEN) {return;}
|
|
}
|
|
|
|
websocketpp::message::data::ptr msg = get_control_message2();
|
|
|
|
if (!msg) {
|
|
throw exception("Endpoint send queue is full",error::SEND_QUEUE_FULL);
|
|
}
|
|
if (op != frame::opcode::TEXT && op != frame::opcode::BINARY) {
|
|
throw exception("opcode must be either TEXT or BINARY",error::GENERIC);
|
|
}
|
|
|
|
msg->reset(op);
|
|
msg->set_payload(payload);
|
|
send(msg);
|
|
}
|
|
|
|
/// Send message
|
|
/**
|
|
* Prepares (if necessary) and sends the given message
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from OPEN, ignored otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @param msg A pointer to a data message buffer to send.
|
|
*/
|
|
template <typename endpoint,template <class> class role,template <class> class socket>
|
|
void connection<endpoint,role,socket>::send(message::data_ptr msg) {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_lock);
|
|
if (m_state != session::state::OPEN) {return;}
|
|
|
|
m_processor->prepare_frame(msg);
|
|
|
|
// Using strand post here rather than ioservice.post(strand.wrap)
|
|
// to ensure that messages are sent in order
|
|
m_strand.post(boost::bind(
|
|
&type::write_message,
|
|
type::shared_from_this(),
|
|
msg
|
|
));
|
|
}
|
|
|
|
} // namespace websocketpp
|
|
|
|
#endif // WEBSOCKETPP_CONNECTION_HPP
|