From with a PHP program, I'd like to make an HTTP request and use the response body in my program. What could be simpler? PHP has a zillion ways to do it, not limited to:
file_get_contents(),
fopen() and
fread(),
fsockopen(), curl, and PEAR HTTP_Request.
One additional requirement that makes this slightly more interesting: I don't want to waste any memory on a response body that's too big. If I slurp in a giant request body, I might run over my
memory_limit.
Here's what I came up with to solve this problem:
class CurlReader {
public $body;
public $headers;
protected $c;
protected $maxLength;
public function __construct($maxLength) {
$this->maxLength = $maxLength;
$this->c = curl_init();
curl_setopt($this->c, CURLOPT_HEADERFUNCTION, array($this,'parseHeader'));
curl_setopt($this->c, CURLOPT_WRITEFUNCTION, array($this, 'parseBody'));
}
protected function parseHeader($curl, $data) {
if (stripos($data, 'content-length: ') === 0) {
$contentLength = (int) substr($data,strlen('content-length: '));
if ($contentLength > $this->maxLength) {
throw new Exception('Response Too Big');
}
}
list($header, $value) = explode(': ', $data, 2);
if (strlen($header = trim($header))) {
$this->headers[$header] = trim($value);
}
return strlen($data);
}
protected function parseBody($curl, $data) {
$this->body .= $data;
return strlen($data);
}
public function get($url) {
$this->headers = array();
$this->body = '';
curl_setopt($this->c, CURLOPT_URL, $url);
try {
return @curl_exec($this->c);
} catch (Exception $e) {
print $e->getMessage();
return false;
}
}
}
Then, the class can be used like this:
$c = new CurlReader(1048576);
if ($c->get('http://www.sklar.com/')) {
// Do something with $c->body;
}
if ($c->get('http://www.sklar.com/images/sklar.head.color.300dpi.jpeg')) {
// Do something with $c->body;
} else {
print 'Your giant image is too big.';
}
A few notes/potential improvements:
- Throwing an exception from inside a curl callback segfaults in PHP 5.0.4. This is fixed in PHP 5.0.5.
- It might be nice to keep track of the length as it accumulates in parseBody() so as to stop accumulating when the body has reached $maxLength bytes. This would handle responses that don't include a Content-Length header.
- Yes, you could do this by opening the socket yourself, explicitly parsing response headers, and then just closing the socket if the Content-Length was too big. But then you don't get all the goodies that curl provides, like handling chunked content encoding, managing HTTP 1.1 keep-alives, and so on.