Line data Source code
1 : /*
2 : ___________________________________________
3 : | _ ___ _ |
4 : | | | |__ \ | | |
5 : | | |__ ) |__ _ __ _ ___ _ __ | |_ |
6 : | | '_ \ / // _` |/ _` |/ _ \ '_ \| __| | HTTP/2 AGENT FOR MOCK TESTING
7 : | | | | |/ /| (_| | (_| | __/ | | | |_ | Version 0.0.z
8 : | |_| |_|____\__,_|\__, |\___|_| |_|\__| | https://github.com/testillano/h2agent
9 : | __/ | |
10 : | |___/ |
11 : |___________________________________________|
12 :
13 : Licensed under the MIT License <http://opensource.org/licenses/MIT>.
14 : SPDX-License-Identifier: MIT
15 : Copyright (c) 2021 Eduardo Ramos
16 :
17 : Permission is hereby granted, free of charge, to any person obtaining a copy
18 : of this software and associated documentation files (the "Software"), to deal
19 : in the Software without restriction, including without limitation the rights
20 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 : copies of the Software, and to permit persons to whom the Software is
22 : furnished to do so, subject to the following conditions:
23 :
24 : The above copyright notice and this permission notice shall be included in all
25 : copies or substantial portions of the Software.
26 :
27 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33 : SOFTWARE.
34 : */
35 :
36 : #include <boost/optional.hpp>
37 :
38 : #include <sstream>
39 : #include <errno.h>
40 :
41 : #include <nlohmann/json.hpp>
42 :
43 : #include <ert/tracing/Logger.hpp>
44 : #include <ert/http2comm/Http.hpp>
45 : #include <ert/http2comm/Http2Headers.hpp>
46 :
47 : #include <MyAdminHttp2Server.hpp>
48 : #include <MyTrafficHttp2Server.hpp>
49 : //#include <MyTrafficHttp2Client.hpp>
50 :
51 : #include <AdminData.hpp>
52 : #include <MockServerData.hpp>
53 : #include <Configuration.hpp>
54 : #include <GlobalVariable.hpp>
55 : #include <FileManager.hpp>
56 : #include <SocketManager.hpp>
57 : #include <functions.hpp>
58 :
59 :
60 : namespace h2agent
61 : {
62 : namespace http2
63 : {
64 :
65 29 : bool statusCodeOK(int statusCode) {
66 29 : return (statusCode >= ert::http2comm::ResponseCode::OK && statusCode < ert::http2comm::ResponseCode::MULTIPLE_CHOICES); // [200,300)
67 : }
68 :
69 49 : MyAdminHttp2Server::MyAdminHttp2Server(const std::string &name, size_t workerThreads):
70 49 : ert::http2comm::Http2Server(name, workerThreads, workerThreads, nullptr) {
71 :
72 49 : admin_data_ = new model::AdminData();
73 49 : common_resources_.AdminDataPtr = admin_data_; // it would be dirty to assign this outside like Configuration or other common resources
74 :
75 : // Client data storage
76 49 : client_data_ = true;
77 49 : client_data_key_history_ = true;
78 49 : purge_execution_ = true;
79 49 : }
80 :
81 60 : MyAdminHttp2Server::~MyAdminHttp2Server()
82 : {
83 30 : delete (admin_data_);
84 60 : }
85 :
86 48 : bool MyAdminHttp2Server::checkMethodIsAllowed(
87 : const nghttp2::asio_http2::server::request& req,
88 : std::vector<std::string>& allowedMethods)
89 : {
90 240 : allowedMethods = {"POST", "GET", "DELETE", "PUT"};
91 48 : return (req.method() == "POST" || req.method() == "GET" || req.method() == "DELETE" || req.method() == "PUT");
92 144 : }
93 :
94 48 : bool MyAdminHttp2Server::checkMethodIsImplemented(
95 : const nghttp2::asio_http2::server::request& req)
96 : {
97 48 : return (req.method() == "POST" || req.method() == "GET" || req.method() == "DELETE" || req.method() == "PUT");
98 : }
99 :
100 :
101 48 : bool MyAdminHttp2Server::checkHeaders(const
102 : nghttp2::asio_http2::server::request& req)
103 : {
104 : // Don't check headers for GET and DELETE:
105 48 : if (req.method() == "GET" || req.method() == "DELETE" || req.method() == "PUT") {
106 23 : return true;
107 : }
108 :
109 50 : auto ctype = req.header().find("content-type");
110 50 : auto clength = req.header().find("content-length");
111 25 : auto ctype_end = req.header().end();
112 :
113 25 : LOGDEBUG(
114 : ert::tracing::Logger::debug(
115 : ert::tracing::Logger::asString(
116 : "[ReceivedRequest] Headers: content-type = %s; content-length = %s",
117 : (ctype != ctype_end) ? ctype->second.value.c_str() : "(absent)",
118 : (clength != ctype_end) ? clength->second.value.c_str() : "(absent)"), ERT_FILE_LOCATION));
119 :
120 25 : if (ctype != ctype_end)
121 : {
122 25 : return (ctype->second.value == "application/json");
123 : }
124 :
125 0 : return (clength != ctype_end && clength->second.value != "0");
126 : }
127 :
128 :
129 46 : std::string MyAdminHttp2Server::getPathSuffix(const std::string &uriPath) const
130 : {
131 46 : std::string result;
132 :
133 46 : size_t apiPathSize = getApiPath().size(); // /admin/v1
134 46 : size_t uriPathSize = uriPath.size(); // /admin/v1<suffix>
135 : //LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("apiPathSize %d uriPathSize %d", apiPathSize, uriPathSize), ERT_FILE_LOCATION));
136 :
137 : // Special case
138 46 : if (uriPathSize <= apiPathSize) return result; // indeed, it should not be lesser, as API & VERSION is already checked
139 :
140 44 : result = uriPath.substr(apiPathSize + 1);
141 :
142 44 : if (result.back() == '/') result.pop_back(); // normalize by mean removing last slash (if exists)
143 :
144 44 : return result;
145 : } // LCOV_EXCL_LINE
146 :
147 : /*
148 : #include <iomanip>
149 :
150 : void MyAdminHttp2Server::buildJsonResponse(bool result, const std::string &response, std::string &jsonResponse) const
151 : {
152 : std::stringstream ss;
153 : ss << R"({ "result":")" << (result ? "true":"false") << R"(", "response": )" << std::quoted(response) << R"( })";
154 : jsonResponse = ss.str();
155 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("jsonResponse %s", jsonResponse.c_str()), ERT_FILE_LOCATION));
156 : }
157 :
158 : THIS WAS REPLACED TEMPORARILY BY A SLIGHTLY LESS EFFICIENT VERSION, TO AVOID VALGRIND COMPLAIN:
159 : */
160 :
161 23 : std::string MyAdminHttp2Server::buildJsonResponse(bool responseResult, const std::string &responseBody) const
162 : {
163 23 : std::string result;
164 23 : result = R"({ "result":")";
165 23 : result += (responseResult ? "true":"false");
166 23 : result += R"(", "response": )";
167 23 : result += R"(")";
168 23 : result += responseBody;
169 23 : result += R"(" })";
170 23 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Json Response %s", result.c_str()), ERT_FILE_LOCATION));
171 :
172 23 : return result;
173 : } // LCOV_EXCL_LINE
174 :
175 2 : void MyAdminHttp2Server::receiveNOOP(unsigned int& statusCode, nghttp2::asio_http2::header_map& headers, std::string &responseBody) const
176 : {
177 2 : LOGDEBUG(ert::tracing::Logger::debug("receiveNOOP()", ERT_FILE_LOCATION));
178 : // Response document:
179 : // {
180 : // "result":"<true or false>",
181 : // "response":"<additional information>"
182 : // }
183 2 : responseBody = buildJsonResponse(false, "no operation provided");
184 4 : headers.emplace("content-type", nghttp2::asio_http2::header_value{"application/json"});
185 2 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
186 2 : }
187 :
188 11 : int MyAdminHttp2Server::serverMatching(const nlohmann::json &configurationObject, std::string& log) const
189 : {
190 11 : log = "server-matching operation; ";
191 :
192 11 : h2agent::model::AdminServerMatchingData::LoadResult loadResult = getAdminData()->loadServerMatching(configurationObject);
193 11 : int result = ((loadResult == h2agent::model::AdminServerMatchingData::Success) ? ert::http2comm::ResponseCode::CREATED:ert::http2comm::ResponseCode::BAD_REQUEST); // 201 or 400
194 :
195 11 : if (loadResult == h2agent::model::AdminServerMatchingData::Success) {
196 8 : log += "valid schema and matching data received";
197 :
198 : // Warn in case previous server provisions exists:
199 8 : if (getAdminData()->getServerProvisionData().size() != 0)
200 3 : LOGWARNING(
201 : if (getAdminData()->getServerProvisionData().size() != 0) {
202 : ert::tracing::Logger::warning("There are current server provisions: remove/update them to avoid unexpected behavior (matching must be configured firstly !)", ERT_FILE_LOCATION);
203 : }
204 : );
205 : }
206 3 : else if (loadResult == h2agent::model::AdminServerMatchingData::BadSchema) {
207 2 : log += "invalid schema";
208 : }
209 1 : else if (loadResult == h2agent::model::AdminServerMatchingData::BadContent) {
210 1 : log += "invalid matching data received";
211 : }
212 :
213 11 : return result;
214 : }
215 :
216 14 : int MyAdminHttp2Server::serverProvision(const nlohmann::json &configurationObject, std::string& log) const
217 : {
218 14 : log = "server-provision operation; ";
219 :
220 14 : h2agent::model::AdminServerProvisionData::LoadResult loadResult = getAdminData()->loadServerProvision(configurationObject, common_resources_);
221 14 : int result = ((loadResult == h2agent::model::AdminServerProvisionData::Success) ? ert::http2comm::ResponseCode::CREATED:ert::http2comm::ResponseCode::BAD_REQUEST); // 201 or 400
222 :
223 14 : bool isArray = configurationObject.is_array();
224 14 : if (loadResult == h2agent::model::AdminServerProvisionData::Success) {
225 11 : log += (isArray ? "valid schemas and server provisions data received":"valid schema and server provision data received");
226 : }
227 3 : else if (loadResult == h2agent::model::AdminServerProvisionData::BadSchema) {
228 3 : log += (isArray ? "detected one invalid schema":"invalid schema");
229 : }
230 0 : else if (loadResult == h2agent::model::AdminServerProvisionData::BadContent) {
231 0 : log += (isArray ? "detected one invalid server provision data received":"invalid server provision data received");
232 : }
233 :
234 14 : return result;
235 : }
236 :
237 6 : int MyAdminHttp2Server::clientEndpoint(const nlohmann::json &configurationObject, std::string& log) const
238 : {
239 6 : log = "client-endpoint operation; ";
240 :
241 6 : h2agent::model::AdminClientEndpointData::LoadResult loadResult = getAdminData()->loadClientEndpoint(configurationObject, common_resources_);
242 6 : int result = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
243 6 : if (loadResult == h2agent::model::AdminClientEndpointData::Success) {
244 2 : result = ert::http2comm::ResponseCode::CREATED; // 201
245 : }
246 4 : else if (loadResult == h2agent::model::AdminClientEndpointData::Accepted) {
247 0 : result = ert::http2comm::ResponseCode::ACCEPTED; // 202
248 : }
249 :
250 6 : bool isArray = configurationObject.is_array();
251 6 : if (loadResult == h2agent::model::AdminClientEndpointData::Success || loadResult == h2agent::model::AdminClientEndpointData::Accepted) {
252 2 : log += (isArray ? "valid schemas and client endpoints data received":"valid schema and client endpoint data received");
253 : }
254 4 : else if (loadResult == h2agent::model::AdminClientEndpointData::BadSchema) {
255 2 : log += (isArray ? "detected one invalid schema":"invalid schema");
256 : }
257 2 : else if (loadResult == h2agent::model::AdminClientEndpointData::BadContent) {
258 2 : log += (isArray ? "detected one invalid client endpoint data received":"invalid client endpoint data received");
259 : }
260 :
261 6 : return result;
262 : }
263 :
264 0 : int MyAdminHttp2Server::clientProvision(const nlohmann::json &configurationObject, std::string& log) const
265 : {
266 0 : log = "client-provision operation; ";
267 :
268 0 : h2agent::model::AdminClientProvisionData::LoadResult loadResult = getAdminData()->loadClientProvision(configurationObject, common_resources_);
269 0 : int result = ((loadResult == h2agent::model::AdminClientProvisionData::Success) ? 201:400);
270 :
271 0 : bool isArray = configurationObject.is_array();
272 0 : if (loadResult == h2agent::model::AdminClientProvisionData::Success) {
273 0 : log += (isArray ? "valid schemas and client provisions data received":"valid schema and client provision data received");
274 : }
275 0 : else if (loadResult == h2agent::model::AdminClientProvisionData::BadSchema) {
276 0 : log += (isArray ? "detected one invalid schema":"invalid schema");
277 : }
278 0 : else if (loadResult == h2agent::model::AdminClientProvisionData::BadContent) {
279 0 : log += (isArray ? "detected one invalid client provision data received":"invalid client provision data received");
280 : }
281 :
282 0 : return result;
283 : }
284 :
285 8 : int MyAdminHttp2Server::globalVariable(const nlohmann::json &configurationObject, std::string& log) const
286 : {
287 8 : log = "global-variable operation; ";
288 :
289 8 : int result = getGlobalVariable()->loadJson(configurationObject) ? ert::http2comm::ResponseCode::CREATED:ert::http2comm::ResponseCode::BAD_REQUEST; // 201 or 400
290 8 : log += (statusCodeOK(result) ? "valid schema and global variables received":"invalid schema");
291 :
292 8 : return result;
293 : }
294 :
295 5 : int MyAdminHttp2Server::schema(const nlohmann::json &configurationObject, std::string& log) const
296 : {
297 5 : log = "schema operation; ";
298 :
299 5 : h2agent::model::AdminSchemaData::LoadResult loadResult = getAdminData()->loadSchema(configurationObject);
300 5 : int result = ((loadResult == h2agent::model::AdminSchemaData::Success) ? ert::http2comm::ResponseCode::CREATED:ert::http2comm::ResponseCode::BAD_REQUEST); // 201 or 400
301 :
302 5 : bool isArray = configurationObject.is_array();
303 5 : if (loadResult == h2agent::model::AdminSchemaData::Success) {
304 4 : log += (isArray ? "valid schemas and schemas data received":"valid schema and schema data received");
305 : }
306 1 : else if (loadResult == h2agent::model::AdminSchemaData::BadSchema) {
307 1 : log += (isArray ? "detected one invalid schema":"invalid schema");
308 : }
309 0 : else if (loadResult == h2agent::model::AdminSchemaData::BadContent) {
310 0 : log += (isArray ? "detected one invalid schema data received":"invalid schema data received");
311 : }
312 :
313 5 : return result;
314 : }
315 :
316 21 : void MyAdminHttp2Server::receivePOST(const std::string &pathSuffix, const std::string& requestBody, unsigned int& statusCode, nghttp2::asio_http2::header_map& headers, std::string &responseBody) const
317 : {
318 21 : LOGDEBUG(ert::tracing::Logger::debug("receivePOST()", ERT_FILE_LOCATION));
319 21 : LOGDEBUG(ert::tracing::Logger::debug("Json body received (admin interface)", ERT_FILE_LOCATION));
320 :
321 21 : std::string jsonResponse_response;
322 :
323 : // All responses are json content:
324 42 : headers.emplace("content-type", nghttp2::asio_http2::header_value{"application/json"});
325 :
326 : // Admin schema validation:
327 21 : nlohmann::json requestJson;
328 21 : bool success = h2agent::model::parseJsonContent(requestBody, requestJson);
329 :
330 21 : if (success) {
331 21 : if (pathSuffix == "server-matching") {
332 7 : statusCode = serverMatching(requestJson, jsonResponse_response);
333 : }
334 14 : else if (pathSuffix == "server-provision") {
335 10 : statusCode = serverProvision(requestJson, jsonResponse_response);
336 : }
337 4 : else if (pathSuffix == "client-endpoint") {
338 0 : statusCode = clientEndpoint(requestJson, jsonResponse_response);
339 : }
340 4 : else if (pathSuffix == "client-provision") {
341 0 : statusCode = clientProvision(requestJson, jsonResponse_response);
342 : }
343 4 : else if (pathSuffix == "schema") {
344 2 : statusCode = schema(requestJson, jsonResponse_response);
345 : }
346 2 : else if (pathSuffix == "global-variable") {
347 2 : statusCode = globalVariable(requestJson, jsonResponse_response);
348 : }
349 : else {
350 0 : statusCode = ert::http2comm::ResponseCode::NOT_IMPLEMENTED; // 501
351 0 : jsonResponse_response = "unsupported operation";
352 : }
353 : }
354 : else
355 : {
356 : // Response data:
357 0 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
358 0 : jsonResponse_response = "failed to parse json from body request";
359 : }
360 :
361 : // Build json response body:
362 21 : responseBody = buildJsonResponse(statusCodeOK(statusCode), jsonResponse_response);
363 21 : }
364 :
365 23 : void MyAdminHttp2Server::receiveGET(const std::string &uri, const std::string &pathSuffix, const std::string &queryParams, unsigned int& statusCode, nghttp2::asio_http2::header_map& headers, std::string &responseBody) const
366 : {
367 23 : LOGDEBUG(ert::tracing::Logger::debug("receiveGET()", ERT_FILE_LOCATION));
368 :
369 : // All responses, except for 'logging', are json content:
370 23 : bool jsonContent = true;
371 :
372 : // composed path suffixes
373 23 : std::smatch matches;
374 23 : static std::regex clientProvisionId("^client-provision/(.*)", std::regex::optimize);
375 :
376 :
377 23 : if (pathSuffix == "server-matching/schema") {
378 : // Add the $id field dynamically (full URI including scheme/host)
379 0 : nlohmann::json jsonSchema = getAdminData()->getServerMatchingData().getSchema().getJson();
380 0 : jsonSchema["$id"] = uri;
381 0 : responseBody = jsonSchema.dump();
382 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
383 0 : }
384 23 : else if (pathSuffix == "server-provision/schema") {
385 : // Add the $id field dynamically (full URI including scheme/host)
386 0 : nlohmann::json jsonSchema = getAdminData()->getServerProvisionData().getSchema().getJson();
387 0 : jsonSchema["$id"] = uri;
388 0 : responseBody = jsonSchema.dump();
389 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
390 0 : }
391 23 : else if (pathSuffix == "client-endpoint/schema") {
392 : // Add the $id field dynamically (full URI including scheme/host)
393 0 : nlohmann::json jsonSchema = getAdminData()->getClientEndpointData().getSchema().getJson();
394 0 : jsonSchema["$id"] = uri;
395 0 : responseBody = jsonSchema.dump();
396 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
397 0 : }
398 23 : else if (pathSuffix == "client-provision/schema") {
399 : // Add the $id field dynamically (full URI including scheme/host)
400 0 : nlohmann::json jsonSchema = getAdminData()->getClientProvisionData().getSchema().getJson();
401 0 : jsonSchema["$id"] = uri;
402 0 : responseBody = jsonSchema.dump();
403 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
404 0 : }
405 23 : else if (pathSuffix == "schema/schema") {
406 : // Add the $id field dynamically (full URI including scheme/host)
407 0 : nlohmann::json jsonSchema = getAdminData()->getSchemaData().getSchema().getJson();
408 0 : jsonSchema["$id"] = uri;
409 0 : responseBody = jsonSchema.dump();
410 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
411 0 : }
412 23 : else if (pathSuffix == "server-data/summary") {
413 0 : std::string maxKeys = "";
414 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
415 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
416 0 : auto it = qmap.find("maxKeys");
417 0 : if (it != qmap.end()) maxKeys = it->second;
418 0 : }
419 :
420 0 : responseBody = getMockServerData()->summary(maxKeys);
421 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
422 0 : }
423 23 : else if (pathSuffix == "client-data/summary") {
424 0 : std::string maxKeys = "";
425 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
426 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
427 0 : auto it = qmap.find("maxKeys");
428 0 : if (it != qmap.end()) maxKeys = it->second;
429 0 : }
430 :
431 0 : responseBody = getMockClientData()->summary(maxKeys);
432 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
433 0 : }
434 23 : else if (pathSuffix == "global-variable") {
435 4 : std::string name = "";
436 4 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
437 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
438 0 : auto it = qmap.find("name");
439 0 : if (it != qmap.end()) name = it->second;
440 0 : if (name.empty()) {
441 0 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
442 0 : responseBody = "";
443 : }
444 0 : }
445 4 : if (statusCode != ert::http2comm::ResponseCode::BAD_REQUEST) { // 400
446 4 : if (name.empty()) {
447 4 : responseBody = getGlobalVariable()->asJsonString();
448 4 : statusCode = ((responseBody == "{}") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK); // response body will be emptied by nghttp2 when status code is 204 (No Content)
449 : }
450 : else {
451 : bool exists;
452 0 : responseBody = getGlobalVariable()->getValue(name, exists);
453 0 : statusCode = (exists ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::NO_CONTENT); // response body will be emptied by nghttp2 when status code is 204 (No Content)
454 : }
455 : }
456 4 : }
457 19 : else if (pathSuffix == "global-variable/schema") {
458 : // Add the $id field dynamically (full URI including scheme/host)
459 0 : nlohmann::json jsonSchema = getGlobalVariable()->getSchema().getJson();
460 0 : jsonSchema["$id"] = uri;
461 0 : responseBody = jsonSchema.dump();
462 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
463 0 : }
464 19 : else if (pathSuffix == "server-matching") {
465 7 : responseBody = getAdminData()->getServerMatchingData().getJson().dump();
466 7 : statusCode = ert::http2comm::ResponseCode::OK; // 200
467 : }
468 12 : else if (pathSuffix == "server-provision") {
469 4 : bool ordered = (getAdminData()->getServerMatchingData().getAlgorithm() == h2agent::model::AdminServerMatchingData::RegexMatching);
470 4 : responseBody = getAdminData()->getServerProvisionData().asJsonString(ordered);
471 4 : statusCode = ((responseBody == "[]") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK); // response body will be emptied by nghttp2 when status code is 204 (No Content)
472 : }
473 8 : else if (pathSuffix == "server-provision/unused") {
474 4 : bool ordered = (getAdminData()->getServerMatchingData().getAlgorithm() == h2agent::model::AdminServerMatchingData::RegexMatching);
475 4 : responseBody = getAdminData()->getServerProvisionData().asJsonString(ordered, true /*unused*/);
476 4 : statusCode = ((responseBody == "[]") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK); // response body will be emptied by nghttp2 when status code is 204 (No Content)
477 : }
478 4 : else if (pathSuffix == "client-endpoint") {
479 0 : responseBody = getAdminData()->getClientEndpointData().asJsonString();
480 0 : statusCode = ((responseBody == "[]") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK); // response body will be emptied by nghttp2 when status code is 204 (No Content)
481 : }
482 4 : else if (pathSuffix == "client-provision") {
483 0 : responseBody = getAdminData()->getClientProvisionData().asJsonString();
484 0 : statusCode = ((responseBody == "[]") ? 204:200); // response body will be emptied by nghttp2 when status code is 204 (No Content)
485 : }
486 4 : else if (pathSuffix == "client-provision/unused") {
487 0 : responseBody = getAdminData()->getClientProvisionData().asJsonString(true /*unused*/);
488 0 : statusCode = ((responseBody == "[]") ? 204:200); // response body will be emptied by nghttp2 when status code is 204 (No Content)
489 : }
490 4 : else if (std::regex_match(pathSuffix, matches, clientProvisionId)) { // client-provision/<client provision id>
491 0 : triggerClientOperation(matches.str(1), queryParams, statusCode);
492 0 : bool result = statusCodeOK(statusCode);
493 0 : responseBody = buildJsonResponse(result, (result ? "operation processed":"operation failed"));
494 : }
495 4 : else if (pathSuffix == "schema") {
496 4 : responseBody = getAdminData()->getSchemaData().asJsonString();
497 4 : statusCode = ((responseBody == "[]") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK); // response body will be emptied by nghttp2 when status code is 204 (No Content)
498 : }
499 0 : else if (pathSuffix == "server-data") {
500 0 : std::string requestMethod = "";
501 0 : std::string requestUri = "";
502 0 : std::string eventNumber = "";
503 0 : std::string eventPath = "";
504 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
505 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
506 0 : auto it = qmap.find("requestMethod");
507 0 : if (it != qmap.end()) requestMethod = it->second;
508 0 : it = qmap.find("requestUri");
509 0 : if (it != qmap.end()) requestUri = it->second;
510 0 : it = qmap.find("eventNumber");
511 0 : if (it != qmap.end()) eventNumber = it->second;
512 0 : it = qmap.find("eventPath");
513 0 : if (it != qmap.end()) eventPath = it->second;
514 0 : }
515 :
516 0 : bool validQuery = false;
517 : try { // dump could throw exception if something weird is done (binary data with non-binary content-type)
518 0 : h2agent::model::EventLocationKey elkey(requestMethod, requestUri, eventNumber, eventPath);
519 0 : responseBody = getMockServerData()->asJsonString(elkey, validQuery);
520 0 : }
521 0 : catch (const std::exception& e)
522 : {
523 : //validQuery = false; // will be ert::http2comm::ResponseCode::OK (200) with empty result (corner case)
524 0 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
525 0 : }
526 0 : statusCode = validQuery ? ((responseBody == "[]") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK):ert::http2comm::ResponseCode::BAD_REQUEST; // response body will be emptied by nghttp2 when status code is 204 (No Content)
527 0 : }
528 0 : else if (pathSuffix == "client-data") {
529 0 : std::string clientEndpointId = "";
530 0 : std::string requestMethod = "";
531 0 : std::string requestUri = "";
532 0 : std::string eventNumber = "";
533 0 : std::string eventPath = "";
534 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
535 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
536 0 : auto it = qmap.find("clientEndpointId");
537 0 : if (it != qmap.end()) clientEndpointId = it->second;
538 0 : it = qmap.find("requestMethod");
539 0 : if (it != qmap.end()) requestMethod = it->second;
540 0 : it = qmap.find("requestUri");
541 0 : if (it != qmap.end()) requestUri = it->second;
542 0 : it = qmap.find("eventNumber");
543 0 : if (it != qmap.end()) eventNumber = it->second;
544 0 : it = qmap.find("eventPath");
545 0 : if (it != qmap.end()) eventPath = it->second;
546 0 : }
547 :
548 0 : bool validQuery = false;
549 : try { // dump could throw exception if something weird is done (binary data with non-binary content-type)
550 0 : h2agent::model::EventLocationKey elkey(clientEndpointId, requestMethod, requestUri, eventNumber, eventPath);
551 0 : responseBody = getMockClientData()->asJsonString(elkey, validQuery);
552 0 : }
553 0 : catch (const std::exception& e)
554 : {
555 : //validQuery = false; // will be ert::http2comm::ResponseCode::OK (200) with empty result (corner case)
556 0 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
557 0 : }
558 0 : statusCode = validQuery ? ((responseBody == "[]") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK):ert::http2comm::ResponseCode::BAD_REQUEST; // response body will be emptied by nghttp2 when status code is 204 (No Content)
559 0 : }
560 0 : else if (pathSuffix == "configuration") {
561 0 : responseBody = getConfiguration()->asJsonString();
562 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
563 : }
564 0 : else if (pathSuffix == "server/configuration") {
565 0 : responseBody = getHttp2Server()->configurationAsJsonString();
566 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
567 : }
568 0 : else if (pathSuffix == "server-data/configuration") {
569 0 : responseBody = getHttp2Server()->dataConfigurationAsJsonString();
570 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
571 : }
572 0 : else if (pathSuffix == "client-data/configuration") {
573 0 : responseBody = clientDataConfigurationAsJsonString();
574 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
575 : }
576 0 : else if (pathSuffix == "files/configuration") {
577 0 : responseBody = getFileManager()->configurationAsJsonString();
578 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
579 : }
580 0 : else if (pathSuffix == "files") {
581 0 : responseBody = getFileManager()->asJsonString();
582 0 : statusCode = ((responseBody == "[]") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK); // 204 or 200
583 : }
584 0 : else if (pathSuffix == "udp-sockets") {
585 0 : responseBody = getSocketManager()->asJsonString();
586 0 : statusCode = ((responseBody == "[]") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK); // 204 or 200
587 : }
588 0 : else if (pathSuffix == "logging") {
589 0 : responseBody = ert::tracing::Logger::levelAsString(ert::tracing::Logger::getLevel());
590 0 : headers.emplace("content-type", nghttp2::asio_http2::header_value{"text/html"});
591 0 : jsonContent = false;
592 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
593 : }
594 : else {
595 0 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
596 0 : responseBody = buildJsonResponse(false, std::string("invalid operation '") + pathSuffix + std::string("'"));
597 : }
598 :
599 69 : if (jsonContent) headers.emplace("content-type", nghttp2::asio_http2::header_value{"application/json"});
600 23 : }
601 :
602 0 : void MyAdminHttp2Server::receiveDELETE(const std::string &pathSuffix, const std::string &queryParams, unsigned int& statusCode) const
603 : {
604 0 : LOGDEBUG(ert::tracing::Logger::debug("receiveDELETE()", ERT_FILE_LOCATION));
605 :
606 0 : if (pathSuffix == "server-provision") {
607 0 : statusCode = (getAdminData()->clearServerProvisions() ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::NO_CONTENT); // 200 or 204
608 : }
609 0 : else if (pathSuffix == "client-endpoint") {
610 0 : statusCode = (getAdminData()->clearClientEndpoints() ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::NO_CONTENT); // 200 or 204
611 : }
612 0 : else if (pathSuffix == "client-provision") {
613 0 : statusCode = (getAdminData()->clearClientProvisions() ? 200:204);
614 : }
615 0 : else if (pathSuffix == "schema") {
616 0 : statusCode = (getAdminData()->clearSchemas() ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::NO_CONTENT); // 200 or 204
617 : }
618 0 : else if (pathSuffix == "server-data") {
619 0 : bool serverDataDeleted = false;
620 0 : std::string requestMethod = "";
621 0 : std::string requestUri = "";
622 0 : std::string eventNumber = "";
623 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
624 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
625 0 : auto it = qmap.find("requestMethod");
626 0 : if (it != qmap.end()) requestMethod = it->second;
627 0 : it = qmap.find("requestUri");
628 0 : if (it != qmap.end()) requestUri = it->second;
629 0 : it = qmap.find("eventNumber");
630 0 : if (it != qmap.end()) eventNumber = it->second;
631 0 : }
632 :
633 0 : h2agent::model::EventKey ekey(requestMethod, requestUri, eventNumber);
634 0 : bool success = getMockServerData()->clear(serverDataDeleted, ekey);
635 :
636 0 : statusCode = (success ? (serverDataDeleted ? ert::http2comm::ResponseCode::OK /*200*/:ert::http2comm::ResponseCode::NO_CONTENT /*204*/):ert::http2comm::ResponseCode::BAD_REQUEST /*400*/);
637 0 : }
638 0 : else if (pathSuffix == "client-data") {
639 0 : bool clientDataDeleted = false;
640 0 : std::string clientEndpointId = "";
641 0 : std::string requestMethod = "";
642 0 : std::string requestUri = "";
643 0 : std::string eventNumber = "";
644 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
645 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
646 0 : auto it = qmap.find("clientEndpointId");
647 0 : if (it != qmap.end()) clientEndpointId = it->second;
648 0 : it = qmap.find("requestMethod");
649 0 : if (it != qmap.end()) requestMethod = it->second;
650 0 : it = qmap.find("requestUri");
651 0 : if (it != qmap.end()) requestUri = it->second;
652 0 : it = qmap.find("eventNumber");
653 0 : if (it != qmap.end()) eventNumber = it->second;
654 0 : }
655 :
656 0 : h2agent::model::EventKey ekey(clientEndpointId, requestMethod, requestUri, eventNumber);
657 0 : bool success = getMockClientData()->clear(clientDataDeleted, ekey);
658 :
659 0 : statusCode = (success ? (clientDataDeleted ? ert::http2comm::ResponseCode::OK /*200*/:ert::http2comm::ResponseCode::NO_CONTENT /*204*/):ert::http2comm::ResponseCode::BAD_REQUEST /*400*/);
660 0 : }
661 0 : else if (pathSuffix == "global-variable") {
662 0 : bool globalVariableDeleted = false;
663 0 : std::string name = "";
664 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
665 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
666 0 : auto it = qmap.find("name");
667 0 : if (it != qmap.end()) name = it->second;
668 0 : if (name.empty()) {
669 0 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
670 : }
671 0 : }
672 0 : if (statusCode != ert::http2comm::ResponseCode::BAD_REQUEST) { // 400
673 0 : if (name.empty()) {
674 0 : statusCode = (getGlobalVariable()->clear() ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::NO_CONTENT); // 204
675 : }
676 : else {
677 : bool exists;
678 0 : getGlobalVariable()->removeVariable(name, exists);
679 0 : statusCode = (exists ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::NO_CONTENT); // 200 or 204
680 : }
681 : }
682 0 : }
683 : else {
684 0 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
685 : }
686 0 : }
687 :
688 0 : void MyAdminHttp2Server::receivePUT(const std::string &pathSuffix, const std::string &queryParams, unsigned int& statusCode)
689 : {
690 0 : LOGDEBUG(ert::tracing::Logger::debug("receivePUT()", ERT_FILE_LOCATION));
691 :
692 0 : bool success = false;
693 :
694 0 : if (pathSuffix == "logging") {
695 0 : std::string level = "?";
696 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
697 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
698 0 : auto it = qmap.find("level");
699 0 : if (it != qmap.end()) level = it->second;
700 0 : }
701 :
702 0 : std::string previousLevel = ert::tracing::Logger::levelAsString(ert::tracing::Logger::getLevel());
703 0 : if (level != "?") {
704 0 : success = ert::tracing::Logger::setLevel(level);
705 : }
706 :
707 : //LOGWARNING(
708 0 : if (success) {
709 0 : if (level != previousLevel)
710 0 : ert::tracing::Logger::warning(ert::tracing::Logger::asString("Log level changed: %s -> %s", previousLevel.c_str(), level.c_str()), ERT_FILE_LOCATION);
711 : else
712 0 : ert::tracing::Logger::warning(ert::tracing::Logger::asString("Log level unchanged (already %s)", previousLevel.c_str()), ERT_FILE_LOCATION);
713 : }
714 : else {
715 0 : ert::tracing::Logger::error(ert::tracing::Logger::asString("Invalid log level provided (%s). Keeping current (%s)", level.c_str(), previousLevel.c_str()), ERT_FILE_LOCATION);
716 : }
717 : //);
718 0 : }
719 0 : else if (pathSuffix == "server/configuration") {
720 0 : std::string receiveRequestBody;
721 0 : std::string preReserveRequestBody;
722 :
723 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
724 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
725 0 : auto it = qmap.find("receiveRequestBody");
726 0 : if (it != qmap.end()) receiveRequestBody = it->second;
727 0 : it = qmap.find("preReserveRequestBody");
728 0 : if (it != qmap.end()) preReserveRequestBody = it->second;
729 0 : }
730 :
731 0 : bool b_receiveRequestBody = (receiveRequestBody == "true");
732 0 : bool b_preReserveRequestBody = (preReserveRequestBody == "true");
733 :
734 0 : success = (!receiveRequestBody.empty() || !preReserveRequestBody.empty());
735 :
736 0 : if (!receiveRequestBody.empty()) {
737 0 : success = (receiveRequestBody == "true" || receiveRequestBody == "false");
738 0 : if (success) {
739 0 : getHttp2Server()->setReceiveRequestBody(b_receiveRequestBody);
740 0 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Traffic server request body reception: %s", b_receiveRequestBody ? "processed":"ignored"), ERT_FILE_LOCATION));
741 : }
742 : }
743 :
744 0 : if (success && !preReserveRequestBody.empty()) {
745 0 : success = (preReserveRequestBody == "true" || preReserveRequestBody == "false");
746 0 : if (success) {
747 0 : getHttp2Server()->setPreReserveRequestBody(b_preReserveRequestBody);
748 0 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Traffic server dynamic request body allocation: %s", b_preReserveRequestBody ? "false":"true"), ERT_FILE_LOCATION));
749 : }
750 : }
751 0 : }
752 0 : else if (pathSuffix == "server-data/configuration" || pathSuffix == "client-data/configuration") {
753 :
754 0 : std::string discard;
755 0 : std::string discardKeyHistory;
756 0 : std::string disablePurge;
757 :
758 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
759 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
760 0 : auto it = qmap.find("discard");
761 0 : if (it != qmap.end()) discard = it->second;
762 0 : it = qmap.find("discardKeyHistory");
763 0 : if (it != qmap.end()) discardKeyHistory = it->second;
764 0 : it = qmap.find("disablePurge");
765 0 : if (it != qmap.end()) disablePurge = it->second;
766 0 : }
767 :
768 0 : bool b_discard = (discard == "true");
769 0 : bool b_discardKeyHistory = (discardKeyHistory == "true");
770 0 : bool b_disablePurge = (disablePurge == "true");
771 :
772 0 : success = (!discard.empty() || !discardKeyHistory.empty() || !disablePurge.empty());
773 :
774 0 : if (success) {
775 0 : if (!discard.empty() && !discardKeyHistory.empty())
776 0 : success = !(b_discard && !b_discardKeyHistory); // it has no sense to try to keep history if whole data is discarded
777 : }
778 :
779 0 : bool serverMode = (pathSuffix == "server-data/configuration"); // true: server mode, false: client mode
780 0 : const char *mode = (serverMode ? "server":"client");
781 :
782 0 : if (success) {
783 0 : if (!discard.empty()) {
784 0 : if (serverMode) getHttp2Server()->discardData(b_discard);
785 0 : else discardClientData(b_discard);
786 0 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Discard %s-data: %s", mode, b_discard ? "true":"false"), ERT_FILE_LOCATION));
787 : }
788 0 : if (!discardKeyHistory.empty()) {
789 0 : if (serverMode) getHttp2Server()->discardDataKeyHistory(b_discardKeyHistory);
790 0 : else discardClientDataKeyHistory(b_discardKeyHistory);
791 0 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Discard %s-data key history: %s", mode, b_discardKeyHistory ? "true":"false"), ERT_FILE_LOCATION));
792 : }
793 0 : if (!disablePurge.empty()) {
794 0 : if (serverMode) getHttp2Server()->disablePurge(b_disablePurge);
795 0 : else disableClientPurge(b_disablePurge);
796 0 : LOGWARNING(
797 : ert::tracing::Logger::warning(ert::tracing::Logger::asString("Disable %s purge execution: %s", mode, b_disablePurge ? "true":"false"), ERT_FILE_LOCATION);
798 : if (!b_disablePurge && b_discardKeyHistory)
799 : ert::tracing::Logger::warning(ert::tracing::Logger::asString("Purge execution will be limited as history is discarded for %s data", mode), ERT_FILE_LOCATION);
800 : if (!b_disablePurge && b_discard)
801 : ert::tracing::Logger::warning(ert::tracing::Logger::asString("Purge execution has no sense as no events will be stored at %s storage", mode), ERT_FILE_LOCATION);
802 : );
803 : }
804 : }
805 : else {
806 0 : ert::tracing::Logger::error(ert::tracing::Logger::asString("Cannot keep requests history if %s data storage is discarded", mode), ERT_FILE_LOCATION);
807 : }
808 0 : }
809 0 : else if (pathSuffix == "files/configuration") {
810 0 : std::string readCache;
811 :
812 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
813 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
814 0 : auto it = qmap.find("readCache");
815 0 : if (it != qmap.end()) readCache = it->second;
816 :
817 0 : success = (readCache == "true" || readCache == "false");
818 0 : }
819 :
820 0 : if (success) {
821 0 : bool b_readCache = (readCache == "true");
822 0 : getFileManager()->enableReadCache(b_readCache);
823 0 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("File read cache: %s", b_readCache ? "true":"false"), ERT_FILE_LOCATION));
824 : }
825 0 : }
826 :
827 0 : statusCode = success ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::BAD_REQUEST; // 200 or 400
828 0 : }
829 :
830 46 : void MyAdminHttp2Server::receive(const std::uint64_t &receptionId,
831 : const nghttp2::asio_http2::server::request&
832 : req,
833 : const std::string &requestBody,
834 : const std::chrono::microseconds &receptionTimestampUs,
835 : unsigned int& statusCode, nghttp2::asio_http2::header_map& headers,
836 : std::string& responseBody, unsigned int &responseDelayMs)
837 : {
838 46 : LOGDEBUG(ert::tracing::Logger::debug("receive()", ERT_FILE_LOCATION));
839 :
840 : // see uri_ref struct (https://nghttp2.org/documentation/asio_http2.h.html#asio-http2-h)
841 46 : std::string method = req.method();
842 : //std::string uriPath = req.uri().raw_path; // percent-encoded
843 46 : std::string uriPath = req.uri().path; // decoded
844 46 : std::string uriQuery = req.uri().raw_query; // parameter values may be percent-encoded
845 :
846 : // Get path suffix normalized:
847 46 : std::string pathSuffix = getPathSuffix(uriPath);
848 46 : bool noPathSuffix = pathSuffix.empty();
849 46 : LOGDEBUG(
850 : if (noPathSuffix) {
851 : ert::tracing::Logger::debug("URI Path Suffix: <null>", ERT_FILE_LOCATION);
852 : }
853 : else {
854 : std::stringstream ss;
855 : ss << "ADMIN REQUEST RECEIVED | Method: " << method
856 : << " | Headers: " << ert::http2comm::headersAsString(req.header())
857 : << " | Uri: " << req.uri().scheme << "://" << req.uri().host << uriPath;
858 : if (!uriQuery.empty()) {
859 : ss << " | Query Params: " << uriQuery;
860 : }
861 : if (!requestBody.empty()) {
862 : std::string requestBodyWithoutNewlines = requestBody; // administrative interface receives json bodies in POST requests, so we normalize for logging
863 : requestBodyWithoutNewlines.erase(std::remove(requestBodyWithoutNewlines.begin(), requestBodyWithoutNewlines.end(), '\n'), requestBodyWithoutNewlines.end());
864 : ss << " | Body: " << requestBodyWithoutNewlines;
865 : }
866 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
867 : }
868 : );
869 :
870 : // Defaults
871 46 : responseBody.clear();
872 :
873 : // No operation provided:
874 46 : if (noPathSuffix) {
875 2 : receiveNOOP(statusCode, headers, responseBody);
876 2 : return;
877 : }
878 :
879 : // Methods supported:
880 44 : if (method == "DELETE") {
881 0 : receiveDELETE(pathSuffix, uriQuery, statusCode);
882 0 : headers.clear();
883 0 : return;
884 : }
885 44 : else if (method == "GET") {
886 46 : receiveGET(req.uri().scheme + std::string("://") + req.uri().host + uriPath /* schema $id */, pathSuffix, uriQuery, statusCode, headers, responseBody);
887 23 : return;
888 : }
889 21 : else if (method == "POST") {
890 21 : receivePOST(pathSuffix, requestBody, statusCode, headers, responseBody);
891 21 : return;
892 : }
893 0 : else if (method == "PUT") {
894 0 : receivePUT(pathSuffix, uriQuery, statusCode);
895 0 : headers.clear();
896 0 : return;
897 : }
898 184 : }
899 :
900 0 : void MyAdminHttp2Server::triggerClientOperation(const std::string &clientProvisionId, const std::string &queryParams, unsigned int& statusCode) const {
901 :
902 0 : std::string inState = DEFAULT_ADMIN_PROVISION_STATE; // administrative operation triggers "initial" provisions by default
903 0 : std::string sequenceBegin = "";
904 0 : std::string sequenceEnd = "";
905 0 : std::string rps = "";
906 0 : std::string repeat = "";
907 :
908 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
909 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
910 0 : auto it = qmap.find("inState");
911 0 : if (it != qmap.end()) inState = it->second;
912 0 : it = qmap.find("sequenceBegin");
913 0 : if (it != qmap.end()) sequenceBegin = it->second;
914 0 : it = qmap.find("sequenceEnd");
915 0 : if (it != qmap.end()) sequenceEnd = it->second;
916 0 : it = qmap.find("rps");
917 0 : if (it != qmap.end()) rps = it->second;
918 0 : it = qmap.find("repeat");
919 0 : if (it != qmap.end()) repeat = it->second;
920 0 : }
921 :
922 : // Admin provision:
923 0 : const h2agent::model::AdminClientProvisionData & provisionData = getAdminData()->getClientProvisionData();
924 0 : std::shared_ptr<h2agent::model::AdminClientProvision> provision = provisionData.find(inState, clientProvisionId);
925 :
926 0 : if (!provision) {
927 0 : statusCode = ert::http2comm::ResponseCode::NOT_FOUND; // 404
928 0 : return;
929 : }
930 :
931 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
932 0 : if (!sequenceBegin.empty() || !sequenceEnd.empty() || !rps.empty() || !repeat.empty()) {
933 0 : if (provision->updateTriggering(sequenceBegin, sequenceEnd, rps, repeat)) {
934 0 : statusCode = ert::http2comm::ResponseCode::ACCEPTED; // 202; "sender" operates asynchronously
935 : }
936 : else {
937 0 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
938 0 : return;
939 : }
940 : }
941 :
942 : // Process provision (before sending)
943 0 : provision->employ(); // set provision as employed:
944 0 : std::string requestMethod{};
945 0 : std::string requestUri{};
946 0 : std::string requestBody{};
947 0 : nghttp2::asio_http2::header_map requestHeaders;
948 :
949 0 : std::string outState{};
950 0 : unsigned int requestDelayMs{};
951 0 : unsigned int requestTimeoutMs{};
952 :
953 0 : std::string error{}; // error detail (empty when all is OK)
954 :
955 0 : provision->transform(requestMethod, requestUri, requestBody, requestHeaders, outState, requestDelayMs, requestTimeoutMs, error);
956 0 : LOGDEBUG(
957 : ert::tracing::Logger::debug(ert::tracing::Logger::asString("Request method: %s", requestMethod.c_str()), ERT_FILE_LOCATION);
958 : ert::tracing::Logger::debug(ert::tracing::Logger::asString("Request uri: %s", requestUri.c_str()), ERT_FILE_LOCATION);
959 : ert::tracing::Logger::debug(ert::tracing::Logger::asString("Request body: %s", requestBody.c_str()), ERT_FILE_LOCATION);
960 : );
961 :
962 0 : if (error.empty()) {
963 0 : const h2agent::model::AdminClientEndpointData & clientEndpointData = getAdminData()->getClientEndpointData();
964 0 : std::shared_ptr<h2agent::model::AdminClientEndpoint> clientEndpoint(nullptr);
965 0 : clientEndpoint = clientEndpointData.find(provision->getClientEndpointId());
966 0 : if (clientEndpoint) {
967 0 : if (clientEndpoint->getPermit()) {
968 0 : clientEndpoint->connect();
969 0 : ert::http2comm::Http2Client::response response = clientEndpoint->getClient()->send(requestMethod, requestUri, requestBody, requestHeaders, std::chrono::milliseconds(requestTimeoutMs));
970 0 : clientEndpoint->getClient()->incrementGeneralUniqueClientSequence();
971 :
972 : // Store event:
973 0 : if (client_data_) {
974 0 : h2agent::model::DataKey dataKey(provision->getClientEndpointId(), requestMethod, requestUri);
975 0 : h2agent::model::DataPart responseBodyDataPart(response.body);
976 0 : getMockClientData()->loadEvent(dataKey, provision->getClientProvisionId(), inState, outState, response.sendingUs, response.receptionUs, response.statusCode, requestHeaders, response.headers, requestBody, responseBodyDataPart, clientEndpoint->getGeneralUniqueClientSequence(), provision->getSeq(), requestDelayMs, requestTimeoutMs, client_data_key_history_ /* history enabled */);
977 0 : }
978 0 : }
979 : else {
980 0 : error = "Referenced client endpoint is disabled (not permitted)";
981 : }
982 : }
983 : else {
984 0 : error = "Referenced client endpoint is not provisioned";
985 : }
986 0 : }
987 : else {
988 0 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
989 : }
990 0 : }
991 :
992 0 : std::string MyAdminHttp2Server::clientDataConfigurationAsJsonString() const {
993 0 : nlohmann::json result;
994 :
995 0 : result["storeEvents"] = client_data_;
996 0 : result["storeEventsKeyHistory"] = client_data_key_history_;
997 0 : result["purgeExecution"] = purge_execution_;
998 :
999 0 : return result.dump();
1000 0 : }
1001 :
1002 : }
1003 : }
|