mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
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:
2
Makefile
2
Makefile
@@ -28,7 +28,7 @@
|
||||
# It's authors were Jonathan Wallace and Bernhard Fluehmann.
|
||||
|
||||
|
||||
objects = websocket_session.o websocket_server.o websocket_frame.o \
|
||||
objects = websocket_server_session.o websocket_client_session.o websocket_session.o websocket_server.o websocket_client.o websocket_frame.o \
|
||||
network_utilities.o sha1.o base64.o
|
||||
|
||||
OS=$(shell uname)
|
||||
|
||||
23
examples/chat_client/Makefile
Normal file
23
examples/chat_client/Makefile
Normal 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
BIN
examples/chat_client/chat_client
Executable file
Binary file not shown.
78
examples/chat_client/chat_client.cpp
Normal file
78
examples/chat_client/chat_client.cpp
Normal 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;
|
||||
}
|
||||
177
examples/chat_client/chat_client.html
Normal file
177
examples/chat_client/chat_client.html
Normal 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>
|
||||
107
examples/chat_client/chat_client_handler.cpp
Normal file
107
examples/chat_client/chat_client_handler.cpp
Normal 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
|
||||
}
|
||||
94
examples/chat_client/chat_client_handler.hpp
Normal file
94
examples/chat_client/chat_client_handler.hpp
Normal 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
|
||||
4
examples/chat_client/jquery-1.6.3.min.js
vendored
Normal file
4
examples/chat_client/jquery-1.6.3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
@@ -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;
|
||||
|
||||
BIN
examples/echo_server/echo_server_old
Executable file
BIN
examples/echo_server/echo_server_old
Executable file
Binary file not shown.
191
src/websocket_client.cpp
Normal file
191
src/websocket_client.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* 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 "websocket_client.hpp"
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using websocketpp::client;
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
client::client(boost::asio::io_service& io_service,connection_handler_ptr defc)
|
||||
: m_elog_level(LOG_ALL),
|
||||
m_alog_level(ALOG_ALL),
|
||||
m_state(CLIENT_STATE_NULL),
|
||||
m_max_message_size(DEFAULT_MAX_MESSAGE_SIZE),
|
||||
m_io_service(io_service),
|
||||
m_resolver(io_service),
|
||||
m_def_con_handler(defc) {}
|
||||
|
||||
void client::init() {
|
||||
m_client_session = client_session_ptr(
|
||||
new client_session(
|
||||
shared_from_this(),
|
||||
m_io_service,
|
||||
m_def_con_handler
|
||||
)
|
||||
);
|
||||
m_state = CLIENT_STATE_INITIALIZED;
|
||||
}
|
||||
|
||||
void client::connect(const std::string& url) {
|
||||
if (m_state != CLIENT_STATE_INITIALIZED) {
|
||||
throw client_error("connect can only be called after init and before a connection has been established");
|
||||
}
|
||||
|
||||
m_client_session->set_url(url);
|
||||
|
||||
std::stringstream port;
|
||||
port << m_client_session->get_port();
|
||||
|
||||
|
||||
tcp::resolver::query query(m_client_session->get_host(),
|
||||
port.str());
|
||||
tcp::resolver::iterator iterator = m_resolver.resolve(query);
|
||||
|
||||
boost::asio::async_connect(m_client_session->socket(),
|
||||
iterator,boost::bind(&client::handle_connect,
|
||||
this,
|
||||
boost::asio::placeholders::error));
|
||||
m_state = CLIENT_STATE_CONNECTING;
|
||||
}
|
||||
|
||||
|
||||
void client::add_subprotocol(const std::string& p) {
|
||||
if (m_state != CLIENT_STATE_INITIALIZED) {
|
||||
throw client_error("add_protocol can only be called after init and before connect");
|
||||
}
|
||||
m_client_session->add_subprotocol(p);
|
||||
}
|
||||
|
||||
void client::set_header(const std::string& key,const std::string& val) {
|
||||
if (m_state != CLIENT_STATE_INITIALIZED) {
|
||||
throw client_error("set_header can only be called after init and before connect");
|
||||
}
|
||||
m_client_session->set_header(key,val);
|
||||
}
|
||||
|
||||
void client::set_origin(const std::string& val) {
|
||||
if (m_state != CLIENT_STATE_INITIALIZED) {
|
||||
throw client_error("set_origin can only be called after init and before connect");
|
||||
}
|
||||
m_client_session->set_origin(val);
|
||||
}
|
||||
|
||||
|
||||
void client::set_max_message_size(uint64_t val) {
|
||||
if (val > frame::PAYLOAD_64BIT_LIMIT) {
|
||||
std::stringstream err;
|
||||
err << "Invalid maximum message size: " << val;
|
||||
|
||||
// TODO: Figure out what the ideal error behavior for this method.
|
||||
// Options:
|
||||
// Throw exception
|
||||
// Log error and set value to maximum allowed
|
||||
// Log error and leave value at whatever it was before
|
||||
log(err.str(),LOG_WARN);
|
||||
//throw client_error(err.str());
|
||||
}
|
||||
m_max_message_size = val;
|
||||
}
|
||||
|
||||
bool client::test_elog_level(uint16_t level) {
|
||||
return (level >= m_elog_level);
|
||||
}
|
||||
void client::set_elog_level(uint16_t level) {
|
||||
std::stringstream msg;
|
||||
msg << "Error logging level changing from "
|
||||
<< m_elog_level << " to " << level;
|
||||
log(msg.str(),LOG_INFO);
|
||||
|
||||
m_elog_level = level;
|
||||
}
|
||||
bool client::test_alog_level(uint16_t level) {
|
||||
return (level & m_alog_level);
|
||||
}
|
||||
void client::set_alog_level(uint16_t level) {
|
||||
if (test_alog_level(level)) {
|
||||
return;
|
||||
}
|
||||
std::stringstream msg;
|
||||
msg << "Access logging level " << level << " being set";
|
||||
access_log(msg.str(),ALOG_INFO);
|
||||
|
||||
m_alog_level |= level;
|
||||
}
|
||||
void client::unset_alog_level(uint16_t level) {
|
||||
if (!test_alog_level(level)) {
|
||||
return;
|
||||
}
|
||||
std::stringstream msg;
|
||||
msg << "Access logging level " << level << " being unset";
|
||||
access_log(msg.str(),ALOG_INFO);
|
||||
|
||||
m_alog_level &= ~level;
|
||||
}
|
||||
|
||||
bool client::validate_message_size(uint64_t val) {
|
||||
if (val > m_max_message_size) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void client::log(std::string msg,uint16_t level) {
|
||||
if (!test_elog_level(level)) {
|
||||
return;
|
||||
}
|
||||
std::cerr << "[Error Log] "
|
||||
<< boost::posix_time::to_iso_extended_string(
|
||||
boost::posix_time::second_clock::local_time())
|
||||
<< " " << msg << std::endl;
|
||||
}
|
||||
void client::access_log(std::string msg,uint16_t level) {
|
||||
if (!test_alog_level(level)) {
|
||||
return;
|
||||
}
|
||||
std::cout << "[Access Log] "
|
||||
<< boost::posix_time::to_iso_extended_string(
|
||||
boost::posix_time::second_clock::local_time())
|
||||
<< " " << msg << std::endl;
|
||||
}
|
||||
|
||||
void client::handle_connect(const boost::system::error_code& error) {
|
||||
if (!error) {
|
||||
m_state = CLIENT_STATE_CONNECTED;
|
||||
m_client_session->on_connect();
|
||||
} else {
|
||||
std::stringstream err;
|
||||
err << "An error occurred while establishing a connection: " << error;
|
||||
|
||||
log(err.str(),LOG_ERROR);
|
||||
throw client_error(err.str());
|
||||
}
|
||||
}
|
||||
160
src/websocket_client.hpp
Normal file
160
src/websocket_client.hpp
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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 WEBSOCKET_CLIENT_HPP
|
||||
#define WEBSOCKET_CLIENT_HPP
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace websocketpp {
|
||||
class client;
|
||||
typedef boost::shared_ptr<client> client_ptr;
|
||||
}
|
||||
|
||||
#include "websocketpp.hpp"
|
||||
#include "websocket_client_session.hpp"
|
||||
#include "websocket_connection_handler.hpp"
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
namespace websocketpp {
|
||||
|
||||
class client_error : public std::exception {
|
||||
public:
|
||||
client_error(const std::string& msg)
|
||||
: m_msg(msg) {}
|
||||
~client_error() throw() {}
|
||||
|
||||
virtual const char* what() const throw() {
|
||||
return m_msg.c_str();
|
||||
}
|
||||
private:
|
||||
std::string m_msg;
|
||||
};
|
||||
|
||||
class client : public boost::enable_shared_from_this<client> {
|
||||
public:
|
||||
// System logging levels
|
||||
/* static const uint16_t LOG_ALL = 0;
|
||||
static const uint16_t LOG_DEBUG = 1;
|
||||
static const uint16_t LOG_INFO = 2;
|
||||
static const uint16_t LOG_WARN = 3;
|
||||
static const uint16_t LOG_ERROR = 4;
|
||||
static const uint16_t LOG_FATAL = 5;
|
||||
static const uint16_t LOG_OFF = 6;
|
||||
|
||||
// Access logging controls
|
||||
// Individual bits
|
||||
static const uint16_t ALOG_CONNECT = 0x1;
|
||||
static const uint16_t ALOG_DISCONNECT = 0x2;
|
||||
static const uint16_t ALOG_MISC_CONTROL = 0x4;
|
||||
static const uint16_t ALOG_FRAME = 0x8;
|
||||
static const uint16_t ALOG_MESSAGE = 0x10;
|
||||
static const uint16_t ALOG_INFO = 0x20;
|
||||
static const uint16_t ALOG_HANDSHAKE = 0x40;
|
||||
// Useful groups
|
||||
static const uint16_t ALOG_OFF = 0x0;
|
||||
static const uint16_t ALOG_CONTROL = ALOG_CONNECT
|
||||
& ALOG_DISCONNECT
|
||||
& ALOG_MISC_CONTROL;
|
||||
static const uint16_t ALOG_ALL = 0xFFFF;
|
||||
*/
|
||||
static const uint16_t CLIENT_STATE_NULL = 0;
|
||||
static const uint16_t CLIENT_STATE_INITIALIZED = 1;
|
||||
static const uint16_t CLIENT_STATE_CONNECTING = 2;
|
||||
static const uint16_t CLIENT_STATE_CONNECTED = 3;
|
||||
|
||||
client(boost::asio::io_service& io_service,
|
||||
connection_handler_ptr defc);
|
||||
|
||||
// INTERFACE FOR LOCAL APPLICATIONS
|
||||
|
||||
// initializes the session. Methods that affect the opening handshake
|
||||
// such as add_protocol and set_header must be called after init and
|
||||
// before connect.
|
||||
void init();
|
||||
|
||||
// starts the connection process. Should be called before
|
||||
// io_service.run(), connection process will not start until run() has
|
||||
// been called.
|
||||
void connect(const std::string& url);
|
||||
|
||||
// Adds a protocol to the opening handshake.
|
||||
// Must be called before connect
|
||||
void add_subprotocol(const std::string& p);
|
||||
|
||||
// Sets the value of the given HTTP header to be sent during the
|
||||
// opening handshake. Must be called before connect
|
||||
void set_header(const std::string& key,const std::string& val);
|
||||
|
||||
void set_origin(const std::string& val);
|
||||
|
||||
void set_max_message_size(uint64_t val);
|
||||
|
||||
// Test methods determine if a message of the given level should be
|
||||
// written. elog shows all values above the level set. alog shows only
|
||||
// the values explicitly set.
|
||||
bool test_elog_level(uint16_t level);
|
||||
void set_elog_level(uint16_t level);
|
||||
|
||||
bool test_alog_level(uint16_t level);
|
||||
void set_alog_level(uint16_t level);
|
||||
void unset_alog_level(uint16_t level);
|
||||
|
||||
// INTERFACE FOR SESSIONS
|
||||
|
||||
// Check if message size is within server's acceptable parameters
|
||||
bool validate_message_size(uint64_t val);
|
||||
|
||||
// write to the server's logs
|
||||
void log(std::string msg,uint16_t level = LOG_ERROR);
|
||||
void access_log(std::string msg,uint16_t level);
|
||||
private:
|
||||
// if no errors starts the session's read loop and returns to the
|
||||
// start_accept phase.
|
||||
void handle_connect(const boost::system::error_code& error);
|
||||
|
||||
private:
|
||||
uint16_t m_elog_level;
|
||||
uint16_t m_alog_level;
|
||||
|
||||
uint16_t m_state;
|
||||
|
||||
std::set<std::string> m_hosts;
|
||||
uint64_t m_max_message_size;
|
||||
boost::asio::io_service& m_io_service;
|
||||
tcp::resolver m_resolver;
|
||||
client_session_ptr m_client_session;
|
||||
connection_handler_ptr m_def_con_handler;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // WEBSOCKET_CLIENT_HPP
|
||||
313
src/websocket_client_session.cpp
Normal file
313
src/websocket_client_session.cpp
Normal file
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* 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 "websocketpp.hpp"
|
||||
#include "websocket_client_session.hpp"
|
||||
|
||||
#include "websocket_frame.hpp"
|
||||
#include "utf8_validator/utf8_validator.hpp"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
using websocketpp::client_session;
|
||||
|
||||
client_session::client_session (client_ptr c,
|
||||
boost::asio::io_service& io_service,
|
||||
connection_handler_ptr defc)
|
||||
: session(io_service,defc),m_client(c) {}
|
||||
|
||||
void client_session::on_connect() {
|
||||
write_handshake();
|
||||
}
|
||||
|
||||
void client_session::set_url(const std::string& url) {
|
||||
// TODO: impliment
|
||||
// TODO: input validation
|
||||
|
||||
m_host = "thor-websocket.zaphoyd.net";
|
||||
m_port = 9003;
|
||||
m_resource = "/chat";
|
||||
|
||||
}
|
||||
|
||||
bool client_session::get_secure() const {
|
||||
return m_secure;
|
||||
}
|
||||
|
||||
std::string client_session::get_host() const{
|
||||
return m_host;
|
||||
}
|
||||
|
||||
uint16_t client_session::get_port() const {
|
||||
return m_port;
|
||||
}
|
||||
|
||||
void client_session::set_header(const std::string &key,const std::string &val) {
|
||||
// TODO: prevent use of reserved headers
|
||||
m_client_headers[key] = val;
|
||||
}
|
||||
|
||||
void client_session::set_origin(const std::string& val) {
|
||||
// TODO: input validation
|
||||
m_client_origin = val;
|
||||
}
|
||||
|
||||
void client_session::add_subprotocol(const std::string &val) {
|
||||
// TODO: input validation
|
||||
m_client_subprotocols.push_back(val);
|
||||
}
|
||||
|
||||
void client_session::add_extension(const std::string& val) {
|
||||
// TODO: input validation
|
||||
m_client_extensions.push_back(val);
|
||||
}
|
||||
|
||||
void client_session::read_handshake() {
|
||||
boost::asio::async_read_until(
|
||||
m_socket,
|
||||
m_buf,
|
||||
"\r\n\r\n",
|
||||
boost::bind(
|
||||
&session::handle_read_handshake,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void client_session::handle_read_handshake(const boost::system::error_code& e,
|
||||
std::size_t bytes_transferred) {
|
||||
// parse server handshake
|
||||
|
||||
|
||||
// read handshake and set local state (or pass to write_handshake)
|
||||
std::ostringstream line;
|
||||
line << &m_buf;
|
||||
m_raw_server_handshake += line.str();
|
||||
|
||||
m_client->access_log(m_raw_server_handshake,ALOG_HANDSHAKE);
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
std::string::size_type start = 0;
|
||||
std::string::size_type end;
|
||||
|
||||
// Get request and parse headers
|
||||
end = m_raw_server_handshake.find("\r\n",start);
|
||||
|
||||
while(end != std::string::npos) {
|
||||
tokens.push_back(m_raw_server_handshake.substr(start, end - start));
|
||||
|
||||
start = end + 2;
|
||||
|
||||
end = m_raw_server_handshake.find("\r\n",start);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < tokens.size(); i++) {
|
||||
if (i == 0) {
|
||||
m_server_http_request = tokens[i];
|
||||
}
|
||||
|
||||
end = tokens[i].find(": ",0);
|
||||
|
||||
if (end != std::string::npos) {
|
||||
std::string h = tokens[i].substr(0,end);
|
||||
if (get_server_header(h) == "") {
|
||||
m_server_headers[h] = tokens[i].substr(end+2);
|
||||
} else {
|
||||
m_server_headers[h] += ", " + tokens[i].substr(end+2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handshake error checking
|
||||
try {
|
||||
std::stringstream err;
|
||||
std::string h;
|
||||
|
||||
// TODO: allow versions greater than 1.1
|
||||
if (m_server_http_request.substr(0,9) != "HTTP/1.1 ") {
|
||||
err << "Websocket handshake has invalid HTTP version: "
|
||||
<< m_server_http_request.substr(0,9);
|
||||
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
// check the HTTP version
|
||||
if (m_server_http_request.substr(9,3) != "101") {
|
||||
err << "Websocket handshake ended with status "
|
||||
<< m_server_http_request.substr(9);
|
||||
|
||||
// TODO: check version header for other supported versions.
|
||||
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
// verify the presence of required headers
|
||||
h = get_server_header("Upgrade");
|
||||
if (h == "") {
|
||||
throw(handshake_error("Required Upgrade header is missing",400));
|
||||
} else if (!boost::iequals(h,"websocket")) {
|
||||
err << "Upgrade header was " << h << " instead of \"websocket\"";
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
h = get_server_header("Connection");
|
||||
if (h == "") {
|
||||
throw(handshake_error("Required Connection header is missing",400));
|
||||
} else if (!boost::ifind_first(h,"upgrade")) {
|
||||
err << "Connection header, \"" << h
|
||||
<< "\", does not contain required token \"upgrade\"";
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
if (get_server_header("Sec-WebSocket-Accept") == "") {
|
||||
throw(handshake_error("Required Sec-WebSocket-Key header is missing",400));
|
||||
} else {
|
||||
// TODO: make a helper function for this.
|
||||
std::string server_key = m_client_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_client->log("Error computing handshake sha1 hash.",LOG_ERROR);
|
||||
// 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 != get_server_header("Sec-WebSocket-Accept")) {
|
||||
m_client->log("Server key does not match",LOG_ERROR);
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (const handshake_error& e) {
|
||||
std::stringstream err;
|
||||
err << "Caught handshake exception: " << e.what();
|
||||
|
||||
m_client->access_log(e.what(),ALOG_HANDSHAKE);
|
||||
m_client->log(err.str(),LOG_ERROR);
|
||||
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
log_open_result();
|
||||
|
||||
m_status = OPEN;
|
||||
|
||||
if (m_local_interface) {
|
||||
m_local_interface->on_open(shared_from_this());
|
||||
}
|
||||
|
||||
reset_message();
|
||||
read_frame();
|
||||
}
|
||||
|
||||
void client_session::write_handshake() {
|
||||
// generate client handshake.
|
||||
std::string client_handshake;
|
||||
|
||||
client_handshake += "GET "+m_resource+" HTTP/1.1\r\n";
|
||||
|
||||
set_header("Upgrade","websocket");
|
||||
set_header("Connection","Upgrade");
|
||||
set_header("Sec-WebSocket-Version","13");
|
||||
|
||||
set_header("Host",m_host);
|
||||
|
||||
if (m_client_origin != "") {
|
||||
set_header("Origin",m_client_origin);
|
||||
}
|
||||
|
||||
// TODO: generate proper key
|
||||
m_client_key = "XO4pxrIMLnK1CEVQP9untQ==";
|
||||
set_header("Sec-WebSocket-Key",m_client_key);
|
||||
|
||||
|
||||
|
||||
set_header("User Agent","WebSocket++/2011-09-25");
|
||||
|
||||
header_list::iterator it;
|
||||
for (it = m_client_headers.begin(); it != m_client_headers.end(); it++) {
|
||||
client_handshake += it->first + ": " + it->second + "\r\n";
|
||||
}
|
||||
|
||||
client_handshake += "\r\n";
|
||||
|
||||
m_raw_client_handshake = client_handshake;
|
||||
|
||||
// start async write to handle_write_handshake
|
||||
boost::asio::async_write(
|
||||
m_socket,
|
||||
boost::asio::buffer(m_raw_client_handshake),
|
||||
boost::bind(
|
||||
&session::handle_write_handshake,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void client_session::handle_write_handshake(const boost::system::error_code& error) {
|
||||
if (error) {
|
||||
handle_error("Error writing handshake",error);
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
read_handshake();
|
||||
}
|
||||
|
||||
void client_session::log(const std::string& msg, uint16_t level) const {
|
||||
m_client->log(msg,level);
|
||||
}
|
||||
|
||||
void client_session::access_log(const std::string& msg, uint16_t level) const {
|
||||
m_client->access_log(msg,level);
|
||||
}
|
||||
129
src/websocket_client_session.hpp
Normal file
129
src/websocket_client_session.hpp
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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 WEBSOCKET_CLIENT_SESSION_HPP
|
||||
#define WEBSOCKET_CLIENT_SESSION_HPP
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/enable_shared_from_this.hpp>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace websocketpp {
|
||||
class client_session;
|
||||
typedef boost::shared_ptr<client_session> client_session_ptr;
|
||||
}
|
||||
|
||||
#include "websocket_session.hpp"
|
||||
#include "websocket_client.hpp"
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
namespace websocketpp {
|
||||
|
||||
class client_session : public session {
|
||||
public:
|
||||
client_session (client_ptr c,
|
||||
boost::asio::io_service& io_service,
|
||||
connection_handler_ptr defc);
|
||||
|
||||
/*** CLIENT INTERFACE ***/
|
||||
|
||||
// This function is called when a tcp connection has been established and
|
||||
// the connection is ready to start the opening handshake.
|
||||
void on_connect();
|
||||
|
||||
/*** HANDSHAKE INTERFACE ***/
|
||||
|
||||
void set_url(const std::string& url);
|
||||
|
||||
bool get_secure() const;
|
||||
std::string get_host() const;
|
||||
uint16_t get_port() const;
|
||||
|
||||
// Set an HTTP header for the outgoing client handshake.
|
||||
void set_header(const std::string& key,const std::string& val);
|
||||
|
||||
// adds a subprotocol. This will result in the appropriate
|
||||
// Sec-WebSocket-Protocol header being sent with the opening connection.
|
||||
// Values will be sent in the order they were added. Servers interpret this
|
||||
// order as the preferred order.
|
||||
void add_subprotocol(const std::string &val);
|
||||
|
||||
// Sets the origin value that will be sent to the server
|
||||
void set_origin(const std::string &val);
|
||||
|
||||
// Adds an extension to the extension list. Extensions are sent in the
|
||||
// order added
|
||||
void add_extension(const std::string& val);
|
||||
|
||||
/*** SESSION INTERFACE ***/
|
||||
// see session
|
||||
bool is_server() const {return false;}
|
||||
|
||||
void log(const std::string& msg, uint16_t level) const;
|
||||
void access_log(const std::string& msg, uint16_t level) const;
|
||||
protected:
|
||||
// Opening handshake processors and callbacks.
|
||||
virtual void write_handshake();
|
||||
virtual void handle_write_handshake(const boost::system::error_code& e);
|
||||
virtual void read_handshake();
|
||||
virtual void handle_read_handshake(const boost::system::error_code& e,
|
||||
std::size_t bytes_transferred);
|
||||
|
||||
private:
|
||||
|
||||
protected:
|
||||
// url parts
|
||||
bool m_secure;
|
||||
std::string m_host;
|
||||
uint16_t m_port;
|
||||
|
||||
// handshake stuff
|
||||
std::string m_client_key;
|
||||
|
||||
// connection resources
|
||||
client_ptr m_client;
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // WEBSOCKET_CLIENT_SESSION_HPP
|
||||
@@ -56,11 +56,14 @@ public:
|
||||
// client (useful for returning non-standard error codes)
|
||||
virtual void validate(session_ptr client) = 0;
|
||||
|
||||
|
||||
|
||||
// this will be called once the connected websocket is avaliable for
|
||||
// writing messages. client may be a new websocket session or an existing
|
||||
// session that was recently passed to this handler.
|
||||
virtual void connect(session_ptr client) = 0;
|
||||
virtual void on_open(session_ptr client) = 0;
|
||||
|
||||
|
||||
// this will be called when the connected websocket is no longer avaliable
|
||||
// for writing messages. This occurs under the following conditions:
|
||||
// - Disconnect message recieved from the remote endpoint
|
||||
@@ -69,15 +72,15 @@ public:
|
||||
// calls the disconnect method of session
|
||||
// - The connection handler assigned to this client was set to another
|
||||
// handler
|
||||
virtual void disconnect(session_ptr client,uint16_t status,const std::string &reason) = 0;
|
||||
virtual void on_close(session_ptr client,uint16_t status,const std::string &reason) = 0;
|
||||
|
||||
// this will be called when a text message is recieved. Text will be
|
||||
// encoded as UTF-8.
|
||||
virtual void message(session_ptr client,const std::string &msg) = 0;
|
||||
virtual void on_message(session_ptr client,const std::string &msg) = 0;
|
||||
|
||||
// this will be called when a binary message is recieved. Argument is a
|
||||
// vector of the raw bytes in the message body.
|
||||
virtual void message(session_ptr client,
|
||||
virtual void on_message(session_ptr client,
|
||||
const std::vector<unsigned char> &data) = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -37,7 +37,9 @@ using websocketpp::server;
|
||||
server::server(boost::asio::io_service& io_service,
|
||||
const tcp::endpoint& endpoint,
|
||||
connection_handler_ptr defc)
|
||||
: m_max_message_size(DEFAULT_MAX_MESSAGE_SIZE),
|
||||
: m_elog_level(LOG_ALL),
|
||||
m_alog_level(ALOG_ALL),
|
||||
m_max_message_size(DEFAULT_MAX_MESSAGE_SIZE),
|
||||
m_io_service(io_service),
|
||||
m_acceptor(io_service, endpoint),
|
||||
m_def_con_handler(defc) {}
|
||||
@@ -61,11 +63,47 @@ void server::set_max_message_size(uint64_t val) {
|
||||
// Throw exception
|
||||
// Log error and set value to maximum allowed
|
||||
// Log error and leave value at whatever it was before
|
||||
throw server_error(err.str());
|
||||
log(err.str(),LOG_WARN);
|
||||
//throw server_error(err.str());
|
||||
}
|
||||
m_max_message_size = val;
|
||||
}
|
||||
|
||||
bool server::test_elog_level(uint16_t level) {
|
||||
return (level >= m_elog_level);
|
||||
}
|
||||
void server::set_elog_level(uint16_t level) {
|
||||
std::stringstream msg;
|
||||
msg << "Error logging level changing from "
|
||||
<< m_elog_level << " to " << level;
|
||||
log(msg.str(),LOG_INFO);
|
||||
|
||||
m_elog_level = level;
|
||||
}
|
||||
bool server::test_alog_level(uint16_t level) {
|
||||
return (level & m_alog_level);
|
||||
}
|
||||
void server::set_alog_level(uint16_t level) {
|
||||
if (test_alog_level(level)) {
|
||||
return;
|
||||
}
|
||||
std::stringstream msg;
|
||||
msg << "Access logging level " << level << " being set";
|
||||
access_log(msg.str(),ALOG_INFO);
|
||||
|
||||
m_alog_level |= level;
|
||||
}
|
||||
void server::unset_alog_level(uint16_t level) {
|
||||
if (!test_alog_level(level)) {
|
||||
return;
|
||||
}
|
||||
std::stringstream msg;
|
||||
msg << "Access logging level " << level << " being unset";
|
||||
access_log(msg.str(),ALOG_INFO);
|
||||
|
||||
m_alog_level &= ~level;
|
||||
}
|
||||
|
||||
bool server::validate_host(std::string host) {
|
||||
if (m_hosts.find(host) == m_hosts.end()) {
|
||||
return false;
|
||||
@@ -80,13 +118,19 @@ bool server::validate_message_size(uint64_t val) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void server::error_log(std::string msg) {
|
||||
void server::log(std::string msg,uint16_t level) {
|
||||
if (!test_elog_level(level)) {
|
||||
return;
|
||||
}
|
||||
std::cerr << "[Error Log] "
|
||||
<< boost::posix_time::to_iso_extended_string(
|
||||
boost::posix_time::second_clock::local_time())
|
||||
<< " " << msg << std::endl;
|
||||
}
|
||||
void server::access_log(std::string msg) {
|
||||
void server::access_log(std::string msg,uint16_t level) {
|
||||
if (!test_alog_level(level)) {
|
||||
return;
|
||||
}
|
||||
std::cout << "[Access Log] "
|
||||
<< boost::posix_time::to_iso_extended_string(
|
||||
boost::posix_time::second_clock::local_time())
|
||||
@@ -94,31 +138,31 @@ void server::access_log(std::string msg) {
|
||||
}
|
||||
|
||||
void server::start_accept() {
|
||||
session_ptr new_ws(new session(shared_from_this(),
|
||||
m_io_service,
|
||||
m_def_con_handler));
|
||||
server_session_ptr new_session(new server_session(shared_from_this(),
|
||||
m_io_service,
|
||||
m_def_con_handler));
|
||||
|
||||
m_acceptor.async_accept(
|
||||
new_ws->socket(),
|
||||
new_session->socket(),
|
||||
boost::bind(
|
||||
&server::handle_accept,
|
||||
this,
|
||||
new_ws,
|
||||
new_session,
|
||||
boost::asio::placeholders::error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void server::handle_accept(session_ptr session,
|
||||
void server::handle_accept(server_session_ptr session,
|
||||
const boost::system::error_code& error) {
|
||||
|
||||
if (!error) {
|
||||
session->start();
|
||||
session->on_connect();
|
||||
} else {
|
||||
std::stringstream err;
|
||||
err << "Error accepting socket connection: " << error;
|
||||
|
||||
error_log(err.str());
|
||||
log(err.str(),LOG_ERROR);
|
||||
throw server_error(err.str());
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace websocketpp {
|
||||
}
|
||||
|
||||
#include "websocketpp.hpp"
|
||||
#include "websocket_session.hpp"
|
||||
#include "websocket_server_session.hpp"
|
||||
#include "websocket_connection_handler.hpp"
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
@@ -61,6 +61,31 @@ private:
|
||||
|
||||
class server : public boost::enable_shared_from_this<server> {
|
||||
public:
|
||||
// System logging levels
|
||||
/*static const uint16_t LOG_ALL = 0;
|
||||
static const uint16_t LOG_DEBUG = 1;
|
||||
static const uint16_t LOG_INFO = 2;
|
||||
static const uint16_t LOG_WARN = 3;
|
||||
static const uint16_t LOG_ERROR = 4;
|
||||
static const uint16_t LOG_FATAL = 5;
|
||||
static const uint16_t LOG_OFF = 6;
|
||||
|
||||
// Access logging controls
|
||||
// Individual bits
|
||||
static const uint16_t ALOG_CONNECT = 0x1;
|
||||
static const uint16_t ALOG_DISCONNECT = 0x2;
|
||||
static const uint16_t ALOG_MISC_CONTROL = 0x4;
|
||||
static const uint16_t ALOG_FRAME = 0x8;
|
||||
static const uint16_t ALOG_MESSAGE = 0x10;
|
||||
static const uint16_t ALOG_INFO = 0x20;
|
||||
static const uint16_t ALOG_HANDSHAKE = 0x40;
|
||||
// Useful groups
|
||||
static const uint16_t ALOG_OFF = 0x0;
|
||||
static const uint16_t ALOG_CONTROL = ALOG_CONNECT
|
||||
& ALOG_DISCONNECT
|
||||
& ALOG_MISC_CONTROL;
|
||||
static const uint16_t ALOG_ALL = 0xFFFF;
|
||||
*/
|
||||
server(boost::asio::io_service& io_service,
|
||||
const tcp::endpoint& endpoint,
|
||||
connection_handler_ptr defc);
|
||||
@@ -68,7 +93,9 @@ class server : public boost::enable_shared_from_this<server> {
|
||||
// creates a new session object and connects the next websocket
|
||||
// connection to it.
|
||||
void start_accept();
|
||||
|
||||
|
||||
// INTERFACE FOR LOCAL APPLICATIONS
|
||||
|
||||
// Add or remove a host string (host:port) to the list of acceptable
|
||||
// hosts to accept websocket connections from. Additions/deletions here
|
||||
// only affect new connections.
|
||||
@@ -77,6 +104,18 @@ class server : public boost::enable_shared_from_this<server> {
|
||||
|
||||
void set_max_message_size(uint64_t val);
|
||||
|
||||
// Test methods determine if a message of the given level should be
|
||||
// written. elog shows all values above the level set. alog shows only
|
||||
// the values explicitly set.
|
||||
bool test_elog_level(uint16_t level);
|
||||
void set_elog_level(uint16_t level);
|
||||
|
||||
bool test_alog_level(uint16_t level);
|
||||
void set_alog_level(uint16_t level);
|
||||
void unset_alog_level(uint16_t level);
|
||||
|
||||
// INTERFACE FOR SESSIONS
|
||||
|
||||
// Check if this server will respond to this host.
|
||||
bool validate_host(std::string host);
|
||||
|
||||
@@ -84,17 +123,18 @@ class server : public boost::enable_shared_from_this<server> {
|
||||
bool validate_message_size(uint64_t val);
|
||||
|
||||
// write to the server's logs
|
||||
void error_log(std::string msg);
|
||||
void access_log(std::string msg);
|
||||
void log(std::string msg,uint16_t level = LOG_ERROR);
|
||||
void access_log(std::string msg,uint16_t level);
|
||||
private:
|
||||
|
||||
|
||||
// if no errors starts the session's read loop and returns to the
|
||||
// start_accept phase.
|
||||
void handle_accept(session_ptr session,
|
||||
void handle_accept(server_session_ptr session,
|
||||
const boost::system::error_code& error);
|
||||
|
||||
private:
|
||||
uint16_t m_elog_level;
|
||||
uint16_t m_alog_level;
|
||||
|
||||
std::set<std::string> m_hosts;
|
||||
uint64_t m_max_message_size;
|
||||
boost::asio::io_service& m_io_service;
|
||||
@@ -104,4 +144,4 @@ class server : public boost::enable_shared_from_this<server> {
|
||||
|
||||
}
|
||||
|
||||
#endif // WEBSOCKET_SERVER_HPP
|
||||
#endif // WEBSOCKET_SERVER_HPP
|
||||
|
||||
346
src/websocket_server_session.cpp
Normal file
346
src/websocket_server_session.cpp
Normal file
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* 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 "websocketpp.hpp"
|
||||
#include "websocket_server_session.hpp"
|
||||
|
||||
#include "websocket_frame.hpp"
|
||||
#include "utf8_validator/utf8_validator.hpp"
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
using websocketpp::server_session;
|
||||
|
||||
server_session::server_session(server_ptr s,
|
||||
boost::asio::io_service& io_service,
|
||||
connection_handler_ptr defc)
|
||||
: session(io_service,defc),m_server(s) {}
|
||||
|
||||
void server_session::on_connect() {
|
||||
read_handshake();
|
||||
}
|
||||
|
||||
|
||||
void server_session::set_header(const std::string &key,const std::string &val) {
|
||||
// TODO: prevent use of reserved headers;
|
||||
m_server_headers[key] = val;
|
||||
}
|
||||
|
||||
void server_session::select_subprotocol(const std::string& val) {
|
||||
std::vector<std::string>::iterator it;
|
||||
|
||||
it = std::find(m_client_subprotocols.begin(),
|
||||
m_client_subprotocols.end(),
|
||||
val);
|
||||
|
||||
if (val != "" && it == m_client_subprotocols.end()) {
|
||||
throw server_error("Attempted to choose a subprotocol not proposed by the client");
|
||||
}
|
||||
|
||||
m_server_subprotocol = val;
|
||||
}
|
||||
|
||||
void server_session::select_extension(const std::string& val) {
|
||||
if (val == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string>::iterator it;
|
||||
|
||||
it = std::find(m_client_extensions.begin(),
|
||||
m_client_extensions.end(),
|
||||
val);
|
||||
|
||||
if (it == m_client_extensions.end()) {
|
||||
throw server_error("Attempted to choose an extension not proposed by the client");
|
||||
}
|
||||
|
||||
m_server_extensions.push_back(val);
|
||||
}
|
||||
|
||||
void server_session::read_handshake() {
|
||||
boost::asio::async_read_until(
|
||||
m_socket,
|
||||
m_buf,
|
||||
"\r\n\r\n",
|
||||
boost::bind(
|
||||
&session::handle_read_handshake,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void server_session::handle_read_handshake(const boost::system::error_code& e,
|
||||
std::size_t bytes_transferred) {
|
||||
std::ostringstream line;
|
||||
line << &m_buf;
|
||||
m_raw_client_handshake += line.str();
|
||||
|
||||
access_log(m_raw_client_handshake,ALOG_HANDSHAKE);
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
std::string::size_type start = 0;
|
||||
std::string::size_type end;
|
||||
|
||||
// Get request and parse headers
|
||||
end = m_raw_client_handshake.find("\r\n",start);
|
||||
|
||||
while(end != std::string::npos) {
|
||||
tokens.push_back(m_raw_client_handshake.substr(start, end - start));
|
||||
|
||||
start = end + 2;
|
||||
|
||||
end = m_raw_client_handshake.find("\r\n",start);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < tokens.size(); i++) {
|
||||
if (i == 0) {
|
||||
m_client_http_request = tokens[i];
|
||||
}
|
||||
|
||||
end = tokens[i].find(": ",0);
|
||||
|
||||
if (end != std::string::npos) {
|
||||
std::string h = tokens[i].substr(0,end);
|
||||
|
||||
if (get_client_header(h) == "") {
|
||||
m_client_headers[h] = tokens[i].substr(end+2);
|
||||
} else {
|
||||
m_client_headers[h] += ", " + tokens[i].substr(end+2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handshake error checking
|
||||
try {
|
||||
std::stringstream err;
|
||||
std::string h;
|
||||
|
||||
// check the method
|
||||
if (m_client_http_request.substr(0,4) != "GET ") {
|
||||
err << "Websocket handshake has invalid method: "
|
||||
<< m_client_http_request.substr(0,4);
|
||||
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
// check the HTTP version
|
||||
// TODO: allow versions greater than 1.1
|
||||
end = m_client_http_request.find(" HTTP/1.1",4);
|
||||
if (end == std::string::npos) {
|
||||
err << "Websocket handshake has invalid HTTP version";
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
m_resource = m_client_http_request.substr(4,end-4);
|
||||
|
||||
// verify the presence of required headers
|
||||
h = get_client_header("Host");
|
||||
if (h == "") {
|
||||
throw(handshake_error("Required Host header is missing",400));
|
||||
} else if (!m_server->validate_host(h)) {
|
||||
err << "Host " << h << " is not one of this server's names.";
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
h = get_client_header("Upgrade");
|
||||
if (h == "") {
|
||||
throw(handshake_error("Required Upgrade header is missing",400));
|
||||
} else if (!boost::iequals(h,"websocket")) {
|
||||
err << "Upgrade header was " << h << " instead of \"websocket\"";
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
h = get_client_header("Connection");
|
||||
if (h == "") {
|
||||
throw(handshake_error("Required Connection header is missing",400));
|
||||
} else if (!boost::ifind_first(h,"upgrade")) {
|
||||
err << "Connection header, \"" << h
|
||||
<< "\", does not contain required token \"upgrade\"";
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
if (get_client_header("Sec-WebSocket-Key") == "") {
|
||||
throw(handshake_error("Required Sec-WebSocket-Key header is missing",400));
|
||||
}
|
||||
|
||||
h = get_client_header("Sec-WebSocket-Version");
|
||||
if (h == "") {
|
||||
throw(handshake_error("Required Sec-WebSocket-Version header is missing",400));
|
||||
} else {
|
||||
m_version = atoi(h.c_str());
|
||||
|
||||
if (m_version != 7 && m_version != 8 && m_version != 13) {
|
||||
err << "This server doesn't support WebSocket protocol version "
|
||||
<< m_version;
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
}
|
||||
|
||||
if (m_version < 13) {
|
||||
h = get_client_header("Sec-WebSocket-Origin");
|
||||
} else {
|
||||
h = get_client_header("Origin");
|
||||
}
|
||||
|
||||
if (h != "") {
|
||||
m_client_origin = h;
|
||||
}
|
||||
|
||||
// TODO: extract subprotocols
|
||||
// TODO: extract extensions
|
||||
|
||||
// optional headers (delegated to the local interface)
|
||||
if (m_local_interface) {
|
||||
m_local_interface->validate(shared_from_this());
|
||||
}
|
||||
|
||||
m_server_http_code = 101;
|
||||
m_server_http_string = "Switching Protocols";
|
||||
} catch (const handshake_error& e) {
|
||||
std::stringstream err;
|
||||
err << "Caught handshake exception: " << e.what();
|
||||
|
||||
access_log(e.what(),ALOG_HANDSHAKE);
|
||||
log(err.str(),LOG_ERROR);
|
||||
|
||||
m_server_http_code = e.m_http_error_code;
|
||||
m_server_http_string = e.m_http_error_msg;
|
||||
}
|
||||
|
||||
write_handshake();
|
||||
}
|
||||
|
||||
void server_session::write_handshake() {
|
||||
std::stringstream h;
|
||||
|
||||
|
||||
|
||||
if (m_server_http_code == 101) {
|
||||
std::string server_key = get_client_header("Sec-WebSocket-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)){
|
||||
// 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);
|
||||
|
||||
// set handshake accept headers
|
||||
set_header("Sec-WebSocket-Accept",server_key);
|
||||
set_header("Upgrade","websocket");
|
||||
set_header("Connection","Upgrade");
|
||||
} else {
|
||||
log("Error computing handshake sha1 hash.",LOG_ERROR);
|
||||
m_server_http_code = 500;
|
||||
m_server_http_string = "";
|
||||
}
|
||||
}
|
||||
|
||||
// hardcoded server headers
|
||||
set_header("Server","WebSocket++/2011-09-25");
|
||||
|
||||
h << "HTTP/1.1 " << m_server_http_code << " "
|
||||
<< (m_server_http_string != "" ? m_server_http_string :
|
||||
lookup_http_error_string(m_server_http_code))
|
||||
<< "\r\n";
|
||||
|
||||
header_list::iterator it;
|
||||
for (it = m_server_headers.begin(); it != m_server_headers.end(); it++) {
|
||||
h << it->first << ": " << it->second << "\r\n";
|
||||
}
|
||||
|
||||
h << "\r\n";
|
||||
|
||||
m_raw_server_handshake = h.str();
|
||||
|
||||
// start async write to handle_write_handshake
|
||||
boost::asio::async_write(
|
||||
m_socket,
|
||||
boost::asio::buffer(m_raw_server_handshake),
|
||||
boost::bind(
|
||||
&session::handle_write_handshake,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void server_session::handle_write_handshake(const boost::system::error_code& error) {
|
||||
if (error) {
|
||||
handle_error("Error writing handshake response",error);
|
||||
return;
|
||||
}
|
||||
|
||||
log_open_result();
|
||||
|
||||
if (m_server_http_code != 101) {
|
||||
std::stringstream err;
|
||||
err << "Handshake ended with HTTP error: " << m_server_http_code << " "
|
||||
<< (m_server_http_string != "" ? m_server_http_string : lookup_http_error_string(m_server_http_code));
|
||||
log(err.str(),LOG_ERROR);
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
m_status = OPEN;
|
||||
|
||||
if (m_local_interface) {
|
||||
m_local_interface->on_open(shared_from_this());
|
||||
}
|
||||
|
||||
reset_message();
|
||||
this->read_frame();
|
||||
}
|
||||
|
||||
void server_session::log(const std::string& msg, uint16_t level) const {
|
||||
m_server->log(msg,level);
|
||||
}
|
||||
|
||||
void server_session::access_log(const std::string& msg, uint16_t level) const {
|
||||
m_server->access_log(msg,level);
|
||||
}
|
||||
112
src/websocket_server_session.hpp
Normal file
112
src/websocket_server_session.hpp
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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 WEBSOCKET_SERVER_SESSION_HPP
|
||||
#define WEBSOCKET_SERVER_SESSION_HPP
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/enable_shared_from_this.hpp>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace websocketpp {
|
||||
class server_session;
|
||||
typedef boost::shared_ptr<server_session> server_session_ptr;
|
||||
}
|
||||
|
||||
#include "websocket_session.hpp"
|
||||
#include "websocket_server.hpp"
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
namespace websocketpp {
|
||||
|
||||
class server_session : public session {
|
||||
public:
|
||||
server_session (server_ptr s,
|
||||
boost::asio::io_service& io_service,
|
||||
connection_handler_ptr defc);
|
||||
|
||||
/*** SERVER INTERFACE ***/
|
||||
|
||||
// This function is called when a connection to a new client has been
|
||||
// established and the server is ready to read the client handshake.
|
||||
void on_connect();
|
||||
|
||||
/*** HANDSHAKE INTERFACE ***/
|
||||
|
||||
// Set an HTTP header for the outgoing server handshake response.
|
||||
void set_header(const std::string& key, const std::string& val);
|
||||
|
||||
// Selects a subprotocol for the connection to use. val must be a value
|
||||
// present in the client's opening handshake or the empty string for null.
|
||||
void select_subprotocol(const std::string& val);
|
||||
|
||||
// Selects an extension from the list offered by the client. Each extension
|
||||
// selected must have been offered by the client. Extensions will be used
|
||||
// in the order that they were selected here.
|
||||
void select_extension(const std::string& val);
|
||||
|
||||
/*** SESSION INTERFACE ***/
|
||||
// see session
|
||||
virtual bool is_server() const { return true;}
|
||||
|
||||
void log(const std::string& msg, uint16_t level) const;
|
||||
void access_log(const std::string& msg, uint16_t level) const;
|
||||
protected:
|
||||
// Opening handshake processors and callbacks. These need to be defined in
|
||||
virtual void write_handshake();
|
||||
virtual void handle_write_handshake(const boost::system::error_code& e);
|
||||
virtual void read_handshake();
|
||||
virtual void handle_read_handshake(const boost::system::error_code& e,
|
||||
std::size_t bytes_transferred);
|
||||
|
||||
|
||||
private:
|
||||
|
||||
protected:
|
||||
// connection resources
|
||||
server_ptr m_server;
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // WEBSOCKET_SERVER_SESSION_HPP
|
||||
@@ -25,6 +25,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "websocketpp.hpp"
|
||||
#include "websocket_session.hpp"
|
||||
|
||||
#include "websocket_frame.hpp"
|
||||
@@ -42,7 +43,7 @@
|
||||
|
||||
using websocketpp::session;
|
||||
|
||||
session::session (server_ptr s,boost::asio::io_service& io_service,
|
||||
session::session (boost::asio::io_service& io_service,
|
||||
connection_handler_ptr defc)
|
||||
: m_status(CONNECTING),
|
||||
m_local_close_code(CLOSE_STATUS_NO_STATUS),
|
||||
@@ -50,8 +51,8 @@ session::session (server_ptr s,boost::asio::io_service& io_service,
|
||||
m_was_clean(false),
|
||||
m_closed_by_me(false),
|
||||
m_dropped_by_me(false),
|
||||
m_server(s),
|
||||
m_socket(io_service),
|
||||
m_io_service(io_service),
|
||||
m_local_interface(defc),
|
||||
|
||||
|
||||
@@ -62,58 +63,65 @@ tcp::socket& session::socket() {
|
||||
return m_socket;
|
||||
}
|
||||
|
||||
void session::start() {
|
||||
// async read to handle_read_handshake
|
||||
boost::asio::async_read_until(
|
||||
m_socket,
|
||||
m_buf,
|
||||
"\r\n\r\n",
|
||||
boost::bind(
|
||||
&session::handle_read_handshake,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred
|
||||
)
|
||||
);
|
||||
boost::asio::io_service& session::io_service() {
|
||||
return m_io_service;
|
||||
}
|
||||
|
||||
void session::set_handler(connection_handler_ptr new_con) {
|
||||
if (m_local_interface) {
|
||||
m_local_interface->disconnect(shared_from_this(),4000,"Setting new connection handler");
|
||||
// TODO: this should be another method and not reusing onclose
|
||||
//m_local_interface->disconnect(shared_from_this(),4000,"Setting new connection handler");
|
||||
}
|
||||
m_local_interface = new_con;
|
||||
m_local_interface->connect(shared_from_this());
|
||||
m_local_interface->on_open(shared_from_this());
|
||||
}
|
||||
|
||||
std::string session::get_header(const std::string& key) const {
|
||||
std::map<std::string,std::string>::const_iterator h = m_headers.find(key);
|
||||
const std::string& session::get_subprotocol() const {
|
||||
if (m_status == CONNECTING) {
|
||||
log("Subprotocol is not avaliable before the handshake has completed.",LOG_WARN);
|
||||
throw server_error("Subprotocol is not avaliable before the handshake has completed.");
|
||||
}
|
||||
return m_server_subprotocol;
|
||||
}
|
||||
|
||||
const std::string& session::get_resource() const {
|
||||
return m_resource;
|
||||
}
|
||||
|
||||
const std::string& session::get_origin() const {
|
||||
return m_client_origin;
|
||||
}
|
||||
|
||||
std::string session::get_client_header(const std::string& key) const {
|
||||
return get_header(key,m_client_headers);
|
||||
}
|
||||
|
||||
std::string session::get_server_header(const std::string& key) const {
|
||||
return get_header(key,m_server_headers);
|
||||
}
|
||||
|
||||
std::string session::get_header(const std::string& key,
|
||||
const header_list& list) const {
|
||||
header_list::const_iterator h = list.find(key);
|
||||
|
||||
if (h == m_headers.end()) {
|
||||
return std::string();
|
||||
if (h == list.end()) {
|
||||
return "";
|
||||
} else {
|
||||
return h->second;
|
||||
}
|
||||
}
|
||||
|
||||
void session::add_header(const std::string &key,const std::string &value) {
|
||||
throw "unimplimented";
|
||||
const std::vector<std::string>& session::get_extensions() const {
|
||||
return m_server_extensions;
|
||||
}
|
||||
|
||||
std::string session::get_request() const {
|
||||
return m_request;
|
||||
}
|
||||
|
||||
std::string session::get_origin() const {
|
||||
if (m_version < 13) {
|
||||
return get_header("Sec-WebSocket-Origin");
|
||||
} else {
|
||||
return get_header("Origin");
|
||||
}
|
||||
unsigned int session::get_version() const {
|
||||
return m_version;
|
||||
}
|
||||
|
||||
void session::send(const std::string &msg) {
|
||||
if (m_status != OPEN) {
|
||||
// error?
|
||||
log("Tried to send a message from a session that wasn't open",LOG_WARN);
|
||||
return;
|
||||
}
|
||||
m_write_frame.set_fin(true);
|
||||
@@ -123,10 +131,9 @@ void session::send(const std::string &msg) {
|
||||
write_frame();
|
||||
}
|
||||
|
||||
// send binary frame
|
||||
void session::send(const std::vector<unsigned char> &data) {
|
||||
if (m_status != OPEN) {
|
||||
// error?
|
||||
log("Tried to send a message from a session that wasn't open",LOG_WARN);
|
||||
return;
|
||||
}
|
||||
m_write_frame.set_fin(true);
|
||||
@@ -136,10 +143,15 @@ void session::send(const std::vector<unsigned char> &data) {
|
||||
write_frame();
|
||||
}
|
||||
|
||||
// send close frame
|
||||
void session::close(uint16_t status,const std::string& msg) {
|
||||
disconnect(status,msg);
|
||||
// TODO: close behavior
|
||||
}
|
||||
|
||||
// TODO: clean this up, needs to be broken out into more specific methods
|
||||
void session::disconnect(uint16_t status,const std::string &message) {
|
||||
if (m_status != OPEN) {
|
||||
m_server->error_log("got a disconnect call from invalid state");
|
||||
log("Tried to disconnect a session that wasn't open",LOG_WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -158,6 +170,7 @@ void session::disconnect(uint16_t status,const std::string &message) {
|
||||
m_write_frame.set_status(CLOSE_STATUS_NORMAL,"");
|
||||
} else if (status == CLOSE_STATUS_ABNORMAL_CLOSE) {
|
||||
// unknown internal error, don't set a status? use protocol error?
|
||||
log("Tried to disconnect with status ABNORMAL_CLOSE",LOG_DEBUG);
|
||||
} else {
|
||||
m_write_frame.set_status(status,message);
|
||||
}
|
||||
@@ -166,6 +179,10 @@ void session::disconnect(uint16_t status,const std::string &message) {
|
||||
}
|
||||
|
||||
void session::ping(const std::string &msg) {
|
||||
if (m_status != OPEN) {
|
||||
log("Tried to send a ping from a session that wasn't open",LOG_WARN);
|
||||
return;
|
||||
}
|
||||
m_write_frame.set_fin(true);
|
||||
m_write_frame.set_opcode(frame::PING);
|
||||
m_write_frame.set_payload(msg);
|
||||
@@ -174,6 +191,10 @@ void session::ping(const std::string &msg) {
|
||||
}
|
||||
|
||||
void session::pong(const std::string &msg) {
|
||||
if (m_status != OPEN) {
|
||||
log("Tried to send a pong from a session that wasn't open",LOG_WARN);
|
||||
return;
|
||||
}
|
||||
m_write_frame.set_fin(true);
|
||||
m_write_frame.set_opcode(frame::PONG);
|
||||
m_write_frame.set_payload(msg);
|
||||
@@ -181,230 +202,6 @@ void session::pong(const std::string &msg) {
|
||||
write_frame();
|
||||
}
|
||||
|
||||
void session::handle_read_handshake(const boost::system::error_code& e,
|
||||
std::size_t bytes_transferred) {
|
||||
// read handshake and set local state (or pass to write_handshake)
|
||||
std::ostringstream line;
|
||||
line << &m_buf;
|
||||
m_handshake += line.str();
|
||||
|
||||
//std::cout << "=== Raw Message ===" << std::endl;
|
||||
//std::cout << m_handshake << std::endl;
|
||||
//std::cout << "=== Raw Message end ===" << std::endl;
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
std::string::size_type start = 0;
|
||||
std::string::size_type end;
|
||||
|
||||
// Get request and parse headers
|
||||
end = m_handshake.find("\r\n",start);
|
||||
|
||||
while(end != std::string::npos) {
|
||||
tokens.push_back(m_handshake.substr(start, end - start));
|
||||
|
||||
start = end + 2;
|
||||
|
||||
end = m_handshake.find("\r\n",start);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < tokens.size(); i++) {
|
||||
if (i == 0) {
|
||||
m_request = tokens[i];
|
||||
}
|
||||
|
||||
end = tokens[i].find(": ",0);
|
||||
|
||||
if (end != std::string::npos) {
|
||||
m_headers[tokens[i].substr(0,end)] = tokens[i].substr(end+2);
|
||||
}
|
||||
}
|
||||
|
||||
// handshake error checking
|
||||
try {
|
||||
std::stringstream err;
|
||||
std::string h;
|
||||
|
||||
// check the method
|
||||
if (m_request.substr(0,4) != "GET ") {
|
||||
err << "Websocket handshake has invalid method: "
|
||||
<< m_request.substr(0,4);
|
||||
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
// check the HTTP version
|
||||
// TODO: allow versions greater than 1.1
|
||||
end = m_request.find(" HTTP/1.1",4);
|
||||
if (end == std::string::npos) {
|
||||
err << "Websocket handshake has invalid HTTP version";
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
m_request = m_request.substr(4,end-4);
|
||||
|
||||
// verify the presence of required headers
|
||||
h = get_header("Host");
|
||||
if (h == "") {
|
||||
throw(handshake_error("Required Host header is missing",400));
|
||||
} else if (!m_server->validate_host(h)) {
|
||||
err << "Host " << h << " is not one of this server's names.";
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
h = get_header("Upgrade");
|
||||
if (h == "") {
|
||||
throw(handshake_error("Required Upgrade header is missing",400));
|
||||
} else if (!boost::iequals(h,"websocket")) {
|
||||
err << "Upgrade header was " << h << " instead of \"websocket\"";
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
h = get_header("Connection");
|
||||
if (h == "") {
|
||||
throw(handshake_error("Required Connection header is missing",400));
|
||||
} else if (!boost::ifind_first(h,"upgrade")) {
|
||||
err << "Connection header, \"" << h
|
||||
<< "\", does not contain required token \"upgrade\"";
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
|
||||
if (get_header("Sec-WebSocket-Key") == "") {
|
||||
throw(handshake_error("Required Sec-WebSocket-Key header is missing",400));
|
||||
}
|
||||
|
||||
h = get_header("Sec-WebSocket-Version");
|
||||
if (h == "") {
|
||||
throw(handshake_error("Required Sec-WebSocket-Version header is missing",400));
|
||||
} else {
|
||||
m_version = atoi(h.c_str());
|
||||
|
||||
if (m_version != 7 && m_version != 8 && m_version != 13) {
|
||||
err << "This server doesn't support WebSocket protocol version "
|
||||
<< m_version;
|
||||
throw(handshake_error(err.str(),400));
|
||||
}
|
||||
}
|
||||
|
||||
// optional headers (delegated to the local interface)
|
||||
if (m_local_interface) {
|
||||
m_local_interface->validate(shared_from_this());
|
||||
}
|
||||
|
||||
} catch (const handshake_error& e) {
|
||||
std::stringstream err;
|
||||
err << "Caught handshake exception: " << e.what();
|
||||
|
||||
m_server->error_log(err.str());
|
||||
e.write(shared_from_this());
|
||||
return;
|
||||
}
|
||||
|
||||
this->write_handshake();
|
||||
}
|
||||
|
||||
void session::write_handshake() {
|
||||
std::string server_handshake = "";
|
||||
std::string server_key = m_headers["Sec-WebSocket-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_server->error_log("Error computing handshake sha1 hash.");
|
||||
write_http_error(500,"");
|
||||
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);
|
||||
|
||||
server_handshake += "HTTP/1.1 101 Switching Protocols\r\n";
|
||||
server_handshake += "Upgrade: websocket\r\n";
|
||||
server_handshake += "Connection: Upgrade\r\n";
|
||||
server_handshake += "Sec-WebSocket-Accept: "+server_key+"\r\n";
|
||||
server_handshake += "Server: WebSocket++/2011-09-22\r\n\r\n";
|
||||
|
||||
// TODO: handler requested headers
|
||||
|
||||
// start async write to handle_write_handshake
|
||||
boost::asio::async_write(
|
||||
m_socket,
|
||||
boost::asio::buffer(server_handshake),
|
||||
boost::bind(
|
||||
&session::handle_write_handshake,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void session::handle_write_handshake(const boost::system::error_code& error) {
|
||||
if (error) {
|
||||
handle_error("Error writing handshake",error);
|
||||
return;
|
||||
}
|
||||
|
||||
access_log_open(101);
|
||||
|
||||
m_status = OPEN;
|
||||
|
||||
if (m_local_interface) {
|
||||
m_local_interface->connect(shared_from_this());
|
||||
}
|
||||
|
||||
reset_message();
|
||||
this->read_frame();
|
||||
}
|
||||
|
||||
void session::write_http_error(int code,const std::string &msg) {
|
||||
std::stringstream server_handshake;
|
||||
|
||||
server_handshake << "HTTP/1.1 " << code << " "
|
||||
<< (msg != "" ? msg : lookup_http_error_string(code))
|
||||
<< "\r\n"
|
||||
<< "Server: WebSocket++/2011-09-22\r\n";
|
||||
|
||||
// additional headers?
|
||||
|
||||
server_handshake << "\r\n";
|
||||
|
||||
// start async write to handle_write_handshake
|
||||
boost::asio::async_write(
|
||||
m_socket,
|
||||
boost::asio::buffer(server_handshake.str()),
|
||||
boost::bind(
|
||||
&session::handle_write_http_error,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error
|
||||
)
|
||||
);
|
||||
|
||||
access_log_open(code);
|
||||
|
||||
std::stringstream err;
|
||||
err << "Handshake ended with HTTP error: " << code << " "
|
||||
<< (msg != "" ? msg : lookup_http_error_string(code));
|
||||
|
||||
m_server->error_log(err.str());
|
||||
}
|
||||
|
||||
void session::handle_write_http_error(const boost::system::error_code& error) {
|
||||
if (error) {
|
||||
handle_error("Error writing http response",error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void session::read_frame() {
|
||||
boost::asio::async_read(
|
||||
m_socket,
|
||||
@@ -418,11 +215,10 @@ void session::read_frame() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void session::handle_frame_header(const boost::system::error_code& error) {
|
||||
if (error) {
|
||||
handle_error("Error reading basic frame header",error);
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -431,6 +227,7 @@ void session::handle_frame_header(const boost::system::error_code& error) {
|
||||
if (!m_read_frame.validate_basic_header()) {
|
||||
handle_error("Basic header validation failed",boost::system::error_code());
|
||||
disconnect(CLOSE_STATUS_PROTOCOL_ERROR,"");
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -454,6 +251,7 @@ void session::handle_extended_frame_header(
|
||||
const boost::system::error_code& error) {
|
||||
if (error) {
|
||||
handle_error("Error reading extended frame header",error);
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -464,16 +262,6 @@ void session::handle_extended_frame_header(
|
||||
}
|
||||
|
||||
void session::read_payload() {
|
||||
/*char * foo = m_read_frame.get_header();
|
||||
|
||||
std::cout << std::hex << ((uint16_t*)foo)[0] << std::endl;
|
||||
|
||||
std::cout << "opcode: " << m_read_frame.get_opcode() << std::endl;
|
||||
std::cout << "fin: " << m_read_frame.get_fin() << std::endl;
|
||||
std::cout << "mask: " << m_read_frame.get_masked() << std::endl;
|
||||
std::cout << "size: " << (uint16_t)m_read_frame.get_basic_size() << std::endl;
|
||||
std::cout << "payload_size: " << m_read_frame.get_payload_size() << std::endl;*/
|
||||
|
||||
boost::asio::async_read(
|
||||
m_socket,
|
||||
boost::asio::buffer(m_read_frame.get_payload()),
|
||||
@@ -488,11 +276,11 @@ void session::read_payload() {
|
||||
void session::handle_read_payload (const boost::system::error_code& error) {
|
||||
if (error) {
|
||||
handle_error("Error reading payload data frame header",error);
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
m_read_frame.process_payload();
|
||||
|
||||
|
||||
if (m_status == OPEN) {
|
||||
switch (m_read_frame.get_opcode()) {
|
||||
@@ -516,6 +304,7 @@ void session::handle_read_payload (const boost::system::error_code& error) {
|
||||
break;
|
||||
default:
|
||||
disconnect(CLOSE_STATUS_PROTOCOL_ERROR,"Invalid Opcode");
|
||||
// TODO: close behavior
|
||||
break;
|
||||
}
|
||||
} else if (m_status == CLOSING) {
|
||||
@@ -526,23 +315,26 @@ void session::handle_read_payload (const boost::system::error_code& error) {
|
||||
}
|
||||
} else {
|
||||
// Recieved message before or after connection was opened/closed
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
// check if there was an error processing this frame and fail the connection
|
||||
if (m_error) {
|
||||
m_server->error_log("Connection has been closed uncleanly");
|
||||
log("Connection has been closed uncleanly",LOG_ERROR);
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_status == CLOSED) {
|
||||
access_log_close();
|
||||
log_close_result();
|
||||
|
||||
if (m_local_interface) {
|
||||
m_local_interface->disconnect(shared_from_this(),
|
||||
m_close_code,
|
||||
m_close_message);
|
||||
m_local_interface->on_close(shared_from_this(),
|
||||
m_close_code,
|
||||
m_close_message);
|
||||
}
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -552,14 +344,16 @@ void session::handle_read_payload (const boost::system::error_code& error) {
|
||||
void session::handle_write_frame (const boost::system::error_code& error) {
|
||||
if (error) {
|
||||
handle_error("Error writing frame data",error);
|
||||
// TODO: close behavior
|
||||
}
|
||||
|
||||
//std::cout << "Successfully wrote frame." << std::endl;
|
||||
}
|
||||
|
||||
void session::process_ping() {
|
||||
m_server->access_log("Ping");
|
||||
|
||||
access_log("Ping",ALOG_MISC_CONTROL);
|
||||
// TODO: on_ping
|
||||
|
||||
// send pong
|
||||
m_write_frame.set_fin(true);
|
||||
m_write_frame.set_opcode(frame::PONG);
|
||||
@@ -569,12 +363,14 @@ void session::process_ping() {
|
||||
}
|
||||
|
||||
void session::process_pong() {
|
||||
m_server->access_log("Pong");
|
||||
access_log("Pong",ALOG_MISC_CONTROL);
|
||||
// TODO: on_pong
|
||||
}
|
||||
|
||||
void session::process_text() {
|
||||
if (!m_read_frame.validate_utf8(&m_utf8_state,&m_utf8_codepoint)) {
|
||||
disconnect(CLOSE_STATUS_INVALID_PAYLOAD,"Invalid UTF8 Data");
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -586,6 +382,7 @@ void session::process_binary() {
|
||||
handle_error("Got a new message before the previous was finished.",
|
||||
boost::system::error_code());
|
||||
disconnect(CLOSE_STATUS_PROTOCOL_ERROR,"");
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -605,12 +402,14 @@ void session::process_continuation() {
|
||||
handle_error("Got a continuation frame without an outstanding message.",
|
||||
boost::system::error_code());
|
||||
disconnect(CLOSE_STATUS_PROTOCOL_ERROR,"");
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_current_opcode == frame::TEXT_FRAME) {
|
||||
if (!m_read_frame.validate_utf8(&m_utf8_state,&m_utf8_codepoint)) {
|
||||
disconnect(CLOSE_STATUS_INVALID_PAYLOAD,"Invalid UTF8 Data");
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -635,6 +434,7 @@ void session::process_close() {
|
||||
if (m_status == OPEN) {
|
||||
// This is the case where the remote initiated the close.
|
||||
m_closed_by_me = false;
|
||||
// TODO: close behavior
|
||||
disconnect(status,message);
|
||||
} else if (m_status == CLOSING) {
|
||||
// this is an ack of our close message
|
||||
@@ -654,9 +454,9 @@ void session::deliver_message() {
|
||||
|
||||
if (m_current_opcode == frame::BINARY_FRAME) {
|
||||
if (m_fragmented) {
|
||||
m_local_interface->message(shared_from_this(),m_current_message);
|
||||
m_local_interface->on_message(shared_from_this(),m_current_message);
|
||||
} else {
|
||||
m_local_interface->message(shared_from_this(),
|
||||
m_local_interface->on_message(shared_from_this(),
|
||||
m_read_frame.get_payload());
|
||||
}
|
||||
} else if (m_current_opcode == frame::TEXT_FRAME) {
|
||||
@@ -665,6 +465,7 @@ void session::deliver_message() {
|
||||
// make sure the finished frame is valid utf8
|
||||
if (m_utf8_state != utf8_validator::UTF8_ACCEPT) {
|
||||
disconnect(CLOSE_STATUS_INVALID_PAYLOAD,"Invalid UTF8 Data");
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -677,12 +478,12 @@ void session::deliver_message() {
|
||||
);
|
||||
}
|
||||
|
||||
m_local_interface->message(shared_from_this(),msg);
|
||||
m_local_interface->on_message(shared_from_this(),msg);
|
||||
} else {
|
||||
// Not sure if this should be a fatal error or not
|
||||
std::stringstream err;
|
||||
err << "Attempted to deliver a message of unsupported opcode " << m_current_opcode;
|
||||
m_server->error_log(err.str());
|
||||
log(err.str(),LOG_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -694,9 +495,6 @@ void session::extract_payload() {
|
||||
}
|
||||
|
||||
void session::write_frame() {
|
||||
// print debug info
|
||||
m_write_frame.print_frame();
|
||||
|
||||
std::vector<boost::asio::mutable_buffer> data;
|
||||
|
||||
data.push_back(
|
||||
@@ -729,7 +527,7 @@ void session::reset_message() {
|
||||
m_utf8_codepoint = 0;
|
||||
}
|
||||
|
||||
void session::access_log_close() {
|
||||
void session::log_close_result() {
|
||||
std::stringstream msg;
|
||||
|
||||
msg << "[Connection " << this << "] "
|
||||
@@ -739,19 +537,19 @@ void session::access_log_close() {
|
||||
<< "] remote:[" << m_remote_close_code
|
||||
<< (m_remote_close_msg == "" ? "" : ","+m_remote_close_msg) << "]";
|
||||
|
||||
m_server->access_log(msg.str());
|
||||
access_log(msg.str(),ALOG_DISCONNECT);
|
||||
}
|
||||
|
||||
void session::access_log_open(int code) {
|
||||
void session::log_open_result() {
|
||||
std::stringstream msg;
|
||||
|
||||
msg << "[Connection " << this << "] "
|
||||
<< m_socket.remote_endpoint()
|
||||
<< " v" << m_version << " "
|
||||
<< (get_header("User-Agent") == "" ? "NULL" : get_header("User-Agent"))
|
||||
<< " " << m_request << " " << code;
|
||||
<< (get_client_header("User-Agent") == "" ? "NULL" : get_client_header("User-Agent"))
|
||||
<< " " << m_resource << " " << m_server_http_code;
|
||||
|
||||
m_server->access_log(msg.str());
|
||||
access_log(msg.str(),ALOG_HANDSHAKE);
|
||||
}
|
||||
|
||||
void session::handle_error(std::string msg,
|
||||
@@ -760,10 +558,10 @@ void session::handle_error(std::string msg,
|
||||
|
||||
e << "[Connection " << this << "] " << msg << " (" << error << ")";
|
||||
|
||||
m_server->error_log(e.str());
|
||||
log(e.str(),LOG_ERROR);
|
||||
|
||||
if (m_local_interface) {
|
||||
m_local_interface->disconnect(shared_from_this(),1006,e.str());
|
||||
m_local_interface->on_close(shared_from_this(),1006,e.str());
|
||||
}
|
||||
|
||||
m_error = true;
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace websocketpp {
|
||||
class handshake_error;
|
||||
}
|
||||
|
||||
#include "websocket_server.hpp"
|
||||
#include "websocketpp.hpp"
|
||||
#include "websocket_frame.hpp"
|
||||
#include "websocket_connection_handler.hpp"
|
||||
|
||||
@@ -63,6 +63,8 @@ using boost::asio::ip::tcp;
|
||||
|
||||
namespace websocketpp {
|
||||
|
||||
typedef std::map<std::string,std::string> header_list;
|
||||
|
||||
class session : public boost::enable_shared_from_this<session> {
|
||||
public:
|
||||
friend class handshake_error;
|
||||
@@ -86,12 +88,12 @@ public:
|
||||
static const uint16_t CLOSE_STATUS_POLICY_VIOLATION = 1008;
|
||||
static const uint16_t CLOSE_STATUS_MESSAGE_TOO_BIG = 1009;
|
||||
static const uint16_t CLOSE_STATUS_EXTENSION_REQUIRE = 1010;
|
||||
|
||||
session (server_ptr s,
|
||||
boost::asio::io_service& io_service,
|
||||
|
||||
session (boost::asio::io_service& io_service,
|
||||
connection_handler_ptr defc);
|
||||
|
||||
tcp::socket& socket();
|
||||
boost::asio::io_service& io_service();
|
||||
|
||||
/*** SERVER INTERFACE ***/
|
||||
|
||||
@@ -99,7 +101,7 @@ public:
|
||||
// that come after it are called as a result of an async event completing.
|
||||
// if any method in this chain returns before adding a new async event the
|
||||
// session will end.
|
||||
void start();
|
||||
virtual void on_connect() = 0;
|
||||
|
||||
// sets the internal connection handler of this connection to new_con.
|
||||
// This is useful if you want to switch handler objects during a connection
|
||||
@@ -110,24 +112,20 @@ public:
|
||||
|
||||
|
||||
/*** HANDSHAKE INTERFACE ***/
|
||||
// Set session connection information (avaliable only before/during the
|
||||
// opening handshake)
|
||||
|
||||
// gets the value of a header or the empty string if not present.
|
||||
std::string get_header(const std::string &key) const;
|
||||
// adds an arbitrary header to the server handshake HTTP response.
|
||||
void add_header(const std::string &key,const std::string &value);
|
||||
// Get session status (valid once the connection is open)
|
||||
|
||||
std::string get_request() const;
|
||||
std::string get_origin() const;
|
||||
|
||||
// sets the subprotocol being used. This will result in the appropriate
|
||||
// Sec-WebSocket-Protocol header being sent back to the client. The value
|
||||
// here must have been present in the client's opening handshake.
|
||||
void set_subprotocol(const std::string &protocol);
|
||||
|
||||
|
||||
//int get_version();
|
||||
|
||||
//void add_extension();
|
||||
// returns the subprotocol that was negotiated during the opening handshake
|
||||
// or the empty string if no subprotocol was requested.
|
||||
const std::string& get_subprotocol() const;
|
||||
const std::string& get_resource() const;
|
||||
const std::string& get_origin() const;
|
||||
std::string get_client_header(const std::string& key) const;
|
||||
std::string get_server_header(const std::string& key) const;
|
||||
const std::vector<std::string>& get_extensions() const;
|
||||
unsigned int get_version() const;
|
||||
|
||||
/*** SESSION INTERFACE ***/
|
||||
|
||||
@@ -137,45 +135,29 @@ public:
|
||||
void ping(const std::string &msg);
|
||||
void pong(const std::string &msg);
|
||||
|
||||
void disconnect(uint16_t status,const std::string &reason);
|
||||
private:
|
||||
// handle_read_handshake reads the HTTP headers of the initial websocket
|
||||
// handshake, parses out the request and headers, and does error checking
|
||||
// TODO: Generalize a lot of the hard coded things in this method.
|
||||
void handle_read_handshake(const boost::system::error_code& e,
|
||||
std::size_t bytes_transferred);
|
||||
|
||||
// write_handshake calculates the server portion of the handshake and
|
||||
// sends it back.
|
||||
// TODO: Generalize this to include things like protocols, cookies, etc
|
||||
void write_handshake();
|
||||
// handle_write_handshake checks for errors writing the server handshake,
|
||||
// officially declares a connection open, notifies the local interface,
|
||||
// and starts the frame reading loop.
|
||||
void handle_write_handshake(const boost::system::error_code& error);
|
||||
|
||||
// construct and write an HTTP error in the case the handshake goes poorly
|
||||
void write_http_error(int http_code,const std::string &http_err_str);
|
||||
void handle_write_http_error(const boost::system::error_code& error);
|
||||
// initiate a connection close
|
||||
void close(uint16_t status,const std::string &reason);
|
||||
void disconnect(uint16_t status,const std::string& reason); // temp
|
||||
|
||||
virtual bool is_server() const = 0;
|
||||
protected:
|
||||
// Opening handshake processors and callbacks. These need to be defined in
|
||||
// derived classes.
|
||||
virtual void write_handshake() = 0;
|
||||
virtual void handle_write_handshake(const boost::system::error_code& e) = 0;
|
||||
virtual void read_handshake() = 0;
|
||||
virtual void handle_read_handshake(const boost::system::error_code& e,
|
||||
std::size_t bytes_transferred) = 0;
|
||||
|
||||
// start async read for a websocket frame (2 bytes) to handle_frame_header
|
||||
void read_frame();
|
||||
|
||||
// reads frame header and devices if it needs to read more header or go
|
||||
// straight to the payload.
|
||||
void handle_frame_header(const boost::system::error_code& error);
|
||||
|
||||
// process extra headers and start payload read
|
||||
void handle_extended_frame_header(const boost::system::error_code& error);
|
||||
|
||||
// initiate payload read
|
||||
void read_payload();
|
||||
|
||||
// now the frame object should be complete. Process and send it on then
|
||||
// reset for new frame
|
||||
void handle_read_payload (const boost::system::error_code& error);
|
||||
|
||||
// checks for errors writing frames
|
||||
// write m_write_frame out to the socket.
|
||||
void write_frame();
|
||||
void handle_write_frame (const boost::system::error_code& error);
|
||||
|
||||
// helper functions for processing each opcode
|
||||
@@ -194,27 +176,43 @@ private:
|
||||
// messages are recieved.
|
||||
void extract_payload();
|
||||
|
||||
// write m_write_frame out to the socket.
|
||||
void write_frame();
|
||||
|
||||
// reset session for a new message
|
||||
void reset_message();
|
||||
|
||||
// prints connection state to the server access log with given http response
|
||||
// code
|
||||
void access_log_close();
|
||||
void access_log_open(int code);
|
||||
// logging
|
||||
virtual void log(const std::string& msg, uint16_t level) const = 0;
|
||||
virtual void access_log(const std::string& msg, uint16_t level) const = 0;
|
||||
|
||||
void log_close_result();
|
||||
void log_open_result();
|
||||
|
||||
// prints a diagnostic message and disconnects the local interface
|
||||
void handle_error(std::string msg,const boost::system::error_code& error);
|
||||
private:
|
||||
std::string get_header(const std::string& key,
|
||||
const header_list& list) const;
|
||||
|
||||
protected:
|
||||
// Immutable state about the current connection from the handshake
|
||||
std::string m_handshake;
|
||||
std::string m_request;
|
||||
std::map<std::string,std::string> m_headers;
|
||||
unsigned int m_version;
|
||||
std::string m_subprotocol;
|
||||
|
||||
// Client handshake
|
||||
std::string m_raw_client_handshake;
|
||||
std::string m_client_http_request;
|
||||
std::string m_resource;
|
||||
std::string m_client_origin;
|
||||
header_list m_client_headers;
|
||||
std::vector<std::string> m_client_subprotocols;
|
||||
std::vector<std::string> m_client_extensions;
|
||||
unsigned int m_version;
|
||||
|
||||
// Server handshake
|
||||
std::string m_raw_server_handshake;
|
||||
std::string m_server_http_request;
|
||||
header_list m_server_headers;
|
||||
std::string m_server_subprotocol;
|
||||
std::vector<std::string> m_server_extensions;
|
||||
uint16_t m_server_http_code;
|
||||
std::string m_server_http_string;
|
||||
|
||||
// Mutable connection state;
|
||||
status_code m_status;
|
||||
uint16_t m_close_code;
|
||||
@@ -230,9 +228,9 @@ private:
|
||||
bool m_dropped_by_me;
|
||||
|
||||
// Connection Resources
|
||||
server_ptr m_server;
|
||||
tcp::socket m_socket;
|
||||
connection_handler_ptr m_local_interface;
|
||||
tcp::socket m_socket;
|
||||
boost::asio::io_service& m_io_service;
|
||||
connection_handler_ptr m_local_interface;
|
||||
|
||||
// Buffers
|
||||
boost::asio::streambuf m_buf;
|
||||
@@ -266,11 +264,6 @@ public:
|
||||
return m_msg.c_str();
|
||||
}
|
||||
|
||||
void write(session_ptr s) const {
|
||||
s->write_http_error(m_http_error_code,m_http_error_msg);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_msg;
|
||||
int m_http_error_code;
|
||||
std::string m_http_error_msg;
|
||||
|
||||
@@ -30,11 +30,42 @@
|
||||
#ifndef WEBSOCKETPP_HPP
|
||||
#define WEBSOCKETPP_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Defaults
|
||||
namespace websocketpp {
|
||||
const uint64_t DEFAULT_MAX_MESSAGE_SIZE = 0xFFFFFF; // ~16MB
|
||||
|
||||
// System logging levels
|
||||
static const uint16_t LOG_ALL = 0;
|
||||
static const uint16_t LOG_DEBUG = 1;
|
||||
static const uint16_t LOG_INFO = 2;
|
||||
static const uint16_t LOG_WARN = 3;
|
||||
static const uint16_t LOG_ERROR = 4;
|
||||
static const uint16_t LOG_FATAL = 5;
|
||||
static const uint16_t LOG_OFF = 6;
|
||||
|
||||
// Access logging controls
|
||||
// Individual bits
|
||||
static const uint16_t ALOG_CONNECT = 0x1;
|
||||
static const uint16_t ALOG_DISCONNECT = 0x2;
|
||||
static const uint16_t ALOG_MISC_CONTROL = 0x4;
|
||||
static const uint16_t ALOG_FRAME = 0x8;
|
||||
static const uint16_t ALOG_MESSAGE = 0x10;
|
||||
static const uint16_t ALOG_INFO = 0x20;
|
||||
static const uint16_t ALOG_HANDSHAKE = 0x40;
|
||||
// Useful groups
|
||||
static const uint16_t ALOG_OFF = 0x0;
|
||||
static const uint16_t ALOG_CONTROL = ALOG_CONNECT
|
||||
& ALOG_DISCONNECT
|
||||
& ALOG_MISC_CONTROL;
|
||||
static const uint16_t ALOG_ALL = 0xFFFF;
|
||||
}
|
||||
|
||||
#include "websocket_session.hpp"
|
||||
#include "websocket_server_session.hpp"
|
||||
#include "websocket_client_session.hpp"
|
||||
#include "websocket_server.hpp"
|
||||
#include "websocket_client.hpp"
|
||||
|
||||
#endif // WEBSOCKETPP_HPP
|
||||
|
||||
Reference in New Issue
Block a user