PHP & Others

Following redirects with Curl in PHP

페이지 정보

본문

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_PUTCURLOPT_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:

  1. Don’t use CURLOPT_INFILE, but send the request body as a string instead, with CURLOPT_POSTFIELDS.
  2. 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.

관련자료

등록된 댓글이 없습니다.
Today's proverb
마음이 나누어주는 것은 결코 사라지지 않습니다. 그것은 다른 사람들의 가슴 안에 저장됩니다. (로빈 세인트 존)