In PHP, how can I replicate the expand/contract feature for Tinyurls as on search.twitter.com?
If you want to find out where a tinyurl is going, use fsockopen to get a connection to tinyurl.com on port 80, and send it an HTTP request like this
GET /dmsfm HTTP/1.0
Host: tinyurl.com
The response you get back will look like
HTTP/1.0 301 Moved Permanently
Connection: close
X-Powered-By: PHP/5.2.6
Location: http://en.wikipedia.org/wiki/TinyURL
Content-type: text/html
Content-Length: 0
Date: Mon, 15 Sep 2008 12:29:04 GMT
Server: TinyURL/1.6
example code...
<?php
$tinyurl="dmsfm";
$fp = fsockopen("tinyurl.com", 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET /$tinyurl HTTP/1.0\r\n";
$out .= "Host: tinyurl.com\r\n";
$out .= "Connection: Close\r\n\r\n";
$response="";
fwrite($fp, $out);
while (!feof($fp)) {
$response.=fgets($fp, 128);
}
fclose($fp);
//now parse the Location: header out of the response
}
?>
And here is how to contract an arbitrary URL using the TinyURL API. The general call pattern goes like this, it's a simple HTTP request with parameters:
http://tinyurl.com/api-create.php?url=http://insertyourstuffhere.com
This will return the corresponding TinyURL for http://insertyourstuffhere.com. In PHP, you can wrap this in an fsockopen() call or, for convenience, just use the file() function to retrieve it:
function make_tinyurl($longurl)
{
return(implode('', file(
'http://tinyurl.com/api-create.php?url='.urlencode($longurl))));
}
// make an example call
print(make_tinyurl('http://www.joelonsoftware.com/items/2008/09/15.html'));
As people have answered programatically how to create and resolve tinyurl.com redirects, I'd like to (strongly) suggest something: caching.
In the twitter example, if every time you clicked the "expand" button, it did an XmlHTTPRequest to, say, /api/resolve_tinyurl/http://tinyurl.com/abcd, then the server created a HTTP connection to tinyurl.com, and inspected the header - it would destroy both twitter and tinyurl's servers..
An infinitely more sensible method would be to do something like this Python'y pseudo-code..
def resolve_tinyurl(url):
key = md5( url.lower_case() )
if cache.has_key(key)
return cache[md5]
else:
resolved = query_tinyurl(url)
cache[key] = resolved
return resolved
Where cache's items magically get backed up into memory, and/or a file, and query_tinyurl() works as Paul Dixon's answer does.
Here is another way to decode short urls via CURL library:
function doShortURLDecode($url) {
$ch = #curl_init($url);
#curl_setopt($ch, CURLOPT_HEADER, TRUE);
#curl_setopt($ch, CURLOPT_NOBODY, TRUE);
#curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
#curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$response = #curl_exec($ch);
preg_match('/Location: (.*)\n/', $response, $a);
if (!isset($a[1])) return $url;
return $a[1];
}
It's described here.
Another simple and easy way:
<?php
function getTinyUrl($url) {
return file_get_contents('http://tinyurl.com/api-create.php?url='.$url);
}
?>
If you just want the location, then do a HEAD request instead of GET.
$tinyurl = 'http://tinyurl.com/3fvbx8';
$context = stream_context_create(array('http' => array('method' => 'HEAD')));
$response = file_get_contents($tinyurl, null, $context);
$location = '';
foreach ($http_response_header as $header) {
if (strpos($header, 'Location:') === 0) {
$location = trim(strrchr($header, ' '));
break;
}
}
echo $location;
// http://www.pingdom.com/reports/vb1395a6sww3/check_overview/?name=twitter.com%2Fhome
In PHP there is also a get_headers function that can be used to decode tiny urls.
The Solution here from #Pons solution, didn't work alone on my php7.3 server reslolving stackexchange URLs like https://stackoverflow.com/q/62317
This solved it:
public function doShortURLDecode($url) {
$ch = #curl_init($url);
#curl_setopt($ch, CURLOPT_HEADER, TRUE);
#curl_setopt($ch, CURLOPT_NOBODY, TRUE);
#curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
#curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$response = #curl_exec($ch);
$cleanresponse= preg_replace('/[^A-Za-z0-9\- _,.:\n\/]/', '', $response);
preg_match('/Location: (.*)[\n\r]/', $cleanresponse, $a);
if (!isset($a[1])) return $url;
return parse_url($url, PHP_URL_SCHEME).'://'.parse_url($url, PHP_URL_HOST).$a[1];
}
Related
Is there a way for PHP CURL functions to get the contents of the website, but stopped on the characters that we just ask. I think this sort of buffer.
so the script did not call the overall page
So schemes like this:
: curl execution
<html>
->
->
->
-> Title Detected
: curl close
->
->
->
->
</ html>
Please this is not a DOM problem. But how to curl stops when it finds that we ask.
this is my code :
function curl_download($Url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $Url);
curl_setopt($ch, CURLOPT_REFERER, $Url);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.46 Safari/536.5");
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
If cURL can't handle this problem, how about fopen? and do you have an example?
Thanks before.Also please give me your example code for me,, thanks
Here is a very simple example using fsockopen(). Extend it to fit your needs.
$host = 'www.site.com';
$port = 80;
$sock = fsockopen($host, $port, $errno, $errstr, 30);
if (!$sock) {
die("Failed to connect. $errno: $errstr");
}
// write http request to socket:
$request = "GET /file.html HTTP/1.0\r\n"
."Host: $host\r\n"
."User-Agent: some-user-agent\r\n"
."Connection: close\r\n"
."\r\n";
fwrite($sock, $request);
$buffer = ''; // buffer for storing response
while (!feof($sock)) {
$buffer .= fgets($sock, 1024); // read 1024 bytes from socket, append to buffer
if (strpos($buffer, '</title>') !== false)) { // title was found
fclose($sock);
break;
}
}
So we connect to the HTTP server on the remote host, issue a simple HTTP/1.0 request, and read the response 1024 bytes at a time until the closing title tag is detected. Once it is found, the connection is closed.
Note, even though we didn't read the entire response from the socket, the underlying system (PHP and the OS socket layer) may have read more (or possibly all depending on size) of the response. In either case, you did prevent PHP from reading most of the response. If the pages are very big, closing the socket early will likely prevent a bulk of the data from actually ever being received.
Hope that helps.
I do not think you can parse the DOM with CURL
I advise you to use the string function like strstr, strtok...
http://www.php.net/manual/en/ref.strings.php
first time asker, but many times you helped me back in the day. Great job! I ask this because I'm struggling here with and issue I'm unable to solve, and as my PHP (and cURL) knowledge is so scarce, I'm lost.
The Background
I'm developing a Javascript app, that needs to connect to several different servers and make XMLRPC calls to them. The app is working perfectly running it locally (disabling cross-domain security), but to make it run online I knew I had to use a cross-domain proxy, so after several days of searching and investigating, I didn't found one that could make the work, so I managed to make one myself (not without blood and sweat). Know what? It (almost) works!!!
This is my proxy.php:
<?
function readHeader($ch, $header) {
//extracting data to send it to the client
$headers = explode("\n", $header);
foreach ($headers as $item) {
// $string= str_replace($delimiter, $mainDelim, $string);
if (strpos($item, 'Set-Cookie:') !== false) {
$cookie = trim(substr($item,strlen('Set-Cookie:')));
header('X-Set-Cookie:' . $cookie);
} else {
header($item);
}
}
return strlen($header);
}
$allowed_domains = array('domain1.com', 'domain2.com');
header('Content-Type: text/html; charset=iso-8859-1');
$REFERRER = $_SERVER['HTTP_REFERER'];
if ($REFERRER == '') {
// What do you do here?
exit(header('Location: index.html'));
}
$domain = substr($REFERRER, strpos($REFERRER, '://') + 3);
$domain = substr($domain, 0, strpos($domain, '/'));
if (!in_array($domain, $allowed_domains)) {
exit(header('Location: index.html'));
}
$XMLRPC_SERVICE = $_SERVER['HTTP_X_PROXY_URL'];
$xml = $HTTP_RAW_POST_DATA;
$header[] = "Content-type: text/xml; charset=utf-8";
$header[] = "Connection: close";
$header[] = "Accept: text/xml";
if ($_SERVER['HTTP_X_SET_COOKIE'])
$cookie = $_SERVER['HTTP_X_SET_COOKIE'];
if ($_SERVER['HTTP_X_PROXY_URL'] === "other-domain.com")
$header[] = "x-custom-header: value";
$ch = curl_init($XMLRPC_SERVICE);
//URL to post to
curl_setopt($ch, CURLOPT_URL, $XMLRPC_SERVICE);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
if ($cookie)
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
curl_setopt($ch, CURLOPT_HEADERFUNCTION, 'readHeader');
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo curl_error($ch);
} else {
curl_close($ch);
echo $response;
}
?>
The Issue
As I've said, I got it working partially. In fact, it works for most of the usual XMLRPC needs.
It gets the remote server address from the HTTP_X_PROXY_URL header of the request, and using cURL makes the call and returns the values to the javascript client without issues.
The problem comes when I need to get/send a session cookie (probably when getting it, because the cookie value is pretty different when I make calls directly from the app locally). In any case, I can't get the cookie stuff to work. As you see, I'm surrounding the Set-Cookie browser protection on AJAX calls with my own X-Set-Cookie header, that the proxy gets to use or translates accordingly, but the issue with cookies is here, and I can't use cookies, that are critical for app functionality.
I need a PHP cURL configuration so that my script is able to send requests and ignore the answers sent by the API.
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_POST,count($fields));
curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string);
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
//curl_setopt($ch, CURLOPT_TIMEOUT_MS, 100);
$result = curl_exec($ch);
echo $result;
curl_close ($ch);
I tried adding:
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
//curl_setopt($ch, CURLOPT_TIMEOUT_MS, 100);
But its not working properly and the API webserver is not receiving the requests.
The reason for this is I am sending large amount of requests to the API therefore my script is very slow because it waits for each and every request.
Any help is appreciated.
Sender file example ./ajax/sender.php
Script sending POST -> it makes full request to host, but it doesn't wait on answer from server : CURLOPT_HEADER(0) we dont needs headers from server) and CURLOPT_RETURNTRANSFER (false) we don't needs data from server.CURLOPT_TIMEOUT - Extra procteted : We waiting after sent only 1ms on respond server, this is extra quaranty to not wait any more ms if server keep us. ### NOTE ### HTTP1.1 has one package max 16kb. HTTP2 has 36kb one pacakge. If POST are more biggest, server will be send with many packages in series = $SIZE%16kb
$url = 'https://127.0.0.1/ajax/received.php';
$curl = curl_init();
$post['test'] = 'examples daata'; // our data todo in received
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt ($curl, CURLOPT_POST, TRUE);
curl_setopt ($curl, CURLOPT_POSTFIELDS, $post);
curl_setopt($curl, CURLOPT_USERAGENT, 'api');
//curl_setopt($curl, CURLOPT_TIMEOUT, 1); //if your connect is longer than 1s it lose data in POST better is finish script in recevie
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
curl_setopt($curl, CURLOPT_FORBID_REUSE, true);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 1);
curl_setopt($curl, CURLOPT_DNS_CACHE_TIMEOUT, 100);
curl_setopt($curl, CURLOPT_FRESH_CONNECT, true);
curl_exec($curl);
curl_close($curl);
Received file example ./ajax/received.php
ignore_user_abort(true); //if connect is close, we continue php script in background up to script will be end
header("Connection: close\r\n");
header("Content-Encoding: none\r\n");
header("Content-Length: 1");
### we just close connect above if webbrowser, or request waiting on answer ( we know we set CURLOP to not wait) ###
ob_end_clean(); //just flush all content if exists to request. If server still waiting on answer.
//HERE all script doing in background: Example
$this->db->query('UPDATE new_hook_memory SET new=new+1 WHERE id=1');
EDIT 2019 if you using fastcgi just finish fastcgi and browser close connection but script still will be working up to end.
How finish script: PHP mod_fcgi with fastcgi_finish_request();
For Apache2:
ob_end_flush();
flush();
For php-fpm
fastcgi_finish_request(); $this->db->query('UPDATE new_hook_memory SET new=new+1 WHERE id=1');
Old version:
These two solutions work well for me
( Of course it has been a long time, but I don't think this question is outdated )
using file_get_contents:
//url of php to be called
$url = "example.php/test?id=1";
//this will set the minimum time to wait before proceed to the next line to 1 second
$ctx = stream_context_create(['http'=> ['timeout' => 1]]);
file_get_contents($url,null,$ctx);
//the php will read this after 1 second
using cURL:
//url of php to be called
$url = "example.php/test?id=1";
$test = curl_init();
//this will set the minimum time to wait before proceed to the next line to 100 milliseconds
curl_setopt_array($test,[CURLOPT_URL=>$url,CURLOPT_TIMEOUT_MS=>100,CURLOPT_RETURNTRANSFER=>TRUE]);
curl_exec($test);
//this line will be executed after 100 milliseconds
curl_close ($test);
in both case the called php must set ignore_user_abort(true).
And the result will not be printed in both case, but be careful with the timeout you will set, it needs to be greater than the time that the called php needs to start yielding results.
If possible you can run wget in background (using exec)
There was some frustration in finding a solution that actually works, so I ended up building a service based on fsockopen() that can handle both GET and POST requests, without being blocking.
Below is the service class:
class NonBlockingHttpClientService {
private $method = 'GET';
private $params = [];
private $port = 80;
private $host;
private $path;
private $post_content;
public function isPost(): bool
{
return ($this->method === 'POST');
}
public function setMethodToPost(): NonBlockingHttpClientService
{
$this->method = 'POST';
return $this;
}
public function setPort(int $port): NonBlockingHttpClientService
{
$this->port = $port;
return $this;
}
public function setParams(array $params): NonBlockingHttpClientService
{
$this->params = $params;
return $this;
}
private function handleUrl(string $url): void
{
$url = str_replace(['https://', 'http://'], '', $url);
$url_parts = explode('/', $url);
if(count($url_parts) < 2) {
$this->host = $url_parts[0];
$this->path = '/';
} else {
$this->host = $url_parts[0];
$this->path = str_replace($this->host, '', $url);
}
}
private function handleParams(): void
{
if(empty($this->params)) return;
if($this->isPost()) {
$this->post_content = http_build_query($this->params);
} else {
/*
if you want to specify the params as an array for GET request, they will just be
appended to the path as a query string
*/
if(strpos($this->path, '?') === false) {
$this->path .= '?' . ltrim($this->arrayToQueryString($this->params), '&');
} else {
$this->path .= $this->arrayToQueryString($this->params);
}
}
}
private function arrayToQueryString(array $params): string
{
$string = '';
foreach($params as $name => $value) {
$string .= "&$name=" . urlencode($value);
}
return $string;
}
public function doRequest(string $url): bool
{
$this->handleUrl($url);
$this->handleParams();
$host = $this->host;
$path = $this->path;
$fp = fsockopen($host, $this->port, $errno, $errstr, 1);
if (!$fp) {
$error_message = __CLASS__ . ": cannot open connection to $host$path : $errstr ($errno)";
echo $error_message;
error_log($error_message);
return false;
} else {
fwrite($fp, $this->method . " $path HTTP/1.1\r\n");
fwrite($fp, "Host: $host\r\n");
if($this->isPost()) fwrite($fp, "Content-Type: application/x-www-form-urlencoded\r\n");
if($this->isPost()) fwrite($fp, "Content-Length: " . strlen($this->post_content) . "\r\n");
fwrite($fp, "Connection: close\r\n");
fwrite($fp, "\r\n");
if($this->isPost()) fwrite($fp, $this->post_content);
return true;
}
}
}
It can be used like this:
$req = new NonBlockingHttpClientService();
$req->setMethodToPost(); //default is GET, so just omit this for GET requests
$req->setParams([
'test2' => 'aaaa', //if parameters are specified both with setParams() and in the query string, for GET requests, params specified with setParams() will take precedence
'test3' => 'bbbb',
'time' => date('H:i:s')
]);
$req->doRequest('test.localhost/some_path/slow_api.php?test1=value1&test2=value2');
And the slow_api.php file, can be something like this.
<?php
error_log('start');
sleep(10);
error_log(print_r($_REQUEST, 1) . 'end');
I find it easier to monitor (tail -f) the error log in order to see what is happening.
How can you tell if the request succeeded or not? You need to wait for at least the status code from the server to determine that. If latency is the issue, look at the curl multi API to perform multiple requests in parallel. You should be able to set a write callback function to abort reception of returned data once the status code has been returned.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 1);
curl_exec($ch);
curl_close($ch);
That works well for me.
Tested on PHP 7.1.14 Windows
A bit late now but the solution to this for anyone interested is that CURLOPT_RETURNTRANSFER needs to be set to TRUE, not false. That way the curl_exec function returns a value immediately rather than waiting for the request to complete before returning - i.e. it acts asynchronously rather than synchronously.
Example:
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
If you use Linux, you can use shell_exec in your PHP and send the result of curl to dev/null. By this method, you don't receive any answer and after sending PHP goes to the next line and Linux executes your command in the background. You should use this method if the answer is not important to you.
This is an example:
shell_exec("curl 'http://domian.com/message?text=$text' > /dev/null 2>/dev/null &")
Note: You can add any curl option like header or post method or proxy as your need.
Ok I am trying to access some JSON using a PHP proxy as I have been told is the only way to do a cross domain access when you don't control the sites policies.
Here is the code below I am trying to use as a php proxy as shared by a fellow stackoverflow user:
function curl_download($Url){
// is cURL installed yet?
if (!function_exists('curl_init')){
die('Sorry cURL is not installed!');
}
// OK cool - then let's create a new cURL resource handle
$ch = curl_init();
// Now set some options (most are optional)
// Set URL to download
curl_setopt($ch, CURLOPT_URL, $Url);
// Set a referer
curl_setopt($ch, CURLOPT_REFERER, "http://www.example.org/yay.htm");
// User agent
curl_setopt($ch, CURLOPT_USERAGENT, "MozillaXYZ/1.0");
// Include header in result? (0 = yes, 1 = no)
curl_setopt($ch, CURLOPT_HEADER, 0);
// Should cURL return or print out the data? (true = return, false = print)
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Timeout in seconds
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
// Download the given URL, and return output
$output = curl_exec($ch);
// Close the cURL resource, and free system resources
curl_close($ch);
return $output;
}
problem is when I replace $URL with http://www.nfl.com/liveupdate/scorestrip/ss.json nothing seems to happen. I am not really sure how to use this PHP proxy though either as I don't ever do this type of thing.
Am I suppose to create this in a seperate php file and then send a request to this code? I am kind of against the wall on what exactly to do here to make it so I can access the json from the site above.
Am I suppose to create this in a seperate php file and then send a request to this code?
Yes. The code above should resend your request made from JS to a remote service on another domain. Which is what does the trick - enables crossdomain POST requests from JS.
<?php
$server_url = "http://example.com/";
$options = array
(
CURLOPT_HEADER => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_CONNECTTIMEOUT => 0,
CURLOPT_HTTPGET => 1
);
$service = $_GET["service"];
$request_headers = Array();
foreach($_SERVER as $i=>$val) {
if (strpos($i, 'HTTP_') === 0) {
$name = str_replace(array('HTTP_', '_'), array('', '-'), $i);
if ($name != 'HOST')
{
$request_headers[] = "{$name}: {$val}";
}
}
}
$options[CURLOPT_HTTPHEADER] = $request_headers;
switch (strtolower($_SERVER["REQUEST_METHOD"]))
{
case "post":
$options[CURLOPT_POST] = true;
$url = "{$server_url}/services/".$service;
$options[CURLOPT_POSTFIELDS] = file_get_contents("php://input");
break;
case "get":
unset($_GET["service"]);
$querystring = "";
$first = true;
foreach ($_GET as $key => $val)
{
if (!$first) $querystring .= "&";
$querystring .= $key."=".$val;
$first = false;
}
$url = "{$server_url}/services/".$service."?".$querystring;
break;
default:
throw new Exception("Unsupported request method.");
break;
}
$options[CURLOPT_URL] = $url;
$curl_handle = curl_init();
curl_setopt_array($curl_handle,$options);
$server_output = curl_exec($curl_handle);
curl_close($curl_handle);
$response = explode("\r\n\r\n",$server_output);
$headers = explode("\r\n",$response[0]);
foreach ($headers as $header)
{
if ( !preg_match(';^transfer-encoding:;ui', Trim($header)) )
{
header($header);
}
}
echo $response[1];
This is a slightly modified version of the script I use, unfortunately not well documented.
Hope it helps.
I would suggest using Ben Almans Simple PHP Proxy
Simple PHP Proxy
How can I get the destination URL using cURL when the HTTP status code is 302?
<?PHP
$url = "http://www.ecs.soton.ac.uk/news/";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$html = curl_exec($ch);
$status_code = curl_getinfo($ch,CURLINFO_HTTP_CODE);
if($status_code=302 or $status_code=301){
$url = "";
// I want to to get the destination url
}
curl_close($ch);
?>
You can use:
echo curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_HEADER, TRUE); // We'll parse redirect url from header.
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE); // We want to just get redirect url but not to follow it.
$response = curl_exec($ch);
preg_match_all('/^Location:(.*)$/mi', $response, $matches);
curl_close($ch);
echo !empty($matches[1]) ? trim($matches[1][0]) : 'No redirect found';
A bit dated of a response but wanted to show a full working example, some of the solutions out there are pieces:
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url); //set url
curl_setopt($ch, CURLOPT_HEADER, true); //get header
curl_setopt($ch, CURLOPT_NOBODY, true); //do not include response body
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //do not show in browser the response
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); //follow any redirects
curl_exec($ch);
$new_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); //extract the url from the header response
curl_close($ch);
This works with any redirects such as 301 or 302, however on 404's it will just return the original url requested (since it wasn't found). This can be used to update or remove links from your site. This was my need anyway.
You have to grab the Location header for the redirected URL.
In response to user437797's comment on Tamik Soziev's answer (I unfortunately do not have the reputation to comment there directly) :
The CURLINFO_EFFECTIVE_URL works fine, but for it to do as op wants you also have to set CURLOPT_FOLLOWLOCATION to TRUE of course. This is because CURLINFO_EFFECTIVE_URL returns exactly what it says, the effective url that ends up getting loaded. If you don't follow redirects then this will be your requested url, if you do follow redirects then it will be the final url that is redirected to.
The nice thing about this approach is that it also works with multiple redirects, whereas when retrieving and parsing the HTTP header yourself you may have to do that multiple times before the final destination url is exposed.
Also note that the max number of redirects that curl follows can be controlled via CURLOPT_MAXREDIRS. By default it is unlimited (-1) but this may get you into trouble if someone (perhaps intentionally) configured and endless redirect loop for some url.
The new destination for a 302 redirect ist located in the http header field "location".
Example:
HTTP/1.1 302 Found
Date: Tue, 30 Jun 2002 1:20:30 GMT
Server: Apache
Location: http://www.foobar.com/foo/bar
Content-Type: text/html; charset=iso-8859-1
Just grep it with a regex.
To include all HTTP header information include it to the result with the curl option CURLOPT_HEADER. Set it with:
curl_setopt($c, CURLOPT_HEADER, true);
If you simply want curl to follow the redirection use CURLOPT_FOLLOWLOCATION:
curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
Anyway, you shouldn't use the new URI because HTTP Statuscode 302 is only a temporary redirect.
Here's a way to get all headers returned by a curl http request, as well as the status code and an array of header lines for each header.
$url = 'http://google.com';
$opts = array(CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_FOLLOWLOCATION => true);
$ch = curl_init();
curl_setopt_array($ch, $opts);
$return = curl_exec($ch);
curl_close($ch);
$headers = http_response_headers($return);
foreach ($headers as $header) {
$str = http_response_code($header);
$hdr_arr = http_response_header_lines($header);
if (isset($hdr_arr['Location'])) {
$str .= ' - Location: ' . $hdr_arr['Location'];
}
echo $str . '<br />';
}
function http_response_headers($ret_str)
{
$hdrs = array();
$arr = explode("\r\n\r\n", $ret_str);
foreach ($arr as $each) {
if (substr($each, 0, 4) == 'HTTP') {
$hdrs[] = $each;
}
}
return $hdrs;
}
function http_response_header_lines($hdr_str)
{
$lines = explode("\n", $hdr_str);
$hdr_arr['status_line'] = trim(array_shift($lines));
foreach ($lines as $line) {
list($key, $val) = explode(':', $line, 2);
$hdr_arr[trim($key)] = trim($val);
}
return $hdr_arr;
}
function http_response_code($str)
{
return substr(trim(strstr($str, ' ')), 0, 3);
}
Use curl_getinfo($ch), and the first element (url) would indicate the effective URL.