cURL: forward POST over HTTP redirections
Recently, I've been stuck on a Web service call that wouldn't be called as supposed to be. When having troubles with WS, it's very important to dump client request and server response.
Using cURL withing PHP, I couldn't understand why my code was building a POST
request and cURL returned me a GET
request instead when the URI was getting a 301
redirection.
Turned out that it was a configuration issue because the URI wasn't the right one. Thanks to the config team!
What cURL says
When curl follows a redirect and the request is not a plain GET (for example POST or PUT), it will do the following request with a GET if the HTTP response was 301, 302, or 303. If the response code was any other 3xx code, curl will re-send the following request using the same unmodified method.
Make it work
Server
Create a server.php
file and just dump the $_POST
variable:
<?php
var_export($_POST);
Client
Create client.php
and build a POST
cURL request to server.php
:
<?php
header('Content-Type: text/plain');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://localhost/server.php');
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, array('params' => 'foo-bar-baz'));
$response = curl_exec($ch);
echo 'REQUEST HEADERS:' . PHP_EOL . PHP_EOL;
echo curl_getinfo($ch)['request_header'];
echo 'RESPONSE:' . PHP_EOL . PHP_EOL;
echo $response;
curl_close($ch);
Runs the client.php
and it should output this:
REQUEST HEADERS:
POST /server.php HTTP/1.1
Host: localhost
Accept: */*
Content-Length: 152
Expect: 100-continue
Content-Type: multipart/form-data; boundary=----------------------------f8cc685e6441
RESPONSE:
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Date: Wed, 07 May 2014 10:41:36 GMT
Server: Apache/2.2.24 (Unix) mod_ssl/2.2.24 OpenSSL/0.9.8y PHP/5.4.27
X-Powered-By: PHP/5.4.27
Content-Length: 38
Content-Type: text/html; charset=UTF-8
Array
(
[params] => foo-bar-baz
)
Redirection
Create a redirect.php
file and put this in it:
<?php
header('Location: http://localhost/server.php');
exit;
Update the client URI from http://localhost/server.php
to http://localhost/redirect.php
and run again the client:
REQUEST HEADERS:
GET /server.php HTTP/1.1
Host: localhost
Accept: */*
RESPONSE:
HTTP/1.1 100 Continue
HTTP/1.1 302 Found
Date: Wed, 07 May 2014 10:45:19 GMT
Server: Apache/2.2.24 (Unix) mod_ssl/2.2.24 OpenSSL/0.9.8y PHP/5.4.27
X-Powered-By: PHP/5.4.27
Location: http://localhost/server.php
Content-Length: 0
Content-Type: text/html; charset=UTF-8
HTTP/1.1 200 OK
Date: Wed, 07 May 2014 10:45:19 GMT
Server: Apache/2.2.24 (Unix) mod_ssl/2.2.24 OpenSSL/0.9.8y PHP/5.4.27
X-Powered-By: PHP/5.4.27
Content-Length: 10
Content-Type: text/html; charset=UTF-8
Array
(
)
It's obvious that the POST
has been converted to GET
and no more data is forwarded.
Some people say that you will need to set the CURLOPT_CUSTOMREQUEST
cURL option to go through. Indeed, when adding this option, the HTTP verb is well kept:
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
However, the data is still lost. What's the solution then?
Using a 307 redirection
To keep your data, you mustn't use a 301
, 302
or 303
redirection but the 307
redirection:
307 Temporary Redirect (since HTTP/1.1)
In this case, the request should be repeated with another URI; however, future requests should still use the original URI. In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request. For instance, a POST request should be repeated using another POST request.
Give it a try, update your redirect.php
file:
<?php
header('HTTP/1.1 307 Temporary Redirect');
header('Location: http://localhost/server.php');
exit;
Then run the client again:
REQUEST HEADERS:
POST /server.php HTTP/1.1
Host: localhost
Accept: */*
Content-Length: 152
Expect: 100-continue
Content-Type: multipart/form-data; boundary=----------------------------cebd8214326b
RESPONSE:
HTTP/1.1 100 Continue
HTTP/1.1 307 Temporary Redirect
Date: Wed, 07 May 2014 10:58:33 GMT
Server: Apache/2.2.24 (Unix) mod_ssl/2.2.24 OpenSSL/0.9.8y PHP/5.4.27
X-Powered-By: PHP/5.4.27
Location: http://localhost/server.php
Content-Length: 0
Content-Type: text/html; charset=UTF-8
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Date: Wed, 07 May 2014 10:58:33 GMT
Server: Apache/2.2.24 (Unix) mod_ssl/2.2.24 OpenSSL/0.9.8y PHP/5.4.27
X-Powered-By: PHP/5.4.27
Content-Length: 38
Content-Type: text/html; charset=UTF-8
Array
(
[params] => foo-bar-baz
)
You're all set!
There is a 308
redirection which should do the same as the 307
but for permanent redirection but doesn't seem to be supported by cURL yet.
Thanks for reading and happy data forwarding!