Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions net/http/src/THttpServer.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ More information: https://root.cern/root/htmldoc/guides/HttpServer/HttpServer.ht
/// at once, separating them with semicolon (";"). Following engines are supported:
///
/// http - TCivetweb, civetweb-based implementation of http protocol
/// socket - TCivetweb bound to unix socket
/// fastcgi - TFastCgi, special protocol for communicating with web servers
///
/// For each created engine one should provide socket port number like "http:8080" or "fastcgi:9000".
Expand Down Expand Up @@ -411,6 +412,8 @@ void THttpServer::SetDrawPage(const std::string &filename)
/// serv->CreateEngine("http:8080");
/// serv->CreateEngine("civetweb:8080");
/// serv->CreateEngine(":8080");
/// // creates civetweb web server bound with unix socket
/// serv->CreateEngine("socket:/home/user/server.socket?socket_mode=0700");
/// // creates fastcgi server with port 9000
/// serv->CreateEngine("fastcgi:9000");
///
Expand Down
5 changes: 4 additions & 1 deletion net/http/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ execute_process(
# 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")
message(STATUS "Found curl ${CURL_CLI_VERSION} for http server tests")
if(CURL_CLI_VERSION VERSION_GREATER_EQUAL "7.50.0")
ROOT_ADD_GTEST(testUnixSocket test_xsocket.cxx LIBRARIES RHTTP RHTTPSniff Hist)
endif()
if(CURL_CLI_VERSION VERSION_GREATER_EQUAL "8.18.0")
ROOT_ADD_GTEST(testWebSocket test_websocket.cxx LIBRARIES RHTTP Hist)
endif()
Expand Down
125 changes: 5 additions & 120 deletions net/http/test/test_server.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,7 @@

#include "ROOT/TestSupport.hxx"

Int_t httpport = 0;
TString server_url;


// main http server
std::string execute_request(const char *url, const char *post = nullptr)
{
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)
exec = TString::Format("curl -sS -X POST '%s%s' --data-binary @%s -o %s", server_url.Data(), url, pname.Data(), fname.Data());
else
exec = TString::Format("curl -sS '%s%s' -o %s", server_url.Data(), url, fname.Data());

printf("Execute %s\n", exec.Data());

std::string res;

if (gSystem->Exec(exec) != 0)
res = "<fail>";
else
res = THttpServer::ReadFileContent(fname.Data());

gSystem->Unlink(fname);
if (!pname.IsNull())
gSystem->Unlink(pname);

return res;
}
#include "./test_suite.cxx"

// main http server
TEST(THttpServer, main)
Expand All @@ -56,6 +21,8 @@ TEST(THttpServer, main)

gRandom->SetSeed(0);

Int_t httpport = 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
Expand All @@ -71,90 +38,8 @@ TEST(THttpServer, main)
if (!httpport)
return;

server_hash = httpport;
server_url = TString::Format("http:/localhost:%d", httpport);

TNamed obj1("obj1", "title1");
TNamed obj2("obj2", "title2");

serv.Register("/", &obj1);
serv.Register("/", &obj2);

// let process requests in separate thread
serv.CreateServerThread();

// check JSON representation for the object
std::string res = execute_request("/obj1/root.json");
EXPECT_EQ(res, "{\n"
" \"_typename\" : \"TNamed\",\n"
" \"fUniqueID\" : 0,\n"
" \"fBits\" : 8,\n"
" \"fName\" : \"obj1\",\n"
" \"fTitle\" : \"title1\"\n"
"}") << "result of root.json request";

// check XML representation for the object
res = execute_request("/obj1/root.xml");
EXPECT_EQ(res, "<Object class=\"TNamed\">\n"
" <TNamed version=\"1\">\n"
" <TObject fUniqueID=\"0\" fBits=\"8\"/>\n"
" <fName str=\"obj1\"/>\n"
" <fTitle str=\"title1\"/>\n"
" </TNamed>\n"
"</Object>\n") << "result of root.xml request";


// check BINARY representation for the object
res = execute_request("/obj1/root.bin");
// keep minimal margin for binary format change
EXPECT_NEAR(res.length(), 28, 4) << "size of root.bin request";

// check ROOT file creation
res = execute_request("/obj1/file.root");
// TODO: anlyze why so big size for small object
EXPECT_NEAR(res.length(), 1024, 100) << "size of file.root request";

// check item request hierarchy request
res = execute_request("/obj1/item.json");

EXPECT_EQ(res, "{\n"
" \"_name\" : \"obj1\",\n"
" \"_root_version\" : " + std::to_string(gROOT->GetVersionCode()) + ",\n"
" \"_kind\" : \"ROOT.TNamed\",\n"
" \"_title\" : \"title1\"\n"
"}") << "result of item.json request";


// check multi request to several objects
res = execute_request("/multi.json?number=2", "/obj1/root.json\n/obj2/root.json\n");
EXPECT_EQ(res, "[{\n"
" \"_typename\" : \"TNamed\",\n"
" \"fUniqueID\" : 0,\n"
" \"fBits\" : 8,\n"
" \"fName\" : \"obj1\",\n"
" \"fTitle\" : \"title1\"\n"
"}, {\n"
" \"_typename\" : \"TNamed\",\n"
" \"fUniqueID\" : 0,\n"
" \"fBits\" : 8,\n"
" \"fName\" : \"obj2\",\n"
" \"fTitle\" : \"title2\"\n"
"}]") << "result of multi.json request";


// by default methods execution is not allowed
res = execute_request("/obj1/exe.json?method=GetTitle");
EXPECT_EQ(res, "") << "exe.json should be empty in readonly";


// disable readonly to get extra functionality
serv.SetReadOnly(kFALSE);

// only now one can execute method
res = execute_request("/obj1/exe.json?method=GetTitle");
EXPECT_EQ(res, "\"title1\"") << "result of exe.json with object title";

res = execute_request("/obj1/exe.json?method=SetTitle&title=NewTitle");
EXPECT_EQ(res, "null") << "returns null when executes void method";
EXPECT_EQ(std::string("NewTitle"), obj1.GetTitle()) << "title must match with submitted value";
obj1.SetTitle("title1");
test_suite(serv);
}
125 changes: 125 additions & 0 deletions net/http/test/test_suite.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
Int_t server_hash = 0;
TString server_url;
TString unix_socket;

// submit requests to server
std::string execute_request(const char *url, const char *post = nullptr)
{
TString fname = TString::Format("http_server_%d.output", server_hash),
pname, exec;

if (post) {
pname = TString::Format("http_server_%d.post", server_hash);
std::ofstream f(pname.Data());
f << post;
}

if (post)
exec = TString::Format("curl -sS -X POST %s '%s%s' --data-binary @%s -o %s", unix_socket.Data(), server_url.Data(), url, pname.Data(), fname.Data());
else
exec = TString::Format("curl -sS %s '%s%s' -o %s", unix_socket.Data(), server_url.Data(), url, fname.Data());

printf("Execute %s\n", exec.Data());

std::string res;

if (gSystem->Exec(exec) != 0)
res = "<fail>";
else
res = THttpServer::ReadFileContent(fname.Data());

gSystem->Unlink(fname);
if (!pname.IsNull())
gSystem->Unlink(pname);

return res;
}


void test_suite(THttpServer &serv)
{
// let process requests in separate thread
serv.CreateServerThread();

TNamed obj1("obj1", "title1");
TNamed obj2("obj2", "title2");

serv.Register("/", &obj1);
serv.Register("/", &obj2);

// check JSON representation for the object
std::string res = execute_request("/obj1/root.json");
EXPECT_EQ(res, "{\n"
" \"_typename\" : \"TNamed\",\n"
" \"fUniqueID\" : 0,\n"
" \"fBits\" : 8,\n"
" \"fName\" : \"obj1\",\n"
" \"fTitle\" : \"title1\"\n"
"}") << "result of root.json request";

// check XML representation for the object
res = execute_request("/obj1/root.xml");
EXPECT_EQ(res, "<Object class=\"TNamed\">\n"
" <TNamed version=\"1\">\n"
" <TObject fUniqueID=\"0\" fBits=\"8\"/>\n"
" <fName str=\"obj1\"/>\n"
" <fTitle str=\"title1\"/>\n"
" </TNamed>\n"
"</Object>\n") << "result of root.xml request";


// check BINARY representation for the object
res = execute_request("/obj1/root.bin");
// keep minimal margin for binary format change
EXPECT_NEAR(res.length(), 28, 4) << "size of root.bin request";

// check ROOT file creation
res = execute_request("/obj1/file.root");
// TODO: anlyze why so big size for small object
EXPECT_NEAR(res.length(), 1024, 100) << "size of file.root request";

// check item request hierarchy request
res = execute_request("/obj1/item.json");

EXPECT_EQ(res, "{\n"
" \"_name\" : \"obj1\",\n"
" \"_root_version\" : " + std::to_string(gROOT->GetVersionCode()) + ",\n"
" \"_kind\" : \"ROOT.TNamed\",\n"
" \"_title\" : \"title1\"\n"
"}") << "result of item.json request";


// check multi request to several objects
res = execute_request("/multi.json?number=2", "/obj1/root.json\n/obj2/root.json\n");
EXPECT_EQ(res, "[{\n"
" \"_typename\" : \"TNamed\",\n"
" \"fUniqueID\" : 0,\n"
" \"fBits\" : 8,\n"
" \"fName\" : \"obj1\",\n"
" \"fTitle\" : \"title1\"\n"
"}, {\n"
" \"_typename\" : \"TNamed\",\n"
" \"fUniqueID\" : 0,\n"
" \"fBits\" : 8,\n"
" \"fName\" : \"obj2\",\n"
" \"fTitle\" : \"title2\"\n"
"}]") << "result of multi.json request";


// by default methods execution is not allowed
res = execute_request("/obj1/exe.json?method=GetTitle");
EXPECT_EQ(res, "") << "exe.json should be empty in readonly";


// disable readonly to get extra functionality
serv.SetReadOnly(kFALSE);

// only now one can execute method
res = execute_request("/obj1/exe.json?method=GetTitle");
EXPECT_EQ(res, "\"title1\"") << "result of exe.json with object title";

res = execute_request("/obj1/exe.json?method=SetTitle&title=NewTitle");
EXPECT_EQ(res, "null") << "returns null when executes void method";
EXPECT_EQ(std::string("NewTitle"), obj1.GetTitle()) << "title must match with submitted value";
obj1.SetTitle("title1");
}
54 changes: 54 additions & 0 deletions net/http/test/test_xsocket.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include "gtest/gtest.h"

#include <string>
#include <fstream>

#include "THttpServer.h"
#include "TROOT.h"

#include "TSystem.h"
#include "TNamed.h"
#include "TRandom.h"

#include "ROOT/TestSupport.hxx"


#include "./test_suite.cxx"

// main http server
TEST(THttpServer, main)
{
THttpServer serv("");

TString fname;
Bool_t create = kFALSE;

for (Int_t ntry = 0; ntry < 10; ++ntry) {
fname = "root_test_";
auto f = gSystem->TempFileName(fname, "/tmp", ".socket");
if (!f)
continue;
fclose(f);

EXPECT_EQ(fname[0], '/') << "first symbol in temporary file must be slash";
create = serv.CreateEngine("socket:" + fname + "?socket_mode=0700&thrd=2");

if (create)
break;

gSystem->Unlink(fname);
}

EXPECT_EQ(create, kTRUE) << "create unix socket";

if (!create)
return;

server_hash = fname.Hash(); // dummy number for file suffix
unix_socket = "--unix-socket " + fname; // curl argument
server_url = "http://localhost"; // dummy host name for curl

test_suite(serv);

gSystem->Unlink(fname); // cleanup socket file
}
Loading