LCOV - code coverage report
Current view: top level - src/controls - WebServerControl.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #31706 (f8ed4a) with base bb0a08 Lines: 335 365 91.8 %
Date: 2025-11-03 17:23:24 Functions: 37 39 94.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "WebServerControl.h"
      11             : #include "FEProblemBase.h"
      12             : #include "MooseApp.h"
      13             : 
      14             : #include "minijson/minijson.h"
      15             : #include "tinyhttp/http.h"
      16             : 
      17             : registerMooseObject("MooseApp", WebServerControl);
      18             : 
      19             : InputParameters
      20       15257 : WebServerControl::validParams()
      21             : {
      22       15257 :   InputParameters params = Control::validParams();
      23       30514 :   params.addClassDescription("Starts a webserver for sending/receiving JSON messages to get data "
      24             :                              "and control a running MOOSE calculation");
      25       61028 :   params.addParam<unsigned int>("port",
      26             :                                 "The port to listen on; must provide either this or 'file_socket'");
      27       61028 :   params.addParam<FileName>(
      28             :       "file_socket",
      29             :       "The path to the unix file socket to listen on; must provide either this or 'port'");
      30       45771 :   params.addParam<Real>("initial_client_timeout",
      31       30514 :                         10,
      32             :                         "Time in seconds to allow the client to begin communicating on init; if "
      33             :                         "this time is surpassed the run will be killed");
      34       30514 :   params.addParam<Real>("client_timeout",
      35       30514 :                         10,
      36             :                         "Time in seconds to allow the client to communicate; if this time is "
      37             :                         "surpassed the run will be killed");
      38       15257 :   return params;
      39           0 : }
      40             : 
      41         199 : WebServerControl::WebServerControl(const InputParameters & parameters)
      42             :   : Control(parameters),
      43         199 :     _port(queryParam<unsigned int>("port")),
      44         398 :     _file_socket(queryParam<FileName>("file_socket")),
      45         398 :     _initial_client_timeout(getParam<Real>("initial_client_timeout")),
      46         796 :     _client_timeout(getParam<Real>("client_timeout"))
      47             : {
      48         199 :   if (!_port && !_file_socket)
      49           4 :     mooseError("You must provide either the parameter 'port' or 'file_socket' to designate where "
      50             :                "to listen");
      51         195 :   if (_port && _file_socket)
      52           8 :     paramError("port", "Cannot provide both 'port' and 'file_socket'");
      53         191 : }
      54             : 
      55         147 : WebServerControl::~WebServerControl()
      56             : {
      57             :   // Stop server if running; make sure this DOESN'T throw!
      58         147 :   stopServer();
      59         147 : }
      60             : 
      61             : void
      62         191 : WebServerControl::startServer(const Moose::PassKey<StartWebServerControlAction>)
      63             : {
      64             :   // Only start on rank 0
      65         191 :   if (processor_id() == 0)
      66             :   {
      67             :     mooseAssert(_server_weak_ptr.expired(), "Server is already started");
      68             :     mooseAssert(!_server_thread_ptr, "Server thread is already listening");
      69             :     mooseAssert(_port || _file_socket, "Neither set");
      70             : 
      71             :     // Setup and start the server
      72             :     {
      73             :       // Instantiate the server but don't start it, we need
      74             :       // to setup all of the actions
      75         145 :       auto server_ptr = std::make_shared<HttpServer>();
      76             :       // Give the control a weak_ptr to the server, as it will
      77             :       // really be owned by the server thread
      78         145 :       _server_weak_ptr = server_ptr;
      79             : 
      80             :       // Add all of the actions
      81         145 :       addServerActionsInternal();
      82             : 
      83             :       // Post message about server start
      84             :       {
      85         145 :         std::ostringstream message;
      86         145 :         message << "Starting server on ";
      87         145 :         if (_port)
      88           7 :           message << "port " << *_port;
      89         138 :         else if (_file_socket)
      90         138 :           message << "file socket " << *_file_socket;
      91         145 :         outputMessage(message.str());
      92         145 :       }
      93             : 
      94             :       // Start the server thread, giving the thread the server
      95             :       // shared_ptr, as the control only has a weak_ptr to the server
      96         145 :       auto console = _console;
      97         145 :       _server_thread_ptr = std::make_unique<std::thread>(
      98         290 :           [server_ptr, console](const auto port, const auto file_socket)
      99             :           {
     100             :             mooseAssert(server_ptr, "Null server");
     101         145 :             auto & server = *server_ptr;
     102             :             mooseAssert(port || file_socket, "Neither provided");
     103             : 
     104             :             try
     105             :             {
     106         145 :               if (port)
     107           7 :                 server.startListening(uint16_t(*port));
     108             :               else
     109         138 :                 server.startListening(*file_socket);
     110             :             }
     111           0 :             catch (std::exception & e)
     112             :             {
     113           0 :               console << "Server failed with exception: " << e.what() << std::endl;
     114             :             }
     115         141 :           },
     116         145 :           _port,
     117         290 :           _file_socket);
     118         145 :     }
     119             : 
     120             :     // Wait for the client to call /initialize
     121             :     {
     122             :       // Output that we're waiting for the client
     123         145 :       outputMessage("Waiting for client to initialize...");
     124             : 
     125             :       // To output initialization time
     126         145 :       const auto start = std::chrono::steady_clock::now();
     127             : 
     128         413 :       while (!isClientInitialized())
     129             :       {
     130             :         // Kill command sent before initialize
     131         272 :         if (isKillRequested())
     132             :         {
     133           0 :           stopServer();
     134           0 :           mooseError("Client sent kill command; exiting");
     135             :         }
     136             : 
     137             :         // Make sure we haven't reached the timeout
     138         272 :         const auto now = std::chrono::steady_clock::now();
     139         272 :         const auto elapsed = std::chrono::duration<double>(now - start).count();
     140         272 :         if (elapsed > _initial_client_timeout)
     141          12 :           mooseError(clientTimeoutErrorMessage(
     142           4 :               _initial_client_timeout, "initial_client_timeout", "during initialization"));
     143             : 
     144             :         // Poll until the next check
     145         268 :         std::this_thread::sleep_for(std::chrono::milliseconds(10));
     146             :       }
     147             : 
     148             :       // Assign a start time for the timeout thread, considering
     149             :       // client initialization to be a poke
     150         141 :       clientPoke();
     151             : 
     152             :       // Output that the client has initialized
     153         141 :       const auto info = getClientInfo();
     154         141 :       outputClientTiming(
     155         282 :           "\"" + info.name + "\" from " + info.user + "@" + info.host + " initialized", start);
     156         141 :     }
     157             :   }
     158             : 
     159             :   // Let the remaining ranks wait until rank 0 is done
     160         187 :   _communicator.barrier();
     161         187 : }
     162             : 
     163             : void
     164         386 : WebServerControl::execute()
     165             : {
     166             :   // If simulation is requested to terminate, do not go through this control
     167         386 :   if (_fe_problem.isSolveTerminationRequested())
     168           0 :     return;
     169             : 
     170             :   // For outputting the time spent waiting
     171         386 :   const auto start = std::chrono::steady_clock::now();
     172             : 
     173             :   // Needed to broadcast all of the types and names of data that we have received on rank 0
     174             :   // so that we can construct the same objects on the other ranks to receive the data and
     175             :   // set the same values
     176         386 :   std::vector<std::pair<std::string, std::string>> name_and_types;
     177             : 
     178             :   // Need to also broadcast whether or not to terminate the solve on the timestep
     179         386 :   bool terminate_solve = false; // Set value to avoid compiler warnings
     180             : 
     181             :   // Wait for the server on rank 0 to be done
     182         386 :   if (processor_id() == 0)
     183             :   {
     184        1395 :     TIME_SECTION("execute()", 3, "WebServerControl waiting for input")
     185         279 :     outputMessage("Waiting for client to continue...");
     186             : 
     187         279 :     setCurrentlyWaiting(true);
     188             : 
     189             :     // While waiting, yield so the server has time to run. Check for client
     190             :     // timeouts and a kill command.
     191         830 :     while (isCurrentlyWaiting())
     192             :     {
     193             :       // Kill command sent
     194         591 :       if (isKillRequested())
     195             :       {
     196          36 :         stopServer();
     197          36 :         mooseError("Client sent kill command; exiting");
     198             :       }
     199             : 
     200             :       // Check client timeout
     201        1110 :       const auto last = std::chrono::milliseconds(_last_client_poke.load());
     202         555 :       const auto now = std::chrono::steady_clock::now().time_since_epoch();
     203         555 :       const auto elapsed = std::chrono::duration<double>(now - last).count();
     204         555 :       if (elapsed > _client_timeout)
     205             :       {
     206           4 :         stopServer();
     207           8 :         mooseError(clientTimeoutErrorMessage(_client_timeout, "client_timeout"));
     208             :       }
     209             : 
     210         551 :       std::this_thread::sleep_for(std::chrono::milliseconds(10));
     211             :     }
     212             : 
     213             :     // Output waiting time
     214         239 :     outputClientTiming("continued", start);
     215             : 
     216         367 :     for (const auto & value_ptr : _controlled_values)
     217         128 :       name_and_types.emplace_back(value_ptr->name(), value_ptr->type());
     218             : 
     219         239 :     terminate_solve = isTerminateRequested();
     220         239 :     setTerminateRequested(false);
     221         239 :     _terminate_requested.store(false);
     222         239 :   }
     223             : 
     224             :   // All processes need to wait
     225         346 :   _communicator.barrier();
     226             : 
     227             :   // Construct the values on other processors to be received into so that
     228             :   // they're parallel consistent
     229         346 :   comm().broadcast(name_and_types);
     230         346 :   if (processor_id() != 0)
     231         169 :     for (const auto & [name, type] : name_and_types)
     232          62 :       _controlled_values.emplace_back(Moose::WebServerControlTypeRegistry::get(type).build(name));
     233             : 
     234             :   // Set all of the values
     235         536 :   for (auto & value_ptr : _controlled_values)
     236             :   {
     237             :     try
     238             :     {
     239         190 :       value_ptr->setControllableValue(*this);
     240             :     }
     241           0 :     catch (...)
     242             :     {
     243           0 :       mooseError("Error setting '",
     244           0 :                  value_ptr->type(),
     245             :                  "' typed value for parameter '",
     246           0 :                  value_ptr->name(),
     247             :                  "'; it is likely that the parameter has a different type");
     248           0 :     }
     249             :   }
     250             : 
     251         346 :   _controlled_values.clear();
     252             : 
     253             :   // Set solve terminate on all ranks, if requested
     254         346 :   _communicator.broadcast(terminate_solve);
     255         346 :   if (terminate_solve)
     256          11 :     _fe_problem.terminateSolve();
     257         346 : }
     258             : 
     259             : template <WebServerControl::RequestMethod method>
     260             : void
     261        1740 : WebServerControl::addServerAction(
     262             :     const std::string & path,
     263             :     std::function<WebServerControl::Response(const WebServerControl::Request &,
     264             :                                              WebServerControl &)> && action,
     265             :     const WebServerControl::ServerActionOptions & options)
     266             : {
     267             :   static_assert(method == RequestMethod::GET || method == RequestMethod::POST, "Unknown method");
     268             : 
     269        1740 :   auto server_ptr = _server_weak_ptr.lock();
     270        1740 :   if (!server_ptr || _server_thread_ptr)
     271           0 :     mooseError("addServerAction(): Can only call during addServerActions()");
     272        1740 :   auto & server = *server_ptr;
     273             : 
     274             :   // Capture a weak pointer to the WebServerControl so that this action
     275             :   // knows if the control is available or not
     276        1740 :   const std::weak_ptr<WebServerControl> control_weak_ptr =
     277             :       std::dynamic_pointer_cast<WebServerControl>(getSharedPtr());
     278             : 
     279             :   // Build the action that we'll actually pass to the web server.
     280             :   // Here, is very important that we capture everything by value
     281             :   // so that we don't rely on state from the control.
     282        1740 :   auto full_action =
     283        2754 :       [control_weak_ptr, path, options, action](const HttpRequest & http_request) -> HttpResponse
     284             :   {
     285             :     // Helper for returning an error
     286          32 :     const auto error = [](const std::string & error,
     287             :                           const unsigned int status_code = 400) -> HttpResponse
     288             :     {
     289          32 :       miniJson::Json::_object response;
     290          96 :       response["error"] = error;
     291          64 :       return HttpResponse{status_code, response};
     292          32 :     };
     293             : 
     294             :     // Only do work here if we have access to the control;
     295             :     // if this lock fails, it means that it has been destructed
     296             :     // and there isn't anything we can do
     297        5508 :     if (auto control_ptr = control_weak_ptr.lock())
     298             :     {
     299        2754 :       auto & control = *control_ptr;
     300             : 
     301             :       // Setup the request to be passed to the user function,
     302             :       // reformatting the HttpRequest as our own request
     303             :       // so we can change the underlying library if needed
     304        2754 :       Request request;
     305        2754 :       const auto & json_keys = options.getRequiredJSONKeys();
     306             :       // Load JSON data if it exists into nlohmann::json
     307        2754 :       if (!http_request.json().isNull())
     308             :       {
     309             :         // JSON data exists but was not needed
     310         513 :         if (json_keys.empty())
     311           0 :           return error("Request should not have JSON");
     312             : 
     313             :         // Serialize from minijson to nlohmann::json
     314         513 :         const auto serialized = http_request.json().serialize();
     315         513 :         const auto deserialized = nlohmann::json::parse(serialized);
     316             : 
     317             :         // Check for required key(s)
     318        1620 :         for (const auto & key : json_keys)
     319        1107 :           if (!deserialized.contains(key))
     320           0 :             return error("Missing required key '" + key + "' in JSON");
     321             : 
     322             :         // And set in the request
     323         513 :         request.setJSON(deserialized, {});
     324         513 :       }
     325             :       // JSON data does not exist but it was needed
     326        2241 :       else if (json_keys.size())
     327           0 :         return error("Request should have JSON");
     328             : 
     329             :       // Option requires initialization
     330        2754 :       if (options.getRequireInitialized() && !control.isClientInitialized())
     331           0 :         return error("Client has not initialized the control");
     332             : 
     333             :       // Option requires waiting
     334        2754 :       if (options.getRequireWaiting() && !control.isCurrentlyWaiting())
     335           0 :         return error("Control is not currently waiting for data");
     336             : 
     337             :       // Call the action method to act on the request
     338        2754 :       Response response;
     339             :       try
     340             :       {
     341        2754 :         response = action(request, control);
     342             :       }
     343           0 :       catch (const std::exception & e)
     344             :       {
     345           0 :         return error(e.what());
     346             :       }
     347             : 
     348             :       // Has an error, return that instead
     349        2754 :       if (response.hasError())
     350          32 :         return error(response.getError(), response.getStatusCode());
     351             : 
     352             :       // No JSON data, just a status code
     353        2722 :       if (!response.hasJSON())
     354         783 :         return HttpResponse{response.getStatusCode()};
     355             : 
     356             :       // Has JSON data, convert from nlohmann::json to miniJson
     357        1939 :       const auto serialized = response.getJSON().dump();
     358        1939 :       std::string parse_error;
     359        1939 :       const auto json = miniJson::Json::parse(serialized, parse_error);
     360             :       mooseAssert(parse_error.empty(), "Should be empty");
     361        1939 :       return HttpResponse{response.getStatusCode(), json};
     362        2754 :     }
     363             : 
     364             :     // Failed to capture weak_ptr to WebServerControl
     365           0 :     return error("Control is no longer available");
     366             :   };
     367             : 
     368        1740 :   auto & when = *server.when("/" + path);
     369             :   if constexpr (method == RequestMethod::GET)
     370        1160 :     when.requested(full_action);
     371             :   else
     372         580 :     when.posted(full_action);
     373        1740 : }
     374             : 
     375             : WebServerControl::ClientInfo
     376         141 : WebServerControl::getClientInfo() const
     377             : {
     378         141 :   std::lock_guard lock(_client_info_lock);
     379         141 :   if (!_client_info)
     380           0 :     mooseError("WebServerControl::getClientInfo(): Client info is not set yet");
     381         282 :   return *_client_info;
     382         141 : }
     383             : 
     384             : void
     385         949 : WebServerControl::outputMessage(const std::string & message) const
     386             : {
     387         949 :   _console << typeAndName() << ": " << message << std::endl;
     388         949 : }
     389             : 
     390             : void
     391         145 : WebServerControl::addServerActionsInternal()
     392             : {
     393             :   //
     394             :   // -- General actions ------------------------------------------------------------
     395             :   //
     396             : 
     397             :   // GET /check: Helper for checking if the server is running
     398             :   // Requires waiting: no
     399             :   // Return code: 200
     400             :   // Return JSON data: none
     401             :   {
     402         243 :     const auto check = [](const Request &, WebServerControl &) -> Response
     403         243 :     { return Response{200}; };
     404             : 
     405         145 :     ServerActionOptions options;
     406         145 :     options.setRequireWaiting(false, {});
     407         145 :     options.setRequireInitialized(false, {});
     408             : 
     409         435 :     addServerAction<RequestMethod::GET>("check", check, options);
     410         145 :   }
     411             : 
     412             :   // GET /continue: Tell the simulation to continue
     413             :   // Requires waiting: yes
     414             :   // Return code: 200
     415             :   // Return JSON data:
     416             :   //    'error': string, optional
     417             :   //         The error (only set if an error occurred)
     418             :   {
     419         231 :     const auto set_continue = [](const Request &, WebServerControl & control) -> Response
     420             :     {
     421         231 :       control.setCurrentlyWaiting(false);
     422         231 :       return Response{200};
     423             :     };
     424             : 
     425         435 :     addServerAction<RequestMethod::GET>("continue", set_continue);
     426             :   }
     427             : 
     428             :   // GET /initialize: Initializes the communication with the client
     429             :   // Requires waiting: no
     430             :   // POST JSON data:
     431             :   //    'name': string
     432             :   //        The name of the client
     433             :   //    'host': string
     434             :   //        The name the client host
     435             :   //    'user': string
     436             :   //        The name of the client user
     437             :   // Return code: 200
     438             :   // Return JSON data:
     439             :   //    'flags': list[string]
     440             :   //         The execute on flags
     441             :   //    'error': string, optional
     442             :   //         The error (only set if an error occurred)
     443             :   {
     444         141 :     const auto initialize = [](const Request & req, WebServerControl & control) -> Response
     445             :     {
     446         141 :       if (control.isClientInitialized())
     447           0 :         return ErrorResponse("Initialize has already been called");
     448             : 
     449             :       // Store the information received in the client info
     450         141 :       const auto & json = req.getJSON();
     451         141 :       ClientInfo client_info;
     452         141 :       const auto name = convertJSON<std::string>(json, "name");
     453         141 :       const auto host = convertJSON<std::string>(json, "host");
     454         141 :       const auto user = convertJSON<std::string>(json, "user");
     455         141 :       client_info.data = json;
     456         141 :       control.setClientInfo(client_info);
     457             : 
     458             :       // Capture the sorted exceute on flags
     459         141 :       std::set<std::string> flags_sorted;
     460         594 :       for (const auto & it : control.getParam<ExecFlagEnum>("execute_on"))
     461         171 :         flags_sorted.insert(it);
     462         141 :       const std::vector<std::string> flags(flags_sorted.begin(), flags_sorted.end());
     463             : 
     464             :       // Send back context about the control
     465         141 :       nlohmann::json response_json;
     466         141 :       response_json["control_name"] = control.name();
     467         141 :       response_json["control_type"] = control.type();
     468         141 :       response_json["execute_on_flags"] = flags_sorted;
     469             : 
     470             :       // Set initialized
     471         141 :       control.setClientInitialized();
     472             : 
     473         141 :       return Response{200, response_json};
     474         141 :     };
     475             : 
     476         145 :     ServerActionOptions options;
     477         145 :     options.setRequireWaiting(false, {});
     478         145 :     options.setRequireInitialized(false, {});
     479         580 :     options.requireJSONKeys({"host", "name", "user"});
     480             : 
     481         290 :     addServerAction<RequestMethod::POST>("initialize", initialize, options);
     482         145 :   }
     483             : 
     484             :   // GET /poke: "Poke" the server; used for checking timeouts
     485             :   // Requires waiting: no
     486             :   // Return code: 200
     487             :   // Return JSON data: none
     488             :   {
     489         137 :     const auto poke = [](const Request &, WebServerControl & control) -> Response
     490             :     {
     491         137 :       control.clientPoke();
     492         137 :       return Response{200};
     493             :     };
     494             : 
     495         145 :     ServerActionOptions options;
     496         145 :     options.setRequireWaiting(false, {});
     497             : 
     498         435 :     addServerAction<RequestMethod::GET>("poke", poke, options);
     499         145 :   }
     500             : 
     501             :   // GET /terminate: Tell FEProblemBase to terminate the solve
     502             :   // Requires waiting: yes
     503             :   // Return code: 200
     504             :   // Return JSON data:
     505             :   //    'error': string, optional
     506             :   //         The error (only set if an error occurred)
     507             :   {
     508           8 :     const auto terminate = [](const Request &, WebServerControl & control) -> Response
     509             :     {
     510           8 :       control.setTerminateRequested();
     511           8 :       control.setCurrentlyWaiting(false);
     512           8 :       return Response{200};
     513             :     };
     514             : 
     515         435 :     addServerAction<RequestMethod::GET>("terminate", terminate);
     516             :   }
     517             : 
     518             :   // GET /waiting: Check if waiting and the waiting exec flag if it exists
     519             :   // Requires waiting: no
     520             :   // Return code: 200
     521             :   // Return JSON data:
     522             :   //    'waiting': bool
     523             :   //         Whether or not the control is waiting
     524             :   //     'execute_on_flag': string, optional
     525             :   //         Only exists if waiting=true, the execute flag that is being waited on
     526             :   {
     527        1530 :     const auto waiting = [](const Request &, WebServerControl & control) -> Response
     528             :     {
     529        1530 :       nlohmann::json response_json;
     530        1530 :       if (control.isCurrentlyWaiting())
     531             :       {
     532        1044 :         response_json["waiting"] = true;
     533        1044 :         response_json["execute_on_flag"] =
     534        2088 :             static_cast<std::string>(control._fe_problem.getCurrentExecuteOnFlag());
     535             :       }
     536             :       else
     537         486 :         response_json["waiting"] = false;
     538             : 
     539        3060 :       return Response{200, response_json};
     540        1530 :     };
     541             : 
     542         145 :     ServerActionOptions options;
     543         145 :     options.setRequireWaiting(false, {});
     544             : 
     545         435 :     addServerAction<RequestMethod::GET>("waiting", waiting, options);
     546         145 :   }
     547             : 
     548             :   // GET /kill: Tell the client poll thread to kill
     549             :   // Requires waiting: no
     550             :   {
     551          36 :     const auto kill = [](const Request &, WebServerControl & control) -> Response
     552             :     {
     553          36 :       control.setKillRequested();
     554          36 :       return Response{200};
     555             :     };
     556             : 
     557         435 :     addServerAction<RequestMethod::GET>("kill", kill);
     558             :   }
     559             : 
     560             :   //
     561             :   // -- Get actions ----------------------------------------------------------------
     562             :   //
     563             : 
     564             :   // GET /get/dt: Get current simulation timestep size
     565             :   // Requires waiting: yes
     566             :   // Return code: 200
     567             :   // Return JSON data:
     568             :   //    'dt': double
     569             :   //         Current timestep size
     570             :   // Return JSON data:
     571             :   //    'error': string, optional
     572             :   //         The error (only set if an error occurred)
     573             :   {
     574          28 :     const auto get_dt = [](const Request &, WebServerControl & control) -> Response
     575             :     {
     576          28 :       nlohmann::json response_json;
     577          28 :       response_json["dt"] = control._fe_problem.dt();
     578          56 :       return Response{200, response_json};
     579          28 :     };
     580             : 
     581         435 :     addServerAction<RequestMethod::GET>("get/dt", get_dt);
     582             :   }
     583             : 
     584             :   // POST /get/postprocessor: Get a postprocessor value
     585             :   // Requires waiting: yes
     586             :   // POST JSON data:
     587             :   //    'name': string
     588             :   //        The name of the Postprocessor
     589             :   // Return code: 200
     590             :   // Return JSON data:
     591             :   //    'value': double
     592             :   //         The postprocessor value
     593             :   //    'error': string, optional
     594             :   //         The error (only set if an error occurred)
     595             :   {
     596          88 :     const auto get_postprocessor = [](const Request & req, WebServerControl & control) -> Response
     597             :     {
     598             :       // Get the postprocessor name
     599          88 :       const auto name = convertJSON<std::string>(req.getJSON(), "name");
     600             : 
     601             :       // Postprocessor should exist
     602          88 :       if (!control.hasPostprocessorByName(name))
     603           4 :         return ErrorResponse("Postprocessor '" + name + "' not found");
     604             : 
     605          84 :       nlohmann::json response_json;
     606          84 :       response_json["value"] = control.getPostprocessorValueByName(name);
     607          84 :       return Response{200, response_json};
     608          88 :     };
     609             : 
     610         145 :     ServerActionOptions options;
     611         145 :     options.requireJSONKey("name");
     612             : 
     613         435 :     addServerAction<RequestMethod::POST>("get/postprocessor", get_postprocessor, options);
     614         145 :   }
     615             : 
     616             :   // POST /get/reporter: Get a Reporter value
     617             :   // Requires waiting: yes
     618             :   // POST JSON data:
     619             :   //    'name': string
     620             :   //        The name of the Reporter value (object_name/value_name)
     621             :   // Return code: 200
     622             :   // Return JSON data:
     623             :   //    'value': any
     624             :   //         The reporter value
     625             :   // Return JSON data:
     626             :   //    'error': string, optional
     627             :   //         The error (only set if an error occurred)
     628             :   {
     629         128 :     const auto get_reporter = [](const Request & req, WebServerControl & control) -> Response
     630             :     {
     631             :       // Get the reporter name
     632         128 :       const auto name = convertJSON<std::string>(req.getJSON(), "name");
     633         128 :       if (!ReporterName::isValidName(name))
     634           0 :         return ErrorResponse("Name '" + name + "' not a valid reporter value name");
     635         128 :       const auto rname = ReporterName(name);
     636             : 
     637             :       // Reporter should exist
     638         128 :       if (!control.hasReporterValueByName(rname))
     639           0 :         return ErrorResponse("Reporter value '" + name + "' was not found");
     640             : 
     641             :       // Store the reporter value
     642         128 :       nlohmann::json response_json;
     643         128 :       control.getReporterContextBaseByName(rname).store(response_json["value"]);
     644             : 
     645         128 :       return Response{200, response_json};
     646         128 :     };
     647             : 
     648         145 :     ServerActionOptions options;
     649         145 :     options.requireJSONKey("name");
     650             : 
     651         435 :     addServerAction<RequestMethod::POST>("get/reporter", get_reporter, options);
     652         145 :   }
     653             : 
     654             :   // GET /get/time: Get current simulation time
     655             :   // Requires waiting: yes
     656             :   // Return code: 200
     657             :   // Return JSON data:
     658             :   //    'time': double
     659             :   //         Current time
     660             :   //    'error': string, optional
     661             :   //         The error (only set if an error occurred)
     662             :   {
     663          28 :     const auto get_time = [](const Request &, WebServerControl & control) -> Response
     664             :     {
     665          28 :       nlohmann::json response_json;
     666          28 :       response_json["time"] = control._fe_problem.time();
     667          56 :       return Response{200, response_json};
     668          28 :     };
     669             : 
     670         435 :     addServerAction<RequestMethod::GET>("get/time", get_time);
     671             :   }
     672             : 
     673             :   //
     674             :   // -- Set actions ----------------------------------------------------------------
     675             :   //
     676             : 
     677             :   // POST /set/controllable: Get a controllable parameter
     678             :   // Requires waiting: yes
     679             :   // POST JSON data:
     680             :   //    'name': string
     681             :   //        The path to the controllable data
     682             :   //    'value': any
     683             :   //        The value to set
     684             :   //    'type: string
     685             :   //        The C++ type of the controllable parameter
     686             :   // Return code: 201
     687             :   // Return JSON data:
     688             :   //    'error': string, optional
     689             :   //         The error (only set if an error occurred)
     690             :   {
     691         156 :     const auto set_controllable = [](const Request & req, WebServerControl & control) -> Response
     692             :     {
     693         156 :       const auto & json = req.getJSON();
     694             : 
     695             :       // Get the parameter type
     696         156 :       const auto type = convertJSON<std::string>(json, "type");
     697         156 :       const auto registered_type = Moose::WebServerControlTypeRegistry::query(type);
     698         156 :       if (!registered_type)
     699           8 :         return ErrorResponse("Type '" + type +
     700           4 :                              "' not registered for setting a controllable parameter");
     701             : 
     702             :       // Get the parameter name
     703         152 :       const auto name = convertJSON<std::string>(json, "name");
     704             :       // Parameter should exist
     705         152 :       if (!control.hasControllableParameterByName(name))
     706           4 :         return ErrorResponse("Controllable parameter '" + name + "' not found");
     707             : 
     708             :       // 'value' must exist
     709         148 :       const auto value_it = json.find("value");
     710         148 :       if (value_it == json.end())
     711           0 :         return ErrorResponse("Missing 'value' entry");
     712             : 
     713             :       // Build the value (also does the parsing)
     714             :       {
     715         148 :         std::unique_ptr<ControlledValueBase> value;
     716             :         try
     717             :         {
     718         148 :           value = registered_type->build(name, *value_it);
     719             :         }
     720          20 :         catch (std::exception & e)
     721             :         {
     722          20 :           return ErrorResponse("While parsing 'value': " + std::string(e.what()));
     723          20 :         }
     724             : 
     725         128 :         std::lock_guard<std::mutex> lock(control._controlled_values_mutex);
     726         128 :         control._controlled_values.emplace_back(std::move(value));
     727         148 :       }
     728             : 
     729         128 :       return Response{201};
     730         156 :     };
     731             : 
     732         145 :     ServerActionOptions options;
     733         580 :     options.requireJSONKeys({"name", "type", "value"});
     734             : 
     735         290 :     addServerAction<RequestMethod::POST>("set/controllable", set_controllable, options);
     736         145 :   }
     737             : 
     738             :   // Let derived classes add actions
     739         145 :   addServerActions();
     740        1015 : }
     741             : 
     742             : void
     743         141 : WebServerControl::setClientInfo(const WebServerControl::ClientInfo & info)
     744             : {
     745         141 :   std::lock_guard lock(_client_info_lock);
     746         141 :   _client_info = info;
     747         141 : }
     748             : 
     749             : void
     750         278 : WebServerControl::clientPoke()
     751             : {
     752         278 :   const int64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(
     753         278 :                           std::chrono::steady_clock::now().time_since_epoch())
     754         278 :                           .count();
     755         278 :   _last_client_poke.store(now);
     756         278 : }
     757             : 
     758             : void
     759         380 : WebServerControl::outputClientTiming(
     760             :     const std::string & message,
     761             :     const std::chrono::time_point<std::chrono::steady_clock> & start) const
     762             : {
     763         380 :   const auto now = std::chrono::steady_clock::now();
     764         380 :   const auto elapsed = std::chrono::duration<double>(now - start).count();
     765             : 
     766         380 :   std::ostringstream out;
     767         380 :   out << "Client " << message << " after " << std::fixed << std::setprecision(2) << elapsed
     768         380 :       << " seconds";
     769         380 :   outputMessage(out.str());
     770         380 : }
     771             : 
     772             : std::string
     773           8 : WebServerControl::clientTimeoutErrorMessage(
     774             :     const Real timeout,
     775             :     const std::string & timeout_param_name,
     776             :     const std::optional<std::string> & suffix /* = {} */) const
     777             : {
     778           8 :   std::ostringstream oss;
     779          20 :   oss << "The client timed out" << (suffix ? (" " + *suffix) : "") << "\nThe timeout is "
     780           8 :       << std::fixed << std::setprecision(2) << timeout << " seconds and is set by the '"
     781           8 :       << timeout_param_name << "' parameter";
     782          16 :   return oss.str();
     783           8 : }
     784             : 
     785             : void
     786         187 : WebServerControl::stopServer()
     787             : {
     788             :   // Only have something to do here if the server still exists
     789             :   // and the server thread was set; make sure this doesn't
     790             :   // throw because it is used in the destructor
     791         187 :   if (auto server_ptr = _server_weak_ptr.lock(); server_ptr && _server_thread_ptr)
     792             :   {
     793             :     try
     794             :     {
     795         141 :       server_ptr->shutdown();
     796         141 :       _server_thread_ptr->join();
     797         141 :       _server_thread_ptr.reset();
     798             :     }
     799           0 :     catch (...)
     800             :     {
     801           0 :     }
     802         187 :   }
     803         187 : }
     804             : 
     805             : /// Explicitly instantiate the addServerAction method for the valid request types
     806             : ///@{
     807             : template void WebServerControl::addServerAction<WebServerControl::RequestMethod::GET>(
     808             :     const std::string &,
     809             :     std::function<WebServerControl::Response(const WebServerControl::Request &,
     810             :                                              WebServerControl &)> &&,
     811             :     const WebServerControl::ServerActionOptions &);
     812             : template void WebServerControl::addServerAction<WebServerControl::RequestMethod::POST>(
     813             :     const std::string &,
     814             :     std::function<WebServerControl::Response(const WebServerControl::Request &,
     815             :                                              WebServerControl &)> &&,
     816             :     const WebServerControl::ServerActionOptions & options);
     817             : ///@}
     818             : 
     819             : const nlohmann::json &
     820         513 : WebServerControl::Request::getJSON() const
     821             : {
     822         513 :   if (!hasJSON())
     823           0 :     throw std::runtime_error("Request does not contain JSON when it should");
     824         513 :   return *_json;
     825             : }
     826             : 
     827         815 : WebServerControl::Response::Response(const unsigned int status_code) : _status_code(status_code) {}
     828             : 
     829        1939 : WebServerControl::Response::Response(const unsigned int status_code, const nlohmann::json & json)
     830        1939 :   : _status_code(status_code), _json(json)
     831             : {
     832        1939 : }
     833             : 
     834             : const nlohmann::json &
     835        1939 : WebServerControl::Response::getJSON() const
     836             : {
     837        1939 :   if (!_json)
     838           0 :     throw std::runtime_error("Response does not contain JSON when it should");
     839        1939 :   return *_json;
     840             : }
     841             : 
     842             : const std::string &
     843          32 : WebServerControl::Response::getError() const
     844             : {
     845          32 :   if (!_error)
     846           0 :     ::mooseError("Does not have an error");
     847          32 :   return *_error;
     848             : }
     849             : 
     850          32 : WebServerControl::ErrorResponse::ErrorResponse(const std::string & error,
     851          32 :                                                const unsigned int status_code /* = 400 */)
     852          32 :   : WebServerControl::Response(status_code)
     853             : {
     854          32 :   setError(error);
     855          32 : }

Generated by: LCOV version 1.14