321 lines
11 KiB
C++
321 lines
11 KiB
C++
/*
|
|
* (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_
|