Following redirects with Curl in PHP.
As a good web citizen, I try to always follow redirects. Not just in my browser, where I actually don’t have all that much control over things, but also a consumer of web services.
When doing requests with CURL, redirects are not followed by default.
<?php
$curl = curl_init('http://example.org/someredirect');
curl_setopt($curl, CURLOPT_POSTFIELDS, "foo");
curl_setopt($curl, CURLOPT_POST, true);
curl_exec($curl);
?>
Assuming the given url actually redirects like this:
HTTP/1.1 301 Moved Permanently
Location: /newendpoint
Curl will automatically just stop. To make it follow redirects, the
FOLLOWLOCATION
setting is needed, as such:
<?php
$curl = curl_init('http://example.org/someredirect');
curl_setopt($curl, CURLOPT_POSTFIELDS, "foo");
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_exec($curl);
?>
CURLOPT_FOLLOWLOCATION
will follow the redirects up to 5 times (by default).
However, if you look at the second request, it actually does a GET
request
after the POST
.
GET /newendpoint HTTP/1.1
This is also the default behavior for browsers, but actually non-conforming with the HTTP standard, and also not desirable for consumers of web services.
To fix this, all you have to do is use CURLOPT_CUSTOMREQUEST
instead of
CURLOPT_POST
:
<?php
$curl = curl_init('http://example.org/someredirect');
curl_setopt($curl, CURLOPT_POSTFIELDS, "foo");
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_exec($curl);
?>
Streams
After doing this, the secondary request will be a POST
request as well.
There’s one more issue though, if you were doing a POST
or a PUT
request
you probably had a request body attached.
There’s two ways to supply a request body, as a string or as a stream. If we were uploading a file it makes much more sense to use a stream, because it unlike posting a string, a stream doesn’t have to be kept in memory.
To upload a stream with curl, you need CURLOPT_PUT
and CURLOPT_INFILE
.
Don’t let the name CURLOPT_PUT
fool you, it’s use for every request, and
without CURLOPT_PUT
, CURLOPT_INFILE
is ignored.
For example, this is how we could upload a large file using POST.
<?php
$curl = curl_init('http://example.org/someredirect');
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_PUT, true);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($curl, CURLOPT_INFILE, fopen('largefile.json', 'r'));
curl_exec($curl);
?>
This will work great, unless the target location redirects. If it does, curl will throw the following error:
Necessary data rewind wasn't possible (code #65)
This seems to be related to PHP bug #47204.
Basically this means that you cannot use CURLOPT_INFILE
and
CURLOPT_FOLLOWLOCATION
together. There’s two alternatives:
- Don’t use
CURLOPT_INFILE
, but send the request body as a string instead, withCURLOPT_POSTFIELDS
. - Don’t use
CURLOPT_FOLLOWLOCATION
, but instead manually check if the response was a 3xx redirect and manually follow each hop.
Strings
Using CURLOPT_POSTFIELDS
you can supply a request body as a string. Lets
try to upload our earlier failed request using that method:
<?php
$curl = curl_init('http://example.org/someredirect');
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($curl, CURLOPT_POSTFIELDS, file_get_contents('largefile.json'));
curl_exec($curl);
?>
This also will not work exactly as you expect. While the second request to
/someredirect
will still be a POST request, it will be sent with an empty
request body.
To fix this, use the undocumented CURLOPT_POSTREDIR
option.
<?php
$curl = curl_init('http://example.org/someredirect');
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($curl, CURLOPT_POSTFIELDS, file_get_contents('largefile.json'));
curl_setopt($curl, CURLOPT_POSTREDIR, 3);
curl_exec($curl);
?>
According to the PHP changelog, this was added in PHP 5.3.2, and according to PHP bug #49571 there are four possible values:
0 -> do not set any behavior
1 -> follow redirect with the same type of request only for 301 redirects.
2 -> follow redirect with the same type of request only for 302 redirects.
3 -> follow redirect with the same type of request both for 301 and 302 redirects.
Comments
MeadSteve •
I guess when you get a 301/308 it'd normally be worth logging something as well, for the maintainer of the code to take some action. Versus a 302/307 where you'd be happy for the code to do this silently.
Ed Dikotope •
Best answer to my problem over the entire internet. Thank you very much.
Tom Binga •
You helped solved a problem that's been holding me back for a week or so now. Thank you!
Marcos Saturno •
Very good info!
I'm still having problems when trying to write onto web HDFS (hadoop) from a PHP script. If I make a PUT request passing CREATE as a parameter, it will provide me a 307 temporary redirect, so i'd need to make a second PUT request to the new URL provided. I'm not able to follow it or at least slipt the redirect URL from the HTTP Response. Could you please help me with it?
DOC:
http://hadoop.apache.org/do...
I'm using something like:
$options = array(
// CURLOPT_PUT => true,
CURLOPT_HEADER => true,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_FOLLOWLOCATION => true
);
$ch = curl_init('<host>:<port>/webhdfs/v1/user/USER?op=CREATE');
// Execute
curl_setopt_array($ch, $options);
curl_exec($ch);
//echo curl_errno($ch);
if(!curl_errno($ch))
{
$info = curl_getinfo($ch);
echo 'Took ' . $info['total_time'] . ' seconds to send a request to ' . $info['url'];
}
// Close handle
curl_close($ch);
?>
Marcos Saturno •
*I commented the //CURLOPT_PUT, but also tested with both on. ;-)
Evert •
Does your request not have a body? I wonder if that messes things up. You may want to PUT an empty string instead of nothing at all, because curl may fall back to 'GET' behavior (although with a PUT method).
Marcos Saturno •
Hmm, good tip!
But actually I found some classes ready to use WEB HDFS:
https://github.com/simpleen...
Tx for the help, anyways!
Regards,
Marcos.
Roger Qiu •
I don't feel secure knowing my request might be redirected to some place I don't know.
Shanly •
Awesome post! This solved my problem, thanks.
Bub •
Well done!
Arlanthir •
Thank you for this.
Ajai •
i am doing an request using Advanced REST Client and i find some redirects (not cached) , but i get the actual response. But when i try the same using PHP curl it doesnt work , i dont get the output , but the redirected output is displayed.
Please can any one help me
richyrich •
Thank you so much for this article, saved my time. All the best!
Hassan Nomani Alvi •
I am trying to post data using curl.After posting I would like to go to the url.In other words I am trying to get same functionality as we get with form method="post" and action="someurl.php" .How to do this?Thanks in advance.