32#include <wx/filename.h>
44#include "model/nav_object_database.h"
55using namespace std::chrono_literals;
57static const char*
const kHttpAddr =
"http://0.0.0.0:8000";
58static const char*
const kHttpsAddr =
"http://0.0.0.0:8443";
60static const char*
const kHttpPortableAddr =
"http://0.0.0.0:8001";
61static const char*
const kHttpsPortableAddr =
"http://0.0.0.0:8444";
62static const char*
const kVersionReply = R
"--({ "version": "@version@" })--";
63static const char*
const kListRoutesReply =
64 R
"--( { "version": "@version@", "routes": "@routes@" })--";
67enum { ORS_START_OF_SESSION, ORS_CHUNK_N, ORS_CHUNK_LAST };
70 const enum class Cmd {
90 const std::string& src,
91 const std::string& gpx_data,
bool _force,
93 return RestIoEvtData(Cmd::Object, key, src, gpx_data,
"", _force,
99 const std::string& src) {
100 return {Cmd::Ping, key, src,
"",
""};
105 const std::string& src,
106 const std::string& guid) {
107 return {Cmd::CheckWrite, key, src,
"", guid};
112 const std::string& src) {
113 return {Cmd::ListRoutes, key, src,
"",
""};
116 static RestIoEvtData CreateActivateRouteData(
const std::string& key,
117 const std::string& src,
118 const std::string& guid) {
119 return {Cmd::ActivateRoute, key, src, guid,
""};
122 static RestIoEvtData CreatePluginMsgData(
const std::string& key,
123 const std::string& src,
124 const std::string&
id,
125 const std::string& msg) {
126 return {Cmd::PluginMsg, key, src, msg,
id};
129 static RestIoEvtData CreateReverseRouteData(
const std::string& key,
130 const std::string& src,
131 const std::string& guid) {
132 return {Cmd::ReverseRoute, key, src, guid,
""};
136 RestIoEvtData(Cmd c, std::string key, std::string src, std::string _payload,
137 std::string _id,
bool _force,
bool _activate);
138 RestIoEvtData(Cmd c, std::string key, std::string src, std::string _payload,
144static inline std::string HttpVarToString(
const struct mg_str& query,
147 struct mg_str mgs = mg_http_var(query, mg_str(var));
148 if (mgs.len && mgs.ptr)
string = std::string(mgs.ptr, mgs.len);
153 const std::shared_ptr<RestIoEvtData>& evt_data,
int id) {
155 evt->SetSharedPtr(evt_data);
156 parent->QueueEvent(evt);
157 if (!
dynamic_cast<wxApp*
>(wxAppConsole::GetInstance())) {
158 wxTheApp->ProcessPendingEvents();
162static void HandleRxObject(
struct mg_connection* c,
struct mg_http_message* hm,
164 int MID = ORS_CHUNK_N;
166 std::string api_key = HttpVarToString(hm->query,
"apikey");
167 std::string source = HttpVarToString(hm->query,
"source");
168 std::string force = HttpVarToString(hm->query,
"force");
169 std::string activate = HttpVarToString(hm->query,
"activate");
170 std::string xml_content;
172 xml_content = std::string(hm->chunk.ptr, hm->chunk.len);
174 MID = ORS_CHUNK_LAST;
176 mg_http_delete_chunk(c, hm);
179 if (!source.empty()) {
180 assert(parent &&
"Null parent pointer");
183 api_key, source, xml_content, !force.empty(), !activate.empty()));
184 PostEvent(parent, data_ptr, MID);
186 if (MID == ORS_CHUNK_LAST) {
187 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
191 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
192 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
196static void HandlePing(
struct mg_connection* c,
struct mg_http_message* hm,
198 std::string api_key = HttpVarToString(hm->query,
"apikey");
199 std::string source = HttpVarToString(hm->query,
"source");
200 if (!source.empty()) {
201 assert(parent &&
"Null parent pointer");
203 auto data_ptr = std::make_shared<RestIoEvtData>(
205 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
206 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
210 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
212 mg_http_reply(c, 200,
"",
"{\"result\": %d, \"version\": \"%s\"}\n",
216static void HandleWritable(
struct mg_connection* c,
struct mg_http_message* hm,
218 std::string apikey = HttpVarToString(hm->query,
"apikey");
219 std::string source = HttpVarToString(hm->query,
"source");
220 std::string guid = HttpVarToString(hm->query,
"guid");
221 if (!source.empty()) {
222 assert(parent &&
"Null parent pointer");
224 auto data_ptr = std::make_shared<RestIoEvtData>(
226 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
227 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
231 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
233 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
236static void HandleListRoutes(
struct mg_connection* c,
237 struct mg_http_message* hm,
RestServer* parent) {
238 std::string apikey = HttpVarToString(hm->query,
"apikey");
239 std::string source = HttpVarToString(hm->query,
"source");
240 if (!source.empty()) {
241 assert(parent &&
"Null parent pointer");
243 auto data_ptr = std::make_shared<RestIoEvtData>(
245 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
246 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
250 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
252 mg_http_reply(c, 200,
"", parent->
m_reply_body.c_str());
255static void HandleActivateRoute(
struct mg_connection* c,
256 struct mg_http_message* hm,
258 std::string apikey = HttpVarToString(hm->query,
"apikey");
259 std::string source = HttpVarToString(hm->query,
"source");
260 std::string guid = HttpVarToString(hm->query,
"guid");
261 if (!source.empty()) {
262 assert(parent &&
"Null parent pointer");
264 auto data_ptr = std::make_shared<RestIoEvtData>(
265 RestIoEvtData::CreateActivateRouteData(apikey, source, guid));
266 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
267 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
271 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
273 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
276static void HandlePluginMsg(
struct mg_connection* c,
struct mg_http_message* hm,
278 std::string apikey = HttpVarToString(hm->query,
"apikey");
279 std::string source = HttpVarToString(hm->query,
"source");
280 std::string
id = HttpVarToString(hm->query,
"id");
281 std::string message = HttpVarToString(hm->query,
"message");
282 int chunk_status = hm->chunk.len ? ORS_CHUNK_N : ORS_CHUNK_LAST;
284 if (hm->chunk.len) content = std::string(hm->chunk.ptr, hm->chunk.len);
285 mg_http_delete_chunk(c, hm);
286 if (!source.empty()) {
287 assert(parent &&
"Null parent pointer");
289 auto data_ptr = std::make_shared<RestIoEvtData>(
290 RestIoEvtData::CreatePluginMsgData(apikey, source,
id, content));
291 PostEvent(parent, data_ptr, chunk_status);
292 if (chunk_status == ORS_CHUNK_LAST) {
293 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
297 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
298 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n",
304static void HandleReverseRoute(
struct mg_connection* c,
305 struct mg_http_message* hm,
RestServer* parent) {
306 std::string apikey = HttpVarToString(hm->query,
"apikey");
307 std::string source = HttpVarToString(hm->query,
"source");
308 std::string guid = HttpVarToString(hm->query,
"guid");
309 if (!source.empty()) {
310 assert(parent &&
"Null parent pointer");
312 auto data_ptr = std::make_shared<RestIoEvtData>(
313 RestIoEvtData::CreateReverseRouteData(apikey, source, guid));
314 PostEvent(parent, data_ptr, ORS_CHUNK_LAST);
315 std::unique_lock<std::mutex> lock{parent->
ret_mutex};
319 if (!r) wxLogWarning(
"Timeout waiting for REST server condition");
321 mg_http_reply(c, 200,
"",
"{\"result\": %d}\n", parent->
GetReturnStatus());
326static void fn(
struct mg_connection* c,
int ev,
void* ev_data,
void* fn_data) {
327 auto parent =
static_cast<RestServer*
>(fn_data);
329 if (ev == MG_EV_ACCEPT ) {
330 struct mg_tls_opts opts = {0};
334 opts.ciphers =
nullptr;
335 mg_tls_init(c, &opts);
336 }
else if (ev == MG_EV_TLS_HS) {
337 PostEvent(parent,
nullptr, ORS_START_OF_SESSION);
338 }
else if (ev == MG_EV_HTTP_CHUNK) {
339 auto hm = (
struct mg_http_message*)ev_data;
340 if (mg_http_match_uri(hm,
"/api/ping")) {
341 HandlePing(c, hm, parent);
342 }
else if (mg_http_match_uri(hm,
"/api/rx_object")) {
343 HandleRxObject(c, hm, parent);
344 }
else if (mg_http_match_uri(hm,
"/api/writable")) {
345 HandleWritable(c, hm, parent);
346 }
else if (mg_http_match_uri(hm,
"/api/get-version")) {
347 std::string reply(kVersionReply);
349 mg_http_reply(c, 200,
"", reply.c_str());
350 }
else if (mg_http_match_uri(hm,
"/api/list-routes")) {
351 HandleListRoutes(c, hm, parent);
352 }
else if (mg_http_match_uri(hm,
"/api/activate-route")) {
353 HandleActivateRoute(c, hm, parent);
354 }
else if (mg_http_match_uri(hm,
"/api/reverse-route")) {
355 HandleReverseRoute(c, hm, parent);
356 }
else if (mg_http_match_uri(hm,
"/api/plugin-msg")) {
357 HandlePluginMsg(c, hm, parent);
359 mg_http_reply(c, 404,
"",
"url: not found\n");
367 case RestServerResult::NoError:
369 case RestServerResult::GenericError:
370 s = _(
"Server Generic Error");
371 case RestServerResult::ObjectRejected:
372 s = _(
"Peer rejected object");
373 case RestServerResult::DuplicateRejected:
374 s = _(
"Peer rejected duplicate object");
375 case RestServerResult::RouteInsertError:
376 s = _(
"Peer internal error (insert)");
377 case RestServerResult::NewPinRequested:
378 s = _(
"Peer requests new pincode");
379 case RestServerResult::ObjectParseError:
380 s = _(
"XML parse error");
381 case RestServerResult::Void:
382 s = _(
"Unspecified error");
384 return s.ToStdString();
390RestServer::IoThread::IoThread(
RestServer& parent, std::string ip)
391 : run_flag(-1), m_parent(parent), m_server_ip(std::move(ip)) {}
393void RestServer::IoThread::Run() {
395 struct mg_mgr mgr = {0};
396 mg_log_set(MG_LL_DEBUG);
400 MESSAGE_LOG <<
"Listening on " << m_server_ip <<
"\n";
401 mg_http_listen(&mgr, m_server_ip.c_str(), fn, &m_parent);
403 while (run_flag > 0) mg_mgr_poll(&mgr, 200);
406 m_parent.m_exit_sem.Post();
409void RestServer::IoThread::Stop() { run_flag = 0; }
411bool RestServer::IoThread::WaitUntilStopped() {
412 auto r = m_parent.m_exit_sem.WaitTimeout(10000);
413 if (r != wxSEMA_NO_ERROR) {
414 WARNING_LOG <<
"Semaphore error: " << r;
416 return r == wxSEMA_NO_ERROR;
419RestServer::Apikeys RestServer::Apikeys::Parse(
const std::string& s) {
422 for (
const auto& ip_key : ip_keys) {
424 if (words.size() != 2)
continue;
425 if (apikeys.find(words[0]) == apikeys.end()) {
426 apikeys[words[0]] = words[1];
432std::string RestServer::Apikeys::ToString()
const {
433 std::stringstream ss;
434 for (
const auto& it : *this) ss << it.first <<
":" << it.second <<
";";
440 std::lock_guard<std::mutex> lock{
ret_mutex};
441 return_status = result;
448 m_endpoint(portable ? kHttpsPortableAddr : kHttpsAddr),
449 m_dlg_ctx(std::move(ctx)),
450 m_route_ctx(std::move(route_ctx)),
452 m_pin_dialog(nullptr),
454 m_io_thread(*this, m_endpoint),
457 Bind(REST_IO_EVT, &RestServer::HandleServerMessage,
this);
463 m_certificate_directory = certificate_location.string();
464 m_cert_file = (certificate_location /
"cert.pem").
string();
465 m_key_file = (certificate_location /
"key.pem").
string();
469 if (!m_std_thread.joinable()) {
470 m_std_thread = std::thread([&]() { m_io_thread.Run(); });
476 wxLogDebug(
"Stopping REST service");
478 if (m_std_thread.joinable()) {
479 wxLogDebug(
"Stopping io thread");
481 m_io_thread.WaitUntilStopped();
486bool RestServer::LoadConfig() {
487 TheBaseConfig()->SetPath(
"/Settings/RestServer");
489 TheBaseConfig()->Read(
"ServerKeys", &key_string);
490 m_key_map = Apikeys::Parse(key_string.ToStdString());
491 TheBaseConfig()->Read(
"ServerOverwriteDuplicates", &m_overwrite,
false);
495bool RestServer::SaveConfig() {
496 TheBaseConfig()->SetPath(
"/Settings/RestServer");
497 TheBaseConfig()->Write(
"ServerKeys", wxString(m_key_map.ToString()));
498 TheBaseConfig()->Write(
"ServerOverwriteDuplicates", m_overwrite);
499 TheBaseConfig()->Flush();
505 if (m_key_map.find(evt_data.
source) != m_key_map.end()) {
506 if (m_key_map[evt_data.
source] == evt_data.
api_key)
return true;
510 std::string new_api_key = m_pincode.
Hash();
511 if (evt_data.
api_key.size() < 10)
514 m_key_map[evt_data.
source] = new_api_key;
517 std::stringstream ss;
518 ss << evt_data.
source <<
" " << _(
"wants to send you new data.") <<
"\n"
519 << _(
"Please enter the following PIN number on ") << evt_data.
source <<
" "
520 << _(
"to pair with this device") <<
"\n";
521 if (m_pin_dialog) m_pin_dialog->Destroy();
527void RestServer::HandleServerMessage(
ObservedEvt& event) {
528 auto evt_data = obs::UnpackEvtPointer<RestIoEvtData>(event);
530 switch (event.GetId()) {
531 case ORS_START_OF_SESSION:
533 m_upload_path = wxFileName::CreateTempFileName(
"ocpn_tul").ToStdString();
535 m_ul_stream.open(m_upload_path.c_str(), std::ios::out | std::ios::trunc);
536 if (!m_ul_stream.is_open()) {
537 wxLogMessage(
"REST_server: Cannot open %s for write", m_upload_path);
538 m_upload_path.clear();
544 if (!m_upload_path.empty() && m_ul_stream.is_open()) {
545 m_ul_stream.write(evt_data->
payload.c_str(), evt_data->
payload.size());
550 if (m_pin_dialog) wxQueueEvent(m_pin_dialog,
new wxCloseEvent);
551 if (!m_upload_path.empty() && m_ul_stream.is_open()) m_ul_stream.close();
555 if (!CheckApiKey(*evt_data)) {
560 switch (evt_data->cmd) {
561 case RestIoEvtData::Cmd::Ping:
564 case RestIoEvtData::Cmd::CheckWrite: {
566 auto dup = m_route_ctx.find_route_by_guid(guid);
567 if (!dup || evt_data->
force || m_overwrite) {
574 case RestIoEvtData::Cmd::Object: {
577 if (result.status == pugi::status_ok) {
578 m_upload_path.clear();
582 object =
object.next_sibling()) {
583 if (!strcmp(
object.name(),
"rte")) {
584 HandleRoute(
object, *evt_data);
585 }
else if (!strcmp(
object.name(),
"trk")) {
586 HandleTrack(
object, *evt_data);
587 }
else if (!strcmp(
object.name(),
"wpt")) {
588 HandleWaypoint(
object, *evt_data);
596 case RestIoEvtData::Cmd::ListRoutes: {
597 std::stringstream ss;
600 if (ss.str() !=
"[") ss <<
", ";
601 ss <<
"[ \"" << r->GetGUID() <<
"\", \"" << r->GetName() <<
"\"]";
604 std::string reply(kListRoutesReply);
610 case RestIoEvtData::Cmd::ActivateRoute: {
615 case RestIoEvtData::Cmd::ReverseRoute: {
624 case RestIoEvtData::Cmd::PluginMsg: {
625 std::ifstream f(m_upload_path);
626 m_upload_path.clear();
627 std::stringstream ss;
629 auto msg = std::make_shared<PluginMsg>(
PluginMsg(evt_data->
id, ss.str()));
630 NavMsgBus::GetInstance().
Notify(msg);
638 Route* route = GPXLoadRoute1(
object,
false,
false,
false, 0,
true);
641 bool overwrite_one =
false;
642 Route* duplicate = m_route_ctx.find_route_by_guid(route->GetGUID());
644 if (!m_overwrite && !evt_data.
force) {
646 _(
"The received route already exists on this system.\nReplace?"),
647 _(
"Always replace objects?"));
648 if (result.status != ID_STG_OK) {
652 m_overwrite = result.check1_value;
653 overwrite_one =
true;
659 if (m_overwrite || overwrite_one || evt_data.
force) {
661 m_route_ctx.delete_route(duplicate);
665 if (InsertRouteA(route, &pSet)) {
666 NavObj_dB::GetInstance().InsertRoute(route);
680 Track* track = GPXLoadTrack1(
object,
true,
false,
false, 0);
683 bool overwrite_one =
false;
685 Track* duplicate = m_route_ctx.find_track_by_guid(track->m_GUID);
687 if (!m_overwrite && !evt_data.
force) {
689 _(
"The received track already exists on this system.\nReplace?"),
690 _(
"Always replace objects?"));
692 if (result.status != ID_STG_OK) {
696 m_overwrite = result.check1_value;
697 overwrite_one =
true;
703 if (m_overwrite || overwrite_one || evt_data.
force) {
704 NavObj_dB::GetInstance().DeleteTrack(duplicate);
705 m_route_ctx.delete_track(duplicate);
708 if (InsertTrack(track,
false)) {
709 NavObj_dB::GetInstance().InsertTrack(track);
713 m_dlg_ctx.top_level_refresh();
720 GPXLoadWaypoint1(
object,
"circle",
"",
false,
false,
false, 0);
724 bool overwrite_one =
false;
728 if (!m_overwrite && !evt_data.
force) {
730 _(
"The received waypoint already exists on this system.\nReplace?"),
731 _(
"Always replace objects?"));
732 if (result.status != ID_STG_OK) {
736 m_overwrite = result.check1_value;
737 overwrite_one =
true;
743 if (m_overwrite || overwrite_one || evt_data.
force) {
744 NavObj_dB::GetInstance().DeleteRoutePoint(duplicate);
745 m_route_ctx.delete_waypoint(duplicate);
747 if (InsertWpt(rp, m_overwrite || overwrite_one || evt_data.
force)) {
749 NavObj_dB::GetInstance().InsertRoutePoint(rp);
752 m_dlg_ctx.top_level_refresh();
756RestIoEvtData::RestIoEvtData(Cmd c, std::string key, std::string src,
757 std::string _payload, std::string _id,
bool _force,
760 api_key(std::move(key)),
761 source(std::move(src)),
765 payload(std::move(_payload)) {}
768 : run_pincode_dlg([](const std::string&, const std::string&) -> wxDialog* {
771 update_route_mgr([]() {}),
772 run_accept_object_dlg([](
const wxString&,
const wxString&) {
775 top_level_refresh([]() {}) {}
778 : find_route_by_guid(
779 [](const wxString&) {
return static_cast<Route*
>(
nullptr); }),
781 [](
const wxString&) {
return static_cast<Track*
>(
nullptr); }),
783 [](
const wxString&) {
return static_cast<RoutePoint*
>(
nullptr); }),
784 delete_route([](
Route*) ->
void {}),
785 delete_track([](
Track*) ->
void {}),
786 delete_waypoint([](
RoutePoint*) ->
void {}) {}
obs::EventVar reverse_route
Notified with a string GUID when user wants to reverse a route.
obs::EventVar activate_route
Notified with a string GUID when user wants to activate a route.
obs::EventVar on_routes_update
Notified when list of routes is updated (no data in event)
void Notify(std::shared_ptr< const NavMsg > message)
Accept message received by driver, make it available for upper layers.
Custom event class for OpenCPN's notification system.
A random generated int value with accessors for string and hashcode.
static Pincode Create()
Create a new pincode based on a random value.
std::string Hash() const
Return a hashvalue string.
std::string ToString() const
Return value as string.
std::string CompatHash()
Return a hashvalue as computed on 5.8 hosts.
A plugin to plugin json message over the REST interface.
Callbacks for handling dialogs and RouteManager updates.
RestServerDlgCtx()
All dummy stubs constructor.
std::function< AcceptObjectDlgResult(const wxString &msg, const wxString &check1msg)> run_accept_object_dlg
Run the "Accept Object" dialog, returns value from ShowModal().
std::function< wxDialog *(const std::string &msg, const std::string &text1)> run_pincode_dlg
Run the "Server wants a pincode" dialog.
AbstractRestServer implementation and interface to underlying IO thread.
std::string m_cert_file
Semi-static storage used by IoThread C code.
std::string m_reply_body
IoThread interface: body of return message, if any.
RestServerResult GetReturnStatus()
IoThread interface.
std::condition_variable return_status_cv
IoThread interface: Guards return_status.
std::mutex ret_mutex
IoThread interface: Guards return_status.
void StopServer() override
Stop server thread, blocks until completed.
void UpdateRouteMgr() const
IoThread interface.
void UpdateReturnStatus(RestServerResult r)
IoThread interface.
std::string m_key_file
Semi-static storage used by IoThread C code.
bool StartServer(const fs::path &certificate_location) override
Start the server thread.
Callbacks for handling routes and tracks.
RouteCtx()
Dummy stubs constructor.
Represents a waypoint or mark within the navigation system.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
Represents a navigational route in the navigation system.
Represents a track, which is a series of connected track points.
void Notify() override
Notify all listeners, no data supplied.
Raw messages layer, supports sending and recieving navmsg messages.
Global variables stored in configuration file.
wxCommandEvt subclass which can carry also a shared_ptr<void>
Misc GUI event vars, a singleton.
Enhanced logging interface on top of wx/log.h.
bool replace(std::string &str, const std::string &from, const std::string &to)
Perform in place substitution in str, replacing "from" with "to".
std::vector< std::string > split(const char *token_string, const std::string &delimiter)
Return vector of items in s separated by delimiter.
MySQL based storage for routes, tracks, etc.
Miscellaneous utilities, many of which string related.
Peer communications pincode abstraction.
std::string RestResultText(RestServerResult result)
RestServerResult string representation.
wxDEFINE_EVENT(REST_IO_EVT, ObservedEvt)
Event from IO thread to main.
RestServerResult
Return codes from HandleServerMessage and eventually in the http response.
RouteList * pRouteList
Global instance.
Returned status from RunAcceptObjectDlg.
static RestIoEvtData CreateListRoutesData(const std::string &key, const std::string &src)
Create a Cmd::ListRoutes instance.
static RestIoEvtData CreateChkWriteData(const std::string &key, const std::string &src, const std::string &guid)
Create a Cmd::CheckWrite instance.
const bool activate
rest API parameter activate
static RestIoEvtData CreateCmdData(const std::string &key, const std::string &src, const std::string &gpx_data, bool _force, bool _activate)
Create a Cmd::Object instance.
static RestIoEvtData CreatePingData(const std::string &key, const std::string &src)
Create a Cmd::Ping instance:
const std::string payload
GPX data for Cmd::Object, Guid for Cmd::CheckWrite, Activate, Reverse.
const std::string source
Rest API parameter source.
const bool force
rest API parameter force
const std::string api_key
Rest API parameter apikey.
const std::string id
rest API parameter id for PluginMsg.