diff options
author | Brett Stottlemyer <bstottle@ford.com> | 2018-07-15 20:43:18 -0400 |
---|---|---|
committer | Brett Stottlemyer <bstottle@ford.com> | 2018-08-15 18:54:13 +0000 |
commit | e170cbe42b8f654894e4035c8ddfd6759a034be8 (patch) | |
tree | a0bf0b811aed03836f85df6acfa2414a552f0153 /src | |
parent | 8761c1a282f596a64e0fc624c87added43fb47d8 (diff) |
Support externally generated QIODevices
This adds the APIs that will be necessary to create SSL sockets flexibly
(and outside of QtRO) and pass them in. The integration tests are extended
to show everything works if the tcp/ip connection is created outside QtRO.
The Registry is supported by allowing an "external schema" to be set as the
HostNode's url, which is then used by the registry as the address for any
remoted() source objects. The client Node calls registerExternalSchema()
with a std::function callback that can create the client-side QIODevice
given the registry provided url.
Change-Id: I0f2d0ea270771e096a787134ef87d537769045f6
Reviewed-by: Michael Brasser <michael.brasser@live.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc | 137 | ||||
-rw-r--r-- | src/remoteobjects/doc/src/remoteobjects-index.qdoc | 1 | ||||
-rw-r--r-- | src/remoteobjects/qconnectionfactories.cpp | 38 | ||||
-rw-r--r-- | src/remoteobjects/qconnectionfactories_p.h | 22 | ||||
-rw-r--r-- | src/remoteobjects/qremoteobjectnode.cpp | 148 | ||||
-rw-r--r-- | src/remoteobjects/qremoteobjectnode.h | 14 | ||||
-rw-r--r-- | src/remoteobjects/qremoteobjectnode_p.h | 5 | ||||
-rw-r--r-- | src/remoteobjects/qremoteobjectreplica.cpp | 17 | ||||
-rw-r--r-- | src/remoteobjects/qremoteobjectreplica_p.h | 6 | ||||
-rw-r--r-- | src/remoteobjects/qremoteobjectsource.cpp | 8 | ||||
-rw-r--r-- | src/remoteobjects/qremoteobjectsource_p.h | 8 | ||||
-rw-r--r-- | src/remoteobjects/qremoteobjectsourceio.cpp | 51 | ||||
-rw-r--r-- | src/remoteobjects/qremoteobjectsourceio_p.h | 7 | ||||
-rw-r--r-- | src/remoteobjects/qtremoteobjectglobal.h | 1 |
14 files changed, 404 insertions, 59 deletions
diff --git a/src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc b/src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc new file mode 100644 index 0000000..fdc8e4f --- /dev/null +++ b/src/remoteobjects/doc/src/remoteobjects-external-schemas.qdoc @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ford Motor Company +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! +\page qtremoteobjects-external-schemas.html +\title Qt Remote Objects - External QIODevices +\brief Describes how Qt Remote Objects supports custom QIODevice channels. + +\section1 External QIODevices + +Qt Remote Objects supports several communications channels out-of-the-box, such +as the \l QTcpServer and \l QTcpSocket pair. Given the desired \l QUrl for tcp, +or the desired name (for the \l QLocalServer and \l QLocalSocket pair), the +code needed to listen and connect are boilerplate and handled internally by Qt. +Qt Remote Objects supports other types of \l QIODevice as well, and the \l +QRemoteObjectNode classes provide additional methods to support cases where +custom code is needed. + +A contrived example with TCP/IP is shown below. A more realistic example would +use an SSL connection, which would require configuration of certificates, etc. + +\code + // Create the server and listen outside of QtRO + QTcpServer tcpServer; + tcpServer.listen(QHostAddress(127.0.0.1), 65213); + + // Create the host node. We don't need a hostUrl unless we want to take + // advantage of external schemas (see next example). + QRemoteObjectHost srcNode(); + + // Make sure any connections are handed to QtRO + QObject::connect(&tcpServer, &QTcpServer::newConnection, &srcNode, + [&srcNode, &tcpServer]() { + srcNode.addHostSideConnection(tcpServer.nextPendingConnection()); + }); +\endcode + +The Replica side code needs to manually connect to the Host +\code + QRemoteObjectNode repNode(); + QTcpSocket *socket = new QTcpSocket(&repNode); + connect(socket, &QTcpSocket::connected, &repNode, + [socket, &repNode]() { + repNode.addClientSideConnection(socket); + }); + socket->connectToHost(QHostAddress(127.0.0.1), 65213); + }; +\endcode + +\section1 External Schemas + +It is possible to create each side of the QIODevice and call \l +{QRemoteObjectNode::addClientSideConnection(QIODevice *ioDevice)} and \l +{QRemoteObjectHostBase::addHostSideConnection(QIODevice *ioDevice)} as shown +above. This is fully supported, but requires the client know how to establish +the connection or have a way to discover that information. This is exactly the +problem the registry was designed to solve. + +Qt Remote Objects also allows "External Schemas" to be used with the registry, +which helps with connection setup. On the \l QRemoteObjectHostNode side, the +user must set the hostUrl with the desired schema. + +\code + // Use standard tcp url for the registry + const QUrl registryUrl = QUrl(QStringLiteral("tcp://127.0.0.1:65212")); + // Use "exttcp" for the "external" interface + const QUrl extUrl = QUrl(QStringLiteral("exttcp://127.0.0.1:65213")); + + // Create the server and listen outside of QtRO + QTcpServer tcpServer; + tcpServer.listen(QHostAddress(extUrl.host()), extUrl.port()); + + // We need a registry for everyone to connect to + QRemoteObjectRegistryHost registry(registryUrl); + + // Finally, we create our host node and register "exttcp" as our schema. + // We need the AllowExternalRegistration parameter to prevent the node from + // setting a hostUrlInvalid error. + QRemoteObjectHost srcNode(extUrl, registryUrl, QRemoteObjectHost::AllowExternalRegistration); + // From now on, when we call enableRemoting() from this node, the registry + // will be updated to show the Source object at extUrl. +\endcode + +On the \l Replica side, the \l QRemoteObjectNode needs to register a callback +to be used when the external schema is detected. The callback must be a \l +RemoteObjectSchemaHandler. + +\code + // Use standard tcp url for the registry + const QUrl registryUrl = QUrl(QStringLiteral("tcp://127.0.0.1:65212")); + + // This time create the node connected to the registry + QRemoteObjectNode repNode(registryUrl); + + // Create the RemoteObjectSchemaHandler callback + QRemoteObjectNode::RemoteObjectSchemaHandler setupTcp = [&repNode](QUrl url) { + QTcpSocket *socket = new QTcpSocket(&repNode); + connect(socket, &QTcpSocket::connected, + [socket, &repNode]() { + repNode.addClientSideConnection(socket); + }); + connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QSslSocket::error), + [socket](QAbstractSocket::SocketError error) { + delete socket; + }); + socket->connectToHost(url.host(), url.port()); + }; + + // Once we call registerExternalSchema, the above method will be called + // whenever the registry sees an object we are interested in on "exttcp" + repNode.registerExternalSchema(QStringLiteral("exttcp"), setupTcp); +\endcode +*/ diff --git a/src/remoteobjects/doc/src/remoteobjects-index.qdoc b/src/remoteobjects/doc/src/remoteobjects-index.qdoc index d483271..7b008bf 100644 --- a/src/remoteobjects/doc/src/remoteobjects-index.qdoc +++ b/src/remoteobjects/doc/src/remoteobjects-index.qdoc @@ -96,6 +96,7 @@ See \l{Qt Licensing} for further details. \li \l {Source Objects}{Qt Remote Objects Source Objects} \li \l {Replica Objects}{Qt Remote Objects Replica Objects} \li \l {Qt Remote Objects Registry} + \li \l {Qt Remote Objects - External QIODevices} \li \l {Qt Remote Objects Compiler} \li \l {Remote Object Interaction} \li \l {Using Qt Remote Objects} diff --git a/src/remoteobjects/qconnectionfactories.cpp b/src/remoteobjects/qconnectionfactories.cpp index 317d2fa..0ca2fa8 100644 --- a/src/remoteobjects/qconnectionfactories.cpp +++ b/src/remoteobjects/qconnectionfactories.cpp @@ -79,13 +79,14 @@ inline bool fromDataStream(QDataStream &in, QRemoteObjectPacketTypeEnum &type, Q case Ping: type = Ping; break; case Pong: type = Pong; break; default: - qCWarning(QT_REMOTEOBJECT_IO) << "Invalid packet received" << type; + qCWarning(QT_REMOTEOBJECT_IO) << "Invalid packet received" << _type; } if (type == Invalid) return false; if (type == ObjectList) return true; in >> name; + qCDebug(QT_REMOTEOBJECT_IO) << "Packet received of type" << type << "for object" << name; return true; } @@ -225,6 +226,41 @@ ServerIoDevice *QConnectionAbstractServer::nextPendingConnection() return iodevice; } +ExternalIoDevice::ExternalIoDevice(QIODevice *device, QObject *parent) + : IoDeviceBase(parent) + , m_device(device) +{ + initializeDataStream(); + connect(m_device, &QIODevice::aboutToClose, this, [this]() { this->m_isClosing = true; }); + connect(m_device, &QIODevice::readyRead, this, &ExternalIoDevice::readyRead); + auto meta = device->metaObject(); + if (-1 == meta->indexOfSignal(SIGNAL(disconnected()))) + connect(m_device, SIGNAL(disconnected()), this, SIGNAL(disconnected())); +} + +QIODevice *ExternalIoDevice::connection() const +{ + return m_device; +} + +bool ExternalIoDevice::isOpen() const +{ + if (!m_device) + return false; + return m_device->isOpen() && IoDeviceBase::isOpen(); +} + +void ExternalIoDevice::doClose() +{ + if (isOpen()) + m_device->close(); +} + +QString ExternalIoDevice::deviceType() const +{ + return QStringLiteral("ExternalIoDevice"); +} + /*! \class QtROServerFactory \inmodule QtRemoteObjects diff --git a/src/remoteobjects/qconnectionfactories_p.h b/src/remoteobjects/qconnectionfactories_p.h index 56283be..84bc917 100644 --- a/src/remoteobjects/qconnectionfactories_p.h +++ b/src/remoteobjects/qconnectionfactories_p.h @@ -53,6 +53,8 @@ #include <QAbstractSocket> #include <QDataStream> +#include <QIODevice> +#include <QPointer> #include <QtRemoteObjects/qtremoteobjectglobal.h> @@ -91,6 +93,7 @@ public: Q_SIGNALS: void readyRead(); + void disconnected(); protected: virtual QString deviceType() const = 0; @@ -111,9 +114,6 @@ class Q_REMOTEOBJECTS_EXPORT ServerIoDevice : public IoDeviceBase public: explicit ServerIoDevice(QObject *parent = nullptr); -Q_SIGNALS: - void disconnected(); - protected: QString deviceType() const override; }; @@ -156,7 +156,6 @@ public: QUrl url() const; Q_SIGNALS: - void disconnected(); void shouldReconnect(ClientIoDevice*); protected: @@ -169,6 +168,21 @@ private: QUrl m_url; }; +class ExternalIoDevice : public IoDeviceBase +{ + Q_OBJECT + +public: + explicit ExternalIoDevice(QIODevice *device, QObject *parent=nullptr); + QIODevice *connection() const override; + bool isOpen() const override; + +protected: + void doClose() override; + QString deviceType() const override; + QPointer<QIODevice> m_device; +}; + class QtROServerFactory { public: diff --git a/src/remoteobjects/qremoteobjectnode.cpp b/src/remoteobjects/qremoteobjectnode.cpp index 5173c45..2dd7086 100644 --- a/src/remoteobjects/qremoteobjectnode.cpp +++ b/src/remoteobjects/qremoteobjectnode.cpp @@ -163,6 +163,44 @@ void QRemoteObjectNode::setHeartbeatInterval(int interval) } /*! + \since 5.12 + \typedef QRemoteObjectNode::RemoteObjectSchemaHandler + + Typedef for a std::function method that can take a QUrl input and is + responsible for creating the communications channel between this node and + the node hosting the desired \l Source. As some types of QIODevices (e.g., + QSslSocket) require additional steps before the device is ready for use, + the method is responsible for calling \l addClientSideConnection once the + connection is fully established. +*/ + +/*! + \since 5.12 + \brief Provide a custom method to handle externally provided schemas + + This method is tied to the \l Registry and \l {External Schemas}. By + registering a std::function handler for an external schema, the registered + method will be called when the registry is notified of a \l Source you've + acquired being available. Without this registration, QtRO would only be + able to handle the "built-in" schemas. + + The provided method, \a handler, will be called when the registry sees a \l + Source object on a new (not yet connected) Node with a {QUrl::schema()} of + \a schema. The \a handler, of type \l + QRemoteObjectNode::RemoteObjectSchemaHandler will get the \l QUrl of the + Node providing the \l Source as an input parameter, and is responsible for + establishing the communications channel (a \l QIODevice of some sort) and + calling \l addClientSideConnection with it. + + \sa RemoteObjectSchemaHandler +*/ +void QRemoteObjectNode::registerExternalSchema(const QString &schema, QRemoteObjectNode::RemoteObjectSchemaHandler handler) +{ + Q_D(QRemoteObjectNode); + d->schemaHandlers.insert(schema, handler); +} + +/*! \since 5.11 \brief Forward Remote Objects from another network @@ -642,21 +680,26 @@ bool QRemoteObjectNodePrivate::initConnection(const QUrl &address) requestedUrls.insert(address); + if (schemaHandlers.contains(address.scheme())) { + schemaHandlers[address.scheme()](address); + return true; + } + ClientIoDevice *connection = QtROClientFactory::instance()->create(address, q); if (!connection) { qROPrivWarning() << "Could not create ClientIoDevice for client. Invalid url/scheme provided?" << address; return false; } - qROPrivDebug() << "Opening connection to" << address.toString(); qROPrivDebug() << "Replica Connection isValid" << connection->isOpen(); QObject::connect(connection, &ClientIoDevice::shouldReconnect, q, [this, connection]() { onShouldReconnect(connection); }); - QObject::connect(connection, &ClientIoDevice::readyRead, q, [this, connection]() { + QObject::connect(connection, &IoDeviceBase::readyRead, q, [this, connection]() { onClientRead(connection); }); connection->connectToServer(); + return true; } @@ -791,7 +834,7 @@ void QRemoteObjectNodePrivate::handleReplicaConnection(const QString &name) } } -void QRemoteObjectNodePrivate::handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, ClientIoDevice *connection) +void QRemoteObjectNodePrivate::handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, IoDeviceBase *connection) { if (!checkSignatures(rep->m_objectSignature, sourceSignature)) { qROPrivWarning() << "Signature mismatch for" << rep->m_metaObject->className() << (rep->m_objectName.isEmpty() ? QLatin1String("(unnamed)") : rep->m_objectName); @@ -820,7 +863,7 @@ QReplicaImplementationInterface *QRemoteObjectHostBasePrivate::handleNewAcquire( void QRemoteObjectNodePrivate::onClientRead(QObject *obj) { using namespace QRemoteObjectPackets; - ClientIoDevice *connection = qobject_cast<ClientIoDevice*>(obj); + IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(obj); QRemoteObjectPacketTypeEnum packetType; Q_ASSERT(connection); @@ -913,7 +956,7 @@ void QRemoteObjectNodePrivate::onClientRead(QObject *obj) qROPrivDebug() << "RemoveObject-->" << rxName << this; connectedSources.remove(rxName); connection->removeSource(rxName); - if (replicas.contains(rxName)) { //We have a replica waiting on this remoteObject + if (replicas.contains(rxName)) { //We have a replica using the removed source QSharedPointer<QConnectedReplicaImplementation> rep = qSharedPointerCast<QConnectedReplicaImplementation>(replicas.value(rxName).toStrongRef()); if (rep && !rep->connectionToSource.isNull()) { rep->connectionToSource.clear(); @@ -1105,6 +1148,26 @@ void QRemoteObjectNodePrivate::onClientRead(QObject *obj) */ /*! + \enum QRemoteObjectHostBase::AllowedSchemas + + This enum is used to specify whether a Node will accept a url with an + unrecognized schema for the hostUrl. By default only urls with known + schemas are accepted, but using \c AllowExternalRegistration will enable + the \l Registry to pass your external (to QtRO) url to client Nodes. + + \value BuiltInSchemasOnly Only allow the hostUrl to be set to a QtRO + supported schema. This is the default value, and causes a Node error to be + set if an unrecognized schema is provided. + \value AllowExternalRegistration The provided schema is registered as an + \l {External Schemas} {External Schema} + + \sa QRemoteObjectHost(const QUrl &address, const QUrl ®istryAddress = + QUrl(), AllowedSchemas allowedSchemas=BuiltInSchemasOnly, QObject *parent = + nullptr), setHostUrl(const QUrl &hostAddress, AllowedSchemas + allowedSchemas=BuiltInSchemasOnly) +*/ + +/*! \fn ObjectType *QRemoteObjectNode::acquire(const QString &name) Returns a pointer to a Replica of type ObjectType (which is a template @@ -1217,15 +1280,20 @@ QRemoteObjectHost::QRemoteObjectHost(QObject *parent) Constructs a new QRemoteObjectHost Node (i.e., a Node that supports exposing \l Source objects on the QtRO network) with address \a address. If set, \a registryAddress will be used to connect to the \l - QRemoteObjectRegistry at the provided address. + QRemoteObjectRegistry at the provided address. The \a allowedSchemas + parameter is only needed (and should be set to \l + {QRemoteObjectHostBase::AllowExternalRegistration} + {AllowExternalRegistration}) if the schema of the url should be used as an + \l {External Schemas} {External Schema} by the registry. \sa setHostUrl(), setRegistryUrl() */ -QRemoteObjectHost::QRemoteObjectHost(const QUrl &address, const QUrl ®istryAddress, QObject *parent) +QRemoteObjectHost::QRemoteObjectHost(const QUrl &address, const QUrl ®istryAddress, + AllowedSchemas allowedSchemas, QObject *parent) : QRemoteObjectHostBase(*new QRemoteObjectHostPrivate, parent) { if (!address.isEmpty()) { - if (!setHostUrl(address)) + if (!setHostUrl(address, allowedSchemas)) return; } @@ -1325,7 +1393,7 @@ QUrl QRemoteObjectHostBase::hostUrl() const \internal The HostBase version of this method is protected so the method isn't exposed on RegistryHost nodes. */ -bool QRemoteObjectHostBase::setHostUrl(const QUrl &hostAddress) +bool QRemoteObjectHostBase::setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas) { Q_D(QRemoteObjectHostBase); if (d->remoteObjectIo) { @@ -1333,13 +1401,11 @@ bool QRemoteObjectHostBase::setHostUrl(const QUrl &hostAddress) return false; } - d->remoteObjectIo = new QRemoteObjectSourceIo(hostAddress, this); - if (d->remoteObjectIo->m_server.isNull()) { //Invalid url/scheme + if (allowedSchemas == AllowedSchemas::BuiltInSchemasOnly && !QtROServerFactory::instance()->isValid(hostAddress)) { d->setLastError(HostUrlInvalid); - delete d->remoteObjectIo; - d->remoteObjectIo = 0; return false; } + d->remoteObjectIo = new QRemoteObjectSourceIo(hostAddress, this); //If we've given a name to the node, set it on the sourceIo as well if (!objectName().isEmpty()) @@ -1368,10 +1434,15 @@ QUrl QRemoteObjectHost::hostUrl() const Sets the \a hostAddress for a host QRemoteObjectNode. Returns \c true if the Host address is set, otherwise \c false. + + The \a allowedSchemas parameter is only needed (and should be set to \l + {QRemoteObjectHostBase::AllowExternalRegistration} + {AllowExternalRegistration}) if the schema of the url should be used as an + \l {External Schemas} {External Schema} by the registry. */ -bool QRemoteObjectHost::setHostUrl(const QUrl &hostAddress) +bool QRemoteObjectHost::setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas) { - return QRemoteObjectHostBase::setHostUrl(hostAddress); + return QRemoteObjectHostBase::setHostUrl(hostAddress, allowedSchemas); } /*! @@ -1568,6 +1639,30 @@ bool QRemoteObjectNode::connectToNode(const QUrl &address) } /*! + \since 5.12 + + In order to \l QRemoteObjectNode::acquire() \l Replica objects over \l + {External QIODevices}, Qt Remote Objects needs access to the communications + channel (a \l QIODEvice) between the respective nodes. It is the + addClientSideConnection() call that enables this, taking the \a ioDevice as + input. Any acquire() call made without calling addClientSideConnection will + still work, but the Node will not be able to initialize the \l Replica + without being provided the connection to the Host node. + + \sa {QRemoteObjectHostBase::addHostSideConnection} +*/ +void QRemoteObjectNode::addClientSideConnection(QIODevice *ioDevice) +{ + Q_D(QRemoteObjectNode); + ExternalIoDevice *device = new ExternalIoDevice(ioDevice, this); + connect(device, &IoDeviceBase::readyRead, this, [d, device]() { + d->onClientRead(device); + }); + if (device->bytesAvailable()) + d->onClientRead(device); +} + +/*! \fn void QRemoteObjectNode::remoteObjectAdded(const QRemoteObjectSourceLocation &loc) This signal is emitted whenever a new \l {Source} object is added to @@ -1784,6 +1879,29 @@ bool QRemoteObjectHostBase::disableRemoting(QObject *remoteObject) } /*! + \since 5.12 + + In order to \l QRemoteObjectHost::enableRemoting() \l Source objects over + \l {External QIODevices}, Qt Remote Objects needs access to the + communications channel (a \l QIODEvice) between the respective nodes. It is + the addHostSideConnection() call that enables this on the \l Source side, + taking the \a ioDevice as input. Any enableRemoting() call will still work + without calling addHostSideConnection, but the Node will not be able to + share the \l Source objects without being provided the connection to the + Replica node. + + \sa addClientSideConnection +*/ +void QRemoteObjectHostBase::addHostSideConnection(QIODevice *ioDevice) +{ + Q_D(QRemoteObjectHostBase); + if (!d->remoteObjectIo) + d->remoteObjectIo = new QRemoteObjectSourceIo(this); + ExternalIoDevice *device = new ExternalIoDevice(ioDevice, this); + return d->remoteObjectIo->newConnection(device); +} + +/*! Returns a pointer to a Replica which is specifically derived from \l QAbstractItemModel. The \a name provided must match the name used with the matching \l {QRemoteObjectHostBase::enableRemoting} {enableRemoting} that put diff --git a/src/remoteobjects/qremoteobjectnode.h b/src/remoteobjects/qremoteobjectnode.h index a8c4d8f..35d0d9d 100644 --- a/src/remoteobjects/qremoteobjectnode.h +++ b/src/remoteobjects/qremoteobjectnode.h @@ -105,6 +105,7 @@ public: ~QRemoteObjectNode() override; Q_INVOKABLE bool connectToNode(const QUrl &address); + void addClientSideConnection(QIODevice *ioDevice); virtual void setName(const QString &name); template < class ObjectType > ObjectType *acquire(const QString &name = QString()) @@ -140,6 +141,9 @@ public: int heartbeatInterval() const; void setHeartbeatInterval(int interval); + typedef std::function<void (QUrl)> RemoteObjectSchemaHandler; + void registerExternalSchema(const QString &schema, RemoteObjectSchemaHandler handler); + Q_SIGNALS: void remoteObjectAdded(const QRemoteObjectSourceLocation &); void remoteObjectRemoved(const QRemoteObjectSourceLocation &); @@ -165,6 +169,8 @@ class Q_REMOTEOBJECTS_EXPORT QRemoteObjectHostBase : public QRemoteObjectNode { Q_OBJECT public: + enum AllowedSchemas { BuiltInSchemasOnly, AllowExternalRegistration }; + Q_ENUM(AllowedSchemas) ~QRemoteObjectHostBase() override; void setName(const QString &name) override; @@ -177,6 +183,7 @@ public: bool enableRemoting(QObject *object, const QString &name = QString()); bool enableRemoting(QAbstractItemModel *model, const QString &name, const QVector<int> roles, QItemSelectionModel *selectionModel = nullptr); bool disableRemoting(QObject *remoteObject); + void addHostSideConnection(QIODevice *ioDevice); typedef std::function<bool(const QString &, const QString &)> RemoteObjectNameFilter; bool proxy(const QUrl ®istryUrl, const QUrl &hostUrl={}, @@ -185,7 +192,7 @@ public: protected: virtual QUrl hostUrl() const; - virtual bool setHostUrl(const QUrl &hostAddress); + virtual bool setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas=BuiltInSchemasOnly); QRemoteObjectHostBase(QRemoteObjectHostBasePrivate &, QObject *); private: @@ -198,11 +205,12 @@ class Q_REMOTEOBJECTS_EXPORT QRemoteObjectHost : public QRemoteObjectHostBase Q_OBJECT public: QRemoteObjectHost(QObject *parent = nullptr); - QRemoteObjectHost(const QUrl &address, const QUrl ®istryAddress = QUrl(), QObject *parent = nullptr); + QRemoteObjectHost(const QUrl &address, const QUrl ®istryAddress = QUrl(), + AllowedSchemas allowedSchemas=BuiltInSchemasOnly, QObject *parent = nullptr); QRemoteObjectHost(const QUrl &address, QObject *parent); ~QRemoteObjectHost() override; QUrl hostUrl() const override; - bool setHostUrl(const QUrl &hostAddress) override; + bool setHostUrl(const QUrl &hostAddress, AllowedSchemas allowedSchemas=BuiltInSchemasOnly) override; protected: QRemoteObjectHost(QRemoteObjectHostPrivate &, QObject *); diff --git a/src/remoteobjects/qremoteobjectnode_p.h b/src/remoteobjects/qremoteobjectnode_p.h index efcf936..1a8ebb6 100644 --- a/src/remoteobjects/qremoteobjectnode_p.h +++ b/src/remoteobjects/qremoteobjectnode_p.h @@ -154,7 +154,7 @@ public: virtual QReplicaImplementationInterface *handleNewAcquire(const QMetaObject *meta, QRemoteObjectReplica *instance, const QString &name); void handleReplicaConnection(const QString &name); - void handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, ClientIoDevice *connection); + void handleReplicaConnection(const QByteArray &sourceSignature, QConnectedReplicaImplementation *rep, IoDeviceBase *connection); void initialize(); private: bool checkSignatures(const QByteArray &a, const QByteArray &b); @@ -162,7 +162,7 @@ private: public: struct SourceInfo { - ClientIoDevice* device; + IoDeviceBase* device; QString typeName; QByteArray objectSignature; }; @@ -171,6 +171,7 @@ public: QUrl registryAddress; QHash<QString, QWeakPointer<QReplicaImplementationInterface> > replicas; QMap<QString, SourceInfo> connectedSources; + QMap<QString, QRemoteObjectNode::RemoteObjectSchemaHandler> schemaHandlers; QSet<ClientIoDevice*> pendingReconnect; QSet<QUrl> requestedUrls; QRemoteObjectRegistry *registry; diff --git a/src/remoteobjects/qremoteobjectreplica.cpp b/src/remoteobjects/qremoteobjectreplica.cpp index 6cfec3c..dc2f926 100644 --- a/src/remoteobjects/qremoteobjectreplica.cpp +++ b/src/remoteobjects/qremoteobjectreplica.cpp @@ -104,16 +104,23 @@ QConnectedReplicaImplementation::QConnectedReplicaImplementation(const QString & m_heartbeatTimer.start(); }); connect(&m_heartbeatTimer, &QTimer::timeout, this, [this] { + // TODO: Revisit if a baseclass method can be used to avoid specialized cast + // conditional logic. + auto clientIo = qobject_cast<ClientIoDevice *>(connectionToSource); if (m_pendingCalls.contains(0)) { // The source didn't respond in time, disconnect the connection - if (connectionToSource) - connectionToSource->disconnectFromServer(); + if (clientIo) + clientIo->disconnectFromServer(); + else if (connectionToSource) + connectionToSource->close(); } else { serializePingPacket(m_packet, m_objectName); if (sendCommandWithReply(0).d->serialId == -1) { m_heartbeatTimer.stop(); - if (connectionToSource) - connectionToSource->disconnectFromServer(); + if (clientIo) + clientIo->disconnectFromServer(); + else if (connectionToSource) + connectionToSource->close(); } } }); @@ -449,7 +456,7 @@ void QConnectedReplicaImplementation::setProperty(int i, const QVariant &prop) m_propertyStorage[i] = prop; } -void QConnectedReplicaImplementation::setConnection(ClientIoDevice *conn) +void QConnectedReplicaImplementation::setConnection(IoDeviceBase *conn) { if (connectionToSource.isNull()) { connectionToSource = conn; diff --git a/src/remoteobjects/qremoteobjectreplica_p.h b/src/remoteobjects/qremoteobjectreplica_p.h index 6b64b67..471964c 100644 --- a/src/remoteobjects/qremoteobjectreplica_p.h +++ b/src/remoteobjects/qremoteobjectreplica_p.h @@ -67,7 +67,7 @@ QT_BEGIN_NAMESPACE class QRemoteObjectReplica; class QRemoteObjectSource; -class ClientIoDevice; +class IoDeviceBase; class QReplicaImplementationInterface { @@ -165,7 +165,7 @@ public: QRemoteObjectPendingCall sendCommandWithReply(int serialId); bool waitForFinished(const QRemoteObjectPendingCall &call, int timeout) override; void notifyAboutReply(int ackedSerialId, const QVariant &value) override; - void setConnection(ClientIoDevice *conn); + void setConnection(IoDeviceBase *conn); void setDisconnected(); void _q_send(QMetaObject::Call call, int index, const QVariantList &args) override; @@ -176,7 +176,7 @@ public: QVector<QRemoteObjectReplica *> m_parentsNeedingConnect; QVariantList m_propertyStorage; QVector<int> m_childIndices; - QPointer<ClientIoDevice> connectionToSource; + QPointer<IoDeviceBase> connectionToSource; // pending call data int m_curSerialId = 1; // 0 is reserved for heartbeat signals diff --git a/src/remoteobjects/qremoteobjectsource.cpp b/src/remoteobjects/qremoteobjectsource.cpp index 5c9f7f9..36bee20 100644 --- a/src/remoteobjects/qremoteobjectsource.cpp +++ b/src/remoteobjects/qremoteobjectsource.cpp @@ -228,7 +228,7 @@ QRemoteObjectRootSource::~QRemoteObjectRootSource() delete it; } d->m_sourceIo->unregisterSource(this); - Q_FOREACH (ServerIoDevice *io, d->m_listeners) { + Q_FOREACH (IoDeviceBase *io, d->m_listeners) { removeListener(io, true); } delete d; @@ -323,11 +323,11 @@ void QRemoteObjectSourceBase::handleMetaCall(int index, QMetaObject::Call call, serializeInvokePacket(d->m_packet, name(), call, index, *marshalArgs(index, a), -1, propertyIndex); d->m_packet.baseAddress = 0; - Q_FOREACH (ServerIoDevice *io, d->m_listeners) + Q_FOREACH (IoDeviceBase *io, d->m_listeners) io->write(d->m_packet.array, d->m_packet.size); } -void QRemoteObjectRootSource::addListener(ServerIoDevice *io, bool dynamic) +void QRemoteObjectRootSource::addListener(IoDeviceBase *io, bool dynamic) { d->m_listeners.append(io); d->isDynamic = dynamic; @@ -347,7 +347,7 @@ void QRemoteObjectRootSource::addListener(ServerIoDevice *io, bool dynamic) d->isDynamic = false; } -int QRemoteObjectRootSource::removeListener(ServerIoDevice *io, bool shouldSendRemove) +int QRemoteObjectRootSource::removeListener(IoDeviceBase *io, bool shouldSendRemove) { d->m_listeners.removeAll(io); if (shouldSendRemove) diff --git a/src/remoteobjects/qremoteobjectsource_p.h b/src/remoteobjects/qremoteobjectsource_p.h index 86c1c5e..2961e5c 100644 --- a/src/remoteobjects/qremoteobjectsource_p.h +++ b/src/remoteobjects/qremoteobjectsource_p.h @@ -62,7 +62,7 @@ QT_BEGIN_NAMESPACE class QRemoteObjectSourceIo; -class ServerIoDevice; +class IoDeviceBase; class QRemoteObjectSourceBase : public QObject { @@ -87,7 +87,7 @@ public: struct Private { Private(QRemoteObjectSourceIo *io) : m_sourceIo(io), isDynamic(false) {} QRemoteObjectSourceIo *m_sourceIo; - QVector<ServerIoDevice*> m_listeners; + QVector<IoDeviceBase*> m_listeners; QRemoteObjectPackets::DataStreamPacket m_packet; // Types needed during recursively sending a root to a new listener @@ -122,8 +122,8 @@ public: bool isRoot() const override { return true; } QString name() const override { return m_name; } - void addListener(ServerIoDevice *io, bool dynamic = false); - int removeListener(ServerIoDevice *io, bool shouldSendRemove = false); + void addListener(IoDeviceBase *io, bool dynamic = false); + int removeListener(IoDeviceBase *io, bool shouldSendRemove = false); QString m_name; }; diff --git a/src/remoteobjects/qremoteobjectsourceio.cpp b/src/remoteobjects/qremoteobjectsourceio.cpp index 2ba2043..d864ed5 100644 --- a/src/remoteobjects/qremoteobjectsourceio.cpp +++ b/src/remoteobjects/qremoteobjectsourceio.cpp @@ -52,22 +52,31 @@ using namespace QtRemoteObjects; QRemoteObjectSourceIo::QRemoteObjectSourceIo(const QUrl &address, QObject *parent) : QObject(parent) - , m_server(QtROServerFactory::instance()->create(address, this)) + , m_server(QtROServerFactory::instance()->isValid(address) ? + QtROServerFactory::instance()->create(address, this) : nullptr) { if (m_server && m_server->listen(address)) { qRODebug(this) << "QRemoteObjectSourceIo is Listening" << address; } else { - qROWarning(this) << "Listen failed for URL:" << address; - if (m_server) + if (m_server) { + qROWarning(this) << "Listen failed for URL:" << address; qROWarning(this) << m_server->serverError(); - else - qROWarning(this) << "Most likely an unrecognized scheme was used."; + } else { + m_address = address; + qRODebug(this) << "Using" << address << "as external url."; + } return; } connect(m_server.data(), &QConnectionAbstractServer::newConnection, this, &QRemoteObjectSourceIo::handleConnection); } +QRemoteObjectSourceIo::QRemoteObjectSourceIo(QObject *parent) + : QObject(parent) + , m_server(nullptr) +{ +} + QRemoteObjectSourceIo::~QRemoteObjectSourceIo() { qDeleteAll(m_sourceRoots.values()); @@ -93,7 +102,7 @@ bool QRemoteObjectSourceIo::enableRemoting(QObject *object, const SourceApiMap * new QRemoteObjectRootSource(object, api, adapter, this); QRemoteObjectPackets::serializeObjectListPacket(m_packet, {QRemoteObjectPackets::ObjectInfo{api->name(), api->typeName(), api->objectSignature()}}); - foreach (ServerIoDevice *conn, m_connections) + for (auto conn : m_connections) conn->write(m_packet.array, m_packet.size); if (const int count = m_connections.size()) qRODebug(this) << "Wrote new QObjectListPacket for" << api->name() << "to" << count << "connections"; @@ -120,8 +129,10 @@ void QRemoteObjectSourceIo::registerSource(QRemoteObjectSourceBase *source) qRODebug(this) << "Registering" << name; m_sourceRoots[name] = root; m_objectToSourceMap[source->m_object] = root; - const auto &type = source->m_api->typeName(); - emit remoteObjectAdded(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress()))); + if (serverAddress().isValid()) { + const auto &type = source->m_api->typeName(); + emit remoteObjectAdded(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress()))); + } } } @@ -134,13 +145,14 @@ void QRemoteObjectSourceIo::unregisterSource(QRemoteObjectSourceBase *source) const auto type = source->m_api->typeName(); m_objectToSourceMap.remove(source->m_object); m_sourceRoots.remove(name); - emit remoteObjectRemoved(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress()))); + if (serverAddress().isValid()) + emit remoteObjectRemoved(qMakePair(name, QRemoteObjectSourceLocationInfo(type, serverAddress()))); } } void QRemoteObjectSourceIo::onServerDisconnect(QObject *conn) { - ServerIoDevice *connection = qobject_cast<ServerIoDevice*>(conn); + IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(conn); m_connections.remove(connection); qRODebug(this) << "OnServerDisconnect"; @@ -158,7 +170,7 @@ void QRemoteObjectSourceIo::onServerDisconnect(QObject *conn) void QRemoteObjectSourceIo::onServerRead(QObject *conn) { // Assert the invariant here conn is of type QIODevice - ServerIoDevice *connection = qobject_cast<ServerIoDevice*>(conn); + IoDeviceBase *connection = qobject_cast<IoDeviceBase*>(conn); QRemoteObjectPacketTypeEnum packetType; do { @@ -258,13 +270,18 @@ void QRemoteObjectSourceIo::handleConnection() qRODebug(this) << "handleConnection" << m_connections; ServerIoDevice *conn = m_server->nextPendingConnection(); + newConnection(conn); +} + +void QRemoteObjectSourceIo::newConnection(IoDeviceBase *conn) +{ m_connections.insert(conn); - connect(conn, &ServerIoDevice::disconnected, this, [this, conn]() { - onServerDisconnect(conn); - }); - connect(conn, &ServerIoDevice::readyRead, this, [this, conn]() { + connect(conn, &IoDeviceBase::readyRead, this, [this, conn]() { onServerRead(conn); }); + connect(conn, &IoDeviceBase::disconnected, this, [this, conn]() { + onServerDisconnect(conn); + }); serializeHandshakePacket(m_packet); conn->write(m_packet.array, m_packet.size); @@ -280,7 +297,9 @@ void QRemoteObjectSourceIo::handleConnection() QUrl QRemoteObjectSourceIo::serverAddress() const { - return m_server->address(); + if (m_server) + return m_server->address(); + return m_address; } QT_END_NAMESPACE diff --git a/src/remoteobjects/qremoteobjectsourceio_p.h b/src/remoteobjects/qremoteobjectsourceio_p.h index c9adde7..4cc045c 100644 --- a/src/remoteobjects/qremoteobjectsourceio_p.h +++ b/src/remoteobjects/qremoteobjectsourceio_p.h @@ -70,12 +70,14 @@ class QRemoteObjectSourceIo : public QObject Q_OBJECT public: explicit QRemoteObjectSourceIo(const QUrl &address, QObject *parent = nullptr); + explicit QRemoteObjectSourceIo(QObject *parent = nullptr); ~QRemoteObjectSourceIo() override; bool enableRemoting(QObject *object, const QMetaObject *meta, const QString &name, const QString &typeName); bool enableRemoting(QObject *object, const SourceApiMap *api, QObject *adapter = nullptr); bool disableRemoting(QObject *object); + void newConnection(IoDeviceBase *conn); QUrl serverAddress() const; @@ -94,15 +96,16 @@ public: void unregisterSource(QRemoteObjectSourceBase *source); QHash<QIODevice*, quint32> m_readSize; - QSet<ServerIoDevice*> m_connections; + QSet<IoDeviceBase*> m_connections; QHash<QObject *, QRemoteObjectRootSource*> m_objectToSourceMap; QMap<QString, QRemoteObjectSourceBase*> m_sourceObjects; QMap<QString, QRemoteObjectRootSource*> m_sourceRoots; - QHash<ServerIoDevice*, QUrl> m_registryMapping; + QHash<IoDeviceBase*, QUrl> m_registryMapping; QScopedPointer<QConnectionAbstractServer> m_server; QRemoteObjectPackets::DataStreamPacket m_packet; QString m_rxName; QVariantList m_rxArgs; + QUrl m_address; }; QT_END_NAMESPACE diff --git a/src/remoteobjects/qtremoteobjectglobal.h b/src/remoteobjects/qtremoteobjectglobal.h index 7e6dfb9..eb23b44 100644 --- a/src/remoteobjects/qtremoteobjectglobal.h +++ b/src/remoteobjects/qtremoteobjectglobal.h @@ -173,6 +173,7 @@ enum QRemoteObjectPacketTypeEnum Ping, Pong }; +Q_ENUM_NS(QRemoteObjectPacketTypeEnum) enum InitialAction { FetchRootSize, |