LCOV - code coverage report
Current view: top level - http2 - MyTrafficHttp2Server.cpp (source / functions) Coverage Total Hit
Test: final-coverage.info Lines: 96.5 % 144 139
Test Date: 2025-02-14 17:40:40 Functions: 90.0 % 10 9

            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              : 
     182            8 :         LOGINFORMATIONAL(
     183              :         if (receptionId % 1000 == 0) {
     184              :         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);
     185              :             ert::tracing::Logger::informational(msg,  ERT_FILE_LOCATION);
     186              :         }
     187              :         );
     188              :     }
     189              : 
     190           11 :     LOGDEBUG(
     191              :         std::stringstream ss;
     192              :         // Original URI:
     193              :         std::string originalUri = uriPath;
     194              :     if (!uriQuery.empty()) {
     195              :     originalUri += "?";
     196              :     originalUri += uriQuery;
     197              : }
     198              : ss << "TRAFFIC REQUEST RECEIVED"
     199              :    << " | Reception id (general unique server sequence): " << receptionId
     200              :    << " | Method: " << method
     201              :    << " | Headers: " << ert::http2comm::headersAsString(req.header())
     202              :    << " | Uri: " << req.uri().scheme << "://" << req.uri().host << originalUri
     203              :    << " | Query parameters: " << ((getAdminData()->getServerMatchingData().getUriPathQueryParametersFilter() == h2agent::model::AdminServerMatchingData::Ignore) ? "ignored":"not ignored")
     204              :    << " | Body (as ascii string, dots for non-printable): " << requestBodyDataPart.asAsciiString();
     205              :    ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
     206              : );
     207              : 
     208              :     // Normalized URI: original URI with query parameters normalized (ordered) / Classification URI: may ignore, sort or pass by query parameters
     209           11 :     std::string normalizedUri = uriPath;
     210           11 :     std::string classificationUri = uriPath;
     211              : 
     212              :     // Query parameters transformation:
     213           11 :     std::map<std::string, std::string> qmap; // query parameters map
     214           11 :     if (!uriQuery.empty()) {
     215            9 :         char separator = ((getAdminData()->getServerMatchingData().getUriPathQueryParametersSeparator() == h2agent::model::AdminServerMatchingData::Ampersand) ? '&':';');
     216            9 :         std::string uriQueryNormalized;
     217            9 :         std::string *ptr_uriQueryNormalized = &uriQueryNormalized;
     218            9 :         qmap = h2agent::model::extractQueryParameters(uriQuery, ptr_uriQueryNormalized, separator); // needed even for 'Ignore' QParam filter type
     219              : 
     220            9 :         normalizedUri += "?";
     221            9 :         normalizedUri += uriQueryNormalized;
     222              : 
     223            9 :         h2agent::model::AdminServerMatchingData::UriPathQueryParametersFilterType uriPathQueryParametersFilterType = getAdminData()->getServerMatchingData().getUriPathQueryParametersFilter();
     224            9 :         switch (uriPathQueryParametersFilterType) {
     225            1 :         case h2agent::model::AdminServerMatchingData::PassBy:
     226            1 :             classificationUri += "?";
     227            1 :             classificationUri += uriQuery;
     228            1 :             break;
     229            7 :         case h2agent::model::AdminServerMatchingData::Sort:
     230            7 :             classificationUri += "?";
     231            7 :             classificationUri += uriQueryNormalized;
     232            7 :             break;
     233            1 :         case h2agent::model::AdminServerMatchingData::Ignore:
     234            1 :             break;
     235              :         }
     236            9 :     }
     237              : 
     238           11 :     LOGDEBUG(
     239              :         std::stringstream ss;
     240              :         ss << "Normalized Uri (server data event keys): " << req.uri().scheme << "://" << req.uri().host << normalizedUri;
     241              :         ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
     242              :     );
     243              : 
     244              : // Admin provision & matching configuration:
     245           11 :     const h2agent::model::AdminServerProvisionData & provisionData = getAdminData()->getServerProvisionData();
     246           11 :     const h2agent::model::AdminServerMatchingData & matchingData = getAdminData()->getServerMatchingData();
     247              : 
     248              : // Find mock context:
     249           22 :     std::string inState{};
     250           22 :     h2agent::model::DataKey normalizedKey(method, normalizedUri);
     251              : 
     252           11 :     /*bool requestFound = */getMockServerData()->findLastRegisteredRequestState(normalizedKey, inState); // if not found, inState will be 'initial'
     253              : 
     254              : // Matching algorithm:
     255           11 :     h2agent::model::AdminServerMatchingData::AlgorithmType algorithmType = matchingData.getAlgorithm();
     256           11 :     std::shared_ptr<h2agent::model::AdminServerProvision> provision(nullptr);
     257              : 
     258           11 :     LOGDEBUG(
     259              :         std::stringstream ss;
     260              :     if (algorithmType != h2agent::model::AdminServerMatchingData::FullMatchingRegexReplace) {
     261              :     ss << "Classification Uri: " << req.uri().scheme << "://" << req.uri().host << classificationUri;
     262              :         ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
     263              :     }
     264              :     );
     265              : 
     266           11 :     switch (algorithmType) {
     267            9 :     case h2agent::model::AdminServerMatchingData::FullMatching:
     268            9 :         LOGDEBUG(
     269              :             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());
     270              :             ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     271              :         );
     272            9 :         provision = provisionData.find(inState, method, classificationUri);
     273            9 :         break;
     274              : 
     275            1 :     case h2agent::model::AdminServerMatchingData::FullMatchingRegexReplace:
     276              :         // In this case, our classification URI is pending to be transformed:
     277            1 :         classificationUri = std::regex_replace (classificationUri, matchingData.getRgx(), matchingData.getFmt());
     278            1 :         LOGDEBUG(
     279              :             std::string msg = ert::tracing::Logger::asString("Classification Uri (after regex-replace transformation): %s", classificationUri.c_str());
     280              :             ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     281              :             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());
     282              :             ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     283              :         );
     284            1 :         provision = provisionData.find(inState, method, classificationUri);
     285            1 :         break;
     286            1 :     case h2agent::model::AdminServerMatchingData::RegexMatching:
     287            1 :         LOGDEBUG(
     288              :             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());
     289              :             ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     290              :         );
     291              : 
     292              :         // as provision key is built combining inState, method and uri fields, a regular expression could also be provided for inState
     293              :         //  (method is strictly checked). TODO could we avoid this rare and unpredictable usage ?
     294            1 :         provision = provisionData.findRegexMatching(inState, method, classificationUri);
     295            1 :         break;
     296              :     }
     297              : 
     298              :     // Fall back to possible default provision (empty URI):
     299           11 :     if (!provision) {
     300            6 :         LOGDEBUG(
     301              :             std::string msg = ert::tracing::Logger::asString("No provision found for classification URI. Trying with default fallback provision for '%s'", method.c_str());
     302              :             ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     303              :         );
     304              : 
     305           12 :         provision = provisionData.find(inState, method, "");
     306              :     }
     307              : 
     308           11 :     if (provision) {
     309              : 
     310            5 :         LOGDEBUG(ert::tracing::Logger::debug("Provision successfully indentified !", ERT_FILE_LOCATION));
     311            5 :         provision->employ();
     312              : 
     313            5 :         std::string outState;
     314            5 :         std::string outStateMethod;
     315            5 :         std::string outStateUri;
     316              : 
     317              :         // Process provision
     318            5 :         provision->transform(normalizedUri, uriPath, qmap, requestBodyDataPart, req.header(), receptionId,
     319              :                              statusCode, headers, responseBody, responseDelayMs, outState, outStateMethod, outStateUri);
     320              : 
     321              :         // Special out-states:
     322            5 :         if (purge_execution_ && outState == "purge") {
     323            1 :             bool somethingDeleted = false;
     324            2 :             bool success = getMockServerData()->clear(somethingDeleted, h2agent::model::EventKey(normalizedKey, ""));
     325            1 :             LOGDEBUG(
     326              :                 std::string msg = ert::tracing::Logger::asString("Requested purge in out-state. Removal %s", success ? "successful":"failed");
     327              :                 ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     328              :             );
     329              :             // metrics
     330            1 :             if(metrics_) {
     331            1 :                 if (success) purged_contexts_successful_counter_->Increment();
     332            0 :                 else purged_contexts_failed_counter_->Increment();
     333              :             }
     334              :         }
     335              :         else {
     336            4 :             bool hasVirtualMethod = !outStateMethod.empty();
     337              : 
     338              :             // Store event context information
     339            4 :             if (server_data_) {
     340            4 :                 normalizedKey.setProvisionUri(provision->getRequestUri()); // additional context
     341           16 :                 getMockServerData()->loadEvent(normalizedKey, inState, (hasVirtualMethod ? provision->getOutState():outState), receptionTimestampUs, statusCode, req.header(), headers, requestBodyDataPart, responseBody, receptionId, responseDelayMs, server_data_key_history_ /* history enabled */);
     342              : 
     343              :                 // Virtual storage:
     344            4 :                 if (hasVirtualMethod) {
     345            2 :                     LOGWARNING(
     346              :                         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);
     347              :                     );
     348            2 :                     if (outStateUri.empty()) {
     349            0 :                         outStateUri = normalizedUri; // by default
     350              :                     }
     351              : 
     352            2 :                     h2agent::model::DataKey foreignKey(outStateMethod /* foreign method */, outStateUri /* foreign uri */);
     353            2 :                     foreignKey.setProvisionUri(provision->getRequestUri()); // additional context
     354            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 */);
     355            2 :                 }
     356              :             }
     357              :         }
     358              : 
     359              :         // metrics
     360            5 :         if(metrics_) {
     361            3 :             provisioned_requests_successful_counter_->Increment();
     362              :         }
     363            5 :     }
     364              :     else {
     365            6 :         LOGDEBUG(
     366              :             std::string msg = ert::tracing::Logger::asString("Default fallback provision not found: returning status code 501 (Not Implemented).", method.c_str());
     367              :             ert::tracing::Logger::debug(msg, ERT_FILE_LOCATION);
     368              :         );
     369              : 
     370            6 :         statusCode = ert::http2comm::ResponseCode::NOT_IMPLEMENTED; // 501
     371              :         // Store even if not provision was identified (helps to troubleshoot design problems in test configuration):
     372            6 :         if (server_data_) {
     373           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 */);
     374              :         }
     375              :         // metrics
     376            6 :         if(metrics_) {
     377            5 :             provisioned_requests_failed_counter_->Increment();
     378              :         }
     379              :     }
     380              : 
     381              : 
     382           11 :     LOGDEBUG(
     383              :         std::stringstream ss;
     384              :         ss << "RESPONSE TO SEND| StatusCode: " << statusCode << " | Headers: " << ert::http2comm::headersAsString(headers);
     385              :     if (!responseBody.empty()) {
     386              :     std::string output;
     387              :     h2agent::model::asAsciiString(responseBody, output);
     388              :         ss << " | Body (as ascii string, dots for non-printable): " << output;
     389              :     }
     390              :     ert::tracing::Logger::debug(ss.str(), ERT_FILE_LOCATION);
     391              :     );
     392           11 : }
     393              : 
     394              : }
     395              : }
        

Generated by: LCOV version 2.0-1