14 #include "minijson/minijson.h" 15 #include "tinyhttp/http.h" 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'");
29 "The path to the unix file socket to listen on; must provide either this or 'port'");
32 "Time in seconds to allow the client to begin communicating on init; if " 33 "this time is surpassed the run will be killed");
36 "Time in seconds to allow the client to communicate; if this time is " 37 "surpassed the run will be killed");
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"))
49 mooseError(
"You must provide either the parameter 'port' or 'file_socket' to designate where " 52 paramError(
"port",
"Cannot provide both 'port' and 'file_socket'");
75 auto server_ptr = std::make_shared<HttpServer>();
85 std::ostringstream message;
86 message <<
"Starting server on ";
88 message <<
"port " << *
_port;
98 [server_ptr, console](
const auto port,
const auto file_socket)
100 mooseAssert(server_ptr,
"Null server");
101 auto & server = *server_ptr;
102 mooseAssert(port || file_socket,
"Neither provided");
107 server.startListening(uint16_t(*port));
109 server.startListening(*file_socket);
111 catch (std::exception & e)
113 console <<
"Server failed with exception: " << e.what() << std::endl;
126 const auto start = std::chrono::steady_clock::now();
134 mooseError(
"Client sent kill command; exiting");
138 const auto now = std::chrono::steady_clock::now();
139 const auto elapsed = std::chrono::duration<double>(now - start).count();
145 std::this_thread::sleep_for(std::chrono::milliseconds(10));
155 "\"" +
info.name +
"\" from " +
info.user +
"@" +
info.host +
" initialized", start);
171 const auto start = std::chrono::steady_clock::now();
176 std::vector<std::pair<std::string, std::string>> name_and_types;
179 bool terminate_solve =
false;
184 TIME_SECTION(
"execute()", 3,
"WebServerControl waiting for input")
197 mooseError(
"Client sent kill command; exiting");
202 const auto now = std::chrono::steady_clock::now().time_since_epoch();
203 const auto elapsed = std::chrono::duration<double>(now - last).count();
210 std::this_thread::sleep_for(std::chrono::milliseconds(10));
217 name_and_types.emplace_back(value_ptr->name(), value_ptr->type());
231 for (
const auto & [
name,
type] : name_and_types)
239 value_ptr->setControllableValue(*
this);
245 "' typed value for parameter '",
247 "'; it is likely that the parameter has a different type");
259 template <WebServerControl::RequestMethod method>
262 const std::string & path,
271 mooseError(
"addServerAction(): Can only call during addServerActions()");
272 auto & server = *server_ptr;
276 const std::weak_ptr<WebServerControl> control_weak_ptr =
283 [control_weak_ptr, path, options, action](
const HttpRequest & http_request) -> HttpResponse
286 const auto error = [](
const std::string & error,
287 const unsigned int status_code = 400) -> HttpResponse
289 miniJson::Json::_object response;
290 response[
"error"] = error;
291 return HttpResponse{status_code, response};
297 if (
auto control_ptr = control_weak_ptr.lock())
299 auto & control = *control_ptr;
305 const auto & json_keys = options.getRequiredJSONKeys();
307 if (!http_request.json().isNull())
310 if (json_keys.empty())
311 return error(
"Request should not have JSON");
314 const auto serialized = http_request.json().serialize();
315 const auto deserialized = nlohmann::json::parse(serialized);
318 for (
const auto & key : json_keys)
319 if (!deserialized.contains(key))
320 return error(
"Missing required key '" + key +
"' in JSON");
323 request.setJSON(deserialized, {});
326 else if (json_keys.size())
327 return error(
"Request should have JSON");
330 if (options.getRequireInitialized() && !control.isClientInitialized())
331 return error(
"Client has not initialized the control");
334 if (options.getRequireWaiting() && !control.isCurrentlyWaiting())
335 return error(
"Control is not currently waiting for data");
341 response = action(
request, control);
343 catch (
const std::exception & e)
345 return error(e.what());
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");
365 return error(
"Control is no longer available");
368 auto & when = *server.when(
"/" + path);
370 when.requested(full_action);
372 when.posted(full_action);
380 mooseError(
"WebServerControl::getClientInfo(): Client info is not set yet");
409 addServerAction<RequestMethod::GET>(
"check",
check, options);
421 control.setCurrentlyWaiting(
false);
425 addServerAction<RequestMethod::GET>(
"continue", set_continue);
446 if (control.isClientInitialized())
450 const auto & json = req.
getJSON();
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);
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());
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;
471 control.setClientInitialized();
473 return Response{200, response_json};
481 addServerAction<RequestMethod::POST>(
"initialize",
initialize, options);
491 control.clientPoke();
498 addServerAction<RequestMethod::GET>(
"poke", poke, options);
510 control.setTerminateRequested();
511 control.setCurrentlyWaiting(
false);
515 addServerAction<RequestMethod::GET>(
"terminate", terminate);
529 nlohmann::json response_json;
530 if (control.isCurrentlyWaiting())
532 response_json[
"waiting"] =
true;
533 response_json[
"execute_on_flag"] =
534 static_cast<std::string
>(control._fe_problem.getCurrentExecuteOnFlag());
537 response_json[
"waiting"] =
false;
539 return Response{200, response_json};
545 addServerAction<RequestMethod::GET>(
"waiting", waiting, options);
553 control.setKillRequested();
557 addServerAction<RequestMethod::GET>(
"kill", kill);
576 nlohmann::json response_json;
577 response_json[
"dt"] = control._fe_problem.dt();
578 return Response{200, response_json};
581 addServerAction<RequestMethod::GET>(
"get/dt", get_dt);
599 const auto name = convertJSON<std::string>(req.
getJSON(),
"name");
602 if (!control.hasPostprocessorByName(
name))
605 nlohmann::json response_json;
606 response_json[
"value"] = control.getPostprocessorValueByName(
name);
607 return Response{200, response_json};
613 addServerAction<RequestMethod::POST>(
"get/postprocessor", get_postprocessor, options);
632 const auto name = convertJSON<std::string>(req.
getJSON(),
"name");
638 if (!control.hasReporterValueByName(rname))
642 nlohmann::json response_json;
643 control.getReporterContextBaseByName(rname).store(response_json[
"value"]);
645 return Response{200, response_json};
651 addServerAction<RequestMethod::POST>(
"get/reporter", get_reporter, options);
665 nlohmann::json response_json;
666 response_json[
"time"] = control._fe_problem.time();
667 return Response{200, response_json};
670 addServerAction<RequestMethod::GET>(
"get/time", get_time);
693 const auto & json = req.
getJSON();
696 const auto type = convertJSON<std::string>(json,
"type");
698 if (!registered_type)
700 "' not registered for setting a controllable parameter");
703 const auto name = convertJSON<std::string>(json,
"name");
705 if (!control.hasControllableParameterByName(
name))
709 const auto value_it = json.find(
"value");
710 if (value_it == json.end())
715 std::unique_ptr<ControlledValueBase>
value;
718 value = registered_type->build(
name, *value_it);
720 catch (std::exception & e)
722 return ErrorResponse(
"While parsing 'value': " + std::string(e.what()));
725 std::lock_guard<std::mutex> lock(control._controlled_values_mutex);
726 control._controlled_values.emplace_back(std::move(
value));
735 addServerAction<RequestMethod::POST>(
"set/controllable", set_controllable, options);
752 const int64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(
753 std::chrono::steady_clock::now().time_since_epoch())
760 const std::string & message,
761 const std::chrono::time_point<std::chrono::steady_clock> & start)
const 763 const auto now = std::chrono::steady_clock::now();
764 const auto elapsed = std::chrono::duration<double>(now - start).count();
766 std::ostringstream
out;
767 out <<
"Client " << message <<
" after " << std::fixed << std::setprecision(2) << elapsed
775 const std::string & timeout_param_name,
776 const std::optional<std::string> & suffix )
const 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";
795 server_ptr->shutdown();
807 template void WebServerControl::addServerAction<WebServerControl::RequestMethod::GET>(
812 template void WebServerControl::addServerAction<WebServerControl::RequestMethod::POST>(
819 const nlohmann::json &
823 throw std::runtime_error(
"Request does not contain JSON when it should");
830 : _status_code(status_code), _json(json)
834 const nlohmann::json &
838 throw std::runtime_error(
"Response does not contain JSON when it should");
851 const unsigned int status_code )
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.
A MultiMooseEnum object to hold "execute_on" flags.
static InputParameters validParams()
Class constructor.
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 ¶m, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
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.
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.
const Parallel::Communicator & comm() const
Represents an error response to the client from the server.
WebServerControl(const InputParameters ¶meters)
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.
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.
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.
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've heard from the client.
virtual void terminateSolve()
Allow objects to request clean termination of the solve.
Options to be passed to addServerAction.
FEProblemBase & _fe_problem
Reference to the FEProblemBase for this object.
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.
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'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's combined type and name; useful in error handling.
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.
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.
bool isCurrentlyWaiting() const
Get whether or not the control is currently waiting.
Stores the information sent by the client on initialize.
static const RegisteredTypeBase & get(const std::string &type)
Get the registration for the given type, erroring if it isn'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...
Represents a response to the client from the server.
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.
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.