diff --git a/net/http/test/CMakeLists.txt b/net/http/test/CMakeLists.txt index 62d10242e72b2..ea6467e650b2f 100644 --- a/net/http/test/CMakeLists.txt +++ b/net/http/test/CMakeLists.txt @@ -5,14 +5,37 @@ # For the list of contributors see $ROOTSYS/README/CREDITS. ############################################################################ -# CMakeLists.txt file for building ROOT net/http package +# CMakeLists.txt file for building ROOT net/http/test package # @author Sergey Linev, GSI ############################################################################ +# http server does not work on Windows without extra firewall configuration +if(WIN32) + return() +endif() + +# curl is required for tests find_program(CURL_EXECUTABLE curl) -# http server does not work on default Windows configuration -# so skip testing there -if(CURL_EXECUTABLE AND NOT WIN32) - ROOT_ADD_GTEST(testHttpServer test_server.cxx LIBRARIES RHTTP RHTTPSniff Hist) +if(NOT CURL_EXECUTABLE) + return() +endif() + +ROOT_ADD_GTEST(testHttpServer test_server.cxx LIBRARIES RHTTP RHTTPSniff Hist) + +execute_process( + COMMAND "${CURL_EXECUTABLE}" --version + OUTPUT_VARIABLE CURL_CLI_OUTPUT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +# only newer curl support websockets - so test version +# curl introduce support from 8.11 but it works reliably only with 8.18 +if(CURL_CLI_OUTPUT MATCHES "curl ([0-9]+\\.[0-9]+\\.[0-9]+)") + set(CURL_CLI_VERSION "${CMAKE_MATCH_1}") + message(STATUS "Find curl ${CURL_CLI_VERSION} for http server tests") + if(CURL_CLI_VERSION VERSION_GREATER_EQUAL "8.18.0") + ROOT_ADD_GTEST(testWebSocket test_websocket.cxx LIBRARIES RHTTP Hist) + endif() endif() diff --git a/net/http/test/test_server.cxx b/net/http/test/test_server.cxx index daf88ddb60803..a4a5404462124 100644 --- a/net/http/test/test_server.cxx +++ b/net/http/test/test_server.cxx @@ -15,6 +15,7 @@ Int_t httpport = 0; TString server_url; + // main http server std::string execute_request(const char *url, const char *post = nullptr) { @@ -25,10 +26,12 @@ std::string execute_request(const char *url, const char *post = nullptr) pname = TString::Format("http_server_%d.post", httpport); std::ofstream f(pname.Data()); f << post; + } + + if (post) exec = TString::Format("curl -sS -X POST '%s%s' --data-binary @%s -o %s", server_url.Data(), url, pname.Data(), fname.Data()); - } else { + else exec = TString::Format("curl -sS '%s%s' -o %s", server_url.Data(), url, fname.Data()); - } printf("Execute %s\n", exec.Data()); @@ -56,7 +59,7 @@ TEST(THttpServer, main) for(int ntry = 0; ntry < 100; ++ntry) { Int_t port = (Int_t) (25000 + gRandom->Rndm() * 1000); // only two threads, bind to loopback address only - TString arg = TString::Format("http:%d?loopback&thrds=2", port); + TString arg = TString::Format("http:%d?loopback&thrds=3", port); if (serv.CreateEngine(arg)) { httpport = port; break; diff --git a/net/http/test/test_websocket.cxx b/net/http/test/test_websocket.cxx new file mode 100644 index 0000000000000..0b34e9a473406 --- /dev/null +++ b/net/http/test/test_websocket.cxx @@ -0,0 +1,140 @@ +#include "gtest/gtest.h" + +#include +#include + +#include "THttpServer.h" +#include "THttpWSHandler.h" +#include "TROOT.h" + +#include "TSystem.h" +#include "TRandom.h" + +#include "ROOT/TestSupport.hxx" + +Int_t httpport = 0; +TString server_url; + +class TUserHandler : public THttpWSHandler { + public: + UInt_t fWSId{0}; + Int_t fServCnt{0}; + + TUserHandler(const char *name = nullptr, const char *title = nullptr) : THttpWSHandler(name, title) {} + + // load custom HTML page when open correspondent address + TString GetDefaultPageContent() override { return "dummy_page_content"; } + + // ignore threads safety - allow multi-threaded use of handler + Bool_t AllowMTProcess() const override { return kFALSE; } + + Bool_t ProcessWS(THttpCallArg *arg) override + { + if (!arg || (arg->GetWSId() == 0)) + return kTRUE; + + if (arg->IsMethod("WS_CONNECT")) { + // accept only if connection not established + return fWSId == 0; + } + + if (arg->IsMethod("WS_READY")) { + fWSId = arg->GetWSId(); + return kTRUE; + } + + if (arg->IsMethod("WS_CLOSE")) { + fWSId = 0; + return kTRUE; + } + + if (arg->IsMethod("WS_DATA")) { + TString str; + str.Append((const char *)arg->GetPostData(), arg->GetPostDataLength()); + if ((str == "DataRequest") && (arg->GetWSId() == fWSId)) + SendCharStarWS(arg->GetWSId(), "DataResponse"); + else + SendCharStarWS(arg->GetWSId(), "DataRejected"); + return kTRUE; + } + + return kFALSE; + } +}; + + +// main http server +std::string execute_request(const char *url, const char *post = nullptr, Bool_t ws = kFALSE) +{ + TString fname = TString::Format("http_server_%d.output", httpport), + pname, exec; + + if (post) { + pname = TString::Format("http_server_%d.post", httpport); + std::ofstream f(pname.Data()); + f << post; + } + + if (post && ws) + exec = TString::Format("curl --no-progress-meter --max-time 10 -T . -N ws://localhost:%d%s < %s > %s", httpport, url, pname.Data(), fname.Data()); + else if (post) + exec = TString::Format("curl --no-progress-meter -X POST '%s%s' --data-binary @%s -o %s", server_url.Data(), url, pname.Data(), fname.Data()); + else + exec = TString::Format("curl --no-progress-meter '%s%s' -o %s", server_url.Data(), url, fname.Data()); + + printf("Execute %s\n", exec.Data()); + + std::string res; + + // websocket ends with timeout - ignore failure for it + if ((gSystem->Exec(exec) != 0) && !ws) + res = ""; + else + res = THttpServer::ReadFileContent(fname.Data()); + + gSystem->Unlink(fname); + if (!pname.IsNull()) + gSystem->Unlink(pname); + + return res; +} + +// main http server +TEST(THttpServer, main) +{ + THttpServer serv(""); + + gRandom->SetSeed(0); + + for(int ntry = 0; ntry < 100; ++ntry) { + Int_t port = (Int_t) (25000 + gRandom->Rndm() * 1000); + // only two threads, bind to loopback address only + TString arg = TString::Format("http:%d?loopback&thrds=3", port); + if (serv.CreateEngine(arg)) { + httpport = port; + break; + } + } + + EXPECT_NE(httpport, 0); + + if (!httpport) + return; + + server_url = TString::Format("http:/localhost:%d", httpport); + + TUserHandler handler("ws", "Test WebSocket handler"); + + serv.Register("/", &handler); + + // let process requests in separate thread + serv.CreateServerThread(); + + // test reply on html page request + std::string res = execute_request("/ws/index.htm"); + EXPECT_EQ(res, "dummy_page_content") << "default html page for webcosket handler"; + + // test reply on websocket request + res = execute_request("/ws/root.websocket", "DataRequest", kTRUE); + EXPECT_EQ(res, "DataResponse") << "check data after websocket communication"; +}