Adds immediate migration results
This commit is contained in:
2395 files changed
+357322
-2524
No files matched your search
+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