MotionLib  1.0.0
SamBuCa motion library
Server.cpp
1 /*
2 ** Copyright (C) 2022 CERN
3 **
4 ** This software is provided 'as-is', without any express or implied
5 ** warranty. In no event will the authors be held liable for any damages
6 ** arising from the use of this software.
7 **
8 ** Permission is granted to anyone to use this software for any purpose,
9 ** including commercial applications, and to alter it and redistribute it
10 ** freely, subject to the following restrictions:
11 **
12 ** 1. The origin of this software must not be misrepresented; you must not
13 ** claim that you wrote the original software. If you use this software
14 ** in a product, an acknowledgment in the product documentation would be
15 ** appreciated but is not required.
16 ** 2. Altered source versions must be plainly marked as such, and must not be
17 ** misrepresented as being the original software.
18 ** 3. This notice may not be removed or altered from any source distribution.
19 **
20 ** Created on: 2022-07-11T13:24:55
21 ** Author: Sylvain Fargier <sylvain.fargier@cern.ch>
22 */
23 
24 #include "Server.hpp"
25 
26 #include <cstdlib>
27 #include <string>
28 
29 #include <oatpp/web/server/handler/ErrorHandler.hpp>
30 #include <oatpp/web/server/interceptor/RequestInterceptor.hpp>
31 #include <unistd.h>
32 
33 #define SWAGGER_ROOT_PATH "/api-docs"
34 #define SWAGGER_UI_PATH "/ui"
35 
36 #include <ccut/Thread.hpp>
37 #include <oatpp-swagger/AsyncController.hpp>
38 #include <oatpp-swagger/Model.hpp>
39 #include <oatpp-swagger/Resources.hpp>
40 #include <oatpp/core/macro/component.hpp>
41 #include <oatpp/network/Server.hpp>
42 #include <oatpp/network/tcp/server/ConnectionProvider.hpp>
43 #include <oatpp/parser/json/mapping/ObjectMapper.hpp>
44 #include <oatpp/web/server/AsyncHttpConnectionHandler.hpp>
45 #include <oatpp/web/server/HttpConnectionHandler.hpp>
46 
47 #include "../MotionController.hpp"
48 #include "ApiController.hpp"
49 #include "AsyncAliasController.hpp"
50 #include "AsyncApiController.hpp"
51 #include "AsyncAxisController.hpp"
52 #include "AsyncAxisPositionMonitorController.hpp"
53 #include "AsyncConfigController.hpp"
54 #include "AsyncGpioController.hpp"
55 #include "AsyncPositionController.hpp"
56 #include "AsyncTriggerController.hpp"
57 #include "AsyncTypesController.hpp"
58 #include "Environment.hpp"
59 #include "SmcController.hpp"
60 
61 /* swagger generated resources */
62 #include "favicon-16x16.png.cpp"
63 #include "favicon-32x32.png.cpp"
64 #include "index.html.cpp"
65 #include "oauth2-redirect.html.cpp"
66 #include "swagger-ui-bundle.js.cpp"
67 #include "swagger-ui-es-bundle-core.js.cpp"
68 #include "swagger-ui-es-bundle.js.cpp"
69 #include "swagger-ui-standalone-preset.js.cpp"
70 #include "swagger-ui.css.cpp"
71 #include "swagger-ui.js.cpp"
72 
73 #define ENV_PORT "DEBUG_PORT"
74 #define ENV_ADDR "DEBUG_ADDR"
75 // used by swagger
76 #define ENV_HOSTNAME "DEBUG_HOSTNAME"
77 
78 namespace http = oatpp::web::protocol::http;
79 
80 namespace smc {
81 namespace debug {
82 
84  public oatpp::web::server::interceptor::RequestInterceptor
85 {
86 public:
87  std::shared_ptr<OutgoingResponse> intercept(
88  const std::shared_ptr<IncomingRequest> &request) override
89  {
90  const auto &line = request->getStartingLine();
91  OATPP_LOGV("req", "%s %s", line.method.std_str().c_str(),
92  line.path.std_str().c_str());
93 
94  if (line.method == "OPTIONS")
95  return OutgoingResponse::createShared(http::Status::CODE_204,
96  nullptr);
97 
98  return nullptr;
99  }
100 };
101 
103  public oatpp::web::server::interceptor::ResponseInterceptor
104 {
105 public:
106  std::shared_ptr<OutgoingResponse> intercept(
107  const std::shared_ptr<IncomingRequest> &request,
108  const std::shared_ptr<OutgoingResponse> &response) override
109  {
110  response->putOrReplaceHeader(http::Header::SERVER, "CERN/MRO");
111 
112  if (request)
113  {
114  const auto &line = request->getStartingLine();
115  const int32_t code = response->getStatus().code;
116  if (code >= 400)
117  {
118  OATPP_LOGE("req", "%s %s -> %i", line.method.std_str().c_str(),
119  line.path.std_str().c_str(), code);
120  }
121  else
122  {
123  OATPP_LOGV("req", "%s %s -> %i", line.method.std_str().c_str(),
124  line.path.std_str().c_str(), code);
125  }
126 
127  if (line.method == "OPTIONS")
128  {
129  response->putHeaderIfNotExists(http::Header::CORS_METHODS,
130  "GET,PUT,POST,DELETE,OPTIONS");
131  response->putHeaderIfNotExists(http::Header::CORS_MAX_AGE,
132  "172800");
133  }
134  }
135 
136  response->putHeaderIfNotExists(http::Header::CORS_ORIGIN, "*");
137  response->putHeaderIfNotExists(http::Header::CORS_HEADERS, "*");
138  return response;
139  }
140 };
141 
142 class ErrorHandler : public oatpp::web::server::handler::DefaultErrorHandler
143 {
144 public:
145  using oatpp::web::server::handler::DefaultErrorHandler::handleError;
146 
147  std::shared_ptr<http::outgoing::Response> handleError(
148  const http::Status &status,
149  const oatpp::String &message,
150  const Headers &headers) override
151  {
152  OATPP_LOGE("req", "error: %s", message.getValue("").c_str());
153  return DefaultErrorHandler::handleError(status, message, headers);
154  }
155 };
156 
157 class ServerData : public ccut::Thread
158 {
159 public:
160  static const oatpp::network::Address &getAddress()
161  {
162  const char *addr = getenv(ENV_ADDR);
163  const char *port_str = getenv(ENV_PORT);
164  uint16_t port = (port_str) ? std::stoul(std::string(port_str)) : 8080;
165 
166  static const oatpp::network::Address address = {
167  (addr ? addr : "0.0.0.0"), port, oatpp::network::Address::IP_4};
168  return address;
169  }
170 
175  std::shared_ptr<oatpp::network::ServerConnectionProvider>,
176  serverConnectionProvider)
177  ([] {
178  return oatpp::network::tcp::server::ConnectionProvider::createShared(
179  ServerData::getAddress());
180  }());
181 
185  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>,
186  httpRouter)
187  ([] { return oatpp::web::server::HttpRouter::createShared(); }());
188 
193  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>,
194  serverConnectionHandler)
195  ([] {
196  OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>,
197  router); // get Router component
198  auto ret = oatpp::web::server::AsyncHttpConnectionHandler::createShared(
199  router);
200  ret->addRequestInterceptor(std::make_shared<RequestInterceptor>());
201  ret->addResponseInterceptor(std::make_shared<ResponseInterceptor>());
202  ret->setErrorHandler(std::make_shared<ErrorHandler>());
203  return ret;
204  }());
205 
210  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>,
211  apiObjectMapper)
212  ([] {
213  return oatpp::parser::json::mapping::ObjectMapper::createShared();
214  }());
215 
216  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::swagger::DocumentInfo>,
217  swaggerDocumentInfo)
218  ([] {
219  oatpp::swagger::DocumentInfo::Builder builder;
220 
221  builder.setTitle("Motion-Library")
222  .setDescription("Motion Library debug interface")
223  .setVersion("1.0");
224 
225  const std::string port = std::to_string(ServerData::getAddress().port);
226  {
227  const char *hostname = getenv(ENV_HOSTNAME);
228  if (hostname)
229  {
230  builder.addServer(
231  std::string("http://") + hostname + std::string(":") + port,
232  "configured server hostname");
233  }
234  }
235 
236  {
237  char hostname[HOST_NAME_MAX + 1];
238  if (gethostname(hostname, HOST_NAME_MAX) == 0)
239  {
240  builder.addServer(
241  std::string("http://") + hostname + std::string(":") + port,
242  "detected server hostname");
243  }
244  }
245  builder.addServer("http://localhost:" + port, "server on localhost");
246  return builder.build();
247  }());
248 
252  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::swagger::Resources>,
253  swaggerResources)
254  ([] {
255  auto res = std::make_shared<oatpp::swagger::Resources>("/tmp");
256  res->cacheResource("favicon-16x16.png", favicon_16x16_png);
257  res->cacheResource("favicon-32x32.png", favicon_32x32_png);
258  res->cacheResource("index.html", index_html);
259  res->cacheResource("oauth2-redirect.html", oauth2_redirect_html);
260  res->cacheResource("swagger-ui-bundle.js", swagger_ui_bundle_js);
261  res->cacheResource("swagger-ui-bundle.js.map", "");
262  res->cacheResource("swagger-ui-es-bundle-core.js",
263  swagger_ui_es_bundle_core_js);
264  res->cacheResource("swagger-ui-es-bundle-core.js.map", "");
265  res->cacheResource("swagger-ui-es-bundle.js", swagger_ui_es_bundle_js);
266  res->cacheResource("swagger-ui-es-bundle.js.map", "");
267  res->cacheResource("swagger-ui-standalone-preset.js",
268  swagger_ui_standalone_preset_js);
269  res->cacheResource("swagger-ui-standalone-preset.js.map", "");
270  res->cacheResource("swagger-ui.css", swagger_ui_css);
271  res->cacheResource("swagger-ui.css.map", "");
272  res->cacheResource("swagger-ui.js", swagger_ui_js);
273  res->cacheResource("swagger-ui.js.map", "");
274 
275  return res;
276  }());
277 
278  void stop()
279  {
280  {
281  std::unique_lock<std::mutex> lock(m_mutex);
282  m_started.store(false);
283  m_waked = true;
284  m_cond.notify_all();
285  }
286 
287  server->stop();
288 
289  OATPP_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>,
290  serverConnectionHandler);
291  serverConnectionHandler->stop();
292 
293  Thread::stop();
294  }
295 
296  virtual void thread_func() override;
297 
298  std::list<SmcController::Shared> smcControllers;
299  std::shared_ptr<oatpp::swagger::AsyncController> swaggerController;
300  std::unique_ptr<oatpp::network::Server> server;
301 };
302 
303 void ServerData::thread_func()
304 {
305  {
306  OATPP_COMPONENT(
307  std::shared_ptr<oatpp::network::ServerConnectionProvider>,
308  connectionProvider);
309  OATPP_LOGI("motion-lib", "Server running on port %s",
310  connectionProvider->getProperty("port").getData());
311  }
312  server->run();
313 }
314 
315 Server::Server()
316 {
317  init();
318 }
319 
320 Server::~Server()
321 {
322  stop();
323 }
324 
325 void Server::connect(const MotionController &controller)
326 {
327  for (auto c : m_data->smcControllers)
328  c->connect(controller.getPlatformFactory(), controller.getDeviceStore());
329 }
330 
331 void Server::stop()
332 {
333  if (m_data)
334  m_data->stop();
335  m_data.reset();
336  m_env.reset(); /* env must be released last */
337 }
338 
339 void Server::start()
340 {
341  init();
342  if (m_data)
343  m_data->start();
344 }
345 
346 void Server::run()
347 {
348  init();
349  if (m_data)
350  m_data->run();
351 }
352 
353 void Server::init()
354 {
355  if (m_data)
356  return;
357 
358  if (!m_env)
359  m_env = Environment::instance();
360  m_data.reset(new ServerData);
361 
362  OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router);
363 
364  m_data->smcControllers.emplace_back(new AsyncApiController);
365  m_data->smcControllers.emplace_back(new AsyncAxisController);
366  m_data->smcControllers.emplace_back(new AsyncGpioController);
367  m_data->smcControllers.emplace_back(new AsyncAliasController);
368  m_data->smcControllers.emplace_back(new AsyncTypesController);
369  m_data->smcControllers.emplace_back(new AsyncConfigController);
370  m_data->smcControllers.emplace_back(new AsyncPositionController);
371  m_data->smcControllers.emplace_back(new AsyncAxisPositionMonitorController);
372  m_data->smcControllers.emplace_back(new AsyncTriggerController);
373 
374  std::shared_ptr<oatpp::web::server::handler::ErrorHandler> errHdlr{
375  new ErrorHandler};
376  oatpp::web::server::api::Endpoints ep;
377  for (auto controller : m_data->smcControllers)
378  {
379  controller->setErrorHandler(errHdlr);
380  router->addController(controller);
381  ep.append(controller->getEndpoints());
382  }
383 
384  m_data->swaggerController = oatpp::swagger::AsyncController::createShared(
385  ep);
386  router->addController(m_data->swaggerController);
387 
388  /* Get connection handler component */
389  OATPP_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>,
390  connectionHandler);
391 
392  /* Get connection provider component */
393  OATPP_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>,
394  connectionProvider);
395 
396  /* Create server which takes provided TCP connections and passes them to
397  * HTTP connection handler */
398  m_data->server.reset(
399  new oatpp::network::Server(connectionProvider, connectionHandler));
400 }
401 
402 } // namespace debug
403 } // namespace smc
std::shared_ptr< internal::PlatformFactory > getPlatformFactory() const
for internal use and debugging purpose
std::shared_ptr< internal::DeviceStore > getDeviceStore() const
for internal use and debugging purpose
OATPP_CREATE_COMPONENT(std::shared_ptr< oatpp::network::ConnectionHandler >, serverConnectionHandler)([]
Create ConnectionHandler component which uses Router component to route requests.
Definition: Server.cpp:193
OATPP_CREATE_COMPONENT(std::shared_ptr< oatpp::data::mapping::ObjectMapper >, apiObjectMapper)([]
Create ObjectMapper component to serialize/deserialize DTOs in Contoller's API.
Definition: Server.cpp:210
OATPP_CREATE_COMPONENT(std::shared_ptr< oatpp::swagger::Resources >, swaggerResources)([]
Swagger-Ui Resources (<oatpp-examples>/lib/oatpp-swagger/res)
Definition: Server.cpp:252
OATPP_CREATE_COMPONENT(std::shared_ptr< oatpp::web::server::HttpRouter >, httpRouter)([]
Create Router component.
Definition: Server.cpp:185
OATPP_CREATE_COMPONENT(std::shared_ptr< oatpp::network::ServerConnectionProvider >, serverConnectionProvider)([]
Create ConnectionProvider component which listens on the port.
Definition: Server.cpp:174
void connect(const MotionController &controller)
(re)connect the debug server
Definition: Server.cpp:325
const std::string & to_string(Axis::State state)
convert State to string
Definition: Axis.cpp:78
main motion-lib namespace
Definition: Client.cpp:30