https://mooseframework.inl.gov
WebServerControl.C
Go to the documentation of this file.
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 
18 
21 {
23  params.addClassDescription("Starts a webserver for sending/receiving JSON messages to get data "
24  "and control a running MOOSE calculation");
25  params.addParam<unsigned int>("port",
26  "The port to listen on; must provide either this or 'file_socket'");
27  params.addParam<FileName>(
28  "file_socket",
29  "The path to the unix file socket to listen on; must provide either this or 'port'");
30  params.addParam<Real>("initial_client_timeout",
31  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  params.addParam<Real>("client_timeout",
35  10,
36  "Time in seconds to allow the client to communicate; if this time is "
37  "surpassed the run will be killed");
38  return params;
39 }
40 
42  : Control(parameters),
43  _port(queryParam<unsigned int>("port")),
44  _file_socket(queryParam<FileName>("file_socket")),
45  _initial_client_timeout(getParam<Real>("initial_client_timeout")),
46  _client_timeout(getParam<Real>("client_timeout"))
47 {
48  if (!_port && !_file_socket)
49  mooseError("You must provide either the parameter 'port' or 'file_socket' to designate where "
50  "to listen");
51  if (_port && _file_socket)
52  paramError("port", "Cannot provide both 'port' and 'file_socket'");
53 }
54 
56 {
57  // Stop server if running; make sure this DOESN'T throw!
58  stopServer();
59 }
60 
61 void
63 {
64  // Only start on rank 0
65  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  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  _server_weak_ptr = server_ptr;
79 
80  // Add all of the actions
82 
83  // Post message about server start
84  {
85  std::ostringstream message;
86  message << "Starting server on ";
87  if (_port)
88  message << "port " << *_port;
89  else if (_file_socket)
90  message << "file socket " << *_file_socket;
91  outputMessage(message.str());
92  }
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  auto console = _console;
97  _server_thread_ptr = std::make_unique<std::thread>(
98  [server_ptr, console](const auto port, const auto file_socket)
99  {
100  mooseAssert(server_ptr, "Null server");
101  auto & server = *server_ptr;
102  mooseAssert(port || file_socket, "Neither provided");
103 
104  try
105  {
106  if (port)
107  server.startListening(uint16_t(*port));
108  else
109  server.startListening(*file_socket);
110  }
111  catch (std::exception & e)
112  {
113  console << "Server failed with exception: " << e.what() << std::endl;
114  }
115  },
116  _port,
117  _file_socket);
118  }
119 
120  // Wait for the client to call /initialize
121  {
122  // Output that we're waiting for the client
123  outputMessage("Waiting for client to initialize...");
124 
125  // To output initialization time
126  const auto start = std::chrono::steady_clock::now();
127 
128  while (!isClientInitialized())
129  {
130  // Kill command sent before initialize
131  if (isKillRequested())
132  {
133  stopServer();
134  mooseError("Client sent kill command; exiting");
135  }
136 
137  // Make sure we haven't reached the timeout
138  const auto now = std::chrono::steady_clock::now();
139  const auto elapsed = std::chrono::duration<double>(now - start).count();
140  if (elapsed > _initial_client_timeout)
142  _initial_client_timeout, "initial_client_timeout", "during initialization"));
143 
144  // Poll until the next check
145  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  clientPoke();
151 
152  // Output that the client has initialized
153  const auto info = getClientInfo();
155  "\"" + info.name + "\" from " + info.user + "@" + info.host + " initialized", start);
156  }
157  }
158 
159  // Let the remaining ranks wait until rank 0 is done
161 }
162 
163 void
165 {
166  // If simulation is requested to terminate, do not go through this control
168  return;
169 
170  // For outputting the time spent waiting
171  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  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  bool terminate_solve = false; // Set value to avoid compiler warnings
180 
181  // Wait for the server on rank 0 to be done
182  if (processor_id() == 0)
183  {
184  TIME_SECTION("execute()", 3, "WebServerControl waiting for input")
185  outputMessage("Waiting for client to continue...");
186 
187  setCurrentlyWaiting(true);
188 
189  // While waiting, yield so the server has time to run. Check for client
190  // timeouts and a kill command.
191  while (isCurrentlyWaiting())
192  {
193  // Kill command sent
194  if (isKillRequested())
195  {
196  stopServer();
197  mooseError("Client sent kill command; exiting");
198  }
199 
200  // Check client timeout
201  const auto last = std::chrono::milliseconds(_last_client_poke.load());
202  const auto now = std::chrono::steady_clock::now().time_since_epoch();
203  const auto elapsed = std::chrono::duration<double>(now - last).count();
204  if (elapsed > _client_timeout)
205  {
206  stopServer();
208  }
209 
210  std::this_thread::sleep_for(std::chrono::milliseconds(10));
211  }
212 
213  // Output waiting time
214  outputClientTiming("continued", start);
215 
216  for (const auto & value_ptr : _controlled_values)
217  name_and_types.emplace_back(value_ptr->name(), value_ptr->type());
218 
219  terminate_solve = isTerminateRequested();
220  setTerminateRequested(false);
221  _terminate_requested.store(false);
222  }
223 
224  // All processes need to wait
226 
227  // Construct the values on other processors to be received into so that
228  // they're parallel consistent
229  comm().broadcast(name_and_types);
230  if (processor_id() != 0)
231  for (const auto & [name, type] : name_and_types)
233 
234  // Set all of the values
235  for (auto & value_ptr : _controlled_values)
236  {
237  try
238  {
239  value_ptr->setControllableValue(*this);
240  }
241  catch (...)
242  {
243  mooseError("Error setting '",
244  value_ptr->type(),
245  "' typed value for parameter '",
246  value_ptr->name(),
247  "'; it is likely that the parameter has a different type");
248  }
249  }
250 
251  _controlled_values.clear();
252 
253  // Set solve terminate on all ranks, if requested
254  _communicator.broadcast(terminate_solve);
255  if (terminate_solve)
257 }
258 
259 template <WebServerControl::RequestMethod method>
260 void
262  const std::string & path,
264  WebServerControl &)> && action,
266 {
267  static_assert(method == RequestMethod::GET || method == RequestMethod::POST, "Unknown method");
268 
269  auto server_ptr = _server_weak_ptr.lock();
270  if (!server_ptr || _server_thread_ptr)
271  mooseError("addServerAction(): Can only call during addServerActions()");
272  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  const std::weak_ptr<WebServerControl> control_weak_ptr =
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  auto full_action =
283  [control_weak_ptr, path, options, action](const HttpRequest & http_request) -> HttpResponse
284  {
285  // Helper for returning an error
286  const auto error = [](const std::string & error,
287  const unsigned int status_code = 400) -> HttpResponse
288  {
289  miniJson::Json::_object response;
290  response["error"] = error;
291  return HttpResponse{status_code, response};
292  };
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  if (auto control_ptr = control_weak_ptr.lock())
298  {
299  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
305  const auto & json_keys = options.getRequiredJSONKeys();
306  // Load JSON data if it exists into nlohmann::json
307  if (!http_request.json().isNull())
308  {
309  // JSON data exists but was not needed
310  if (json_keys.empty())
311  return error("Request should not have JSON");
312 
313  // Serialize from minijson to nlohmann::json
314  const auto serialized = http_request.json().serialize();
315  const auto deserialized = nlohmann::json::parse(serialized);
316 
317  // Check for required key(s)
318  for (const auto & key : json_keys)
319  if (!deserialized.contains(key))
320  return error("Missing required key '" + key + "' in JSON");
321 
322  // And set in the request
323  request.setJSON(deserialized, {});
324  }
325  // JSON data does not exist but it was needed
326  else if (json_keys.size())
327  return error("Request should have JSON");
328 
329  // Option requires initialization
330  if (options.getRequireInitialized() && !control.isClientInitialized())
331  return error("Client has not initialized the control");
332 
333  // Option requires waiting
334  if (options.getRequireWaiting() && !control.isCurrentlyWaiting())
335  return error("Control is not currently waiting for data");
336 
337  // Call the action method to act on the request
338  Response response;
339  try
340  {
341  response = action(request, control);
342  }
343  catch (const std::exception & e)
344  {
345  return error(e.what());
346  }
347 
348  // Has an error, return that instead
349  if (response.hasError())
350  return error(response.getError(), response.getStatusCode());
351 
352  // No JSON data, just a status code
353  if (!response.hasJSON())
354  return HttpResponse{response.getStatusCode()};
355 
356  // Has JSON data, convert from nlohmann::json to miniJson
357  const auto serialized = response.getJSON().dump();
358  std::string parse_error;
359  const auto json = miniJson::Json::parse(serialized, parse_error);
360  mooseAssert(parse_error.empty(), "Should be empty");
361  return HttpResponse{response.getStatusCode(), json};
362  }
363 
364  // Failed to capture weak_ptr to WebServerControl
365  return error("Control is no longer available");
366  };
367 
368  auto & when = *server.when("/" + path);
369  if constexpr (method == RequestMethod::GET)
370  when.requested(full_action);
371  else
372  when.posted(full_action);
373 }
374 
377 {
378  std::lock_guard lock(_client_info_lock);
379  if (!_client_info)
380  mooseError("WebServerControl::getClientInfo(): Client info is not set yet");
381  return *_client_info;
382 }
383 
384 void
385 WebServerControl::outputMessage(const std::string & message) const
386 {
387  _console << typeAndName() << ": " << message << std::endl;
388 }
389 
390 void
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  const auto check = [](const Request &, WebServerControl &) -> Response
403  { return Response{200}; };
404 
405  ServerActionOptions options;
406  options.setRequireWaiting(false, {});
407  options.setRequireInitialized(false, {});
408 
409  addServerAction<RequestMethod::GET>("check", check, options);
410  }
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  const auto set_continue = [](const Request &, WebServerControl & control) -> Response
420  {
421  control.setCurrentlyWaiting(false);
422  return Response{200};
423  };
424 
425  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  const auto initialize = [](const Request & req, WebServerControl & control) -> Response
445  {
446  if (control.isClientInitialized())
447  return ErrorResponse("Initialize has already been called");
448 
449  // Store the information received in the client info
450  const auto & json = req.getJSON();
451  ClientInfo client_info;
452  const auto name = convertJSON<std::string>(json, "name");
453  const auto host = convertJSON<std::string>(json, "host");
454  const auto user = convertJSON<std::string>(json, "user");
455  client_info.data = json;
456  control.setClientInfo(client_info);
457 
458  // Capture the sorted exceute on flags
459  std::set<std::string> flags_sorted;
460  for (const auto & it : control.getParam<ExecFlagEnum>("execute_on"))
461  flags_sorted.insert(it);
462  const std::vector<std::string> flags(flags_sorted.begin(), flags_sorted.end());
463 
464  // Send back context about the control
465  nlohmann::json response_json;
466  response_json["control_name"] = control.name();
467  response_json["control_type"] = control.type();
468  response_json["execute_on_flags"] = flags_sorted;
469 
470  // Set initialized
471  control.setClientInitialized();
472 
473  return Response{200, response_json};
474  };
475 
476  ServerActionOptions options;
477  options.setRequireWaiting(false, {});
478  options.setRequireInitialized(false, {});
479  options.requireJSONKeys({"host", "name", "user"});
480 
481  addServerAction<RequestMethod::POST>("initialize", initialize, options);
482  }
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  const auto poke = [](const Request &, WebServerControl & control) -> Response
490  {
491  control.clientPoke();
492  return Response{200};
493  };
494 
495  ServerActionOptions options;
496  options.setRequireWaiting(false, {});
497 
498  addServerAction<RequestMethod::GET>("poke", poke, options);
499  }
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  const auto terminate = [](const Request &, WebServerControl & control) -> Response
509  {
510  control.setTerminateRequested();
511  control.setCurrentlyWaiting(false);
512  return Response{200};
513  };
514 
515  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  const auto waiting = [](const Request &, WebServerControl & control) -> Response
528  {
529  nlohmann::json response_json;
530  if (control.isCurrentlyWaiting())
531  {
532  response_json["waiting"] = true;
533  response_json["execute_on_flag"] =
534  static_cast<std::string>(control._fe_problem.getCurrentExecuteOnFlag());
535  }
536  else
537  response_json["waiting"] = false;
538 
539  return Response{200, response_json};
540  };
541 
542  ServerActionOptions options;
543  options.setRequireWaiting(false, {});
544 
545  addServerAction<RequestMethod::GET>("waiting", waiting, options);
546  }
547 
548  // GET /kill: Tell the client poll thread to kill
549  // Requires waiting: no
550  {
551  const auto kill = [](const Request &, WebServerControl & control) -> Response
552  {
553  control.setKillRequested();
554  return Response{200};
555  };
556 
557  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  const auto get_dt = [](const Request &, WebServerControl & control) -> Response
575  {
576  nlohmann::json response_json;
577  response_json["dt"] = control._fe_problem.dt();
578  return Response{200, response_json};
579  };
580 
581  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  const auto get_postprocessor = [](const Request & req, WebServerControl & control) -> Response
597  {
598  // Get the postprocessor name
599  const auto name = convertJSON<std::string>(req.getJSON(), "name");
600 
601  // Postprocessor should exist
602  if (!control.hasPostprocessorByName(name))
603  return ErrorResponse("Postprocessor '" + name + "' not found");
604 
605  nlohmann::json response_json;
606  response_json["value"] = control.getPostprocessorValueByName(name);
607  return Response{200, response_json};
608  };
609 
610  ServerActionOptions options;
611  options.requireJSONKey("name");
612 
613  addServerAction<RequestMethod::POST>("get/postprocessor", get_postprocessor, options);
614  }
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  const auto get_reporter = [](const Request & req, WebServerControl & control) -> Response
630  {
631  // Get the reporter name
632  const auto name = convertJSON<std::string>(req.getJSON(), "name");
634  return ErrorResponse("Name '" + name + "' not a valid reporter value name");
635  const auto rname = ReporterName(name);
636 
637  // Reporter should exist
638  if (!control.hasReporterValueByName(rname))
639  return ErrorResponse("Reporter value '" + name + "' was not found");
640 
641  // Store the reporter value
642  nlohmann::json response_json;
643  control.getReporterContextBaseByName(rname).store(response_json["value"]);
644 
645  return Response{200, response_json};
646  };
647 
648  ServerActionOptions options;
649  options.requireJSONKey("name");
650 
651  addServerAction<RequestMethod::POST>("get/reporter", get_reporter, options);
652  }
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  const auto get_time = [](const Request &, WebServerControl & control) -> Response
664  {
665  nlohmann::json response_json;
666  response_json["time"] = control._fe_problem.time();
667  return Response{200, response_json};
668  };
669 
670  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  const auto set_controllable = [](const Request & req, WebServerControl & control) -> Response
692  {
693  const auto & json = req.getJSON();
694 
695  // Get the parameter type
696  const auto type = convertJSON<std::string>(json, "type");
697  const auto registered_type = Moose::WebServerControlTypeRegistry::query(type);
698  if (!registered_type)
699  return ErrorResponse("Type '" + type +
700  "' not registered for setting a controllable parameter");
701 
702  // Get the parameter name
703  const auto name = convertJSON<std::string>(json, "name");
704  // Parameter should exist
705  if (!control.hasControllableParameterByName(name))
706  return ErrorResponse("Controllable parameter '" + name + "' not found");
707 
708  // 'value' must exist
709  const auto value_it = json.find("value");
710  if (value_it == json.end())
711  return ErrorResponse("Missing 'value' entry");
712 
713  // Build the value (also does the parsing)
714  {
715  std::unique_ptr<ControlledValueBase> value;
716  try
717  {
718  value = registered_type->build(name, *value_it);
719  }
720  catch (std::exception & e)
721  {
722  return ErrorResponse("While parsing 'value': " + std::string(e.what()));
723  }
724 
725  std::lock_guard<std::mutex> lock(control._controlled_values_mutex);
726  control._controlled_values.emplace_back(std::move(value));
727  }
728 
729  return Response{201};
730  };
731 
732  ServerActionOptions options;
733  options.requireJSONKeys({"name", "type", "value"});
734 
735  addServerAction<RequestMethod::POST>("set/controllable", set_controllable, options);
736  }
737 
738  // Let derived classes add actions
740 }
741 
742 void
744 {
745  std::lock_guard lock(_client_info_lock);
746  _client_info = info;
747 }
748 
749 void
751 {
752  const int64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(
753  std::chrono::steady_clock::now().time_since_epoch())
754  .count();
755  _last_client_poke.store(now);
756 }
757 
758 void
760  const std::string & message,
761  const std::chrono::time_point<std::chrono::steady_clock> & start) const
762 {
763  const auto now = std::chrono::steady_clock::now();
764  const auto elapsed = std::chrono::duration<double>(now - start).count();
765 
766  std::ostringstream out;
767  out << "Client " << message << " after " << std::fixed << std::setprecision(2) << elapsed
768  << " seconds";
769  outputMessage(out.str());
770 }
771 
772 std::string
774  const Real timeout,
775  const std::string & timeout_param_name,
776  const std::optional<std::string> & suffix /* = {} */) const
777 {
778  std::ostringstream oss;
779  oss << "The client timed out" << (suffix ? (" " + *suffix) : "") << "\nThe timeout is "
780  << std::fixed << std::setprecision(2) << timeout << " seconds and is set by the '"
781  << timeout_param_name << "' parameter";
782  return oss.str();
783 }
784 
785 void
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  if (auto server_ptr = _server_weak_ptr.lock(); server_ptr && _server_thread_ptr)
792  {
793  try
794  {
795  server_ptr->shutdown();
796  _server_thread_ptr->join();
797  _server_thread_ptr.reset();
798  }
799  catch (...)
800  {
801  }
802  }
803 }
804 
807 template void WebServerControl::addServerAction<WebServerControl::RequestMethod::GET>(
808  const std::string &,
810  WebServerControl &)> &&,
812 template void WebServerControl::addServerAction<WebServerControl::RequestMethod::POST>(
813  const std::string &,
815  WebServerControl &)> &&,
816  const WebServerControl::ServerActionOptions & options);
818 
819 const nlohmann::json &
821 {
822  if (!hasJSON())
823  throw std::runtime_error("Request does not contain JSON when it should");
824  return *_json;
825 }
826 
827 WebServerControl::Response::Response(const unsigned int status_code) : _status_code(status_code) {}
828 
829 WebServerControl::Response::Response(const unsigned int status_code, const nlohmann::json & json)
830  : _status_code(status_code), _json(json)
831 {
832 }
833 
834 const nlohmann::json &
836 {
837  if (!_json)
838  throw std::runtime_error("Response does not contain JSON when it should");
839  return *_json;
840 }
841 
842 const std::string &
844 {
845  if (!_error)
846  ::mooseError("Does not have an error");
847  return *_error;
848 }
849 
851  const unsigned int status_code /* = 400 */)
852  : WebServerControl::Response(status_code)
853 {
854  setError(error);
855 }
void setTerminateRequested(const bool value=true)
Set for the control to terminate the solve; used by /terminate in the server.
void setError(const std::string &error)
Set the error message.
bool isTerminateRequested() const
Whether or not the client has called /terminate.
MPI_Request request
A MultiMooseEnum object to hold "execute_on" flags.
Definition: ExecFlagEnum.h:21
static InputParameters validParams()
Class constructor.
Definition: Control.C:16
void addServerAction(const std::string &path, std::function< Response(const Request &, WebServerControl &)> &&action, const ServerActionOptions &options={})
Adds an action for the server to perform at the given path.
void setClientInfo(const ClientInfo &info)
Set the ClientInfo object received from the client during /initialize.
bool isClientInitialized() const
Whether or not the client has called /initialize.
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
Definition: MooseBase.h:439
void setRequireWaiting(const bool value, const Moose::PassKey< WebServerControl >)
Set the require waiting flag; only accessible by the WebServerControl.
void addServerActionsInternal()
Adds the internal actions to the server.
MPI_Info info
Starts a webserver that an external process can connect to in order to send JSON messages to control ...
ClientInfo getClientInfo() const
Get the information sent by the client on initialize.
ErrorResponse(const std::string &error, const unsigned int status_code=400)
virtual void addServerActions()
Entrypoint for controls derived from this one to add additional actions.
const Real _initial_client_timeout
Time in seconds to allow the client to initially communicate before timing out.
void setCurrentlyWaiting(const bool value=true)
Set that the control is currently waiting; used by the server.
std::optional< ClientInfo > _client_info
Client information received on /initialize by the server.
void requireJSONKeys(std::initializer_list< std::string > &&keys)
Append keys to be required in JSON in the request data.
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
const Parallel::Communicator & comm() const
Represents an error response to the client from the server.
WebServerControl(const InputParameters &parameters)
const FileName *const _file_socket
File socket to listen on, if any.
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
These are reworked from https://stackoverflow.com/a/11003103.
std::shared_ptr< MooseObject > getSharedPtr()
Get another shared pointer to this object that has the same ownership group.
Definition: MooseObject.C:72
const Parallel::Communicator & _communicator
void startServer(const Moose::PassKey< StartWebServerControlAction >)
Start the server.
void requireJSONKey(const std::string &key)
Append a key to be required in JSON in the request data.
void initialize(EquationSystems &es, const std::string &system_name)
std::mutex _client_info_lock
Lock for _client_info as it is written by the server thread.
bool isKillRequested() const
Get whether or not the client sent the kill command.
virtual bool isSolveTerminationRequested() const
Check of termination has been requested.
Definition: Problem.h:43
void outputMessage(const std::string &message) const
Output a message with the prefix of this control type and name.
std::optional< nlohmann::json > _json
The underlying JSON data, if any.
std::vector< std::unique_ptr< ControlledValueBase > > _controlled_values
The values received to control; filled on rank 0 from the server and then broadcast.
const nlohmann::json & getJSON() const
const std::string & name() const
Get the name of the class.
Definition: MooseBase.h:103
const unsigned int *const _port
Port to listen on, if any.
void setRequireInitialized(const bool value, const Moose::PassKey< WebServerControl >)
Set the require initialized flag; only accessible by the WebServerControl.
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
std::atomic< int64_t > _last_client_poke
The most recent time we&#39;ve heard from the client.
virtual void terminateSolve()
Allow objects to request clean termination of the solve.
Definition: Problem.h:37
Options to be passed to addServerAction.
FEProblemBase & _fe_problem
Reference to the FEProblemBase for this object.
Definition: Control.h:76
static const RegisteredTypeBase * query(const std::string &type)
Query the registration for the given type.
const std::string & getError() const
virtual void execute() override final
Execute the control.
const std::string & type() const
Get the type of this class.
Definition: MooseBase.h:93
void outputClientTiming(const std::string &message, const std::chrono::time_point< std::chrono::steady_clock > &start) const
Output a timing message with the prefix of this control.
void clientPoke()
Store a client&#39;s poke, which is a timing used to determine the client timeout.
std::unique_ptr< std::thread > _server_thread_ptr
The server thread.
std::string typeAndName() const
Get the class&#39;s combined type and name; useful in error handling.
Definition: MooseBase.C:57
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
static bool isValidName(const std::string &object_and_value_name)
Determines if the inputted string is convertible to a ReporterName.
Definition: ReporterName.C:35
const Real _client_timeout
Time in seconds to allow the client to communicate after init before timing out.
std::weak_ptr< HttpServer > _server_weak_ptr
Weak pointer to the server; the server itself is owned by the server thread.
std::atomic< bool > _terminate_requested
Whether or not the solve should be terminated in the next execute() call.
nlohmann::json data
Raw data.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
Base class for Control objects.
Definition: Control.h:34
bool isCurrentlyWaiting() const
Get whether or not the control is currently waiting.
Stores the information sent by the client on initialize.
OStreamProxy out
static const RegisteredTypeBase & get(const std::string &type)
Get the registration for the given type, erroring if it isn&#39;t registered.
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition: MooseBase.h:271
Represents a response to the client from the server.
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump...
void addParam(const std::string &name, const S &value, const std::string &doc_string)
These methods add an optional parameter and a documentation string to the InputParameters object...
registerMooseObject("MooseApp", WebServerControl)
const nlohmann::json & getJSON() const
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
Represents a request from the client.
static InputParameters validParams()
processor_id_type processor_id() const
std::string clientTimeoutErrorMessage(const Real timeout, const std::string &timeout_param_name, const std::optional< std::string > &suffix={}) const
Helper for producing an error message about a client timeout.
unsigned int getStatusCode() const
void ErrorVector unsigned int
The Reporter system is comprised of objects that can contain any number of data values.
Definition: ReporterName.h:30
void stopServer()
Stop the server if it exists and is running.
Result check(std::string requirements, const Registry &capabilities)
Checks if a set of requirements is satisified by the given capability registry.