mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
962 lines
34 KiB
C++
962 lines
34 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_SERVER_HPP
|
|
#define WEBSOCKETPP_ROLE_SERVER_HPP
|
|
|
|
#include "../processors/hybi.hpp"
|
|
#include "../processors/hybi_legacy.hpp"
|
|
#include "../rng/blank_rng.hpp"
|
|
|
|
#include "../shared_const_buffer.hpp"
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/asio.hpp>
|
|
#include <boost/bind.hpp>
|
|
#include <boost/shared_ptr.hpp>
|
|
#include <boost/scoped_ptr.hpp>
|
|
#include <boost/thread.hpp>
|
|
#include <boost/thread/recursive_mutex.hpp>
|
|
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
|
|
#ifdef _MSC_VER
|
|
// Disable "warning C4355: 'this' : used in base member initializer list".
|
|
# pragma warning(push)
|
|
# pragma warning(disable:4355)
|
|
#endif
|
|
|
|
namespace websocketpp {
|
|
|
|
typedef boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type> bufIterator;
|
|
|
|
static std::pair<bufIterator, bool> match_header(boost::shared_ptr<std::string> string,
|
|
bufIterator nBegin, bufIterator nEnd)
|
|
{
|
|
if (nBegin == nEnd)
|
|
return std::make_pair(nEnd, false);
|
|
|
|
(*string) += std::string(nBegin, nEnd);
|
|
std::string::const_iterator begin = string->begin();
|
|
std::string::const_iterator end = string->end();
|
|
|
|
static const std::string eol_match = "\n";
|
|
static const std::string header_match = "\r\n\r\n";
|
|
static const std::string flash_match = "<policy-file-request/>";
|
|
|
|
// Do we have a complete HTTP request
|
|
std::string::const_iterator it = std::search(begin, end, header_match.begin(), header_match.end());
|
|
if (it != end)
|
|
{
|
|
int leftOver = (end - (it + header_match.size()));
|
|
return std::make_pair(nEnd - leftOver, true);
|
|
}
|
|
|
|
// If we don't have a flash policy request, we're done
|
|
it = std::search(begin, end, flash_match.begin(), flash_match.end());
|
|
if (it == end) // No match
|
|
return std::make_pair(nEnd, false);
|
|
|
|
// If we have a line ending before the flash policy request, treat as http
|
|
std::string::const_iterator it2 = std::search(begin, end, eol_match.begin(), eol_match.end());
|
|
if ((it2 != end) && (it2 < it))
|
|
return std::make_pair(nEnd, false);
|
|
|
|
// Treat as flash policy request
|
|
int leftOver = (end - (it + flash_match.size()));
|
|
return std::make_pair(nEnd - leftOver, true);
|
|
}
|
|
|
|
// Forward declarations
|
|
template <typename T> struct endpoint_traits;
|
|
|
|
namespace role {
|
|
|
|
template <class endpoint>
|
|
class server {
|
|
public:
|
|
// Connection specific details
|
|
template <typename connection_type>
|
|
class connection : boost::noncopyable {
|
|
public:
|
|
typedef connection<connection_type> type;
|
|
typedef endpoint endpoint_type;
|
|
|
|
// Valid always
|
|
int get_version() const {
|
|
return m_version;
|
|
}
|
|
std::string get_request_header(const std::string& key) const {
|
|
return m_request.header(key);
|
|
}
|
|
std::string get_origin() const {
|
|
return m_origin;
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
// Valid for CONNECTING state
|
|
void add_response_header(const std::string& key, const std::string& value) {
|
|
m_response.add_header(key,value);
|
|
}
|
|
void replace_response_header(const std::string& key, const std::string& value) {
|
|
m_response.replace_header(key,value);
|
|
}
|
|
void remove_response_header(const std::string& key) {
|
|
m_response.remove_header(key);
|
|
}
|
|
|
|
const std::vector<std::string>& get_subprotocols() const {
|
|
return m_requested_subprotocols;
|
|
}
|
|
const std::vector<std::string>& get_extensions() const {
|
|
return m_requested_extensions;
|
|
}
|
|
void select_subprotocol(const std::string& value);
|
|
void select_extension(const std::string& value);
|
|
|
|
// Valid if get_version() returns -1 (ie this is an http connection)
|
|
void set_body(const std::string& value);
|
|
|
|
int32_t rand() {
|
|
return 0;
|
|
}
|
|
|
|
bool is_server() const {
|
|
return true;
|
|
}
|
|
|
|
// 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)),
|
|
m_version(-1),
|
|
m_uri() {}
|
|
|
|
// initializes the websocket connection
|
|
void async_init();
|
|
void handle_read_request(boost::shared_ptr<std::string>, const boost::system::error_code& error,
|
|
std::size_t bytes_transferred);
|
|
void handle_short_key3(const boost::system::error_code& error,
|
|
std::size_t bytes_transferred);
|
|
void write_response();
|
|
void handle_write_response(const boost::system::error_code& error);
|
|
|
|
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;
|
|
|
|
http::parser::request m_request;
|
|
http::parser::response m_response;
|
|
blank_rng m_rng;
|
|
};
|
|
|
|
// types
|
|
typedef server<endpoint> type;
|
|
typedef endpoint endpoint_type;
|
|
|
|
typedef typename endpoint_traits<endpoint>::connection_ptr connection_ptr;
|
|
typedef typename endpoint_traits<endpoint>::handler_ptr handler_ptr;
|
|
|
|
// handler interface callback base class
|
|
class handler_interface {
|
|
public:
|
|
virtual ~handler_interface() {}
|
|
|
|
virtual void on_handshake_init(connection_ptr con) {}
|
|
|
|
virtual void validate(connection_ptr con) {}
|
|
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) {}
|
|
|
|
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) {}
|
|
virtual bool http(connection_ptr con) { return true; }
|
|
|
|
virtual void on_send_empty(connection_ptr con) {}
|
|
};
|
|
|
|
server(boost::asio::io_service& m)
|
|
: m_endpoint(static_cast< endpoint_type& >(*this)),
|
|
m_io_service(m),
|
|
// the only way to set an endpoint address family appears to be using
|
|
// this constructor, which also requires a port. This port number can be
|
|
// ignored, as it is always overwriten later by the listen() member func
|
|
m_acceptor(m),
|
|
m_state(IDLE),
|
|
m_timer(m,boost::posix_time::seconds(0)) {}
|
|
|
|
void start_listen(uint16_t port, size_t num_threads = 1);
|
|
void start_listen(const boost::asio::ip::tcp::endpoint& e, size_t num_threads = 1);
|
|
// uses internal resolver
|
|
void start_listen(const std::string &host, const std::string &service, size_t num_threads = 1);
|
|
|
|
template <typename InternetProtocol>
|
|
void start_listen(const InternetProtocol &internet_protocol, uint16_t port, size_t num_threads = 1) {
|
|
m_endpoint.m_alog->at(log::alevel::DEVEL)
|
|
<< "role::server listening on port " << port << log::endl;
|
|
boost::asio::ip::tcp::endpoint e(internet_protocol, port);
|
|
start_listen(e,num_threads);
|
|
}
|
|
|
|
void stop_listen(bool join);
|
|
|
|
// legacy interface
|
|
void listen(uint16_t port, size_t num_threads = 1) {
|
|
start_listen(port,num_threads>1 ? num_threads : 0);
|
|
if(num_threads > 1) stop_listen(true);
|
|
}
|
|
void listen(const boost::asio::ip::tcp::endpoint& e, size_t num_threads = 1) {
|
|
start_listen(e,num_threads>1 ? num_threads : 0);
|
|
if(num_threads > 1) stop_listen(true);
|
|
}
|
|
void listen(const std::string &host, const std::string &service, size_t num_threads = 1) {
|
|
start_listen(host,service,num_threads>1 ? num_threads : 0);
|
|
if(num_threads > 1) stop_listen(true);
|
|
}
|
|
template <typename InternetProtocol>
|
|
void listen(const InternetProtocol &internet_protocol, uint16_t port, size_t num_threads = 1) {
|
|
start_listen(internet_protocol,port,num_threads>1 ? num_threads : 0);
|
|
if(num_threads > 1) stop_listen(true);
|
|
}
|
|
|
|
protected:
|
|
bool is_server() {
|
|
return true;
|
|
}
|
|
private:
|
|
enum state {
|
|
IDLE = 0,
|
|
LISTENING = 1,
|
|
STOPPING = 2,
|
|
STOPPED = 3
|
|
};
|
|
|
|
// start_accept creates a new connection and begins an async_accept on it
|
|
void start_accept();
|
|
|
|
// handle_accept will begin the connection's read/write loop and then reset
|
|
// the server to accept a new connection. Errors returned by async_accept
|
|
// are logged and ingored.
|
|
void handle_accept(connection_ptr con,
|
|
const boost::system::error_code& error);
|
|
|
|
endpoint_type& m_endpoint;
|
|
boost::asio::io_service& m_io_service;
|
|
boost::asio::ip::tcp::acceptor m_acceptor;
|
|
state m_state;
|
|
|
|
boost::asio::deadline_timer m_timer;
|
|
|
|
std::vector< boost::shared_ptr<boost::thread> > m_listening_threads;
|
|
};
|
|
|
|
template <class endpoint>
|
|
void server<endpoint>::start_listen(const boost::asio::ip::tcp::endpoint& e,size_t num_threads) {
|
|
{
|
|
boost::unique_lock<boost::recursive_mutex> lock(m_endpoint.m_lock);
|
|
|
|
if (m_state != IDLE) {
|
|
throw exception("listen called from invalid state.");
|
|
}
|
|
|
|
m_acceptor.open(e.protocol());
|
|
m_acceptor.set_option(boost::asio::socket_base::reuse_address(true));
|
|
m_acceptor.bind(e);
|
|
m_acceptor.listen();
|
|
|
|
this->start_accept();
|
|
}
|
|
|
|
if (num_threads > MAX_THREAD_POOL_SIZE) {
|
|
throw exception("listen called with invalid num_threads value");
|
|
}
|
|
|
|
m_state = LISTENING;
|
|
|
|
for (std::size_t i = 0; i < num_threads; ++i) {
|
|
boost::shared_ptr<boost::thread> thread(
|
|
new boost::thread(boost::bind(
|
|
&endpoint_type::run_internal,
|
|
&m_endpoint
|
|
))
|
|
);
|
|
m_listening_threads.push_back(thread);
|
|
}
|
|
|
|
if(num_threads == 0)
|
|
{
|
|
for (;;) {
|
|
try {
|
|
m_endpoint.run_internal();
|
|
break;
|
|
} catch (const std::exception & e) { // Don't let an exception trash the endpoint DJS
|
|
m_endpoint.elog().at(log::elevel::RERROR) << "run_internal exception: " << e.what() << log::endl;
|
|
}
|
|
}
|
|
m_state = IDLE;
|
|
}
|
|
}
|
|
|
|
template <class endpoint>
|
|
void server<endpoint>::stop_listen(bool join) {
|
|
{
|
|
boost::unique_lock<boost::recursive_mutex> lock(m_endpoint.m_lock);
|
|
|
|
if (m_state != LISTENING) {
|
|
throw exception("stop_listen called from invalid state");
|
|
}
|
|
|
|
m_acceptor.close();
|
|
}
|
|
|
|
if(join) {
|
|
for (std::size_t i = 0; i < m_listening_threads.size(); ++i) {
|
|
m_listening_threads[i]->join();
|
|
}
|
|
}
|
|
|
|
m_listening_threads.clear();
|
|
|
|
m_state = IDLE;
|
|
}
|
|
|
|
template <class endpoint>
|
|
void server<endpoint>::start_listen(uint16_t port, size_t num_threads) {
|
|
start_listen(boost::asio::ip::tcp::v6(), port, num_threads);
|
|
}
|
|
|
|
template <class endpoint>
|
|
void server<endpoint>::start_listen(const std::string &host, const std::string &service, size_t num_threads) {
|
|
boost::asio::ip::tcp::resolver resolver(m_io_service);
|
|
boost::asio::ip::tcp::resolver::query query(host, service);
|
|
boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
|
|
boost::asio::ip::tcp::resolver::iterator end;
|
|
if (endpoint_iterator == end) {
|
|
throw std::invalid_argument("Can't resolve host/service to listen");
|
|
}
|
|
const boost::asio::ip::tcp::endpoint &ep = *endpoint_iterator;
|
|
start_listen(ep,num_threads);
|
|
}
|
|
|
|
template <class endpoint>
|
|
void server<endpoint>::start_accept() {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_endpoint.m_lock);
|
|
|
|
connection_ptr con = m_endpoint.create_connection();
|
|
|
|
if (con == connection_ptr()) {
|
|
// the endpoint is no longer capable of accepting new connections.
|
|
m_endpoint.m_alog->at(log::alevel::CONNECT)
|
|
<< "Connection refused because endpoint is out of resources or closing."
|
|
<< log::endl;
|
|
return;
|
|
}
|
|
|
|
m_acceptor.async_accept(
|
|
con->get_raw_socket(),
|
|
boost::bind(
|
|
&type::handle_accept,
|
|
this,
|
|
con,
|
|
boost::asio::placeholders::error
|
|
)
|
|
);
|
|
}
|
|
|
|
// handle_accept will begin the connection's read/write loop and then reset
|
|
// the server to accept a new connection. Errors returned by async_accept
|
|
// are logged and ingored.
|
|
template <class endpoint>
|
|
void server<endpoint>::handle_accept(connection_ptr con,
|
|
const boost::system::error_code& error)
|
|
{
|
|
bool delay = false;
|
|
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_endpoint.m_lock);
|
|
|
|
try
|
|
{
|
|
if (error) {
|
|
con->m_fail_code = fail::status::SYSTEM;
|
|
con->m_fail_system = error;
|
|
|
|
if (error == boost::system::errc::too_many_files_open) {
|
|
con->m_fail_reason = "too many files open";
|
|
delay = true;
|
|
|
|
} else if (error == boost::asio::error::operation_aborted) {
|
|
con->m_fail_reason = "io_service operation canceled";
|
|
|
|
// the operation was canceled. This was probably due to the
|
|
// io_service being stopped.
|
|
} else {
|
|
con->m_fail_reason = "unknown";
|
|
}
|
|
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "async_accept returned error: " << error
|
|
<< " (" << con->m_fail_reason << ")" << log::endl;
|
|
|
|
con->terminate(false);
|
|
} else {
|
|
con->start();
|
|
}
|
|
}
|
|
catch (const std::exception & e)
|
|
{ // We must call start_accept, even if we throw DJS
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "handle_accept caught exception: " << e.what() << log::endl;
|
|
}
|
|
|
|
if (delay)
|
|
{ // Don't spin if too many files are open DJS
|
|
m_timer.expires_from_now(boost::posix_time::milliseconds(500));
|
|
m_timer.async_wait(boost::bind(&type::start_accept,this));
|
|
}
|
|
else
|
|
this->start_accept();
|
|
}
|
|
|
|
// server<endpoint>::connection<connnection_type> Implimentation
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void server<endpoint>::connection<connection_type>::select_subprotocol(
|
|
const std::string& value)
|
|
{
|
|
// TODO: should this be locked?
|
|
|
|
std::vector<std::string>::iterator it;
|
|
|
|
it = std::find(m_requested_subprotocols.begin(),
|
|
m_requested_subprotocols.end(),
|
|
value);
|
|
|
|
if (value != "" && it == m_requested_subprotocols.end()) {
|
|
throw std::invalid_argument("Attempted to choose a subprotocol not proposed by the client");
|
|
}
|
|
|
|
m_subprotocol = value;
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void server<endpoint>::connection<connection_type>::select_extension(
|
|
const std::string& value)
|
|
{
|
|
// TODO: should this be locked?
|
|
|
|
if (value == "") {
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string>::iterator it;
|
|
|
|
it = std::find(m_requested_extensions.begin(),
|
|
m_requested_extensions.end(),
|
|
value);
|
|
|
|
if (it == m_requested_extensions.end()) {
|
|
throw std::invalid_argument("Attempted to choose an extension not proposed by the client");
|
|
}
|
|
|
|
m_extensions.push_back(value);
|
|
}
|
|
|
|
// Valid if get_version() returns -1 (ie this is an http connection)
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void server<endpoint>::connection<connection_type>::set_body(
|
|
const std::string& value)
|
|
{
|
|
// TODO: should this be locked?
|
|
|
|
if (m_connection.m_version != -1) {
|
|
// TODO: throw exception
|
|
throw std::invalid_argument("set_body called from invalid state");
|
|
}
|
|
|
|
m_response.set_body(value);
|
|
}
|
|
|
|
/// initiates an async read for an HTTP header
|
|
/**
|
|
* Thread Safety: locks connection
|
|
*/
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void server<endpoint>::connection<connection_type>::async_init() {
|
|
boost::lock_guard<boost::recursive_mutex> lock(m_connection.m_lock);
|
|
|
|
m_connection.get_handler()->on_handshake_init(m_connection.shared_from_this());
|
|
|
|
// TODO: make this value configurable
|
|
m_connection.register_timeout(5000,fail::status::TIMEOUT_WS,
|
|
"Timeout on WebSocket handshake");
|
|
|
|
static boost::arg<1> pl1;
|
|
static boost::arg<2> pl2;
|
|
|
|
boost::shared_ptr<std::string> stringPtr = boost::make_shared<std::string>();
|
|
m_connection.get_socket().async_read_until(
|
|
m_connection.buffer(),
|
|
boost::bind(&match_header, stringPtr, pl1, pl2),
|
|
m_connection.get_strand().wrap(boost::bind(
|
|
&type::handle_read_request,
|
|
m_connection.shared_from_this(),
|
|
stringPtr,
|
|
boost::asio::placeholders::error,
|
|
boost::asio::placeholders::bytes_transferred
|
|
))
|
|
);
|
|
}
|
|
|
|
/// processes the response from an async read for an HTTP header
|
|
/**
|
|
* Thread Safety: async asio calls are not thread safe
|
|
*/
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void server<endpoint>::connection<connection_type>::handle_read_request(
|
|
boost::shared_ptr<std::string> header,
|
|
const boost::system::error_code& error, std::size_t /*bytes_transferred*/)
|
|
{
|
|
if (error) {
|
|
// log error
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "Error reading HTTP request. code: " << error << log::endl;
|
|
m_connection.terminate(false);
|
|
return;
|
|
}
|
|
|
|
m_connection.buffer().consume(header->size());
|
|
|
|
try {
|
|
std::istringstream request(*header);
|
|
|
|
if (!m_request.parse_complete(request)) {
|
|
// not a valid HTTP request/response
|
|
if (m_request.method().find("<policy-file-request/>") != std::string::npos)
|
|
{ // set m_version to -1, call async_write->handle_write_response
|
|
std::string reply =
|
|
"<?xml version=\"1.0\"?><cross-domain-policy>"
|
|
"<allow-access-from domain=\"*\" to-ports=\"";
|
|
reply += boost::lexical_cast<std::string>(m_connection.get_raw_socket().local_endpoint().port());
|
|
reply += "\"/></cross-domain-policy>";
|
|
reply.append("\0", 1);
|
|
|
|
m_version = -1;
|
|
shared_const_buffer buffer(reply);
|
|
m_connection.get_socket().async_write(
|
|
shared_const_buffer(reply),
|
|
boost::bind(
|
|
&type::handle_write_response,
|
|
m_connection.shared_from_this(),
|
|
boost::asio::placeholders::error
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
throw http::exception("Received invalid HTTP Request",http::status_code::BAD_REQUEST);
|
|
}
|
|
|
|
// TODO: is there a way to short circuit this or something?
|
|
// This is often useful but slow to generate.
|
|
//m_endpoint.m_alog.at(log::alevel::DEBUG_HANDSHAKE) << m_request.raw() << log::endl;
|
|
|
|
std::string h = m_request.header("Upgrade");
|
|
if (boost::ifind_first(h,"websocket")) {
|
|
// Version is stored in the Sec-WebSocket-Version header for all
|
|
// versions after draft Hybi 00/Hixie 76. The absense of a version
|
|
// header is assumed to mean Hybi 00.
|
|
h = m_request.header("Sec-WebSocket-Version");
|
|
if (h == "") {
|
|
m_version = 0;
|
|
} else {
|
|
m_version = atoi(h.c_str());
|
|
if (m_version == 0) {
|
|
throw(http::exception("Unable to determine connection version",http::status_code::BAD_REQUEST));
|
|
}
|
|
}
|
|
|
|
// Choose an appropriate websocket processor based on the version
|
|
if (m_version == 0) {
|
|
m_connection.m_processor = processor::ptr(
|
|
new processor::hybi_legacy<connection_type>(m_connection)
|
|
);
|
|
|
|
// Hybi legacy requires some extra out of band bookkeeping that
|
|
// future versions wont. We need to pull off an additional eight
|
|
// bytes after the /r/n/r/n and store them somewhere that the
|
|
// processor can find them.
|
|
char foo[8];
|
|
|
|
request.read(foo,8);
|
|
|
|
if (request.gcount() != 8) {
|
|
size_t left = 8 - static_cast<size_t>(request.gcount());
|
|
// This likely occurs because the full key3 wasn't included
|
|
// in the asio read. It is likely that the extra bytes are
|
|
// actually on the wire and another asio read would get them
|
|
// Fixing this will require a way of restarting the
|
|
// handshake read and storing the existing bytes until that
|
|
// comes back. Issue #101
|
|
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "Short Key 3: " << zsutil::to_hex(std::string(foo))
|
|
<< " bytes missing: " << left
|
|
<< " eofbit: " << (request.eof() ? "true" : "false")
|
|
<< " failbit: " << (request.fail() ? "true" : "false")
|
|
<< " badbit: " << (request.bad() ? "true" : "false")
|
|
<< " goodbit: " << (request.good() ? "true" : "false")
|
|
<< log::endl;
|
|
|
|
throw http::exception("Full Key3 not found in first chop",
|
|
http::status_code::INTERNAL_SERVER_ERROR);
|
|
/*m_request.add_header("Sec-WebSocket-Key3",std::string(foo));
|
|
|
|
|
|
|
|
boost::asio::async_read(
|
|
m_connection.get_socket(),
|
|
m_connection.buffer(),
|
|
boost::asio::transfer_at_least(left),
|
|
m_connection.get_strand().wrap(boost::bind(
|
|
&type::handle_short_key3,
|
|
m_connection.shared_from_this(),
|
|
boost::asio::placeholders::error,
|
|
boost::asio::placeholders::bytes_transferred
|
|
))
|
|
);
|
|
return;*/
|
|
}
|
|
m_request.add_header("Sec-WebSocket-Key3",std::string(foo));
|
|
} else if (m_version == 7 || m_version == 8 || m_version == 13) {
|
|
m_connection.m_processor = processor::ptr(
|
|
new processor::hybi<connection_type>(m_connection)
|
|
);
|
|
} else {
|
|
// version does not match any processor we have avaliable. Send
|
|
// an HTTP error and return the versions we do support in a the
|
|
// appropriate response header.
|
|
m_response.add_header("Sec-WebSocket-Version","13, 8, 7");
|
|
|
|
throw(http::exception("Unsupported WebSocket version",http::status_code::BAD_REQUEST));
|
|
}
|
|
|
|
m_connection.m_processor->validate_handshake(m_request);
|
|
|
|
// Extract subprotocols
|
|
std::string subprotocols = m_request.header("Sec-WebSocket-Protocol");
|
|
if(subprotocols.length() > 0) {
|
|
boost::char_separator<char> sep(",");
|
|
boost::tokenizer< boost::char_separator<char> > tokens(subprotocols, sep);
|
|
for(boost::tokenizer< boost::char_separator<char> >::iterator it = tokens.begin(); it != tokens.end(); ++it){
|
|
std::string proto = *it;
|
|
boost::trim(proto);
|
|
if (proto.length() > 0){
|
|
m_requested_subprotocols.push_back(proto);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_origin = m_connection.m_processor->get_origin(m_request);
|
|
m_uri = m_connection.m_processor->get_uri(m_request);
|
|
|
|
m_endpoint.get_handler()->validate(m_connection.shared_from_this());
|
|
|
|
m_response.set_status(http::status_code::SWITCHING_PROTOCOLS);
|
|
} else {
|
|
// should there be a more encapsulated http processor here?
|
|
m_origin = m_request.header("Origin");
|
|
|
|
// Set URI
|
|
std::string h = m_request.header("Host");
|
|
|
|
size_t last_colon = h.rfind(":");
|
|
size_t last_sbrace = h.rfind("]");
|
|
|
|
if (last_colon == std::string::npos ||
|
|
(last_sbrace != std::string::npos && last_sbrace > last_colon))
|
|
{
|
|
// TODO: this makes the assumption that WS and HTTP
|
|
// default ports are the same.
|
|
m_uri.reset(new uri(m_connection.is_secure(),h,m_request.uri()));
|
|
} else {
|
|
m_uri.reset(new uri(m_connection.is_secure(),
|
|
h.substr(0,last_colon),
|
|
h.substr(last_colon+1),
|
|
m_request.uri()));
|
|
}
|
|
|
|
// continue as HTTP?
|
|
if (m_endpoint.get_handler()->http(m_connection.shared_from_this()))
|
|
m_response.set_status(http::status_code::OK);
|
|
else
|
|
m_response.set_status(http::status_code::INTERNAL_SERVER_ERROR);
|
|
}
|
|
} catch (const http::exception& e) {
|
|
m_endpoint.m_elog->at(log::elevel::RERROR) << e.what() << log::endl;
|
|
m_response.set_status(e.m_error_code,e.m_error_msg);
|
|
m_response.set_body(e.m_body);
|
|
} catch (const uri_exception& e) {
|
|
// there was some error building the uri
|
|
m_endpoint.m_elog->at(log::elevel::RERROR) << e.what() << log::endl;
|
|
m_response.set_status(http::status_code::BAD_REQUEST);
|
|
}
|
|
|
|
write_response();
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void server<endpoint>::connection<connection_type>::handle_short_key3 (
|
|
const boost::system::error_code& error, std::size_t /*bytes_transferred*/)
|
|
{
|
|
if (error) {
|
|
// log 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());
|
|
|
|
std::string foo = m_request.header("Sec-WebSocket-Key3");
|
|
|
|
size_t left = 8-foo.size();
|
|
|
|
if (left == 0) {
|
|
// ?????
|
|
throw http::exception("handle_short_key3 called without short key",
|
|
http::status_code::INTERNAL_SERVER_ERROR);
|
|
}
|
|
|
|
char foo2[9];
|
|
|
|
request.get(foo2,left);
|
|
|
|
if (request.gcount() != left) {
|
|
// ?????
|
|
throw http::exception("Full Key3 not found",
|
|
http::status_code::INTERNAL_SERVER_ERROR);
|
|
}
|
|
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "Recovered from Short Key 3: " << left << log::endl;
|
|
|
|
foo.append(std::string(foo2));
|
|
|
|
m_request.replace_header("Sec-WebSocket-Key3",foo2);
|
|
|
|
m_connection.m_processor->validate_handshake(m_request);
|
|
m_origin = m_connection.m_processor->get_origin(m_request);
|
|
m_uri = m_connection.m_processor->get_uri(m_request);
|
|
|
|
m_endpoint.get_handler()->validate(m_connection.shared_from_this());
|
|
|
|
m_response.set_status(http::status_code::SWITCHING_PROTOCOLS);
|
|
} catch (const http::exception& e) {
|
|
m_endpoint.m_elog->at(log::elevel::RERROR) << e.what() << log::endl;
|
|
m_response.set_status(e.m_error_code,e.m_error_msg);
|
|
m_response.set_body(e.m_body);
|
|
} catch (const uri_exception& e) {
|
|
// there was some error building the uri
|
|
m_endpoint.m_elog->at(log::elevel::RERROR) << e.what() << log::endl;
|
|
m_response.set_status(http::status_code::BAD_REQUEST);
|
|
}
|
|
|
|
write_response();
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void server<endpoint>::connection<connection_type>::write_response() {
|
|
bool ws_response = true;
|
|
|
|
m_response.set_version("HTTP/1.1");
|
|
|
|
if (m_response.get_status_code() == http::status_code::SWITCHING_PROTOCOLS) {
|
|
// websocket response
|
|
m_connection.m_processor->handshake_response(m_request,m_response);
|
|
|
|
if (m_subprotocol != "") {
|
|
m_response.replace_header("Sec-WebSocket-Protocol",m_subprotocol);
|
|
}
|
|
|
|
// TODO: return negotiated extensions
|
|
} else {
|
|
// TODO: HTTP response
|
|
ws_response = false;
|
|
}
|
|
|
|
m_response.replace_header("Server",USER_AGENT);
|
|
|
|
std::string raw = m_response.raw();
|
|
|
|
// Hack for legacy HyBi
|
|
if (ws_response && m_version == 0) {
|
|
raw += boost::dynamic_pointer_cast<processor::hybi_legacy<connection_type> >(m_connection.m_processor)->get_key3();
|
|
}
|
|
|
|
shared_const_buffer buffer(raw);
|
|
|
|
m_endpoint.m_alog->at(log::alevel::DEBUG_HANDSHAKE) << raw << log::endl;
|
|
|
|
m_connection.get_socket().async_write(
|
|
//boost::asio::buffer(raw),
|
|
buffer,
|
|
boost::bind(
|
|
&type::handle_write_response,
|
|
m_connection.shared_from_this(),
|
|
boost::asio::placeholders::error
|
|
)
|
|
);
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void server<endpoint>::connection<connection_type>::handle_write_response(
|
|
const boost::system::error_code& error)
|
|
{
|
|
if (error) {
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "Network error writing handshake respons. code: " << error
|
|
<< log::endl;
|
|
|
|
m_connection.terminate(false);
|
|
return;
|
|
}
|
|
|
|
if (m_response.get_status_code() != http::status_code::SWITCHING_PROTOCOLS) {
|
|
if (m_version == -1) {
|
|
// if this was not a websocket connection, we have written
|
|
// the expected response and the connection can be closed.
|
|
} else {
|
|
// this was a websocket connection that ended in an error
|
|
m_endpoint.m_elog->at(log::elevel::RERROR)
|
|
<< "Handshake ended with HTTP error: "
|
|
<< m_response.get_status_code() << " "
|
|
<< m_response.get_status_msg() << log::endl;
|
|
}
|
|
m_connection.terminate(true);
|
|
return;
|
|
}
|
|
|
|
m_connection.cancel_timeout();
|
|
|
|
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());
|
|
}
|
|
|
|
template <class endpoint>
|
|
template <class connection_type>
|
|
void server<endpoint>::connection<connection_type>::log_open_result() {
|
|
std::stringstream version;
|
|
version << "v" << m_version << " ";
|
|
|
|
std::string remote;
|
|
boost::system::error_code ec;
|
|
boost::asio::ip::tcp::endpoint ep = m_connection.get_raw_socket().remote_endpoint(ec);
|
|
if (ec) {
|
|
m_endpoint.m_elog->at(log::elevel::WARN)
|
|
<< "Error getting remote endpoint. code: " << ec << log::endl;
|
|
}
|
|
|
|
m_endpoint.m_alog->at(log::alevel::CONNECT)
|
|
<< (m_version == -1 ? "HTTP" : "WebSocket") << " Connection ";
|
|
|
|
if (ec) {
|
|
m_endpoint.m_alog->at(log::alevel::CONNECT) << "Unknown";
|
|
} else {
|
|
m_endpoint.m_alog->at(log::alevel::CONNECT) << ep;
|
|
}
|
|
|
|
m_endpoint.m_alog->at(log::alevel::CONNECT) << " "
|
|
<< (m_version == -1 ? "" : version.str())
|
|
<< (get_request_header("User-Agent") == "" ? "NULL" : get_request_header("User-Agent"))
|
|
<< " " << (m_uri ? m_uri->get_resource() : "uri is NULL") << " "
|
|
<< m_response.get_status_code() << log::endl;
|
|
}
|
|
|
|
} // namespace role
|
|
} // namespace websocketpp
|
|
|
|
#ifdef _MSC_VER
|
|
# pragma warning(pop)
|
|
#endif
|
|
|
|
#endif // WEBSOCKETPP_ROLE_SERVER_HPP
|