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 : }