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 0 : bool exists = getGlobalVariable()->tryGet(name, responseBody);
452 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)
453 : }
454 : }
455 4 : }
456 19 : else if (pathSuffix == "global-variable/schema") {
457 : // Add the $id field dynamically (full URI including scheme/host)
458 0 : nlohmann::json jsonSchema = getGlobalVariable()->getSchema().getJson();
459 0 : jsonSchema["$id"] = uri;
460 0 : responseBody = jsonSchema.dump();
461 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
462 0 : }
463 19 : else if (pathSuffix == "server-matching") {
464 7 : responseBody = getAdminData()->getServerMatchingData().getJson().dump();
465 7 : statusCode = ert::http2comm::ResponseCode::OK; // 200
466 : }
467 12 : else if (pathSuffix == "server-provision") {
468 4 : bool ordered = (getAdminData()->getServerMatchingData().getAlgorithm() == h2agent::model::AdminServerMatchingData::RegexMatching);
469 4 : responseBody = getAdminData()->getServerProvisionData().asJsonString(ordered);
470 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)
471 : }
472 8 : else if (pathSuffix == "server-provision/unused") {
473 4 : bool ordered = (getAdminData()->getServerMatchingData().getAlgorithm() == h2agent::model::AdminServerMatchingData::RegexMatching);
474 4 : responseBody = getAdminData()->getServerProvisionData().asJsonString(ordered, true /*unused*/);
475 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)
476 : }
477 4 : else if (pathSuffix == "client-endpoint") {
478 0 : responseBody = getAdminData()->getClientEndpointData().asJsonString();
479 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)
480 : }
481 4 : else if (pathSuffix == "client-provision") {
482 0 : responseBody = getAdminData()->getClientProvisionData().asJsonString();
483 0 : statusCode = ((responseBody == "[]") ? 204:200); // response body will be emptied by nghttp2 when status code is 204 (No Content)
484 : }
485 4 : else if (pathSuffix == "client-provision/unused") {
486 0 : responseBody = getAdminData()->getClientProvisionData().asJsonString(true /*unused*/);
487 0 : statusCode = ((responseBody == "[]") ? 204:200); // response body will be emptied by nghttp2 when status code is 204 (No Content)
488 : }
489 4 : else if (std::regex_match(pathSuffix, matches, clientProvisionId)) { // client-provision/<client provision id>
490 0 : triggerClientOperation(matches.str(1), queryParams, statusCode);
491 0 : bool result = statusCodeOK(statusCode);
492 0 : responseBody = buildJsonResponse(result, (result ? "operation processed":"operation failed"));
493 : }
494 4 : else if (pathSuffix == "schema") {
495 4 : responseBody = getAdminData()->getSchemaData().asJsonString();
496 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)
497 : }
498 0 : else if (pathSuffix == "server-data") {
499 0 : std::string requestMethod = "";
500 0 : std::string requestUri = "";
501 0 : std::string eventNumber = "";
502 0 : std::string eventPath = "";
503 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
504 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
505 0 : auto it = qmap.find("requestMethod");
506 0 : if (it != qmap.end()) requestMethod = it->second;
507 0 : it = qmap.find("requestUri");
508 0 : if (it != qmap.end()) requestUri = it->second;
509 0 : it = qmap.find("eventNumber");
510 0 : if (it != qmap.end()) eventNumber = it->second;
511 0 : it = qmap.find("eventPath");
512 0 : if (it != qmap.end()) eventPath = it->second;
513 0 : }
514 :
515 0 : bool validQuery = false;
516 : try { // dump could throw exception if something weird is done (binary data with non-binary content-type)
517 0 : h2agent::model::EventLocationKey elkey(requestMethod, requestUri, eventNumber, eventPath);
518 0 : responseBody = getMockServerData()->asJsonString(elkey, validQuery);
519 0 : }
520 0 : catch (const std::exception& e)
521 : {
522 : //validQuery = false; // will be ert::http2comm::ResponseCode::OK (200) with empty result (corner case)
523 0 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
524 0 : }
525 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)
526 0 : }
527 0 : else if (pathSuffix == "client-data") {
528 0 : std::string clientEndpointId = "";
529 0 : std::string requestMethod = "";
530 0 : std::string requestUri = "";
531 0 : std::string eventNumber = "";
532 0 : std::string eventPath = "";
533 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
534 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
535 0 : auto it = qmap.find("clientEndpointId");
536 0 : if (it != qmap.end()) clientEndpointId = it->second;
537 0 : it = qmap.find("requestMethod");
538 0 : if (it != qmap.end()) requestMethod = it->second;
539 0 : it = qmap.find("requestUri");
540 0 : if (it != qmap.end()) requestUri = it->second;
541 0 : it = qmap.find("eventNumber");
542 0 : if (it != qmap.end()) eventNumber = it->second;
543 0 : it = qmap.find("eventPath");
544 0 : if (it != qmap.end()) eventPath = it->second;
545 0 : }
546 :
547 0 : bool validQuery = false;
548 : try { // dump could throw exception if something weird is done (binary data with non-binary content-type)
549 0 : h2agent::model::EventLocationKey elkey(clientEndpointId, requestMethod, requestUri, eventNumber, eventPath);
550 0 : responseBody = getMockClientData()->asJsonString(elkey, validQuery);
551 0 : }
552 0 : catch (const std::exception& e)
553 : {
554 : //validQuery = false; // will be ert::http2comm::ResponseCode::OK (200) with empty result (corner case)
555 0 : ert::tracing::Logger::error(e.what(), ERT_FILE_LOCATION);
556 0 : }
557 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)
558 0 : }
559 0 : else if (pathSuffix == "configuration") {
560 0 : responseBody = getConfiguration()->asJsonString();
561 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
562 : }
563 0 : else if (pathSuffix == "server/configuration") {
564 0 : responseBody = getHttp2Server()->configurationAsJsonString();
565 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
566 : }
567 0 : else if (pathSuffix == "server-data/configuration") {
568 0 : responseBody = getHttp2Server()->dataConfigurationAsJsonString();
569 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
570 : }
571 0 : else if (pathSuffix == "client-data/configuration") {
572 0 : responseBody = clientDataConfigurationAsJsonString();
573 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
574 : }
575 0 : else if (pathSuffix == "files/configuration") {
576 0 : responseBody = getFileManager()->configurationAsJsonString();
577 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
578 : }
579 0 : else if (pathSuffix == "files") {
580 0 : responseBody = getFileManager()->asJsonString();
581 0 : statusCode = ((responseBody == "[]") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK); // 204 or 200
582 : }
583 0 : else if (pathSuffix == "udp-sockets") {
584 0 : responseBody = getSocketManager()->asJsonString();
585 0 : statusCode = ((responseBody == "[]") ? ert::http2comm::ResponseCode::NO_CONTENT:ert::http2comm::ResponseCode::OK); // 204 or 200
586 : }
587 0 : else if (pathSuffix == "logging") {
588 0 : responseBody = ert::tracing::Logger::levelAsString(ert::tracing::Logger::getLevel());
589 0 : headers.emplace("content-type", nghttp2::asio_http2::header_value{"text/html"});
590 0 : jsonContent = false;
591 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
592 : }
593 : else {
594 0 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
595 0 : responseBody = buildJsonResponse(false, std::string("invalid operation '") + pathSuffix + std::string("'"));
596 : }
597 :
598 69 : if (jsonContent) headers.emplace("content-type", nghttp2::asio_http2::header_value{"application/json"});
599 23 : }
600 :
601 0 : void MyAdminHttp2Server::receiveDELETE(const std::string &pathSuffix, const std::string &queryParams, unsigned int& statusCode) const
602 : {
603 0 : LOGDEBUG(ert::tracing::Logger::debug("receiveDELETE()", ERT_FILE_LOCATION));
604 :
605 0 : if (pathSuffix == "server-provision") {
606 0 : statusCode = (getAdminData()->clearServerProvisions() ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::NO_CONTENT); // 200 or 204
607 : }
608 0 : else if (pathSuffix == "client-endpoint") {
609 0 : statusCode = (getAdminData()->clearClientEndpoints() ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::NO_CONTENT); // 200 or 204
610 : }
611 0 : else if (pathSuffix == "client-provision") {
612 0 : statusCode = (getAdminData()->clearClientProvisions() ? 200:204);
613 : }
614 0 : else if (pathSuffix == "schema") {
615 0 : statusCode = (getAdminData()->clearSchemas() ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::NO_CONTENT); // 200 or 204
616 : }
617 0 : else if (pathSuffix == "server-data") {
618 0 : bool serverDataDeleted = false;
619 0 : std::string requestMethod = "";
620 0 : std::string requestUri = "";
621 0 : std::string eventNumber = "";
622 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
623 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
624 0 : auto it = qmap.find("requestMethod");
625 0 : if (it != qmap.end()) requestMethod = it->second;
626 0 : it = qmap.find("requestUri");
627 0 : if (it != qmap.end()) requestUri = it->second;
628 0 : it = qmap.find("eventNumber");
629 0 : if (it != qmap.end()) eventNumber = it->second;
630 0 : }
631 :
632 0 : h2agent::model::EventKey ekey(requestMethod, requestUri, eventNumber);
633 0 : bool success = getMockServerData()->clear(serverDataDeleted, ekey);
634 :
635 0 : statusCode = (success ? (serverDataDeleted ? ert::http2comm::ResponseCode::OK /*200*/:ert::http2comm::ResponseCode::NO_CONTENT /*204*/):ert::http2comm::ResponseCode::BAD_REQUEST /*400*/);
636 0 : }
637 0 : else if (pathSuffix == "client-data") {
638 0 : bool clientDataDeleted = false;
639 0 : std::string clientEndpointId = "";
640 0 : std::string requestMethod = "";
641 0 : std::string requestUri = "";
642 0 : std::string eventNumber = "";
643 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
644 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
645 0 : auto it = qmap.find("clientEndpointId");
646 0 : if (it != qmap.end()) clientEndpointId = it->second;
647 0 : it = qmap.find("requestMethod");
648 0 : if (it != qmap.end()) requestMethod = it->second;
649 0 : it = qmap.find("requestUri");
650 0 : if (it != qmap.end()) requestUri = it->second;
651 0 : it = qmap.find("eventNumber");
652 0 : if (it != qmap.end()) eventNumber = it->second;
653 0 : }
654 :
655 0 : h2agent::model::EventKey ekey(clientEndpointId, requestMethod, requestUri, eventNumber);
656 0 : bool success = getMockClientData()->clear(clientDataDeleted, ekey);
657 :
658 0 : statusCode = (success ? (clientDataDeleted ? ert::http2comm::ResponseCode::OK /*200*/:ert::http2comm::ResponseCode::NO_CONTENT /*204*/):ert::http2comm::ResponseCode::BAD_REQUEST /*400*/);
659 0 : }
660 0 : else if (pathSuffix == "global-variable") {
661 0 : bool globalVariableDeleted = false;
662 0 : std::string name = "";
663 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
664 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
665 0 : auto it = qmap.find("name");
666 0 : if (it != qmap.end()) name = it->second;
667 0 : if (name.empty()) {
668 0 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
669 : }
670 0 : }
671 0 : if (statusCode != ert::http2comm::ResponseCode::BAD_REQUEST) { // 400
672 0 : if (name.empty()) {
673 0 : statusCode = (getGlobalVariable()->clear() ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::NO_CONTENT); // 204
674 : }
675 : else {
676 : bool exists;
677 0 : getGlobalVariable()->remove(name, exists);
678 0 : statusCode = (exists ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::NO_CONTENT); // 200 or 204
679 : }
680 : }
681 0 : }
682 : else {
683 0 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
684 : }
685 0 : }
686 :
687 0 : void MyAdminHttp2Server::receivePUT(const std::string &pathSuffix, const std::string &queryParams, unsigned int& statusCode)
688 : {
689 0 : LOGDEBUG(ert::tracing::Logger::debug("receivePUT()", ERT_FILE_LOCATION));
690 :
691 0 : bool success = false;
692 :
693 0 : if (pathSuffix == "logging") {
694 0 : std::string level = "?";
695 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
696 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
697 0 : auto it = qmap.find("level");
698 0 : if (it != qmap.end()) level = it->second;
699 0 : }
700 :
701 0 : std::string previousLevel = ert::tracing::Logger::levelAsString(ert::tracing::Logger::getLevel());
702 0 : if (level != "?") {
703 0 : success = ert::tracing::Logger::setLevel(level);
704 : }
705 :
706 : //LOGWARNING(
707 0 : if (success) {
708 0 : if (level != previousLevel)
709 0 : ert::tracing::Logger::warning(ert::tracing::Logger::asString("Log level changed: %s -> %s", previousLevel.c_str(), level.c_str()), ERT_FILE_LOCATION);
710 : else
711 0 : ert::tracing::Logger::warning(ert::tracing::Logger::asString("Log level unchanged (already %s)", previousLevel.c_str()), ERT_FILE_LOCATION);
712 : }
713 : else {
714 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);
715 : }
716 : //);
717 0 : }
718 0 : else if (pathSuffix == "server/configuration") {
719 0 : std::string receiveRequestBody;
720 0 : std::string preReserveRequestBody;
721 :
722 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
723 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
724 0 : auto it = qmap.find("receiveRequestBody");
725 0 : if (it != qmap.end()) receiveRequestBody = it->second;
726 0 : it = qmap.find("preReserveRequestBody");
727 0 : if (it != qmap.end()) preReserveRequestBody = it->second;
728 0 : }
729 :
730 0 : bool b_receiveRequestBody = (receiveRequestBody == "true");
731 0 : bool b_preReserveRequestBody = (preReserveRequestBody == "true");
732 :
733 0 : success = (!receiveRequestBody.empty() || !preReserveRequestBody.empty());
734 :
735 0 : if (!receiveRequestBody.empty()) {
736 0 : success = (receiveRequestBody == "true" || receiveRequestBody == "false");
737 0 : if (success) {
738 0 : getHttp2Server()->setReceiveRequestBody(b_receiveRequestBody);
739 0 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Traffic server request body reception: %s", b_receiveRequestBody ? "processed":"ignored"), ERT_FILE_LOCATION));
740 : }
741 : }
742 :
743 0 : if (success && !preReserveRequestBody.empty()) {
744 0 : success = (preReserveRequestBody == "true" || preReserveRequestBody == "false");
745 0 : if (success) {
746 0 : getHttp2Server()->setPreReserveRequestBody(b_preReserveRequestBody);
747 0 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Traffic server dynamic request body allocation: %s", b_preReserveRequestBody ? "false":"true"), ERT_FILE_LOCATION));
748 : }
749 : }
750 0 : }
751 0 : else if (pathSuffix == "server-data/configuration" || pathSuffix == "client-data/configuration") {
752 :
753 0 : std::string discard;
754 0 : std::string discardKeyHistory;
755 0 : std::string disablePurge;
756 :
757 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
758 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
759 0 : auto it = qmap.find("discard");
760 0 : if (it != qmap.end()) discard = it->second;
761 0 : it = qmap.find("discardKeyHistory");
762 0 : if (it != qmap.end()) discardKeyHistory = it->second;
763 0 : it = qmap.find("disablePurge");
764 0 : if (it != qmap.end()) disablePurge = it->second;
765 0 : }
766 :
767 0 : bool b_discard = (discard == "true");
768 0 : bool b_discardKeyHistory = (discardKeyHistory == "true");
769 0 : bool b_disablePurge = (disablePurge == "true");
770 :
771 0 : success = (!discard.empty() || !discardKeyHistory.empty() || !disablePurge.empty());
772 :
773 0 : if (success) {
774 0 : if (!discard.empty() && !discardKeyHistory.empty())
775 0 : success = !(b_discard && !b_discardKeyHistory); // it has no sense to try to keep history if whole data is discarded
776 : }
777 :
778 0 : bool serverMode = (pathSuffix == "server-data/configuration"); // true: server mode, false: client mode
779 0 : const char *mode = (serverMode ? "server":"client");
780 :
781 0 : if (success) {
782 0 : if (!discard.empty()) {
783 0 : if (serverMode) getHttp2Server()->discardData(b_discard);
784 0 : else discardClientData(b_discard);
785 0 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Discard %s-data: %s", mode, b_discard ? "true":"false"), ERT_FILE_LOCATION));
786 : }
787 0 : if (!discardKeyHistory.empty()) {
788 0 : if (serverMode) getHttp2Server()->discardDataKeyHistory(b_discardKeyHistory);
789 0 : else discardClientDataKeyHistory(b_discardKeyHistory);
790 0 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Discard %s-data key history: %s", mode, b_discardKeyHistory ? "true":"false"), ERT_FILE_LOCATION));
791 : }
792 0 : if (!disablePurge.empty()) {
793 0 : if (serverMode) getHttp2Server()->disablePurge(b_disablePurge);
794 0 : else disableClientPurge(b_disablePurge);
795 0 : LOGWARNING(
796 : ert::tracing::Logger::warning(ert::tracing::Logger::asString("Disable %s purge execution: %s", mode, b_disablePurge ? "true":"false"), ERT_FILE_LOCATION);
797 : if (!b_disablePurge && b_discardKeyHistory)
798 : ert::tracing::Logger::warning(ert::tracing::Logger::asString("Purge execution will be limited as history is discarded for %s data", mode), ERT_FILE_LOCATION);
799 : if (!b_disablePurge && b_discard)
800 : 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);
801 : );
802 : }
803 : }
804 : else {
805 0 : ert::tracing::Logger::error(ert::tracing::Logger::asString("Cannot keep requests history if %s data storage is discarded", mode), ERT_FILE_LOCATION);
806 : }
807 0 : }
808 0 : else if (pathSuffix == "files/configuration") {
809 0 : std::string readCache;
810 :
811 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
812 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
813 0 : auto it = qmap.find("readCache");
814 0 : if (it != qmap.end()) readCache = it->second;
815 :
816 0 : success = (readCache == "true" || readCache == "false");
817 0 : }
818 :
819 0 : if (success) {
820 0 : bool b_readCache = (readCache == "true");
821 0 : getFileManager()->enableReadCache(b_readCache);
822 0 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("File read cache: %s", b_readCache ? "true":"false"), ERT_FILE_LOCATION));
823 : }
824 0 : }
825 :
826 0 : statusCode = success ? ert::http2comm::ResponseCode::OK:ert::http2comm::ResponseCode::BAD_REQUEST; // 200 or 400
827 0 : }
828 :
829 46 : void MyAdminHttp2Server::receive(const std::uint64_t &receptionId,
830 : const nghttp2::asio_http2::server::request&
831 : req,
832 : const std::string &requestBody,
833 : const std::chrono::microseconds &receptionTimestampUs,
834 : unsigned int& statusCode, nghttp2::asio_http2::header_map& headers,
835 : std::string& responseBody, unsigned int &responseDelayMs)
836 : {
837 46 : LOGDEBUG(ert::tracing::Logger::debug("receive()", ERT_FILE_LOCATION));
838 :
839 : // see uri_ref struct (https://nghttp2.org/documentation/asio_http2.h.html#asio-http2-h)
840 46 : std::string method = req.method();
841 : //std::string uriPath = req.uri().raw_path; // percent-encoded
842 46 : std::string uriPath = req.uri().path; // decoded
843 46 : std::string uriQuery = req.uri().raw_query; // parameter values may be percent-encoded
844 :
845 : // Get path suffix normalized:
846 46 : std::string pathSuffix = getPathSuffix(uriPath);
847 46 : bool noPathSuffix = pathSuffix.empty();
848 46 : LOGDEBUG(
849 : if (noPathSuffix) {
850 : ert::tracing::Logger::debug("URI Path Suffix: <null>", ERT_FILE_LOCATION);
851 : }
852 : else {
853 : std::stringstream ss;
854 : ss << "ADMIN REQUEST RECEIVED | Method: " << method
855 : << " | Headers: " << ert::http2comm::headersAsString(req.header())
856 : << " | Uri: " << req.uri().scheme << "://" << req.uri().host << uriPath;
857 : if (!uriQuery.empty()) {
858 : ss << " | Query Params: " << uriQuery;
859 : }
860 : if (!requestBody.empty()) {
861 : std::string requestBodyWithoutNewlines = requestBody; // administrative interface receives json bodies in POST requests, so we normalize for logging
862 : requestBodyWithoutNewlines.erase(std::remove(requestBodyWithoutNewlines.begin(), requestBodyWithoutNewlines.end(), '\n'), requestBodyWithoutNewlines.end());
863 : ss << " | Body: " << requestBodyWithoutNewlines;
864 : }
865 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
866 : }
867 : );
868 :
869 : // Defaults
870 46 : responseBody.clear();
871 :
872 : // No operation provided:
873 46 : if (noPathSuffix) {
874 2 : receiveNOOP(statusCode, headers, responseBody);
875 2 : return;
876 : }
877 :
878 : // Methods supported:
879 44 : if (method == "DELETE") {
880 0 : receiveDELETE(pathSuffix, uriQuery, statusCode);
881 0 : headers.clear();
882 0 : return;
883 : }
884 44 : else if (method == "GET") {
885 46 : receiveGET(req.uri().scheme + std::string("://") + req.uri().host + uriPath /* schema $id */, pathSuffix, uriQuery, statusCode, headers, responseBody);
886 23 : return;
887 : }
888 21 : else if (method == "POST") {
889 21 : receivePOST(pathSuffix, requestBody, statusCode, headers, responseBody);
890 21 : return;
891 : }
892 0 : else if (method == "PUT") {
893 0 : receivePUT(pathSuffix, uriQuery, statusCode);
894 0 : headers.clear();
895 0 : return;
896 : }
897 184 : }
898 :
899 0 : void MyAdminHttp2Server::triggerClientOperation(const std::string &clientProvisionId, const std::string &queryParams, unsigned int& statusCode) const {
900 :
901 0 : std::string inState = DEFAULT_ADMIN_PROVISION_STATE; // administrative operation triggers "initial" provisions by default
902 0 : std::string sequenceBegin = "";
903 0 : std::string sequenceEnd = "";
904 0 : std::string rps = "";
905 0 : std::string repeat = "";
906 :
907 0 : if (!queryParams.empty()) { // https://stackoverflow.com/questions/978061/http-get-with-request-body#:~:text=Yes.,semantic%20meaning%20to%20the%20request.
908 0 : std::map<std::string, std::string> qmap = h2agent::model::extractQueryParameters(queryParams);
909 0 : auto it = qmap.find("inState");
910 0 : if (it != qmap.end()) inState = it->second;
911 0 : it = qmap.find("sequenceBegin");
912 0 : if (it != qmap.end()) sequenceBegin = it->second;
913 0 : it = qmap.find("sequenceEnd");
914 0 : if (it != qmap.end()) sequenceEnd = it->second;
915 0 : it = qmap.find("rps");
916 0 : if (it != qmap.end()) rps = it->second;
917 0 : it = qmap.find("repeat");
918 0 : if (it != qmap.end()) repeat = it->second;
919 0 : }
920 :
921 : // Admin provision:
922 0 : const h2agent::model::AdminClientProvisionData & provisionData = getAdminData()->getClientProvisionData();
923 0 : std::shared_ptr<h2agent::model::AdminClientProvision> provision = provisionData.find(inState, clientProvisionId);
924 :
925 0 : if (!provision) {
926 0 : statusCode = ert::http2comm::ResponseCode::NOT_FOUND; // 404
927 0 : return;
928 : }
929 :
930 0 : statusCode = ert::http2comm::ResponseCode::OK; // 200
931 0 : if (!sequenceBegin.empty() || !sequenceEnd.empty() || !rps.empty() || !repeat.empty()) {
932 0 : if (provision->updateTriggering(sequenceBegin, sequenceEnd, rps, repeat)) {
933 0 : statusCode = ert::http2comm::ResponseCode::ACCEPTED; // 202; "sender" operates asynchronously
934 : }
935 : else {
936 0 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
937 0 : return;
938 : }
939 : }
940 :
941 : // Process provision (before sending)
942 0 : provision->employ(); // set provision as employed:
943 0 : std::string requestMethod{};
944 0 : std::string requestUri{};
945 0 : std::string requestBody{};
946 0 : nghttp2::asio_http2::header_map requestHeaders;
947 :
948 0 : std::string outState{};
949 0 : unsigned int requestDelayMs{};
950 0 : unsigned int requestTimeoutMs{};
951 :
952 0 : std::string error{}; // error detail (empty when all is OK)
953 :
954 0 : provision->transform(requestMethod, requestUri, requestBody, requestHeaders, outState, requestDelayMs, requestTimeoutMs, error);
955 0 : LOGDEBUG(
956 : ert::tracing::Logger::debug(ert::tracing::Logger::asString("Request method: %s", requestMethod.c_str()), ERT_FILE_LOCATION);
957 : ert::tracing::Logger::debug(ert::tracing::Logger::asString("Request uri: %s", requestUri.c_str()), ERT_FILE_LOCATION);
958 : ert::tracing::Logger::debug(ert::tracing::Logger::asString("Request body: %s", requestBody.c_str()), ERT_FILE_LOCATION);
959 : );
960 :
961 0 : if (error.empty()) {
962 0 : const h2agent::model::AdminClientEndpointData & clientEndpointData = getAdminData()->getClientEndpointData();
963 0 : std::shared_ptr<h2agent::model::AdminClientEndpoint> clientEndpoint(nullptr);
964 0 : clientEndpoint = clientEndpointData.find(provision->getClientEndpointId());
965 0 : if (clientEndpoint) {
966 0 : if (clientEndpoint->getPermit()) {
967 0 : clientEndpoint->connect();
968 0 : ert::http2comm::Http2Client::response response = clientEndpoint->getClient()->send(requestMethod, requestUri, requestBody, requestHeaders, std::chrono::milliseconds(requestTimeoutMs));
969 0 : clientEndpoint->getClient()->incrementGeneralUniqueClientSequence();
970 :
971 : // Store event:
972 0 : if (client_data_) {
973 0 : h2agent::model::DataKey dataKey(provision->getClientEndpointId(), requestMethod, requestUri);
974 0 : h2agent::model::DataPart responseBodyDataPart(response.body);
975 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 */);
976 0 : }
977 0 : }
978 : else {
979 0 : error = "Referenced client endpoint is disabled (not permitted)";
980 : }
981 : }
982 : else {
983 0 : error = "Referenced client endpoint is not provisioned";
984 : }
985 0 : }
986 : else {
987 0 : statusCode = ert::http2comm::ResponseCode::BAD_REQUEST; // 400
988 : }
989 0 : }
990 :
991 0 : std::string MyAdminHttp2Server::clientDataConfigurationAsJsonString() const {
992 0 : nlohmann::json result;
993 :
994 0 : result["storeEvents"] = client_data_;
995 0 : result["storeEventsKeyHistory"] = client_data_key_history_;
996 0 : result["purgeExecution"] = purge_execution_;
997 :
998 0 : return result.dump();
999 0 : }
1000 :
1001 : }
1002 : }
|