lots of misc fixes, mostly broadcast server related

This commit is contained in:
Peter Thorson
2011-12-21 08:23:03 -06:00
parent 3405a91e56
commit 4d03909d58
18 changed files with 898 additions and 621 deletions

View File

@@ -12,7 +12,8 @@
<script type="text/javascript">
var options = {"console_enabled": true};
var ws;
var ws_client;
var ws_admin;
var url;
var data2 = [], total_points = 240;
@@ -26,34 +27,58 @@ var msgs = {};
function connect() {
url = document.getElementById("server_url").value;
console.log(url);
if ("WebSocket" in window) {
ws = new WebSocket(url);
ws_client = new WebSocket(url);
ws_admin = new WebSocket(url+"/admin");
} else if ("MozWebSocket" in window) {
ws = new MozWebSocket(url);
ws_client = new MozWebSocket(url);
ws_admin = new MozWebSocket(url+"/admin");
} else {
document.getElementById("messages").innerHTML += "This Browser does not support WebSockets<br />";
$("#messages").innerHTML += "This Browser does not support WebSockets<br />";
return;
}
ws.onopen = function(e) {
document.getElementById("messages").innerHTML += "Client: A connection to "+ws.URL+" has been opened.<br />";
ws_client.onopen = function(e) {
$("#messages").append("Client: A client connection to "+url+" has been opened.<br />");
document.getElementById("server_url").disabled = true;
document.getElementById("toggle_connect").innerHTML = "Disconnect";
$("#server_url").disabled = true;
$("#toggle_connect").html("Disconnect");
};
ws_admin.onopen = function(e) {
$("#messages").append("Client: An admin connection to "+url+"/admin has been opened.<br />");
$("#server_url").disabled = true;
$("#toggle_connect").html("Disconnect");
};
ws.onerror = function(e) {
document.getElementById("messages").innerHTML += "Client: An error occured, see console log for more details.<br />";
ws_client.onerror = function(e) {
$("#messages").append("Client: An error occured on the client channel, see console log for more details.<br />");
console.log(e);
};
ws_admin.onerror = function(e) {
$("#messages").append("Client: An error occured on the admin channel, see console log for more details.<br />");
console.log(e);
};
ws.onclose = function(e) {
document.getElementById("messages").innerHTML += "Client: The connection to "+url+" was closed.<br />";
ws_client.onclose = function(e) {
$("#messages").append("Client: The client connection to "+url+" was closed.<br />");
clear_hud();
};
ws_admin.onclose = function(e) {
$("#messages").append("Client: The admin connection to "+url+"/admin was closed.<br />");
clear_hud();
};
ws.onmessage = function(e) {
ws_client.onmessage = function(e) {
if (options.console_enabled) {
$("#messages").append("Broadcasted Message: "+e.data+"<br />");
}
}
ws_admin.onmessage = function(e) {
foo = JSON.parse(e.data);
if (foo.type == "message") {
@@ -143,7 +168,8 @@ function clear_hud() {
}
function disconnect() {
ws.close();
ws_client.close();
ws_admin.close();
}
function toggle_connect() {
@@ -154,20 +180,20 @@ function toggle_connect() {
}
}
function send() {
if (ws === undefined || ws.readyState != 1) {
document.getElementById("messages").innerHTML += "Client: Websocket is not avaliable for writing<br />";
function broadcast() {
if (ws_client === undefined || ws_client.readyState != 1) {
$("#messages").append("Client: Client websocket is not avaliable for writing<br />");
return;
}
ws.send(document.getElementById("msg").value);
ws_client.send(document.getElementById("msg").value);
document.getElementById("msg").value = "";
}
function send_command(command,args) {
var cmd = command+":";
ws.send(cmd);
ws_admin.send(cmd);
}
function send_test_message(size,type) {
@@ -277,12 +303,12 @@ body,html {
<div id="controls">
<div id="server">
<input type="text" name="server_url" id="server_url" value="ws://thor-websocket.zaphoyd.net:9002/admin" />
<input type="text" name="server_url" id="server_url" value="ws://thor-websocket.zaphoyd.net:9002" />
<button id="toggle_connect" onclick="toggle_connect();">Connect</button>
</div>
<div id="message_input"><input type="text" name="msg" id="msg" value="Hello World!" />
<button onclick="send();">Broadcast</button></div>
<button onclick="broadcast();">Broadcast</button></div>
<h2>Stats</h2>
<h3>Server</h3>

View File

@@ -0,0 +1,190 @@
/*
* 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_BROADCAST_ADMIN_HANDLER_HPP
#define WEBSOCKETPP_BROADCAST_ADMIN_HANDLER_HPP
#include "../../src/endpoint.hpp"
#include "../../src/roles/server.hpp"
#include "../../src/sockets/ssl.hpp"
#include "broadcast_handler.hpp"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <map>
#include <set>
#include <sstream>
namespace websocketpp {
namespace broadcast {
template <typename endpoint_type>
class admin_handler : public endpoint_type::handler {
public:
typedef admin_handler<endpoint_type> type;
typedef boost::shared_ptr<type> ptr;
typedef typename endpoint_type::handler_ptr handler_ptr;
typedef typename handler<endpoint_type>::ptr broadcast_handler_ptr;
typedef typename endpoint_type::connection_ptr connection_ptr;
admin_handler()
: m_epoch(boost::posix_time::time_from_string("1970-01-01 00:00:00.000"))
{}
void on_open(connection_ptr connection) {
if (!m_timer) {
m_timer.reset(new boost::asio::deadline_timer(connection->get_io_service(),boost::posix_time::seconds(0)));
m_timer->expires_from_now(boost::posix_time::milliseconds(250));
m_timer->async_wait(boost::bind(&type::on_timer,this,boost::asio::placeholders::error));
}
m_connections.insert(connection);
}
// this dummy tls init function will cause all TLS connections to fail.
// TLS handling for broadcast::handler is usually done by a lobby handler.
// If you want to use the broadcast handler alone with TLS then return the
// appropriately filled in context here.
boost::shared_ptr<boost::asio::ssl::context> on_tls_init() {
return boost::shared_ptr<boost::asio::ssl::context>();
}
void on_load(connection_ptr connection, handler_ptr old_handler) {
this->on_open(connection);
m_lobby = old_handler;
}
void track(broadcast_handler_ptr target) {
m_broadcast_handler = target;
}
void on_close(connection_ptr connection) {
m_connections.erase(connection);
}
void on_message(connection_ptr connection,websocketpp::message::data_ptr msg) {
typename std::set<connection_ptr>::iterator it;
wscmd::cmd command = wscmd::parse(msg->get_payload());
if (command.command == "close") {
handle_close(connection,command);
} else {
command_error(connection,"Invalid Command");
}
connection->recycle(msg);
}
void command_error(connection_ptr connection,const std::string msg) {
connection->send("{\"type\":\"error\",\"value\":\""+msg+"\"}");
}
// close: - close this connection
// close:all; close all connections
void handle_close(connection_ptr connection,const wscmd::cmd& command) {
if (!m_broadcast_handler) {
// Unable to connect to local broadcast handler
return;
}
m_broadcast_handler->close_connection(connection_ptr());
}
long get_ms(boost::posix_time::ptime s) const {
boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
boost::posix_time::time_period period(s,now);
return period.length().total_milliseconds();
}
void on_timer(const boost::system::error_code& error) {
if (!m_broadcast_handler) {
// Unable to connect to local broadcast handler
return;
}
long milli_seconds = get_ms(m_epoch);
std::stringstream update;
update << "{\"type\":\"stats\""
<< ",\"timestamp\":" << milli_seconds
<< ",\"connections\":" << m_broadcast_handler->get_connection_count()
<< ",\"admin_connections\":" << m_connections.size()
<< ",\"messages\":[";
const msg_map& m = m_broadcast_handler->get_message_stats();
msg_map::const_iterator msg_it;
msg_map::const_iterator last = m.end();
if (m.size() > 0) {
last--;
}
for (msg_it = m.begin(); msg_it != m.end(); msg_it++) {
update << "{\"id\":" << (*msg_it).second.id
<< ",\"hash\":\"" << (*msg_it).second.hash << "\""
<< ",\"sent\":" << (*msg_it).second.sent
<< ",\"acked\":" << (*msg_it).second.acked
<< ",\"size\":" << (*msg_it).second.size
<< ",\"time\":" << (*msg_it).second.time
<< "}" << (msg_it == last ? "" : ",");
}
update << "]}";
m_broadcast_handler->clear_message_stats();
typename std::set<connection_ptr>::iterator it;
for (it = m_connections.begin(); it != m_connections.end(); it++) {
(*it)->send(update.str(),false);
}
m_timer->expires_from_now(boost::posix_time::milliseconds(250));
m_timer->async_wait(
boost::bind(
&type::on_timer,
this,
boost::asio::placeholders::error
)
);
}
private:
handler_ptr m_lobby;
broadcast_handler_ptr m_broadcast_handler;
std::set<connection_ptr> m_connections;
boost::posix_time::ptime m_epoch;
boost::shared_ptr<boost::asio::deadline_timer> m_timer;
};
} // namespace broadcast
} // namespace websocketpp
#endif // WEBSOCKETPP_BROADCAST_ADMIN_HANDLER_HPP

View File

@@ -0,0 +1,201 @@
/*
* 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_BROADCAST_HANDLER_HPP
#define WEBSOCKETPP_BROADCAST_HANDLER_HPP
#include "wscmd.hpp"
#include "../../src/endpoint.hpp"
#include "../../src/roles/server.hpp"
#include "../../src/sockets/ssl.hpp"
#include "../../src/md5/md5.hpp"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <map>
#include <set>
namespace websocketpp {
namespace broadcast {
/// this structure is used to keep track of message statistics
struct msg {
int id;
size_t sent;
size_t acked;
size_t size;
uint64_t time;
std::string hash;
boost::posix_time::ptime time_sent;
};
typedef std::map<std::string,struct msg> msg_map;
template <typename endpoint_type>
class handler : public endpoint_type::handler {
public:
typedef handler<endpoint_type> type;
typedef boost::shared_ptr<type> ptr;
typedef typename endpoint_type::handler_ptr handler_ptr;
typedef typename endpoint_type::connection_ptr connection_ptr;
handler() : m_nextid(0) {}
void on_open(connection_ptr connection) {
m_connections.insert(connection);
}
// this dummy tls init function will cause all TLS connections to fail.
// TLS handling for broadcast::handler is usually done by a lobby handler.
// If you want to use the broadcast handler alone with TLS then return the
// appropriately filled in context here.
boost::shared_ptr<boost::asio::ssl::context> on_tls_init() {
return boost::shared_ptr<boost::asio::ssl::context>();
}
void on_load(connection_ptr connection, handler_ptr old_handler) {
this->on_open(connection);
m_lobby = old_handler;
}
void on_close(connection_ptr connection) {
m_connections.erase(connection);
}
void on_message(connection_ptr connection,message::data_ptr msg) {
typename std::set<connection_ptr>::iterator it;
wscmd::cmd command = wscmd::parse(msg->get_payload());
if (command.command == "ack") {
handle_ack(connection,command);
} else {
broadcast_message(msg);
}
connection->recycle(msg);
}
void command_error(connection_ptr connection,const std::string msg) {
connection->send("{\"type\":\"error\",\"value\":\""+msg+"\"}");
}
// ack:e3458d0aceff8b70a3e5c0afec632881=38;e3458d0aceff8b70a3e5c0afec632881=42;
void handle_ack(connection_ptr connection,const wscmd::cmd& command) {
wscmd::arg_list::const_iterator arg_it;
size_t count;
for (arg_it = command.args.begin(); arg_it != command.args.end(); arg_it++) {
if (m_msgs.find(arg_it->first) == m_msgs.end()) {
std::cout << "ack for message we didn't send" << std::endl;
continue;
}
count = atol(arg_it->second.c_str());
if (count == 0) {
continue;
}
struct msg& m(m_msgs[arg_it->first]);
m.acked += count;
if (m.acked == m.sent) {
m.time = get_ms(m.time_sent);
}
}
}
// close: - close this connection
// close:all; close all connections
void close_connection(connection_ptr connection) {
if (connection){
connection->close(close::status::NORMAL);
} else {
typename std::set<connection_ptr>::iterator it;
for (it = m_connections.begin(); it != m_connections.end(); it++) {
(*it)->close(close::status::NORMAL);
}
}
}
void broadcast_message(message::data_ptr msg) {
std::string hash = md5_hash_hex(msg->get_payload());
struct msg& new_msg(m_msgs[hash]);
new_msg.id = m_nextid++;
new_msg.hash = hash;
new_msg.size = msg->get_payload().size();
new_msg.time_sent = boost::posix_time::microsec_clock::local_time();
new_msg.time = 0;
typename std::set<connection_ptr>::iterator it;
// broadcast to clients
for (it = m_connections.begin(); it != m_connections.end(); it++) {
(*it)->send(msg->get_payload(),(msg->get_opcode() == frame::opcode::BINARY));
}
new_msg.sent = m_connections.size();
new_msg.acked = 0;
}
long get_ms(boost::posix_time::ptime s) const {
boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
boost::posix_time::time_period period(s,now);
return period.length().total_milliseconds();
}
// hooks for admin console
size_t get_connection_count() const {
return m_connections.size();
}
const msg_map& get_message_stats() const {
return m_msgs;
}
void clear_message_stats() {
m_msgs.empty();
}
private:
handler_ptr m_lobby;
int m_nextid;
msg_map m_msgs;
std::set<connection_ptr> m_connections;
};
} // namespace broadcast
} // namespace websocketpp
#endif // WEBSOCKETPP_BROADCAST_HANDLER_HPP

View File

@@ -0,0 +1,142 @@
/*
* 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_BROADCAST_SERVER_HANDLER_HPP
#define WEBSOCKETPP_BROADCAST_SERVER_HANDLER_HPP
#include "../../src/endpoint.hpp"
#include "../../src/roles/server.hpp"
#include "../../src/sockets/ssl.hpp"
#include "broadcast_handler.hpp"
#include "broadcast_admin_handler.hpp"
#include <boost/date_time/posix_time/posix_time.hpp>
namespace websocketpp {
namespace broadcast {
template <typename endpoint_type>
class server_handler : public endpoint_type::handler {
public:
typedef server_handler<endpoint_type> type;
typedef boost::shared_ptr<type> ptr;
typedef typename endpoint_type::handler_ptr handler_ptr;
typedef typename admin_handler<endpoint_type>::ptr admin_handler_ptr;
typedef typename handler<endpoint_type>::ptr broadcast_handler_ptr;
typedef typename endpoint_type::connection_ptr connection_ptr;
server_handler();
std::string get_password() const {
return "test";
}
boost::shared_ptr<boost::asio::ssl::context> on_tls_init() {
// create a tls context, init, and return.
boost::shared_ptr<boost::asio::ssl::context> context(new boost::asio::ssl::context(boost::asio::ssl::context::tlsv1));
try {
context->set_options(boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::single_dh_use);
context->set_password_callback(boost::bind(&type::get_password, this));
context->use_certificate_chain_file("../../src/ssl/server.pem");
context->use_private_key_file("../../src/ssl/server.pem", boost::asio::ssl::context::pem);
context->use_tmp_dh_file("../../src/ssl/dh512.pem");
} catch (std::exception& e) {
std::cout << e.what() << std::endl;
}
return context;
}
void validate(connection_ptr connection) {}
void on_open(connection_ptr connection) {
if (connection->get_resource() == "/admin") {
connection->set_handler(m_admin_handler);
} else {
connection->set_handler(m_broadcast_handler);
}
}
void on_unload(connection_ptr connection, handler_ptr new_handler) {
}
void on_close(connection_ptr connection) {}
void on_message(connection_ptr connection,websocketpp::message::data_ptr msg) {}
void http(connection_ptr connection);
void on_fail(connection_ptr connection) {
std::cout << "connection failed" << std::endl;
}
// utility
handler_ptr get_broadcast_handler() {
return m_broadcast_handler;
}
private:
admin_handler_ptr m_admin_handler;
broadcast_handler_ptr m_broadcast_handler;
};
} // namespace broadcast
} // namespace websocketpp
namespace websocketpp {
namespace broadcast {
template <class endpoint>
server_handler<endpoint>::server_handler()
: m_admin_handler(new admin_handler<endpoint>()),
m_broadcast_handler(new handler<endpoint>())
{
m_admin_handler->track(m_broadcast_handler);
}
template <class endpoint>
void server_handler<endpoint>::http(connection_ptr connection) {
std::stringstream foo;
foo << "<html><body><p>"
<< m_broadcast_handler->get_connection_count()
<< " current connections.</p></body></html>";
connection->set_body(foo.str());
}
} // namespace broadcast
} // namespace websocketpp
#endif // WEBSOCKETPP_BROADCAST_SERVER_HANDLER_HPP

View File

@@ -28,7 +28,8 @@
#include "../../src/endpoint.hpp"
#include "../../src/roles/server.hpp"
#include "../../src/sockets/ssl.hpp"
#include "../../src/md5/md5.hpp"
#include "broadcast_server_handler.hpp"
#include <boost/date_time/posix_time/posix_time.hpp>
@@ -37,460 +38,12 @@
#include <sys/resource.h>
struct msg {
int id;
size_t sent;
size_t acked;
size_t size;
uint64_t time;
std::string hash;
boost::posix_time::ptime time_sent;
};
typedef websocketpp::endpoint<websocketpp::role::server,websocketpp::socket::plain> plain_endpoint_type;
typedef plain_endpoint_type::handler_ptr plain_handler_ptr;
typedef websocketpp::endpoint<websocketpp::role::server,websocketpp::socket::ssl> tls_endpoint_type;
typedef tls_endpoint_type::handler_ptr tls_handler_ptr;
template <typename endpoint_type>
class broadcast_server_handler : public endpoint_type::handler {
public:
typedef broadcast_server_handler<endpoint_type> type;
typedef typename endpoint_type::connection_ptr connection_ptr;
broadcast_server_handler()
: m_epoch(boost::posix_time::time_from_string("1970-01-01 00:00:00.000")),
m_nextid(0)
{
m_messages = 0;
m_data = 0;
}
std::string get_password() const {
return "test";
}
boost::shared_ptr<boost::asio::ssl::context> on_tls_init() {
// create a tls context, init, and return.
boost::shared_ptr<boost::asio::ssl::context> context(new boost::asio::ssl::context(boost::asio::ssl::context::tlsv1));
try {
context->set_options(boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::single_dh_use);
context->set_password_callback(boost::bind(&type::get_password, this));
context->use_certificate_chain_file("../../src/ssl/server.pem");
context->use_private_key_file("../../src/ssl/server.pem", boost::asio::ssl::context::pem);
context->use_tmp_dh_file("../../src/ssl/dh512.pem");
} catch (std::exception& e) {
std::cout << e.what() << std::endl;
}
return context;
}
void validate(connection_ptr connection) {
//std::cout << "state: " << connection->get_state() << std::endl;
}
void on_open(connection_ptr connection) {
if (!m_timer) {
m_timer.reset(new boost::asio::deadline_timer(connection->get_io_service(),boost::posix_time::seconds(0)));
m_timer->expires_from_now(boost::posix_time::milliseconds(250));
m_timer->async_wait(boost::bind(&type::on_timer,this,boost::asio::placeholders::error));
m_last_time = boost::posix_time::microsec_clock::local_time();
}
if (connection->get_resource() == "/admin") {
m_admin_connections.insert(connection);
} else {
m_connections.insert(connection);
}
/*typename std::set<connection_ptr>::iterator it;
std::stringstream foo;
foo << "{\"type\":\"con\""
<< ",\"timestamp\":" << get_ms(m_epoch)
<< ",\"value\":" << m_connections.size()
<< "}";
for (it = m_admin_connections.begin(); it != m_admin_connections.end(); it++) {
(*it)->send(foo.str(),false);
}*/
}
void on_close(connection_ptr connection) {
//std::cout << "connection closed" << std::endl;
m_connections.erase(connection);
m_admin_connections.erase(connection);
/*typename std::set<connection_ptr>::iterator it;
std::stringstream foo;
foo << "{\"type\":\"con\""
<< ",\"timestamp\":" << get_ms(m_epoch)
<< ",\"value\":" << m_connections.size()
<< "}";
for (it = m_admin_connections.begin(); it != m_admin_connections.end(); it++) {
(*it)->send(foo.str(),false);
}*/
}
void on_message(connection_ptr connection,websocketpp::message::data_ptr msg) {
typename std::set<connection_ptr>::iterator it;
// command structure
// command:arg1=val1;arg2=val2;arg3=val3;
// commands
// ack: messages to ack
// example: `ack:e3458d0aceff8b70a3e5c0afec632881=38;e3458d0aceff8b70a3e5c0afec632881=42;`
// send: [vals]
// message; opcode=X; payload="X"
// frame; [fuzzer stuff]
// close:code=1000;reason=msg;
// (instructs the opposite end to close with given optional code/msg)
const std::string &m(msg->get_payload());
std::string::size_type start;
std::string::size_type end;
start = m.find(":",0);
if (start != std::string::npos) {
std::string command = m.substr(0,start);
// parse args
std::map<std::string,std::string> args;
start++; // skip the colon
end = m.find(";",start);
// find all semicolons
while (end != std::string::npos) {
std::string arg;
std::string val;
std::string::size_type sep = m.find("=",start);
if (sep != std::string::npos) {
arg = m.substr(start,sep-start);
val = m.substr(sep+1,end-sep-1);
} else {
arg = m.substr(start,end-start);
val = "";
}
args[arg] = val;
start = end+1;
end = m.find(";",start);
}
if (command == "close") {
handle_close(connection,args);
} else if (command == "send") {
handle_send(connection,args);
} else {
command_error(connection,"Unrecognized command: "+command);
}
} else {
command_error(connection,"Invalid command syntax");
}
if (msg->get_payload().substr(0,27) == "{\"type\":\"acks\",\"messages\":[") {
//std::cout << "got ack" << std::endl;
//std::cout << msg->get_payload() << std::endl;
// process a
//
//{"type":"acks","messages":[{"e3458d0aceff8b70a3e5c0afec632881":38},{"e3458d0aceff8b70a3e5c0afec632881":38}]}
std::string::size_type start = 27;
std::string::size_type end = msg->get_payload().find(",",start);
size_t count;
m_messages_acked = 0;
while (end != std::string::npos) {
if (end-start < 38) {
// error, not the input we were expecting
continue;
} else {
count = atol(msg->get_payload().substr(start+36,end-2).c_str());
if (count == 0) {
// error parsing number
continue;
}
}
std::string hash = msg->get_payload().substr(start+2,32);
std::map<std::string,struct msg>::iterator it = m_msgs.find(hash);
if (it == m_msgs.end()) {
std::cout << "ack for message we didn't send" << std::endl;
continue;
}
struct msg& m(m_msgs[hash]);
m.acked += count;
if (m.acked == m.sent) {
m.time = get_ms(m.time_sent);
}
start = end+1;
end = msg->get_payload().find(",",start);
}
end = msg->get_payload().size();
// get the last value
if (end-start < 38) {
// error, not the input we were expecting
return;
} else {
count = atol(msg->get_payload().substr(start+36,end-4).c_str());
if (count == 0) {
// error parsing number
return;
}
}
std::string hash = msg->get_payload().substr(start+2,32);
std::map<std::string,struct msg>::iterator it = m_msgs.find(hash);
if (it == m_msgs.end()) {
std::cout << "ack for message we didn't send: " << hash
<< "(" << hash.size() << ")" << std::endl;
return;
}
struct msg& m(m_msgs[msg->get_payload().substr(start+2,32)]);
m.acked += count;
if (m.acked == m.sent) {
m.time = get_ms(m.time_sent);
}
//m_ack_stats[msg->get_payload().substr(start+2,32)] = count;
// m_messages_acked += count;
} else {
std::string hash = websocketpp::md5_hash_hex(msg->get_payload());
struct msg& new_msg(m_msgs[hash]);
new_msg.id = m_nextid++;
new_msg.hash = hash;
new_msg.size = msg->get_payload().size();
new_msg.time_sent = boost::posix_time::microsec_clock::local_time();
new_msg.time = 0;
// broadcast to clients
for (it = m_connections.begin(); it != m_connections.end(); it++) {
//std::cout << "sending message: (" << hash.size() << ") " << hash << std::endl;
m_messages++;
m_data += msg->get_payload().size();
(*it)->send(msg->get_payload(),(msg->get_opcode() == websocketpp::frame::opcode::BINARY));
}
new_msg.sent = m_connections.size();
new_msg.acked = 0;
// broadcast to admins
std::stringstream foo;
foo << "{\"type\":\"message\",\"value\":\"";
if (msg->get_opcode() == websocketpp::frame::opcode::BINARY) {
foo << "[Binary Message, length: " << msg->get_payload().size() << "]";
} else {
if (msg->get_payload().size() > 126) {
foo << "[UTF8 Message, length: " << msg->get_payload().size() << "]";
} else {
foo << msg->get_payload();
}
}
foo << "\"}";
for (it = m_admin_connections.begin(); it != m_admin_connections.end(); it++) {
(*it)->send(foo.str(),false);
}
}
connection->recycle(msg);
}
void command_error(connection_ptr connection,const std::string msg) {
connection->send("{\"type\":\"error\",\"value\":\""+msg+"\"}");
}
// in order to keep parsing this command language as simple as possible
// the following values must be escaped (with \) if they are to appear
// literally in string arguments: :,;=\
// close: [reason; code=1000; msg=X], [all]
// (instructs the opposite end to close with given optional code/msg)
void handle_close(connection_ptr connection,
const std::map<std::string,std::string> args)
{
if (args.size() == 0) {
typename std::set<connection_ptr>::iterator it;
for (it = m_connections.begin(); it != m_connections.end(); it++) {
(*it)->close(websocketpp::close::status::NORMAL);
}
} else {
command_error(connection,"close arguments not supported");
}
}
void handle_send(connection_ptr connection,
const std::map<std::string,std::string> args)
{
}
void http(connection_ptr connection) {
std::stringstream foo;
foo << "<html><body><p>" << m_connections.size() << " current connections.</p></body></html>";
connection->set_body(foo.str());
}
long get_ms(boost::posix_time::ptime s) {
boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
boost::posix_time::time_period period(s,now);
return period.length().total_milliseconds();
}
void on_timer(const boost::system::error_code& error) {
// there is new data. This is the first time that there is no new data
//if (m_messages != m_messages_cache || m_data != m_data_cache) {
//boost::posix_time::time_period period(m_last_time,now);
//m_last_time = now;
/*{
type: stats
connections: int
admin_connections: int
messages: [
{
id: int
hash: string
sent: int
acked: int
}
]
}*/
long milli_seconds = get_ms(m_epoch);
//double seconds = milli_seconds/1000.0;
//m_messages_cache = m_messages;
//m_data_cache = m_data;
//m_messages_sent += m_messages;
//m_data_sent += m_data;
//std::cout << "m: " << m_messages
// << " milli: " << milli_seconds
// << std::endl;
/*
<< ",\"messages\":" << m_messages
<< ",\"bytes\":" << m_data
<< ",\"messages_sent\":" << m_messages_sent
<< ",\"messages_acked\":" << m_messages_acked
<< ",\"bytes_sent\":" << m_data_sent
*/
std::stringstream foo;
foo << "{\"type\":\"stats\""
<< ",\"timestamp\":" << milli_seconds
<< ",\"connections\":" << m_connections.size()
<< ",\"admin_connections\":" << m_admin_connections.size()
<< ",\"messages\":[";
std::map<std::string,struct msg>::iterator msg_it;
std::map<std::string,struct msg>::iterator last = m_msgs.end();
if (m_msgs.size() > 0) {
last--;
}
for (msg_it = m_msgs.begin(); msg_it != m_msgs.end(); msg_it++) {
foo << "{\"id\":" << (*msg_it).second.id
<< ",\"hash\":\"" << (*msg_it).second.hash << "\""
<< ",\"sent\":" << (*msg_it).second.sent
<< ",\"acked\":" << (*msg_it).second.acked
<< ",\"size\":" << (*msg_it).second.size
<< ",\"time\":" << (*msg_it).second.time
<< "}" << (msg_it == last ? "" : ",");
}
foo << "]}";
//m_msgs.clear();
//<< ((m_messages_cache * seconds)*1000) << ",\"data\":"
//<< ((m_data_cache * seconds)*1000) << ",\"messages_sent\":"
//<< m_messages_sent <<",\"data_sent\":" << m_data_sent << "}";
typename std::set<connection_ptr>::iterator it;
for (it = m_admin_connections.begin(); it != m_admin_connections.end(); it++) {
(*it)->send(foo.str(),false);
}
//m_messages = 0;
//m_data = 0;
//}
m_timer->expires_from_now(boost::posix_time::milliseconds(250));
m_timer->async_wait(boost::bind(&type::on_timer,this,boost::asio::placeholders::error));
}
void on_fail(connection_ptr connection) {
std::cout << "connection failed" << std::endl;
}
private:
unsigned int m_messages;
size_t m_data;
unsigned int m_messages_cache;
size_t m_data_cache;
unsigned int m_messages_sent;
size_t m_data_sent;
boost::shared_ptr<boost::asio::deadline_timer> m_timer;
boost::posix_time::ptime m_epoch;
boost::posix_time::ptime m_last_time;
size_t m_messages_acked;
std::map<std::string,size_t> m_ack_stats;
int m_nextid;
std::map<std::string,struct msg> m_msgs;
std::set<connection_ptr> m_connections;
std::set<connection_ptr> m_admin_connections;
};
int main(int argc, char* argv[]) {
unsigned short port = 9002;
bool tls = false;
@@ -532,7 +85,7 @@ int main(int argc, char* argv[]) {
try {
if (tls) {
tls_handler_ptr h(new broadcast_server_handler<tls_endpoint_type>());
tls_handler_ptr h(new websocketpp::broadcast::server_handler<tls_endpoint_type>());
tls_endpoint_type e(h);
e.alog().unset_level(websocketpp::log::alevel::ALL);
@@ -541,7 +94,7 @@ int main(int argc, char* argv[]) {
std::cout << "Starting Secure WebSocket broadcast server on port " << port << std::endl;
e.listen(port);
} else {
plain_handler_ptr h(new broadcast_server_handler<plain_endpoint_type>());
plain_handler_ptr h(new websocketpp::broadcast::server_handler<plain_endpoint_type>());
plain_endpoint_type e(h);
e.alog().set_level(websocketpp::log::alevel::ALL);

View File

@@ -0,0 +1,99 @@
/*
* 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 WSCMD_HPP
#define WSCMD_HPP
#include <map>
#include <string>
namespace wscmd {
// Parses a wscmd string.
// command structure
// command:arg1=val1;arg2=val2;arg3=val3;
// commands
// ack: messages to ack
// example: `ack:e3458d0aceff8b70a3e5c0afec632881=38;e3458d0aceff8b70a3e5c0afec632881=42;`
// send: [vals]
// message; opcode=X; payload="X"
// frame; [fuzzer stuff]
// close:code=1000;reason=msg;
// (instructs the opposite end to close with given optional code/msg)
typedef std::map<std::string,std::string> arg_list;
struct cmd {
// TODO: move semantics
std::string command;
arg_list args;
};
wscmd::cmd parse(const std::string& m);
wscmd::cmd parse(const std::string& m) {
cmd command;
std::string::size_type start;
std::string::size_type end;
start = m.find(":",0);
if (start != std::string::npos) {
command.command = m.substr(0,start);
start++; // skip the colon
end = m.find(";",start);
// find all semicolons
while (end != std::string::npos) {
std::string arg;
std::string val;
std::string::size_type sep = m.find("=",start);
if (sep != std::string::npos) {
arg = m.substr(start,sep-start);
val = m.substr(sep+1,end-sep-1);
} else {
arg = m.substr(start,end-start);
val = "";
}
command.args[arg] = val;
start = end+1;
end = m.find(";",start);
}
}
return command;
}
} // namespace wscmd
#endif // WSCMD_HPP