Adds immediate migration results
This commit is contained in:
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
|
||||
+177
@@ -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
|
||||
+84
@@ -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
|
||||
+320
@@ -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_
|
||||
+209
@@ -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
|
||||
+165
@@ -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
|
||||
+63
@@ -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
|
||||
Reference in new issue
Block a user