LCOV - code coverage report
Current view: top level - include/controls - WebServerControl.h (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 7323e9 Lines: 43 48 89.6 %
Date: 2025-11-05 20:01:15 Functions: 48 48 100.0 %
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             : #pragma once
      11             : 
      12             : #include "Control.h"
      13             : 
      14             : #include <atomic>
      15             : #include <memory>
      16             : #include <thread>
      17             : 
      18             : #include "WebServerControlTypeRegistry.h"
      19             : 
      20             : class StartWebServerControlAction;
      21             : class HttpServer;
      22             : 
      23             : /**
      24             :  * Starts a webserver that an external process can connect to
      25             :  * in order to send JSON messages to control the solve
      26             :  */
      27             : class WebServerControl : public Control
      28             : {
      29             : public:
      30             :   static InputParameters validParams();
      31             : 
      32             :   WebServerControl(const InputParameters & parameters);
      33             :   ~WebServerControl();
      34             : 
      35             :   /**
      36             :    * Start the server
      37             :    *
      38             :    * Called by the StartWebServerControlAction after controls are setup
      39             :    */
      40             :   void startServer(const Moose::PassKey<StartWebServerControlAction>);
      41             : 
      42             :   virtual void execute() override final;
      43             : 
      44             :   using ControlledValueBase = Moose::WebServerControlTypeRegistry::ControlledValueBase;
      45             : 
      46             :   /**
      47             :    * Class containing a value to be controlled.
      48             :    *
      49             :    * Is responsible for building the value from JSON input,
      50             :    * broadcasting the value over all ranks, and setting the
      51             :    * value on all ranks.
      52             :    *
      53             :    * These objects are registered in
      54             :    * Moose::WebServerControlTypeRegistration.
      55             :    */
      56             :   template <class T>
      57             :   class ControlledValue : public ControlledValueBase
      58             :   {
      59             :   public:
      60          62 :     ControlledValue(const std::string & name, const std::string & type)
      61          62 :       : ControlledValueBase(name, type)
      62             :     {
      63          62 :     }
      64         128 :     ControlledValue(const std::string & name, const std::string & type, const T & value)
      65         128 :       : ControlledValueBase(name, type), _value(value)
      66             :     {
      67         128 :     }
      68             : 
      69             :     /// The underlying type of the value
      70             :     using value_type = T;
      71             : 
      72         190 :     virtual void setControllableValue(WebServerControl & control) override final
      73             :     {
      74         190 :       control.comm().broadcast(_value);
      75         190 :       control.setControllableValueByName<T>(name(), _value);
      76         190 :     }
      77             : 
      78             :   private:
      79             :     /// The underlying value
      80             :     T _value;
      81             :   };
      82             : 
      83             : protected:
      84             :   /**
      85             :    * Define the valid methods for a client request.
      86             :    */
      87             :   enum class RequestMethod
      88             :   {
      89             :     GET,
      90             :     POST
      91             :   };
      92             : 
      93             :   /**
      94             :    * Represents a request from the client.
      95             :    */
      96             :   struct Request
      97             :   {
      98             :     Request() = default;
      99             : 
     100             :     /**
     101             :      * @return Whether or not the request has JSON data
     102             :      */
     103         513 :     bool hasJSON() const { return _json.has_value(); }
     104             : 
     105             :     /**
     106             :      * @return Get the JSON data from the request
     107             :      */
     108             :     const nlohmann::json & getJSON() const;
     109             : 
     110             :     /**
     111             :      * Set the JSON data in the request.
     112             :      */
     113         513 :     void setJSON(const nlohmann::json & json, const Moose::PassKey<WebServerControl>)
     114             :     {
     115         513 :       _json = json;
     116         513 :     }
     117             : 
     118             :   private:
     119             :     /// The underlying JSON data, if any
     120             :     std::optional<nlohmann::json> _json;
     121             :   };
     122             : 
     123             :   /**
     124             :    * Represents a response to the client from the server.
     125             :    */
     126             :   struct Response
     127             :   {
     128             :     Response() = default;
     129             : 
     130             :     /**
     131             :      * Construct a response given a status code.
     132             :      */
     133             :     Response(const unsigned int status_code);
     134             :     /**
     135             :      * Construct a response given a status code and JSON data.
     136             :      */
     137             :     Response(const unsigned int status_code, const nlohmann::json & json);
     138             : 
     139             :     /**
     140             :      * @return The status code for the response
     141             :      */
     142        2742 :     unsigned int getStatusCode() const { return _status_code; }
     143             : 
     144             :     /**
     145             :      * @return Whether or not the response has JSON data
     146             :      */
     147        2710 :     bool hasJSON() const { return _json.has_value(); }
     148             :     /**
     149             :      * @return Get the JSON data in the response if it exists
     150             :      */
     151             :     const nlohmann::json & getJSON() const;
     152             : 
     153             :     /**
     154             :      * @return Whether or not the response is an error
     155             :      */
     156        2742 :     bool hasError() const { return _error.has_value(); }
     157             :     /**
     158             :      * @return Get the error message if it exists
     159             :      */
     160             :     const std::string & getError() const;
     161             : 
     162             :   protected:
     163             :     /**
     164             :      * Set the error message
     165             :      */
     166          32 :     void setError(const std::string & error) { _error = error; }
     167             : 
     168             :   private:
     169             :     /// The status code
     170             :     unsigned int _status_code = 0;
     171             :     /// The JSON data, if any
     172             :     std::optional<nlohmann::json> _json;
     173             :     /// The error message, if any
     174             :     std::optional<std::string> _error;
     175             :   };
     176             : 
     177             :   /**
     178             :    * Represents an error response to the client from the server.
     179             :    */
     180             :   struct ErrorResponse : public Response
     181             :   {
     182             :     ErrorResponse(const std::string & error, const unsigned int status_code = 400);
     183             :   };
     184             : 
     185             :   /**
     186             :    * Options to be passed to addServerAction
     187             :    */
     188             :   struct ServerActionOptions
     189             :   {
     190        1740 :     ServerActionOptions() : _require_waiting(true), _require_initialized(true) {}
     191             : 
     192             :     /**
     193             :      * @return Whether or not to require waiting
     194             :      */
     195        2742 :     bool getRequireWaiting() const { return _require_waiting; }
     196             :     /**
     197             :      * Set the require waiting flag; only accessible by the WebServerControl
     198             :      */
     199         580 :     void setRequireWaiting(const bool value, const Moose::PassKey<WebServerControl>)
     200             :     {
     201         580 :       _require_waiting = value;
     202         580 :     }
     203             : 
     204             :     /**
     205             :      * @return Whether or not to require initialization
     206             :      */
     207        2742 :     bool getRequireInitialized() const { return _require_initialized; }
     208             :     /**
     209             :      * Set the require initialized flag; only accessible by the WebServerControl
     210             :      */
     211         290 :     void setRequireInitialized(const bool value, const Moose::PassKey<WebServerControl>)
     212             :     {
     213         290 :       _require_initialized = value;
     214         290 :     }
     215             : 
     216             :     /**
     217             :      * @return The JSON keys that are required in the request data
     218             :      */
     219        2742 :     const std::set<std::string> getRequiredJSONKeys() const { return _required_json_keys; }
     220             :     /**
     221             :      * Append a key to be required in JSON in the request data
     222             :      */
     223         290 :     void requireJSONKey(const std::string & key) { _required_json_keys.insert(key); }
     224             :     /**
     225             :      * Append keys to be required in JSON in the request data
     226             :      */
     227         290 :     void requireJSONKeys(std::initializer_list<std::string> && keys)
     228             :     {
     229         290 :       _required_json_keys.insert(keys);
     230         290 :     }
     231             : 
     232             :   private:
     233             :     /// Whether or not to require waiting
     234             :     bool _require_waiting;
     235             :     /// Whether or not to require initialization
     236             :     bool _require_initialized;
     237             :     /// JSON keys that are required in the data
     238             :     std::set<std::string> _required_json_keys;
     239             :   };
     240             : 
     241             :   /**
     242             :    * Adds an action for the server to perform at the given path.
     243             :    *
     244             :    * @param path The path for the webserver to act on
     245             :    * @param action The action to perform
     246             :    * @param options Options to apply to the endpoint
     247             :    */
     248             :   template <RequestMethod method>
     249             :   void addServerAction(const std::string & path,
     250             :                        std::function<Response(const Request &, WebServerControl &)> && action,
     251             :                        const ServerActionOptions & options = {});
     252             : 
     253             :   /**
     254             :    * Entrypoint for controls derived from this one to add additional actions
     255             :    */
     256         145 :   virtual void addServerActions() {};
     257             : 
     258             :   /**
     259             :    * Helper for converting a value to JSON for the given key
     260             :    *
     261             :    * Will raise exceptions with the context of the key if encountered
     262             :    */
     263             :   template <class value_T, class key_T>
     264             :   static value_T convertJSON(const nlohmann::json & json_value, const key_T & key);
     265             : 
     266             :   /**
     267             :    * Get whether or not the control is currently waiting
     268             :    */
     269        3024 :   bool isCurrentlyWaiting() const { return _currently_waiting.load(); }
     270             : 
     271             :   /**
     272             :    * Stores the information sent by the client on initialize
     273             :    */
     274             :   struct ClientInfo
     275             :   {
     276             :     /// Client name
     277             :     std::string name;
     278             :     /// Client host
     279             :     std::string host;
     280             :     /// Client user
     281             :     std::string user;
     282             :     /// Raw data
     283             :     nlohmann::json data;
     284             :   };
     285             : 
     286             :   /**
     287             :    * Get the information sent by the client on initialize
     288             :    */
     289             :   ClientInfo getClientInfo() const;
     290             : 
     291             :   /**
     292             :    * Output a message with the prefix of this control type and name
     293             :    */
     294             :   void outputMessage(const std::string & message) const;
     295             : 
     296             : private:
     297             :   /**
     298             :    * Adds the internal actions to the server
     299             :    *
     300             :    * Enables derived classes to override addServerActions()
     301             :    * without mucking with the standard actions.
     302             :    */
     303             :   void addServerActionsInternal();
     304             : 
     305             :   /**
     306             :    * Set the ClientInfo object received from the client during /initialize
     307             :    */
     308             :   void setClientInfo(const ClientInfo & info);
     309             : 
     310             :   /**
     311             :    * Store a client's poke, which is a timing used to determine the client timeout
     312             :    */
     313             :   void clientPoke();
     314             : 
     315             :   /**
     316             :    * Whether or not the client has called /initialize
     317             :    */
     318        2902 :   bool isClientInitialized() const { return _client_initialized.load(); }
     319             :   /**
     320             :    * Set that the client has called /initialized; used by the server
     321             :    */
     322         141 :   void setClientInitialized() { _client_initialized.store(true); }
     323             : 
     324             :   /**
     325             :    * Set that the control is currently waiting; used by the server
     326             :    */
     327         518 :   void setCurrentlyWaiting(const bool value = true) { _currently_waiting.store(value); }
     328             : 
     329             :   /**
     330             :    * Whether or not the client has called /terminate
     331             :    */
     332         239 :   bool isTerminateRequested() const { return _terminate_requested.load(); }
     333             :   /**
     334             :    * Set for the control to terminate the solve; used by /terminate in the server
     335             :    */
     336         247 :   void setTerminateRequested(const bool value = true) { _terminate_requested.store(value); }
     337             : 
     338             :   /**
     339             :    * Get whether or not the client sent the kill command.
     340             :    */
     341         826 :   bool isKillRequested() const { return _kill_requested.load(); }
     342             :   /**
     343             :    * Set for the control to kill the solve; used by /kill in the server
     344             :    */
     345          36 :   void setKillRequested() { _kill_requested.store(true); }
     346             : 
     347             :   /**
     348             :    * Output a timing message with the prefix of this control
     349             :    */
     350             :   void outputClientTiming(const std::string & message,
     351             :                           const std::chrono::time_point<std::chrono::steady_clock> & start) const;
     352             : 
     353             :   /**
     354             :    * Helper for producing an error message about a client timeout
     355             :    *
     356             :    * Used for both the initial and during-run timeout
     357             :    */
     358             :   std::string clientTimeoutErrorMessage(const Real timeout,
     359             :                                         const std::string & timeout_param_name,
     360             :                                         const std::optional<std::string> & suffix = {}) const;
     361             : 
     362             :   /**
     363             :    * Stop the server if it exists and is running
     364             :    */
     365             :   void stopServer();
     366             : 
     367             :   /// Port to listen on, if any
     368             :   const unsigned int * const _port;
     369             :   /// File socket to listen on, if any
     370             :   const FileName * const _file_socket;
     371             :   /// Time in seconds to allow the client to initially communicate before timing out
     372             :   const Real _initial_client_timeout;
     373             :   /// Time in seconds to allow the client to communicate after init before timing out
     374             :   const Real _client_timeout;
     375             : 
     376             :   /// Whether or not the client has called /initialize
     377             :   std::atomic<bool> _client_initialized = false;
     378             :   /// Whether or not the Control is currently waiting
     379             :   std::atomic<bool> _currently_waiting = false;
     380             :   /// Whether or not the solve should be terminated in the next execute() call
     381             :   std::atomic<bool> _terminate_requested = false;
     382             :   /// Whether or not the client has called /kill
     383             :   std::atomic<bool> _kill_requested = false;
     384             :   /// The most recent time we've heard from the client
     385             :   std::atomic<int64_t> _last_client_poke = 0;
     386             : 
     387             :   /// Client information received on /initialize by the server
     388             :   std::optional<ClientInfo> _client_info;
     389             :   /// Lock for _client_info as it is written by the server thread
     390             :   mutable std::mutex _client_info_lock;
     391             : 
     392             :   /// Weak pointer to the server; the server itself is owned by the server thread
     393             :   std::weak_ptr<HttpServer> _server_weak_ptr;
     394             :   /// The server thread
     395             :   std::unique_ptr<std::thread> _server_thread_ptr;
     396             : 
     397             :   /// The values received to control; filled on rank 0 from the server and then broadcast
     398             :   std::vector<std::unique_ptr<ControlledValueBase>> _controlled_values;
     399             :   /// Mutex to prevent threaded writes to _controlled_values
     400             :   std::mutex _controlled_values_mutex;
     401             : };
     402             : 
     403             : template <class value_T, class key_T>
     404             : value_T
     405         947 : WebServerControl::convertJSON(const nlohmann::json & json_value, const key_T & key)
     406             : {
     407             :   try
     408             :   {
     409         947 :     return json_value[key].template get<value_T>();
     410             :   }
     411           0 :   catch (const std::exception & e)
     412             :   {
     413           0 :     std::ostringstream message;
     414           0 :     message << "While parsing '" << key << "' " << e.what();
     415           0 :     throw std::runtime_error(message.str());
     416           0 :   }
     417             : }

Generated by: LCOV version 1.14