From 9e95955fc7d00c017cb1e93cc64f11c3ca1dcd9c Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 24 Jun 2012 20:03:17 -0700 Subject: [PATCH] Work on WS server. --- src/WSDoor.cpp | 196 +++++++++++++++++++++++++++++++++++++++++++------ src/WSDoor.h | 4 +- 2 files changed, 176 insertions(+), 24 deletions(-) diff --git a/src/WSDoor.cpp b/src/WSDoor.cpp index 0d5c71ac41..01b395b3fe 100644 --- a/src/WSDoor.cpp +++ b/src/WSDoor.cpp @@ -1,18 +1,31 @@ #include "WSDoor.h" -#include - -#include -#include - #include "Application.h" #include "Config.h" #include "Log.h" +#include "NetworkOPs.h" #include "utils.h" -using namespace std; -using namespace boost::asio::ip; +#include + +#include +#include +#include + +#include "../json/reader.h" +#include "../json/writer.h" + +// +// This is a light weight, untrusted interface for web clients. +// For now we don't provide proof. Later we will. +// + +// +// Strategy: +// - We only talk to NetworkOPs (so we will work even in thin mode) +// - NetworkOPs is smart enough to subscribe and or pass back messages +// // Generate DH for SSL connection. static DH* handleTmpDh(SSL* ssl, int is_export, int iKeyLength) @@ -20,17 +33,64 @@ static DH* handleTmpDh(SSL* ssl, int is_export, int iKeyLength) return 512 == iKeyLength ? theApp->getWallet().getDh512() : theApp->getWallet().getDh1024(); } +template +class WSServerHandler; + +// +// Storage for connection specific info +// - Subscriptions +// +class WSConnection : public InfoSub +{ +public: + typedef typename websocketpp::WSDOOR_SERVER::handler::connection_ptr connection_ptr; + typedef typename websocketpp::WSDOOR_SERVER::handler::message_ptr message_ptr; + +protected: + WSServerHandler* mHandler; + connection_ptr mConnection; + +public: + WSConnection() + : mHandler((WSServerHandler*)(NULL)), + mConnection(connection_ptr()) { ; } + + WSConnection(WSServerHandler* wshpHandler, connection_ptr cpConnection) + : mHandler(wshpHandler), mConnection(cpConnection) { ; } + + ~WSConnection() + { + // XXX Unsubscribe. + nothing(); + } + + // Implement overriden functions from base class: + void send(const Json::Value& jvObj); +}; + + // A single instance of this object is made. // This instance dispatches all events. There is no per connection persistency. template -class WSServerHandler : public endpoint_type::handler { -private: - boost::shared_ptr mCtx; - +class WSServerHandler : public endpoint_type::handler +{ public: typedef typename endpoint_type::handler::connection_ptr connection_ptr; typedef typename endpoint_type::handler::message_ptr message_ptr; + // Private reasons to close. + enum { + crTooSlow = 4000, // Client is too slow. + }; + +private: + boost::shared_ptr mCtx; + +protected: + boost::mutex mMapLock; + boost::unordered_map mMap; + +public: WSServerHandler(boost::shared_ptr spCtx) : mCtx(spCtx) {} boost::shared_ptr on_tls_init() @@ -38,17 +98,103 @@ public: return mCtx; } - void on_message(connection_ptr con, message_ptr msg) { - con->send(msg->get_payload(), msg->get_opcode()); + void send(connection_ptr cpClient, message_ptr mpMessage) + { + try + { + cpClient->send(mpMessage->get_payload(), mpMessage->get_opcode()); + } + catch (...) + { + cpClient->close(websocketpp::close::status::value(crTooSlow), std::string("Client is too slow.")); + } + } + + void send(connection_ptr cpClient, const std::string& strMessage) + { + try + { + Log(lsINFO) << "Ws:: Sending '" << strMessage << "'"; + + cpClient->send(strMessage); + } + catch (...) + { + cpClient->close(websocketpp::close::status::value(crTooSlow), std::string("Client is too slow.")); + } + } + + void send(connection_ptr cpClient, const Json::Value& jvObj) + { + Json::FastWriter jfwWriter; + + Log(lsINFO) << "Ws:: Object '" << jfwWriter.write(jvObj) << "'"; + + + send(cpClient, jfwWriter.write(jvObj)); + } + + void on_open(connection_ptr cpClient) + { + boost::mutex::scoped_lock sl(mMapLock); + + mMap[cpClient] = WSConnection(this, cpClient); + } + + void on_close(connection_ptr cpClient) + { + boost::mutex::scoped_lock sl(mMapLock); + + mMap.erase(cpClient); + } + + void on_message(connection_ptr cpClient, message_ptr mpMessage) + { + Json::Value jvRequest; + Json::Reader jrReader; + + if (mpMessage->get_opcode() != websocketpp::frame::opcode::TEXT) + { + Json::Value jvResult(Json::objectValue); + + jvResult["type"] = "error"; + jvResult["error"] = "wsTextRequired"; // We only accept text messages. + + send(cpClient, jvResult); + } + else if (!jrReader.parse(mpMessage->get_payload(), jvRequest) || jvRequest.isNull() || !jvRequest.isObject()) + { + Json::Value jvResult(Json::objectValue); + + jvResult["type"] = "error"; + jvResult["error"] = "jsonInvalid"; // Received invalid json. + jvResult["value"] = mpMessage->get_payload(); + + send(cpClient, jvResult); + } + else + { + Json::Value jvResult(Json::objectValue); + + jvResult["type"] = "success"; + jvResult["value"] = mpMessage->get_payload(); + + send(cpClient, jvResult); + } } - void http(connection_ptr con) { - con->set_body("WebSocket++ TLS certificate test

WebSocket++ TLS certificate test

This is an HTTP(S) page served by a WebSocket++ server for the purposes of confirming that certificates are working since browsers normally silently ignore certificate issues.

"); + // Respond to http requests. + void http(connection_ptr cpClient) + { + cpClient->set_body( + "" SYSTEM_NAME " Test" + "

" SYSTEM_NAME " Test

This page shows http(s) connectivity is working.

"); } }; void WSDoor::startListening() { + // Generate a single SSL context for use by all connections. boost::shared_ptr mCtx; mCtx = boost::make_shared(boost::asio::ssl::context::sslv23); @@ -59,20 +205,21 @@ void WSDoor::startListening() SSL_CTX_set_tmp_dh_callback(mCtx->native_handle(), handleTmpDh); + // Construct a single handler for all requests. websocketpp::WSDOOR_SERVER::handler::ptr handler(new WSServerHandler(mCtx)); + // Construct a websocket server. mEndpoint = new websocketpp::WSDOOR_SERVER(handler); // mEndpoint->alog().unset_level(websocketpp::log::alevel::ALL); // mEndpoint->elog().unset_level(websocketpp::log::elevel::ALL); - Log(lsINFO) << "listening>"; + // Call the main-event-loop of the websocket server. + mEndpoint->listen( + boost::asio::ip::tcp::endpoint( + boost::asio::ip::address().from_string(theConfig.WEBSOCKET_IP), theConfig.WEBSOCKET_PORT)); - mEndpoint->listen(boost::asio::ip::tcp::endpoint(address().from_string(theConfig.WEBSOCKET_IP), theConfig.WEBSOCKET_PORT)); - - free(mEndpoint); - - Log(lsINFO) << "listening<"; + delete mEndpoint; } WSDoor* WSDoor::createWSDoor() @@ -97,10 +244,15 @@ void WSDoor::stop() { if (mThread) { - mEndpoint->stop(); // XXX Make this thread safe + mEndpoint->stop(); mThread->join(); } } +void WSConnection::send(const Json::Value& jvObj) +{ + mHandler->send(mConnection, jvObj); +} + // vim:ts=4 diff --git a/src/WSDoor.h b/src/WSDoor.h index f44de663f3..af90ddaf88 100644 --- a/src/WSDoor.h +++ b/src/WSDoor.h @@ -19,8 +19,8 @@ class WSDoor { private: - websocketpp::WSDOOR_SERVER* mEndpoint; - boost::thread* mThread; + websocketpp::WSDOOR_SERVER* mEndpoint; + boost::thread* mThread; void startListening();