WebRequest 2.1
This version uses a substantially different server-pull set of functions. It has been beta tested but not fully tested, so if you have troubles with it please let me know. This code is strong enough for you to put into production systems (I have it in mine), but don't do it without a backup... it's not really well vetted yet.
The event pops, onSuccess and onFailure have been completely implemented.
Chunked downloads now work as they should.
<?php
class webRequest2
{
private $socket;
protected $finalURL;
protected $rawContent;
protected $rawHeader;
protected $rawResponse;
protected $caughtEarlyTerm;
protected $chunkedLength;
protected $chunkedTransfer;
protected $cookies;
protected $cookieStr;
protected $errorFlag;
protected $getList;
protected $headers;
protected $postList;
protected $postStr;
public $accept;
public $charSet;
public $domain;
public $debugLogFile;
public $debugLogClearOnDispatch;
public $debugMode;
public $earlyTermStr;
public $language;
public $manualPostContent;
public $method;
public $port;
public $postMode;
public $proxy;
public $redirect;
public $referer;
public $resultCode;
public $timeout;
public $url;
public $userAgent;
public $useSSL;
// Event Handlers
public $onFailure;
public $onProxyRetry;
public $onSuccess;
// Protected and special functions
function webRequest2()
{
$this->reset();
preg_match('/^([0-9])/', phpversion(), $parts);
$this->ancient = ($parts[1] < '5');
$this->userAgent = 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/417.9 (KHTML, like Gecko) Safari/417.8';
$this->accept = 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5';
$this->charSet = 'ISO-8859-1,utf-8:q=0.7,*;q=0.7';
$this->language = 'en-us,en;q=0.5';
$this->referer = '';
if (!defined('WRD_OFF'))
{
define('WRD_OFF', 0);
define('WRD_ECHO', 1);
define('WRD_LOG', 2);
define('WRM_GET', 0);
define('WRM_POST', 1);
define('WRP_NORMAL', 0);
define('WRP_MULTIPART', 1);
}
$this->debugMode = WRD_OFF;
$this->postMode = WRP_NORMAL;
$this->debugLogFile = '';
$this->debugLogClearOnDispatch = true;
$this->timeout = 30;
$this->useSSL = false;
$this->proxy = '';
}
protected function buildCookieStr()
{
$cookieStr = '';
$start = true;
foreach($this->cookies as $name=>$value)
{
if (!$start) { $cookieStr .= '; '; }
$cookieStr .= "$name=$value";
$start = false;
}
$this->debug("Built COOKIE String: $cookieStr");
return $cookieStr;
}
protected function buildGetStr()
{
$getStr = '';
$getCount = count($this->getList);
if ($getCount)
{
$sepStr = '?';
foreach($this->getList as $name=>$value)
{
$value = urlencode($value);
$getStr .= "$sepStr$name=$value";
$sepStr = '&';
}
}
$this->debug("Built GET String: $getStr");
return $getStr;
}
protected function buildPostStr()
{
if ($this->manualPostContent)
return $this->manualPostContent;
$postStr = '';
$postCount = count($this->postList);
if ($postCount)
{
$sepStr = '';
foreach($this->postList as $name=>$arr)
{
if ($arr['type'] == 'XML')
{
$value = $arr['content'];
} else {
$value = urlencode($arr['content']);
}
$postStr .= "$sepStr$name=$value";
$sepStr = '&';
}
} else {
$postStr = 'No Content';
}
$this->debug("Built POST String: $cookieStr");
return $postStr;
}
protected function buildHeader()
{
$header[0] = ''; // place holder for first line of header
$header[] = "Host: {$this->domain}";
$header[] = "User-Agent: {$this->userAgent}";
$header[] = "Accept: {$this->accept}";
$header[] = "Accept-Language: {$this->language}";
$header[] = "Accept-Encoding: ";
$header[] = "Accept-Charset: {$this->charSet}";
if ($this->referer) { $header[] = "Referer: {$this->referer}"; }
if ($this->hasCookies()) { $header[] = "Cookie: {$this->buildCookieStr()}"; }
$header[] = "Connection: close";
$hostStr = ($this->proxy) ? "http://{$this->domain}" : '';
switch($this->method)
{
case 'get':
case 'GET':
case WRM_GET:
$header[0] = "GET $hostStr{$this->finalURL} HTTP/1.1";
$header[] = '';
$header[] = "Content-Type: text/html";
$header[] = "Content-Length: 0";
$header[] = '';
break;
case 'post':
case 'POST':
case WRM_POST:
if (count($this->postList) == 0) $this->postMode = WRP_NORMAL;
$header[0] = "POST $hostStr{$this->finalURL} HTTP/1.1";
switch ($this->postMode)
{
case WRP_NORMAL:
$postData = $this->buildPostStr();
$requestLen = strlen($postData);
$header[] = "Content-Type: application/x-www-form-urlencoded";
$header[] = "Content-Length: $requestLen";
$header[] = '';
$header[] = $postData;
break;
case WRP_MULTIPART:
$boundary = time() . time();
$postData = $this->buildMultipartPostStr($boundary);
$requestLen = strlen($postData);
$header[] = "Content-Type: multipart/form-data; boundary=$boundary";
$header[] = "Content-Length: $requestLen";
$header[] = '';
$header[] = "$postData";
break;
default:
$this->debug("buildHeader: Terminal failure - unknown postMode '{$this->postMode}'");
throw new Exception("buildHeader: Terminal failure - unknown postMode '{$this->postMode}'");
break;
}
break;
default:
$this->debug("buildHeader: Terminal failure - unknown method '{$this->method}'");
throw new Exception("buildHeader: Terminal failure - unknown method '{$this->method}'");
break;
}
$out = implode("\r\n", $header);
$this->debug("Outbound Header:\n$out");
return $out;
}
protected function buildMultipartPostStr($boundary)
{
$out = array();
foreach($this->postList as $name=>$arr)
{
$value = $arr['content'];
$type = $arr['type'];
$out[] = "--$boundary";
$out[] = "Content-Disposition: form-data; name=\"$name\"";
$out[] = "Content-type: $type";
$out[] = '';
$out[] = "$value";
}
$out[] = "--$boundary--";
return implode("\r\n", $out);
}
protected function buildURL()
{
$this->finalURL = "{$this->url}{$this->buildGetStr()}";
$this->debug("FinalURL: {$this->finalURL}");
}
protected function clearDebugLog()
{
if ($this->debugMode == WRD_LOG)
{
if (!$this->debugLogFile)
throw new Exception('webRequest2: Debug mode set to LOG, but debugLogFile property not set');
if (file_put_contents($this->debugLogFile, '') === false)
throw new Exception('webRequest2: Debug mode set to LOG, but debugLogFile cannot be written to');
}
}
protected function debug($msg)
{
switch($this->debugMode)
{
case WRD_OFF:
return;
case WRD_ECHO:
echo "$msg\n";
break;
case WRD_LOG:
if (!$this->debugLogFile)
throw new Exception('webRequest2: Debug mode set to LOG, but debugLogFile property not set');
if (file_put_contents($this->debugLogFile, "$msg\n\n", FILE_APPEND) == false)
throw new Exception('webRequest2: Debug mode set to LOG, but debugLogFile cannot be written to');
}
}
protected function exec_getContent($count)
{
$thisChunkLen = 0;
$this->debug('exec_getContent: Starts');
while ((!feof($this->socket)) and ($count > 0))
{
$thisBuff = fread($this->socket, $count);
$thisBuffLen += strlen($thisBuff);
if (!strlen($thisBuff))
{
$this->debug("exec_getContent: Timeout?");
$this->errorFlag = true;
break;
}
$this->debug('exec_getContent: Packet Received ' . strlen($thisBuff));
$count -= strlen($thisBuff);
$this->rawResponse .= $thisBuff;
$this->rawContent .= $thisBuff;
// New, 2008-02-13: If we see the existence of an earlyTermStr and it's found in what
// we've received so far, close and and go home...
if ($this->earlyTermStr)
{
$this->debug("exec_getContent: Evaluating content against early term string");
if (strpos($buff, $this->rawContent))
{
$this->debug("exec_getContent: Sees an early termination string '{$this->earlyTermStr}'");
$this->caughtEarlyTerm = true;
return $buff;
}
}
}
$this->debug("exec_getChunk Complete: $thisChunkLen bytes");
}
protected function exec_getHeader()
{
$lines = array();
$this->debug('exec_getHeader: Starts');
$thisLine = trim(fgets($this->socket));
while ((!feof($this->socket)) and ($thisLine))
{
$this->debug("exec_getHeader: Received $thisLine");
$lines[] = $thisLine;
if (substr($thisLine, 0, 4) == 'HTTP')
{
preg_match('~HTTP/([^ ]+) ([0-9]{1,4}) (.*)~', $thisLine, $matches);
$this->resultVersion = $matches[1];
$this->resultCode = $matches[2];
$this->resultMessage = $matches[3];
} else {
preg_match('/([^ ]+): (.*)/', $thisLine, $matches);