Adds immediate migration results
This commit is contained in:
2395 files changed
+357322
-2524
No files matched your search
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* (c) Copyright, Real-Time Innovations, 2016-.
|
||||
* 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 HPP_SERVICE_DYNAMIC_BUFFER_HPP_
|
||||
#define HPP_SERVICE_DYNAMIC_BUFFER_HPP_
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include "osapi/osapi_heap.h"
|
||||
|
||||
#include <dds/core/Value.hpp>
|
||||
#include <dds/core/types.hpp>
|
||||
#include <rti/apputils/ServiceException.hpp>
|
||||
|
||||
namespace rti { namespace apputils {
|
||||
|
||||
class DynamicBufferProperty {
|
||||
public:
|
||||
|
||||
DynamicBufferProperty(int32_t min_size, bool trim_to_size)
|
||||
:min_size_(min_size), trim_to_size_(trim_to_size)
|
||||
{
|
||||
}
|
||||
|
||||
DynamicBufferProperty(const DynamicBufferProperty &property)
|
||||
:min_size_(property.min_size_), trim_to_size_(property.trim_to_size_)
|
||||
{
|
||||
}
|
||||
|
||||
DynamicBufferProperty& min_size(int32_t min_size)
|
||||
{
|
||||
min_size_ = min_size;
|
||||
return *this;
|
||||
}
|
||||
int32_t min_size() const
|
||||
{
|
||||
return min_size_;
|
||||
}
|
||||
|
||||
DynamicBufferProperty& trim_to_size(bool trim_to_size)
|
||||
{
|
||||
trim_to_size_ = trim_to_size;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool trim_to_size() const
|
||||
{
|
||||
return trim_to_size_;
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t min_size_;
|
||||
bool trim_to_size_;
|
||||
};
|
||||
|
||||
|
||||
class DynamicBuffer {
|
||||
public:
|
||||
|
||||
DynamicBuffer(const DynamicBufferProperty &property)
|
||||
:property_(property), buffer_(NULL), buffer_length_(0)
|
||||
{
|
||||
ensure_length(property.min_size());
|
||||
}
|
||||
|
||||
~DynamicBuffer()
|
||||
{
|
||||
if (buffer_ != NULL) {
|
||||
RTIOsapiHeap_freeArray(buffer_);
|
||||
buffer_ = NULL;
|
||||
}
|
||||
buffer_length_ = 0;
|
||||
}
|
||||
|
||||
void ensure_length(int32_t new_length)
|
||||
{
|
||||
if (new_length > buffer_length_) {
|
||||
if (!RTIOsapiHeap_reallocateArray(&buffer_, new_length, char)) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_OSAPI_MEMORY_LOG_OUT_OF_HEAP_STRING_d,
|
||||
new_length);
|
||||
}
|
||||
buffer_length_ = new_length;
|
||||
}
|
||||
}
|
||||
|
||||
void trim_to_size()
|
||||
{
|
||||
if (property_.trim_to_size()) {
|
||||
if (!RTIOsapiHeap_reallocateArray(
|
||||
&buffer_,
|
||||
property_.min_size(),
|
||||
char)) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_OSAPI_MEMORY_LOG_OUT_OF_HEAP_STRING_d,
|
||||
property_.min_size());
|
||||
}
|
||||
buffer_length_ = property_.min_size();
|
||||
}
|
||||
}
|
||||
|
||||
void release(char *&buffer, int32_t& length)
|
||||
{
|
||||
buffer = buffer_;
|
||||
length = buffer_length_;
|
||||
buffer_ = NULL;
|
||||
buffer_length_ = 0;
|
||||
}
|
||||
|
||||
void own(char *buffer, int32_t length)
|
||||
{
|
||||
buffer_ = buffer;
|
||||
buffer_length_ = length;
|
||||
}
|
||||
|
||||
char *buffer()
|
||||
{
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
int32_t length()
|
||||
{
|
||||
return buffer_length_;
|
||||
}
|
||||
|
||||
private:
|
||||
const DynamicBufferProperty property_;
|
||||
char* buffer_;
|
||||
int32_t buffer_length_;
|
||||
};
|
||||
|
||||
|
||||
} } /* namespace rti::apputils */
|
||||
#endif /* HPP_SERVICE_DYNAMIC_BUFFER_HPP_ */
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* (c) Copyright, Real-Time Innovations, 2016-.
|
||||
* 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 HPP_SERVICE_DYNAMIC_DATA_CONVERTER_HPP_
|
||||
#define HPP_SERVICE_DYNAMIC_DATA_CONVERTER_HPP_
|
||||
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include "osapi/osapi_heap.h"
|
||||
#include <dds/core/Value.hpp>
|
||||
#include <dds/core/types.hpp>
|
||||
#include <assert.h>
|
||||
#include <dds/core/xtypes/DynamicType.hpp>
|
||||
#include <dds/core/xtypes/DynamicData.hpp>
|
||||
#include <rti/core/NativeValueType.hpp>
|
||||
#include <rti/apputils/ServiceException.hpp>
|
||||
#include <rti/apputils/DynamicBuffer.hpp>
|
||||
#include "apputils/apputils_dynamic_data_converter_impl.h"
|
||||
|
||||
namespace rti { namespace apputils {
|
||||
|
||||
class DynamicDataConverter {
|
||||
public:
|
||||
|
||||
class ScopedSample {
|
||||
public:
|
||||
|
||||
ScopedSample(
|
||||
APPUTILSDynamicDataConverter *converter,
|
||||
DDS_DynamicData *native) :
|
||||
converter_(converter),
|
||||
native_(native)
|
||||
{
|
||||
}
|
||||
|
||||
~ScopedSample()
|
||||
{
|
||||
APPUTILSDynamicDataConverter_return_sample(converter_, native_);
|
||||
}
|
||||
|
||||
dds::core::xtypes::DynamicData& get()
|
||||
{
|
||||
return rti::core::native_conversions::cast_from_native<
|
||||
dds::core::xtypes::DynamicData>(*native_);
|
||||
}
|
||||
|
||||
private:
|
||||
APPUTILSDynamicDataConverter *converter_;
|
||||
DDS_DynamicData *native_;
|
||||
};
|
||||
|
||||
class ScopedBuffer {
|
||||
public:
|
||||
|
||||
ScopedBuffer() : converter_(NULL), native_(NULL), native_length_(0)
|
||||
{
|
||||
}
|
||||
|
||||
ScopedBuffer(
|
||||
APPUTILSDynamicDataConverter *converter,
|
||||
void *native,
|
||||
uint32_t native_length) :
|
||||
converter_(converter),
|
||||
native_(native),
|
||||
native_length_(native_length)
|
||||
{
|
||||
}
|
||||
|
||||
~ScopedBuffer()
|
||||
{
|
||||
if (native_ != NULL) {
|
||||
APPUTILSDynamicDataConverter_return_buffer(converter_, native_);
|
||||
}
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
assign(converter_, NULL, 0);
|
||||
}
|
||||
|
||||
void assign(
|
||||
APPUTILSDynamicDataConverter *converter,
|
||||
void *native,
|
||||
uint32_t native_length)
|
||||
{
|
||||
if (native_ != NULL) {
|
||||
APPUTILSDynamicDataConverter_return_buffer(
|
||||
converter_,
|
||||
const_cast<void *>(native_));
|
||||
}
|
||||
converter_ = converter;
|
||||
native_ = native;
|
||||
native_length_ = native_length;
|
||||
}
|
||||
|
||||
ScopedBuffer& operator =(ScopedBuffer& other)
|
||||
{
|
||||
assign(other.converter_, other.native_, other.native_length_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const void * get()
|
||||
{
|
||||
return native_;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T * get() {
|
||||
return static_cast<const T *>(native_);
|
||||
}
|
||||
|
||||
uint32_t length()
|
||||
{
|
||||
return native_length_;
|
||||
}
|
||||
|
||||
private:
|
||||
APPUTILSDynamicDataConverter *converter_;
|
||||
void *native_;
|
||||
uint32_t native_length_;
|
||||
};
|
||||
|
||||
/**
|
||||
* The dynamic data converter allows for deferred initialization. When doing
|
||||
* so, it can be initialized at any moment by calling the set() method.
|
||||
*/
|
||||
DynamicDataConverter() : initialized_(false) {}
|
||||
|
||||
/**
|
||||
* Direct initialization constructor. A valid type-code has to be provided
|
||||
* (not null and it has to be a non-primitive and non null type-kind type).
|
||||
*/
|
||||
DynamicDataConverter(
|
||||
const dds::core::xtypes::DynamicType& type_code,
|
||||
const DynamicBufferProperty &property) :
|
||||
initialized_(false)
|
||||
{
|
||||
set(type_code, property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this dynamic data converter. A valid type-code has to be
|
||||
* provided (not null and it has to be a non-primitive and non null
|
||||
* type-kind type).
|
||||
* If the converter has already been initialized, this method is a no-op.
|
||||
*/
|
||||
void set(
|
||||
const dds::core::xtypes::DynamicType& type_code,
|
||||
const DynamicBufferProperty &property)
|
||||
{
|
||||
if (!initialized_) {
|
||||
if (!APPUTILSDynamicDataConverter_initialize(
|
||||
&converter_,
|
||||
&(type_code.native()),
|
||||
(RTI_UINT32) property.min_size())) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_INIT_FAILURE_s,
|
||||
"native converter");
|
||||
}
|
||||
initialized_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
~DynamicDataConverter()
|
||||
{
|
||||
if (initialized_) {
|
||||
APPUTILSDynamicDataConverter_finalize(&converter_);
|
||||
}
|
||||
}
|
||||
|
||||
ScopedSample sample(
|
||||
const dds::core::xtypes::DynamicData& sample,
|
||||
const DDS_DataRepresentationId_t target_representation)
|
||||
{
|
||||
DDS_DynamicData *native = APPUTILSDynamicDataConverter_get_sample(
|
||||
&converter_,
|
||||
const_cast<DDS_DynamicData *>(&sample.native()),
|
||||
target_representation);
|
||||
if (native == NULL) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_ANY_FAILURE_s,
|
||||
"get native sample");
|
||||
}
|
||||
return ScopedSample(&converter_, native);
|
||||
}
|
||||
|
||||
ScopedBuffer buffer(
|
||||
const dds::core::xtypes::DynamicData& sample,
|
||||
const DDS_DataRepresentationId_t target_representation)
|
||||
{
|
||||
DDS_UnsignedLong native_length = 0;
|
||||
void *native = APPUTILSDynamicDataConverter_get_buffer(
|
||||
&converter_,
|
||||
&native_length,
|
||||
const_cast<DDS_DynamicData *>(&sample.native()),
|
||||
target_representation);
|
||||
if (native == NULL) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_ANY_FAILURE_s,
|
||||
"get native buffer");
|
||||
}
|
||||
return ScopedBuffer(&converter_, native, (uint32_t) native_length);
|
||||
}
|
||||
|
||||
void buffer(
|
||||
ScopedBuffer& scoped_buffer,
|
||||
const dds::core::xtypes::DynamicData& sample,
|
||||
const DDS_DataRepresentationId_t target_representation)
|
||||
{
|
||||
DDS_UnsignedLong native_length = 0;
|
||||
/*
|
||||
* The scoped buffer has to be cleared before we can use it again. The
|
||||
* buffer will automatically be returned to this converter. After this,
|
||||
* we can safely get the buffer again from the dynamic data sample and
|
||||
* reassign to the scoped buffer.
|
||||
*/
|
||||
scoped_buffer.clear();
|
||||
void *native = APPUTILSDynamicDataConverter_get_buffer(
|
||||
&converter_,
|
||||
&native_length,
|
||||
const_cast<DDS_DynamicData *>(&sample.native()),
|
||||
target_representation);
|
||||
if (native == NULL) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_ANY_FAILURE_s,
|
||||
"get native buffer");
|
||||
}
|
||||
scoped_buffer.assign(&converter_, native, (uint32_t) native_length);
|
||||
}
|
||||
|
||||
private:
|
||||
APPUTILSDynamicDataConverter converter_;
|
||||
bool initialized_;
|
||||
};
|
||||
|
||||
|
||||
} } /* namespace rti::apputils */
|
||||
#endif /* HPP_SERVICE_DYNAMIC_DATA_CONVERTER_HPP_ */
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* (c) Copyright, Real-Time Innovations, 2017-.
|
||||
* 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 HPP_SERVICE_ENTITY_ID_HPP_
|
||||
#define HPP_SERVICE_ENTITY_ID_HPP_
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include "rtixmlutils/rtixmlutils_object.h"
|
||||
|
||||
namespace rti { namespace apputils {
|
||||
|
||||
class EntityFullNameIterator {
|
||||
public:
|
||||
|
||||
EntityFullNameIterator(const std::string& full_name)
|
||||
: full_name_(full_name), substring_pos_(0), next_substring_pos_(0)
|
||||
{
|
||||
next();
|
||||
}
|
||||
|
||||
~EntityFullNameIterator()
|
||||
{
|
||||
}
|
||||
|
||||
bool is_end() const
|
||||
{
|
||||
return substring_pos_ == full_name_.length();
|
||||
}
|
||||
|
||||
EntityFullNameIterator& end()
|
||||
{
|
||||
substring_pos_ = full_name_.length();
|
||||
next_substring_pos_ = substring_pos_;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
EntityFullNameIterator& last()
|
||||
{
|
||||
next_substring_pos_ = full_name_.length();
|
||||
substring_pos_ = next_substring_pos_;
|
||||
|
||||
for (; substring_pos_ > 0; --substring_pos_) {
|
||||
if (full_name_[substring_pos_] == '\"') {
|
||||
size_t opening_quotes = full_name_.rfind(
|
||||
"\"",
|
||||
substring_pos_ - 1);
|
||||
if (opening_quotes == std::string::npos) {
|
||||
DDSLog_warn(
|
||||
&RTI_LOG_ANY_s,
|
||||
"detected unbalanced escaped name. Missing closing '\"'?");
|
||||
} else {
|
||||
// skip all characters in escaped name
|
||||
substring_pos_ = opening_quotes;
|
||||
}
|
||||
} else if (full_name_.find(
|
||||
RTIXMLUTILS_OBJECT_NAME_SEPARATOR,
|
||||
substring_pos_) == substring_pos_) {
|
||||
substring_pos_ += strlen(RTIXMLUTILS_OBJECT_NAME_SEPARATOR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
std::string parent()
|
||||
{
|
||||
size_t parent_length = (substring_pos_ == 0)
|
||||
? 0
|
||||
: substring_pos_ - strlen(RTIXMLUTILS_OBJECT_NAME_SEPARATOR);
|
||||
return full_name_.substr(0, parent_length);
|
||||
}
|
||||
|
||||
std::string operator*() const
|
||||
{
|
||||
size_t substring_pos = substring_pos_;
|
||||
size_t name_length = next_substring_pos_ - substring_pos_;
|
||||
if (name_length > 1
|
||||
&& full_name_[substring_pos_ + name_length - 1] == '\"'
|
||||
&& full_name_[substring_pos_] == '\"') {
|
||||
name_length -= 2;
|
||||
++substring_pos;
|
||||
|
||||
}
|
||||
return full_name_.substr(substring_pos, name_length);
|
||||
}
|
||||
|
||||
EntityFullNameIterator& operator++()
|
||||
{
|
||||
next();
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string escaped()
|
||||
{
|
||||
size_t substring_pos = substring_pos_;
|
||||
size_t name_length = next_substring_pos_ - substring_pos_;
|
||||
|
||||
return full_name_.substr(substring_pos, name_length);
|
||||
}
|
||||
|
||||
|
||||
friend bool operator==(
|
||||
const EntityFullNameIterator &s1,
|
||||
const EntityFullNameIterator &s2)
|
||||
{
|
||||
return s1.substring_pos_ == s2.substring_pos_
|
||||
&& s1.next_substring_pos_ == s2.next_substring_pos_
|
||||
&& &s1.full_name_ == &s2.full_name_;
|
||||
}
|
||||
|
||||
friend bool operator!=(
|
||||
const EntityFullNameIterator &s1,
|
||||
const EntityFullNameIterator &s2)
|
||||
{
|
||||
return !(s1 == s2);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void next()
|
||||
{
|
||||
if (full_name_.find(
|
||||
RTIXMLUTILS_OBJECT_NAME_SEPARATOR,
|
||||
next_substring_pos_) == next_substring_pos_) {
|
||||
next_substring_pos_ += strlen(RTIXMLUTILS_OBJECT_NAME_SEPARATOR);
|
||||
}
|
||||
substring_pos_ = next_substring_pos_;
|
||||
|
||||
for (; next_substring_pos_ < full_name_.length(); ++next_substring_pos_) {
|
||||
if (full_name_[next_substring_pos_] == '\"') {
|
||||
size_t closing_quotes = full_name_.find(
|
||||
"\"",
|
||||
next_substring_pos_ + 1);
|
||||
if (closing_quotes == std::string::npos) {
|
||||
DDSLog_warn(
|
||||
&RTI_LOG_ANY_s,
|
||||
"detected unbalanced escaped name. Missing closing '\"'?");
|
||||
} else {
|
||||
// skip all characters in escaped name
|
||||
next_substring_pos_ = closing_quotes;
|
||||
}
|
||||
} else if (full_name_.find(
|
||||
RTIXMLUTILS_OBJECT_NAME_SEPARATOR,
|
||||
next_substring_pos_) == next_substring_pos_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string& full_name_;
|
||||
size_t substring_pos_;
|
||||
size_t next_substring_pos_;
|
||||
};
|
||||
|
||||
template<typename ENTITY_KIND_TYPE>
|
||||
class EntityId {
|
||||
public:
|
||||
typedef EntityFullNameIterator name_iterator;
|
||||
|
||||
public:
|
||||
|
||||
EntityId(const std::string& the_full_name, ENTITY_KIND_TYPE the_kind)
|
||||
: full_name_(the_full_name), kind_(the_kind)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_nil() const
|
||||
{
|
||||
return kind_ == ENTITY_KIND_TYPE::count_;
|
||||
}
|
||||
|
||||
name_iterator begin() const
|
||||
{
|
||||
return EntityFullNameIterator(full_name_);
|
||||
}
|
||||
|
||||
name_iterator end() const
|
||||
{
|
||||
return EntityFullNameIterator(full_name_).end();
|
||||
}
|
||||
|
||||
const std::string& full_name() const
|
||||
{
|
||||
return full_name_;
|
||||
}
|
||||
|
||||
const std::string name() const
|
||||
{
|
||||
return (*EntityFullNameIterator(full_name_).last());
|
||||
}
|
||||
|
||||
const std::string parent_name() const
|
||||
{
|
||||
return EntityFullNameIterator(full_name_).last().parent();
|
||||
}
|
||||
|
||||
const ENTITY_KIND_TYPE& kind() const
|
||||
{
|
||||
return kind_;
|
||||
}
|
||||
|
||||
static EntityId<ENTITY_KIND_TYPE> nil()
|
||||
{
|
||||
static EntityId<ENTITY_KIND_TYPE> nil(
|
||||
"",
|
||||
ENTITY_KIND_TYPE::count_);
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
friend bool operator==(
|
||||
const EntityId<ENTITY_KIND_TYPE>& left,
|
||||
const EntityId<ENTITY_KIND_TYPE>& right)
|
||||
{
|
||||
return left.kind_ == right.kind_
|
||||
&& left.full_name_ == right.full_name_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string full_name_;
|
||||
ENTITY_KIND_TYPE kind_;
|
||||
};
|
||||
|
||||
} } /* namespace rti::apputils */
|
||||
|
||||
|
||||
#endif //HPP_SERVICE_ENTITY_ID_HPP_
|
||||
|
||||
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* (c) Copyright, Real-Time Innovations, 2016-.
|
||||
* 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 HPP_SERVICE_RESOURCE_IDENTIFIER_HPP_
|
||||
#define HPP_SERVICE_RESOURCE_IDENTIFIER_HPP_
|
||||
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "reda/reda_inlineList.h"
|
||||
#include "apputils/apputils_resource_identifier_impl.h"
|
||||
|
||||
#include <dds/core/Value.hpp>
|
||||
#include <rti/core/NativeValueType.hpp>
|
||||
#include <rti/apputils/ServiceException.hpp>
|
||||
#include <rti/apputils/EntityId.hpp>
|
||||
|
||||
namespace rti { namespace apputils {
|
||||
|
||||
class ResourceIdentifier;
|
||||
|
||||
class RelativeResource {
|
||||
public:
|
||||
|
||||
typedef const APPUTILSRelativeResourceInfo native_type;
|
||||
|
||||
|
||||
RelativeResource(native_type *native_value)
|
||||
: native_relative_resource_(native_value)
|
||||
{
|
||||
}
|
||||
|
||||
~RelativeResource()
|
||||
{
|
||||
}
|
||||
|
||||
RelativeResource(const RelativeResource &other)
|
||||
: native_relative_resource_(other.native_relative_resource_)
|
||||
{
|
||||
}
|
||||
|
||||
RelativeResource operator * () const
|
||||
{
|
||||
return RelativeResource(*this);
|
||||
}
|
||||
|
||||
RelativeResource & operator = (const RelativeResource& other)
|
||||
{
|
||||
native_relative_resource_ = other.native_relative_resource_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
RelativeResource& operator ++ ()
|
||||
{
|
||||
native_relative_resource_ = next();
|
||||
return *this;
|
||||
}
|
||||
|
||||
RelativeResource& operator -- ()
|
||||
{
|
||||
native_relative_resource_ = prev();
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend bool operator == (
|
||||
const RelativeResource & s1,
|
||||
const RelativeResource & s2)
|
||||
{
|
||||
return s1.native_relative_resource_ == s2.native_relative_resource_;
|
||||
}
|
||||
|
||||
friend bool operator != (
|
||||
const RelativeResource & s1,
|
||||
const RelativeResource & s2)
|
||||
{
|
||||
return !(s1 == s2);
|
||||
}
|
||||
|
||||
std::string name() const
|
||||
{
|
||||
return APPUTILSRelativeResourceInfo_get_name(
|
||||
native_relative_resource_);
|
||||
}
|
||||
|
||||
std::string kind() const
|
||||
{
|
||||
return APPUTILSRelativeResourceInfo_get_kind(
|
||||
native_relative_resource_);
|
||||
}
|
||||
|
||||
RelativeResource& last()
|
||||
{
|
||||
REDAInlineListNode *last = (struct REDAInlineListNode *)
|
||||
REDAInlineList_getLast(
|
||||
native_relative_resource_->_node.inlineList);
|
||||
assert(last != NULL);
|
||||
native_relative_resource_ = reinterpret_cast<native_type*>(last);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RelativeResource& begin()
|
||||
{
|
||||
REDAInlineListNode *first = (struct REDAInlineListNode *)
|
||||
REDAInlineList_getFirst(
|
||||
native_relative_resource_->_node.inlineList);
|
||||
assert(first != NULL);
|
||||
native_relative_resource_ = reinterpret_cast<native_type*> (first);
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend const std::string to_string(
|
||||
const RelativeResource& relative_id)
|
||||
{
|
||||
return relative_id.kind() + "/" + relative_id.name();
|
||||
}
|
||||
|
||||
friend class ResourceIdentifier;
|
||||
private:
|
||||
|
||||
static RelativeResource nil()
|
||||
{
|
||||
static RelativeResource nil(NULL);
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
native_type *next()
|
||||
{
|
||||
return reinterpret_cast<native_type *>(
|
||||
REDAInlineListNode_getNext(&native_relative_resource_->_node));
|
||||
}
|
||||
|
||||
native_type * prev()
|
||||
{
|
||||
void *prev =
|
||||
REDAInlineListNode_getPrev(&native_relative_resource_->_node);
|
||||
if (prev != NULL) {
|
||||
return reinterpret_cast<native_type*> (prev);
|
||||
}
|
||||
|
||||
return native_relative_resource_;
|
||||
}
|
||||
|
||||
private:
|
||||
native_type *native_relative_resource_;
|
||||
};
|
||||
|
||||
class ResourceIdentifier {
|
||||
public:
|
||||
typedef APPUTILSResourceIdentifier native_type;
|
||||
typedef RelativeResource iterator;
|
||||
typedef RelativeResource relative_resource;
|
||||
|
||||
public:
|
||||
|
||||
ResourceIdentifier()
|
||||
{
|
||||
if (!APPUTILSResourceIdentifier_initialize(&native_)) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_INIT_FAILURE_s,
|
||||
"native resource identifier");
|
||||
}
|
||||
}
|
||||
|
||||
ResourceIdentifier(const std::string& formatted_uri)
|
||||
{
|
||||
if (!APPUTILSResourceIdentifier_initialize(&native_)) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_INIT_FAILURE_s,
|
||||
"native resource identifier");
|
||||
}
|
||||
if (!APPUTILSResourceIdentifier_parse_formatted_identifier(
|
||||
&native_,
|
||||
formatted_uri.c_str())) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_SERVICE_LOG_MALFORMED_RESOURCE_IDENTIFIER_s,
|
||||
formatted_uri.c_str());
|
||||
}
|
||||
parse(formatted_uri);
|
||||
}
|
||||
|
||||
ResourceIdentifier(const ResourceIdentifier& other)
|
||||
{
|
||||
if (!APPUTILSResourceIdentifier_initialize(&native_)) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_INIT_FAILURE_s,
|
||||
"native resource identifier");
|
||||
}
|
||||
append(other);
|
||||
}
|
||||
|
||||
ResourceIdentifier(
|
||||
const ResourceIdentifier& parent,
|
||||
const ResourceIdentifier& child)
|
||||
{
|
||||
if (!APPUTILSResourceIdentifier_initialize(&native_)) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_INIT_FAILURE_s,
|
||||
"native resource identifier");
|
||||
}
|
||||
append(parent);
|
||||
append(child);
|
||||
}
|
||||
|
||||
ResourceIdentifier(
|
||||
const std::string& kind,
|
||||
const std::string& name)
|
||||
{
|
||||
if (!APPUTILSResourceIdentifier_initialize(&native_)) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_INIT_FAILURE_s,
|
||||
"native resource identifier");
|
||||
}
|
||||
add(kind, name);
|
||||
}
|
||||
|
||||
ResourceIdentifier(
|
||||
const ResourceIdentifier::relative_resource& relative_resource)
|
||||
{
|
||||
if (!APPUTILSResourceIdentifier_initialize(&native_)) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_INIT_FAILURE_s,
|
||||
"native resource identifier");
|
||||
}
|
||||
add(relative_resource);
|
||||
}
|
||||
|
||||
ResourceIdentifier& parse(const std::string& formatted_uri)
|
||||
{
|
||||
if (!APPUTILSResourceIdentifier_parse_formatted_identifier(
|
||||
&native_,
|
||||
formatted_uri.c_str())) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_SERVICE_LOG_MALFORMED_RESOURCE_IDENTIFIER_s,
|
||||
formatted_uri.c_str());
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
~ResourceIdentifier()
|
||||
{
|
||||
APPUTILSResourceIdentifier_finalize(&native_);
|
||||
}
|
||||
|
||||
iterator last() const
|
||||
{
|
||||
return iterator(APPUTILSResourceIdentifier_get_resource(
|
||||
const_cast<native_type*> (&native_)));
|
||||
|
||||
}
|
||||
|
||||
iterator end() const
|
||||
{
|
||||
return iterator::nil();
|
||||
}
|
||||
|
||||
iterator begin() const
|
||||
{
|
||||
iterator last = this->last();
|
||||
if (last != end()) {
|
||||
return last.begin();
|
||||
}
|
||||
|
||||
return last;
|
||||
}
|
||||
|
||||
ResourceIdentifier parent() const
|
||||
{
|
||||
ResourceIdentifier parent_id;
|
||||
iterator it = begin();
|
||||
for (; it != last(); ++it) {
|
||||
parent_id += ResourceIdentifier(it.kind(), it.name());
|
||||
}
|
||||
|
||||
return parent_id;
|
||||
}
|
||||
|
||||
std::string fully_qualified_name() const
|
||||
{
|
||||
const auto result = APPUTILSResourceIdentifier_get_fully_qualified_name(
|
||||
const_cast<native_type *>(&native_));
|
||||
return (result == nullptr) ? "" : result;
|
||||
}
|
||||
|
||||
std::string fully_qualified_kind() const
|
||||
{
|
||||
const auto result = APPUTILSResourceIdentifier_get_fully_qualified_kind(
|
||||
const_cast<native_type *>(&native_));
|
||||
return (result == nullptr) ? "" : result;
|
||||
}
|
||||
|
||||
std::string custom_method() const
|
||||
{
|
||||
const auto result = APPUTILSResourceIdentifier_get_custom_method(
|
||||
const_cast<native_type *>(&native_));
|
||||
return (result == nullptr) ? "" : result;
|
||||
}
|
||||
|
||||
ResourceIdentifier& operator+=(
|
||||
const ResourceIdentifier& right)
|
||||
{
|
||||
return append(right);
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(
|
||||
std::ostream& os,
|
||||
const ResourceIdentifier& resource_id)
|
||||
{
|
||||
os << to_string(resource_id);
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
friend const std::string to_string(
|
||||
const ResourceIdentifier& resource_id)
|
||||
{
|
||||
const char* resource_id_str = to_native_string(resource_id);
|
||||
if (resource_id_str != NULL) {
|
||||
return std::string(resource_id_str);
|
||||
} else {
|
||||
return std::string("");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
friend const char* to_native_string(
|
||||
const ResourceIdentifier& resource_id)
|
||||
{
|
||||
return APPUTILSResourceIdentifier_to_string(
|
||||
const_cast<native_type*>(&resource_id.native_));
|
||||
}
|
||||
|
||||
native_type& native() const
|
||||
{
|
||||
return const_cast<native_type&>(native_);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
ResourceIdentifier& add(
|
||||
const std::string& kind,
|
||||
const std::string& name = "")
|
||||
{
|
||||
if (!APPUTILSResourceIdentifier_add_relative_resource(
|
||||
&native_,
|
||||
kind.c_str(),
|
||||
name.c_str())) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_ADD_FAILURE_s,
|
||||
"relative resource");
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ResourceIdentifier& add(
|
||||
const ResourceIdentifier::relative_resource& relative_resource)
|
||||
{
|
||||
add(relative_resource.kind(), relative_resource.name());
|
||||
return *this;
|
||||
}
|
||||
|
||||
ResourceIdentifier& append(
|
||||
const ResourceIdentifier& right)
|
||||
{
|
||||
ResourceIdentifier::iterator it = right.begin();
|
||||
for (; it != end(); ++it) {
|
||||
add(it);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
native_type native_;
|
||||
|
||||
};
|
||||
|
||||
} } /* namespace rti::apputils */
|
||||
|
||||
|
||||
#endif /* HPP_SERVICE_RESOURCE_IDENTIFIER_HPP_ */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* (c) Copyright, Real-Time Innovations, 2016-.
|
||||
* 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 HPP_SERVICE_SERVICE_EXCEPTION_HPP_
|
||||
#define HPP_SERVICE_SERVICE_EXCEPTION_HPP_
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include <dds/core/types.hpp>
|
||||
#include <rti/core/Exception.hpp>
|
||||
|
||||
namespace rti { namespace apputils {
|
||||
|
||||
static
|
||||
void initialize_exception_message(
|
||||
std::string& message,
|
||||
const char *throwing_context,
|
||||
const RTILogMessage *log_message,
|
||||
va_list ap)
|
||||
{
|
||||
char message_buffer[RTI_LOG_MSG_SIZE_MAX];
|
||||
|
||||
RTIOsapiUtility_unusedReturnValue(
|
||||
RTIOsapiUtility_vsnprintf(
|
||||
message_buffer,
|
||||
RTI_LOG_MSG_SIZE_MAX,
|
||||
log_message->format,
|
||||
ap),
|
||||
int);
|
||||
RTIOsapiUtility_unusedReturnValue(
|
||||
RTIOsapiUtility_snprintf(
|
||||
&message[0],
|
||||
RTI_LOG_MSG_SIZE_MAX,
|
||||
"%s:%s",
|
||||
throwing_context,
|
||||
message_buffer),
|
||||
int);
|
||||
}
|
||||
|
||||
static
|
||||
void initialize_exception_message(
|
||||
std::string& message,
|
||||
const char *throwing_context,
|
||||
const RTILogMessageTemplate *log_message,
|
||||
const char *parameterizedString,
|
||||
va_list ap)
|
||||
{
|
||||
char message_buffer[RTI_LOG_MSG_SIZE_MAX];
|
||||
const char *SEPARATOR_STR = " | ";
|
||||
|
||||
RTIOsapiUtility_unusedReturnValue(
|
||||
RTIOsapiUtility_vsnprintf(
|
||||
message_buffer,
|
||||
RTI_LOG_MSG_SIZE_MAX,
|
||||
parameterizedString,
|
||||
ap),
|
||||
int);
|
||||
RTIOsapiUtility_unusedReturnValue(
|
||||
RTIOsapiUtility_snprintf(
|
||||
&message[0],
|
||||
RTI_LOG_MSG_SIZE_MAX,
|
||||
"%s%s%s:%s",
|
||||
throwing_context,
|
||||
log_message->format,
|
||||
strlen(log_message->format) > 0 ? SEPARATOR_STR : "",
|
||||
message_buffer),
|
||||
int);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Exception class for Services
|
||||
*/
|
||||
class ServiceException : public std::exception {
|
||||
public:
|
||||
|
||||
ServiceException(
|
||||
const char *throwing_context,
|
||||
const RTILogMessage *log_message,
|
||||
...)
|
||||
: message_(RTI_LOG_MSG_SIZE_MAX, '\0'),
|
||||
message_id_(log_message->messageId)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, log_message);
|
||||
initialize_exception_message(
|
||||
message_,
|
||||
throwing_context,
|
||||
log_message,
|
||||
ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
ServiceException(
|
||||
const char *throwing_context,
|
||||
const RTILogMessageTemplate *log_message,
|
||||
const char *parameterizedString,
|
||||
...)
|
||||
: message_(RTI_LOG_MSG_SIZE_MAX, '\0'),
|
||||
message_id_(log_message->messageId)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, parameterizedString);
|
||||
initialize_exception_message(
|
||||
message_,
|
||||
throwing_context,
|
||||
log_message,
|
||||
parameterizedString,
|
||||
ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
uint32_t message_id() const
|
||||
{
|
||||
return message_id_;
|
||||
}
|
||||
|
||||
virtual const char* what() const throw ()
|
||||
{
|
||||
return message_.c_str();
|
||||
}
|
||||
|
||||
virtual ~ServiceException() throw ()
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
ServiceException() : message_id_(0u)
|
||||
{
|
||||
}
|
||||
|
||||
std::string message_;
|
||||
std::uint32_t message_id_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Fatal exception class for Services. This exception will cause
|
||||
* the underlying service to call the shutdown hook.
|
||||
*/
|
||||
class FatalServiceException : public ServiceException {
|
||||
public:
|
||||
template <typename... Args>
|
||||
FatalServiceException(
|
||||
const char *throwing_context,
|
||||
const RTILogMessage *log_message,
|
||||
Args... args)
|
||||
: ServiceException(throwing_context, log_message, args...)
|
||||
{ }
|
||||
|
||||
template <typename... Args>
|
||||
FatalServiceException(
|
||||
const char *throwing_context,
|
||||
const RTILogMessageTemplate *log_message,
|
||||
const char *parameterizedString,
|
||||
Args... args)
|
||||
: ServiceException(
|
||||
throwing_context,
|
||||
log_message,
|
||||
parameterizedString,
|
||||
args...)
|
||||
{ }
|
||||
|
||||
virtual ~FatalServiceException() throw()
|
||||
{ }
|
||||
};
|
||||
|
||||
} } /* namespace rti::apputils */
|
||||
|
||||
#define RTI_THROW_SERVICE_EXCEPTION(...) \
|
||||
throw rti::apputils::ServiceException(\
|
||||
RTI_FUNCTION_NAME,\
|
||||
__VA_ARGS__)
|
||||
|
||||
#define RTI_THROW_FATAL_SERVICE_EXCEPTION(...) \
|
||||
throw rti::apputils::FatalServiceException(\
|
||||
RTI_FUNCTION_NAME,\
|
||||
__VA_ARGS__)
|
||||
|
||||
#define RTI_SERVICE_CHECK_CONDITION(EXPRESSION) \
|
||||
if(!(EXPRESSION)) { \
|
||||
RTI_THROW_SERVICE_EXCEPTION( \
|
||||
&RTI_LOG_ANY_s, \
|
||||
__FILE__ ":" RTI_TOSTRING(__LINE__) ": condition failed: " #EXPRESSION); \
|
||||
}
|
||||
|
||||
#define RTI_SERVICE_CHECK_CONDITION_W_EXTRA_INFO(EXPRESSION, EXTRA_INFO) \
|
||||
if(!(EXPRESSION)) { \
|
||||
RTI_THROW_SERVICE_EXCEPTION( \
|
||||
&RTI_LOG_ANY_ss, \
|
||||
__FILE__ ":" RTI_TOSTRING(__LINE__) ": condition failed: " #EXPRESSION ", ", \
|
||||
EXTRA_INFO); \
|
||||
}
|
||||
|
||||
|
||||
#endif /* HPP_SERVICE_SERVICE_EXCEPTION_HPP_ */
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* (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 STRING_BUFFER_HPP
|
||||
#define STRING_BUFFER_HPP
|
||||
|
||||
#include "rti/apputils/ServiceException.hpp"
|
||||
|
||||
namespace rti { namespace apputils {
|
||||
|
||||
// Default value of a StringBuffer
|
||||
constexpr size_t STRING_BUFFER_LENGTH = 1000;
|
||||
|
||||
/**
|
||||
* A GenericStringBuffer is a smart buffer that allocates an INITIAL_CAPACITY
|
||||
* in the stack and allocates more capacity in the heap if needed. The template
|
||||
* parameter INITIAL_CAPACITY determines the capacity that is allocated in the
|
||||
* stack. In case there is a resize which involves heap allocation, the
|
||||
* destructor of the GenericStringBuffer class frees the memory.
|
||||
*/
|
||||
template <std::size_t INITIAL_CAPACITY>
|
||||
class GenericStringBuffer {
|
||||
public:
|
||||
/**
|
||||
* This constructor creates a new empty GenericStringBuffer with
|
||||
* INITIAL_CAPACITY.
|
||||
*/
|
||||
GenericStringBuffer() : capacity_(INITIAL_CAPACITY), c_str_ptr_(nullptr)
|
||||
{
|
||||
RTIOsapiMemory_zero(c_str_, capacity_);
|
||||
c_str_ptr_ = c_str_;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor creates a new GenericStringBuffer ensuring the size
|
||||
* of the input parameter. It allocates memory in the heap if necessary.
|
||||
* @param size Size to ensure.
|
||||
*/
|
||||
GenericStringBuffer(const size_t size)
|
||||
: capacity_(INITIAL_CAPACITY), c_str_ptr_(nullptr)
|
||||
{
|
||||
RTIOsapiMemory_zero(c_str_, capacity_);
|
||||
c_str_ptr_ = c_str_;
|
||||
ensure_capacity(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor creates a new GenericStringBuffer object containing
|
||||
* an input string. It ensures whatever capacity is necessary for the input
|
||||
* string.
|
||||
* @param str std::string that initializes the GenericStringBuffer.
|
||||
*/
|
||||
GenericStringBuffer(const std::string& str)
|
||||
: capacity_(INITIAL_CAPACITY), c_str_ptr_(nullptr)
|
||||
{
|
||||
RTIOsapiMemory_zero(c_str_, capacity_);
|
||||
c_str_ptr_ = c_str_;
|
||||
|
||||
ensure_capacity(str.length() + 1);
|
||||
RTIOsapiUtility_unusedReturnValue(
|
||||
RTIOsapiUtility_strcpy(c_str_ptr_, str.length(), str.c_str()),
|
||||
char *);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor.
|
||||
* @param other String buffer that we want to copy
|
||||
*/
|
||||
GenericStringBuffer(const GenericStringBuffer& other)
|
||||
: capacity_(INITIAL_CAPACITY), c_str_ptr_(nullptr)
|
||||
{
|
||||
RTIOsapiMemory_zero(c_str_, capacity_);
|
||||
c_str_ptr_ = c_str_;
|
||||
|
||||
ensure_capacity(other.length() + 1);
|
||||
RTIOsapiUtility_unusedReturnValue(
|
||||
RTIOsapiUtility_strcpy(
|
||||
c_str_ptr_,
|
||||
other.length(),
|
||||
other.c_str()),
|
||||
char *);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move constructor.
|
||||
* @param other String buffer that we want to move
|
||||
*/
|
||||
GenericStringBuffer(GenericStringBuffer&& other)
|
||||
: capacity_(INITIAL_CAPACITY), c_str_ptr_(nullptr)
|
||||
{
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor of the GenericStringBuffer. It frees whatever memory may
|
||||
* have been allocated by the ensure_capacity method().
|
||||
*/
|
||||
~GenericStringBuffer()
|
||||
{
|
||||
if (capacity_ > INITIAL_CAPACITY) {
|
||||
DDS_String_free(c_str_ptr_);
|
||||
c_str_ptr_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy-assignment operator
|
||||
* @param other The buffer we want to copy.
|
||||
* @return Object in which we have copied.
|
||||
*/
|
||||
GenericStringBuffer& operator=(const GenericStringBuffer& other)
|
||||
{
|
||||
if (this != &other) {
|
||||
ensure_capacity(other.length() + 1);
|
||||
RTIOsapiUtility_unusedReturnValue(
|
||||
RTIOsapiUtility_strcpy(
|
||||
c_str_ptr_,
|
||||
other.length(),
|
||||
other.c_str()),
|
||||
char *);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move-assignment operator
|
||||
* @param other The buffer we want to move.
|
||||
* @return Object in which we have moved.
|
||||
*/
|
||||
GenericStringBuffer& operator=(GenericStringBuffer&& other)
|
||||
{
|
||||
if (this != &other) {
|
||||
if (other.capacity_ <= INITIAL_CAPACITY) {
|
||||
RTIOsapiMemory_move(c_str_, other.c_str_, INITIAL_CAPACITY);
|
||||
c_str_ptr_ = c_str_;
|
||||
} else {
|
||||
capacity_ = other.capacity_;
|
||||
c_str_ptr_ = other.c_str_ptr_;
|
||||
|
||||
other.capacity_ = INITIAL_CAPACITY;
|
||||
other.c_str_ptr_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that can be written in the internal char *
|
||||
* buffer
|
||||
* @return Capacity of the internal char * buffer.
|
||||
*/
|
||||
size_t capacity() const
|
||||
{
|
||||
return capacity_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the internal char * buffer. That is the strlen
|
||||
* of whatever is in there.
|
||||
* @return Size of the string stored in the internal char * buffer.
|
||||
*/
|
||||
size_t length() const
|
||||
{
|
||||
return strlen(c_str_ptr_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a const pointer to the internal char * buffer.
|
||||
* @return Const pointer to the internal char * buffer.
|
||||
*/
|
||||
const char *c_str() const
|
||||
{
|
||||
return c_str_ptr_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer to the internal char * buffer
|
||||
* @return Pointer to the internal char * buffer.
|
||||
*/
|
||||
char *c_str_ptr()
|
||||
{
|
||||
return c_str_ptr_;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method ensures a capacity by either asserting that the
|
||||
* GenericStringBuffer has enough space to allocate a string, or
|
||||
* reallocating some space in the heap. If that is the case more space is
|
||||
* allocated in the heap via DDS_String_alloc().
|
||||
* @param capacity Size that needs to be ensured.
|
||||
*/
|
||||
void ensure_capacity(const size_t capacity)
|
||||
{
|
||||
if (capacity > capacity_) {
|
||||
// We need to free the string if it was already allocated, this
|
||||
// happens when _capacity > INITIAL_CAPACITY
|
||||
if (capacity_ > INITIAL_CAPACITY) {
|
||||
DDS_String_free(c_str_ptr_);
|
||||
}
|
||||
|
||||
// DDS_String_alloc adds one to the input parameter. We could
|
||||
// spare a byte, but we want to be precise.
|
||||
c_str_ptr_ = DDS_String_alloc(capacity - 1);
|
||||
if (c_str_ptr_ == nullptr) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_ANY_FAILURE_s,
|
||||
"Error allocating string.");
|
||||
}
|
||||
RTIOsapiMemory_zero(c_str_ptr_, capacity_);
|
||||
capacity_ = capacity;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
size_t capacity_;
|
||||
char *c_str_ptr_;
|
||||
char c_str_[INITIAL_CAPACITY];
|
||||
};
|
||||
|
||||
/**
|
||||
* A StringBuffer is a GenericStringBuffer with an initial STRING_BUFFER_LENGTH.
|
||||
*/
|
||||
typedef GenericStringBuffer<STRING_BUFFER_LENGTH> StringBuffer;
|
||||
|
||||
/**
|
||||
* This method saves a "freeform string" into a StringBuffer object.
|
||||
* @param [in,out] string_buffer StringBuffer in which the input string will
|
||||
* be saved. Note that if there's not enough capacity, the method will
|
||||
* automatically allocate more space for the string.
|
||||
* @param format Format of the string to be saved.
|
||||
* @param ... Parameters to the formatted string.
|
||||
*/
|
||||
inline void StringBuffer_save_freeform(
|
||||
StringBuffer& string_buffer,
|
||||
const char *format,
|
||||
...)
|
||||
{
|
||||
char *c_str_ptr = string_buffer.c_str_ptr();
|
||||
size_t capacity = string_buffer.capacity();
|
||||
|
||||
va_list args, args_aux;
|
||||
|
||||
RTIXMLSaveContext save_ctxt = RTIXMLSaveContext_INITIALIZER;
|
||||
|
||||
RTIXMLSaveContext_initialize(&save_ctxt);
|
||||
save_ctxt.sout = c_str_ptr;
|
||||
|
||||
if (!RTIOsapiUtility_sizeToUInt32(save_ctxt.ssize, capacity)) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_ANY_FAILURE_s,
|
||||
"Error allocating string.");
|
||||
}
|
||||
|
||||
// Wrap RTIXMLSaveContext_freeform_w_argsI call with va_start and va_end
|
||||
// of the va_list that we need to pass in.
|
||||
va_start(args, format);
|
||||
va_start(args_aux, format);
|
||||
RTIXMLSaveContext_freeform_v(&save_ctxt, format, args, args_aux);
|
||||
va_end(args);
|
||||
va_end(args_aux);
|
||||
|
||||
if (save_ctxt.outputStringLength + 1 > capacity) {
|
||||
capacity = save_ctxt.outputStringLength + 1;
|
||||
string_buffer.ensure_capacity(capacity);
|
||||
|
||||
RTIXMLSaveContext_initialize(&save_ctxt);
|
||||
save_ctxt.sout = string_buffer.c_str_ptr();
|
||||
|
||||
if (!RTIOsapiUtility_sizeToUInt32(save_ctxt.ssize, capacity)) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_ANY_FAILURE_s,
|
||||
"Error allocating string.");
|
||||
}
|
||||
|
||||
// Wrap RTIXMLSaveContext_freeform_w_argsI call with va_start and
|
||||
// va_end of the va_list that we need to pass in.
|
||||
va_start(args, format);
|
||||
va_start(args_aux, format);
|
||||
RTIXMLSaveContext_freeform_v(&save_ctxt, format, args, args_aux);
|
||||
va_end(args);
|
||||
va_end(args_aux);
|
||||
}
|
||||
}
|
||||
}} // namespace rti::apputils
|
||||
|
||||
#endif // STRING_BUFFER_HPP
|
||||
@@ -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
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
(c) Copyright, Real-Time Innovations, 2017-
|
||||
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 HPP_SERVICE_LOG_CONFIG_HPP_
|
||||
#define HPP_SERVICE_LOG_CONFIG_HPP_
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdarg>
|
||||
|
||||
#include "log/log_common.h"
|
||||
|
||||
#include <rti/advlog/Context.hpp>
|
||||
#include <rti/config/Logger.hpp>
|
||||
#include <rti/apputils/ServiceException.hpp>
|
||||
|
||||
#define RTI_SUBMODULE_MASK_ALL (0xFFFF)
|
||||
|
||||
namespace rti { namespace apputils { namespace log {
|
||||
|
||||
/**
|
||||
* @deprecated This class is deprecated and may be removed in future releases.
|
||||
*
|
||||
* @brief This class is used to configure the logging verbosity and mask of a
|
||||
* Logger instance.
|
||||
*/
|
||||
class LogConfig {
|
||||
public:
|
||||
|
||||
static LogConfig& instance()
|
||||
{
|
||||
static LogConfig singleton;
|
||||
return singleton;
|
||||
}
|
||||
|
||||
RTILogBitmap instrumentation_mask() const
|
||||
{
|
||||
return instrumentation_mask_;
|
||||
}
|
||||
|
||||
LogConfig& instrumentation_mask(RTILogBitmap logmask)
|
||||
{
|
||||
instrumentation_mask_ = logmask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
rti::config::Verbosity verbosity()
|
||||
{
|
||||
return static_cast<rti::config::Verbosity::type>(instrumentation_mask_);
|
||||
}
|
||||
|
||||
void verbosity(rti::config::Verbosity verbosity)
|
||||
{
|
||||
instrumentation_mask(
|
||||
static_cast<RTILogBitmap>(verbosity.underlying()));
|
||||
}
|
||||
|
||||
~LogConfig()
|
||||
{
|
||||
}
|
||||
|
||||
static RTILogBitmap mask_all()
|
||||
{
|
||||
return RTI_SUBMODULE_MASK_ALL;
|
||||
}
|
||||
|
||||
static RTILogBitmap mask_none()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// Constructor is private: this class is a singleton
|
||||
LogConfig()
|
||||
: instrumentation_mask_(RTI_LOG_BITMAP_DEFAULT)
|
||||
{
|
||||
RTILog_appendPrintMask(RTI_LOG_PRINT_BIT_AT_METHOD);
|
||||
}
|
||||
|
||||
RTILogBitmap instrumentation_mask_;
|
||||
};
|
||||
|
||||
inline
|
||||
rti::config::Verbosity dds_verbosity_from_level(int32_t level)
|
||||
{
|
||||
switch (level) {
|
||||
case 6:
|
||||
return rti::config::Verbosity::STATUS_REMOTE;
|
||||
case 5:
|
||||
/* no break */
|
||||
case 4:
|
||||
return rti::config::Verbosity::WARNING;
|
||||
case 3:
|
||||
/* no break */
|
||||
case 2:
|
||||
/* no break */
|
||||
case 1:
|
||||
return rti::config::Verbosity::EXCEPTION;
|
||||
case 0:
|
||||
return rti::config::Verbosity::SILENT;
|
||||
default:
|
||||
return rti::config::Verbosity::EXCEPTION;
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
rti::config::Verbosity service_verbosity_from_level(int32_t level)
|
||||
{
|
||||
RTILogBitmap native_verbosity = RTI_LOG_BITMAP_NONE;
|
||||
|
||||
switch (level) {
|
||||
/* no break */
|
||||
case 6:
|
||||
native_verbosity |= RTI_LOG_BIT_CONTENT;
|
||||
/* no break */
|
||||
case 5:
|
||||
native_verbosity |= RTI_LOG_BIT_PERIODIC;
|
||||
/* no break */
|
||||
case 4:
|
||||
native_verbosity |= RTI_LOG_BIT_REMOTE;
|
||||
/* no break */
|
||||
case 3:
|
||||
native_verbosity |= RTI_LOG_BIT_LOCAL;
|
||||
/* no break */
|
||||
case 2:
|
||||
native_verbosity |= RTI_LOG_BIT_WARN;
|
||||
/* no break */
|
||||
case 1:
|
||||
native_verbosity |= RTI_LOG_BIT_EXCEPTION | RTI_LOG_BIT_FATAL_ERROR;
|
||||
break;
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
native_verbosity = RTI_LOG_BITMAP_DEFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
return static_cast<rti::config::Verbosity::type>(native_verbosity);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Example:
|
||||
* MyCatcherMethod: caught exception from:
|
||||
* MyThrowerMethod:!init failure
|
||||
*/
|
||||
inline void log_service_exception(
|
||||
RTILogBitmap instrumentation_mask,
|
||||
const rti::apputils::ServiceException& exception,
|
||||
const char *method_name)
|
||||
{
|
||||
RTILogMessage log_message;
|
||||
|
||||
log_message.format = "caught exception from:\n %s";
|
||||
log_message.messageId = exception.message_id();
|
||||
|
||||
if (instrumentation_mask & RTI_LOG_BIT_EXCEPTION) {
|
||||
RTILogMessage_printWithParams(
|
||||
RTI_LOG_PRINT_FORMAT_MASK_ALL_EXCEPT_LOCATION_FILELINE_AND_MODULE,
|
||||
RTI_LOG_BIT_EXCEPTION,
|
||||
MODULE_LOG,
|
||||
NULL, /* filename */
|
||||
0, /* lineno */
|
||||
method_name,
|
||||
&log_message,
|
||||
exception.what());
|
||||
}
|
||||
}
|
||||
|
||||
} } } /* namespace rti::apputils::log */
|
||||
|
||||
|
||||
/* ================================================================= */
|
||||
/* Logging macros */
|
||||
/* ================================================================= */
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/*i
|
||||
* @brief See \ref RTILogCategory_log
|
||||
*/
|
||||
#define SERVICELogger_log(instrumentationBit__, logMessage__, worker__, ...) \
|
||||
RTILogCategory_log( \
|
||||
(MODULE_LOG_CONFIG).instrumentation_mask(), \
|
||||
RTI_SUBMODULE_MASK_ALL, \
|
||||
RTI_SUBMODULE_MASK_ALL, \
|
||||
(instrumentationBit__), \
|
||||
DEFINE_MODULE_NUMBER(MODULE_NS), \
|
||||
(logMessage__), \
|
||||
(worker__), \
|
||||
__VA_ARGS__)
|
||||
|
||||
/*
|
||||
* --- C-like logger macros ---------------------------------------------------
|
||||
*/
|
||||
#define SERVICELog_fromException(SERVICE_EXCEPTION) \
|
||||
rti::apputils::log::log_service_exception( \
|
||||
(MODULE_LOG_CONFIG).instrumentation_mask(), \
|
||||
SERVICE_EXCEPTION, \
|
||||
RTI_FUNCTION_NAME)
|
||||
|
||||
/*
|
||||
* For legacy reasons, we keep the definition of freeForm.
|
||||
* freeForm is now deprecated and it should not be used anymore.
|
||||
* These are the alternatives to freeForm (only to be used in test code or
|
||||
* non-production code, like an instrumented build):
|
||||
* - If you need to log (verbosity filtered) an unformatted parameterized string
|
||||
* you can use RTITestLog_logParamString (in tests) or
|
||||
* RTILogParamString_logUnformatted (in non-test, instrumented build code).
|
||||
* This is equivalent to old freeForm().
|
||||
* - If you just need to print (not verbosity filtered) an unformatted parameterized
|
||||
* string, use RTILogParamString_printPlain.
|
||||
* - Finally, if you need print a formatted parameterized string, use RTILogParamString_print.
|
||||
*/
|
||||
#define SERVICELog_freeForm(INSTRUMENT_BIT, ...) \
|
||||
RTILogParamString_logUnformatted( \
|
||||
(MODULE_LOG_CONFIG).instrumentation_mask(), \
|
||||
RTI_SUBMODULE_MASK_ALL, \
|
||||
RTI_SUBMODULE_MASK_ALL, \
|
||||
(INSTRUMENT_BIT), \
|
||||
__VA_ARGS__)
|
||||
|
||||
#define SERVICELog_logMessage(INSTRUMENT_BIT, ...) \
|
||||
RTILogMessage_log( \
|
||||
(MODULE_LOG_CONFIG).instrumentation_mask(), \
|
||||
RTI_SUBMODULE_MASK_ALL, \
|
||||
RTI_SUBMODULE_MASK_ALL, \
|
||||
RTI_LOG_PRINT_FORMAT_MASK_ALL_EXCEPT_LOCATION, \
|
||||
(INSTRUMENT_BIT), \
|
||||
DEFINE_MODULE_NUMBER(MODULE_NS), \
|
||||
__VA_ARGS__)
|
||||
|
||||
#define SERVICELogMessage_log(INSTRUMENT_BIT, ...) \
|
||||
RTILogMessage_log( \
|
||||
(MODULE_LOG_CONFIG).instrumentation_mask(), \
|
||||
RTI_SUBMODULE_MASK_ALL, \
|
||||
RTI_SUBMODULE_MASK_ALL, \
|
||||
RTI_LOG_PRINT_FORMAT_MASK_ALL, \
|
||||
(INSTRUMENT_BIT), \
|
||||
DEFINE_MODULE_NUMBER(MODULE_NS), \
|
||||
__VA_ARGS__)
|
||||
|
||||
#define SERVICELog_exception(...) \
|
||||
SERVICELogMessage_log(RTI_LOG_BIT_EXCEPTION, __VA_ARGS__)
|
||||
|
||||
#define SERVICELog_warn(...) \
|
||||
SERVICELogMessage_log(RTI_LOG_BIT_WARN, __VA_ARGS__)
|
||||
|
||||
#define SERVICELog_local(...) \
|
||||
SERVICELogMessage_log(RTI_LOG_BIT_LOCAL, __VA_ARGS__)
|
||||
|
||||
#define SERVICELog_content(...) \
|
||||
SERVICELogMessage_log(RTI_LOG_BIT_CONTENT, __VA_ARGS__)
|
||||
|
||||
#define SERVICELog_remote(...) \
|
||||
SERVICELogMessage_log(RTI_LOG_BIT_REMOTE, __VA_ARGS__)
|
||||
|
||||
|
||||
#define SERVICELog_any(INSTRUMENT_BIT, ...) \
|
||||
if ((((MODULE_LOG_CONFIG).instrumentation_mask()) & (INSTRUMENT_BIT)) \
|
||||
&& ((RTI_SUBMODULE_MASK_ALL) & (RTI_SUBMODULE_MASK_ALL)))
|
||||
|
||||
#define SERVICELog_flushActivityContextWithAction(INSTRUMENT_BIT, ACTION) \
|
||||
SERVICELog_logMessage((INSTRUMENT_BIT), &RTI_LOG_ANY_s, (ACTION));
|
||||
|
||||
#define SERVICELog_flushActivityContext(INSTRUMENT_BIT) \
|
||||
SERVICELog_logMessage((INSTRUMENT_BIT), &RTI_LOG_ANY_s, "");
|
||||
|
||||
/*
|
||||
* The macro:
|
||||
* - Creates a contextScope
|
||||
* - Enter a resource entry into the context.
|
||||
* - Enter an activity entry into the context.
|
||||
*
|
||||
* When the function where the macro is called is end, the destructor of
|
||||
* the contextScope will be called and the entries will leave the context.
|
||||
*/
|
||||
#define RTI_SERVICE_CONTEXT_ENTER( \
|
||||
RESOURCE_ID, \
|
||||
...) \
|
||||
rti::advlog::ContextScope context_scope_guard_implicit__; \
|
||||
context_scope_guard_implicit__.enter(RESOURCE_ID); \
|
||||
context_scope_guard_implicit__.enter(__VA_ARGS__)
|
||||
|
||||
#define RTI_SERVICE_CONTEXT_ENTER_LOG( \
|
||||
RESOURCE_ID, \
|
||||
...) \
|
||||
RTI_SERVICE_CONTEXT_ENTER( \
|
||||
RESOURCE_ID, \
|
||||
__VA_ARGS__); \
|
||||
SERVICELog_flushActivityContext(RTI_LOG_BIT_LOCAL)
|
||||
|
||||
#endif /* HPP_SERVICE_LOG_CONFIG_HPP_ */
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* (c) Copyright, Real-Time Innovations, 2016-.
|
||||
* 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 HPP_SERVICE_MONITORING_COUNTERS_HPP_
|
||||
#define HPP_SERVICE_MONITORING_COUNTERS_HPP_
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include <dds/core/types.hpp>
|
||||
|
||||
namespace rti { namespace apputils { namespace monitoring {
|
||||
|
||||
struct NetworkPerformanceCounters {
|
||||
|
||||
NetworkPerformanceCounters()
|
||||
:received_samples(0), received_bytes(0), latency_ms(0)
|
||||
{
|
||||
}
|
||||
|
||||
int64_t received_samples;
|
||||
int64_t received_bytes;
|
||||
int64_t latency_ms;
|
||||
};
|
||||
|
||||
} } }// rti::apputils::monitoring
|
||||
|
||||
#endif /* HPP_SERVICE_MONITORING_COUNTERS_HPP_ */
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* (c) Copyright, Real-Time Innovations, 2016-.
|
||||
* 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 HPP_SERVICE_MONITORING_UTILS_HPP_
|
||||
#define HPP_SERVICE_MONITORING_UTILS_HPP_
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include "osapi/osapi_host.h"
|
||||
#include "osapi/osapi_process.h"
|
||||
#include "apputils/apputils_monitoring.h"
|
||||
|
||||
#include <rti/core/Guid.hpp>
|
||||
#include <rti/apputils/ResourceIdentifier.hpp>
|
||||
|
||||
namespace rti { namespace apputils { namespace monitoring {
|
||||
|
||||
inline
|
||||
void compute_object_id(
|
||||
rti::core::Guid& out_guid,
|
||||
const rti::core::Guid& application_id,
|
||||
const rti::apputils::ResourceIdentifier& resource_id)
|
||||
{
|
||||
APPUTILSMonitoringSupport_compute_resource_guid(
|
||||
&out_guid.native(),
|
||||
&application_id.native(),
|
||||
&resource_id.native());
|
||||
}
|
||||
|
||||
inline
|
||||
void compute_application_guid(
|
||||
rti::core::Guid &out_guid,
|
||||
const std::string& application_name)
|
||||
{
|
||||
APPUTILSMonitoringSupport_compute_application_guid(
|
||||
&out_guid.native(),
|
||||
application_name.c_str());
|
||||
}
|
||||
|
||||
} } }// rti::apputils::monitoring
|
||||
|
||||
#endif /* HPP_SERVICE_MONITORING_UTILS_HPP_ */
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
(c) Copyright, Real-Time Innovations, 2023-.
|
||||
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 HPP_APPUTILS_SQLITEVERIFIER_HPP_
|
||||
#define HPP_APPUTILS_SQLITEVERIFIER_HPP_
|
||||
|
||||
#include "rti/apputils/SQLiteConnection.hpp"
|
||||
#include "rti/apputils/test/TestFramework.hpp"
|
||||
#include "rti/util/util.hpp"
|
||||
|
||||
#include "rti/recording/storage/EStorageFormat.hpp"
|
||||
|
||||
namespace rti { namespace apputils { namespace test {
|
||||
|
||||
/**
|
||||
* @brief Basic utility class with different checks for an SQLite Connection.
|
||||
*/
|
||||
class SQLiteVerifierBase {
|
||||
public:
|
||||
|
||||
SQLiteVerifierBase(rti::apputils::SQLiteConnection& connection) :
|
||||
connection_(connection) { }
|
||||
|
||||
bool check_table_exists(const std::string& table)
|
||||
{
|
||||
std::ostringstream string_stream;
|
||||
string_stream << "SELECT name FROM sqlite_master WHERE name = '";
|
||||
string_stream << table;
|
||||
string_stream << "'";
|
||||
|
||||
rti::apputils::SQLiteConnection::RowIterator row_it =
|
||||
connection_.begin(string_stream.str());
|
||||
if (!row_it.at_end()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool wait_till_table_exists(
|
||||
const std::string& table,
|
||||
const unsigned int seconds = DEFAULT_TEST_TIMEOUT_SEC)
|
||||
{
|
||||
bool exists = check_table_exists(table);
|
||||
dds::core::Duration sleepDuration(1,0);
|
||||
for (uint32_t i = 0; i < seconds && !exists; i++) {
|
||||
rti::util::sleep(sleepDuration);
|
||||
exists = check_table_exists(table);
|
||||
}
|
||||
if (!exists) {
|
||||
RTITestLogger_log(
|
||||
RTI_LOG_BIT_EXCEPTION,
|
||||
&RTI_TEST_LOG_FAILURE_TEMPLATE,
|
||||
"Table not found: %s",
|
||||
table.c_str());
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
bool check_column_exists(
|
||||
const std::string& table,
|
||||
const std::string& column)
|
||||
{
|
||||
std::ostringstream string_stream;
|
||||
string_stream << "PRAGMA table_info([" << table << "])";
|
||||
|
||||
rti::apputils::SQLiteConnection::RowIterator row_it =
|
||||
connection_.begin(string_stream.str());
|
||||
while (!row_it.at_end()) {
|
||||
std::string column_name;
|
||||
row_it.get(1, column_name);
|
||||
if (column_name.compare(column) == 0) {
|
||||
return true;
|
||||
}
|
||||
++row_it;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint32_t num_rows_for_statement(const std::string& sql)
|
||||
{
|
||||
rti::apputils::SQLiteConnection::RowIterator row_it =
|
||||
connection_.begin(sql);
|
||||
uint32_t row_count = 0;
|
||||
while (!row_it.at_end()) {
|
||||
row_count++;
|
||||
++row_it;
|
||||
}
|
||||
return row_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @pre check_table_exists(table) == true
|
||||
*/
|
||||
uint32_t num_rows(
|
||||
const std::string& table,
|
||||
const bool filter_invalid = false)
|
||||
{
|
||||
std::ostringstream string_stream;
|
||||
string_stream << "SELECT COUNT(*) FROM [" << table << "]";
|
||||
if (filter_invalid) {
|
||||
string_stream << " WHERE SampleInfo_valid_data = 1";
|
||||
}
|
||||
rti::apputils::SQLiteConnection::RowIterator row_it =
|
||||
connection_.begin(string_stream.str());
|
||||
int64_t value = 0;
|
||||
row_it.get(0, value);
|
||||
return (uint32_t) value;
|
||||
}
|
||||
|
||||
bool check_num_rows(
|
||||
const std::string& table,
|
||||
const uint32_t expected_count,
|
||||
const bool filter_invalid = false)
|
||||
{
|
||||
try {
|
||||
RTI_TEST_ASSERT_EQUALS_INT(
|
||||
expected_count,
|
||||
num_rows(table, filter_invalid));
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_num_rows_for_statement(
|
||||
const std::string& sql,
|
||||
const unsigned int expected_count)
|
||||
{
|
||||
try {
|
||||
RTI_TEST_ASSERT_EQUALS_INT(
|
||||
expected_count,
|
||||
num_rows_for_statement(sql));
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wait_till_num_rows(
|
||||
const std::string& table,
|
||||
const unsigned int expected_count,
|
||||
const bool filter_invalid = false,
|
||||
const unsigned int seconds = DEFAULT_TEST_TIMEOUT_SEC)
|
||||
{
|
||||
try {
|
||||
RTI_TEST_WAIT_FOR_EQUALS_INT(
|
||||
expected_count,
|
||||
num_rows(table, filter_invalid),
|
||||
static_cast<int32_t>(seconds));
|
||||
} catch (const std::exception& ex) {
|
||||
RTITestLogger_log(
|
||||
RTI_LOG_BIT_EXCEPTION,
|
||||
&RTI_TEST_LOG_FAILURE_TEMPLATE,
|
||||
"exception thrown: %s",
|
||||
ex.what());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wait_till_num_rows_for_statement(
|
||||
const std::string& sql,
|
||||
const unsigned int expected_count,
|
||||
const unsigned int seconds = DEFAULT_TEST_TIMEOUT_SEC)
|
||||
{
|
||||
try {
|
||||
RTI_TEST_WAIT_FOR_EQUALS_INT(
|
||||
expected_count,
|
||||
num_rows_for_statement(sql),
|
||||
static_cast<int32_t>(seconds));
|
||||
} catch (const std::exception& ex) {
|
||||
RTITestLogger_log(
|
||||
RTI_LOG_BIT_EXCEPTION,
|
||||
&RTI_TEST_LOG_FAILURE_TEMPLATE,
|
||||
"exception thrown: %s",
|
||||
ex.what());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_num_rows_invariant(
|
||||
const std::string& table,
|
||||
const unsigned int expected_count,
|
||||
const bool filter_invalid = false,
|
||||
const unsigned int seconds = DEFAULT_TEST_TIMEOUT_SEC)
|
||||
{
|
||||
const struct RTINtpTime sleepTime = {0,429496730};
|
||||
int sleeps = 0;
|
||||
bool result = check_num_rows(table, expected_count, filter_invalid);
|
||||
while(result && sleeps < (static_cast<int32_t>(seconds)) * 10) {
|
||||
RTIOsapiThread_sleep(&sleepTime);
|
||||
++sleeps;
|
||||
result = check_num_rows(table, expected_count, filter_invalid);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
rti::apputils::SQLiteConnection& connection_;
|
||||
};
|
||||
|
||||
} } } /* namespace rti::apputils::test */
|
||||
|
||||
|
||||
#endif /* HPP_APPUTILS_SQLITEVERIFIER_HPP_ */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
(c) Copyright, Real-Time Innovations, 2023.
|
||||
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 HPP_APPUTILS_TEST_LOG_CAPTURE_HPP_
|
||||
#define HPP_APPUTILS_TEST_LOG_CAPTURE_HPP_
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include <rti/apputils/ServiceException.hpp>
|
||||
|
||||
#include "rtitest/test_setting.h"
|
||||
#include "ddsctesthelpers/test_helper.h"
|
||||
|
||||
namespace rti { namespace apputils { namespace test {
|
||||
|
||||
/**
|
||||
* @brief This class is a C++ wrapper for the APPUTILSTestUtils_LoggerDevice
|
||||
* type, used to capture logs and find expected messages.
|
||||
*/
|
||||
class TestLogCapture {
|
||||
public:
|
||||
TestLogCapture(const std::string& expected_message) :
|
||||
expected_message_(expected_message)
|
||||
{
|
||||
device_data_ = DDSCTesterHelperLoggerDeviceData_INITIALIZER(NULL);
|
||||
if (!DDSCTesterHelperLoggerDeviceData_initialize(&device_data_)) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_INIT_FAILURE_s,
|
||||
"logger device data");
|
||||
}
|
||||
device_data_.expectedMessage = expected_message_.c_str();
|
||||
|
||||
if (!DDSCTesterHelper_cacheLoggingConfiguration()) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_ANY_FAILURE_s,
|
||||
"cache logging configuration");
|
||||
}
|
||||
|
||||
// Prepare the device logger for catching the expected message
|
||||
logger_device_ = NDDS_Config_LoggerDevice_INITIALIZER;
|
||||
logger_device_.device_data = &device_data_;
|
||||
logger_device_.write = DDSCTesterHelper_loggerDeviceWrite;
|
||||
|
||||
if (!NDDS_Config_Logger_set_output_device(
|
||||
NDDS_Config_Logger_get_instance(),
|
||||
&logger_device_)) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_INIT_FAILURE_s,
|
||||
"set output device");
|
||||
}
|
||||
}
|
||||
|
||||
~TestLogCapture()
|
||||
{
|
||||
DDSCTesterHelperLoggerDeviceData_finalize(&device_data_);
|
||||
|
||||
// Restore the logging configuration; this will restore the previous
|
||||
// output device if it was changed
|
||||
if (!DDSCTesterHelper_restoreLoggingConfiguration()) {
|
||||
RTITestLogger_log(
|
||||
RTI_LOG_BIT_EXCEPTION,
|
||||
&RTI_TEST_LOG_FAILURE_TEMPLATE,
|
||||
"restore logging configuration");
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int found_messages() const
|
||||
{
|
||||
return device_data_.foundMessages;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
struct NDDS_Config_LoggerDevice logger_device_;
|
||||
|
||||
struct DDSCTesterHelperLoggerDeviceData device_data_;
|
||||
|
||||
std::string expected_message_;
|
||||
};
|
||||
|
||||
} } } /* namespace rti::apputils::test */
|
||||
|
||||
#endif /* HPP_APPUTILS_TEST_LOG_CAPTURE_HPP_ */
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/* End $Id$ */
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
(c) Copyright, Real-Time Innovations, 2018.
|
||||
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 HPP_APPUTILS_LOCKEDBUFFER_HPP_
|
||||
#define HPP_APPUTILS_LOCKEDBUFFER_HPP_
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include "dds/core/types.hpp"
|
||||
|
||||
namespace rti { namespace apputils { namespace util {
|
||||
|
||||
/**
|
||||
* A type-agnostic class that services as a locked, unmodifiable region in
|
||||
* memory. This is handy to transport a data buffer from one code region to
|
||||
* another without having to perform any copy operations. Producer of the buffer
|
||||
* and consumer of the buffer know how to work with the raw data.
|
||||
*/
|
||||
class UntypedLockedBuffer {
|
||||
public:
|
||||
UntypedLockedBuffer() : buffer_(NULL), size_(0) {}
|
||||
UntypedLockedBuffer(const void *buffer, const uint32_t size) :
|
||||
buffer_(buffer), size_(size) {}
|
||||
~UntypedLockedBuffer() {}
|
||||
|
||||
void set(const void *buffer, const uint32_t size)
|
||||
{
|
||||
buffer_ = buffer;
|
||||
size_ = size;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
buffer_ = NULL;
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
uint32_t size() const { return size_; }
|
||||
|
||||
protected:
|
||||
|
||||
const void * buffer() const { return buffer_; }
|
||||
|
||||
private:
|
||||
const void *buffer_;
|
||||
uint32_t size_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This class represents a locked, unmodifiable region in memory. This is handy
|
||||
* to transport a data buffer from one code region to another without having to
|
||||
* perform any copy operations. Producer of the buffer and consumer of the
|
||||
* buffer know how to work with the raw data.
|
||||
*/
|
||||
template <typename T>
|
||||
class LockedBuffer : public UntypedLockedBuffer {
|
||||
public:
|
||||
LockedBuffer() : UntypedLockedBuffer() {}
|
||||
LockedBuffer(const T *buffer, const uint32_t size) :
|
||||
UntypedLockedBuffer(static_cast<const void *>(buffer), size) {}
|
||||
~LockedBuffer() {}
|
||||
|
||||
void set(const T *buffer, const uint32_t size)
|
||||
{
|
||||
UntypedLockedBuffer::set(static_cast<const void *>(buffer), size);
|
||||
}
|
||||
|
||||
const T * buffer() const {
|
||||
return static_cast<const T *>(UntypedLockedBuffer::buffer());
|
||||
}
|
||||
};
|
||||
|
||||
} } } // namespace rti::apputils::util
|
||||
|
||||
#endif /* HPP_APPUTILS_LOCKEDBUFFER_HPP_ */
|
||||
@@ -0,0 +1,495 @@
|
||||
/*
|
||||
* (c) Copyright, Real-Time Innovations, 2018.
|
||||
* 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 HPP_APPUTILS_PATH_HPP_
|
||||
#define HPP_APPUTILS_PATH_HPP_
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <stdint.h>
|
||||
#include <cerrno>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#if defined(RTI_UNIX) || defined(RTI_QNX)
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
#if defined(RTI_UNIX) || defined(RTI_QNX)
|
||||
#if !defined(_XOPEN_SOURCE) && !defined(RTI_DARWIN)
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#include <ftw.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#ifdef RTI_WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "osapi/osapi_type.h"
|
||||
#include "osapi/osapi_utility.h"
|
||||
|
||||
#include <rti/core/Exception.hpp>
|
||||
#include <rti/apputils/ServiceException.hpp>
|
||||
|
||||
namespace rti { namespace apputils { namespace util {
|
||||
|
||||
class Path {
|
||||
public:
|
||||
Path()
|
||||
{ }
|
||||
|
||||
Path(const std::string& path) : path_string_(path)
|
||||
{
|
||||
path_string_ = remove_double_separators(path_string_);
|
||||
path_string_ = remove_trailing_separators(path_string_);
|
||||
}
|
||||
|
||||
Path(const Path& path) : path_string_(path.path_string_)
|
||||
{ }
|
||||
|
||||
operator const std::string &() const
|
||||
{
|
||||
return path_string_;
|
||||
}
|
||||
|
||||
operator const char *() const
|
||||
{
|
||||
return path_string_.c_str();
|
||||
}
|
||||
|
||||
const char *c_str() const
|
||||
{
|
||||
return path_string_.c_str();
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return path_string_.empty();
|
||||
}
|
||||
|
||||
void append(const std::string& fragment)
|
||||
{
|
||||
if (!path_string_.empty()) {
|
||||
path_string_ += "/";
|
||||
}
|
||||
path_string_ += fragment;
|
||||
path_string_ = remove_double_separators(path_string_);
|
||||
path_string_ = remove_trailing_separators(path_string_);
|
||||
}
|
||||
|
||||
void append(const Path& fragment)
|
||||
{
|
||||
append((const std::string&) fragment);
|
||||
}
|
||||
|
||||
void operator+=(const std::string& fragment)
|
||||
{
|
||||
append(fragment);
|
||||
}
|
||||
|
||||
void operator+=(const Path& fragment)
|
||||
{
|
||||
append((const std::string&) fragment);
|
||||
}
|
||||
|
||||
Path operator+(const std::string& other) const
|
||||
{
|
||||
Path path(*this);
|
||||
path.append(other);
|
||||
return path;
|
||||
}
|
||||
|
||||
Path operator+(const Path& other) const
|
||||
{
|
||||
Path path(*this);
|
||||
path.append(other);
|
||||
return path;
|
||||
}
|
||||
|
||||
int compare(const Path& other)
|
||||
{
|
||||
return path_string_.compare((const std::string&) other);
|
||||
}
|
||||
|
||||
int compare(const std::string& other)
|
||||
{
|
||||
return path_string_.compare(other);
|
||||
}
|
||||
|
||||
int compare(const char *other)
|
||||
{
|
||||
return path_string_.compare(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Path is interpreted as a file. Return whether the file exists or not.
|
||||
*/
|
||||
bool exists() const
|
||||
{
|
||||
// Check if the path is a file and if it exists
|
||||
if (RTIOsapiFile_exists(path_string_.c_str()) == RTI_TRUE) {
|
||||
return true;
|
||||
} else {
|
||||
#if defined(RTI_UNIX) || defined(RTI_QNX)
|
||||
// Otherwise check if it's a directory and if it exists
|
||||
struct stat info;
|
||||
if (stat(path_string_.c_str(), &info) == 0
|
||||
&& S_ISDIR(info.st_mode)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#elif defined(RTI_WIN32)
|
||||
#ifndef S_ISDIR
|
||||
#define S_ISDIR(mode) (((mode) &_S_IFMT) == _S_IFDIR)
|
||||
#endif
|
||||
// Otherwise check if it's a directory and if it exists
|
||||
struct _stat info;
|
||||
if (_stat(path_string_.c_str(), &info) == 0
|
||||
&& S_ISDIR(info.st_mode)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void create_directory(const Path& path)
|
||||
{
|
||||
#if defined(RTI_UNIX) || defined(RTI_QNX)
|
||||
errno = 0;
|
||||
if (mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0
|
||||
&& (errno != EEXIST)) {
|
||||
std::stringstream error_msg;
|
||||
error_msg << "!create directory: " << path << "\n"
|
||||
<< "CreateDirectory() error code: " << errno << "\n";
|
||||
throw dds::core::IllegalOperationError(error_msg.str());
|
||||
}
|
||||
#elif defined(RTI_WIN32)
|
||||
if (!CreateDirectory(path, NULL)) {
|
||||
DWORD last_error = GetLastError();
|
||||
if (last_error != ERROR_ALREADY_EXISTS) {
|
||||
std::stringstream error_msg;
|
||||
error_msg << "!create directory: " << path << "\n"
|
||||
<< "CreateDirectory() error code: " << last_error
|
||||
<< "\n";
|
||||
throw dds::core::IllegalOperationError(error_msg.str());
|
||||
}
|
||||
}
|
||||
#else
|
||||
throw dds::core::IllegalOperationError("unsupported");
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpreting the path as a directory, create any non-existing directories
|
||||
* in the path.
|
||||
*/
|
||||
void create_directory()
|
||||
{
|
||||
// If the path is a file, and it exists, exit
|
||||
if (exists()) {
|
||||
return;
|
||||
}
|
||||
// Get splits, build a
|
||||
std::vector<std::string> splits = split();
|
||||
Path aux;
|
||||
for (std::vector<std::string>::iterator split_it = splits.begin();
|
||||
split_it != splits.end();
|
||||
split_it++) {
|
||||
aux += *split_it;
|
||||
if (!aux.exists()) {
|
||||
create_directory(aux);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_directory(const Path& path)
|
||||
{
|
||||
#if defined(RTI_UNIX) || defined(RTI_QNX)
|
||||
errno = 0;
|
||||
if (nftw(path, remove_file, 20, FTW_DEPTH) == -1) {
|
||||
if (errno != ENOENT) {
|
||||
perror("nftw");
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_ANY_FAILURE_s,
|
||||
"remove_directory() error");
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (!RTIOsapiFile_removeFileOrDirectory(path.c_str())) {
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_ANY_ss,
|
||||
"Failed to delete directory: ",
|
||||
path.c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void copy_to(const Path& destination_path)
|
||||
{
|
||||
if (!exists()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
std::ifstream src(path_string_.c_str(), std::ios::binary);
|
||||
std::ofstream dst(
|
||||
destination_path.path_string_.c_str(),
|
||||
std::ios::binary);
|
||||
dst << src.rdbuf();
|
||||
} catch (std::exception& ex) {
|
||||
std::stringstream stream;
|
||||
stream << "Failed to copy file" << std::endl;
|
||||
stream << " origin : " << path_string_ << std::endl;
|
||||
stream << " destination: " << destination_path << std::endl;
|
||||
stream << " error message: " << ex.what() << std::endl;
|
||||
DDSLog_exception(&RTI_LOG_ANY_s, stream.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void append_extension(const std::string& extension)
|
||||
{
|
||||
path_string_ += extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method detects and returns a (last) "." in the last split of a
|
||||
* string. The substring after this dot is considered to be the file
|
||||
* extension.
|
||||
*/
|
||||
std::string detect_file_extension()
|
||||
{
|
||||
std::string file_extension;
|
||||
std::vector<std::string> splits = split();
|
||||
std::string last_split = splits[splits.size() - 1];
|
||||
std::string::size_type pos = last_split.find_last_of('.');
|
||||
if (pos != std::string::npos) {
|
||||
file_extension = last_split.substr(pos + 1);
|
||||
}
|
||||
return file_extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the file name (the last split in the path). Depending
|
||||
* on the parameter, it will strip out the file extension or not.
|
||||
*/
|
||||
std::string detect_file_name(bool include_extension = true) const
|
||||
{
|
||||
std::vector<std::string> splits = split();
|
||||
std::string last_split = splits[splits.size() - 1];
|
||||
if (!include_extension) {
|
||||
std::string::size_type pos = last_split.find_last_of('.');
|
||||
if (pos != std::string::npos) {
|
||||
return last_split.substr(0, pos);
|
||||
}
|
||||
}
|
||||
return last_split;
|
||||
}
|
||||
|
||||
std::vector<std::string> split() const
|
||||
{
|
||||
std::vector<std::string> splits;
|
||||
std::istringstream stream(path_string_);
|
||||
std::string string_split;
|
||||
bool prepend_separator = false;
|
||||
while (getline(stream, string_split, '/')) {
|
||||
if (string_split.empty()) {
|
||||
// This can happen if the path is absolute (starts with a
|
||||
// separator). The first split should contain that separator.
|
||||
// Signal this with a boolean variable
|
||||
prepend_separator = true;
|
||||
continue;
|
||||
}
|
||||
if (prepend_separator) {
|
||||
splits.push_back("/" + string_split);
|
||||
prepend_separator = false;
|
||||
} else {
|
||||
splits.push_back(string_split);
|
||||
}
|
||||
}
|
||||
return splits;
|
||||
}
|
||||
|
||||
static Path from_splits(const std::vector<std::string>& splits)
|
||||
{
|
||||
Path path;
|
||||
for (size_t i = 0; i < splits.size(); i++) {
|
||||
path += splits[i];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
uint64_t file_size() const
|
||||
{
|
||||
#ifdef RTI_WIN32
|
||||
HANDLE file_handle = CreateFile(
|
||||
path_string_.c_str(),
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL);
|
||||
if (file_handle == INVALID_HANDLE_VALUE) {
|
||||
DDSLog_warn(
|
||||
&RTI_LOG_ANY_ss,
|
||||
"Failed to get file size for file: ",
|
||||
path_string_.c_str());
|
||||
return 0;
|
||||
}
|
||||
LARGE_INTEGER size;
|
||||
if (!GetFileSizeEx(file_handle, &size)) {
|
||||
CloseHandle(file_handle);
|
||||
DDSLog_warn(
|
||||
&RTI_LOG_ANY_ss,
|
||||
"Failed to get file size for file: ",
|
||||
path_string_.c_str());
|
||||
return 0;
|
||||
}
|
||||
CloseHandle(file_handle);
|
||||
return size.QuadPart;
|
||||
#else
|
||||
struct stat buffer;
|
||||
if (stat(path_string_.c_str(), &buffer) != 0) {
|
||||
DDSLog_warn(
|
||||
&RTI_LOG_ANY_ss,
|
||||
"Failed to get file size for file: ",
|
||||
path_string_.c_str());
|
||||
return 0;
|
||||
}
|
||||
return (uint64_t) buffer.st_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(RTI_INTY)
|
||||
/*
|
||||
* CORE-10727 [Antonio L]:
|
||||
* cwd() is not supported in all the architectures. We have to
|
||||
* port it.
|
||||
*/
|
||||
static Path cwd()
|
||||
{
|
||||
char working_directory[RTI_OSAPI_STRING_SEQ_STRING_MAX_SIZE] = { 0 };
|
||||
bool no_error = false;
|
||||
|
||||
no_error =
|
||||
(RTIOsapiUtility_getCurrentDirectory(
|
||||
working_directory,
|
||||
RTI_OSAPI_STRING_SEQ_STRING_MAX_SIZE)
|
||||
!= NULL);
|
||||
if (!no_error) {
|
||||
int errnum = RTIOsapiUtility_getError();
|
||||
char error_string[RTI_OSAPI_ERROR_STRING_MAX_SIZE] = { 0 };
|
||||
RTI_THROW_SERVICE_EXCEPTION(
|
||||
&RTI_LOG_OS_FAILURE_sXs,
|
||||
"getcwd",
|
||||
errnum,
|
||||
RTIOsapiUtility_getErrorString(
|
||||
error_string,
|
||||
RTI_OSAPI_ERROR_STRING_MAX_SIZE,
|
||||
errnum));
|
||||
}
|
||||
return Path(working_directory);
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::string path_string_;
|
||||
|
||||
#define SEPARATOR_STR "/"
|
||||
|
||||
/**
|
||||
* Converter Windows-style separators into Unix-style ones
|
||||
*/
|
||||
std::string normalize(const std::string& path)
|
||||
{
|
||||
std::string normalized(path);
|
||||
const std::string SEPARATOR(SEPARATOR_STR);
|
||||
std::string::size_type pos = normalized.find_first_of('\\');
|
||||
while (pos != std::string::npos) {
|
||||
normalized = normalized.replace(pos, 1, SEPARATOR);
|
||||
pos = normalized.find_first_of('\\', pos + 1);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
std::string remove_trailing_separators(const std::string& path)
|
||||
{
|
||||
std::string modified(normalize(path));
|
||||
if (!modified.empty() && modified.at(modified.size() - 1) == '/') {
|
||||
modified.erase(modified.size() - 1);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
std::string remove_double_separators(std::string& path)
|
||||
{
|
||||
const std::string DOUBLE_SEPARATOR_STR("//");
|
||||
std::string modified(normalize(path));
|
||||
std::string::size_type pos = modified.find(DOUBLE_SEPARATOR_STR);
|
||||
while (pos != std::string::npos) {
|
||||
modified = modified.replace(
|
||||
pos,
|
||||
DOUBLE_SEPARATOR_STR.size(),
|
||||
SEPARATOR_STR);
|
||||
pos = modified.find(DOUBLE_SEPARATOR_STR, pos + 1);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
static int remove_file(
|
||||
const char *fpath,
|
||||
const struct stat *sb,
|
||||
int tflag,
|
||||
struct FTW *ftwbuf)
|
||||
{
|
||||
RTIOsapiUtility_unusedParameter(sb);
|
||||
RTIOsapiUtility_unusedParameter(tflag);
|
||||
RTIOsapiUtility_unusedParameter(ftwbuf);
|
||||
|
||||
#ifdef RTI_QNX
|
||||
/**
|
||||
* In QNX we create the working directory in the NFS. Because the NFS
|
||||
* have important delays, if we try to remove all the directories,
|
||||
* the remove will fail because the NFS will accept the request to
|
||||
* remove the files, but they won't be removed once we reach the upper
|
||||
* level. Therefore, we will not remove the directories of the QNX
|
||||
* testers
|
||||
*/
|
||||
struct stat buf;
|
||||
int err = stat(fpath, &buf);
|
||||
errno = 0;
|
||||
if (err == 0 && (buf.st_mode & _S_IFMT) != S_IFDIR)
|
||||
errno = remove(fpath);
|
||||
return errno;
|
||||
#else
|
||||
return remove(fpath);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
}}} // namespace rti::apputils::util
|
||||
|
||||
#endif /* HPP_APPUTILS_PATH_HPP_ */
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* (c) Copyright, Real-Time Innovations, 2016-.
|
||||
* 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 APPUTILS_HPP_
|
||||
#define APPUTILS_HPP_
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
#include "osapi/osapi_utility.h"
|
||||
#include <dds/core/types.hpp>
|
||||
|
||||
#include <rti/core/Exception.hpp>
|
||||
#include "dds/core/Time.hpp"
|
||||
|
||||
|
||||
#define STATIC_CONST_STRING_DEFINITION(classname, name, value) \
|
||||
const std::string& classname::name() \
|
||||
{ \
|
||||
static std::string name##var(value); \
|
||||
return name##var; \
|
||||
}
|
||||
|
||||
#define RECORDER_FILE_PATH_MAX_LENGTH (4096)
|
||||
|
||||
namespace rti { namespace apputils { namespace util {
|
||||
|
||||
constexpr int64_t g_int64_max = 0x7FFFFFFFFFFFFFFFULL;
|
||||
constexpr uint64_t g_uint64_max = 0xFFFFFFFFFFFFFFFFULL;
|
||||
|
||||
inline
|
||||
std::string normalize_path(const std::string& file_name)
|
||||
{
|
||||
char normalized[RECORDER_FILE_PATH_MAX_LENGTH] = {'\0'};
|
||||
if (!RTIOsapiUtility_normalizePath(
|
||||
normalized,
|
||||
RECORDER_FILE_PATH_MAX_LENGTH,
|
||||
file_name.c_str())) {
|
||||
std::stringstream error_msg;
|
||||
error_msg << "!normalize path " << file_name.c_str() << "\n";
|
||||
throw dds::core::IllegalOperationError(error_msg.str());
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
inline
|
||||
int64_t convert_uint64_into_int64(uint64_t value)
|
||||
{
|
||||
int64_t signed_int64 = 0;
|
||||
if (value > g_int64_max) {
|
||||
signed_int64 = g_int64_max;
|
||||
} else {
|
||||
signed_int64 = (int64_t) value;
|
||||
}
|
||||
|
||||
return signed_int64;
|
||||
}
|
||||
|
||||
inline
|
||||
uint64_t to_nanosecs(dds::core::Time time)
|
||||
{
|
||||
uint64_t nanosec;
|
||||
try {
|
||||
nanosec = time.to_nanosecs();
|
||||
} catch (std::overflow_error&) {
|
||||
nanosec = g_uint64_max;
|
||||
}
|
||||
|
||||
return nanosec;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if the sum of two values would produce an overflow.
|
||||
*
|
||||
* @tparam T type of the elements to be summed.
|
||||
*
|
||||
* @param lhs Left-hand side of the sum
|
||||
* @param rhs Right-hand side of the sum
|
||||
*
|
||||
* @return true if the sum overflows, false otherwise.
|
||||
*/
|
||||
template <typename T>
|
||||
bool sum_overflows(T lhs, T rhs)
|
||||
{
|
||||
return lhs > 0 && rhs > 0 && (std::numeric_limits<T>::max)() - lhs < rhs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if the sum of two values would produce an underflow.
|
||||
*
|
||||
* @tparam T type of the elements to be summed.
|
||||
*
|
||||
* @param lhs Left-hand side of the sum
|
||||
* @param rhs Right-hand side of the sum
|
||||
*
|
||||
* @return true if the sum underflows, false otherwise.
|
||||
*/
|
||||
template <typename T>
|
||||
bool sum_underflows(T lhs, T rhs)
|
||||
{
|
||||
return lhs < 0 && rhs < 0 && std::numeric_limits<T>::lowest() - lhs > rhs;
|
||||
}
|
||||
|
||||
} } } // namespace rti:apputils::util
|
||||
|
||||
#endif /* APPUTILS_HPP_ */
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
(c) Copyright, Real-Time Innovations, 2018.
|
||||
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 HPP_RTI_APPUTILS_VERSIONABLECOMPONENT_HPP_
|
||||
#define HPP_RTI_APPUTILS_VERSIONABLECOMPONENT_HPP_
|
||||
|
||||
// IMPORTANT: macros.hpp must be the first RTI header included in every header
|
||||
// file so that symbols are exported correctly on Windows
|
||||
#include <dds/core/macros.hpp>
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include "dds/core/Exception.hpp"
|
||||
|
||||
namespace rti { namespace apputils { namespace util {
|
||||
|
||||
class ComponentVersion {
|
||||
public:
|
||||
constexpr ComponentVersion() noexcept :
|
||||
major_(0),
|
||||
minor_(0)
|
||||
{ }
|
||||
|
||||
constexpr ComponentVersion(const uint8_t major, const uint8_t minor) noexcept :
|
||||
major_(major),
|
||||
minor_(minor)
|
||||
{ }
|
||||
|
||||
ComponentVersion(const std::string& version_str)
|
||||
{
|
||||
std::istringstream stream(version_str);
|
||||
int16_t value;
|
||||
|
||||
stream >> value;
|
||||
if (stream.fail()) {
|
||||
throw dds::core::InvalidArgumentError(version_string_error(version_str));
|
||||
}
|
||||
major_ = static_cast<uint8_t>(value);
|
||||
|
||||
char separator;
|
||||
stream >> separator;
|
||||
if (stream.fail() || separator != '.') {
|
||||
throw dds::core::InvalidArgumentError(version_string_error(version_str));
|
||||
}
|
||||
|
||||
stream >> value;
|
||||
if (stream.fail()) {
|
||||
throw dds::core::InvalidArgumentError(version_string_error(version_str));
|
||||
}
|
||||
minor_ = static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
uint8_t get_major() const noexcept { return major_; }
|
||||
uint8_t get_minor() const noexcept { return minor_; }
|
||||
|
||||
bool operator ==(const ComponentVersion& other) const noexcept
|
||||
{
|
||||
return (major_ == other.major_ && minor_ == other.minor_);
|
||||
}
|
||||
|
||||
bool operator !=(const ComponentVersion& other) const noexcept
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool operator < (const ComponentVersion& other) const noexcept
|
||||
{
|
||||
if ((int16_t) major_ - (int16_t) other.major_ == 0) {
|
||||
return ((int16_t) minor_ - (int16_t) other.minor_ < 0);
|
||||
} else {
|
||||
return ((int16_t) major_ - (int16_t) other.major_ < 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool operator > (const ComponentVersion& other) const noexcept
|
||||
{
|
||||
if ((int16_t) major_ - (int16_t) other.major_ == 0) {
|
||||
return ((int16_t) minor_ - (int16_t) other.minor_ > 0);
|
||||
} else {
|
||||
return ((int16_t) major_ - (int16_t) other.major_ > 0);
|
||||
}
|
||||
}
|
||||
|
||||
operator std::string () const noexcept
|
||||
{
|
||||
return std::to_string(major_) + "." + std::to_string(minor_);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t major_;
|
||||
uint8_t minor_;
|
||||
|
||||
std::string version_string_error (const std::string & str) const noexcept
|
||||
{
|
||||
return "VersionableComponent: invalid version string: " + str;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A versionable component has a name and a version and it's used to
|
||||
* keep track of the versions of the components used to record data.
|
||||
* This information is kept in the 'metadata' database file. It can later
|
||||
* be recovered by Replay/Converter to use exactly the same components'
|
||||
* versions that were when recording.
|
||||
* @param VersionType The version object to be used. By default, the Version
|
||||
* type implemented above is used (a simple, two-digit, stringifyable
|
||||
* version type).
|
||||
*/
|
||||
template<typename VersionType>
|
||||
class VersionableComponent {
|
||||
public:
|
||||
|
||||
VersionableComponent(
|
||||
std::string component_name,
|
||||
const VersionType& version) :
|
||||
component_name_(component_name),
|
||||
version_(version) { }
|
||||
|
||||
const VersionType& version() const { return version_; }
|
||||
|
||||
const std::string& component_name() const { return component_name_; }
|
||||
|
||||
void verify_version(const VersionType& version) const
|
||||
{
|
||||
if (version > version_) {
|
||||
std::stringstream error_stream;
|
||||
error_stream << "VersionableComponent: " << component_name_.c_str()
|
||||
<< ": detected newer version. The current version of the "
|
||||
<< "component in this tool ("
|
||||
<< ((const std::string&) version_).c_str()
|
||||
<< ") is unable to read the newer "
|
||||
<< component_name_.c_str() <<" version ("
|
||||
<< ((const std::string&) version).c_str() <<") found ";
|
||||
throw dds::core::IllegalOperationError(error_stream.str());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::string component_name_;
|
||||
VersionType version_;
|
||||
};
|
||||
|
||||
typedef VersionableComponent<ComponentVersion> DefaultVersionableComponent;
|
||||
|
||||
} } } /* namespace rti::apputils::util */
|
||||
|
||||
#endif /* HPP_RTI_APPUTILS_VERSIONABLECOMPONENT_HPP_ */
|
||||
Reference in new issue
Block a user