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

?>