Adds immediate migration results

This commit is contained in:
torsten committed 2026-08-18 22:12:24 +02:00
1 parent ba4121728b
commit 6a2a0667ab
2395 files changed
+357322 -2524

No files matched your search

@@ -0,0 +1,84 @@
/*
* (c) Copyright, Real-Time Innovations, 2024.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef CIVETWEBSOCKET_CONNECTION_H
#define CIVETWEBSOCKET_CONNECTION_H
#include <civetweb.h>
#include "WebSocketConnection.hpp"
namespace rti { namespace apputils { namespace websocket { namespace civetweb {
class CivetWebSocketConnection : public WebSocketConnection {
public:
explicit CivetWebSocketConnection(struct mg_connection *conn) : conn_(conn)
{
}
void send(const StringBuffer& msg)
{
struct mg_context *context = mg_get_context(conn_);
mg_lock_context(context);
mg_websocket_write(
conn_,
MG_WEBSOCKET_OPCODE_TEXT,
msg.c_str(),
msg.length());
mg_unlock_context(context);
}
void send_pong(const StringBuffer& msg)
{
mg_context *context = mg_get_context(conn_);
mg_lock_context(context);
mg_websocket_write(
conn_,
MG_WEBSOCKET_OPCODE_PONG,
msg.c_str(),
msg.length());
mg_unlock_context(context);
}
bool operator==(WebSocketConnection* conn)
{
return conn_ == static_cast<CivetWebSocketConnection *>(conn)->conn();
}
const struct mg_connection* conn() const
{
return conn_;
}
void close()
{
struct mg_context *context = mg_get_context(conn_);
mg_lock_context(context);
mg_close_connection(conn_);
mg_unlock_context(context);
}
private:
mg_connection* conn_;
};
}}}} // namespace rti::apputils::websocket::civetweb
#endif // CIVETWEBSOCKET_CONNECTION_H
@@ -0,0 +1,292 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_SERVER_CONNECTION_HPP
#define HTTP_SERVER_CONNECTION_HPP
#include <CivetServer.h>
#include <civetweb.h>
#include <nlohmann/json.hpp>
#include "rti/apputils/http/common/HttpUtils.hpp"
#include "rti/apputils/http/common/Response.hpp"
#include "rti/apputils/http/log/Log.hpp"
#include "log.peer.h"
#include "Request.hpp"
namespace rti { namespace apputils { namespace http { namespace server {
/**
* @brief This class implements the interfaces defined by rest::Connection to
* deal with CivetWeb's native connections.
*/
class Connection {
public:
/**
* @brief Constructor of the Connection class. It saves a reference to
* CivetWeb's native connection. It also constructs a Request from
* the connection passed by parameter.
*
* @param conn CivetWeb's connection that is wrapped in this class.
*/
explicit Connection(mg_connection *conn) : conn_(conn), request_(conn_)
{ }
/**
* Destructor of the Connection class.
*/
~Connection()
{ }
/**
* @brief This method returns the HTTP request.
*
* @return The HTTP Request.
*/
const Request& request() const
{
return request_;
}
/**
* @brief This method returns the CivetWeb's native connection.
*
* @return The CivetWeb's native connection.
*/
mg_connection *connection() const
{
return conn_;
}
/**
* @brief This method sends an "Error 501: Not Implemented" response.
*/
void send_not_implemented(std::string const& message = std::string())
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code =
static_cast<unsigned short>(StatusCode::not_implemented);
response.http_reason_phrase =
status_code_to_string(StatusCode::not_implemented);
response.http_content = message;
this->send_http_response(response);
}
/**
* @brief This method sends an "OK 204: No Content" response.
*/
void send_http_ok_no_content()
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code =
static_cast<unsigned short>(StatusCode::no_content);
response.http_reason_phrase =
status_code_to_string(StatusCode::no_content);
this->send_http_response(response);
}
/**
* @brief This method sends an "Error 400: Bad Request" response.
*/
void send_http_bad_request(std::string const& message = std::string())
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code =
static_cast<unsigned short>(StatusCode::bad_request);
response.http_reason_phrase =
status_code_to_string(StatusCode::bad_request);
response.http_content = message;
this->send_http_response(response);
}
/**
* @brief This method sends an "Error 404: Not Found" response.
*/
void send_http_not_found(std::string const& message = std::string())
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code =
static_cast<unsigned short>(StatusCode::not_found);
response.http_reason_phrase =
status_code_to_string(StatusCode::not_found);
response.http_content = message;
this->send_http_response(response);
}
/**
* @brief This method sends an "Error 405: Method Not Allowed" response.
*/
void send_http_method_not_allowed(
std::string const& message = std::string())
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code =
static_cast<unsigned short>(StatusCode::not_allowed);
response.http_reason_phrase =
status_code_to_string(StatusCode::not_allowed);
response.http_content = message;
this->send_http_response(response);
}
/**
* @brief This method sends an "OK 202: Accepted" response.
*/
void send_http_accepted(std::string const& message = std::string())
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code =
static_cast<unsigned short>(StatusCode::accepted);
response.http_reason_phrase =
status_code_to_string(StatusCode::accepted);
response.http_content = message;
this->send_http_response(response);
}
/**
* @brief This method sends an "OK 200: OK" response.
*/
void send_http_ok(std::string const& message = std::string())
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code = static_cast<unsigned short>(StatusCode::ok);
response.http_reason_phrase = status_code_to_string(StatusCode::ok);
response.http_content = message;
this->send_http_response(response);
}
/**
* @brief This operation constructs an HTTP response given its definition in
* Response format. Once the Response is constructed, is sent via CivetWeb's
* native APIs.
*/
void send_http_response(const Response& rpns)
{
// Constructs headers and body of the HTTP response.
auto mg_result = mg_printf(
conn_,
"HTTP/%s %u %s\r\n"
"Content-Type: %s\r\n"
"Cache-Control: %s\r\n"
"Content-Length: %lu\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Date: %s\r\n\r\n"
"%s",
HTTP_VERSION,
rpns.http_status_code,
rpns.http_reason_phrase.c_str(),
rpns.http_content_type.c_str(),
HTTP_CACHE_CONTROL_NO_CACHE,
rpns.http_content.length(),
HttpSettings::get_current_date().c_str(),
rpns.http_content.c_str());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result >= 0,
msg_server_error_printf);
APPUTILSLogMessage_log(
RTI_LOG_BIT_LOCAL,
&APPUTILS_LOG_HTTP_SENT_RESPONSE_us,
rpns.http_status_code,
rpns.http_reason_phrase.c_str());
}
/**
* @brief Prepares and send the body of an HTTP response given an exception.
*
* @param [in] e Exception to be formatted and sent.
*/
void send_error_exception_response(const HttpException& e)
{
auto response = Response();
response.http_content_type = HTTP_JSON_CONTENT_TYPE;
response.http_status_code = static_cast<unsigned short>(e.error_code());
response.http_reason_phrase = status_code_to_string(e.error_code());
nlohmann::json http_content_json;
http_content_json["code"] = status_code_to_string(e.error_code());
http_content_json["message"] = e.what();
response.http_content = http_content_json.dump();
this->send_http_response(response);
}
/**
* @brief Read the content of an HTTP request.
*
* @param chunk_size size of the chunk to read.
* @return std::string with the read content.
*/
std::string read_content(std::size_t chunk_size = 256)
{
const auto request_info = mg_get_request_info(conn_);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
request_info != nullptr,
msg_server_error_get_request_info);
if (request_info->content_length >= 0) {
return read_content_with_content_length(
conn_,
static_cast<std::size_t>(request_info->content_length));
}
return read_content_without_content_length(conn_, chunk_size);
}
/**
* @brief Check that content type is the expected one.
*
* @param expected_content_type
* @return true if the content type is the expected one. otherwise
* false.
*/
bool check_content_type(const std::string& expected_content_type)
{
auto ret = true;
// Check if Content-Type is application/json
if (this->request().get_header(
rti::apputils::http::HTTP_CONTENT_TYPE_HEADER)
!= expected_content_type) {
ret = false;
}
return ret;
}
private:
mg_connection *conn_;
Request request_;
};
}}}} // namespace rti::apputils::http::server
#endif // HTTP_SERVER_CONNECTION_HPP
@@ -0,0 +1,232 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_SERVER_REQUEST_HPP
#define HTTP_SERVER_REQUEST_HPP
#include <string>
#include <cstring>
#include <CivetServer.h>
#include <civetweb.h>
#include "rti/apputils/StringBuffer.hpp"
#include "rti/apputils/http/common/Method.hpp"
#include "rti/apputils/http/common/HttpException.hpp"
namespace rti { namespace apputils { namespace http { namespace server {
/**
* @brief This class implements the Request to handle Requests, that is
* requests provided by the CivetWeb server.
*/
class Request {
public:
/**
* @brief Constructor of the Request. Retrieves information from the
* mg_request_info provided by CivetWeb's connection.
*
* @param conn Civetweb's connection.
*/
explicit Request(mg_connection *conn) : conn_(conn)
{ }
/**
* @brief Destructor of the Request class.
*/
~Request()
{ }
/**
* @brief Returns the URL of CivetWeb's request.
*
* @return URL of the HTTP request.
*/
const char *uri() const
{
const auto req = mg_get_request_info(conn_);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
req != nullptr,
msg_server_error_get_request_info);
return req->local_uri;
}
/**
* @brief Gets a URI parameter given its name.
*
* @param value The value of the parameter.
* @param name of the parameter to be retrieved.
* @return Indicates whether the parameter was found.
*/
bool uri_param(std::string& value, const std::string& name) const
{
const auto req = mg_get_request_info(conn_);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
req != nullptr,
msg_server_error_get_request_info);
if (req->query_string == nullptr) {
return false;
}
return CivetServer::getParam(req->query_string, name.c_str(), value);
}
/**
* @brief Constructs a map with the query parameters in a given URI.
*
* @param params Map with decoded names and values of all the query
* parameters in the URI.
* @return Boolean value indicating whether query parameters were retrieved
* or not.
*/
bool uri_params(StringMap& params) const
{
const auto req = mg_get_request_info(conn_);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
req != nullptr,
msg_server_error_get_request_info);
if (req->query_string == nullptr) {
return false;
}
const auto query_string = req->query_string;
auto query_string_length = strlen(query_string);
auto i = std::size_t { 0 };
while (i < query_string_length) {
// Get Name
auto i0 = i;
while ((i < query_string_length) && query_string[i] != '='
&& query_string[i] != '&') {
i++;
}
auto name = std::string();
CivetServer::urlDecode(&query_string[i0], i - i0, name, true);
if (i < query_string_length && query_string[i] == '=') {
i++;
}
// Get Value
i0 = i;
while ((i < query_string_length) && query_string[i] != '&') {
i++;
}
auto value = std::string();
CivetServer::urlDecode(&query_string[i0], i - i0, value, true);
if (i < query_string_length && query_string[i] == '&') {
i++;
}
// Add key-value pair to the map
params[name] = value;
}
return true;
}
/**
* @brief Gets a URI header given its name.
*
* @param Name of the header to be retrieved.
* @return Gets the value of an HTTP header given its name.
*/
const char *get_header(const char *name) const
{
const char *value = mg_get_header(conn_, name);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
value != nullptr,
msg_server_error_get_header);
return value;
}
/**
* @brief This method saves the body of the HTTP request in the body buffer.
*
* @param [out] Body of the HTTP request.
* @param [in,out] Length of the body of the HTTP request.
*/
void get_body(char *& body, size_t& body_length) const
{
auto mg_result = mg_read(conn_, body, body_length);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result >= 0,
msg_server_error_read);
}
/**
* @brief Saves the body of the HTTP request in a string buffer.
*
* @param [out] Body of the HTTP request in StringBuffer.
*/
void get_body(StringBuffer& body) const
{
const auto body_length = get_body_length();
body.ensure_capacity(body_length + 1);
auto mg_result = mg_read(conn_, body.c_str_ptr(), body_length);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result >= 0,
msg_server_error_read);
}
/**
* @brief Returns the length of the body of the HTTP request.
*
* @return Body length.
*/
size_t get_body_length() const
{
const auto req = mg_get_request_info(conn_);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
req != nullptr,
msg_server_error_get_request_info);
auto content_length = req->content_length;
// Check that the content_length is not negative (except for GET,
// DELETE, and PUT operations) where it is permitted.
if ((content_length < 0) && (strcmp(req->request_method, "GET") != 0)
&& (strcmp(req->request_method, "DELETE") != 0)
&& (strcmp(req->request_method, "PUT") != 0)) {
RTI_THROW_HTTP_EXCEPTION(
StatusCode::bad_request,
&RTI_LOG_ANY_FAILURE_s,
"Content-length of the HTTP request is invalid.");
}
return content_length < 0 ? 0 : static_cast<size_t>(content_length);
}
/**
* @brief Returns the method of the HTTP request.
*
* @return Method of the HTTP request.
*/
Method get_http_method() const
{
const struct mg_request_info *req = mg_get_request_info(conn_);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
req != nullptr,
msg_server_error_get_request_info);
return method_from_string(
req->request_method == nullptr ? "" : req->request_method);
}
private:
struct mg_connection *conn_;
};
}}}} // namespace rti::apputils::http::server
#endif // HTTP_SERVER_REQUEST_HPP
@@ -0,0 +1,157 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_SERVER_REQUEST_HANDLER_HPP
#define HTTP_SERVER_REQUEST_HANDLER_HPP
#include <functional>
#include "rti/apputils/http/common/Method.hpp"
#include "rti/apputils/http/common/HttpUtils.hpp"
#include "Connection.hpp"
#include "ServerProperty.hpp"
namespace rti { namespace apputils { namespace http { namespace server {
// Forward declaration
class Server;
/**
* @brief This class defines a generic interface to be implemented by each
* RequestHandler. This implies implementing a set or methods to handle
* different types of requests (e.g., POST, PUT, GET, DELETE, OPTIONS, HEAD and
* PATCH).
*/
class RequestHandler {
public:
/**
* @brief Virtual destructor of the RequestHandler class.
*/
virtual ~RequestHandler()
{ }
/**
* @brief The POST request handler is an interface to implement a method
* that addresses POST requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the web server properties.
*/
virtual void handle_post(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
/**
* @brief The PUT request handler is an interface to implement a method that
* addresses PUT requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the service properties.
*/
virtual void handle_put(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
/**
* @brief The GET request handler is an interface to implement a method that
* addresses GET requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the service properties.
*/
virtual void handle_get(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
/**
* @brief The DELETE request handler is an interface to implement a method
* that addresses DELETE requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the web server properties.
*/
virtual void handle_delete(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
/**
* @brief The OPTIONS request handler is an interface to implement a method
* that addresses OPTIONS requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the web server properties.
*/
virtual void handle_options(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
/**
* @brief The HEAD request handler is an interface to implement a method
* that addresses HEAD requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the web server properties.
*/
virtual void handle_head(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
/**
* @brief The PATCH request handler is an interface to implement a method
* that addresses PATCH requests and generates responses.
*
* @param server Reference to the wrapper of the internal server API.
* @param connection
* @param web_server_property Reference to the web server properties.
*/
virtual void handle_patch(
Server&,
Connection& connection,
const ServerProperty&)
{
connection.send_not_implemented();
}
};
}}}} // namespace rti::apputils::http::server
#endif // HTTP_SERVER_REQUEST_HANDLER_HPP
@@ -0,0 +1,125 @@
/*
* (c) Copyright, Real-Time Innovations, 2019.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_SERVER_HPP
#define HTTP_SERVER_HPP
#include <memory>
#include <string>
#include <thread>
#include <CivetServer.h>
#include "ServerProperty.hpp"
#include "rti/apputils/http/server/detail/CivetWebHandler.hpp"
namespace rti { namespace apputils { namespace http { namespace server {
/**
* @brief Server defines an interface to CivetWeb::CivetServer and allow us to
* create handlers and close the server thought it.
*/
class Server {
public:
/**
* @brief Constructor of the Server class.
*
* @param options CivetServer options
* @param callbacks Callbacks methods
*/
Server(const ServerProperty& server_property,
const CivetCallbacks *callbacks = nullptr)
: server_(std::unique_ptr<CivetServer>(new CivetServer(
server_property.webserver_options(),
callbacks))),
server_property_(server_property),
handlers_()
{ }
~Server()
{
close();
mg_exit_library();
}
/**
* @brief This method closes the server, i.e. calls CivetServer::close().
*/
void close()
{
constexpr auto num_retries = 10u;
for (auto i = 0u; !can_close() && i < num_retries; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
server_->close();
}
/**
* @brief Checks if the server has any callbacks still running so that it
* can be closed.
*
* @return True if the server can be closed. False otherwise.
*/
bool can_close()
{
return std::none_of(
handlers_.begin(),
handlers_.end(),
[](const std::unique_ptr<CivetWebHandler>& handler) {
return handler->callbacks_running();
});
}
template <typename Handler>
void add_handler(Handler *handler)
{
handlers_.emplace_back(std::unique_ptr<CivetWebHandler>(
new CivetWebHandler(server_property_, *this, handler)));
server_->addHandler(Handler::URI_PATTERN, handlers_.back().get());
}
/**
* @brief Adds a URI handler. Existing handlers will be overwritten.
*
* @param uri String with the URI
* @param handler CivetServer pointer to the handler that to be added
*/
template <typename Handler>
void add_handler()
{
add_handler(new Handler());
}
/**
* @brief Adds a URI websocket handler. Existing websocket handlers will be
* overwritten.
*
* @param uri String with the URI
* @param websocket_handler CivetServer pointer to the handler that to be
* added
*/
void add_websocket_handler(const std::string& uri, void *websocket_handler)
{
server_->addWebSocketHandler(
uri,
reinterpret_cast<CivetWebSocketHandler *>(websocket_handler));
}
private:
std::unique_ptr<CivetServer> server_;
const ServerProperty& server_property_;
std::vector<std::unique_ptr<CivetWebHandler>> handlers_;
};
}}}} // namespace rti::apputils::http::server
#endif // HTTP_SERVER_HPP
@@ -0,0 +1,213 @@
/*
* (c) Copyright, Real-Time Innovations, 2022.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef HTTP_SERVER_PROPERTY_HPP
#define HTTP_SERVER_PROPERTY_HPP
#include <algorithm>
#include <dds/core/macros.hpp>
#include <rti/core/NativeValueType.hpp>
#include "rti/apputils/util/Path.hpp"
#include "rti/apputils/http/common/HttpUtils.hpp"
namespace rti { namespace apputils { namespace http { namespace server {
/**
* @brief This class contains the HTTP server properties
*/
class ServerProperty {
public:
/**
* @brief ServerProperty constructor
*/
ServerProperty(const std::string& ports = HTTP_SERVER_DEFAULT_PORT_STR)
: handle_options_request_(false)
{
listening_ports(ports);
}
/**
* @brief Destroy the Server Property object
*/
virtual ~ServerProperty() = default;
/**
* @brief Return the document root path.
*
* @return const std::string&
*/
const std::string& document_root() const
{
return document_root_;
}
/**
* @brief Return the web server options.
*
* @return const std::vector<std::string>&
*/
const std::vector<std::string>& webserver_options() const
{
return webserver_options_;
}
/**
* @brief Return the value of a web server options given it name. If the
* option is not found, the name will be returned.
*
* @return const std::string>& option value, or option name if not found.
*/
const std::string& webserver_option(std::string& name) const
{
auto it = std::find(
webserver_options_.begin(),
webserver_options_.end(),
name);
if (it != webserver_options_.end()
&& ++it != webserver_options_.end()) {
return *it;
}
return name;
}
/**
* @brief This method returns true if the server will handle OPTIONS
* requests, otherwise false.
*
* @return boolean
*/
bool handle_options_request() const
{
return handle_options_request_;
}
/**
* @brief Set the document root path.
*
* @param document_root
* @return ServerProperty&
*/
ServerProperty& document_root(const std::string& document_root)
{
if (!document_root.empty()) {
document_root_ = util::Path(document_root);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
document_root_.exists(),
"Document root path does not exist");
set_webserver_option("document_root", document_root_.c_str());
}
return *this;
}
/**
* @brief Set the listening ports.
*
* @param listening_ports
* @return ServerProperty&
*/
ServerProperty& listening_ports(const std::string& listening_ports)
{
listening_ports_ = listening_ports;
set_webserver_option("listening_ports", listening_ports.c_str());
return *this;
}
/**
* @brief This method enters a key-value pair into the webserver_options
* array.
*
* @param key Name of the option to be set.
* @param value Value of the option.
*/
ServerProperty& set_webserver_option(const char *key, const char *value)
{
// Check if the option already exists.
auto it = std::find(
webserver_options_.begin(),
webserver_options_.end(),
key);
// if exists modify the value
if (it != webserver_options_.end()
&& ++it != webserver_options_.end()) {
*it = value;
} else {
// Add option name
webserver_options_.emplace_back(key);
// Add option value
webserver_options_.emplace_back(value);
}
return *this;
}
/**
* @brief This option enables the server to handle OPTIONS requests if true,
* otherwise civetweb will handle them.
*
* @param handle_options_request boolean value to handle OPTIONS requests.
* @return ServerProperty&
*/
ServerProperty& handle_options_request(bool handle_options_request)
{
handle_options_request_ = handle_options_request;
return *this;
}
static std::string parse_port(const std::string& host)
{
std::string::size_type scheme_pos = host.find("://");
if (scheme_pos == std::string::npos) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"Invalid scheme format");
}
std::string unformatted_host = host.substr(scheme_pos + 1);
std::string::size_type port_pos = unformatted_host.find(":");
if (port_pos == std::string::npos) {
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
"Invalid host:port format");
}
std::string port_string = unformatted_host.substr(port_pos + 1);
try {
// Check that port has a valid format
std::stoi(port_string);
} catch (...) {
std::string error_msg;
error_msg.append("Invalid port value: ").append(port_string);
RTI_THROW_SERVICE_EXCEPTION(
&RTI_LOG_INIT_FAILURE_s,
error_msg.c_str());
}
return port_string;
}
private:
// Server options
util::Path document_root_;
std::string listening_ports_;
std::vector<std::string> webserver_options_;
bool handle_options_request_;
};
}}}} // namespace rti::apputils::http::server
#endif // HTTP_SERVER_PROPERTY_HPP
@@ -0,0 +1,320 @@
/*
* (c) Copyright, Real-Time Innovations, 2024.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef _WEBSOCKET_CONNECTION_H_
#define _WEBSOCKET_CONNECTION_H_
#include <civetweb.h>
#include <map>
#include <rti/apputils/StringBuffer.hpp>
#include <rti/apputils/http/common/WebSocketUtils.h>
#include <rti/apputils/http/common/StatusCode.hpp>
#include <rti/apputils/http/common/HttpException.hpp>
namespace rti { namespace apputils { namespace websocket {
/*
* This class wraps the state of the client connection. Every instance created
* from this class must be kept alive until the connection is closed. Whenever
* the server receives a new web_wait_factorysocket connection attempt, it
* creates a WebSocketConnection instance and this WebSocketConnection will be
* attached to the client connection.
*
* This class has the state of the client connection, the type of content that
* the client accepts and the bind ids that each connection have bound.
*/
class WebSocketConnection {
public:
explicit WebSocketConnection()
: state_(WebSocketStateKind::UNKNOWN),
content_type_(WebSocketContentType::UNKNOWN)
{ }
virtual ~WebSocketConnection() = default;
/**
* Overloading the == operator to compare connections.
* @param conn A pointer to the connection to compare
*/
virtual bool operator==(WebSocketConnection *conn) = 0;
/**
* This method returns the state of the connection
* @return WebSocketStateKind the current status
*/
WebSocketStateKind state() const
{
return state_;
}
/**
* This method returns the content type accepted for the connection
* @return WebSocketContentType
*/
WebSocketContentType content_type() const
{
return content_type_;
}
/**
* This method sets the state of the connection
* @param state WebSocketStateKind the new status
*/
void state(const WebSocketStateKind& state)
{
state_ = state;
}
/**
* This method sets the content type accepted for the connection
* @param content WebSocketContentType
*/
void content_type(const WebSocketContentType& content)
{
content_type_ = content;
}
/**
* This function sends a MG_WEBSOCKET_OPCODE_TEXT message to the user.
* @param msg The message string to be sent
*/
virtual void send(const StringBuffer& msg) = 0;
/**
* This function sends a MG_WEBSOCKET_OPCODE_PONG message to the user.
* @param msg The message string to be sent
*/
virtual void send_pong(const StringBuffer& msg) = 0;
/**
* This function creates the error message to the user.
* @param ex Excetion with the info of an issue
* @param save_ctx context with that point to the message
*/
void create_error(
RTIXMLSaveContext& save_ctx,
const rti::apputils::http::HttpException& ex)
{
struct DDS_PrintFormat format = DDS_PrintFormat_INITIALIZER;
DDS_PrintFormatProperty properties = DDS_PrintFormatProperty_INITIALIZER;
DDS_ReturnCode_t retcode = DDS_RETCODE_ERROR;
if (content_type_ == WebSocketContentType::JSON) {
properties.kind = DDS_JSON_PRINT_FORMAT;
} else if (content_type_ == WebSocketContentType::XML) {
properties.kind = DDS_XML_PRINT_FORMAT;
} else {
properties.kind = DDS_XML_PRINT_FORMAT;
}
retcode = DDS_PrintFormatProperty_to_print_format(&properties, &format);
if (retcode != DDS_RETCODE_OK) {
RTI_THROW_HTTP_EXCEPTION(
rti::apputils::http::StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Unsupported content type");
}
format.print_top_level_beginning(&format, &save_ctx, "error", 0);
format.print_primitive_type_beginning(&format, &save_ctx, "code", 1);
RTIXMLSaveContext_freeform(
&save_ctx,
"%d",
ex.error_code());
format.print_primitive_type_ending(&format, &save_ctx, "code", 1);
RTIXMLSaveContext_freeform(&save_ctx, "%s", format.element_separator);
format.print_primitive_type_beginning(&format, &save_ctx, "message", 1);
RTIXMLSaveContext_freeform(
&save_ctx,
"%sError %s%s",
format.string_opening_delimiter,
ex.what(),
format.string_closing_delimiter);
format.print_primitive_type_ending(&format, &save_ctx, "message", 1);
RTIXMLSaveContext_freeform(&save_ctx, "%s", format.element_separator);
format.print_primitive_type_beginning(&format, &save_ctx, "method", 1);
RTIXMLSaveContext_freeform(
&save_ctx,
"%s%s%s",
format.string_opening_delimiter,
RTI_FUNCTION_NAME,
format.string_closing_delimiter);
format.print_primitive_type_ending(&format, &save_ctx, "method", 1);
format.print_top_level_ending(&format, &save_ctx, "error", 0);
}
/**
* This function sends an error message to the user.
* @param msg The error message that the error will have
*/
void send_error(const rti::apputils::http::HttpException& ex)
{
StringBuffer buffer;
RTIXMLSaveContext save_ctx = RTIXMLSaveContext_INITIALIZER;
save_ctx.sout = (char*) buffer.c_str();
if (!RTIOsapiUtility_sizeToUInt32(save_ctx.ssize, buffer.capacity())) {
RTI_THROW_HTTP_EXCEPTION(
rti::apputils::http::StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Error allocating string");
}
create_error(save_ctx, ex);
if (save_ctx.outputStringLength + 1 > buffer.capacity()) {
buffer.ensure_capacity(save_ctx.outputStringLength + 1);
// Reset save_context
RTIXMLSaveContext_initialize(&save_ctx);
save_ctx.sout = (char *) buffer.c_str();
if (!RTIOsapiUtility_sizeToUInt32(save_ctx.ssize, buffer.capacity())) {
RTI_THROW_HTTP_EXCEPTION(
rti::apputils::http::StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Error allocating string");
}
create_error(save_ctx, ex);
}
send(buffer);
}
/**
* This function creates the request error message to the user.
* @param ex RequestExcetion with the info of an issue
* @param save_ctx context with that point to the message
*/
void create_request_error(
RTIXMLSaveContext& save_ctx,
const RequestException& ex)
{
struct DDS_PrintFormat format = DDS_PrintFormat_INITIALIZER;
DDS_PrintFormatProperty properties = DDS_PrintFormatProperty_INITIALIZER;
DDS_ReturnCode_t retcode = DDS_RETCODE_ERROR;
if (content_type_ == WebSocketContentType::JSON) {
properties.kind = DDS_JSON_PRINT_FORMAT;
} else if (content_type_ == WebSocketContentType::XML) {
properties.kind = DDS_XML_PRINT_FORMAT;
} else {
properties.kind = DDS_XML_PRINT_FORMAT;
}
retcode = DDS_PrintFormatProperty_to_print_format(&properties, &format);
if (retcode != DDS_RETCODE_OK) {
RTI_THROW_HTTP_EXCEPTION(
rti::apputils::http::StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Unsupported content type");
}
format.print_top_level_beginning(
&format,
&save_ctx,
WEBSOCKET_RESPONSE_NAME,
0);
format.print_primitive_type_beginning(&format, &save_ctx, "return_code", 1);
RTIXMLSaveContext_freeform(
&save_ctx,
"%d",
ex.error_code());
format.print_primitive_type_ending(&format, &save_ctx, "return_code", 1);
RTIXMLSaveContext_freeform(&save_ctx, "%s", format.element_separator);
format.print_primitive_type_beginning(&format, &save_ctx, "error", 1);
RTIXMLSaveContext_freeform(
&save_ctx,
"%s%s%s",
format.string_opening_delimiter,
ex.what(),
format.string_closing_delimiter);
format.print_primitive_type_ending(&format, &save_ctx, "error", 1);
RTIXMLSaveContext_freeform(&save_ctx, "%s", format.element_separator);
format.print_primitive_type_beginning(&format, &save_ctx, "method", 1);
RTIXMLSaveContext_freeform(
&save_ctx,
"%s%s%s",
format.string_opening_delimiter,
ex.get_method_name(),
format.string_closing_delimiter);
format.print_primitive_type_ending(&format, &save_ctx, "method", 1);
format.print_top_level_ending(
&format,
&save_ctx,
WEBSOCKET_RESPONSE_NAME,
0);
}
/**
* This function sends an REQUEST error message to the user.
* @param msg The error message that the error will have
*/
void send_request_error(const RequestException& ex)
{
StringBuffer buffer;
RTIXMLSaveContext save_ctx = RTIXMLSaveContext_INITIALIZER;
save_ctx.sout = (char*) buffer.c_str();
if (!RTIOsapiUtility_sizeToUInt32(save_ctx.ssize, buffer.capacity())) {
RTI_THROW_HTTP_EXCEPTION(
rti::apputils::http::StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Error allocating string");
}
create_request_error(save_ctx, ex);
if (save_ctx.outputStringLength + 1 > buffer.capacity()) {
buffer.ensure_capacity(save_ctx.outputStringLength + 1);
// Reset save_context
RTIXMLSaveContext_initialize(&save_ctx);
save_ctx.sout = (char *) buffer.c_str();
if (!RTIOsapiUtility_sizeToUInt32(save_ctx.ssize, buffer.capacity())) {
RTI_THROW_HTTP_EXCEPTION(
rti::apputils::http::StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Error allocating string");
}
create_request_error(save_ctx, ex);
}
send(buffer);
}
/**
* This function close a WebSocketConnection.
* @param exception with the error message that happend
*/
virtual void close() = 0;
private:
WebSocketStateKind state_;
WebSocketContentType content_type_;
};
}}} // namespace rti::webdds::websocket
#endif // _WEBSOCKET_CONNECTION_H_
@@ -0,0 +1,209 @@
/*
* (c) Copyright, Real-Time Innovations, 2015.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef CIVETWEB_HANDLER_HPP
#define CIVETWEB_HANDLER_HPP
#include <atomic>
#include <CivetServer.h>
#include <civetweb.h>
#include "rti/apputils/http/server/ServerProperty.hpp"
#include "rti/apputils/http/server/Connection.hpp"
#include "rti/apputils/http/server/RequestHandler.hpp"
namespace rti { namespace apputils { namespace http { namespace server {
// Forward declaration
class Server;
/**
* @brief WebHandler implements the handler functions of CivetWeb's API,
* wrapping the calls to the generic RequestHandler. The Request is specialized
* to an specific kind of request handler (e.g., PublisherHandler) whose type is
* specified via template.
*/
class CivetWebHandler : public CivetHandler {
public:
/**
* @brief Constructor of the CivetWebHandler class. It calls the constructor
* of the specific RequestHandler it implements (i.e., handler)
*
* @param web_server_property Reference to the ServerProperty that will
* be used by the RequestHandler.
* @param server Web's server.
*/
explicit CivetWebHandler(
const ServerProperty& web_server_property,
Server& server,
RequestHandler *handler)
: web_server_property_(web_server_property),
server_(server),
handler_(handler),
callbacks_counter_(0)
{ }
/**
* @brief Destructor of the CivetWebHandler class.
*/
virtual ~CivetWebHandler() = default;
/**
* @brief This method handles the POST operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return Always returns true given the fact that the operation is always
* processed by the underlying interfaces.
*/
virtual bool handlePost(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
Connection connection(conn);
handler_->handle_post(server_, connection, web_server_property_);
return true;
}
/**
* @brief This method handles the PUT operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return Always returns true given the fact that the operation is always
* processed by the underlying interfaces.
*/
virtual bool handlePut(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
Connection connection(conn);
handler_->handle_put(server_, connection, web_server_property_);
return true;
}
/**
* @brief This method handles the GET operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return Always returns true given the fact that the operation is always
* processed by the underlying interfaces.
*/
virtual bool handleGet(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
Connection connection(conn);
handler_->handle_get(server_, connection, web_server_property_);
return true;
}
/**
* @brief This method handles the DELETE operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return Always returns true given the fact that the operation is always
* processed by the underlying interfaces.
*/
virtual bool handleDelete(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
Connection connection(conn);
handler_->handle_delete(server_, connection, web_server_property_);
return true;
}
/**
* @brief This method handles the OPTIONS operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return true if the request was processed by the custom handler,
* otherwise false.
*/
virtual bool handleOptions(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
if (web_server_property_.handle_options_request()) {
Connection connection(conn);
handler_->handle_options(server_, connection, web_server_property_);
return true;
}
return false;
}
/**
* @brief This method handles the HEAD operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return Always returns true given the fact that the operation is always
* processed by the underlying interfaces.
*/
virtual bool handleHead(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
Connection connection(conn);
handler_->handle_head(server_, connection, web_server_property_);
return true;
}
/**
* @brief This method handles the PATCH operation.
*
* @param server CivetWeb's server.
* @param conn CiveWeb's connection.
* @return Always returns true given the fact that the operation is always
* processed by the underlying interfaces.
*/
virtual bool handlePatch(CivetServer *, struct mg_connection *conn) final
{
CounterGuard guard { callbacks_counter_ };
Connection connection(conn);
handler_->handle_patch(server_, connection, web_server_property_);
return true;
}
bool callbacks_running() const
{
return callbacks_counter_ != 0;
}
private:
struct CounterGuard {
CounterGuard(std::atomic_short& counter) : counter_(counter)
{
++counter_;
}
~CounterGuard()
{
--counter_;
}
std::atomic_short& counter_;
};
const ServerProperty& web_server_property_;
Server& server_;
std::unique_ptr<RequestHandler> handler_;
std::atomic_short callbacks_counter_;
};
}}}} // namespace rti::apputils::http::server
#endif // CIVETWEB_HANDLER_HPP
@@ -0,0 +1,165 @@
/*
* (c) Copyright, Real-Time Innovations, 2024.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef CIVETWEB_WEBSOCKET_HANDLER_H
#define CIVETWEB_WEBSOCKET_HANDLER_H
#include <CivetServer.h>
#include <civetweb.h>
#include <rti/apputils/http/server/CivetWebSocketConnection.hpp>
#include <rti/apputils/http/server/detail/WebSocketHandler.hpp>
namespace rti { namespace apputils { namespace websocket { namespace civetweb {
/**
* This class implements the CivetWebSocketHandler interface, which is part
* of the CivetWeb API. This class has a WebSocketHandler. It allows us
* to isolate the WebSocket handling that CivetWeb does and the WebSocket
* handling that our WebSocketAPI implements
*/
class CivetWebSocketHandlerImpl : public CivetWebSocketHandler {
public:
CivetWebSocketHandlerImpl(WebSocketHandler& handler) : handler_(handler)
{ }
/**
* This method handles new client connections to the server. For such
* purpose, it creates a new `WebSocketConnection` and attaches it to the
* internal CivetWeb `mg_connection`. This mechanisms allows existing
* connections to be reused in the future.
* @param server CivetWeb server. Unused parameter.
* @param conn CivetWeb connection.
* @return Boolean value indicating the success or failure of the operation.
*/
bool handleConnection(CivetServer *, const struct mg_connection *conn)
{
WebSocketConnection *client_conn =
new CivetWebSocketConnection(const_cast<mg_connection *>(conn));
handler_.handle_connection(*client_conn);
mg_set_user_connection_data(const_cast<mg_connection *>(conn), client_conn);
/*
* client_conn is not being leaked here, it's stored indirectly
* in the user connection data that can be get by calling
* mg_get_user_connection_data as we can see on handleClose.
*/
/* coverity[leaked_storage : FALSE] */
return true;
}
/**
* This method is called when a client connection has ended the handshake
* protocol with success.
* @param server CivetWeb server. Unused parameter.
* @param conn CivetWeb connection
*/
void handleReadyState(CivetServer *, struct mg_connection *conn)
{
WebSocketConnection *client = static_cast<WebSocketConnection *>(
mg_get_user_connection_data(conn));
handler_.handle_ready_state(*client);
}
/**
* This method is called every time a client sends a message to the server
* through the WebSocket connection.
* @param server CivetWeb server. Unused parameter
* @param conn CivetWeb connection
* @param bits A byte with the kind of data (TEXT, PONG, ...)
* @param data String containing the received message, it is not
* null-terminated
* @param data_size String size, the size of the string without the null
* character.
*/
bool handleData(
CivetServer *,
struct mg_connection *conn,
int bits,
char *data,
size_t data_len)
{
StringBuffer msg;
WebSocketConnection *client = static_cast<WebSocketConnection *>(
mg_get_user_connection_data(conn));
// The last byte of the bits say what kind of message it is
switch (((unsigned char) bits) & 0x0F) {
case MG_WEBSOCKET_OPCODE_TEXT:
// This marks the end of the string
*(data + data_len) = '\0';
msg = StringBuffer(data);
try {
handler_.handle_data(*client, msg);
} catch (const RequestException& ex) {
DDSLog_exception(&RTI_LOG_ANY_FAILURE_s, ex.what());
client->send_request_error(ex);
} catch (const rti::apputils::http::HttpException& ex) {
DDSLog_exception(&RTI_LOG_ANY_FAILURE_s, ex.what());
client->send_error(ex);
} catch (...) {
std::string message = "Unexpected exception. The connection will be closed.";
DDSLog_exception(&RTI_LOG_ANY_FAILURE_s, message.c_str());
message.append(": ");
message.append(RTI_FUNCTION_NAME);
client->send(message);
// Close the connection (Especially to avoid vulnerabilities)
client->state(WebSocketStateKind::CLOSE);
client->close();
}
break;
case MG_WEBSOCKET_OPCODE_PING:
// This marks the end of the string
data[data_len] = '\0';
msg = StringBuffer(data);
// just send back the message data in a pong frame
client->send_pong(msg);
break;
default:
break;
}
return true;
}
/**
* This method is called when a client closes the connection. The method
* deletes the connection object.
* @param server CivetWeb Server
* @param conn CivetWeb connection
*/
void handleClose(CivetServer *, const struct mg_connection *conn)
{
struct mg_context *ctx = mg_get_context(conn);
WebSocketConnection *client =
static_cast<WebSocketConnection *>(mg_get_user_connection_data(conn));
if (client->state() != WebSocketStateKind::CLOSE){
handler_.handle_close(*client);
}
mg_lock_context(ctx);
delete client;
mg_set_user_connection_data(const_cast<mg_connection *>(conn), NULL);
mg_unlock_context(ctx);
}
~CivetWebSocketHandlerImpl()
{
}
private:
WebSocketHandler& handler_;
};
}}}} // namespace rti::webdds::websocket::civetweb
#endif
@@ -0,0 +1,63 @@
/*
* (c) Copyright, Real-Time Innovations, 2024.
* All rights reserved.
*
* No duplications, whole or partial, manual or electronic, may be made
* without express written permission. Any such copies, or
* revisions thereof, must display this notice unaltered.
* This code contains trade secrets of Real-Time Innovations, Inc.
*/
#ifndef WEBSOCKET_HANDLER_HPP
#define WEBSOCKET_HANDLER_HPP
#include <rti/apputils/http/server/WebSocketConnection.hpp>
#include <rti/apputils/StringBuffer.hpp>
namespace rti { namespace apputils { namespace websocket {
/**
* This class is a generic handler for WebSocket communication. It
* implements methods that handle all the different events that can be
* triggered in the context of a WebSocket connection.
*/
class WebSocketHandler {
public:
/**
* This method is called when a client establishes a connection with the
* server. When that happens, we set the status of the WebSocketConnection
* to WebSocketStatus_CREATED.
* @param conn WebSocketConnection connection that will be created
*/
virtual void handle_connection(WebSocketConnection& conn) = 0;
/**
* This method is called when the websocket's handshake protocol has
* ended successfully. The state of the connection is marked as
* WebSocketStateKind::OPEN.
* @param conn WebSocketConnection connection that will be updated
*/
virtual void handle_ready_state(WebSocketConnection& conn) = 0;
/**
* This method is called every time a client sends a message. The message is
* wrapped in a StringBuffer. Upon reception, the method identifies the
* message kind and calls the appropriate Handler for that kind of message.
* @param conn WebSocketConnection connection that sent the message
* @param msg StringBuffer with the corresponding message
*/
virtual void handle_data(
WebSocketConnection& conn,
const StringBuffer& msg) = 0;
/**
* This method is called when a client closes a connection with the server.
* It detaches all conditions that have been previously attached to the
* connection.
* @param conn WebSocketConnection connection that will be closed
*/
virtual void handle_close(WebSocketConnection& conn) = 0;
};
}}} // namespace rti::apputils::websocket
#endif // WEBSOCKET_HANDLER_HPP