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