summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@qt.io>2017-07-27 14:34:39 +0200
committerTimur Pocheptsov <timur.pocheptsov@qt.io>2017-08-27 04:54:55 +0000
commit53357f01561d7c2b50e0a656ca250f5e3c1af923 (patch)
tree28ac5a34217efb50a4844d498794baedc72d66ae /src/network
parentfabedd399ebe4d28d6eb62c8a863f1bbcce78d3a (diff)
HTTP/2 - implement the proper 'h2c' (protocol upgrade)
Without TLS (and thus ALPN/NPN negotiation) HTTP/2 requires a protocol upgrade procedure, as described in RFC 7540, 3.2. We start as HTTP/1.1 (and thus we create QHttpProtocolHandler first), augmenting the headers we send with 'Upgrade: h2c'. In case we receive HTTP/1.1 response with status code 101 ('Switching Protocols'), we continue as HTTP/2 session, creating QHttp2ProtocolHandler and pretending the first request we sent was HTTP/2 request on a real HTTP/2 stream. If the first response is something different from 101, we continue as HTTP/1.1. This change also required auto-test update: our toy-server now has to respond to the initial HTTP/1.1 request on a platform without ALPN/NPN. As a bonus a subtle flakyness in 'goaway' auto-test went away (well, it was fixed). [ChangeLog][QtNetwork][HTTP/2] In case of clear text HTTP/2 we now initiate a required protocol upgrade procedure instead of 'H2Direct' connection. Task-number: QTBUG-61397 Change-Id: I573fa304fdaf661490159037dc47775d97c8ea5b Reviewed-by: Edward Welbourne <edward.welbourne@qt.io> Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
Diffstat (limited to 'src/network')
-rw-r--r--src/network/access/http2/http2frames.cpp6
-rw-r--r--src/network/access/http2/http2frames_p.h2
-rw-r--r--src/network/access/http2/http2protocol.cpp75
-rw-r--r--src/network/access/http2/http2protocol_p.h6
-rw-r--r--src/network/access/qhttp2protocolhandler.cpp38
-rw-r--r--src/network/access/qhttp2protocolhandler_p.h4
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp35
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h2
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp77
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel_p.h1
-rw-r--r--src/network/access/qhttpnetworkheader_p.h2
11 files changed, 212 insertions, 36 deletions
diff --git a/src/network/access/http2/http2frames.cpp b/src/network/access/http2/http2frames.cpp
index 5a684c2f41..e695b4dd9e 100644
--- a/src/network/access/http2/http2frames.cpp
+++ b/src/network/access/http2/http2frames.cpp
@@ -361,6 +361,12 @@ FrameWriter::FrameWriter(FrameType type, FrameFlags flags, quint32 streamID)
start(type, flags, streamID);
}
+void FrameWriter::setOutboundFrame(Frame &&newFrame)
+{
+ frame = std::move(newFrame);
+ updatePayloadSize();
+}
+
void FrameWriter::start(FrameType type, FrameFlags flags, quint32 streamID)
{
auto &buffer = frame.buffer;
diff --git a/src/network/access/http2/http2frames_p.h b/src/network/access/http2/http2frames_p.h
index e5f6d46c67..4bdc775806 100644
--- a/src/network/access/http2/http2frames_p.h
+++ b/src/network/access/http2/http2frames_p.h
@@ -129,6 +129,8 @@ public:
return frame;
}
+ void setOutboundFrame(Frame &&newFrame);
+
// Frame 'builders':
void start(FrameType type, FrameFlags flags, quint32 streamID);
void setPayloadSize(quint32 size);
diff --git a/src/network/access/http2/http2protocol.cpp b/src/network/access/http2/http2protocol.cpp
index 7f788a6f42..54811aeab0 100644
--- a/src/network/access/http2/http2protocol.cpp
+++ b/src/network/access/http2/http2protocol.cpp
@@ -37,9 +37,14 @@
**
****************************************************************************/
-#include <QtCore/qstring.h>
-
#include "http2protocol_p.h"
+#include "http2frames_p.h"
+
+#include "private/qhttpnetworkrequest_p.h"
+#include "private/qhttpnetworkreply_p.h"
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qstring.h>
QT_BEGIN_NAMESPACE
@@ -57,6 +62,38 @@ const char Http2clientPreface[clientPrefaceLength] =
0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a,
0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};
+QByteArray default_SETTINGS_to_Base64()
+{
+ Frame frame(default_SETTINGS_frame());
+ // SETTINGS frame's payload consists of pairs:
+ // 2-byte-identifier | 4-byte-value == multiple of 6.
+ Q_ASSERT(frame.payloadSize() && !(frame.payloadSize() % 6));
+ const char *src = reinterpret_cast<const char *>(frame.dataBegin());
+ const QByteArray wrapper(QByteArray::fromRawData(src, int(frame.dataSize())));
+ // 3.2.1
+ // The content of the HTTP2-Settings header field is the payload
+ // of a SETTINGS frame (Section 6.5), encoded as a base64url string
+ // (that is, the URL- and filename-safe Base64 encoding described in
+ // Section 5 of [RFC4648], with any trailing '=' characters omitted).
+ return wrapper.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
+}
+
+void prepare_for_protocol_upgrade(QHttpNetworkRequest &request)
+{
+ // RFC 2616, 14.10
+ // RFC 7540, 3.2
+ QByteArray value(request.headerField("Connection"));
+ // We _append_ 'Upgrade':
+ if (value.size())
+ value += ", ";
+
+ value += "Upgrade, HTTP2-Settings";
+ request.setHeaderField("Connection", value);
+ // This we just (re)write.
+ request.setHeaderField("Upgrade", "h2c");
+ // This we just (re)write.
+ request.setHeaderField("HTTP2-Settings", default_SETTINGS_to_Base64());
+}
void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error,
QString &errorMessage)
@@ -151,6 +188,40 @@ QNetworkReply::NetworkError qt_error(quint32 errorCode)
return error;
}
+bool is_PUSH_PROMISE_enabled()
+{
+ bool ok = false;
+ const int env = qEnvironmentVariableIntValue("QT_HTTP2_ENABLE_PUSH_PROMISE", &ok);
+ return ok && env;
+}
+
+bool is_protocol_upgraded(const QHttpNetworkReply &reply)
+{
+ if (reply.statusCode() == 101) {
+ // Do some minimal checks here - we expect 'Upgrade: h2c' to be found.
+ const auto &header = reply.header();
+ for (const QPair<QByteArray, QByteArray> &field : header) {
+ if (field.first.toLower() == "upgrade" && field.second.toLower() == "h2c")
+ return true;
+ }
+ }
+
+ return false;
}
+Frame default_SETTINGS_frame()
+{
+ // 6.5 SETTINGS
+ FrameWriter builder(FrameType::SETTINGS, FrameFlag::EMPTY, connectionStreamID);
+ // MAX frame size (16 kb), disable/enable PUSH_PROMISE
+ builder.append(Settings::MAX_FRAME_SIZE_ID);
+ builder.append(quint32(maxFrameSize));
+ builder.append(Settings::ENABLE_PUSH_ID);
+ builder.append(quint32(is_PUSH_PROMISE_enabled()));
+
+ return builder.outboundFrame();
+}
+
+} // namespace Http2
+
QT_END_NAMESPACE
diff --git a/src/network/access/http2/http2protocol_p.h b/src/network/access/http2/http2protocol_p.h
index 5d730404bb..b26ff0e9f4 100644
--- a/src/network/access/http2/http2protocol_p.h
+++ b/src/network/access/http2/http2protocol_p.h
@@ -59,6 +59,8 @@
QT_BEGIN_NAMESPACE
+class QHttpNetworkRequest;
+class QHttpNetworkReply;
class QString;
namespace Http2
@@ -132,6 +134,7 @@ enum Http2PredefinedParameters
const quint32 lastValidStreamID((quint32(1) << 31) - 1); // HTTP/2, 5.1.1
extern const Q_AUTOTEST_EXPORT char Http2clientPreface[clientPrefaceLength];
+void prepare_for_protocol_upgrade(QHttpNetworkRequest &request);
enum class FrameStatus
{
@@ -169,6 +172,9 @@ enum Http2Error
void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error, QString &errorString);
QString qt_error_string(quint32 errorCode);
QNetworkReply::NetworkError qt_error(quint32 errorCode);
+bool is_PUSH_PROMISE_enabled();
+bool is_protocol_upgraded(const QHttpNetworkReply &reply);
+struct Frame default_SETTINGS_frame();
}
diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp
index 44ab637da8..5032f6017f 100644
--- a/src/network/access/qhttp2protocolhandler.cpp
+++ b/src/network/access/qhttp2protocolhandler.cpp
@@ -170,10 +170,22 @@ QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *chan
decoder(HPack::FieldLookupTable::DefaultSize),
encoder(HPack::FieldLookupTable::DefaultSize, true)
{
+ Q_ASSERT(channel);
continuedFrames.reserve(20);
- bool ok = false;
- const int env = qEnvironmentVariableIntValue("QT_HTTP2_ENABLE_PUSH_PROMISE", &ok);
- pushPromiseEnabled = ok && env;
+ pushPromiseEnabled = is_PUSH_PROMISE_enabled();
+
+ if (!channel->ssl) {
+ // We upgraded from HTTP/1.1 to HTTP/2. channel->request was already sent
+ // as HTTP/1.1 request. The response with status code 101 triggered
+ // protocol switch and now we are waiting for the real response, sent
+ // as HTTP/2 frames.
+ Q_ASSERT(channel->reply);
+ const quint32 initialStreamID = createNewStream(HttpMessagePair(channel->request, channel->reply),
+ true /* uploaded by HTTP/1.1 */);
+ Q_ASSERT(initialStreamID == 1);
+ Stream &stream = activeStreams[initialStreamID];
+ stream.state = Stream::halfClosedLocal;
+ }
}
void QHttp2ProtocolHandler::_q_uploadDataReadyRead()
@@ -356,12 +368,8 @@ bool QHttp2ProtocolHandler::sendClientPreface()
return false;
// 6.5 SETTINGS
- frameWriter.start(FrameType::SETTINGS, FrameFlag::EMPTY, Http2::connectionStreamID);
- // MAX frame size (16 kb), enable/disable PUSH
- frameWriter.append(Settings::MAX_FRAME_SIZE_ID);
- frameWriter.append(quint32(Http2::maxFrameSize));
- frameWriter.append(Settings::ENABLE_PUSH_ID);
- frameWriter.append(quint32(pushPromiseEnabled));
+ frameWriter.setOutboundFrame(default_SETTINGS_frame());
+ Q_ASSERT(frameWriter.outboundFrame().payloadSize());
if (!frameWriter.write(*m_socket))
return false;
@@ -1157,7 +1165,7 @@ void QHttp2ProtocolHandler::finishStreamWithError(Stream &stream, QNetworkReply:
<< "finished with error:" << message;
}
-quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message)
+quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message, bool uploadDone)
{
const qint32 newStreamID = allocateStreamID();
if (!newStreamID)
@@ -1178,10 +1186,12 @@ quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message)
streamInitialSendWindowSize,
streamInitialRecvWindowSize);
- if (auto src = newStream.data()) {
- connect(src, SIGNAL(readyRead()), this,
- SLOT(_q_uploadDataReadyRead()), Qt::QueuedConnection);
- src->setProperty("HTTP2StreamID", newStreamID);
+ if (!uploadDone) {
+ if (auto src = newStream.data()) {
+ connect(src, SIGNAL(readyRead()), this,
+ SLOT(_q_uploadDataReadyRead()), Qt::QueuedConnection);
+ src->setProperty("HTTP2StreamID", newStreamID);
+ }
}
activeStreams.insert(newStreamID, newStream);
diff --git a/src/network/access/qhttp2protocolhandler_p.h b/src/network/access/qhttp2protocolhandler_p.h
index df0cf6a288..82eea21818 100644
--- a/src/network/access/qhttp2protocolhandler_p.h
+++ b/src/network/access/qhttp2protocolhandler_p.h
@@ -98,7 +98,7 @@ private:
using Stream = Http2::Stream;
void _q_readyRead() override;
- void _q_receiveReply() override;
+ Q_INVOKABLE void _q_receiveReply() override;
Q_INVOKABLE bool sendRequest() override;
bool sendClientPreface();
@@ -136,7 +136,7 @@ private:
const QString &message);
// Stream's lifecycle management:
- quint32 createNewStream(const HttpMessagePair &message);
+ quint32 createNewStream(const HttpMessagePair &message, bool uploadDone = false);
void addToSuspended(Stream &stream);
void markAsReset(quint32 streamID);
quint32 popStreamToResume();
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index ae30d3a8cf..0b474ba116 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -627,7 +627,8 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetwor
if (request.isPreConnect())
preConnectRequests++;
- if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP) {
+ if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP
+ || (!encrypt && connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2 && !channels[0].switchedToHttp2)) {
switch (request.priority()) {
case QHttpNetworkRequest::HighPriority:
highPriorityQueue.prepend(pair);
@@ -638,7 +639,7 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetwor
break;
}
}
- else { // SPDY, HTTP/2
+ else { // SPDY, HTTP/2 ('h2' mode)
if (!pair.second->d_func()->requestIsPrepared)
prepareRequest(pair);
channels[0].spdyRequestsToSend.insertMulti(request.priority(), pair);
@@ -672,6 +673,25 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetwor
return reply;
}
+void QHttpNetworkConnectionPrivate::fillHttp2Queue()
+{
+ for (auto &pair : highPriorityQueue) {
+ if (!pair.second->d_func()->requestIsPrepared)
+ prepareRequest(pair);
+ channels[0].spdyRequestsToSend.insertMulti(QHttpNetworkRequest::HighPriority, pair);
+ }
+
+ highPriorityQueue.clear();
+
+ for (auto &pair : lowPriorityQueue) {
+ if (!pair.second->d_func()->requestIsPrepared)
+ prepareRequest(pair);
+ channels[0].spdyRequestsToSend.insertMulti(pair.first.priority(), pair);
+ }
+
+ lowPriorityQueue.clear();
+}
+
void QHttpNetworkConnectionPrivate::requeueRequest(const HttpMessagePair &pair)
{
Q_Q(QHttpNetworkConnection);
@@ -1047,8 +1067,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
}
case QHttpNetworkConnection::ConnectionTypeHTTP2:
case QHttpNetworkConnection::ConnectionTypeSPDY: {
-
- if (channels[0].spdyRequestsToSend.isEmpty())
+ if (channels[0].spdyRequestsToSend.isEmpty() && channels[0].switchedToHttp2)
return;
if (networkLayerState == IPv4)
@@ -1057,7 +1076,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
channels[0].networkLayerPreference = QAbstractSocket::IPv6Protocol;
channels[0].ensureConnection();
if (channels[0].socket && channels[0].socket->state() == QAbstractSocket::ConnectedState
- && !channels[0].pendingEncrypt)
+ && !channels[0].pendingEncrypt && channels[0].spdyRequestsToSend.size())
channels[0].sendRequest();
break;
}
@@ -1355,6 +1374,12 @@ QHttpNetworkReply* QHttpNetworkConnection::sendRequest(const QHttpNetworkRequest
return d->queueRequest(request);
}
+void QHttpNetworkConnection::fillHttp2Queue()
+{
+ Q_D(QHttpNetworkConnection);
+ d->fillHttp2Queue();
+}
+
bool QHttpNetworkConnection::isSsl() const
{
Q_D(const QHttpNetworkConnection);
diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h
index 3dd9bde9bd..f01a2318a5 100644
--- a/src/network/access/qhttpnetworkconnection_p.h
+++ b/src/network/access/qhttpnetworkconnection_p.h
@@ -122,6 +122,7 @@ public:
//add a new HTTP request through this connection
QHttpNetworkReply* sendRequest(const QHttpNetworkRequest &request);
+ void fillHttp2Queue();
#ifndef QT_NO_NETWORKPROXY
//set the proxy for this connection
@@ -208,6 +209,7 @@ public:
QHttpNetworkReply *queueRequest(const QHttpNetworkRequest &request);
void requeueRequest(const HttpMessagePair &pair); // e.g. after pipeline broke
+ void fillHttp2Queue();
bool dequeueRequest(QAbstractSocket *socket);
void prepareRequest(HttpMessagePair &request);
void updateChannel(int i, const HttpMessagePair &messagePair);
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index 6b2018ef86..b1ae29427e 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -63,6 +63,20 @@
QT_BEGIN_NAMESPACE
+namespace
+{
+
+class ProtocolHandlerDeleter : public QObject
+{
+public:
+ explicit ProtocolHandlerDeleter(QAbstractProtocolHandler *h) : handler(h) {}
+ ~ProtocolHandlerDeleter() { delete handler; }
+private:
+ QAbstractProtocolHandler *handler = nullptr;
+};
+
+}
+
// TODO: Put channel specific stuff here so it does not polute qhttpnetworkconnection.cpp
// Because in-flight when sending a request, the server might close our connection (because the persistent HTTP
@@ -424,6 +438,40 @@ void QHttpNetworkConnectionChannel::allDone()
return;
}
+ if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
+ && !ssl && !switchedToHttp2) {
+ if (Http2::is_protocol_upgraded(*reply)) {
+ switchedToHttp2 = true;
+ protocolHandler->setReply(nullptr);
+
+ // As allDone() gets called from the protocol handler, it's not yet
+ // safe to delete it. There is no 'deleteLater', since
+ // QAbstractProtocolHandler is not a QObject. Instead we do this
+ // trick with ProtocolHandlerDeleter, a QObject-derived class.
+ // These dances below just make it somewhat exception-safe.
+ // 1. Create a new owner:
+ QAbstractProtocolHandler *oldHandler = protocolHandler.data();
+ QScopedPointer<ProtocolHandlerDeleter> deleter(new ProtocolHandlerDeleter(oldHandler));
+ // 2. Retire the old one:
+ protocolHandler.take();
+ // 3. Call 'deleteLater':
+ deleter->deleteLater();
+ // 3. Give up the ownerthip:
+ deleter.take();
+
+ connection->fillHttp2Queue();
+ protocolHandler.reset(new QHttp2ProtocolHandler(this));
+ QHttp2ProtocolHandler *h2c = static_cast<QHttp2ProtocolHandler *>(protocolHandler.data());
+ QMetaObject::invokeMethod(h2c, "_q_receiveReply", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ return;
+ } else {
+ // Ok, whatever happened, we do not try HTTP/2 anymore ...
+ connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP);
+ connection->d_func()->activeChannelCount = connection->d_func()->channelCount;
+ }
+ }
+
// while handling 401 & 407, we might reset the status code, so save this.
bool emitFinished = reply->d_func()->shouldEmitSignals();
bool connectionCloseEnabled = reply->d_func()->isConnectionCloseEnabled();
@@ -838,19 +886,23 @@ void QHttpNetworkConnectionChannel::_q_connected()
#endif
} else {
state = QHttpNetworkConnectionChannel::IdleState;
- if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2) {
- // We have to reset QHttp2ProtocolHandler's state machine, it's a new
- // connection and the handler's state is unique per connection.
- protocolHandler.reset(new QHttp2ProtocolHandler(this));
- if (spdyRequestsToSend.count() > 0) {
- // wait for data from the server first (e.g. initial window, max concurrent requests)
- QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ const bool tryProtocolUpgrade = connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2;
+ if (tryProtocolUpgrade) {
+ // For HTTP/1.1 it's already created and never reset.
+ protocolHandler.reset(new QHttpProtocolHandler(this));
+ }
+ switchedToHttp2 = false;
+
+ if (!reply)
+ connection->d_func()->dequeueRequest(socket);
+
+ if (reply) {
+ if (tryProtocolUpgrade) {
+ // Let's augment our request with some magic headers and try to
+ // switch to HTTP/2.
+ Http2::prepare_for_protocol_upgrade(request);
}
- } else {
- if (!reply)
- connection->d_func()->dequeueRequest(socket);
- if (reply)
- sendRequest();
+ sendRequest();
}
}
}
@@ -1078,6 +1130,7 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
// has gone to the SPDY queue already
break;
} else if (nextProtocol == QSslConfiguration::ALPNProtocolHTTP2) {
+ switchedToHttp2 = true;
protocolHandler.reset(new QHttp2ProtocolHandler(this));
connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP2);
break;
diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h
index 584d52ddb7..844a7d5d15 100644
--- a/src/network/access/qhttpnetworkconnectionchannel_p.h
+++ b/src/network/access/qhttpnetworkconnectionchannel_p.h
@@ -127,6 +127,7 @@ public:
// HTTP/2 can be cleartext also, that's why it's
// outside of QT_NO_SSL section. Sorted by priority:
QMultiMap<int, HttpMessagePair> spdyRequestsToSend;
+ bool switchedToHttp2 = false;
#ifndef QT_NO_SSL
bool ignoreAllSslErrors;
QList<QSslError> ignoreSslErrorsList;
diff --git a/src/network/access/qhttpnetworkheader_p.h b/src/network/access/qhttpnetworkheader_p.h
index 89169b9331..46aec1dd8c 100644
--- a/src/network/access/qhttpnetworkheader_p.h
+++ b/src/network/access/qhttpnetworkheader_p.h
@@ -78,7 +78,7 @@ public:
virtual void setHeaderField(const QByteArray &name, const QByteArray &data) = 0;
};
-class QHttpNetworkHeaderPrivate : public QSharedData
+class Q_AUTOTEST_EXPORT QHttpNetworkHeaderPrivate : public QSharedData
{
public:
QUrl url;