mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
715 lines
24 KiB
C++
715 lines
24 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_ROLE_CLIENT_HPP
|
|
#define WEBSOCKETPP_ROLE_CLIENT_HPP
|
|
|
|
#include <limits>
|
|
#include <iostream>
|
|
|
|
#include <boost/cstdint.hpp>
|
|
#include <boost/asio.hpp>
|
|
#include <boost/bind.hpp>
|
|
#include <boost/shared_ptr.hpp>
|
|
#include <boost/random.hpp>
|
|
#include <boost/random/random_device.hpp>
|
|
|
|
#include "../endpoint.hpp"
|
|
#include "../uri.hpp"
|
|
#include "../shared_const_buffer.hpp"
|
|
|
|
#ifdef _MSC_VER
|
|
// Disable "warning C4355: 'this' : used in base member initializer list".
|
|
# pragma warning(push)
|
|
# pragma warning(disable:4355)
|
|
#endif
|
|
|
|
using boost::asio::ip::tcp;
|
|
|
|
namespace websocketpp {
|
|
namespace role {
|
|
|
|
template <class endpoint>
|
|
class client {
|
|
public:
|
|
// Connection specific details
|
|
template <typename connection_type>
|
|
class connection {
|
|
public:
|
|
typedef connection<connection_type> type;
|
|
typedef endpoint endpoint_type;
|
|
|
|
// client connections are friends with their respective client endpoint
|
|
friend class client<endpoint>;
|
|
|
|
// Valid always
|
|
int get_version() const {
|
|
return m_version;
|
|
}
|
|
|
|
std::string get_origin() const {
|
|
return m_origin;
|
|
}
|
|
|
|
// not sure when valid
|
|
std::string get_request_header(const std::string& key) const {
|
|
return m_request.header(key);
|
|
}
|
|
std::string get_response_header(const std::string& key) const {
|
|
return m_response.header(key);
|
|
}
|
|
|
|
// Valid before connect is called
|
|
void add_request_header(const std::string& key, const std::string& value) {
|
|
m_request.add_header(key,value);
|
|
}
|
|
void replace_request_header(const std::string& key, const std::string& value) {
|
|
m_request.replace_header(key,value);
|
|
}
|
|
void remove_request_header(const std::string& key) {
|
|
m_request.remove_header(key);
|
|
}
|
|
|
|
void add_subprotocol(const std::string& value) {
|
|
m_requested_subprotocols.push_back(value);
|
|
}
|
|
|
|
void set_origin(const std::string& value) {
|
|
m_origin = value;
|
|
}
|
|
|
|
// Information about the requested URI
|
|
// valid only after URIs are loaded
|
|
// TODO: check m_uri for NULLness
|
|
bool get_secure() const {
|
|
return m_uri->get_secure();
|
|
}
|
|
std::string get_host() const {
|
|
return m_uri->get_host();
|
|
}
|
|
std::string get_resource() const {
|
|
return m_uri->get_resource();
|
|
}
|
|
uint16_t get_port() const {
|
|
return m_uri->get_port();
|
|
}
|
|
std::string get_uri() const {
|
|
return m_uri->str();
|
|
}
|
|
|
|
int32_t rand() {
|
|
return m_endpoint.rand();
|
|
}
|
|
|
|
bool is_server() const {
|
|
return false;
|
|
}
|
|
|
|
// should this exist?
|
|
boost::asio::io_service& get_io_service() {
|
|
return m_endpoint.get_io_service();
|
|
}
|
|
protected:
|
|
connection(endpoint& e)
|
|
: m_endpoint(e),
|
|
m_connection(static_cast< connection_type& >(*this)),
|
|
// TODO: version shouldn't be hardcoded
|
|
m_version(13) {}
|
|
|
|
void set_uri(uri_ptr u) {
|
|
m_uri = u;
|
|
}
|
|
|
|
void async_init() {
|
|
m_connection.m_processor = processor::ptr(new processor::hybi<connection_type>(m_connection));
|
|
|
|
m_connection.get_handler()->on_handshake_init(m_connection.shared_from_this());
|
|
|
|
write_request();
|
|
}
|
|
|
|
void write_request();
|
|
void handle_write_request(const boost::system::error_code& error);
|
|
void read_response();
|
|
void handle_read_response(const boost::system::error_code& error,
|
|
std::size_t bytes_transferred);
|
|
|
|
void log_open_result();
|
|
private:
|
|
endpoint& m_endpoint;
|
|
connection_type& m_connection;
|
|
|
|
int m_version;
|
|
uri_ptr m_uri;
|
|
std::string m_origin;
|
|
std::vector<std::string> m_requested_subprotocols;
|
|
std::vector<std::string> m_requested_extensions;
|
|
std::string m_subprotocol;
|
|
std::vector<std::string> m_extensions;
|
|
|
|
std::string m_handshake_key;
|
|
http::parser::request m_request;
|
|
http::parser::response m_response;
|
|
};
|
|
|
|
// types
|
|
typedef client<endpoint> type;
|
|
typedef endpoint endpoint_type;
|
|
|
|
typedef typename endpoint_traits<endpoint>::connection_type connection_type;
|
|
typedef typename endpoint_traits<endpoint>::connection_ptr connection_ptr;
|
|
typedef typename endpoint_traits<endpoint>::handler_ptr handler_ptr;
|
|
|
|
// handler interface callback class
|
|
class handler_interface {
|
|
public:
|
|
virtual ~handler_interface() {}
|
|
|
|
// Required
|
|
virtual void on_open(connection_ptr con) {}
|
|
virtual void on_close(connection_ptr con) {}
|
|
virtual void on_fail(connection_ptr con) {}
|
|
|
|
virtual void on_message(connection_ptr con,message::data_ptr) {}
|
|
|
|
// Optional
|
|
virtual void on_handshake_init(connection_ptr con) {}
|
|
virtual bool on_ping(connection_ptr con,std::string) {return true;}
|
|
virtual void on_pong(connection_ptr con,std::string) {}
|
|
virtual void on_pong_timeout(connection_ptr con,std::string) {}
|
|
|
|
};
|
|
|
|
client (boost::asio::io_service& m)
|
|
: m_endpoint(static_cast< endpoint_type& >(*this)),
|
|
m_io_service(m),
|
|
m_gen(m_rng,
|
|
boost::random::uniform_int_distribution<>(
|
|
(std::numeric_limits<int32_t>::min)(),
|
|
(std::numeric_limits<int32_t>::max)()
|
|
)) {}
|
|
|
|
connection_ptr get_connection(const std::string& u);
|
|
|
|
connection_ptr connect(const std::string& u);
|
|
connection_ptr connect(connection_ptr con);
|
|
|
|
void run(bool perpetual = false);
|
|
void end_perpetual();
|
|
void reset();
|
|
protected:
|
|
bool is_server() const {return false;}
|
|
int32_t rand() {return m_gen();}
|
|
private:
|
|
void handle_connect(connection_ptr con, const boost::system::error_code& error);
|
|
|
|
endpoint_type& m_endpoint;
|
|
boost::asio::io_service& m_io_service;
|
|
|
|
boost::random::random_device m_rng;
|
|
boost::random::variate_generator<
|
|
boost::random::random_device&,
|
|
boost::random::uniform_int_distribution<>
|
|
> m_gen;
|
|
|
|
boost::shared_ptr<boost::asio::io_service::work> m_idle_worker;
|
|
};
|
|
|
|
// client implimentation
|
|
|
|
/// Start the client ASIO loop
|
|
/**
|
|
* Calls run on the endpoint's io_service. This method will block until the io_service
|
|
* run method returns. This method may only be called when the endpoint is in the IDLE
|
|
* state. Endpoints start in the idle state and can be returned to the IDLE state by
|
|
* calling reset. `run` has a perpetual flag (default is false) that indicates whether
|
|
* or not it should return after all connections have been made.
|
|
*
|
|
* <b>Important note:</b> Calling run with perpetual = false on a client endpoint will return
|
|
* immediately unless you have already called connect() at least once. To get around
|
|
* this either queue up all connections you want to make before calling run or call
|
|
* run with perpetual in another thread.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from IDLE, an exception is thrown otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @param perpetual whether or not to run the endpoint in perpetual mode
|
|
* @exception websocketpp::exception with code error::INVALID_STATE if called from a state other than IDLE
|
|
*/
|
|
template <class endpoint>
|
|
void client<endpoint>::run(bool perpetual) {
|
|
{
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_endpoint.m_lock);
|
|
|
|
if (m_endpoint.m_state != endpoint::IDLE) {
|
|
throw exception("client::run called from invalid state",error::INVALID_STATE);
|
|
}
|
|
|
|
if (perpetual) {
|
|
m_idle_worker = boost::shared_ptr<boost::asio::io_service::work>(
|
|
new boost::asio::io_service::work(m_io_service)
|
|
);
|
|
}
|
|
|
|
m_endpoint.m_state = endpoint::RUNNING;
|
|
}
|
|
|
|
// TODO: preliminary support for multi-threaded clients. Finish external
|
|
// interface once better tested
|
|
size_t num_threads = 1;
|
|
|
|
if (num_threads == 1) {
|
|
m_io_service.run();
|
|
} else if (num_threads > 1 && num_threads <= MAX_THREAD_POOL_SIZE) {
|
|
std::vector< boost::shared_ptr<boost::thread> > threads;
|
|
|
|
for (std::size_t i = 0; i < num_threads; ++i) {
|
|
boost::shared_ptr<boost::thread> thread(
|
|
new boost::thread(boost::bind(
|
|
&boost::asio::io_service::run,
|
|
&m_io_service
|
|
))
|
|
);
|
|
threads.push_back(thread);
|
|
}
|
|
|
|
for (std::size_t i = 0; i < threads.size(); ++i) {
|
|
threads[i]->join();
|
|
}
|
|
} else {
|
|
throw exception("listen called with invalid num_threads value");
|
|
}
|
|
|
|
m_endpoint.m_state = endpoint::STOPPED;
|
|
}
|
|
|
|
/// End the idle work loop that keeps the io_service active
|
|
/**
|
|
* Calling end_perpetual on a client endpoint that was started in perpetual mode (via
|
|
* run(true), will stop the idle work object that prevents the run method from
|
|
* returning even when there is no work for it to do. Use if you want to gracefully
|
|
* stop the endpoint. Use stop() to forcibly stop the endpoint.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from RUNNING, ignored otherwise
|
|
* Concurrency: callable from any thread
|
|
*/
|
|
template <class endpoint>
|
|
void client<endpoint>::end_perpetual() {
|
|
if (m_idle_worker) {
|
|
m_idle_worker.reset();
|
|
}
|
|
}
|
|
|
|
/// Reset a stopped endpoint.
|
|
/**
|
|
* Resets an endpoint that was stopped by stop() or whose run() method exited due to
|
|
* running out of work. reset() should not be called while the endpoint is running.
|
|
* Use stop() and/or end_perpetual() first and then reset once one of those methods
|
|
* has fully stopped the endpoint.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from STOPPED, an exception is thrown otherwise
|
|
* Concurrency: callable from any thread
|
|
*/
|
|
template <class endpoint>
|
|
void client<endpoint>::reset() {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_endpoint.m_lock);
|
|
|
|
if (m_endpoint.m_state != endpoint::STOPPED) {
|
|
throw exception("client::reset called from invalid state",error::INVALID_STATE);
|
|
}
|
|
|
|
m_io_service.reset();
|
|
|
|
m_endpoint.m_state = endpoint::IDLE;
|
|
}
|
|
|
|
/// Returns a new connection
|
|
/**
|
|
* Creates and returns a pointer to a new connection to the given URI suitable for passing
|
|
* to connect(). This method allows applying connection specific settings before
|
|
* performing the connection.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from IDLE or RUNNING, an exception is thrown otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @param u The URI that this connection will connect to.
|
|
* @return The pointer to the new connection
|
|
*/
|
|
template <class endpoint>
|
|
typename endpoint_traits<endpoint>::connection_ptr
|
|
client<endpoint>::get_connection(const std::string& u) {
|
|
try {
|
|
uri_ptr location(new uri(u));
|
|
|
|
if (location->get_secure() && !m_endpoint.is_secure()) {
|
|
throw websocketpp::exception("Endpoint doesn't support secure connections.",
|
|
websocketpp::error::ENDPOINT_UNSECURE);
|
|
}
|
|
|
|
connection_ptr con = m_endpoint.create_connection();
|
|
|
|
if (!con) {
|
|
throw websocketpp::exception("get_connection called from invalid state",
|
|
websocketpp::error::INVALID_STATE);
|
|
}
|
|
|
|
con->set_uri(location);
|
|
|
|
return con;
|
|
} catch (uri_exception& e) {
|
|
throw websocketpp::exception(e.what(),websocketpp::error::INVALID_URI);
|
|
}
|
|
}
|
|
|
|
/// Begin the connect process for the given connection.
|
|
/**
|
|
* Initiates the async connect request for connection con.
|
|
*
|
|
* Visibility: public
|
|
* State: Valid from IDLE or RUNNING, an exception is thrown otherwise
|
|
* Concurrency: callable from any thread
|
|
*
|
|
* @param con A pointer to the connection to connect
|
|
* @return The pointer to con
|
|
*/
|
|
template <class endpoint>
|
|
typename endpoint_traits<endpoint>::connection_ptr
|
|
client<endpoint>::connect(connection_ptr con) {
|
|
tcp::resolver resolver(m_io_service);
|
|
|
|
std::stringstream p;
|
|
p << con->get_port();
|
|
|
|
tcp::resolver::query query(con->get_host(),p.str());
|
|
tcp::resolver::iterator iterator = resolver.resolve(query);
|
|
|
|
boost::asio::async_connect(
|
|
con->get_raw_socket(),
|
|
iterator,
|
|
boost::bind(
|
|
&endpoint_type::handle_connect,
|
|
this, // shared from this?
|
|
con,
|
|
boost::asio::placeholders::error
|
|
)
|
|
);
|
|
|
|
return con;
|
|
}
|
|
|
|
/// Convenience method, equivalent to connect(get_connection(u))
|
|
template <class endpoint>
|
|
typename endpoint_traits<endpoint>::connection_ptr
|
|
client<endpoint>::connect(const std::string& u) {
|
|
return connect(get_connection(u));
|
|
}
|
|
|
|
template <class endpoint>
|
|
void client<endpoint>::handle_connect(connection_ptr con,
|
|
const boost::system::error_code& error)
|
|
{
|
|
if (!error) {
|
|
m_endpoint.m_alog->at(log::alevel::CONNECT)
|
|
<< "Successful connection" << log::endl;
|
|
|
|
con->start();
|
|
} else {
|
|
con->m_fail_code = fail::status::SYSTEM;
|
|
con->m_fail_system = error;
|
|
|
|
if (error == boost::system::errc::connection_refused) {
|
|
con->m_fail_reason = "Connection refused";
|
|
} else if (error == boost::system::errc::operation_canceled) {
|
|
con->m_fail_reason = "Operation canceled";
|
|
} else if (error == boost::system::errc::connection_reset) {
|
|
con->m_fail_reason = "Connection Reset";
|
|
} else if (error == boost::system::errc::timed_out) {
|
|
con->m_fail_reason = "Operation timed out";
|
|
} else if (error == boost::system::errc::broken_pipe) {
|
|
con->m_fail_reason = "Broken pipe";
|
|
} else {
|
|
con->m_fail_reason = "Unknown";
|
|
}
|
|
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "An error occurred while establishing a connection: "
|
|
<< error << " (" << con->m_fail_reason << ")" << log::endl;
|
|
|
|
con->terminate(false);
|
|
}
|
|
}
|
|
|
|
// client connection implimentation
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void client<endpoint>::connection<connection_type>::write_request() {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_connection.m_lock);
|
|
// async write to handle_write
|
|
|
|
m_request.set_method("GET");
|
|
m_request.set_uri(m_uri->get_resource());
|
|
m_request.set_version("HTTP/1.1");
|
|
|
|
m_request.add_header("Upgrade","websocket");
|
|
m_request.add_header("Connection","Upgrade");
|
|
m_request.replace_header("Sec-WebSocket-Version","13");
|
|
|
|
m_request.replace_header("Host",m_uri->get_host_port());
|
|
|
|
if (m_origin != "") {
|
|
m_request.replace_header("Origin",m_origin);
|
|
}
|
|
|
|
if (m_requested_subprotocols.size() > 0) {
|
|
std::string vals;
|
|
std::string sep = "";
|
|
|
|
std::vector<std::string>::iterator it;
|
|
for (it = m_requested_subprotocols.begin(); it != m_requested_subprotocols.end(); ++it) {
|
|
vals += sep + *it;
|
|
sep = ",";
|
|
}
|
|
|
|
m_request.replace_header("Sec-WebSocket-Protocol",vals);
|
|
}
|
|
|
|
// Generate client key
|
|
int32_t raw_key[4];
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
raw_key[i] = this->rand();
|
|
}
|
|
|
|
m_handshake_key = base64_encode(reinterpret_cast<unsigned char const*>(raw_key), 16);
|
|
|
|
m_request.replace_header("Sec-WebSocket-Key",m_handshake_key);
|
|
|
|
// Unless the user has overridden the user agent, send generic WS++
|
|
if (m_request.header("User-Agent") == "") {
|
|
m_request.replace_header("User-Agent",USER_AGENT);
|
|
}
|
|
|
|
|
|
|
|
// TODO: generating this raw request involves way too much copying in cases
|
|
// without string/vector move semantics.
|
|
shared_const_buffer buffer(m_request.raw());
|
|
|
|
//std::string raw = m_request.raw();
|
|
|
|
//m_endpoint.m_alog->at(log::alevel::DEBUG_HANDSHAKE) << raw << log::endl;
|
|
|
|
boost::asio::async_write(
|
|
m_connection.get_socket(),
|
|
//boost::asio::buffer(raw),
|
|
buffer,
|
|
m_connection.get_strand().wrap(boost::bind(
|
|
&type::handle_write_request,
|
|
m_connection.shared_from_this(),
|
|
boost::asio::placeholders::error
|
|
))
|
|
);
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void client<endpoint>::connection<connection_type>::handle_write_request(
|
|
const boost::system::error_code& error)
|
|
{
|
|
if (error) {
|
|
// TODO: detached state?
|
|
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "Error writing WebSocket request. code: "
|
|
<< error << log::endl;
|
|
m_connection.terminate(false);
|
|
return;
|
|
}
|
|
|
|
read_response();
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void client<endpoint>::connection<connection_type>::read_response() {
|
|
boost::asio::async_read_until(
|
|
m_connection.get_socket(),
|
|
m_connection.buffer(),
|
|
"\r\n\r\n",
|
|
m_connection.get_strand().wrap(boost::bind(
|
|
&type::handle_read_response,
|
|
m_connection.shared_from_this(),
|
|
boost::asio::placeholders::error,
|
|
boost::asio::placeholders::bytes_transferred
|
|
))
|
|
);
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void client<endpoint>::connection<connection_type>::handle_read_response (
|
|
const boost::system::error_code& error, std::size_t bytes_transferred)
|
|
{
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_connection.m_lock);
|
|
|
|
// detached check?
|
|
|
|
if (error) {
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "Error reading HTTP request. code: " << error << log::endl;
|
|
m_connection.terminate(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
std::istream request(&m_connection.buffer());
|
|
|
|
if (!m_response.parse_complete(request)) {
|
|
// not a valid HTTP response
|
|
// TODO: this should be a client error
|
|
throw http::exception("Could not parse server response.",
|
|
http::status_code::BAD_REQUEST);
|
|
}
|
|
|
|
m_endpoint.m_alog->at(log::alevel::DEBUG_HANDSHAKE) << m_response.raw()
|
|
<< log::endl;
|
|
|
|
// error checking
|
|
if (m_response.get_status_code() != http::status_code::SWITCHING_PROTOCOLS) {
|
|
throw http::exception("Server failed to upgrade connection.",
|
|
m_response.get_status_code(),
|
|
m_response.get_status_msg());
|
|
}
|
|
|
|
std::string h = m_response.header("Upgrade");
|
|
if (!boost::ifind_first(h,"websocket")) {
|
|
throw http::exception("Token `websocket` missing from Upgrade header.",
|
|
m_response.get_status_code(),
|
|
m_response.get_status_msg());
|
|
}
|
|
|
|
h = m_response.header("Connection");
|
|
if (!boost::ifind_first(h,"upgrade")) {
|
|
throw http::exception("Token `upgrade` missing from Connection header.",
|
|
m_response.get_status_code(),
|
|
m_response.get_status_msg());
|
|
}
|
|
|
|
h = m_response.header("Sec-WebSocket-Accept");
|
|
if (h == "") {
|
|
throw http::exception("Required Sec-WebSocket-Accept header is missing.",
|
|
m_response.get_status_code(),
|
|
m_response.get_status_msg());
|
|
} else {
|
|
std::string server_key = m_handshake_key;
|
|
server_key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
|
|
SHA1 sha;
|
|
uint32_t message_digest[5];
|
|
|
|
sha.Reset();
|
|
sha << server_key.c_str();
|
|
|
|
if (!sha.Result(message_digest)) {
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "Error computing handshake sha1 hash." << log::endl;
|
|
// TODO: close behavior
|
|
return;
|
|
}
|
|
|
|
// convert sha1 hash bytes to network byte order because this sha1
|
|
// library works on ints rather than bytes
|
|
for (int i = 0; i < 5; i++) {
|
|
message_digest[i] = htonl(message_digest[i]);
|
|
}
|
|
|
|
server_key = base64_encode(
|
|
reinterpret_cast<const unsigned char*>(message_digest),20);
|
|
if (server_key != h) {
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "Server returned incorrect handshake key." << log::endl;
|
|
// TODO: close behavior
|
|
return;
|
|
}
|
|
}
|
|
|
|
log_open_result();
|
|
|
|
m_connection.m_state = session::state::OPEN;
|
|
|
|
m_connection.get_handler()->on_open(m_connection.shared_from_this());
|
|
|
|
get_io_service().post(
|
|
m_connection.m_strand.wrap(boost::bind(
|
|
&connection_type::handle_read_frame,
|
|
m_connection.shared_from_this(),
|
|
boost::system::error_code()
|
|
))
|
|
);
|
|
|
|
//m_connection.handle_read_frame(boost::system::error_code());
|
|
} catch (const http::exception& e) {
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "Error processing server handshake. Server HTTP response: "
|
|
<< e.m_error_msg << " (" << e.m_error_code
|
|
<< ") Local error: " << e.what() << log::endl;
|
|
return;
|
|
}
|
|
|
|
|
|
// start session loop
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void client<endpoint>::connection<connection_type>::log_open_result() {
|
|
std::stringstream version;
|
|
version << "v" << m_version << " ";
|
|
|
|
m_endpoint.m_alog->at(log::alevel::CONNECT)
|
|
<< (m_version == -1 ? "HTTP" : "WebSocket") << " Connection "
|
|
<< m_connection.get_raw_socket().remote_endpoint() << " "
|
|
<< (m_version == -1 ? "" : version.str())
|
|
<< (get_request_header("Server") == "" ? "NULL" : get_request_header("Server"))
|
|
<< " " << m_uri->get_resource() << " " << m_response.get_status_code()
|
|
<< log::endl;
|
|
}
|
|
|
|
} // namespace role
|
|
} // namespace websocketpp
|
|
|
|
#ifdef _MSC_VER
|
|
# pragma warning(pop)
|
|
#endif
|
|
|
|
#endif // WEBSOCKETPP_ROLE_CLIENT_HPP
|