Move the json check to RPC executor (#676)

Fixes #681
This commit is contained in:
cyan317
2023-06-09 11:35:15 +01:00
committed by GitHub
parent 435db339df
commit b873af2d43
6 changed files with 147 additions and 166 deletions

View File

@@ -58,8 +58,21 @@ public:
* @param connection The connection * @param connection The connection
*/ */
void void
operator()(boost::json::object&& req, std::shared_ptr<Server::ConnectionBase> const& connection) operator()(std::string const& reqStr, std::shared_ptr<Server::ConnectionBase> const& connection)
{ {
auto req = boost::json::object{};
try
{
req = boost::json::parse(reqStr).as_object();
}
catch (boost::exception const& _)
{
connection->send(
boost::json::serialize(RPC::makeError(RPC::RippledError::rpcBAD_SYNTAX)),
boost::beast::http::status::ok);
return;
}
perfLog_.debug() << connection->tag() << "Adding to work queue"; perfLog_.debug() << connection->tag() << "Adding to work queue";
// specially handle for http connections // specially handle for http connections
if (!connection->upgraded) if (!connection->upgraded)

View File

@@ -207,21 +207,9 @@ public:
log_.info() << tag() << "Received request from ip = " << clientIp << " - posting to WorkQueue"; log_.info() << tag() << "Received request from ip = " << clientIp << " - posting to WorkQueue";
auto request = boost::json::object{};
try try
{ {
request = boost::json::parse(req_.body()).as_object(); (*handler_)(req_.body(), derived().shared_from_this());
}
catch (boost::exception const& e)
{
return sender_(httpResponse(
http::status::ok,
"application/json",
boost::json::serialize(RPC::makeError(RPC::RippledError::rpcBAD_SYNTAX))));
}
try
{
(*handler_)(std::move(request), derived().shared_from_this());
} }
catch (std::exception const& e) catch (std::exception const& e)
{ {

View File

@@ -221,12 +221,19 @@ public:
perfLog_.info() << tag() << "Received request from ip = " << this->clientIp; perfLog_.info() << tag() << "Received request from ip = " << this->clientIp;
auto sendError = [this](auto error, boost::json::value const& request) { auto sendError = [this](auto error, std::string&& requestStr) {
auto e = RPC::makeError(error); auto e = RPC::makeError(error);
try
if (request.is_object() && request.as_object().contains("id")) {
e["id"] = request.as_object().at("id"); auto request = boost::json::parse(requestStr);
e["request"] = request; if (request.is_object() && request.as_object().contains("id"))
e["id"] = request.as_object().at("id");
e["request"] = std::move(request);
}
catch (std::exception&)
{
e["request"] = std::move(requestStr);
}
auto responseStr = boost::json::serialize(e); auto responseStr = boost::json::serialize(e);
log_.trace() << responseStr; log_.trace() << responseStr;
@@ -236,37 +243,21 @@ public:
std::string msg{static_cast<char const*>(buffer_.data().data()), buffer_.size()}; std::string msg{static_cast<char const*>(buffer_.data().data()), buffer_.size()};
boost::json::value raw = [](std::string&& msg) {
try
{
return boost::json::parse(msg);
}
catch (std::exception&)
{
return boost::json::value{msg};
}
}(std::move(msg));
// dosGuard served request++ and check ip address // dosGuard served request++ and check ip address
if (!dosGuard_.get().request(clientIp)) if (!dosGuard_.get().request(clientIp))
{ {
sendError(RPC::RippledError::rpcSLOW_DOWN, raw); sendError(RPC::RippledError::rpcSLOW_DOWN, std::move(msg));
}
else if (!raw.is_object())
{
sendError(RPC::RippledError::rpcBAD_SYNTAX, raw);
} }
else else
{ {
auto request = raw.as_object();
try try
{ {
(*handler_)(std::move(request), shared_from_this()); (*handler_)(msg, shared_from_this());
} }
catch (std::exception const& e) catch (std::exception const& e)
{ {
perfLog_.error() << tag() << "Caught exception : " << e.what(); perfLog_.error() << tag() << "Caught exception : " << e.what();
sendError(RPC::RippledError::rpcINTERNAL, raw); sendError(RPC::RippledError::rpcINTERNAL, std::move(msg));
} }
} }

View File

@@ -22,7 +22,6 @@
#include <webserver/interface/ConnectionBase.h> #include <webserver/interface/ConnectionBase.h>
#include <boost/beast.hpp> #include <boost/beast.hpp>
#include <boost/json.hpp>
#include <memory> #include <memory>
@@ -33,9 +32,9 @@ namespace Server {
*/ */
// clang-format off // clang-format off
template <typename T> template <typename T>
concept ServerHandler = requires(T handler, boost::json::object&& req, std::shared_ptr<ConnectionBase> const& ws, boost::beast::error_code ec) { concept ServerHandler = requires(T handler, std::string const& req, std::shared_ptr<ConnectionBase> const& ws, boost::beast::error_code ec) {
// the callback when server receives a request // the callback when server receives a request
{ handler(std::move(req), ws) }; { handler(req, ws) };
// the callback when there is an error // the callback when there is an error
{ handler(ec, ws) }; { handler(ec, ws) };
}; };

View File

@@ -84,11 +84,10 @@ protected:
TEST_F(WebRPCExecutorTest, HTTPDefaultPath) TEST_F(WebRPCExecutorTest, HTTPDefaultPath)
{ {
auto request = boost::json::parse(R"({ static auto constexpr request = R"({
"method": "server_info", "method": "server_info",
"params": [{}] "params": [{}]
})") })";
.as_object();
mockBackendPtr->updateRange(MINSEQ); // min mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max mockBackendPtr->updateRange(MAXSEQ); // max
@@ -119,11 +118,10 @@ TEST_F(WebRPCExecutorTest, HTTPDefaultPath)
TEST_F(WebRPCExecutorTest, WsNormalPath) TEST_F(WebRPCExecutorTest, WsNormalPath)
{ {
session->upgraded = true; session->upgraded = true;
auto request = boost::json::parse(R"({ static auto constexpr request = R"({
"command": "server_info", "command": "server_info",
"id": 99 "id": 99
})") })";
.as_object();
mockBackendPtr->updateRange(MINSEQ); // min mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max mockBackendPtr->updateRange(MAXSEQ); // max
@@ -155,11 +153,10 @@ TEST_F(WebRPCExecutorTest, WsNormalPath)
TEST_F(WebRPCExecutorTest, HTTPForwardedPath) TEST_F(WebRPCExecutorTest, HTTPForwardedPath)
{ {
auto request = boost::json::parse(R"({ static auto constexpr request = R"({
"method": "server_info", "method": "server_info",
"params": [{}] "params": [{}]
})") })";
.as_object();
mockBackendPtr->updateRange(MINSEQ); // min mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max mockBackendPtr->updateRange(MAXSEQ); // max
@@ -197,11 +194,10 @@ TEST_F(WebRPCExecutorTest, HTTPForwardedPath)
TEST_F(WebRPCExecutorTest, WsForwardedPath) TEST_F(WebRPCExecutorTest, WsForwardedPath)
{ {
session->upgraded = true; session->upgraded = true;
auto request = boost::json::parse(R"({ static auto constexpr request = R"({
"command": "server_info", "command": "server_info",
"id": 99 "id": 99
})") })";
.as_object();
mockBackendPtr->updateRange(MINSEQ); // min mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max mockBackendPtr->updateRange(MAXSEQ); // max
@@ -275,14 +271,13 @@ TEST_F(WebRPCExecutorTest, HTTPErrorPath)
} }
] ]
})"; })";
auto request = boost::json::parse(requestJSON).as_object();
EXPECT_CALL(*rpcEngine, buildResponse(testing::_)) EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
.WillOnce(testing::Return(RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"})); .WillOnce(testing::Return(RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"}));
EXPECT_CALL(*rpcEngine, notifyErrored("ledger")).Times(1); EXPECT_CALL(*rpcEngine, notifyErrored("ledger")).Times(1);
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45)); EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
(*rpcExecutor)(std::move(request), session); (*rpcExecutor)(std::move(requestJSON), session);
std::this_thread::sleep_for(200ms); std::this_thread::sleep_for(200ms);
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response)); EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
} }
@@ -318,25 +313,23 @@ TEST_F(WebRPCExecutorTest, WsErrorPath)
"ledger_index": "xx", "ledger_index": "xx",
"id": "123" "id": "123"
})"; })";
auto request = boost::json::parse(requestJSON).as_object();
EXPECT_CALL(*rpcEngine, buildResponse(testing::_)) EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
.WillOnce(testing::Return(RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"})); .WillOnce(testing::Return(RPC::Status{RPC::RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"}));
EXPECT_CALL(*rpcEngine, notifyErrored("ledger")).Times(1); EXPECT_CALL(*rpcEngine, notifyErrored("ledger")).Times(1);
EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45)); EXPECT_CALL(*etl, lastCloseAgeSeconds()).WillOnce(testing::Return(45));
(*rpcExecutor)(std::move(request), session); (*rpcExecutor)(std::move(requestJSON), session);
std::this_thread::sleep_for(200ms); std::this_thread::sleep_for(200ms);
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response)); EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
} }
TEST_F(WebRPCExecutorTest, HTTPNotReady) TEST_F(WebRPCExecutorTest, HTTPNotReady)
{ {
auto request = boost::json::parse(R"({ static auto constexpr request = R"({
"method": "server_info", "method": "server_info",
"params": [{}] "params": [{}]
})") })";
.as_object();
static auto constexpr response = R"({ static auto constexpr response = R"({
"result":{ "result":{
@@ -365,11 +358,10 @@ TEST_F(WebRPCExecutorTest, WsNotReady)
{ {
session->upgraded = true; session->upgraded = true;
auto request = boost::json::parse(R"({ static auto constexpr request = R"({
"command": "server_info", "command": "server_info",
"id": 99 "id": 99
})") })";
.as_object();
static auto constexpr response = R"({ static auto constexpr response = R"({
"error":"notReady", "error":"notReady",
@@ -391,10 +383,7 @@ TEST_F(WebRPCExecutorTest, WsNotReady)
TEST_F(WebRPCExecutorTest, HTTPBadSyntax) TEST_F(WebRPCExecutorTest, HTTPBadSyntax)
{ {
auto request = boost::json::parse(R"({ static auto constexpr request = R"({"method2": "server_info"})";
"method2": "server_info"
})")
.as_object();
mockBackendPtr->updateRange(MINSEQ); // min mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max mockBackendPtr->updateRange(MAXSEQ); // max
@@ -420,10 +409,7 @@ TEST_F(WebRPCExecutorTest, HTTPBadSyntax)
TEST_F(WebRPCExecutorTest, HTTPBadSyntaxWhenRequestSubscribe) TEST_F(WebRPCExecutorTest, HTTPBadSyntaxWhenRequestSubscribe)
{ {
auto request = boost::json::parse(R"({ static auto constexpr request = R"({"method": "subscribe"})";
"method": "subscribe"
})")
.as_object();
mockBackendPtr->updateRange(MINSEQ); // min mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max mockBackendPtr->updateRange(MAXSEQ); // max
@@ -450,12 +436,10 @@ TEST_F(WebRPCExecutorTest, HTTPBadSyntaxWhenRequestSubscribe)
TEST_F(WebRPCExecutorTest, WsBadSyntax) TEST_F(WebRPCExecutorTest, WsBadSyntax)
{ {
session->upgraded = true; session->upgraded = true;
auto request = boost::json::parse(R"( static auto constexpr request = R"({
{ "command2": "server_info",
"command2": "server_info", "id": 99
"id": 99 })";
})")
.as_object();
mockBackendPtr->updateRange(MINSEQ); // min mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max mockBackendPtr->updateRange(MAXSEQ); // max
@@ -509,10 +493,9 @@ TEST_F(WebRPCExecutorTest, HTTPInternalError)
} }
] ]
})"; })";
auto request = boost::json::parse(requestJSON).as_object();
EXPECT_CALL(*rpcEngine, buildResponse(testing::_)).Times(1).WillOnce(testing::Throw(std::runtime_error("MyError"))); EXPECT_CALL(*rpcEngine, buildResponse(testing::_)).Times(1).WillOnce(testing::Throw(std::runtime_error("MyError")));
(*rpcExecutor)(std::move(request), session); (*rpcExecutor)(std::move(requestJSON), session);
std::this_thread::sleep_for(200ms); std::this_thread::sleep_for(200ms);
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response)); EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
} }
@@ -541,21 +524,19 @@ TEST_F(WebRPCExecutorTest, WsInternalError)
"command": "ledger", "command": "ledger",
"id": "123" "id": "123"
})"; })";
auto request = boost::json::parse(requestJSON).as_object();
EXPECT_CALL(*rpcEngine, buildResponse(testing::_)).Times(1).WillOnce(testing::Throw(std::runtime_error("MyError"))); EXPECT_CALL(*rpcEngine, buildResponse(testing::_)).Times(1).WillOnce(testing::Throw(std::runtime_error("MyError")));
(*rpcExecutor)(std::move(request), session); (*rpcExecutor)(std::move(requestJSON), session);
std::this_thread::sleep_for(200ms); std::this_thread::sleep_for(200ms);
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response)); EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
} }
TEST_F(WebRPCExecutorTest, HTTPOutDated) TEST_F(WebRPCExecutorTest, HTTPOutDated)
{ {
auto request = boost::json::parse(R"({ static auto constexpr request = R"({
"method": "server_info", "method": "server_info",
"params": [{}] "params": [{}]
})") })";
.as_object();
mockBackendPtr->updateRange(MINSEQ); // min mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max mockBackendPtr->updateRange(MAXSEQ); // max
@@ -591,11 +572,10 @@ TEST_F(WebRPCExecutorTest, WsOutdated)
{ {
session->upgraded = true; session->upgraded = true;
auto request = boost::json::parse(R"({ static auto constexpr request = R"({
"command": "server_info", "command": "server_info",
"id": 99 "id": 99
})") })";
.as_object();
mockBackendPtr->updateRange(MINSEQ); // min mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max mockBackendPtr->updateRange(MAXSEQ); // max
@@ -636,17 +616,22 @@ TEST_F(WebRPCExecutorTest, WsTooBusy)
auto rpcEngine2 = std::make_shared<MockRPCEngine>(); auto rpcEngine2 = std::make_shared<MockRPCEngine>();
auto rpcExecutor2 = auto rpcExecutor2 =
std::make_shared<RPCExecutor<MockRPCEngine, MockETLService>>(cfg, mockBackendPtr, rpcEngine2, etl, subManager); std::make_shared<RPCExecutor<MockRPCEngine, MockETLService>>(cfg, mockBackendPtr, rpcEngine2, etl, subManager);
auto request = boost::json::parse(R"({ static auto constexpr request = R"({
"command": "server_info", "command": "server_info",
"id": 99 "id": 99
})") })";
.as_object();
mockBackendPtr->updateRange(MINSEQ); // min mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max mockBackendPtr->updateRange(MAXSEQ); // max
static auto constexpr response = static auto constexpr response =
R"({"error":"tooBusy","error_code":9,"error_message":"The server is too busy to help you now.","status":"error","type":"response"})"; R"({
"error":"tooBusy",
"error_code":9,
"error_message":"The server is too busy to help you now.",
"status":"error",
"type":"response"
})";
EXPECT_CALL(*rpcEngine2, post).WillOnce(testing::Return(false)); EXPECT_CALL(*rpcEngine2, post).WillOnce(testing::Return(false));
(*rpcExecutor2)(std::move(request), session); (*rpcExecutor2)(std::move(request), session);
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response)); EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
@@ -657,19 +642,57 @@ TEST_F(WebRPCExecutorTest, HTTPTooBusy)
auto rpcEngine2 = std::make_shared<MockRPCEngine>(); auto rpcEngine2 = std::make_shared<MockRPCEngine>();
auto rpcExecutor2 = auto rpcExecutor2 =
std::make_shared<RPCExecutor<MockRPCEngine, MockETLService>>(cfg, mockBackendPtr, rpcEngine2, etl, subManager); std::make_shared<RPCExecutor<MockRPCEngine, MockETLService>>(cfg, mockBackendPtr, rpcEngine2, etl, subManager);
auto request = boost::json::parse(R"({ static auto constexpr request = R"({
"method": "server_info", "method": "server_info",
"params": [{}] "params": [{}]
})") })";
.as_object();
mockBackendPtr->updateRange(MINSEQ); // min mockBackendPtr->updateRange(MINSEQ); // min
mockBackendPtr->updateRange(MAXSEQ); // max mockBackendPtr->updateRange(MAXSEQ); // max
static auto constexpr response = static auto constexpr response =
R"({"error":"tooBusy","error_code":9,"error_message":"The server is too busy to help you now.","status":"error","type":"response"})"; R"({
"error":"tooBusy",
"error_code":9,
"error_message":"The server is too busy to help you now.",
"status":"error",
"type":"response"
})";
EXPECT_CALL(*rpcEngine2, post).WillOnce(testing::Return(false)); EXPECT_CALL(*rpcEngine2, post).WillOnce(testing::Return(false));
(*rpcExecutor2)(std::move(request), session); (*rpcExecutor2)(std::move(request), session);
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response)); EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
} }
TEST_F(WebRPCExecutorTest, HTTPRequestNotJson)
{
static auto constexpr request = "not json";
static auto constexpr response =
R"({
"error":"badSyntax",
"error_code":1,
"error_message":"Syntax error.",
"status":"error",
"type":"response"
})";
(*rpcExecutor)(std::move(request), session);
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
}
TEST_F(WebRPCExecutorTest, WsRequestNotJson)
{
session->upgraded = true;
static auto constexpr request = "not json";
static auto constexpr response =
R"({
"error":"badSyntax",
"error_code":1,
"error_message":"Syntax error.",
"status":"error",
"type":"response"
})";
(*rpcExecutor)(std::move(request), session);
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(response));
}

View File

@@ -162,9 +162,9 @@ class EchoExecutor
{ {
public: public:
void void
operator()(boost::json::object&& req, std::shared_ptr<Server::ConnectionBase> const& ws) operator()(std::string const& reqStr, std::shared_ptr<Server::ConnectionBase> const& ws)
{ {
ws->send(boost::json::serialize(req), http::status::ok); ws->send(std::string(reqStr), http::status::ok);
} }
void void
@@ -177,7 +177,7 @@ class ExceptionExecutor
{ {
public: public:
void void
operator()(boost::json::object&& req, std::shared_ptr<Server::ConnectionBase> const& ws) operator()(std::string const& req, std::shared_ptr<Server::ConnectionBase> const& ws)
{ {
throw std::runtime_error("MyError"); throw std::runtime_error("MyError");
} }
@@ -207,52 +207,6 @@ TEST_F(WebServerTest, Ws)
wsClient.disconnect(); wsClient.disconnect();
} }
TEST_F(WebServerTest, HttpBodyNotJsonValue)
{
auto e = std::make_shared<EchoExecutor>();
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
auto const res = HttpSyncClient::syncPost("localhost", "8888", R"({)");
EXPECT_EQ(
res,
R"({"error":"badSyntax","error_code":1,"error_message":"Syntax error.","status":"error","type":"response"})");
}
TEST_F(WebServerTest, HttpBodyNotJsonObject)
{
auto e = std::make_shared<EchoExecutor>();
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
auto const res = HttpSyncClient::syncPost("localhost", "8888", R"("123")");
EXPECT_EQ(
res,
R"({"error":"badSyntax","error_code":1,"error_message":"Syntax error.","status":"error","type":"response"})");
}
TEST_F(WebServerTest, WsBodyNotJsonValue)
{
auto e = std::make_shared<EchoExecutor>();
WebSocketSyncClient wsClient;
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
wsClient.connect("localhost", "8888");
auto const res = wsClient.syncPost(R"({)");
wsClient.disconnect();
EXPECT_EQ(
res,
R"({"error":"badSyntax","error_code":1,"error_message":"Syntax error.","status":"error","type":"response","request":["{"]})");
}
TEST_F(WebServerTest, WsBodyNotJsonObject)
{
auto e = std::make_shared<EchoExecutor>();
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
WebSocketSyncClient wsClient;
wsClient.connect("localhost", "8888");
auto const res = wsClient.syncPost(R"("Hello")");
wsClient.disconnect();
EXPECT_EQ(
res,
R"({"error":"badSyntax","error_code":1,"error_message":"Syntax error.","status":"error","type":"response","request":"Hello"})");
}
TEST_F(WebServerTest, HttpInternalError) TEST_F(WebServerTest, HttpInternalError)
{ {
auto e = std::make_shared<ExceptionExecutor>(); auto e = std::make_shared<ExceptionExecutor>();
@@ -269,11 +223,24 @@ TEST_F(WebServerTest, WsInternalError)
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e); auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
WebSocketSyncClient wsClient; WebSocketSyncClient wsClient;
wsClient.connect("localhost", "8888"); wsClient.connect("localhost", "8888");
auto const res = wsClient.syncPost(R"({})"); auto const res = wsClient.syncPost(R"({"id":"id1"})");
wsClient.disconnect(); wsClient.disconnect();
EXPECT_EQ( EXPECT_EQ(
res, res,
R"({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response","request":{}})"); R"({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response","id":"id1","request":{"id":"id1"}})");
}
TEST_F(WebServerTest, WsInternalErrorNotJson)
{
auto e = std::make_shared<ExceptionExecutor>();
auto const server = Server::make_HttpServer(cfg, ctx, std::nullopt, dosGuard, e);
WebSocketSyncClient wsClient;
wsClient.connect("localhost", "8888");
auto const res = wsClient.syncPost("not json");
wsClient.disconnect();
EXPECT_EQ(
res,
R"({"error":"internal","error_code":73,"error_message":"Internal error.","status":"error","type":"response","request":"not json"})");
} }
TEST_F(WebServerTest, Https) TEST_F(WebServerTest, Https)