I'm trying to open a non-blocking stream in PHP (5.3.2 & 5.4.4). I do the following:
$fp = fopen($url, 'r');
if ($fp === false)
return false;
print('stream opened'.PHP_EOL);
stream_set_blocking($fp, 0);
The url points to a php file:
<?php sleep(10); ?>
<html><body>Hello</body></html>
The problem is that fopen() seems to block before I am even able to setup the stream as non blocking. Indeed, the stream opened message is printed after 10 seconds and not directly.
When doing a fopen on a url, the HTTP headers are sent at that moment. Since no context has been defiened (and it is not possible to configure contexts with the non-blocking option), fopen waits for the http headers to be sent and blocks.
A workaround is to use fsockopen which only opens the tcp connecion and does nothing more. The drawback of this approach is that the HTTP request has to be created manually.
Here is an (optimizable) implementation that reads data from an url in a non blocking way.
function parse_http_url($url)
{
$parts = parse_url($url);
if ($parts === false) return false;
if (!isset($parts['scheme']))
$parts['scheme'] = 'http';
if ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')
return false;
if (!isset($parts['port']))
$parts['port'] = ($parts['scheme'] === 'http') ? 80 : 443;
if(!isset($parts['path']))
$parts['path'] = '/';
$parts['uri'] = $parts['path'];
if (!empty($parts['query']))
$parts['uri'] .= '?'.$parts['query'];
return $parts;
}
function url_get_contents($url, $options = null) {
if(!($url_parts = parse_http_url($url))) return false;
$timeout = intval(#$options['http']['timeout']);
if (!($fp = fsockopen($url_parts['host'], $url_parts['port'], $errno, $errstr, $timeout))) return false;
stream_set_blocking($fp, 0);
if($timeout > 0) {
stream_set_timeout($fp, $timeout);
$sleep_time = (($timeout * 1000000) / 100); # 1% of timeout in ms
$stop_time = microtime(true) + $timeout;
} else {
$sleep_time = 10000; # 10 ms
}
if (!isset($options['http']['method'])) $options['http']['method'] = 'GET';
if (!isset($options['http']['header'])) $options['http']['header'] = '';
$request = "{$options['http']['method']} {$url_parts['uri']} HTTP/1.1\r\n{$options['http']['header']}\r\n";
if (fwrite($fp, $request) === false) {
fclose($fp);
return false;
}
$content = '';
$buff_size = 4096;
do {
$rd = fread($fp, $buff_size);
if ($rd === false) {
fclose($fp);
return false;
}
$content .= $rd;
$meta = stream_get_meta_data($fp);
if ($meta['eof']) {
fclose($fp);
if(empty($content)) return false;
// HTTP headers should be separated with \r\n only but lets be safe
$content = preg_split('/\r\n|\r|\n/', $content);
$resp = explode(' ', array_shift($content));
$code = isset($resp[1]) ? intval($resp[1]) : 0;
if ($code < 200 || $code >= 300) {
$message = isset($resp[2]) ? $resp[2] : 'Unknown error';
trigger_error("Error {$code} {$message}", E_USER_WARNING);
return false;
}
// Skip headers
while (!empty($content) && array_shift($content) !== '');
return implode("\n", $content);
}
if ($meta['timed_out']) {
fclose($fp);
return false;
}
if (isset($stop_time) && microtime(true) >= $stop_time) {
fclose($fp);
return false;
}
if ($meta['unread_bytes'] === 0) {
usleep($sleep_time);
}
} while(true);
}
Related
I have an error in my code when I compile :
Fatal error: Uncaught ArgumentCountError: Too few arguments to
function getData::QueryWhoisServer(), 0 passed in
C:\xampp\htdocs\testVisitor\index.php on line 19 and exactly 2
expected in C:\xampp\htdocs\testVisitor\Model\getData.php:72 Stack
trace: #0 C:\xampp\htdocs\testVisitor\index.php(19):
getData->QueryWhoisServer() #1 {main} thrown in
C:\xampp\htdocs\testVisitor\Model\getData.php on line 72
I know that since php 7.0 I need to pass argument but the argument are not recognize...
here is my code:
index.php :
require_Once('Model/getData.php');
require_Once('Controller/writeData.php');
$getData = new getData();
$writeData =new writeData();
$getData->get_ip();
$getData->LookupIP($domain);
$getData->ValidateIP($domain);
$getData->QueryWhoisServer();
if($domain && $pageEnCours != preg_match("#localhost/testVisitor/$#",$pageEnCours)) {
$domain = trim($domain);
if($getData->ValidateIP($domain)) {
$result = $getData->LookupIP($domain);
$writeData->write_domain($result);
}
else{
write_error();
};
}
echo $domain;
echo "cc";
and getData.php :
$urlPart1 = $_SERVER['HTTP_HOST'] ;
$urlPart2 = $_SERVER['REQUEST_URI'];
$pageEnCours = $urlPart1 .= $urlPart2;
$domain ='0.0.0.0';
class getData
{
// For the full list of TLDs/Whois servers see http://www.iana.org/domains/root/db/ and http://www.whois365.com/en/listtld/
/**
* Récupérer la véritable adresse IP d'un visiteur
*/
function get_ip() {
// IP si internet partagé
global $domain;
if (isset($_SERVER['HTTP_CLIENT_IP'])) {
return $domain =$_SERVER['HTTP_CLIENT_IP'];
}
// IP derrière un proxy
elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $domain=$_SERVER['HTTP_X_FORWARDED_FOR'];
}
// Sinon : IP normale
else {
return $domain=(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '');
}
}
function LookupIP($ip) {
$whoisservers = array(
//"whois.afrinic.net", // Africa - returns timeout error :-(
//"whois.lacnic.net", // Latin America and Caribbean - returns data for ALL locations worldwide :-)
//"whois.apnic.net", // Asia/Pacific only
//"whois.arin.net", // North America only
//"whois.ripe.net" // Europe, Middle East and Central Asia only
);
$results = array();
foreach($whoisservers as $whoisserver) {
$result = QueryWhoisServer($whoisserver, $ip);
if ($result && !in_array($result, $results)) {
$results[$whoisserver] = $result;
}
}
$res = "RESULTS FOUND: " . count($results);
foreach($results as $whoisserver=>$result) {
$res .= "\n\n-------------\nLookup results for " . $ip . " from " . $whoisserver . " server:\n\n" . $result;
}
return $res;
}
function ValidateIP($ip) {
$ipnums = explode(".", $ip);
if(count($ipnums) != 4) {
return false;
}
foreach($ipnums as $ipnum) {
if(!is_numeric($ipnum) || ($ipnum > 255)) {
return false;
}
}
return $ip;
}
function QueryWhoisServer($whoisserver , $domain ) {
$port = 43;
$timeout = 10;
$fp = #fsockopen($whoisserver, $port, $errno, $errstr, $timeout) or die("Socket Error " . $errno . " - " . $errstr);
//if($whoisserver == "whois.verisign-grs.com") $domain = "=".$domain; // whois.verisign-grs.com requires the equals sign ("=") or it returns any result containing the searched string.
fputs($fp, $domain . "\r\n");
$out = "";
while(!feof($fp)){
$out .= fgets($fp);
}
fclose($fp);
$res = "";
if((strpos(strtolower($out), "error") === FALSE) && (strpos(strtolower($out), "not allocated") === FALSE)) {
$rows = explode("\n", $out);
foreach($rows as $row) {
$row = trim($row);
if(($row != '') && ($row{0} != '#') && ($row{0} != '%') && ($row != preg_match("#^netname|^descr|^country|^person|^address|^phone#",$row ))) {
$res .= $row."\n";
}
}
}
return $res;
}
}
Too few arguments to function getData::QueryWhoisServer(), 0 passed in C:\xampp\htdocs\testVisitor\index.php
$getData->QueryWhoisServer(); is not providing any arguments.
In your function :
function QueryWhoisServer($whoisserver , $domain ) {
$port = 43;
$timeout = 10;
$fp = #fsockopen($whoisserver, $port, $errno, $errstr, $timeout)
fputs($fp, $domain . "\r\n");
$fp need $whoisserver,$portand$timeout
The $port and $timeout are defined in the function
But you need to specify $whoisserver and the $domain (domain is used in fputs) when you call this function,
That will be something like :
$getData->QueryWhoisServer($whoisserver, $domain);
Also in your function
LookupIP($domain);
The result use the fucntion QueryWhoisServer, so try to get the $result
i have a script where i use die to prevent a function's continuous loop. But if i place this above the html, the html script will stop as well so i place it below the html but all the variables get echoed below the actual website. How can i make sure this get's echoed to the place where i want it to be and not below the website? Or is there a different way than using die? This is the code of the function:
function QueryWhoisServer($whoisserver, $domain) {
$port = 43;
$timeout = 5;
$errm = "<p1>No connection could be made</p1>";
$fp = #fsockopen($whoisserver, $port, $errno, $errstr, $timeout) or die($errm);
fputs($fp, $domain . "\r\n");
$out = "";
while (!feof($fp)) {
$out .= fgets($fp);
}
fclose($fp);
$res = "";
if((strpos(strtolower($out), "error") === FALSE) && (strpos(strtolower($out), "not allocated") === FALSE)) {
$rows = explode("\n", $out);
foreach($rows as $row) {
$row = trim($row);
if(($row != ':') && ($row != '#') && ($row != '%')) {
$res .= $row."<br>";
}
}
}
return $res;
}
The keyword break breaks out of any loop, just use it instead of die.
Beware if you have nested loops, as break will only exit the innermost loop. Oddly enough, in php you can use break(2) to break from two loops. I would refrain from doing that though.
die(); stops all PHP execution. It's rare that you'd actually want to do that.
Instead you should look at the try - catch construct and throwing and catching exceptions.
function QueryWhoisServer($whoisserver, $domain) {
try {
$port = 43;
$timeout = 5;
$errm = "<p1>No connection could be made</p1>";
$fp = #fsockopen($whoisserver, $port, $errno, $errstr, $timeout);
if (!fp) {
throw new Exception ("Couldn't open socket.");
}
//after the exception is thrown, the rest of this block will not execute.
fputs($fp, $domain . "\r\n");
$out = "";
while (!feof($fp)) {
$out .= fgets($fp);
}
fclose($fp);
$res = "";
if((strpos(strtolower($out), "error") === FALSE) && (strpos(strtolower($out), "not allocated") === FALSE)) {
$rows = explode("\n", $out);
foreach($rows as $row) {
$row = trim($row);
if(($row != ':') && ($row != '#') && ($row != '%')) {
$res .= $row."<br>";
}
}
}
return $res;
} catch (Exception $e) {
//this block will be executed if any Exception is caught, including the one we threw above.
//you can handle the error here or rethrow it to pass it up the execution stack.
return "";
}
}
PHP's Exceptions manual page
You can use a control variable to avoid infinite looping.
$end_condition = false;
while (!$end_condition)
{
//do the job
if (conditions/are/met)
{
$end_condition = true;
}
}
I want to allow the serving of binary files with some sort of access control. Since the control is rather complex, I cannot simply let Apache serve the files, I have to serve them via PHP, using my Zend Framework 2 app. The action goes like this:
public function sendAction() {
$filename = /* database action */;
$size = filesize($filename);
$response = $this->getResponse();
if($this->getRequest()->getHeaders()->has('Range')) {
list($unit, $range) = explode('=', $this->getRequest()->getHeaders()->get('Range')->toString());
$ranges = explode(',', $range);
$ranges = explode('-', $ranges[0]);
$start = (int)$ranges[0];
$end = (int)(isset($ranges[1]) ? $ranges[1] : $size - 1);
$length = $start - $end;
$response->getHeaders()->addHeaders(array('Content-Type' => 'audio/mpeg', 'Accept-Ranges' => 'bytes', 'Content-Length' => $length - 1));
$response->setStatusCode(206);
$f = fopen($filename, 'r');
if($start) fseek($f, $start);
$out = '';
while($length) {
$read = ($length > 8192) ? 8192 : $length;
$length -= $read;
$out .= fread($fp,$read);
}
fclose($f);
$response->setContent($out);
} else {
$response
->setContent(file_get_contents($filename))
->getHeaders()->addHeaders(array('Content-Type' => 'audio/mpeg', 'Accept-Ranges' => 'bytes'));
}
return $this->getResponse();
}
Well for one, I am sure this is very inefficient as the files are always loaded into the RAM entirely for this before being served.
However, this doesn't seem to work. When I try to access the file, I get the correct audio/mpeg player in Chrome, but then the browser cancels the requests and stops. I can't play the audio at all.
I could not find any hint on the web on how to implement a 206 response in Zend 2 the correct way, perhaps someone can help me here.
You should use stream.
Sample code from my application
public function documentAction()
{
$name = $this->params('name');
try {
if (!$this->getServiceLocator()->get('AuthStorage')->hasIdentity()) {
throw new \Exception('You must login.');
}
$file = getcwd().'/data/uploads/'.pathinfo($name)['basename'];
if (file_exists($file)) {
$response = new \Zend\Http\Response\Stream();
$headers = new \Zend\Http\Headers();
$headers->addHeaderLine('Content-type', 'application/pdf');
$response->setHeaders($headers);
$response->setStream(fopen($file, 'r'));
return $response;
} else {
throw new \Exception('File not exist');
}
}
catch (\Exception $e) {
$this->flashMessenger()->setNamespace('error')->addMessage('404');
return $this->redirect()->toUrl('/');
}
}
This works for pdf rendering in browser (ex: PDFJS), mp4 video streaming or download file.
$headers = new \Zend\Http\Headers();
$headers->addHeaderLine('Content-Type', $type)
->addHeaderLine('Cache-Control', 'must-revalidate')
->addHeaderLine('Pragma', 'public');
$response = new \Zend\Http\Response();
$downloadFile = false | true; // <-- Change this
if($downloadFile) {
$headers->addHeaderLine('Content-Disposition', 'attachment; filename="' . $fileName . '"')
->addHeaderLine('Content-Length', $size);
// ->addHeaderLine('Set-Cookie', 'fileDownload=true; path=/');
$response = new \Zend\Http\Response\Stream();
$response->setStream(fopen($pathFile, 'r'));
$response->setHeaders($headers);
return $response;
}
$size = filesize($pathFile);
$start = 0;
$end = $size - 1;
$buffer = 1024000;
// Get the 'Range' header if one was sent
if (isset($_SERVER['HTTP_RANGE'])) {
$range = $_SERVER['HTTP_RANGE']; // IIS/Some Apache versions
} else if ($apache = apache_request_headers()) { // Try Apache again
$arrHeaders = array();
foreach ($apache as $header => $val) {
$arrHeaders[strtolower($header)] = $val;
}
if (isset($arrHeaders['range'])) {
$range = $arrHeaders['range'];
} else {
$range = false; // We can't get the header/there isn't one set
}
} else {
$range = false; // We can't get the header/there isn't one set
}
// Get the data range requested (if any)
$size = filesize($pathFile);
if ($range) {
$partial = true;
list($param,$range) = explode('=',$range);
if (strtolower(trim($param)) != 'bytes') { // Bad request - range unit is not 'bytes'
$response->setStatusCode(400); // 400 Invalid Request
exit;
}
$range = explode(',',$range);
$range = explode('-',$range[0]); // We only deal with the first requested range
if (count($range) != 2) { // Bad request - 'bytes' parameter is not valid
$response->setStatusCode(400); // 400 Invalid Request
exit;
}
if ($range[0] === '') { // First number missing, return last $range[1] bytes
$end = $size - 1;
$start = $end - intval($range[1]);
} else if ($range[1] === '') { // Second number missing, return from byte $range[0] to end
$start = intval($range[0]);
$end = $size - 1;
} else { // Both numbers present, return specific range
$start = intval($range[0]);
$end = intval($range[1]);
if ($end >= $size || (!$start && (!$end || $end == ($size - 1)))) {
$partial = false; // Invalid range/whole file specified, return whole file
}
}
$length = $end - $start + 1;
} else {
$partial = false; // No range requested
$length = $size;
}
// error_log(var_export(array($range, $partial, $length), true));
// Send standard headers
$headers->addHeaderLine("Content-Length", $length);
$headers->addHeaderLine("Accept-Ranges", "bytes");
// if requested, send extra headers and part of file...
if ($partial) {
$response->setStatusCode(206); // 206 (Partial Content)
$headers->addHeaderLine("Content-Range", "bytes $start-$end/".$size);
if (!$fp = fopen($pathFile, 'r')) { // Error out if we can't read the file
$response->setStatusCode(500); // 500 Internal Server Error
exit;
}
if ($start) {
fseek($fp,$start);
}
$out = '';
while ($length) { // Read in blocks of 8KB so we don't chew up memory on the server
$read = ($length > 8192) ? 8192 : $length;
$length -= $read;
$out .= fread($fp,$read);
}
fclose($fp);
} else {
$out = readfile($pathFile); // ...otherwise just send the whole file
}
$response->setContent($out);
$response->setHeaders($headers);
return $response;
Is there any way (other than checking for ping responses) to detect when a client stream (I don't know if a stream would be any different from sockets) becomes unavailable (ie there is no longer any connection but no clean disconnection was made)?
Using this code:
#!/usr/bin/env php
<?php
$socket = stream_socket_server(
'tcp://192.168.1.1:47700',
$errno,
$errstr,
STREAM_SERVER_BIND|STREAM_SERVER_LISTEN,
stream_context_create(
array(),
array()
)
);
if (!$socket) {
echo 'Could not listen on socket'."\n";
}
else {
$clients = array((int)$socket => $socket);
$last = time();
while(true) {
$read = $clients;
$write = null;
$ex = null;
stream_select(
$read,
$write,
$ex,
5
);
foreach ($read as $sock) {
if ($sock === $socket) {
echo 'Incoming on master...'."\n";
$client = stream_socket_accept(
$socket,
5
);
if ($client) {
stream_set_timeout($client, 1);
$clients[(int)$client] = $client;
}
}
else {
echo 'Incoming on client '.((int)$sock)."...\n";
$length = 1400;
$remaining = $length;
$buffer = '';
$metadata['unread_bytes'] = 0;
do {
if (feof($sock)) {
break;
}
$result = fread($sock, $length);
if ($result === false) {
break;
}
$buffer .= $result;
if (feof($sock)) {
break;
}
$continue = false;
if (strlen($result) == $length) {
$continue = true;
}
$metadata = stream_get_meta_data($sock);
if ($metadata && isset($metadata['unread_bytes']) && $metadata['unread_bytes']) {
$continue = true;
$length = $metadata['unread_bytes'];
}
} while ($continue);
if (strlen($buffer) === 0 || $buffer === false) {
echo 'Client disconnected...'."\n";
stream_socket_shutdown($sock, STREAM_SHUT_RDWR);
unset($clients[(int)$sock]);
}
else {
echo 'Received: '.$buffer."\n";
}
echo 'There are '.(count($clients) - 1).' clients'."\n";
}
}
if ($last < (time() - 5)) {
foreach ($clients as $id => $client) {
if ($client !== $socket) {
$text = 'Yippee!';
$ret = fwrite($client, $text);
if ($ret !== strlen($text)) {
echo 'There seemed to be an error sending to the client'."\n";
}
}
}
}
}
}
if ($socket) {
stream_socket_shutdown($socket, STREAM_SHUT_RDWR);
}
and a sockets client on a different computer, I can connect to the server, send and receive data, and disconnect cleanly and everything functions as expected. If, however, I pull the network connection on the client computer, nothing is detected on the server side - the server keeps on listening to the client socket, and also writes to it without any error manifesting itself.
As I understand it, calling feof($stream) will tell you if the remote socket disconnected, but I'm not absolutely certain about that. I'm using ping/pong myself while continuing to research a solution.
You need to set a socket timeout, in which case you get an error if a client does not respond in a timely fashion.
Check PHP's stream_set_timeout function:
http://www.php.net/manual/en/function.stream-set-timeout.php
Also check socket_set_option:
http://php.net/manual/en/function.socket-set-option.php
and finally, check out this great article on how to use sockets in PHP effectively:
"PHP Socket Programming, done the Right Way™"
http://christophh.net/2012/07/24/php-socket-programming/
function httpGet( $url, $followRedirects=true ) {
global $final_url;
$url_parsed = parse_url($url);
if ( empty($url_parsed['scheme']) ) {
$url_parsed = parse_url('http://'.$url);
}
$final_url = $url_parsed;
$port = $url_parsed["port"];
if ( !$port ) {
$port = 80;
}
$rtn['url']['port'] = $port;
$path = $url_parsed["path"];
if ( empty($path) ) {
$path="/";
}
if ( !empty($url_parsed["query"]) ) {
$path .= "?".$url_parsed["query"];
}
$rtn['url']['path'] = $path;
$host = $url_parsed["host"];
$foundBody = false;
$out = "GET $path HTTP/1.0\r\n";
$out .= "Host: $host\r\n";
$out .= "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0\r\n";
$out .= "Connection: Close\r\n\r\n";
if ( !$fp = #fsockopen($host, $port, $errno, $errstr, 30) ) {
$rtn['errornumber'] = $errno;
$rtn['errorstring'] = $errstr;
}
fwrite($fp, $out);
while (!#feof($fp)) {
$s = #fgets($fp, 128);
if ( $s == "\r\n" ) {
$foundBody = true;
continue;
}
if ( $foundBody ) {
$body .= $s;
} else {
if ( ($followRedirects) && (stristr($s, "location:") != false) ) {
$redirect = preg_replace("/location:/i", "", $s);
return httpGet( trim($redirect) );
}
$header .= $s;
}
}
fclose($fp);
return(trim($body));
}
This code sometimes go infinite loop. What's wrong here?
There is a big, red warning box in the feof() documentation:
Warning
If a connection opened by fsockopen() wasn't closed by the server, feof() will hang. To workaround this, see below example:
Example #1 Handling timeouts with feof()
<?php
function safe_feof($fp, &start = NULL) {
$start = microtime(true);
return feof($fp);
}
/* Assuming $fp is previously opened by fsockopen() */
$start = NULL;
$timeout = ini_get('default_socket_timeout');
while(!safe_feof($fp, $start) && (microtime(true) - $start) < $timeout)
{
/* Handle */
}
?>
Also you should only write to or read from the file pointer, if it is valid (what you are not doing, you just set an error message):
This leads to the second big red warning box:
Warning
If the passed file pointer is not valid you may get an infinite loop, because feof() fails to return TRUE.
Better would be:
$result = '';
if ( !$fp = #fsockopen($host, $port, $errno, $errstr, 30) ) {
$rtn['errornumber'] = $errno;
$rtn['errorstring'] = $errstr;
}
else {
fwrite($fp, $out);
while (!#feof($fp)) {
//...
}
fclose($fp);
$result = trim(body);
}
return $result;
A last remark: If you follow a redirect with
if ( ($followRedirects) && (stristr($s, "location:") != false) ) {
$redirect = preg_replace("/location:/i", "", $s);
return httpGet( trim($redirect) );
}
you never close the file pointer. I think better is:
if ( ($followRedirects) && (stristr($s, "location:") != false) ) {
$redirect = preg_replace("/location:/i", "", $s);
$result = httpGet( trim($redirect) );
break;
}
// ...
return $result;
feof will return false if the connection is still open in a tcp/ip stream.
function httpGet( $url, $followRedirects=true ) {
[...]
return httpGet( trim($redirect) );
}
Nothing prevents you from fetching the same URL again and again.