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 : #include <sstream>
38 : #include <map>
39 : #include <errno.h>
40 :
41 :
42 : #include <ert/tracing/Logger.hpp>
43 : #include <ert/http2comm/Http.hpp>
44 : #include <ert/http2comm/Http2Headers.hpp>
45 :
46 : #include <MyTrafficHttp2Server.hpp>
47 :
48 : #include <AdminData.hpp>
49 : #include <MockServerData.hpp>
50 : #include <Configuration.hpp>
51 : #include <GlobalVariable.hpp>
52 : #include <FileManager.hpp>
53 : #include <SocketManager.hpp>
54 : #include <functions.hpp>
55 :
56 : namespace h2agent
57 : {
58 : namespace http2
59 : {
60 :
61 :
62 49 : MyTrafficHttp2Server::MyTrafficHttp2Server(const std::string &name, size_t workerThreads, size_t maxWorkerThreads, boost::asio::io_context *timersIoContext, int maxQueueDispatcherSize):
63 : ert::http2comm::Http2Server(name, workerThreads, maxWorkerThreads, timersIoContext, maxQueueDispatcherSize),
64 49 : admin_data_(nullptr) {
65 :
66 49 : server_data_ = true;
67 49 : server_data_key_history_ = true;
68 49 : purge_execution_ = true;
69 49 : }
70 :
71 30 : void MyTrafficHttp2Server::enableMyMetrics(ert::metrics::Metrics *metrics, const std::string &source) {
72 :
73 30 : metrics_ = metrics;
74 :
75 30 : if (metrics_) {
76 90 : ert::metrics::labels_t familyLabels = {{"source", (source.empty() ? name_:source)}}; // same way that http2comm library
77 :
78 120 : ert::metrics::counter_family_t& cf = metrics->addCounterFamily("h2agent_traffic_server_provisioned_requests_counter", "Requests provisioned counter in h2agent_traffic_server", familyLabels);
79 :
80 90 : provisioned_requests_successful_counter_ = &(cf.Add({{"result", "successful"}}));
81 90 : provisioned_requests_failed_counter_ = &(cf.Add({{"result", "failed"}}));
82 :
83 120 : ert::metrics::counter_family_t& cf2 = metrics->addCounterFamily("h2agent_traffic_server_purged_contexts_counter", "Contexts purged counter in h2agent_traffic_server", familyLabels);
84 :
85 90 : purged_contexts_successful_counter_ = &(cf2.Add({{"result", "successful"}}));
86 90 : purged_contexts_failed_counter_ = &(cf2.Add({{"result", "failed"}}));
87 30 : }
88 180 : }
89 :
90 11 : bool MyTrafficHttp2Server::checkMethodIsAllowed(
91 : const nghttp2::asio_http2::server::request& req,
92 : std::vector<std::string>& allowedMethods)
93 : {
94 : // NO RESTRICTIONS FOR SIMULATED NODE
95 66 : allowedMethods = {"POST", "GET", "PUT", "DELETE", "HEAD"};
96 11 : return (req.method() == "POST" || req.method() == "GET" || req.method() == "PUT" || req.method() == "DELETE" || req.method() == "HEAD");
97 33 : }
98 :
99 11 : bool MyTrafficHttp2Server::checkMethodIsImplemented(
100 : const nghttp2::asio_http2::server::request& req)
101 : {
102 : // NO RESTRICTIONS FOR SIMULATED NODE
103 11 : return (req.method() == "POST" || req.method() == "GET" || req.method() == "PUT" || req.method() == "DELETE" || req.method() == "HEAD");
104 : }
105 :
106 :
107 11 : bool MyTrafficHttp2Server::checkHeaders(const nghttp2::asio_http2::server::request&
108 : req)
109 : {
110 11 : return true;
111 : /*
112 : auto ctype = req.header().find("content-type");
113 : auto ctype_end = std::end(req.header());
114 :
115 : return ((ctype != ctype_end) ? (ctype->second.value == "application/json") :
116 : false);
117 : */
118 : }
119 :
120 6 : std::string MyTrafficHttp2Server::dataConfigurationAsJsonString() const {
121 6 : nlohmann::json result;
122 :
123 6 : result["storeEvents"] = server_data_;
124 6 : result["storeEventsKeyHistory"] = server_data_key_history_;
125 6 : result["purgeExecution"] = purge_execution_;
126 :
127 12 : return result.dump();
128 6 : }
129 :
130 2 : std::string MyTrafficHttp2Server::configurationAsJsonString() const {
131 2 : nlohmann::json result;
132 :
133 2 : result["receiveRequestBody"] = receive_request_body_.load();
134 2 : result["preReserveRequestBody"] = pre_reserve_request_body_.load();
135 :
136 4 : return result.dump();
137 2 : }
138 :
139 0 : bool MyTrafficHttp2Server::receiveDataLen(const nghttp2::asio_http2::server::request& req) {
140 0 : LOGDEBUG(ert::tracing::Logger::debug("receiveRequestBody()", ERT_FILE_LOCATION));
141 :
142 : // TODO: we could analyze req to get the provision and find out if request body is actually needed.
143 : // To cache the analysis, we should use complete URI as map key (data/len could be received in
144 : // chunks and that's why data/len reception sequence id is not valid and it is not provided by
145 : // http2comm library through this virtual method).
146 :
147 0 : return receive_request_body_.load();
148 : }
149 :
150 11 : bool MyTrafficHttp2Server::preReserveRequestBody() {
151 11 : return pre_reserve_request_body_.load();
152 : }
153 :
154 11 : void MyTrafficHttp2Server::receive(const std::uint64_t &receptionId,
155 : const nghttp2::asio_http2::server::request& req,
156 : const std::string &requestBody,
157 : const std::chrono::microseconds &receptionTimestampUs,
158 : unsigned int& statusCode, nghttp2::asio_http2::header_map& headers,
159 : std::string& responseBody, unsigned int &responseDelayMs)
160 : {
161 11 : LOGDEBUG(ert::tracing::Logger::debug("receive()", ERT_FILE_LOCATION));
162 :
163 : // see uri_ref struct (https://nghttp2.org/documentation/asio_http2.h.html#asio-http2-h)
164 11 : std::string method = req.method();
165 : //std::string uriRawPath = req.uri().raw_path; // percent-encoded
166 11 : std::string uriPath = req.uri().path; // decoded
167 11 : std::string uriQuery = req.uri().raw_query; // parameter values may be percent-encoded
168 : //std::string reqUriFragment = req.uri().fragment; // https://stackoverflow.com/a/65198345/2576671
169 :
170 : // Move request body to internal encoded body data:
171 11 : h2agent::model::DataPart requestBodyDataPart(std::move(requestBody));
172 :
173 : // Busy threads:
174 11 : int currentBusyThreads = getQueueDispatcherBusyThreads();
175 11 : if (currentBusyThreads > 0) { // 0 when queue dispatcher is not used
176 8 : int maxBusyThreads = max_busy_threads_.load();
177 8 : if (currentBusyThreads > maxBusyThreads) {
178 5 : maxBusyThreads = currentBusyThreads;
179 5 : max_busy_threads_.store(maxBusyThreads);
180 : }
181 8 : LOGINFORMATIONAL(
182 : if (receptionId % 1000 == 0) {
183 : std::string msg = ert::tracing::Logger::asString("QueueDispatcher [workers/size/max-size]: %d/%d/%d | Busy workers [current/maximum reached]: %d/%d", getQueueDispatcherThreads(), getQueueDispatcherSize(), getQueueDispatcherMaxSize(), currentBusyThreads, maxBusyThreads);
184 : ert::tracing::Logger::informational(msg, ERT_FILE_LOCATION);
185 : }
186 : );
187 : }
188 :
189 11 : LOGDEBUG(
190 : std::stringstream ss;
191 : // Original URI:
192 : std::string originalUri = uriPath;
193 : if (!uriQuery.empty()) {
194 : originalUri += "?";
195 : originalUri += uriQuery;
196 : }
197 : ss << "TRAFFIC REQUEST RECEIVED"
198 : << " | Reception id (general unique server sequence): " << receptionId
199 : << " | Method: " << method
200 : << " | Headers: " << ert::http2comm::headersAsString(req.header())
201 : << " | Uri: " << req.uri().scheme << "://" << req.uri().host << originalUri
202 : << " | Query parameters: " << ((getAdminData()->getServerMatchingData().getUriPathQueryParametersFilter() == h2agent::model::AdminServerMatchingData::Ignore) ? "ignored":"not ignored")
203 : << " | Body (as ascii string, dots for non-printable): " << requestBodyDataPart.asAsciiString();
204 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
205 : );
206 :
207 : // Normalized URI: original URI with query parameters normalized (ordered) / Classification URI: may ignore, sort or pass by query parameters
208 11 : std::string normalizedUri = uriPath;
209 11 : std::string classificationUri = uriPath;
210 :
211 : // Query parameters transformation:
212 11 : std::map<std::string, std::string> qmap; // query parameters map
213 11 : if (!uriQuery.empty()) {
214 9 : char separator = ((getAdminData()->getServerMatchingData().getUriPathQueryParametersSeparator() == h2agent::model::AdminServerMatchingData::Ampersand) ? '&':';');
215 9 : std::string uriQueryNormalized;
216 9 : std::string *ptr_uriQueryNormalized = &uriQueryNormalized;
217 9 : qmap = h2agent::model::extractQueryParameters(uriQuery, ptr_uriQueryNormalized, separator); // needed even for 'Ignore' QParam filter type
218 :
219 9 : normalizedUri += "?";
220 9 : normalizedUri += uriQueryNormalized;
221 :
222 9 : h2agent::model::AdminServerMatchingData::UriPathQueryParametersFilterType uriPathQueryParametersFilterType = getAdminData()->getServerMatchingData().getUriPathQueryParametersFilter();
223 9 : switch (uriPathQueryParametersFilterType) {
224 1 : case h2agent::model::AdminServerMatchingData::PassBy:
225 1 : classificationUri += "?";
226 1 : classificationUri += uriQuery;
227 1 : break;
228 7 : case h2agent::model::AdminServerMatchingData::Sort:
229 7 : classificationUri += "?";
230 7 : classificationUri += uriQueryNormalized;
231 7 : break;
232 1 : case h2agent::model::AdminServerMatchingData::Ignore:
233 1 : break;
234 : }
235 9 : }
236 :
237 11 : LOGDEBUG(
238 : std::stringstream ss;
239 : ss << "Normalized Uri (server data event keys): " << req.uri().scheme << "://" << req.uri().host << normalizedUri;
240 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
241 : );
242 :
243 : // Admin provision & matching configuration:
244 11 : const h2agent::model::AdminServerProvisionData & provisionData = getAdminData()->getServerProvisionData();
245 11 : const h2agent::model::AdminServerMatchingData & matchingData = getAdminData()->getServerMatchingData();
246 :
247 : // Find mock context:
248 22 : std::string inState{};
249 22 : h2agent::model::DataKey normalizedKey(method, normalizedUri);
250 :
251 11 : /*bool requestFound = */getMockServerData()->findLastRegisteredRequestState(normalizedKey, inState); // if not found, inState will be 'initial'
252 :
253 : // Matching algorithm:
254 11 : h2agent::model::AdminServerMatchingData::AlgorithmType algorithmType = matchingData.getAlgorithm();
255 11 : std::shared_ptr<h2agent::model::AdminServerProvision> provision(nullptr);
256 :
257 11 : LOGDEBUG(
258 : std::stringstream ss;
259 : if (algorithmType != h2agent::model::AdminServerMatchingData::FullMatchingRegexReplace) {
260 : ss << "Classification Uri: " << req.uri().scheme << "://" << req.uri().host << classificationUri;
261 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
262 : }
263 : );
264 :
265 11 : switch (algorithmType) {
266 9 : case h2agent::model::AdminServerMatchingData::FullMatching:
267 9 : LOGDEBUG(
268 : std::string msg = ert::tracing::Logger::asString("Searching 'FullMatching' provision for method '%s', classification uri '%s' and state '%s'", method.c_str(), classificationUri.c_str(), inState.c_str());
269 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
270 : );
271 9 : provision = provisionData.find(inState, method, classificationUri);
272 9 : break;
273 :
274 1 : case h2agent::model::AdminServerMatchingData::FullMatchingRegexReplace:
275 : // In this case, our classification URI is pending to be transformed:
276 1 : classificationUri = std::regex_replace (classificationUri, matchingData.getRgx(), matchingData.getFmt());
277 1 : LOGDEBUG(
278 : std::string msg = ert::tracing::Logger::asString("Classification Uri (after regex-replace transformation): %s", classificationUri.c_str());
279 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
280 : msg = ert::tracing::Logger::asString("Searching 'FullMatchingRegexReplace' provision for method '%s', classification uri '%s' and state '%s'", method.c_str(), classificationUri.c_str(), inState.c_str());
281 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
282 : );
283 1 : provision = provisionData.find(inState, method, classificationUri);
284 1 : break;
285 1 : case h2agent::model::AdminServerMatchingData::RegexMatching:
286 1 : LOGDEBUG(
287 : std::string msg = ert::tracing::Logger::asString("Searching 'RegexMatching' provision for method '%s', classification uri '%s' and state '%s'", method.c_str(), classificationUri.c_str(), inState.c_str());
288 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
289 : );
290 :
291 : // as provision key is built combining inState, method and uri fields, a regular expression could also be provided for inState
292 : // (method is strictly checked). TODO could we avoid this rare and unpredictable usage ?
293 1 : provision = provisionData.findRegexMatching(inState, method, classificationUri);
294 1 : break;
295 : }
296 :
297 : // Fall back to possible default provision (empty URI):
298 11 : if (!provision) {
299 6 : LOGDEBUG(
300 : std::string msg = ert::tracing::Logger::asString("No provision found for classification URI. Trying with default fallback provision for '%s'", method.c_str());
301 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
302 : );
303 :
304 12 : provision = provisionData.find(inState, method, "");
305 : }
306 :
307 11 : if (provision) {
308 :
309 5 : LOGDEBUG(ert::tracing::Logger::debug("Provision successfully indentified !", ERT_FILE_LOCATION));
310 5 : provision->employ();
311 :
312 5 : std::string outState;
313 5 : std::string outStateMethod;
314 5 : std::string outStateUri;
315 :
316 : // Process provision
317 5 : provision->transform(normalizedUri, uriPath, qmap, requestBodyDataPart, req.header(), receptionId,
318 : statusCode, headers, responseBody, responseDelayMs, outState, outStateMethod, outStateUri);
319 :
320 : // Special out-states:
321 5 : if (purge_execution_ && outState == "purge") {
322 1 : bool somethingDeleted = false;
323 2 : bool success = getMockServerData()->clear(somethingDeleted, h2agent::model::EventKey(normalizedKey, ""));
324 1 : LOGDEBUG(
325 : std::string msg = ert::tracing::Logger::asString("Requested purge in out-state. Removal %s", success ? "successful":"failed");
326 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
327 : );
328 : // metrics
329 1 : if(metrics_) {
330 1 : if (success) purged_contexts_successful_counter_->Increment();
331 0 : else purged_contexts_failed_counter_->Increment();
332 : }
333 : }
334 : else {
335 4 : bool hasVirtualMethod = !outStateMethod.empty();
336 :
337 : // Store event context information
338 4 : if (server_data_) {
339 4 : normalizedKey.setProvisionUri(provision->getRequestUri()); // additional context
340 16 : getMockServerData()->loadEvent(normalizedKey, inState, (hasVirtualMethod ? provision->getOutState():outState), receptionTimestampUs, statusCode, req.header(), headers, requestBodyDataPart, responseBody, receptionId, responseDelayMs, server_data_key_history_ /* history enabled */);
341 :
342 : // Virtual storage:
343 4 : if (hasVirtualMethod) {
344 2 : LOGWARNING(
345 : if (outStateMethod == method && outStateUri.empty()) ert::tracing::Logger::warning(ert::tracing::Logger::asString("Redundant 'outState' foreign method with current provision one: '%s'", method.c_str()), ERT_FILE_LOCATION);
346 : );
347 2 : if (outStateUri.empty()) {
348 0 : outStateUri = normalizedUri; // by default
349 : }
350 :
351 2 : h2agent::model::DataKey foreignKey(outStateMethod /* foreign method */, outStateUri /* foreign uri */);
352 2 : foreignKey.setProvisionUri(provision->getRequestUri()); // additional context
353 2 : getMockServerData()->loadEvent(foreignKey, inState, outState, receptionTimestampUs, statusCode, req.header(), headers, requestBodyDataPart, responseBody, receptionId, responseDelayMs, server_data_key_history_ /* history enabled */, method /* virtual method origin*/, normalizedUri /* virtual uri origin */);
354 2 : }
355 : }
356 : }
357 :
358 : // metrics
359 5 : if(metrics_) {
360 3 : provisioned_requests_successful_counter_->Increment();
361 : }
362 5 : }
363 : else {
364 6 : LOGDEBUG(
365 : std::string msg = ert::tracing::Logger::asString("Default fallback provision not found: returning status code 501 (Not Implemented).", method.c_str());
366 : ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
367 : );
368 :
369 6 : statusCode = ert::http2comm::ResponseCode::NOT_IMPLEMENTED; // 501
370 : // Store even if not provision was identified (helps to troubleshoot design problems in test configuration):
371 6 : if (server_data_) {
372 54 : getMockServerData()->loadEvent(normalizedKey, ""/* empty inState, which will be omitted in server data register */, ""/*outState (same as before)*/, receptionTimestampUs, statusCode, req.header(), headers, requestBodyDataPart, responseBody, receptionId, responseDelayMs, true /* history enabled ALWAYS FOR UNKNOWN EVENTS */);
373 : }
374 : // metrics
375 6 : if(metrics_) {
376 5 : provisioned_requests_failed_counter_->Increment();
377 : }
378 : }
379 :
380 :
381 11 : LOGDEBUG(
382 : std::stringstream ss;
383 : ss << "RESPONSE TO SEND| StatusCode: " << statusCode << " | Headers: " << ert::http2comm::headersAsString(headers);
384 : if (!responseBody.empty()) {
385 : std::string output;
386 : h2agent::model::asAsciiString(responseBody, output);
387 : ss << " | Body (as ascii string, dots for non-printable): " << output;
388 : }
389 : ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
390 : );
391 11 : }
392 :
393 0 : void MyTrafficHttp2Server::streamError(uint32_t errorCode, const std::string &serverName, const std::uint64_t &receptionId, const nghttp2::asio_http2::server::request &req) {
394 :
395 : // Inner class implementation (trace): "Error code: %d | Server: %s | Reception id: %llu | Request Method: %s | Request Uri: %s"
396 : //ert::http2comm::Http2Server::streamError(errorCode, serverName, receptionId, req);
397 : // For us, receptionId is the serverSequence.
398 : // Also, we will ignore the serverName as we have only 1 server in this application (h2agent_traffic_server), and the global variable name itself, autoexplains.
399 0 : std::string msg = ert::tracing::Logger::asString("Error code: %d | Traffic server sequence (recvseq): %llu | Request Method: %s | Request Uri: %s", errorCode, receptionId, req.method().c_str(), req.uri().path.c_str());
400 0 : ert::tracing::Logger::error(msg, ERT_FILE_LOCATION);
401 :
402 :
403 : // Update data map (mark response as incompleted) is costly: sequential search for receptionId, and we don't have here normalized URI to narrow the search
404 : // So, we will create a global variable with key "__core.stream-error-traffic-server.<recvseq>.<method>.<uri>" and value "<error code>"
405 : // These variables are useful to inspect errors and discard server data (response sections), or at least interpret them as "intention to send".
406 : // We also have error logs for this kind of events.
407 0 : if (global_variable_ptr_) {
408 0 : static const std::string varPrefix = "__core.stream-error-traffic-server.";
409 0 : std::string var = varPrefix + std::to_string(receptionId) + "." + req.method() + "." + req.uri().path;
410 0 : std::string val = std::to_string(errorCode);
411 :
412 0 : global_variable_ptr_->add(var, val); // shall not exists: server sequence (reception id) is unique
413 0 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable processed (%s = %s)", var.c_str(), val.c_str()), ERT_FILE_LOCATION));
414 0 : }
415 0 : }
416 :
417 11 : std::chrono::milliseconds MyTrafficHttp2Server::responseDelayMs(const std::uint64_t &receptionId) {
418 :
419 11 : bool exists{};
420 11 : long long ms_count{};
421 :
422 11 : if (global_variable_ptr_) {
423 13 : static const std::string varPrefix = "__core.response-delay-ms.";
424 11 : std::string var = varPrefix + std::to_string(receptionId);
425 11 : std::string val = global_variable_ptr_->get(var, exists);
426 11 : if (exists) {
427 : try {
428 0 : ms_count = std::stoll(val);
429 0 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("Variable processed successfully (%s = %s)", var.c_str(), val.c_str()), ERT_FILE_LOCATION));
430 0 : } catch (const std::invalid_argument& e) {
431 0 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Variable value is not a number (%s = %s)", var.c_str(), val.c_str()), ERT_FILE_LOCATION));
432 0 : return std::chrono::milliseconds::zero();
433 0 : } catch (const std::out_of_range& e) {
434 0 : LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Variable value invalid range (%s = %s)", var.c_str(), val.c_str()), ERT_FILE_LOCATION));
435 0 : return std::chrono::milliseconds::zero();
436 0 : }
437 : }
438 : else {
439 11 : LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString("No additional dynamic response delay found (%s = <missing>)", var.c_str()), ERT_FILE_LOCATION));
440 : }
441 11 : }
442 : else {
443 : // This must not happen, that's hardcoded on main.cpp:
444 0 : ert::tracing::Logger::critical("You may need to set global variable map to server instance: myTrafficHttp2Server->setGlobalVariable(myGlobalVariable);", ERT_FILE_LOCATION);
445 : }
446 :
447 11 : return std::chrono::milliseconds(ms_count);
448 : }
449 :
450 : }
451 : }
|