diff options
-rw-r--r-- | src/network/access/qhttpnetworkrequest.cpp | 14 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkrequest_p.h | 7 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessbackend.cpp | 5 | ||||
-rw-r--r-- | src/network/access/qnetworkaccesshttpbackend.cpp | 9 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager.cpp | 37 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager.h | 2 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.cpp | 6 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.h | 1 | ||||
-rw-r--r-- | tests/auto/qnetworkreply/tst_qnetworkreply.cpp | 83 |
9 files changed, 158 insertions, 6 deletions
diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp index 8cdfe6a..645deb8 100644 --- a/src/network/access/qhttpnetworkrequest.cpp +++ b/src/network/access/qhttpnetworkrequest.cpp @@ -61,6 +61,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest uploadByteDevice = other.uploadByteDevice; autoDecompress = other.autoDecompress; pipeliningAllowed = other.pipeliningAllowed; + customVerb = other.customVerb; } QHttpNetworkRequestPrivate::~QHttpNetworkRequestPrivate() @@ -102,6 +103,9 @@ QByteArray QHttpNetworkRequestPrivate::methodName() const case QHttpNetworkRequest::Connect: ba += "CONNECT"; break; + case QHttpNetworkRequest::Custom: + ba += customVerb; + break; default: break; } @@ -230,6 +234,16 @@ void QHttpNetworkRequest::setOperation(Operation operation) d->operation = operation; } +QByteArray QHttpNetworkRequest::customVerb() const +{ + return d->customVerb; +} + +void QHttpNetworkRequest::setCustomVerb(const QByteArray &customVerb) +{ + d->customVerb = customVerb; +} + QHttpNetworkRequest::Priority QHttpNetworkRequest::priority() const { return d->priority; diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h index dad118e..1b35a84 100644 --- a/src/network/access/qhttpnetworkrequest_p.h +++ b/src/network/access/qhttpnetworkrequest_p.h @@ -72,7 +72,8 @@ public: Put, Delete, Trace, - Connect + Connect, + Custom }; enum Priority { @@ -103,6 +104,9 @@ public: Operation operation() const; void setOperation(Operation operation); + QByteArray customVerb() const; + void setCustomVerb(const QByteArray &customOperation); + Priority priority() const; void setPriority(Priority priority); @@ -133,6 +137,7 @@ public: static QByteArray header(const QHttpNetworkRequest &request, bool throughProxy); QHttpNetworkRequest::Operation operation; + QByteArray customVerb; QHttpNetworkRequest::Priority priority; mutable QNonContiguousByteDevice* uploadByteDevice; bool autoDecompress; diff --git a/src/network/access/qnetworkaccessbackend.cpp b/src/network/access/qnetworkaccessbackend.cpp index 8ac64d2..34d576a 100644 --- a/src/network/access/qnetworkaccessbackend.cpp +++ b/src/network/access/qnetworkaccessbackend.cpp @@ -120,8 +120,11 @@ QNonContiguousByteDevice* QNetworkAccessBackend::createUploadByteDevice() if (reply->outgoingDataBuffer) device = QNonContiguousByteDeviceFactory::create(reply->outgoingDataBuffer); - else + else if (reply->outgoingData) { device = QNonContiguousByteDeviceFactory::create(reply->outgoingData); + } else { + return 0; + } bool bufferDisallowed = reply->request.attribute(QNetworkRequest::DoNotBufferUploadDataAttribute, diff --git a/src/network/access/qnetworkaccesshttpbackend.cpp b/src/network/access/qnetworkaccesshttpbackend.cpp index 8e02723..c8f4e5b 100644 --- a/src/network/access/qnetworkaccesshttpbackend.cpp +++ b/src/network/access/qnetworkaccesshttpbackend.cpp @@ -213,6 +213,7 @@ QNetworkAccessHttpBackendFactory::create(QNetworkAccessManager::Operation op, case QNetworkAccessManager::HeadOperation: case QNetworkAccessManager::PutOperation: case QNetworkAccessManager::DeleteOperation: + case QNetworkAccessManager::CustomOperation: break; default: @@ -526,6 +527,14 @@ void QNetworkAccessHttpBackend::postRequest() httpRequest.setOperation(QHttpNetworkRequest::Delete); break; + case QNetworkAccessManager::CustomOperation: + invalidateCache(); // for safety reasons, we don't know what the operation does + httpRequest.setOperation(QHttpNetworkRequest::Custom); + httpRequest.setUploadByteDevice(createUploadByteDevice()); + httpRequest.setCustomVerb(request().attribute( + QNetworkRequest::CustomVerbAttribute).toByteArray()); + break; + default: break; // can't happen } diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index 0b32533..cc4c977 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -154,6 +154,9 @@ static void ensureInitialized() \value DeleteOperation delete contents operation (created with deleteResource()) + \value CustomOperation custom operation (created with + sendCustomRequest()) + \omitvalue UnknownOperation \sa QNetworkReply::operation() @@ -566,7 +569,7 @@ QNetworkReply *QNetworkAccessManager::head(const QNetworkRequest &request) The contents as well as associated headers will be downloaded. - \sa post(), put(), deleteResource() + \sa post(), put(), deleteResource(), sendCustomRequest() */ QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request) { @@ -585,7 +588,7 @@ QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request) \note Sending a POST request on protocols other than HTTP and HTTPS is undefined and will probably fail. - \sa get(), put(), deleteResource() + \sa get(), put(), deleteResource(), sendCustomRequest() */ QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, QIODevice *data) { @@ -626,7 +629,7 @@ QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, const do not allow. Form upload mechanisms, including that of uploading files through HTML forms, use the POST mechanism. - \sa get(), post() + \sa get(), post(), deleteResource(), sendCustomRequest() */ QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, QIODevice *data) { @@ -657,7 +660,7 @@ QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, const \note This feature is currently available for HTTP only, performing an HTTP DELETE request. - \sa get(), post(), put() + \sa get(), post(), put(), sendCustomRequest() */ QNetworkReply *QNetworkAccessManager::deleteResource(const QNetworkRequest &request) { @@ -665,6 +668,32 @@ QNetworkReply *QNetworkAccessManager::deleteResource(const QNetworkRequest &requ } /*! + \since 4.7 + + Sends a custom request to the server identified by the URL of \a request. + + It is the user's responsibility to send a \a verb to the server that is valid + according to the HTTP specification. + + This method provides means to send verbs other than the common ones provided + via get() or post() etc., for instance sending an HTTP OPTIONS command. + + If \a data is not empty, the contents of the \a data + device will be uploaded to the server; in that case, data must be open for + reading and must remain valid until the finished() signal is emitted for this reply. + + \note This feature is currently available for HTTP only. + + \sa get(), post(), put(), deleteResource() +*/ +QNetworkReply *QNetworkAccessManager::sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, QIODevice *data) +{ + QNetworkRequest newRequest(request); + newRequest.setAttribute(QNetworkRequest::CustomVerbAttribute, verb); + return d_func()->postProcess(createRequest(QNetworkAccessManager::CustomOperation, newRequest, data)); +} + +/*! Returns a new QNetworkReply object to handle the operation \a op and request \a req. The device \a outgoingData is always 0 for Get and Head requests, but is the value passed to post() and put() in diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h index d2fe527..3a8ba58 100644 --- a/src/network/access/qnetworkaccessmanager.h +++ b/src/network/access/qnetworkaccessmanager.h @@ -75,6 +75,7 @@ public: PutOperation, PostOperation, DeleteOperation, + CustomOperation, UnknownOperation = 0 }; @@ -102,6 +103,7 @@ public: QNetworkReply *put(const QNetworkRequest &request, QIODevice *data); QNetworkReply *put(const QNetworkRequest &request, const QByteArray &data); QNetworkReply *deleteResource(const QNetworkRequest &request); + QNetworkReply *sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, QIODevice *data = 0); Q_SIGNALS: #ifndef QT_NO_NETWORKPROXY diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index b8438a2..e563f4e 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -182,6 +182,12 @@ QT_BEGIN_NAMESPACE Indicates whether the HTTP pipelining was used for receiving this reply. + \value CustomVerbAttribute + Requests only, type: QVariant::ByteArray + Holds the value for the custom HTTP verb to send (destined for usage + of other verbs than GET, POST, PUT and DELETE). This verb is set + when calling QNetworkAccessManager::sendCustomRequest(). + \value User Special type. Additional information can be passed in QVariants with types ranging from User to UserMax. The default diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index bc2d9da..a0ef1a6 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -78,6 +78,7 @@ public: DoNotBufferUploadDataAttribute, HttpPipeliningAllowedAttribute, HttpPipeliningWasUsedAttribute, + CustomVerbAttribute, User = 1000, UserMax = 32767 diff --git a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp index eec4797..05e2e1b 100644 --- a/tests/auto/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/qnetworkreply/tst_qnetworkreply.cpp @@ -82,6 +82,7 @@ Q_DECLARE_METATYPE(QNetworkProxy) Q_DECLARE_METATYPE(QNetworkProxyQuery) Q_DECLARE_METATYPE(QList<QNetworkProxy>) Q_DECLARE_METATYPE(QNetworkReply::NetworkError) +Q_DECLARE_METATYPE(QBuffer*) class QNetworkReplyPtr: public QSharedPointer<QNetworkReply> { @@ -129,6 +130,9 @@ public: QString runSimpleRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QNetworkReplyPtr &reply, const QByteArray &data = QByteArray()); + QString runCustomRequest(const QNetworkRequest &request, QNetworkReplyPtr &reply, + const QByteArray &verb, QIODevice *data); + public Q_SLOTS: void finished(); void gotError(); @@ -175,6 +179,8 @@ private Q_SLOTS: void deleteFromHttp(); void putGetDeleteGetFromHttp_data(); void putGetDeleteGetFromHttp(); + void sendCustomRequestToHttp_data(); + void sendCustomRequestToHttp(); void ioGetFromData_data(); void ioGetFromData(); @@ -778,6 +784,32 @@ QString tst_QNetworkReply::runSimpleRequest(QNetworkAccessManager::Operation op, return QString(); } +QString tst_QNetworkReply::runCustomRequest(const QNetworkRequest &request, + QNetworkReplyPtr &reply, + const QByteArray &verb, + QIODevice *data) +{ + reply = manager.sendCustomRequest(request, verb, data); + reply->setParent(this); + connect(reply, SIGNAL(finished()), SLOT(finished())); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(gotError())); + + returnCode = Timeout; + loop = new QEventLoop; + QTimer::singleShot(20000, loop, SLOT(quit())); + int code = returnCode == Timeout ? loop->exec() : returnCode; + delete loop; + loop = 0; + + switch (code) { + case Failure: + return "Request failed: " + reply->errorString(); + case Timeout: + return "Network timeout"; + } + return QString(); +} + void tst_QNetworkReply::finished() { loop->exit(returnCode = Success); @@ -1436,6 +1468,57 @@ void tst_QNetworkReply::putGetDeleteGetFromHttp() } +void tst_QNetworkReply::sendCustomRequestToHttp_data() +{ + QTest::addColumn<QUrl>("url"); + QTest::addColumn<QByteArray>("verb"); + QTest::addColumn<QBuffer *>("device"); + QTest::addColumn<int>("resultCode"); + QTest::addColumn<QNetworkReply::NetworkError>("error"); + QTest::addColumn<QByteArray>("expectedContent"); + + QTest::newRow("options") << QUrl("http://" + QtNetworkSettings::serverName()) << + QByteArray("OPTIONS") << (QBuffer *) 0 << 200 << QNetworkReply::NoError << QByteArray(); + QTest::newRow("trace") << QUrl("http://" + QtNetworkSettings::serverName()) << + QByteArray("TRACE") << (QBuffer *) 0 << 200 << QNetworkReply::NoError << QByteArray(); + QTest::newRow("connect") << QUrl("http://" + QtNetworkSettings::serverName()) << + QByteArray("CONNECT") << (QBuffer *) 0 << 400 << QNetworkReply::UnknownContentError << QByteArray(); // 400 = Bad Request + QTest::newRow("nonsense") << QUrl("http://" + QtNetworkSettings::serverName()) << + QByteArray("NONSENSE") << (QBuffer *) 0 << 501 << QNetworkReply::ProtocolUnknownError << QByteArray(); // 501 = Method Not Implemented + + QByteArray ba("test"); + QBuffer *buffer = new QBuffer; + buffer->setData(ba); + buffer->open(QIODevice::ReadOnly); + QTest::newRow("post") << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi") << QByteArray("POST") + << buffer << 200 << QNetworkReply::NoError << QByteArray("098f6bcd4621d373cade4e832627b4f6\n"); + + QByteArray ba2("test"); + QBuffer *buffer2 = new QBuffer; + buffer2->setData(ba2); + buffer2->open(QIODevice::ReadOnly); + QTest::newRow("put") << QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi") << QByteArray("PUT") + << buffer2 << 200 << QNetworkReply::NoError << QByteArray("098f6bcd4621d373cade4e832627b4f6\n"); +} + +void tst_QNetworkReply::sendCustomRequestToHttp() +{ + QFETCH(QUrl, url); + QNetworkRequest request(url); + QNetworkReplyPtr reply; + QFETCH(QByteArray, verb); + QFETCH(QBuffer *, device); + runCustomRequest(request, reply, verb, device); + QCOMPARE(reply->url(), url); + QFETCH(QNetworkReply::NetworkError, error); + QCOMPARE(reply->error(), error); + QFETCH(int, resultCode); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), resultCode); + QFETCH(QByteArray, expectedContent); + if (! expectedContent.isEmpty()) + QCOMPARE(reply->readAll(), expectedContent); +} + void tst_QNetworkReply::ioGetFromData_data() { QTest::addColumn<QString>("urlStr"); |