Skip to content
This repository was archived by the owner on May 30, 2023. It is now read-only.

HTTP PATCH requests do not send body #11384

Closed
sgsabbage opened this issue Jun 4, 2013 · 66 comments
Closed

HTTP PATCH requests do not send body #11384

sgsabbage opened this issue Jun 4, 2013 · 66 comments
Labels

Comments

@sgsabbage
Copy link

Much in the same vein as issues #11367 and #10873 , sending an AJAX PATCH request does not send the body, and instead just sends a blank body. PATCH requests are documented in RFC5789, and are supported by all the browsers I've tested in.

Arguably, it also exists in RFC2068, but I'm aware that that does label it an 'experimental' feature.

@le0pard
Copy link

le0pard commented Jul 19, 2013

+1 - the same problem.

@paultyng
Copy link

Can confirm this problem as well. Rails 4 now supports PATCH out of the box for resources so have been using it but cannot use PhantomJS to test and have had to revert to chrome driver.

@danielwestendorf
Copy link

+1 same problem

@hanssgo
Copy link

hanssgo commented Aug 7, 2013

+1. Just ran into this problem as well

@paultyng
Copy link

paultyng commented Aug 7, 2013

Looks like QT4 doesn't support this built in, would have to do a little work to implement the custom request functionality:

http://harmattan-dev.nokia.com/docs/library/html/qt4/qnetworkaccessmanager.html#Operation-enum
http://harmattan-dev.nokia.com/docs/library/html/qt4/qnetworkaccessmanager.html#sendCustomRequest

@ghost
Copy link

ghost commented Aug 8, 2013

+1 same problem

@papilip
Copy link

papilip commented Aug 13, 2013

+1 we have the same problem

@frobean
Copy link

frobean commented Aug 22, 2013

I ran into this yesterday and didn't have time to wait on an official fix (if one is even planned) so I went in and added sufficient support to get it working for my purposes running on linux. If anyone else would like to patch & build, you can use the following patch. It should apply cleanly to 1.9.1

diff --git src/networkaccessmanager.cpp src/networkaccessmanager.cpp
index fbe8af1..b04463b 100644
--- src/networkaccessmanager.cpp
+++ src/networkaccessmanager.cpp
@@ -59,6 +59,9 @@ static const char *toString(QNetworkAccessManager::Operation op)
     case QNetworkAccessManager::PostOperation:
         str = "POST";
         break;
+    case QNetworkAccessManager::PatchOperation:
+        str = "PATCH";
+        break;
     case QNetworkAccessManager::DeleteOperation:
         str = "DELETE";
         break;
diff --git src/qt/src/3rdparty/webkit/Source/WebCore/platform/network/qt/QNetworkReplyHandler.cpp src/qt/src/3rdparty/webkit/Source/WebCore/platform/network/qt/QNetworkReplyHandler.cpp
index 3cf6439..0921225 100644
--- src/qt/src/3rdparty/webkit/Source/WebCore/platform/network/qt/QNetworkReplyHandler.cpp
+++ src/qt/src/3rdparty/webkit/Source/WebCore/platform/network/qt/QNetworkReplyHandler.cpp
@@ -368,6 +368,8 @@ String QNetworkReplyHandler::httpMethod() const
         return "POST";
     case QNetworkAccessManager::PutOperation:
         return "PUT";
+    case QNetworkAccessManager::PatchOperation:
+        return "PATCH";
     case QNetworkAccessManager::DeleteOperation:
         return "DELETE";
     case QNetworkAccessManager::CustomOperation:
@@ -395,6 +397,8 @@ QNetworkReplyHandler::QNetworkReplyHandler(ResourceHandle* handle, LoadType load
         m_method = QNetworkAccessManager::PostOperation;
     else if (r.httpMethod() == "PUT")
         m_method = QNetworkAccessManager::PutOperation;
+    else if (r.httpMethod() == "PATCH")
+        m_method = QNetworkAccessManager::PatchOperation;
     else if (r.httpMethod() == "DELETE")
         m_method = QNetworkAccessManager::DeleteOperation;
     else
@@ -623,7 +627,7 @@ QNetworkReply* QNetworkReplyHandler::sendNetworkRequest(QNetworkAccessManager* m
         && (!url.toLocalFile().isEmpty() || url.scheme() == QLatin1String("data")))
         m_method = QNetworkAccessManager::GetOperation;

-    if (m_method != QNetworkAccessManager::PostOperation && m_method != QNetworkAccessManager::PutOperation) {
+    if (m_method != QNetworkAccessManager::PostOperation && m_method != QNetworkAccessManager::PutOperation && m_method != QNetworkAccessManager::PatchRequest) {
         // clearing Contents-length and Contents-type of the requests that do not have contents.
         m_request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant());
         m_request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant());
@@ -641,6 +645,15 @@ QNetworkReply* QNetworkReplyHandler::sendNetworkRequest(QNetworkAccessManager* m
             postDevice->setParent(result);
             return result;
         }
+        case QNetworkAccessManager::PatchOperation: {
+            FormDataIODevice* patchDevice = new FormDataIODevice(request.httpBody());
+            // We may be uploading files so prevent QNR from buffering data
+            m_request.setHeader(QNetworkRequest::ContentLengthHeader, patchDevice->getFormDataSize());
+            m_request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, QVariant(true));
+            QNetworkReply* result = manager->patch(m_request, patchDevice);
+            patchDevice->setParent(result);
+            return result;
+        }
         case QNetworkAccessManager::HeadOperation:
             return manager->head(m_request);
         case QNetworkAccessManager::PutOperation: {
diff --git src/qt/src/3rdparty/webkit/Source/WebKit/qt/Api/qwebframe.cpp src/qt/src/3rdparty/webkit/Source/WebKit/qt/Api/qwebframe.cpp
index 04453b6..c517734 100644
--- src/qt/src/3rdparty/webkit/Source/WebKit/qt/Api/qwebframe.cpp
+++ src/qt/src/3rdparty/webkit/Source/WebKit/qt/Api/qwebframe.cpp
@@ -908,6 +908,9 @@ void QWebFrame::load(const QNetworkRequest &req,
         case QNetworkAccessManager::PostOperation:
             request.setHTTPMethod("POST");
             break;
+        case QNetworkAccessManager::PatchOperation:
+            request.setHTTPMethod("PATCH");
+            break;
         case QNetworkAccessManager::DeleteOperation:
             request.setHTTPMethod("DELETE");
             break;
diff --git src/qt/src/network/access/qhttp.cpp src/qt/src/network/access/qhttp.cpp
index dea23f9..984133d 100644
--- src/qt/src/network/access/qhttp.cpp
+++ src/qt/src/network/access/qhttp.cpp
@@ -2254,6 +2254,27 @@ int QHttp::post(const QString &path, const QByteArray &data, QIODevice *to)
     return d->addRequest(new QHttpPGHRequest(header, new QByteArray(data), to));
 }

+int QHttp::patch(const QString &path, QIODevice *data, QIODevice *to )
+{
+    Q_D(QHttp);
+    QHttpRequestHeader header(QLatin1String("PATCH"), path);
+    header.setValue(QLatin1String("Connection"), QLatin1String("Keep-Alive"));
+    return d->addRequest(new QHttpPGHRequest(header, data, to));
+}
+
+/*!
+    \overload
+
+    \a data is used as the content data of the HTTP request.
+*/
+int QHttp::patch(const QString &path, const QByteArray &data, QIODevice *to)
+{
+    Q_D(QHttp);
+    QHttpRequestHeader header(QLatin1String("PATCH"), path);
+    header.setValue(QLatin1String("Connection"), QLatin1String("Keep-Alive"));
+    return d->addRequest(new QHttpPGHRequest(header, new QByteArray(data), to));
+}
+
 /*!
     Sends a header request for \a path to the server set by setHost()
     or as specified in the constructor.
diff --git src/qt/src/network/access/qhttp.h src/qt/src/network/access/qhttp.h
index 9700e6e..1590806 100644
--- src/qt/src/network/access/qhttp.h
+++ src/qt/src/network/access/qhttp.h
@@ -223,6 +223,8 @@ public:
     int get(const QString &path, QIODevice *to=0);
     int post(const QString &path, QIODevice *data, QIODevice *to=0 );
     int post(const QString &path, const QByteArray &data, QIODevice *to=0);
+    int patch(const QString &path, QIODevice *data, QIODevice *to=0 );
+    int patch(const QString &path, const QByteArray &data, QIODevice *to=0);
     int head(const QString &path);
     int request(const QHttpRequestHeader &header, QIODevice *device=0, QIODevice *to=0);
     int request(const QHttpRequestHeader &header, const QByteArray &data, QIODevice *to=0);
diff --git src/qt/src/network/access/qhttpnetworkrequest.cpp src/qt/src/network/access/qhttpnetworkrequest.cpp
index 617541f..78a2d82 100644
--- src/qt/src/network/access/qhttpnetworkrequest.cpp
+++ src/qt/src/network/access/qhttpnetworkrequest.cpp
@@ -90,6 +90,9 @@ QByteArray QHttpNetworkRequestPrivate::methodName() const
     case QHttpNetworkRequest::Post:
         return "POST";
         break;
+    case QHttpNetworkRequest::Patch:
+        return "PATCH";
+        break;
     case QHttpNetworkRequest::Options:
         return "OPTIONS";
         break;
diff --git src/qt/src/network/access/qhttpnetworkrequest_p.h src/qt/src/network/access/qhttpnetworkrequest_p.h
index 3b98342..4b6d654 100644
--- src/qt/src/network/access/qhttpnetworkrequest_p.h
+++ src/qt/src/network/access/qhttpnetworkrequest_p.h
@@ -69,6 +69,7 @@ public:
         Get,
         Head,
         Post,
+        Patch,
         Put,
         Delete,
         Trace,
diff --git src/qt/src/network/access/qnetworkaccesshttpbackend.cpp src/qt/src/network/access/qnetworkaccesshttpbackend.cpp
index 1d048ee..ffd6b32 100644
--- src/qt/src/network/access/qnetworkaccesshttpbackend.cpp
+++ src/qt/src/network/access/qnetworkaccesshttpbackend.cpp
@@ -171,6 +171,7 @@ QNetworkAccessHttpBackendFactory::create(QNetworkAccessManager::Operation op,
     switch (op) {
     case QNetworkAccessManager::GetOperation:
     case QNetworkAccessManager::PostOperation:
+    case QNetworkAccessManager::PatchOperation:
     case QNetworkAccessManager::HeadOperation:
     case QNetworkAccessManager::PutOperation:
     case QNetworkAccessManager::DeleteOperation:
@@ -453,6 +454,12 @@ void QNetworkAccessHttpBackend::postRequest()
         createUploadByteDevice();
         break;

+    case QNetworkAccessManager::PatchOperation:
+        invalidateCache();
+        httpRequest.setOperation(QHttpNetworkRequest::Patch);
+        createUploadByteDevice();
+        break;
+
     case QNetworkAccessManager::PutOperation:
         invalidateCache();
         httpRequest.setOperation(QHttpNetworkRequest::Put);
diff --git src/qt/src/network/access/qnetworkaccessmanager.cpp src/qt/src/network/access/qnetworkaccessmanager.cpp
index 84b1b4b..6cc63d2 100644
--- src/qt/src/network/access/qnetworkaccessmanager.cpp
+++ src/qt/src/network/access/qnetworkaccessmanager.cpp
@@ -715,6 +715,56 @@ QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, const
     return reply;
 }

+QNetworkReply *QNetworkAccessManager::patch(const QNetworkRequest &request, QHttpMultiPart *multiPart)
+{
+    QNetworkRequest newRequest = d_func()->prepareMultipart(request, multiPart);
+    QIODevice *device = multiPart->d_func()->device;
+    QNetworkReply *reply = patch(newRequest, device);
+    return reply;
+}
+
+/*!
+ Uploads the contents of \a data to the destination \a request and
+ returnes a new QNetworkReply object that will be open for reply.
+ 
+ \a data must be opened for reading when this function is called
+ and must remain valid until the finished() signal is emitted for
+ this reply.
+ 
+ Whether anything will be available for reading from the returned
+ object is protocol dependent. For HTTP, the server may send a
+ small HTML page indicating the upload was successful (or not).
+ Other protocols will probably have content in their replies.
+ 
+ \note For HTTP, this request will send a PUT request, which most servers
+ do not allow. Form upload mechanisms, including that of uploading
+ files through HTML forms, use the POST mechanism.
+ 
+ \sa get(), post(), deleteResource(), sendCustomRequest()
+ */
+QNetworkReply *QNetworkAccessManager::patch(const QNetworkRequest &request, QIODevice *data)
+{
+    return d_func()->postProcess(createRequest(QNetworkAccessManager::PatchOperation, request, data));
+}
+
+/*!
+ \overload
+ 
+ Sends the contents of the \a data byte array to the destination
+ specified by \a request.
+ */
+QNetworkReply *QNetworkAccessManager::patch(const QNetworkRequest &request, const QByteArray &data)
+{
+    QBuffer *buffer = new QBuffer;
+    buffer->setData(data);
+    buffer->open(QIODevice::ReadOnly);
+    
+    QNetworkReply *reply = patch(request, buffer);
+    buffer->setParent(reply);
+    return reply;
+}
+
+
 /*!
     \since 4.6

diff --git src/qt/src/network/access/qnetworkaccessmanager.h src/qt/src/network/access/qnetworkaccessmanager.h
index 26a28e1..52b8452 100644
--- src/qt/src/network/access/qnetworkaccessmanager.h
+++ src/qt/src/network/access/qnetworkaccessmanager.h
@@ -84,6 +84,7 @@ public:
         PutOperation,
         PostOperation,
         DeleteOperation,
+        PatchOperation,
         CustomOperation,

         UnknownOperation = 0
@@ -121,6 +122,9 @@ public:
     QNetworkReply *put(const QNetworkRequest &request, QIODevice *data);
     QNetworkReply *put(const QNetworkRequest &request, const QByteArray &data);
     QNetworkReply *put(const QNetworkRequest &request, QHttpMultiPart *multiPart);
+    QNetworkReply *patch(const QNetworkRequest &request, QIODevice *data);
+    QNetworkReply *patch(const QNetworkRequest &request, const QByteArray &data);
+    QNetworkReply *patch(const QNetworkRequest &request, QHttpMultiPart *multiPart);
     QNetworkReply *deleteResource(const QNetworkRequest &request);
     QNetworkReply *sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, QIODevice *data = 0);

diff --git src/webpage.cpp src/webpage.cpp
index c76a4b8..a56b46a 100644
--- src/webpage.cpp
+++ src/webpage.cpp
@@ -817,6 +817,8 @@ void WebPage::openUrl(const QString &address, const QVariant &op, const QVariant
         networkOp = QNetworkAccessManager::PutOperation;
     else if (operation == "post")
         networkOp = QNetworkAccessManager::PostOperation;
+    else if (operation == "patch")
+        networkOp = QNetworkAccessManager::PatchOperation;
     else if (operation == "delete")
         networkOp = QNetworkAccessManager::DeleteOperation;

@dominicbarnes
Copy link

+1

@paultyng
Copy link

I think that Phantom may be already using a custom Qt build, but not sure, so not sure if we should actually customize Qt per that patch or add some other implementation for this.

@mdaverde
Copy link

+1

2 similar comments
@sasata299
Copy link

+1

@StevePotter
Copy link

+1

@butzopower
Copy link

+1, we banged our head on this for a while

@obweger
Copy link

obweger commented Oct 8, 2013

+1 - also, patch requests have content type application/octet-stream, regardless of what you set in code (using jQuery's "ajax" method).

@prasadc82
Copy link

+1

1 similar comment
@tarsolya
Copy link

tarsolya commented Nov 9, 2013

+1

@cletourneau
Copy link

+42

@mattsongb
Copy link

+4

@dezmathio
Copy link

+1

1 similar comment
@Rejjn
Copy link

Rejjn commented Dec 6, 2013

+1

@Mr0grog
Copy link

Mr0grog commented Dec 10, 2013

Would love to see this fixed, as it is a big problem for tests in an app I'm working on.

@tpendragon
Copy link

+1 Can't believe how long I spent figuring this out. Switched to PUT for the moment.

@mikeverbeck
Copy link

+1 spent my morning on this one. Used PUT for now.

@tpendragon
Copy link

Just a note, I had a talk with a colleague and he reminded me of what was wrong with this. It's generally assumed that browsers -won't- support PATCH/PUT/DELETE, so we went from using PUT to using POST and passing a form parameter defining what the method is (we're using Rails, so that was pretty easy - hidden form input named _method that has the value "PATCH").

That isn't to say PhantomJS shouldn't support PATCH, totally should, just a reminder for those who used the workaround I did.

@BartlomiejSkwira
Copy link

I had an issue with DELETE and jquery

//ids is not empty => 731050736,731050734
$.ajax({
            url: 'my_controller/destroy_multiple',
            type: 'delete',
            data: { ids: ids },
            success: function() {
              location.reload(true);
            }
          });

the id param was empty in backend. Also changed method to PUT and it worked.

ps: DELETE worked under Selenium

@frobean
Copy link

frobean commented Jan 7, 2014

Delete, like patch, doesn't send the message body in phantomjs. Hopefully this all gets fixed in a future rev.

Sent from my iPad

On Jan 7, 2014, at 8:44 AM, BartlomiejSkwira notifications@github.com wrote:

I had an issue with DELETE and jquery

//ids is not empty => 731050736,731050734
$.ajax({
url: 'my_controller/destroy_multiple',
type: 'delete',
data: { ids: ids },
success: function() {
location.reload(true);
}
});
the id param was empty in backend. Also changed method to PUT and it worked.


Reply to this email directly or view it on GitHub.

mislav added a commit to JakeChampion/fetch that referenced this issue Oct 31, 2014
PhantomJS doesn't seem to send request body for PATCH:
ariya/phantomjs#11384
@rept
Copy link

rept commented Nov 3, 2014

+1 experiencing the same problem! Anybody trying to get this integrated into the main branch? It's an issue for over a year, but still lots of people are searching crazy why tests are failing to find out this is a known issue.

@Borzik
Copy link

Borzik commented Nov 7, 2014

Hey! If someone has more info on that Qt WebKit issue, pls share it in the issue I created at QtWebKit issue tracker.
Hope it will be resolved someday.

@artemave
Copy link

artemave commented Nov 7, 2014

+1

@Lail
Copy link

Lail commented Nov 18, 2014

+1 - Just spent more time than I care to admit trying to figure out why my test wasn't passing. Using PUT for now.

@jtmalinowski
Copy link

+1

@Muscot
Copy link

Muscot commented Dec 15, 2014

+1 also spent a couple of hours on this problem.

@corklaassebos
Copy link

+3 hours wasted

@KevP
Copy link

KevP commented Dec 23, 2014

+10 DELETE not working... pls fix...

@swebb
Copy link

swebb commented Jan 12, 2015

Summary:

  1. Qt 4.8 doesn't support patch with a body (see here). Qt5 does support patch with a body.
  2. phantomjs 1.9 is built against qt 4.8 (see here)
  3. phantomjs 2 is build against Qt 5, so in theory this should be automatically fixed. However, there is no release date for that.
  4. @frobean's patch can be used to make this work (haven't tried it myself).
  5. I couldn't find any intention of getting @frobean's patch merged.

davidwallacejackson pushed a commit to davidwallacejackson/irc-client that referenced this issue Jan 15, 2015
ariya/phantomjs#11384

Lack of bind() was already pretty bad, but silently failing on a perfectly
normal type of HTTP request is more than I want to work around. Supposedly,
version 2.0 will fix this issue (see the GitHub thread) and maybe bind() as
well, so I'll give Phantom another shot then. Until then I'll just stick with
Chrome/FF, and look into Sauce if I want continuous integration.
@mockdeep
Copy link

Just ran into this issue myself. Switching it to put worked.

@jtmalinowski
Copy link

BTW PhantomJS 2.0 is going to be out soon.

base10 pushed a commit to base10/addressbook that referenced this issue Apr 10, 2015
- NB: PATCH doesn't work in tests for reasons
  - ariya/phantomjs#11384
  - thoughtbot/capybara-webkit#553
base10 pushed a commit to base10/addressbook that referenced this issue Apr 10, 2015
- NB: Using PUT now because PATCH doesn't work in tests for reasons
  - ariya/phantomjs#11384
  - thoughtbot/capybara-webkit#553
@dholdren
Copy link

dholdren commented May 7, 2015

this is fixed in 2.0, shouldn't this be closed?

@sfontana
Copy link

I don't think this was fixed in 2.0, sadface.

@3rd-Eden
Copy link

How is this not resolved yet?! There are patch files and countless of people needing this.

@Sinewyk
Copy link

Sinewyk commented Aug 31, 2015

It is fixed in 2.0, did a quick test case to confirm it. It's just that 2.x cannot take off until binary are available for all platforms so, wait or help =)

@reidcooper
Copy link

I am having this issue currently. PhantomJS 2.1.X. My BackboneJS PATCH requests are sending empty body requests to my rails api.

@andremleblanc
Copy link

I had this problem too, I just switched to PUT for now; but, it would be nice to have this.

@dholdren
Copy link

This bug is back in 2.1 ?

@reidcooper
Copy link

On my local machine, I am properly able to perform a PATCH request with the body that I want, however I am using Semaphore for my CI. My same tests do not perform the PATCH requests. I am curious on what could not be sending them.

@dholdren
Copy link

@reidcooper semaphore defaults to PhantomJS 1.9.8 https://semaphoreci.com/docs/phantomjs.html

@reidcooper
Copy link

@dholdren that seems to have fixed my problem, thanks a lot. However, I did not think about this before because in my Gemfile.lock, I do have version 2.1 of phantomjs?

@kannans
Copy link

kannans commented Aug 3, 2016

👍 PUT method working thanks @here

@stale stale bot added the stale label Dec 25, 2019
@stale
Copy link

stale bot commented Dec 28, 2019

Due to our very limited maintenance capacity (see #14541 for more details), we need to prioritize our development focus on other tasks. Therefore, this issue will be automatically closed. In the future, if we see the need to attend to this issue again, then it will be reopened. Thank you for your contribution!

@stale stale bot closed this as completed Dec 28, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests