Skip to content

Commit

Permalink
Allow to use an HTTP proxy
Browse files Browse the repository at this point in the history
Abstract SSL or plain mode of a ClientSession
  • Loading branch information
philippeVerney committed Feb 22, 2024
1 parent 1333d49 commit bd8cc2c
Show file tree
Hide file tree
Showing 17 changed files with 626 additions and 537 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- dependencies
- The following compilers are known to work (used in CI)
- gcc from version 4.8
- visual studio from version 2017
- visual studio from version 2019
# Prepare the dependencies
Download (build and install if necessary) third party libraries:
- BOOST : All versions from version 1.66 should be ok but you may experience some [min/max build issues](https://github.com/boostorg/beast/issues/1980) using version 1.72 or 1.73.
Expand Down
67 changes: 36 additions & 31 deletions cmake/swigEtp1_2Include.i.in
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,12 @@ typedef long long time_t;
%nspace ETP_NS::TransactionHandlers;
%nspace ETP_NS::DataspaceHandlers;
%nspace ETP_NS::AbstractSession;
%nspace ETP_NS::PlainClientSession;
%nspace ETP_NS::ClientSession;
%nspace ETP_NS::InitializationParameters;
#ifdef WITH_FESAPI
%nspace ETP_NS::FesapiHdfProxyFactory;
#endif
#ifdef WITH_ETP_SSL
%nspace ETP_NS::SslClientSession;
#endif


%nspace Energistics::Etp::v12::Datatypes::SupportedDataObject;
%nspace Energistics::Etp::v12::Datatypes::Uuid;
%nspace Energistics::Etp::v12::Datatypes::Version;
Expand Down Expand Up @@ -1057,10 +1054,7 @@ namespace Energistics {
%shared_ptr(ETP_NS::TransactionHandlers)
%shared_ptr(ETP_NS::DataspaceHandlers)
%shared_ptr(ETP_NS::AbstractSession)
%shared_ptr(ETP_NS::PlainClientSession)
#ifdef WITH_ETP_SSL
%shared_ptr(ETP_NS::SslClientSession)
#endif
%shared_ptr(ETP_NS::ClientSession)

%feature("director") ETP_NS::CoreHandlers;
%feature("director") ETP_NS::DiscoveryHandlers;
Expand Down Expand Up @@ -1874,8 +1868,8 @@ namespace ETP_NS

/******************* CLIENT ***************************/

%nodefaultctor PlainClientSession;
class PlainClientSession : public AbstractSession
%nodefaultctor ClientSession;
class ClientSession : public AbstractSession
{
public:
/**
Expand All @@ -1885,27 +1879,43 @@ namespace ETP_NS
bool run();
};

#ifdef WITH_ETP_SSL
%nodefaultctor SslClientSession;
class SslClientSession : public AbstractSession
class InitializationParameters
{
public:
/**
* Run the websocket and then the ETP session.
* Everything related to this session (including the completion handlers) will operate on the same unique thread in a single event loop.
* @param instanceUuid The UUID of the client instance.
* @param etpServerUrl Must follow the syntax ws://<host>:<port>/<path> or wss://<host>:<port>/<path> or simply <host>:<port>/<path>
* where port is optional and is defaulted to 80 if scheme is "ws" or if no scheme is provided.
* In "wss" schema cases, port is defaulted to 443.
* @param proxyUrl The proxy URL. It must follow the syntax http://<host>:<port> or simply <host>:<port>.
* Leave it empty if your connection to eptServerUrl is direct and does not pass throughr any proxy.
*/
bool run();
};
#endif

class InitializationParameters
{
public:
InitializationParameters(const std::string& instanceUuid, const std::string & etpUrl);
InitializationParameters(const std::string& instanceUuid,
const std::string& etpServerUrl, const std::string& proxyUrl = "");
InitializationParameters(const std::string& instanceUuid, const std::string & host, unsigned short port, const std::string & urlPath = "");
virtual ~InitializationParameters();

void setMaxWebSocketMessagePayloadSize(int64_t value);
uint64_t getMaxWebSocketMessagePayloadSize() const;

void setPreferredMaxFrameSize(uint64_t value);
uint64_t getPreferredMaxFrameSize() const;

void setAdditionalHandshakeHeaderFields(const std::map<std::string, std::string>& extraHandshakeHeaderFields);
const std::map<std::string, std::string>& getAdditionalHandshakeHeaderFields() const;

void setAdditionalCertificates(const std::string& extraCertificates);
const std::string& getAdditionalCertificates() const;

const std::string& getEtpServerHost() const;
uint16_t getEtpServerPort() const;
const std::string& getEtpServerUrlPath() const;

const std::string& getProxyHost() const;
uint16_t getProxyPort() const;

void setForceTls(bool force);
bool isTlsForced();

virtual std::string getApplicationName() const;
virtual std::string getApplicationVersion() const;
Expand All @@ -1921,13 +1931,8 @@ namespace ETP_NS

namespace ClientSessionLaunchers
{
std::shared_ptr<ETP_NS::PlainClientSession> createWsClientSession(InitializationParameters* initializationParams, const std::string & authorization,
const std::map<std::string, std::string>& additionalHandshakeHeaderFields = {}, std::size_t preferredMaxFrameSize = 4096);

#ifdef WITH_ETP_SSL
std::shared_ptr<ETP_NS::SslClientSession> createWssClientSession(InitializationParameters* initializationParams, const std::string & authorization,
const std::map<std::string, std::string>& additionalHandshakeHeaderFields = {}, std::size_t preferredMaxFrameSize = 4096, const std::string & additionalCertificates = "");
#endif
std::shared_ptr<ETP_NS::ClientSession> createClientSession(InitializationParameters* initializationParams,
const std::string & etpServerAuthorization, const std::string& proxyAuthorization = "");
}

#ifdef WITH_FESAPI
Expand Down
124 changes: 14 additions & 110 deletions src/etp/AbstractClientSession.h → src/etp/AbstractClientSessionCRTP.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,62 +18,18 @@ under the License.
-----------------------------------------------------------------------*/
#pragma once

#include "AbstractSession.h"

#include <thread>

#include <boost/asio/connect.hpp>
#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid_io.hpp>

#include "InitializationParameters.h"
#include "ClientSession.h"

namespace ETP_NS
{
// Echoes back all received WebSocket messages.
// This uses the Curiously Recurring Template Pattern so that the same code works with both SSL streams and regular sockets.
template<class Derived>
class AbstractClientSession : public ETP_NS::AbstractSession
class AbstractClientSessionCRTP : public ETP_NS::ClientSession
{
public:

virtual ~AbstractClientSession() = default;

boost::asio::io_context& getIoContext() {
return ioc;
}

const std::string& getHost() const { return host; }
const std::string& getPort() const { return port; }
const std::string& getTarget() const { return target; }
const std::string& getAuthorization() const { return authorization; }

/**
* Run the websocket and then the ETP session in a processing loop.
* Everything related to this session (including the completion handlers) will operate on the current thread in a single event loop.
* Since this is a loop, you may want to operate this method on a dedicated thread not to block your program.
* This method returns only when the session is closed.
*/
bool run() {
successfulConnection = false;

// Look up the domain name before to run the session
// It is important to do this before to run the io context. Otherwise running the io context would return immediately if nothing has to be done.
resolver.async_resolve(
host,
port,
std::bind(
&AbstractClientSession::on_resolve,
std::static_pointer_cast<AbstractClientSession>(shared_from_this()),
std::placeholders::_1,
std::placeholders::_2));

// Run the io_context to perform the resolver and all other binding functions
// Run will return only when there will no more be any uncomplete operations (such as a reading operation for example)
getIoContext().run();

return successfulConnection;
}
virtual ~AbstractClientSessionCRTP() = default;

void on_connect(boost::system::error_code ec) {
if (ec) {
Expand All @@ -83,26 +39,28 @@ namespace ETP_NS
#if BOOST_VERSION < 107000
// Perform the websocket handshake
derived().ws().async_handshake_ex(responseType,
host + ":" + port, target,
etpServerHost + ":" + etpServerPort, etpServerTarget,
[&](websocket::request_type& m)
{
m.insert(boost::beast::http::field::sec_websocket_protocol, "etp12.energistics.org");
m.insert(boost::beast::http::field::authorization, authorization);
m.insert(boost::beast::http::field::authorization, etpServerAuthorization);
m.insert(boost::beast::http::field::proxy_authorization, proxyAuthorization);
m.insert("etp-encoding", "binary");
for (const auto& mapEntry : additionalHandshakeHeaderFields_) {
m.insert(mapEntry.first, mapEntry.second);
}
},
std::bind(
&AbstractClientSession::on_handshake,
std::static_pointer_cast<AbstractClientSession>(shared_from_this()),
&AbstractClientSessionCRTP::on_handshake,
std::static_pointer_cast<AbstractClientSessionCRTP>(shared_from_this()),
std::placeholders::_1));
#else
derived().ws().set_option(websocket::stream_base::decorator(
[&](websocket::request_type& m)
{
m.insert(boost::beast::http::field::sec_websocket_protocol, "etp12.energistics.org");
m.insert(boost::beast::http::field::authorization, authorization);
m.insert(boost::beast::http::field::authorization, etpServerAuthorization);
m.insert(boost::beast::http::field::proxy_authorization, proxyAuthorization);
m.insert("etp-encoding", "binary");
for (const auto& mapEntry : additionalHandshakeHeaderFields_) {
m.insert(mapEntry.first, mapEntry.second);
Expand All @@ -111,10 +69,10 @@ namespace ETP_NS
);
// Perform the websocket handshake
derived().ws().async_handshake(responseType,
host + ":" + port, target,
etpServerHost + ":" + etpServerPort, etpServerTarget,
std::bind(
&AbstractClientSession::on_handshake,
std::static_pointer_cast<AbstractClientSession>(shared_from_this()),
&AbstractClientSessionCRTP::on_handshake,
std::static_pointer_cast<AbstractClientSessionCRTP>(shared_from_this()),
std::placeholders::_1));
#endif
}
Expand Down Expand Up @@ -147,8 +105,6 @@ namespace ETP_NS
std::placeholders::_1,
std::placeholders::_2));
}

virtual void on_resolve(boost::system::error_code ec, tcp::resolver::results_type results) = 0;

void on_handshake(boost::system::error_code ec)
{
Expand All @@ -175,63 +131,11 @@ namespace ETP_NS
}

protected:
boost::asio::io_context ioc;
tcp::resolver resolver;
std::string host;
std::string port;
std::string target;
std::string authorization;
std::map<std::string, std::string> additionalHandshakeHeaderFields_;
websocket::response_type responseType; // In order to check handshake sec_websocket_protocol
Energistics::Etp::v12::Protocol::Core::RequestSession requestSession;
bool successfulConnection = false;
using ClientSession::ClientSession;

// Access the derived class, this is part of the Curiously Recurring Template Pattern idiom.
Derived& derived() { return static_cast<Derived&>(*this); }

AbstractClientSession() :
ioc(4),
resolver(ioc) {
messageId = 2; // The client side of the connection MUST use ONLY non-zero even-numbered messageIds.
}

/**
* @param initializationParams The initialization parameters of the session including IP host, port, requestedProtocols, supportedDataObjects
* @param target usually "/" but a server can decide to serve etp on a particular target
* @param authorization The HTTP authorization attribute to send to the server. It may be empty if not needed.
*/
AbstractClientSession(
InitializationParameters* initializationParams, const std::string & target, const std::string & authorization) :
ioc(4),
resolver(ioc),
host(initializationParams->getHost()),
port(std::to_string(initializationParams->getPort())),
target(target),
authorization(authorization)
{
messageId = 2; // The client side of the connection MUST use ONLY non-zero even-numbered messageIds.

initializationParams->postSessionCreationOperation(this);

// Build the request session
requestSession.applicationName = initializationParams->getApplicationName();
requestSession.applicationVersion = initializationParams->getApplicationVersion();

std::copy(std::begin(initializationParams->getInstanceId().data), std::end(initializationParams->getInstanceId().data), requestSession.clientInstanceId.array.begin());

requestSession.requestedProtocols = initializationParams->makeSupportedProtocols();
requestSession.supportedDataObjects = initializationParams->makeSupportedDataObjects();
requestSession.supportedFormats.push_back("xml");
requestSession.currentDateTime = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();

auto caps = initializationParams->makeEndpointCapabilities();
if (!caps.empty()) {
requestSession.endpointCapabilities = caps;
}

maxWebSocketMessagePayloadSize = initializationParams->getMaxWebSocketMessagePayloadSize();
}

void do_write() {
const std::lock_guard<std::mutex> specificProtocolHandlersLock(specificProtocolHandlersMutex);
if (sendingQueue.empty()) {
Expand Down
Loading

0 comments on commit bd8cc2c

Please sign in to comment.