From e3b9c5e1062486fa96397dd52c9d41340246406d Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Tue, 9 Jan 2018 19:04:47 +0100 Subject: [PATCH] http: change THttpWSEngine class remove inheritance from TNamed, change signature of PreviewData() and PostProcess() methods --- README/ReleaseNotes/v614/index.md | 6 ++ gui/webdisplay/cef/base_handler.cxx | 11 +-- net/http/inc/THttpCallArg.h | 12 +-- net/http/inc/THttpLongPollEngine.h | 6 +- net/http/inc/THttpWSEngine.h | 12 +-- net/http/inc/THttpWSHandler.h | 11 ++- net/http/src/TCivetweb.cxx | 8 +- net/http/src/THttpCallArg.cxx | 22 ++--- net/http/src/THttpLongPollEngine.cxx | 28 +++--- net/http/src/THttpServer.cxx | 2 +- net/http/src/THttpWSEngine.cxx | 24 +++--- net/http/src/THttpWSHandler.cxx | 123 ++++++++++++++++++--------- 12 files changed, 159 insertions(+), 106 deletions(-) diff --git a/README/ReleaseNotes/v614/index.md b/README/ReleaseNotes/v614/index.md index f398814ab34..acbc3a94d62 100644 --- a/README/ReleaseNotes/v614/index.md +++ b/README/ReleaseNotes/v614/index.md @@ -80,6 +80,12 @@ The following people have contributed to this new version: ## Networking Libraries +Changes in websockets handling in THttpServer. + - New THttpWSHandler class should be used to work with websockets. + It includes all necessary methods to handle multiple connections correctly. + See in tutorials/http/ws.C how it can be used. + - Interface of THttpWSEngine class was changed, all its instances handled internally in THttpWSHandler. + ## GUI Libraries ## Montecarlo Libraries diff --git a/gui/webdisplay/cef/base_handler.cxx b/gui/webdisplay/cef/base_handler.cxx index fb5972092bc..d3c30ed5b0f 100644 --- a/gui/webdisplay/cef/base_handler.cxx +++ b/gui/webdisplay/cef/base_handler.cxx @@ -11,6 +11,7 @@ #include "base_handler.h" #include "TString.h" +#include "TError.h" #include "THttpServer.h" #include "THttpWSEngine.h" #include "THttpCallArg.h" @@ -45,8 +46,8 @@ protected: CefRefPtr<CefMessageRouterBrowserSide::Callback> fCallback; public: - TCefWSEngine(const char *name, const char *title, CefRefPtr<CefMessageRouterBrowserSide::Callback> callback) - : THttpWSEngine(name, title), fCallback(callback) + TCefWSEngine(CefRefPtr<CefMessageRouterBrowserSide::Callback> callback) + : THttpWSEngine(), fCallback(callback) { } @@ -66,7 +67,7 @@ public: virtual void Send(const void * /*buf*/, int /*len*/) { - Error("Send", "Should never be called, only text is supported"); + ::Error("TCefWSEngine::Send", "Should never be called, only text is supported"); } virtual void SendCharStar(const char *buf) @@ -76,7 +77,7 @@ public: fCallback->Success(buf); // send next message to JS } - virtual Bool_t PreviewData(THttpCallArg *arg) + virtual Bool_t PreviewData(THttpCallArg &) { // function called in the user code before processing correspondent websocket data // returns kTRUE when user should ignore such http request - it is for internal use @@ -154,7 +155,7 @@ public: arg->SetFileName("root.ws_emulation"); if (message == "connect") { - TCefWSEngine *ws = new TCefWSEngine("name", "title", callback); + TCefWSEngine *ws = new TCefWSEngine(callback); arg->SetMethod("WS_CONNECT"); arg->SetWSHandle(ws); arg->SetWSId(ws->GetId()); diff --git a/net/http/inc/THttpCallArg.h b/net/http/inc/THttpCallArg.h index 3103061b09e..eb93414a48f 100644 --- a/net/http/inc/THttpCallArg.h +++ b/net/http/inc/THttpCallArg.h @@ -19,7 +19,7 @@ #include <condition_variable> class THttpServer; -class TNamed; +class THttpWSEngine; class THttpCallArg : public TObject { @@ -36,8 +36,8 @@ protected: void *fPostData; ///<! binary data received with post request Long_t fPostDataLength; ///<! length of binary data - TNamed *fWSHandle; ///<! web-socket handle, derived from TNamed class - UInt_t fWSId; ///<! websocket identifier, used in web-socket related operations + THttpWSEngine *fWSHandle; ///<! web-socket engine, which helps to run it + UInt_t fWSId; ///<! websocket identifier, used in web-socket related operations std::condition_variable fCond; ///<! condition used to wait for processing @@ -86,9 +86,9 @@ public: void SetPostData(void *data, Long_t length, Bool_t make_copy = kFALSE); - void SetWSHandle(TNamed *handle); + void SetWSHandle(THttpWSEngine *handle); - TNamed *TakeWSHandle(); + THttpWSEngine *TakeWSHandle(); /** set web-socket id */ void SetWSId(UInt_t id) { fWSId = id; } @@ -127,7 +127,7 @@ public: Long_t GetPostDataLength() const { return fPostDataLength; } /** returns post data as TString */ - TString GetPostDataAsString() const { return TString((const char *) GetPostData(), GetPostDataLength()); } + TString GetPostDataAsString() const { return TString((const char *)GetPostData(), GetPostDataLength()); } /** returns path name from request URL */ const char *GetPathName() const { return fPathName.Data(); } diff --git a/net/http/inc/THttpLongPollEngine.h b/net/http/inc/THttpLongPollEngine.h index e1887ddb860..b7b69091f1f 100644 --- a/net/http/inc/THttpLongPollEngine.h +++ b/net/http/inc/THttpLongPollEngine.h @@ -23,7 +23,7 @@ protected: std::list<std::string> fBuf; ///!< entries submitted to client static const char *gLongPollNope; ///!< default reply on the longpoll request public: - THttpLongPollEngine(const char *name, const char *title) : THttpWSEngine(name, title), fPoll(nullptr), fBuf() {} + THttpLongPollEngine() : THttpWSEngine(), fPoll(nullptr), fBuf() {} virtual UInt_t GetId() const; @@ -33,9 +33,9 @@ public: virtual void SendCharStar(const char *buf); - virtual Bool_t PreviewData(THttpCallArg *arg); + virtual Bool_t PreviewData(THttpCallArg &arg); - virtual void PostProcess(THttpCallArg *arg); + virtual void PostProcess(THttpCallArg &arg); ClassDef(THttpLongPollEngine, 0); }; diff --git a/net/http/inc/THttpWSEngine.h b/net/http/inc/THttpWSEngine.h index 105afb8561d..fb0eba135c9 100644 --- a/net/http/inc/THttpWSEngine.h +++ b/net/http/inc/THttpWSEngine.h @@ -12,17 +12,17 @@ #ifndef ROOT_THttpWSEngine #define ROOT_THttpWSEngine -#include "TNamed.h" +#include "Rtypes.h" class THttpCallArg; -class THttpWSEngine : public TNamed { +class THttpWSEngine { protected: - THttpWSEngine(const char *name, const char *title); + THttpWSEngine() = default; public: - virtual ~THttpWSEngine(); + virtual ~THttpWSEngine() {} virtual UInt_t GetId() const = 0; @@ -32,9 +32,9 @@ public: virtual void SendCharStar(const char *str); - virtual Bool_t PreviewData(THttpCallArg *) { return kFALSE; } + virtual Bool_t PreviewData(THttpCallArg &); - virtual void PostProcess(THttpCallArg *) {} + virtual void PostProcess(THttpCallArg &); ClassDef(THttpWSEngine, 0) // abstract class for working with WebSockets-like protocol }; diff --git a/net/http/inc/THttpWSHandler.h b/net/http/inc/THttpWSHandler.h index aa1fc2fb8cf..7a9c1d317ca 100644 --- a/net/http/inc/THttpWSHandler.h +++ b/net/http/inc/THttpWSHandler.h @@ -14,7 +14,7 @@ #include "TNamed.h" -#include "TList.h" +#include <vector> class THttpCallArg; class THttpWSEngine; @@ -30,9 +30,11 @@ private: Bool_t HandleWS(THttpCallArg *arg); + void RemoveEngine(THttpWSEngine *engine); + protected: - TList fEngines; ///<! list of of engines in use, cleaned automatically at the end + std::vector<THttpWSEngine *> fEngines; ///<! list of active WS engines (connections) THttpWSHandler(const char *name, const char *title); @@ -49,6 +51,11 @@ public: /// Return kTRUE if websocket with given ID exists Bool_t HasWS(UInt_t wsid) const { return FindEngine(wsid) != 0; } + /// Returns current number of websocket connections + Int_t GetNumWS() const { return fEngines.size(); } + + UInt_t GetWS(Int_t num = 0) const; + void CloseWS(UInt_t wsid); void SendWS(UInt_t wsid, const void *buf, int len); diff --git a/net/http/src/TCivetweb.cxx b/net/http/src/TCivetweb.cxx index 11d1cddd9d6..0165a649221 100644 --- a/net/http/src/TCivetweb.cxx +++ b/net/http/src/TCivetweb.cxx @@ -33,13 +33,11 @@ protected: struct mg_connection *fWSconn; public: - TCivetwebWSEngine(const char *name, const char *title, struct mg_connection *conn) - : THttpWSEngine(name, title), fWSconn(conn) + TCivetwebWSEngine(struct mg_connection *conn) + : THttpWSEngine(), fWSconn(conn) { } - virtual ~TCivetwebWSEngine() {} - virtual UInt_t GetId() const { return TString::Hash((void *)&fWSconn, sizeof(void *)); } virtual void ClearHandle() { fWSconn = nullptr; } @@ -96,7 +94,7 @@ void websocket_ready_handler(struct mg_connection *conn, void *) arg.SetMethod("WS_READY"); arg.SetWSId(TString::Hash((void *)&conn, sizeof(void *))); - arg.SetWSHandle(new TCivetwebWSEngine("websocket", "title", conn)); + arg.SetWSHandle(new TCivetwebWSEngine(conn)); serv->ExecuteHttp(&arg); } diff --git a/net/http/src/THttpCallArg.cxx b/net/http/src/THttpCallArg.cxx index c9657d2ccb9..71b5f26b563 100644 --- a/net/http/src/THttpCallArg.cxx +++ b/net/http/src/THttpCallArg.cxx @@ -13,7 +13,7 @@ #include <string.h> #include "RZip.h" -#include "TNamed.h" +#include "THttpWSEngine.h" ////////////////////////////////////////////////////////////////////////// // // @@ -31,7 +31,7 @@ ClassImp(THttpCallArg); THttpCallArg::THttpCallArg() : TObject(), fTopName(), fMethod(), fPathName(), fFileName(), fUserName(), fQuery(), fPostData(0), - fPostDataLength(0), fWSHandle(0), fWSId(0), fContentType(), fRequestHeader(), fHeader(), fContent(), fZipping(0), + fPostDataLength(0), fWSHandle(nullptr), fWSId(0), fContentType(), fRequestHeader(), fHeader(), fContent(), fZipping(0), fBinData(0), fBinDataLength(0), fNotifyFlag(kFALSE) { } @@ -43,17 +43,17 @@ THttpCallArg::~THttpCallArg() { if (fPostData) { free(fPostData); - fPostData = 0; + fPostData = nullptr; } if (fWSHandle) { delete fWSHandle; - fWSHandle = 0; + fWSHandle = nullptr; } if (fBinData) { free(fBinData); - fBinData = 0; + fBinData = nullptr; } } @@ -147,7 +147,7 @@ void THttpCallArg::SetPostData(void *data, Long_t length, Bool_t make_copy) { if (fPostData) { free(fPostData); - fPostData = 0; + fPostData = nullptr; fPostDataLength = 0; } @@ -160,7 +160,7 @@ void THttpCallArg::SetPostData(void *data, Long_t length, Bool_t make_copy) data = newdata; } - if (data != 0) + if (data) *(((char *)data) + length) = 0; fPostData = data; @@ -170,7 +170,7 @@ void THttpCallArg::SetPostData(void *data, Long_t length, Bool_t make_copy) //////////////////////////////////////////////////////////////////////////////// /// assign websocket handle with HTTP call -void THttpCallArg::SetWSHandle(TNamed *handle) +void THttpCallArg::SetWSHandle(THttpWSEngine *handle) { if (fWSHandle) delete fWSHandle; @@ -181,10 +181,10 @@ void THttpCallArg::SetWSHandle(TNamed *handle) /// takeout websocket handle with HTTP call /// can be done only once -TNamed *THttpCallArg::TakeWSHandle() +THttpWSEngine *THttpCallArg::TakeWSHandle() { - TNamed *res = fWSHandle; - fWSHandle = 0; + THttpWSEngine *res = fWSHandle; + fWSHandle = nullptr; return res; } diff --git a/net/http/src/THttpLongPollEngine.cxx b/net/http/src/THttpLongPollEngine.cxx index 730f41eb264..b0465123eb9 100644 --- a/net/http/src/THttpLongPollEngine.cxx +++ b/net/http/src/THttpLongPollEngine.cxx @@ -79,17 +79,17 @@ void THttpLongPollEngine::SendCharStar(const char *buf) /// function called in the user code before processing correspondent websocket data /// returns kTRUE when user should ignore such http request - it is for internal use -Bool_t THttpLongPollEngine::PreviewData(THttpCallArg *arg) +Bool_t THttpLongPollEngine::PreviewData(THttpCallArg &arg) { - if (!strstr(arg->GetQuery(), "&dummy")) { + if (!strstr(arg.GetQuery(), "&dummy")) { // this is normal request, deliver and process it as any other // put dummy content, it can be overwritten in the future - arg->SetContentType("text/plain"); - arg->SetContent(gLongPollNope); + arg.SetContentType("text/plain"); + arg.SetContent(gLongPollNope); return kFALSE; } - if (arg == fPoll) { + if (&arg == fPoll) { Error("PreviewData", "NEVER SHOULD HAPPEN"); gSystem->Exit(12); } @@ -104,12 +104,12 @@ Bool_t THttpLongPollEngine::PreviewData(THttpCallArg *arg) } if (fBuf.size() > 0) { - arg->SetContentType("text/plain"); - arg->SetContent(fBuf.front().c_str()); + arg.SetContentType("text/plain"); + arg.SetContent(fBuf.front().c_str()); fBuf.pop_front(); } else { - arg->SetPostponed(); - fPoll = arg; + arg.SetPostponed(); + fPoll = &arg; } // if arguments has "&dummy" string, user should not process it @@ -120,12 +120,12 @@ Bool_t THttpLongPollEngine::PreviewData(THttpCallArg *arg) /// Normally requests from client does not replied directly /// Therefore one can use it to send data with it -void THttpLongPollEngine::PostProcess(THttpCallArg *arg) +void THttpLongPollEngine::PostProcess(THttpCallArg &arg) { - if ((fBuf.size() > 0) && arg->IsContentType("text/plain") && - (arg->GetContentLength() == (Long_t)strlen(gLongPollNope)) && - (strcmp((const char *)arg->GetContent(), gLongPollNope) == 0)) { - arg->SetContent(fBuf.front().c_str()); + if ((fBuf.size() > 0) && arg.IsContentType("text/plain") && + (arg.GetContentLength() == (Long_t)strlen(gLongPollNope)) && + (strcmp((const char *)arg.GetContent(), gLongPollNope) == 0)) { + arg.SetContent(fBuf.front().c_str()); fBuf.pop_front(); } } diff --git a/net/http/src/THttpServer.cxx b/net/http/src/THttpServer.cxx index ae2b012fc32..4c325ff27b2 100644 --- a/net/http/src/THttpServer.cxx +++ b/net/http/src/THttpServer.cxx @@ -828,7 +828,7 @@ void THttpServer::ProcessRequest(THttpCallArg *arg) // if accepted, reply with connection id, which must be used in the following communications arg->SetMethod("WS_CONNECT"); - THttpLongPollEngine *handle = new THttpLongPollEngine("longpoll", arg->fPathName.Data()); + THttpLongPollEngine *handle = new THttpLongPollEngine(); arg->SetWSId(handle->GetId()); if (handler->HandleWS(arg)) { diff --git a/net/http/src/THttpWSEngine.cxx b/net/http/src/THttpWSEngine.cxx index 03c4b5cf21b..2da1115dd50 100644 --- a/net/http/src/THttpWSEngine.cxx +++ b/net/http/src/THttpWSEngine.cxx @@ -20,29 +20,29 @@ // // ////////////////////////////////////////////////////////////////////////// - -ClassImp(THttpWSEngine); - //////////////////////////////////////////////////////////////////////////////// -/// normal constructor +/// Envelope for sending string via the websocket -THttpWSEngine::THttpWSEngine(const char *name, const char *title) - : TNamed(name, title) +void THttpWSEngine::SendCharStar(const char *str) { + if (str) + Send(str, strlen(str)); } //////////////////////////////////////////////////////////////////////////////// -/// destructor +/// Method should be invoked before processing data coming from websocket +/// If method returns kTRUE, this is data is processed internally and +/// not dedicated for further usage -THttpWSEngine::~THttpWSEngine() +Bool_t THttpWSEngine::PreviewData(THttpCallArg &) { + return kFALSE; } //////////////////////////////////////////////////////////////////////////////// -/// Envelope for sending string via the websocket +/// Method invoked after user process data received via websocket +/// Normally request is no longer usable after that -void THttpWSEngine::SendCharStar(const char *str) +void THttpWSEngine::PostProcess(THttpCallArg &) { - if (str) Send(str, strlen(str)); } - diff --git a/net/http/src/THttpWSHandler.cxx b/net/http/src/THttpWSHandler.cxx index 8412cec943e..0703d1cea8d 100644 --- a/net/http/src/THttpWSHandler.cxx +++ b/net/http/src/THttpWSHandler.cxx @@ -14,7 +14,6 @@ #include "THttpWSEngine.h" #include "THttpCallArg.h" - ///////////////////////////////////////////////////////////////////////// /// /// THttpWSHandler @@ -65,62 +64,94 @@ ClassImp(THttpWSHandler); //////////////////////////////////////////////////////////////////////////////// /// normal constructor -THttpWSHandler::THttpWSHandler(const char *name, const char *title) : - TNamed(name, title), fEngines() +THttpWSHandler::THttpWSHandler(const char *name, const char *title) : TNamed(name, title), fEngines() { } +//////////////////////////////////////////////////////////////////////////////// +/// destructor +/// Delete all websockets handles + THttpWSHandler::~THttpWSHandler() { - TIter iter(&fEngines); - THttpWSEngine *engine = 0; - - while ((engine = (THttpWSEngine *)iter()) != 0) + for (auto iter = fEngines.begin(); iter != fEngines.end(); iter++) { + THttpWSEngine *engine = *iter; engine->ClearHandle(); + delete engine; + } - fEngines.Delete(); + fEngines.clear(); } +//////////////////////////////////////////////////////////////////////////////// +/// Return websocket id with given sequential number +/// Number of websockets return with GetNumWS() method -THttpWSEngine *THttpWSHandler::FindEngine(UInt_t id) const +UInt_t THttpWSHandler::GetWS(Int_t num) const { - TIter iter(&fEngines); - THttpWSEngine *engine = 0; + auto iter = fEngines.begin() + num; + return (*iter)->GetId(); +} - while ((engine = (THttpWSEngine *)iter()) != 0) { - if (engine->GetId() == id) return engine; - } +//////////////////////////////////////////////////////////////////////////////// +/// Find websocket connection handle with given id + +THttpWSEngine *THttpWSHandler::FindEngine(UInt_t wsid) const +{ + for (auto iter = fEngines.begin(); iter != fEngines.end(); iter++) + if ((*iter)->GetId() == wsid) + return *iter; - return 0; + return nullptr; } -Bool_t THttpWSHandler::HandleWS(THttpCallArg *arg) +//////////////////////////////////////////////////////////////////////////////// +/// Remove and destroy WS connection + +void THttpWSHandler::RemoveEngine(THttpWSEngine *engine) { - if (!arg->GetWSId()) return ProcessWS(arg); + for (auto iter = fEngines.begin(); iter != fEngines.end(); iter++) + if (*iter == engine) { + fEngines.erase(iter); + break; + } + + delete engine; +} - THttpWSEngine* engine = FindEngine(arg->GetWSId()); +//////////////////////////////////////////////////////////////////////////////// +/// Process request to websocket +/// Different kind of requests coded into THttpCallArg::Method +/// "WS_CONNECT" - connection request +/// "WS_READY" - connection ready +/// "WS_CLOSE" - connection closed +/// All other are normal data, which are delivered to users - if (arg->IsMethod("WS_CONNECT")) { - // accept all requests, in future one could limit number of connections +Bool_t THttpWSHandler::HandleWS(THttpCallArg *arg) +{ + if (!arg->GetWSId()) return ProcessWS(arg); - } + + // normally here one accept or reject connection requests + if (arg->IsMethod("WS_CONNECT")) + return ProcessWS(arg); + + THttpWSEngine *engine = FindEngine(arg->GetWSId()); if (arg->IsMethod("WS_READY")) { if (engine) { - Error("HandleWS","WS engine with similar id exists %u\n", arg->GetWSId()); - fEngines.Remove(engine); - delete engine; + Error("HandleWS", "WS engine with similar id exists %u\n", arg->GetWSId()); + RemoveEngine(engine); } - THttpWSEngine *wshandle = dynamic_cast<THttpWSEngine *>(arg->TakeWSHandle()); + engine = arg->TakeWSHandle(); - fEngines.Add(wshandle); + fEngines.push_back(engine); if (!ProcessWS(arg)) { // if connection refused, remove engine again - fEngines.Remove(wshandle); - delete wshandle; + RemoveEngine(engine); return kFALSE; } @@ -132,42 +163,52 @@ Bool_t THttpWSHandler::HandleWS(THttpCallArg *arg) if (engine) { engine->ClearHandle(); - fEngines.Remove(engine); - delete engine; + RemoveEngine(engine); } return ProcessWS(arg); } - if (engine && engine->PreviewData(arg)) return kTRUE; + if (engine && engine->PreviewData(*arg)) + return kTRUE; Bool_t res = ProcessWS(arg); - if (engine) engine->PostProcess(arg); + if (engine) + engine->PostProcess(*arg); return res; } +//////////////////////////////////////////////////////////////////////////////// +/// Close connection with given websocket id + void THttpWSHandler::CloseWS(UInt_t wsid) { - THttpWSEngine* engine = FindEngine(wsid); + THttpWSEngine *engine = FindEngine(wsid); - if (engine) { - fEngines.Remove(engine); - delete engine; - } + if (engine) + RemoveEngine(engine); } +//////////////////////////////////////////////////////////////////////////////// +/// Send binary data via given websocket id + void THttpWSHandler::SendWS(UInt_t wsid, const void *buf, int len) { - THttpWSEngine* engine = FindEngine(wsid); + THttpWSEngine *engine = FindEngine(wsid); - if (engine) engine->Send(buf, len); + if (engine) + engine->Send(buf, len); } +//////////////////////////////////////////////////////////////////////////////// +/// Send string via given websocket id + void THttpWSHandler::SendCharStarWS(UInt_t wsid, const char *str) { - THttpWSEngine* engine = FindEngine(wsid); + THttpWSEngine *engine = FindEngine(wsid); - if (engine) engine->SendCharStar(str); + if (engine) + engine->SendCharStar(str); } -- GitLab