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,355 @@
/*
* (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_CLIENT_CONNECTION_HPP
#define HTTP_CLIENT_CONNECTION_HPP
#include <array>
#include <chrono>
#include <memory>
#include <string>
#include <thread>
#include <civetweb.h>
#include "rti/apputils/ServiceException.hpp"
#include "rti/apputils/http/common/Response.hpp"
#include "rti/apputils/http/common/HttpUtils.hpp"
#include "rti/apputils/http/log/Log.hpp"
#include "Request.hpp"
namespace rti { namespace apputils { namespace http { namespace client {
/**
* @brief This class is a wrapper around the SSL configuration for the client.
*
*/
struct SslConfig {
SslConfig(
bool the_enabled = false,
std::string the_client_cert = "",
std::string the_server_cert = "")
: enabled(the_enabled),
client_cert(the_client_cert),
server_cert(the_server_cert)
{ }
bool enabled;
std::string client_cert;
std::string server_cert;
};
/**
* @brief This class deal with actual HTTP client requests and
* responses to and from CivetWeb.
*/
class Connection {
public:
/**
* @brief Construct a new Connection object Note that since every request
* reconnects the connection, we do not need to connect in the constructor
* itself.
*
* @param host Hostname of the server
* @param port Port of the server
* @param ssl_config SSL configuration for the client
*/
Connection(
const std::string& host = std::string("localhost"),
std::uint16_t port = HTTP_SERVER_DEFAULT_PORT,
SslConfig ssl_config = SslConfig())
: host_ { host }, port_ { port }, ssl_config_ { ssl_config }
{ }
/**
* @brief send an actual HTTP request to CivetWeb. It extracts all
* the information from the request and constructs a valid HTTP request
* string.
*
* @param request HTTP request to be sent to the server.
*/
void send_http_request(const Request& request)
{
connect();
// Send HTTP method and URL
auto mg_result = mg_printf(
connection_.get(),
"%s %s",
method_to_string(request.http_method()).c_str(),
request.uri());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
// Add URL params to URL
if (request.uri_parameters().size() > 0) {
mg_result = mg_printf(connection_.get(), "?");
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
for (const auto& uri_param : request.uri_parameters()) {
std::array<char, MAX_URI_LENGTH> uri_param_value;
uri_param_value.fill('\0');
// We need to encode the URI params in case they contain
// spaces or any other character that may not be valid in a
// URL
mg_result = mg_url_encode(
uri_param.second.c_str(),
uri_param_value.data(),
MAX_URI_LENGTH);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_url_encode);
mg_result = mg_printf(
connection_.get(),
"%s=%s&",
uri_param.first.c_str(),
uri_param_value.data());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
}
}
// Specify HTTP version
mg_result = mg_printf(connection_.get(), " HTTP/%s\r\n", HTTP_VERSION);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
// Add user-defined headers
for (const auto& header : request.headers()) {
mg_result = mg_printf(
connection_.get(),
"%s: %s\r\n",
header.first.c_str(),
header.second.c_str());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
}
// Add default headers if not defined
if (request.header("Content-Length") == nullptr) {
mg_result = mg_printf(
connection_.get(),
"Content-Length: %llu\r\n",
static_cast<unsigned long long>(request.body().length()));
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
}
if (request.header("Content-Type") == nullptr) {
mg_result = mg_printf(
connection_.get(),
"Content-Type: %s\r\n",
HTTP_PLAIN_TEXT_CONTENT_TYPE);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
}
if (request.header("Cache-Control") == nullptr) {
mg_result = mg_printf(
connection_.get(),
"Cache-Control: %s\r\n",
HTTP_CACHE_CONTROL_NO_CACHE);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
}
if (request.header("Date") == nullptr) {
mg_result = mg_printf(
connection_.get(),
"Date: %s\r\n",
HttpSettings::get_current_date().c_str());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
}
// Separate headers from body
mg_result = mg_printf(connection_.get(), "\r\n");
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result > 0,
msg_client_error_printf);
// Send body of the HTTP request -- if empty it will not write anything
mg_result = mg_printf(connection_.get(), "%s", request.body().c_str());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
mg_result >= 0,
msg_client_error_printf);
}
/**
* @brief returns a Response based on the actual HTTP response received from
* CivetWeb. It extracts all the information originally stored in the
* mg_connection and transforms it into a valid Response object.
*
* @param timeout Number of milliseconds to wait for a response. A negative
* value means wait indefinitely.
*
* @return Response object with the response from CivetWeb.
*/
Response get_http_response(int timeout = HTTP_CLIENT_TIMEOUT_MS)
{
std::array<char, HTTP_BODY_INITIAL_LENGTH> error_buffer;
error_buffer[0] = '\0';
// Get CivetWeb's response.
const auto result = mg_get_response(
connection_.get(),
error_buffer.data(),
error_buffer.size(),
timeout);
// If we timeout trying to get the response, throw an exception.
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
result >= 0,
error_buffer.data());
// Convert CivetWeb's response into a Response
const mg_response_info *response_info =
mg_get_response_info(connection_.get());
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
response_info != nullptr,
msg_client_error_get_response_info);
return Response {
static_cast<unsigned short>(response_info->status_code),
response_info->status_text,
[&] {
const auto header =
mg_get_header(connection_.get(), "Content-Type");
return (header == nullptr) ? "" : header;
}(),
this->read_content(),
[&] {
std::unordered_map<std::string, std::string> headers;
for (int i = 0; i < response_info->num_headers; ++i) {
headers.emplace(
response_info->http_headers[i].name,
response_info->http_headers[i].value);
}
return headers;
}()
};
}
std::string read_content(std::size_t chunk_size = 256)
{
const auto response_info = mg_get_response_info(connection_.get());
if (response_info == nullptr) {
RTI_THROW_HTTP_EXCEPTION(
StatusCode::internal_server_error,
&RTI_LOG_ANY_FAILURE_s,
"Error reading content");
}
if (response_info->content_length >= 0) {
return read_content_with_content_length(
connection_.get(),
static_cast<std::size_t>(response_info->content_length));
}
return read_content_without_content_length(
connection_.get(),
chunk_size);
}
private:
/**
* @brief time we connect, we reset the mg_connection. This ensures
* that CivetWeb's internal buffers and state is cleaned up. This makes
* the Connection easier to use, since it does not need
* to be reconnected with every unit test run.
*/
void connect()
{
std::array<char, HTTP_BODY_INITIAL_LENGTH> error_buffer;
error_buffer[0] = 0;
if (ssl_config_.enabled) {
mg_client_options client_options = create_client_options();
connection_.reset(mg_connect_client_secure(
&client_options,
error_buffer.data(),
HTTP_BODY_INITIAL_LENGTH));
} else {
connection_.reset(mg_connect_client(
host_.c_str(),
port_,
ssl_config_.enabled,
error_buffer.data(),
HTTP_BODY_INITIAL_LENGTH));
}
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
connection_ != nullptr || error_buffer[0] == 0,
error_buffer.data());
}
/**
* @brief This method closes any existing connection.
*/
struct Disconnect {
void operator()(mg_connection *connection) const
{
mg_close_connection(connection);
}
};
/**
* @brief This method create a new mg_client_options object with the
* information provided by the user.
*/
mg_client_options create_client_options()
{
mg_client_options client_options;
client_options.host = host_.c_str();
client_options.port = port_;
client_options.client_cert =
(ssl_config_.client_cert.empty() || !ssl_config_.enabled)
? nullptr
: ssl_config_.client_cert.c_str();
client_options.server_cert =
(ssl_config_.server_cert.empty() || !ssl_config_.enabled)
? nullptr
: ssl_config_.server_cert.c_str();
client_options.host_name =
!ssl_config_.enabled ? nullptr : client_options.host;
return client_options;
}
private:
std::unique_ptr<mg_connection, Disconnect> connection_;
std::string host_;
std::uint16_t port_;
SslConfig ssl_config_;
};
}}}} // namespace rti::apputils::http::client
#endif // HTTP_CLIENT_CONNECTION_HPP
@@ -0,0 +1,202 @@
/*
* (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_CLIENT_REQUEST_HPP
#define HTTP_CLIENT_REQUEST_HPP
#include <array>
#include <string>
#include <cassert>
#include <cstring>
#include "rti/apputils/http/common/Method.hpp"
#include "rti/apputils/http/common/HttpUtils.hpp"
namespace rti { namespace apputils { namespace http { namespace client {
/**
* @brief Request class allow users to define their own requests in order to
* send them to the server trough the Connection class.
*/
class Request {
public:
/**
* @brief Construct a new Request empty object
*/
Request()
{
uri_.fill('\0');
}
/**
* @brief Getter for the HTTP method.
*
* @return Method object associated to the request.
*/
Method http_method() const
{
return http_method_;
}
/**
* @brief Setter for the HTTP method.
*
* @param http_method HTTP method (POST, PUT, GET, DELETE, HEAD, PATCH,
* OPTIONS).
* @return Reference to the Request object.
*/
Request& http_method(Method http_method)
{
http_method_ = http_method;
return *this;
}
/**
* @brief Gets the URL of the HTTP request.
*
* @return URL of the HTTP request.
*/
const char *uri() const
{
return uri_.data();
}
/**
* @brief Sets the URL of the HTTP request.
*
* @param uri URL of the HTTP request.
* @return Reference to the Request object.
*/
Request& uri(const char *uri)
{
std::copy(uri, uri + std::strlen(uri), uri_.data());
return *this;
}
/**
* @brief Sets the URL of the HTTP request.
*
* @param uri URL of the HTTP request.
* @return Reference to the Request object.
*/
Request& uri(const std::string& uri)
{
return this->uri(uri.c_str());
}
/**
* @brief Gets the value of an uri parameter.
*
* @param name Name of the parameter.
* @return const std::string*
*/
const std::string *uri_parameter(const std::string& name) const
{
const auto it = uri_parameters_.find(name);
if (it == uri_parameters_.end()) {
return nullptr;
}
return &it->second;
}
/**
* @brief Gets all the uri parameters.
*
* @return const StringMap&
*/
const StringMap& uri_parameters() const
{
return uri_parameters_;
}
/**
* @brief Sets all the uri parameters.
*
* @param uri_parameters StringMap with all the uri parameters.
* @return Request&
*/
Request& uri_parameters(const StringMap& uri_parameters)
{
uri_parameters_ = uri_parameters;
return *this;
}
/**
* @brief Gets the value of a header.\
*
* @param name Name of the parameter.
* @return const std::string*
*/
const std::string *header(const std::string& name) const
{
const auto it = headers_.find(name);
if (it == headers_.end()) {
return nullptr;
}
return &it->second;
}
/**
* @brief Gets all the headers.
*
* @return const StringMap&
*/
const StringMap& headers() const
{
return headers_;
}
/**
* @brief Sets all the headers.
*
* @param headers StringMap with all the headers.
* @return Request&
*/
Request& headers(const StringMap& headers)
{
headers_ = headers;
return *this;
}
/**
* @brief Gets the body of the request.
*
* @return const std::string&
*/
const std::string& body() const
{
return body_;
}
/**
* @brief Sets the body of the request.
*
* @param body Body of the request.
* @return Request&
*/
Request& body(const std::string& body)
{
body_ = body;
return *this;
}
private:
Method http_method_ = Method::http_get;
std::array<char, MAX_URI_LENGTH> uri_;
StringMap uri_parameters_;
StringMap headers_;
std::string body_;
};
}}}} // namespace rti::apputils::http::client
#endif // HTTP_CLIENT_REQUEST_HPP
@@ -0,0 +1,177 @@
/*
* (c) Copyright, Real-Time Innovations, 2020.
* 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_TEST_CONNECTION_CLIENT_H
#define CIVETWEBSOCKET_TEST_CONNECTION_CLIENT_H
#include <memory>
#include <mutex>
#include "civetweb.h"
#include <queue>
#include <rti/apputils/http/server/CivetWebSocketConnection.hpp>
#include <rti/apputils/log/LogConfig.hpp>
#include "log/LogMsg.hpp"
// #include "utils/WebDDSUtils.h"
#define CIVETWEB_CLIENT_TIMEOUT_MS 1000
#define WEBSOCKET_ERROR_BUFFER_SIZE 10000
#define WEBSOCKETinput_buffer__SIZE 10000
#define WEBSERVER_DEFAULT_PORT 19020
namespace rti { namespace apputils { namespace http { namespace client {
namespace test {
class CivetWebSocketClient {
public:
~CivetWebSocketClient()
{
mg_close_connection(conn_);
}
static int websocket_client_data_handler(
struct mg_connection * /*conn*/,
int flags,
char *data,
size_t data_len,
void *user_data)
{
CivetWebSocketClient *user = NULL;
if (user_data == NULL) {
SERVICELog_exception(
&RTI_LOG_ANY_FAILURE_s,
"No user data defined");
return 1;
}
switch (((unsigned) flags & 0xFu)) {
case MG_WEBSOCKET_OPCODE_TEXT:
user = reinterpret_cast<CivetWebSocketClient *>(user_data);
user->receive(std::string(data, data_len));
break;
default:
SERVICELog_exception(
&RTI_LOG_ANY_FAILURE_s,
"The websocket message code received is not recognize");
break;
}
return 1;
}
static void websocket_client_close_handler(
const struct mg_connection *conn,
void *user_data)
{
struct mg_context *context = mg_get_context(conn);
if (user_data == NULL) {
SERVICELog_exception(
&RTI_LOG_ANY_FAILURE_s,
"No user data defined");
return;
}
mg_lock_context(context);
mg_set_user_connection_data((struct mg_connection *) conn, NULL);
mg_unlock_context(context);
}
static std::shared_ptr<CivetWebSocketClient> connect(
const char *host = "localhost",
int port = WEBSERVER_DEFAULT_PORT,
bool use_ssl = false,
const char *path = "/dds/websocket/test")
{
StringBuffer error_buffer(WEBSOCKET_ERROR_BUFFER_SIZE);
auto result = std::make_shared<CivetWebSocketClient>();
struct mg_connection *conn = mg_connect_websocket_client(
host,
port,
use_ssl,
(char *) error_buffer.c_str_ptr(),
WEBSOCKET_ERROR_BUFFER_SIZE,
path,
NULL,
websocket_client_data_handler,
websocket_client_close_handler,
result.get());
if (conn == NULL) {
SERVICELog_exception(&RTI_LOG_ANY_FAILURE_s, "No Connection");
result.reset();
return result;
}
result->connection(conn);
return result;
}
public:
CivetWebSocketClient() = default;
CivetWebSocketClient(mg_connection *conn)
: conn_(conn)
{ }
void send(const StringBuffer& msg)
{
mg_websocket_client_write(
conn_,
MG_WEBSOCKET_OPCODE_TEXT,
msg.c_str(),
msg.length());
}
void request_close()
{
mg_websocket_client_write(
conn_,
MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE,
NULL,
0);
}
void receive(const StringBuffer& buffer)
{
std::lock_guard<std::recursive_mutex> guard(mutex_);
received_buffer_.push(buffer);
}
bool buffer(StringBuffer& buffer)
{
std::lock_guard<std::recursive_mutex> guard(mutex_);
bool received = false;
if (!received_buffer_.empty()) {
buffer = received_buffer_.front();
received_buffer_.pop();
received = true;
}
return received;
}
void connection(mg_connection *conn)
{
conn_ = conn;
}
private:
mg_connection *conn_ = nullptr;
std::queue<StringBuffer> received_buffer_;
std::recursive_mutex mutex_;
};
}}}}} // namespace rti::apputils::http::client::test
#endif
@@ -0,0 +1,74 @@
/*
* (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_EXCEPTION_HPP
#define HTTP_EXCEPTION_HPP
// RTI headers
#include "rti/apputils/http/common/StatusCode.hpp"
#include "rti/apputils/ServiceException.hpp"
namespace rti { namespace apputils { namespace http {
/**
* @brief Exception class for HTTP errors that inherit from ServiceException
*/
class HttpException : public ServiceException {
public:
/**
* @brief Constructor
*
* @param throwing_context The context where the exception was thrown
* @param status_code HTTP status code
* @param log_message RTILog message (it could be a format string)
*/
template <typename... Args>
HttpException(
const char *throwing_context,
StatusCode error_code,
const RTILogMessage *log_message,
Args... args)
: ServiceException(throwing_context, log_message, args...),
error_code_(error_code)
{ }
/**
* @brief Constructor
*
* @param ServiceException Service Exception to build the HttpException
* @param status_code HTTP status code
*/
HttpException(
const ServiceException& e,
StatusCode error_code)
: ServiceException(e),
error_code_(error_code)
{ }
/**
* @brief Get the HTTP status code
*
* @return HTTP status code
*/
StatusCode error_code() const
{
return error_code_;
}
private:
StatusCode error_code_;
};
}}} // namespace rti::apputils::http
#define RTI_THROW_HTTP_EXCEPTION(...) \
throw rti::apputils::http::HttpException(RTI_FUNCTION_NAME, __VA_ARGS__)
#endif // HTTP_EXCEPTION_HPP
@@ -0,0 +1,167 @@
/*
* (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_UTILS_H
#define HTTP_UTILS_H
#include <ctime>
#include <cstring>
#include <unordered_map>
#include <civetweb.h>
#include <reda/reda_string.h>
#include <dds_c/dds_c_printformat.h>
#include "HttpException.hpp"
#include "StatusCode.hpp"
namespace rti { namespace apputils { namespace http {
using StringMap = std::unordered_map<std::string, std::string>;
// Unfortunately Civetweb doesn't expose these constants, so we needed to
// copy the values from civetweb.c (static struct mg_option config_options[])
constexpr auto HTTP_CLIENT_TIMEOUT_MS = int { 1000 };
constexpr auto HTTP_SERVER_DEFAULT_PORT = int { 8080 };
constexpr auto HTTP_SERVER_DEFAULT_PORT_STR = "8080";
constexpr auto HTTP_SERVER_DEFAULT_HOST = "127.0.0.1";
constexpr auto HTTP_VERSION = "1.1";
constexpr auto HTTP_CACHE_CONTROL_NO_CACHE = "no-cache";
constexpr auto HTTP_BODY_INITIAL_LENGTH = std::size_t { 1000 };
constexpr auto HTTP_PLAIN_TEXT_CONTENT_TYPE = "text/plain";
constexpr auto HTTP_JSON_CONTENT_TYPE = "application/json";
constexpr auto HTTP_XML_CONTENT_TYPE = "application/xml";
constexpr auto HTTP_CONTENT_TYPE_HEADER = "Content-Type";
constexpr auto HTTP_DATE_STR_LENGTH = std::size_t { 64 };
/**
* Maximum URL length we handle is 2048. This value is based on the
* comments on this article http://stackoverflow.com/questions/417142
*/
constexpr auto MAX_URI_LENGTH = int { 2048 };
/**
* @brief This structure contains utility methods to generate HttpSettings that
* need to be set as part of the HTTP response.
*/
struct HttpSettings {
/**
* @brief This method converts time_t to a string. According to RFC2616,
* Sec 14.18, this must be included in all responses other than
* 100, 101, 5xx.
*
* @return A string containing the date in the format specified by RFC2616.
*/
static std::string get_current_date()
{
const auto curtime = std::time(nullptr);
if (curtime == (time_t) -1) {
return "Thu, 01 Jan 1970 00:00:00 GMT";
}
struct tm curr_tm;
#ifdef RTI_WIN32
if (gmtime_s(&curr_tm, &curtime) != 0) {
return "Thu, 01 Jan 1970 00:00:00 GMT";
}
#else
if (gmtime_r(&curtime, &curr_tm) != &curr_tm) {
return "Thu, 01 Jan 1970 00:00:00 GMT";
}
#endif
auto result = std::string(HTTP_DATE_STR_LENGTH, '\0');
const auto bytes_written = std::strftime(
&result[0],
result.size(),
"%a, %d %b %Y %H:%M:%S GMT",
&curr_tm);
RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(
bytes_written > 0,
"Error in std::strftime");
result.resize(bytes_written);
return result;
}
};
inline std::string read_content_with_content_length(
mg_connection *connection,
std::size_t content_length)
{
auto content = std::string(content_length, 0);
auto result = 0;
while (content_length > 0 && result >= 0) {
result = mg_read(connection,
&content[content.size() - content_length],
content_length);
content_length -= result;
}
return content;
}
inline std::string read_content_without_content_length(
mg_connection *connection,
std::size_t chunk_size = 256)
{
auto content = std::string();
auto bytes_read = std::size_t { 0 };
auto result = 0;
do {
bytes_read += result;
content.resize(bytes_read + chunk_size);
result = mg_read(connection, &content[bytes_read], chunk_size);
} while (result > 0);
content.resize(bytes_read);
return content;
}
inline std::string base64_encode(const std::string& in)
{
std::string out;
out.reserve(in.size() * 2);
const char *b64 =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (std::size_t i = 0; i < in.size(); i += 3) {
// Static cast to unsigned char to avoid cert_str34_c_violation
int a = static_cast<unsigned char>(in[i]);
int b = i + 1 >= in.size() ? 0 : static_cast<unsigned char>(in[i + 1]);
int c = i + 2 >= in.size() ? 0 : static_cast<unsigned char>(in[i + 2]);
out.push_back(b64[a >> 2]);
out.push_back(b64[((a & 3) << 4) | (b >> 4)]);
if (i + 1 < in.size()) {
out.push_back(b64[(b & 15) << 2 | (c >> 6)]);
}
if (i + 2 < in.size()) {
out.push_back(b64[c & 63]);
}
}
while (out.size() % 4 != 0) {
out.push_back('=');
}
out.shrink_to_fit();
return out;
}
}}} // namespace rti::apputils::http
#endif // HTTP_UTILS_H
@@ -0,0 +1,96 @@
/*
* (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_METHOD_HPP
#define HTTP_METHOD_HPP
#include <string>
#include "rti/apputils/http/common/HttpException.hpp"
namespace rti {
namespace apputils { namespace http {
/**
* Enum class with every HTTP method
*/
enum class Method {
http_post,
http_put,
http_get,
http_delete,
http_options,
http_head,
http_patch
};
/**
* @brief Convert a Method enum to an std::string
*
* @param http_method Method enum to convert
* @return std::string with the method name
*/
inline std::string method_to_string(Method http_method)
{
switch (http_method) {
case Method::http_post:
return "POST";
case Method::http_put:
return "PUT";
case Method::http_get:
return "GET";
case Method::http_delete:
return "DELETE";
case Method::http_options:
return "OPTIONS";
case Method::http_head:
return "HEAD";
case Method::http_patch:
return "PATCH";
default:
RTI_THROW_HTTP_EXCEPTION(
StatusCode::bad_request,
&RTI_LOG_ANY_FAILURE_s,
"Invalid HTTP method");
}
}
/**
* @brief Convert a std::string to a Method enum
*
* @param http_method String to convert
* @return Method enum
*/
inline Method method_from_string(const std::string& http_method)
{
if (http_method == "POST") {
return Method::http_post;
} else if (http_method == "PUT") {
return Method::http_put;
} else if (http_method == "GET") {
return Method::http_get;
} else if (http_method == "DELETE") {
return Method::http_delete;
} else if (http_method == "OPTIONS") {
return Method::http_options;
} else if (http_method == "HEAD") {
return Method::http_head;
} else if (http_method == "PATCH") {
return Method::http_patch;
} else {
RTI_THROW_HTTP_EXCEPTION(
StatusCode::bad_request,
&RTI_LOG_ANY_FAILURE_s,
"Invalid HTTP method");
}
}
}}} // namespace rti::apputils::http
#endif // HTTP_METHOD_HPP
@@ -0,0 +1,51 @@
/*
* (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_RESPONSE_HPP
#define HTTP_RESPONSE_HPP
#include <string>
#include <unordered_map>
namespace rti { namespace apputils { namespace http {
/**
* @brief This class contains all the elements involved in an HTTP response
*/
struct Response {
/**
* Response's status code
*/
unsigned short http_status_code;
/**
* Response's reason phrase
*/
std::string http_reason_phrase;
/**
* Response's content type
*/
std::string http_content_type;
/**
* Response's body
*/
std::string http_content;
/**
* Response's headers as a map
*/
std::unordered_map<std::string, std::string> http_headers;
};
}}} // namespace rti::apputils::http
#endif // HTTP_RESPONSE_HPP
@@ -0,0 +1,90 @@
/*
* (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_STATUS_CODE_HPP
#define HTTP_STATUS_CODE_HPP
#include <string>
namespace rti { namespace apputils { namespace http {
/**
* @brief Enum class with HTTP status codes.
*/
enum class StatusCode {
ok = 200,
created = 201,
accepted = 202,
no_content = 204,
multiple_choices = 300,
moved_permanently = 301,
moved_temporarily = 302,
not_modified = 304,
bad_request = 400,
unauthorized = 401,
forbidden = 403,
not_found = 404,
not_allowed = 405,
internal_server_error = 500,
not_implemented = 501,
bad_gateway = 502,
service_unavailable = 503
};
/**
* @brief Convert a StatusCode enum to an std::string
*
* @param status_code StatusCode enum to convert
* @return std::string with the status code name
*/
inline std::string status_code_to_string(StatusCode status_code)
{
switch (status_code) {
case StatusCode::ok:
return "OK";
case StatusCode::created:
return "Created";
case StatusCode::accepted:
return "Accepted";
case StatusCode::no_content:
return "No Content";
case StatusCode::multiple_choices:
return "Multiple Choices";
case StatusCode::moved_permanently:
return "Moved Permanently";
case StatusCode::moved_temporarily:
return "Moved Temporarily";
case StatusCode::not_modified:
return "Not Modified";
case StatusCode::bad_request:
return "Bad Request";
case StatusCode::unauthorized:
return "Unauthorized";
case StatusCode::forbidden:
return "Forbidden";
case StatusCode::not_found:
return "Not Found";
case StatusCode::not_allowed:
return "Not Allowed";
case StatusCode::internal_server_error:
return "Internal Server Error";
case StatusCode::not_implemented:
return "Not Implemented";
case StatusCode::bad_gateway:
return "Bad Gateway";
case StatusCode::service_unavailable:
return "Service Unavailable";
default:
return "";
}
}
}}} // namespace rti::apputils::http
#endif // HTTP_STATUS_CODE_HPP
@@ -0,0 +1,100 @@
/*
* (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_API_UTILS_H_
#define WEBSOCKET_API_UTILS_H_
#include "dds_c/dds_c_printformat.h"
#include <memory>
#include <rti/apputils/http/common/HttpException.hpp>
#define ERR_LOST_HEADER "Lost header"
#define WEBSOCKET_RESPONSE_NAME "response"
#define WEBSOCKET_HELLO_FAIL_HEADER "HELLO FAIL:"
namespace rti { namespace apputils { namespace websocket {
enum class WebSocketStateKind{
UNKNOWN,
CREATE,
OPEN,
READY,
CLOSE
};
enum class WebSocketContentType{
UNKNOWN,
JSON,
XML
};
inline DDS_PrintFormatKind to_print_format_kind(WebSocketContentType contentType) {
return contentType == WebSocketContentType::JSON
? DDS_JSON_PRINT_FORMAT
: DDS_XML_PRINT_FORMAT;
}
inline WebSocketContentType to_websocket_content_type(DDS_PrintFormatKind formatKind) {
WebSocketContentType value = WebSocketContentType::UNKNOWN;
switch (formatKind) {
case DDS_JSON_PRINT_FORMAT:
value = WebSocketContentType::JSON;
break;
case DDS_XML_PRINT_FORMAT:
value = WebSocketContentType::XML;
break;
case DDS_DEFAULT_PRINT_FORMAT:
value = WebSocketContentType::XML;
break;
default:
value = WebSocketContentType::UNKNOWN;
break;
}
return value;
}
/**
* The RequestException class defines a exception will log an error message
* via RTIWsddsLog_exeption or will just propagate a given return code
* identifying the error and an error message. The class inherits from
* Exception, as a result users can use familiar interfaces, such as what() to
* get the error message. Also it have the request id to create the error
* message to be sent.
*/
class RequestException : public rti::apputils::http::HttpException {
public:
RequestException(
const char *throwing_context,
rti::apputils::http::StatusCode error_code,
const RTILogMessage *log_message,
const char *method_name,
...)
: HttpException(throwing_context, error_code, log_message),
method_name_(method_name)
{
}
/**
* Returns the method name that threw the exception.
* @return Method name that threw the exception.
*/
const char * get_method_name() const
{
return method_name_;
}
private:
const char *method_name_;
};
}}} // namespace rti::webdds::websocket
#endif // WEBSOCKET_API_UTILS_H_
@@ -0,0 +1,41 @@
/*
* (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_LOG_HPP
#define HTTP_LOG_HPP
#include "log/log_common.h"
#include "log.peer.h"
#include "apputils/apputils_log.h"
#define APPUTILS_CURRENT_SUBMODULE SUBMODULE_APPUTILS_HTTP
const struct RTILogMessage APPUTILS_LOG_HTTP_EXAMPLE_sss= {
PRODUCT_WAVEWORKS|MODULE_APPUTILS|SUBMODULE_APPUTILS_HTTP|0,
"%s: %s: %s"
};
const struct RTILogMessage APPUTILS_LOG_HTTP_SENT_RESPONSE_us= {
PRODUCT_WAVEWORKS|MODULE_APPUTILS|SUBMODULE_APPUTILS_HTTP|1,
"Sent response %u '%s'\n"
};
constexpr auto msg_client_error_printf = "Client: error at mg_printf";
constexpr auto msg_client_error_url_encode = "Client: error at mg_url_encode";
constexpr auto msg_client_error_get_response_info =
"Client: error at mg_get_response_info";
constexpr auto msg_server_error_printf = "Server: error at mg_printf";
constexpr auto msg_server_error_read = "Server: error at mg_read";
constexpr auto msg_server_error_send_http_error = "Server: error at mg_send_http_error";
constexpr auto msg_server_error_get_request_info = "Server: error at mg_get_request_info";
constexpr auto msg_server_error_get_header = "Server: error at mg_get_header";
#endif // HTTP_UTILS_HPP
@@ -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