download this file: class.socket.http.php view text/plain: class.socket.http.php file encoding: UTF-8 [goback]
<?php
##
## this file name is 'class.socket.http.php'
##
## socket http object
##
## [author]
## - san2(at)linuxchannel.net
##
## [changes]
## - 2017.01.04 : change to PHP_NORMAL_READ
## - 2016.10.10 : $iporhost ==> string(ipv4 or host) or array('ipv4','host')
## - 2016.05.23 : add error_print()
## - 2016.02.02 : some
## - 2016.01.14 : more
## - 2015.12.18 : support unix:domain socket
## - 2015.08.13 : bug fixed: _parse_url() and more, but not support SSL
## - 2014.12.09 : timeout tunning
## - 2014.07.14 : some comment
## - 2014.07.09 : tuning, receive EOF
## - 2014.04.30 : add TTFB and idle check timeout
## - 2014.04.27 : add dns lookup time
## - 2014.04.07 : some tuning
## - 2014.01.12 : new build from clss.google.php
##
## [references]
## - http://ftp.linuxchannel.net/devel/php_socket_http/
##
## [usage]
##
## [example]
##
@error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
class socket
{
## loading server latency
## default non-blocking mode
## $iporhost ==> string(ipv4 or host) or array('ipv4','host')
##
public function http($_url, $iporhost='', $port=80, $method='GET', $connect_timeout=5, $connect_retry=1, $read_timeout=10)
{
static $_bsize = 1048576; // socket send/receive buffer size(Bytes)
static $_select_timeout = array('sec'=>0,'usec'=>500000); // select timeout
list($host,$ip,$port,$uri,$lookuptime,$is_ssl,$scheme) = $t = socket::gethostip($_url,$iporhost,$port);
//print_r($t);
## add san2@2015.08.18
##
if($is_ssl)
{
//stream_socket_client();
//stream_socket_enable_crypto($sock,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT);
socket::error('ERROR: not support SSL');
exit(1);
}
if($scheme == 'unix')
{
$ipport = 'unix:'.$ip;
$_sockdomain = AF_UNIX;
$_socktype = SOCK_STREAM;
$_sockprotocol = 0;
}
else
{
$ipport = $ip.':'.$port;
$_sockdomain = AF_INET;
$_socktype = SOCK_STREAM;
$_sockprotocol = SOL_TCP;
}
if(!$sock = @socket_create($_sockdomain,$_socktype,$_sockprotocol))
{
return array(-1000,0,socket_last_error());
}
/***/
//@socket_set_option($sock,SOL_TCP,TCP_NODELAY,1); // Nagle algorithm off
@socket_set_option($sock,SOL_SOCKET,SO_REUSEADDR,1);
@socket_set_option($sock,SOL_SOCKET,SO_KEEPALIVE,1);
@socket_set_option($sock,SOL_SOCKET,SO_SNDBUF,$_bsize);
@socket_set_option($sock,SOL_SOCKET,SO_RCVBUF,$_bsize);
@socket_set_option($sock,SOL_SOCKET,SO_RCVTIMEO,$_select_timeout); // good idea for blocking mode
@socket_set_option($sock,SOL_SOCKET,SO_SNDTIMEO,$_select_timeout);
@socket_set_option($sock,SOL_SOCKET,SO_LINGER,array('l_onoff'=>1,'l_linger'=>0));
/***/
@socket_set_nonblock($sock); // set to non-blocking mode
$method = strtoupper($method);
if(!preg_match('/^(GET|HEAD)$/',$method)) $method = 'GET';
$connect_timeout = (int)$connect_timeout;
$connect_retry = (int)$connect_retry;
$connect_retry = $connect_retry ? $connect_retry : 1;
$http_host = $host ? $host : 'localhost';
$req = $method.' '.$uri.' HTTP/1.1'."\r\n".
'User-Agent: Mozilla/5.0 (Windows NT 5.1) PHP/socket.http'."\r\n".
'Connection: close'."\r\n". // good idea for blocking mode
//'Connection: Keep-Alive'."\r\n". // good idea for blocking mode
'Host: '.$http_host."\r\n";
$req .= "\r\n"; // end of request header
//echo $req;
$_cs = microtime();
for($i=0; $i<$connect_retry; $i++)
{
list($code,$msg) = socket::_connect($sock,$ip,$port,$connect_timeout);
if($code == 1) { $_ce = microtime(); break; } // ok
}
if($code != 1)
{
@socket_close($sock);
return array(-2000,0,"can not connect to $ipport, msg=$msg",$ipport,$uri);
}
$_ws = microtime();
if(!@socket_write($sock,$req))
{
@socket_close($sock);
return array(-3000,0,"can not send request $uri",$ipport,$uri);
}
$_we = microtime();
list($_fb,$header) = socket::_getheader($sock,1024,$read_timeout,$_select_timeout); // read header
$header = socket::_parse_header($header);
//print_r($header);
//echo "_fb=$_fb\n";
/***
if($header['error'])
{
@socket_close($sock);
return array(-1, socket::getms($stime), $header['status'].' '.$uri);
}
***/
$rsize = socket::_read_bodysize($sock,$_bsize,$read_timeout,$_select_timeout,$header['content_length']); // check bandwidth mode
$_ee = microtime();
@socket_close($sock);
return array
(
array
(
$lookuptime, // DNS lookup time
socket::getms($_cs,$_ce), // connect time
socket::getms($_ws,$_we), // send time
socket::getms($_ws,$_fb), // wait time send ~ TTFB(Time To First Byte), server latency think time
socket::getms($_fb,$_ee), // receive time
socket::getms($_cs,$_ee) + $lookuptime // total time
),
$header, // array header
$rsize, // body size
$ipport, // target IP
$uri // rewrite uri
);
}
public function gethostip($_url, $iporhost='', $_port=80)
{
static $_default_connect_port = 80;
list($nhost,$nport,$uri,$scheme) = socket::_parse_url($_url);
if(is_array($iporhost)) list($iporhost,$put_host) = $iporhost; // add san2@2016.10.10
$iporhost = strtolower($iporhost);
if(preg_match(';^unix:;',$iporhost)) // patch san2@2016.01.15
{
$iporhost = preg_replace(';^unix:;','',$iporhost);
if(!$path = realpath($iporhost))
{
$path = realpath(preg_replace(';^/*;','',$iporhost));
}
//if($path) $path = 'unix://'.$path;
return array($put_host?$put_host:$nhost,$path,NULL,$uri,0,FALSE,'unix');
}
if(preg_match('/[a-z]/',$nhost)) $host = $nhost;
else if($nhost) $ip = $nhost;
if($iporhost)
{
list($iporhost,$ipport) = explode(':',$iporhost);
if(preg_match('/[a-z]/',$iporhost)) $host = $iporhost;
else if($iporhost) $ip = $iporhost;
if($put_host) $host = $put_host; // override, add san2@2016.10.10
}
if($ipport) $port = $ipport; // in ip:port
else if($nport) $port = $nport; // in url
else if($_port) $port = $_port; // in group port
else $port = $_default_connect_port; // default
if(!$ip)
{
$s = microtime();
$ip = ($ip=gethostbyname($host)) ? $ip : gethostbyname($host); // one more check, to ip
$lookuptime = socket::getms($s,microtime());
}
if(!$host) $host = $ip; // not good
return array($host,$ip,$port,$uri,(integer)$lookuptime,(boolean)preg_match('/^https/',$scheme),$scheme);
}
## connect to host:port
##
## [return]
## 1 or TRUE : connected
## 0 or FALSE : can't connect, client issue
## -1 : connetion time out, remote issue
##
private function _connect(&$sock, $host, $port, $connect_timeout=5)
{
$_time = time();
//echo "_connect $host,$port\n";
while(1)
{
if((time()-$_time) >= $connect_timeout) { return array(-1,'Connect timeout='.$connect_timeout); break; }
@socket_connect($sock,$host,$port);
$errno = @socket_last_error();
//echo "$errno\t".socket_strerror($errno)."\n"; // debug
if($errno==114 || $errno==115) continue; // in progress
else if($errno == 106) { return array(TRUE,'OK'); break; }
else if($errno < 0) { return array(FALSE,socket_strerror($errno)); break; } // unknown host
else if($errno==100 || $errno==101 || $errno==102) { return array(FALSE,socket_strerror($errno)); break; } // Network fail
else if($errno==110 || $errno==111) { return array(-1,socket_strerror($errno)); break; } // Connection timed out, Connection refused
else if($errno==112 || $errno==113) { return array(-1,socket_strerror($errno)); break; } // Host is down, No route to host
}
return array(FALSE,'');
}
## read HTTP all headers
##
## read header by socket_read()
## PHP_NORMAL_READ : 1
## PHP_BINARY_READ : 2
##
private function _getheader(&$sock, $_bsize=1024, $read_timeout=10, $_select_timeout=array('sec'=>0,'usec'=>500000))
{
$_fb = 0; // first byte time
$_break = FALSE;
$_time = time(); // idle check initial
$stream = array($sock);
//echo "_getheader() $sock\n";
while(1)
{
if((time()-$_time) >= $read_timeout) break;
$select = socket_select($stream,$write=NULL,$except=NULL,$_select_timeout['sec'],$_select_timeout['usec']);
if($select === FALSE) continue;
//echo "select=$select\n";
//if(!in_array($sock,$stream)) break; // is bug ???
//$rbuf = socket::__readheader($sock,$_bsize);
while($buf = @socket_read($sock,$_bsize,PHP_NORMAL_READ))
{
if(!$_fb) $_fb = microtime();
//echo "H:'$buf'";
//if(!defined('_RESPONSE_END_')) define('_RESPONSE_END_',microtime());
if(!trim($obuf) && !trim($buf))
{
@socket_read($sock,1,PHP_BINARY_READ); // head end \n
$_break = TRUE;
break; // end of header
}
$rbuf .= $buf;
$obuf = $buf;
}
if($_break) break; // require
if($buf) $_time = time(); // idle check reset
}
return array($_fb,$rbuf);
}
private function _parse_header($buf)
{
$lines = preg_split('/[\r\n]+/',trim($buf));
$size = sizeof($lines);
$r['status'] = trim($lines[0]);
/***
if(!preg_match(';200 OK;',$lines[0]))
{
$r['error'] = TRUE;
return $r;
}
***/
for($i=1; $i<$size; $i++)
{
$line = trim($lines[$i]);
list($k) = preg_split('/:\s+/',$line);
$r[str_replace('-','_',strtolower($k))] = preg_replace(';^'.$k.':\s+;','',$line);
}
return $r;
}
## only read size, this is a bandwidth test version
##
private function _read_bodysize(&$sock, $_bsize=1048576, $read_timeout=10, $_select_timeout=array('sec'=>0,'usec'=>500000), $content_length=NULL)
{
$_time = time(); // idle check
$rsize = 0;
$stream = array($sock);
while(1)
{
if((time()-$_time) >= $read_timeout) break;
$select = @socket_select($stream,$write=NULL,$except=NULL,$_select_timeout['sec'],$_select_timeout['usec']);
if($select === FALSE) continue;
$buf = socket_read($sock,$_bsize,PHP_NORMAL_READ);
if($buf === '') break;
//echo "'$buf'";
$rsize += strlen($buf);
if($content_length && $rsize>=$content_length) break;
else if(preg_match("/(0\r\n\r\n|f\r\n\r\n)$/",substr($buf,-5))) break;
$_time = time(); // idle check
}
return (int)$rsize;
}
private function getms($_start, $_end=NULL)
{
if(!$_start) return -1; // patch san2@2015.08.13
$end = explode(' ', $_end?$_end:microtime());
$start = explode(' ', $_start);
return sprintf('%d',(($end[1]+$end[0])-($start[1]+$start[0])) * 1000);
}
private function _parse_url($_url)
{
$_url = trim($_url);
if(preg_match(';^/;',$_url)) return array('','',$_url,'');
//$url = parse_url(preg_replace(';^(?:http(s?)://)*;i','http\\1://',$_url)); // patch san2@2015.08.13, disable san2@2016.01.15
$url = parse_url($_url);
$host = $url['host'];
$port = $url['port']; // ? $url['port'] : (preg_match('/^https/',$url['scheme']) ? 443 : 80);
$uri = $url['path'] ? $url['path'] : '/';
if($url['query']) $uri .= '?'.$url['query']; // add san2@2008.12.01
//echo "$host,$port,$uri\n";
return array(strtolower($host),$port,$uri,strtolower($url['scheme']));
}
## PECL/http_chunked_decode
##
## http://stackoverflow.com/questions/10793017/how-to-easily-decode-http-chunked-encoded-string-when-making-raw-http-request // speed good
## https://api.drupal.org/api/drupal/vendor!easyrdf!easyrdf!lib!EasyRdf!Http!Response.php/function/EasyRdf_Http_Response%3A%3AdecodeChunkedBody/8 // speed not good
## http://www.codingforums.com/post-a-php-snippet/147061-decoder-chunked-transfer-encoding.html // speed not good
##
public function chunked_decode($str)
{
for($res=''; !empty($str); $str=trim($str))
{
$pos = strpos($str,"\r\n");
$len = hexdec(substr($str,0,$pos));
$res.= substr($str,$pos+2,$len);
$str = substr($str,$pos+2+$len);
}
return $res;
}
public function error($errstr)
{
return error_log($errstr,0);
}
public function error_print($errstr)
{
return fwrite(STDERR,rtrim($errstr)."\n");
}
} // end of class
?>