refectors session into client_session/server_session/core, adds client handler and chat_client example. Client functionality is experimental and non-compliant at the moment.

This commit is contained in:
Peter Thorson
2011-09-26 09:49:52 -05:00
parent 7d938df15e
commit da1795feac
29 changed files with 2100 additions and 452 deletions

View File

@@ -0,0 +1,23 @@
CFLAGS = -O2
LDFLAGS =
CXX ?= c++
SHARED ?= "1"
ifeq ($(SHARED), 1)
LDFLAGS := $(LDFLAGS) -lboost_system -lboost_thread -lboost_date_time -lwebsocketpp
else
LDFLAGS := $(LDFLAGS) -lboost_system -lboost_thread -lboost_date_time ../../libwebsocketpp.a
endif
chat_client: chat_client.o chat_client_handler.o
$(CXX) $(CFLAGS) $^ -o $@ $(LDFLAGS)
%.o: %.cpp
$(CXX) -c $(CFLAGS) -o $@ $^
# cleanup by removing generated files
#
.PHONY: clean
clean:
rm -f *.o chat_client

BIN
examples/chat_client/chat_client Executable file

Binary file not shown.

View File

@@ -0,0 +1,78 @@
/*
* 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.
*
*/
#include "chat_client_handler.hpp"
#include <websocketpp/websocketpp.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <iostream>
using boost::asio::ip::tcp;
using namespace websocketchat;
int main(int argc, char* argv[]) {
std::string url;
if (argc != 2) {
std::cout << "Usage: `chat_client ws_url`" << std::endl;
} else {
url = argv[1];
}
chat_client_handler_ptr c(new chat_client_handler());
try {
boost::asio::io_service io_service;
websocketpp::client_ptr client(new websocketpp::client(io_service,c));
client->init();
client->set_header("User Agent","WebSocket++/2011-09-25");
client->add_subprotocol("com.zaphoyd.websocketpp.chat");
client->set_origin("http://zaphoyd.com");
client->connect(url);
boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));
char line[512];
while (std::cin.getline(line, 512)) {
c->send(line);
}
t.join();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}

View File

@@ -0,0 +1,177 @@
<!doctype html>
<html>
<head>
<script type="text/javascript" src="jquery-1.6.3.min.js"></script>
</head>
<body>
<script type="text/javascript">
var ws;
var url;
$(document).ready(init);
function init() {
$(document).keypress(function(event) {
if ( event.which == 13 ) {
event.preventDefault();
send();
}
});
}
function connect() {
url = $("#server_url").val();
console.log(url);
if ("WebSocket" in window) {
ws = new WebSocket(url);
} else if ("MozWebSocket" in window) {
ws = new MozWebSocket(url);
} else {
chat_message("This Browser does not support WebSockets");
return;
}
ws.onopen = function(e) {
chat_message("A connection to "+url+" has been opened.");
$("#server_url").attr("disabled",true);
$("#toggle_connect").html("Disconnect");
};
ws.onerror = function(e) {
chat_message("An error occured, see console log for more details.");
console.log(e);
};
ws.onclose = function(e) {
chat_message("The connection to "+url+" was closed.");
};
ws.onmessage = function(e) {
var message = JSON.parse(e.data);
if (message.type == "msg") {
chat_message(message.value,message.sender);
} else if (message.type == "participants") {
var o = "<ul>";
for (var p in message.value) {
o += "<li>"+message.value[p]+"</li>";
}
o += "</ul>";
$("#participants").html(o);
}
};
}
function chat_message(message,sender) {
if (arguments.length == 1) {
sender = "";
}
var style;
if (sender == "") {
style = "client";
} else if (sender == "server") {
style = "server";
sender = "["+sender+"]";
} else {
style = "message";
sender = "["+sender+"]";
}
$("#messages").append("<span class='"+style+"'><span class='sender'>"+sender+"</span> <span class='msg'>"+message+"</span></span><br />");
$("#messages").prop({ scrollTop: $("#messages").prop("scrollHeight") });
}
function disconnect() {
ws.close();
$("#server_url").removeAttr("disabled");
$("#toggle_connect").html("Connect");
$("#participants").html("");
}
function toggle_connect() {
if ($("#server_url").attr("disabled") != "disabled") {
connect();
} else {
disconnect();
}
}
function send() {
if (ws === undefined || ws.readyState != 1) {
chat_message("Websocket is not avaliable for writing");
return;
}
ws.send($("#msg").val());
$("#msg").val("");
}
</script>
<style>
body,html {
margin: 0px;
padding: 0px;
height: 100%;
background-color: #999;
font-family: sans-serif;
font-size: 14px;
}
h3 {
}
#controls {
padding: 4px;
float:right;
width: 300px;
}
input {
width: 200px;
}
#messages {
height: 100%;
overflow: auto;
background-color: black;
}
#messages .client {
color: #ccc;
}
#messages .server {
color: yellow;
}
#messages .message {
color: white;
}
</style>
<div id="controls">
<div id="server">
<input type="text" name="server_url" id="server_url" value="ws://thor-websocket.zaphoyd.net:9000/chat" />
<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();">Send</button></div>
<h3>Chat Participants</h3>
<div id="participants"></div>
</div>
<div id="messages"></div>
</body>
</html>

View File

@@ -0,0 +1,107 @@
/*
* 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.
*
*/
#include "chat_client_handler.hpp"
#include <boost/algorithm/string/replace.hpp>
using websocketchat::chat_client_handler;
using websocketpp::client_session_ptr;
void chat_client_handler::on_open(session_ptr s) {
// not sure if anything needs to happen here.
m_session = s;
std::cout << "Successfully connected" << std::endl;
}
void chat_client_handler::on_close(session_ptr s,uint16_t status,const std::string &reason) {
// not sure if anything needs to happen here either.
m_session = client_session_ptr();
std::cout << "client was disconnected" << std::endl;
}
void chat_client_handler::on_message(session_ptr s,const std::string &msg) {
std::cout << "message from server: " << msg << std::endl;
decode_server_msg(msg);
}
// CLIENT API
// client api methods will be called from outside the io_service.run thread
// they need to be careful to not touch unsyncronized member variables.
void chat_client_handler::send(const std::string &msg) {
if (!m_session) {
std::cerr << "Error: no connected session" << std::endl;
return;
}
m_session->io_service().post(boost::bind(&chat_client_handler::do_send, this, msg));
}
void chat_client_handler::close() {
if (!m_session) {
std::cerr << "Error: no connected session" << std::endl;
return;
}
m_session->io_service().post(boost::bind(&chat_client_handler::do_close,this));
}
// END CLIENT API
void chat_client_handler::do_send(const std::string &msg) {
if (!m_session) {
std::cerr << "Error: no connected session" << std::endl;
return;
}
// check for local commands
if (msg == "list") {
std::cout << "list all participants" << std::endl;
} else if (msg == "close") {
do_close();
} else {
m_session->send(msg);
}
}
void chat_client_handler::do_close() {
if (!m_session) {
std::cerr << "Error: no connected session" << std::endl;
return;
}
m_session->close(websocketpp::session::CLOSE_STATUS_GOING_AWAY,"");
}
// {"type":"participants","value":[<participant>,...]}
// {"type":"msg","sender":"<sender>","value":"<msg>" }
void chat_client_handler::decode_server_msg(const std::string &msg) {
// for messages of type participants, erase and rebuild m_participants
// for messages of type msg, print out message
}

View File

@@ -0,0 +1,94 @@
/*
* 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 CHAT_CLIENT_HANDLER_HPP
#define CHAT_CLIENT_HANDLER_HPP
// com.zaphoyd.websocketpp.chat protocol
//
// client messages:
// alias [UTF8 text, 16 characters max]
// msg [UTF8 text]
//
// server messages:
// {"type":"msg","sender":"<sender>","value":"<msg>" }
// {"type":"participants","value":[<participant>,...]}
#include <boost/shared_ptr.hpp>
#include "../../src/websocketpp.hpp"
#include "../../src/websocket_connection_handler.hpp"
#include <map>
#include <string>
#include <queue>
using websocketpp::session_ptr;
namespace websocketchat {
class chat_client_handler : public websocketpp::connection_handler {
public:
chat_client_handler() {}
virtual ~chat_client_handler() {}
// ignored for clients?
void validate(session_ptr s) {}
// connection to chat room complete
void on_open(session_ptr s);
// connection to chat room closed
void on_close(session_ptr s,uint16_t status,const std::string &reason);
// got a new message from server
void on_message(session_ptr s,const std::string &msg);
// ignore messages
void on_message(session_ptr s,const std::vector<unsigned char> &data) {}
// CLIENT API
void send(const std::string &msg);
void close();
private:
// Client API internal
void do_send(const std::string &msg);
void do_close();
void decode_server_msg(const std::string &msg);
// list of other chat participants
std::set<std::string> m_participants;
std::queue<std::string> m_msg_queue;
session_ptr m_session;
};
typedef boost::shared_ptr<chat_client_handler> chat_client_handler_ptr;
}
#endif // CHAT_CLIENT_HANDLER_HPP

File diff suppressed because one or more lines are too long

View File

@@ -5,9 +5,9 @@ CXX ?= c++
SHARED ?= "1"
ifeq ($(SHARED), 1)
LDFLAGS := $(LDFLAGS) -lboost_system -lwebsocketpp
LDFLAGS := $(LDFLAGS) -lboost_system -boost_date_time -lwebsocketpp
else
LDFLAGS := $(LDFLAGS) -lboost_system ../../libwebsocketpp.a
LDFLAGS := $(LDFLAGS) -lboost_system -lboost_date_time ../../libwebsocketpp.a
endif
chat_server: chat_server.o chat.o

View File

@@ -29,15 +29,15 @@
#include <boost/algorithm/string/replace.hpp>
using websocketchat::chat_handler;
using websocketchat::chat_server_handler;
using websocketpp::session_ptr;
void chat_handler::validate(session_ptr client) {
void chat_server_handler::validate(session_ptr client) {
std::stringstream err;
// We only know about the chat resource
if (client->get_request() != "/chat") {
err << "Request for unknown resource " << client->get_request();
if (client->get_resource() != "/chat") {
err << "Request for unknown resource " << client->get_resource();
throw(websocketpp::handshake_error(err.str(),404));
}
@@ -49,7 +49,7 @@ void chat_handler::validate(session_ptr client) {
}
void chat_handler::connect(session_ptr client) {
void chat_server_handler::on_open(session_ptr client) {
std::cout << "client " << client << " joined the lobby." << std::endl;
m_connections.insert(std::pair<session_ptr,std::string>(client,get_con_id(client)));
@@ -59,7 +59,7 @@ void chat_handler::connect(session_ptr client) {
send_to_all(encode_message("server",m_connections[client]+" has joined the chat."));
}
void chat_handler::disconnect(session_ptr client,uint16_t status,const std::string &reason) {
void chat_server_handler::on_close(session_ptr client,uint16_t status,const std::string &reason) {
std::map<session_ptr,std::string>::iterator it = m_connections.find(client);
if (it == m_connections.end()) {
@@ -80,7 +80,7 @@ void chat_handler::disconnect(session_ptr client,uint16_t status,const std::stri
send_to_all(encode_message("server",alias+" has left the chat."));
}
void chat_handler::message(session_ptr client,const std::string &msg) {
void chat_server_handler::on_message(session_ptr client,const std::string &msg) {
std::cout << "message from client " << client << ": " << msg << std::endl;
@@ -126,7 +126,7 @@ void chat_handler::message(session_ptr client,const std::string &msg) {
}
// {"type":"participants","value":[<participant>,...]}
std::string chat_handler::serialize_state() {
std::string chat_server_handler::serialize_state() {
std::stringstream s;
s << "{\"type\":\"participants\",\"value\":[";
@@ -147,7 +147,7 @@ std::string chat_handler::serialize_state() {
}
// {"type":"msg","sender":"<sender>","value":"<msg>" }
std::string chat_handler::encode_message(std::string sender,std::string msg,bool escape) {
std::string chat_server_handler::encode_message(std::string sender,std::string msg,bool escape) {
std::stringstream s;
// escape JSON characters
@@ -167,13 +167,13 @@ std::string chat_handler::encode_message(std::string sender,std::string msg,bool
return s.str();
}
std::string chat_handler::get_con_id(session_ptr s) {
std::string chat_server_handler::get_con_id(session_ptr s) {
std::stringstream endpoint;
endpoint << s->socket().remote_endpoint();
return endpoint.str();
}
void chat_handler::send_to_all(std::string data) {
void chat_server_handler::send_to_all(std::string data) {
std::map<session_ptr,std::string>::iterator it;
for (it = m_connections.begin(); it != m_connections.end(); it++) {
(*it).first->send(data);

View File

@@ -39,7 +39,7 @@
// {"type":"participants","value":[<participant>,...]}
#include <boost/shared_ptr.hpp>
#include "../../src/websocketpp.hpp"
#include "../../src/websocket_connection_handler.hpp"
#include <map>
@@ -48,23 +48,23 @@
namespace websocketchat {
class chat_handler : public websocketpp::connection_handler {
class chat_server_handler : public websocketpp::connection_handler {
public:
chat_handler() {}
virtual ~chat_handler() {}
chat_server_handler() {}
virtual ~chat_server_handler() {}
void validate(websocketpp::session_ptr client);
// add new connection to the lobby
void connect(websocketpp::session_ptr client);
void on_open(websocketpp::session_ptr client);
// someone disconnected from the lobby, remove them
void disconnect(websocketpp::session_ptr client,uint16_t status,const std::string &reason);
void on_close(websocketpp::session_ptr client,uint16_t status,const std::string &reason);
void message(websocketpp::session_ptr client,const std::string &msg);
void on_message(websocketpp::session_ptr client,const std::string &msg);
// lobby will ignore binary messages
void message(websocketpp::session_ptr client,
void on_message(websocketpp::session_ptr client,
const std::vector<unsigned char> &data) {}
private:
std::string serialize_state();
@@ -77,7 +77,7 @@ private:
std::map<websocketpp::session_ptr,std::string> m_connections;
};
typedef boost::shared_ptr<chat_handler> chat_handler_ptr;
typedef boost::shared_ptr<chat_server_handler> chat_server_handler_ptr;
}
#endif // CHAT_HPP

View File

@@ -27,29 +27,32 @@
#include "chat.hpp"
#include <websocketpp/websocket_server.hpp>
#include "../../src/websocketpp.hpp"
#include <boost/asio.hpp>
#include <iostream>
using boost::asio::ip::tcp;
using namespace websocketchat;
int main(int argc, char* argv[]) {
std::string host = "localhost:9000";
short port = 9000;
std::string host = "localhost";
short port = 9003;
std::string full_host;
if (argc == 3) {
// TODO: input validation?
host = argv[1];
port = atoi(argv[2]);
std::stringstream temp;
temp << argv[1] << ":" << port;
host = temp.str();
}
std::stringstream temp;
websocketchat::chat_handler_ptr chat_handler(new websocketchat::chat_handler());
temp << host << ":" << port;
full_host = temp.str();
chat_server_handler_ptr chat_handler(new chat_server_handler());
try {
boost::asio::io_service io_service;
@@ -61,7 +64,7 @@ int main(int argc, char* argv[]) {
// setup server settings
server->add_host(host);
server->add_host(full_host);
// Chat server should only be receiving small text messages, reduce max
// message size limit slightly to save memory, improve performance, and
// guard against DoS attacks.
@@ -70,7 +73,7 @@ int main(int argc, char* argv[]) {
// start the server
server->start_accept();
std::cout << "Starting chat server on " << host << std::endl;
std::cout << "Starting chat server on " << full_host << std::endl;
io_service.run();
} catch (std::exception& e) {

View File

@@ -27,15 +27,15 @@
#include "echo.hpp"
using websocketecho::echo_handler;
using websocketecho::echo_server_handler;
void echo_handler::validate(websocketpp::session_ptr client) {}
void echo_server_handler::validate(websocketpp::session_ptr client) {}
void echo_handler::message(websocketpp::session_ptr client, const std::string &msg) {
void echo_server_handler::on_message(websocketpp::session_ptr client, const std::string &msg) {
client->send(msg);
}
void echo_handler::message(websocketpp::session_ptr client,
void echo_server_handler::on_message(websocketpp::session_ptr client,
const std::vector<unsigned char> &data) {
client->send(data);
}

View File

@@ -25,38 +25,40 @@
*
*/
#ifndef ECHO_HANDLER_HPP
#define ECHO_HANDLER_HPP
#ifndef ECHO_SERVER_HANDLER_HPP
#define ECHO_SERVER_HANDLER_HPP
#include "../../src/websocketpp.hpp"
#include "../../src/websocket_connection_handler.hpp"
#include <boost/shared_ptr.hpp>
#include <string>
#include <vector>
using websocketpp::session_ptr;
namespace websocketecho {
class echo_handler : public websocketpp::connection_handler {
class echo_server_handler : public websocketpp::connection_handler {
public:
echo_handler() {}
virtual ~echo_handler() {}
echo_server_handler() {}
virtual ~echo_server_handler() {}
// The echo server allows all domains is protocol free.
void validate(websocketpp::session_ptr client);
void validate(session_ptr client);
// an echo server is stateless. The handler has no need to keep track of connected
// clients.
void connect(websocketpp::session_ptr client) {}
void disconnect(websocketpp::session_ptr client,uint16_t status,const std::string &reason) {}
// an echo server is stateless.
// The handler has no need to keep track of connected clients.
void on_open(session_ptr client) {}
void on_close(session_ptr client,uint16_t status,const std::string &reason) {}
// both text and binary messages are echoed back to the sending client.
void message(websocketpp::session_ptr client,const std::string &msg);
void message(websocketpp::session_ptr client,
void on_message(session_ptr client,const std::string &msg);
void on_message(session_ptr client,
const std::vector<unsigned char> &data);
};
typedef boost::shared_ptr<echo_handler> echo_handler_ptr;
typedef boost::shared_ptr<echo_server_handler> echo_server_handler_ptr;
}
#endif // ECHO_HANDLER_HPP
#endif // ECHO_SERVER_HANDLER_HPP

Binary file not shown.

View File

@@ -36,7 +36,7 @@ using boost::asio::ip::tcp;
int main(int argc, char* argv[]) {
std::string host = "localhost";
short port = 5000;
short port = 9002;
std::string full_host;
if (argc == 3) {
@@ -47,10 +47,10 @@ int main(int argc, char* argv[]) {
std::stringstream temp;
temp << argv[1] << ":" << port;
temp << host << ":" << port;
full_host = temp.str();
websocketecho::echo_handler_ptr echo_handler(new websocketecho::echo_handler());
websocketecho::echo_server_handler_ptr echo_handler(new websocketecho::echo_server_handler());
try {
boost::asio::io_service io_service;

Binary file not shown.