If you played with the pretty cool Amazon Web Services, you probably started off fetching a sample off the Developer’s Connection pages. At least, I did.
One cool sample for Simple Queue Service is the one by Justin@AWS. But (you saw it coming, I suppose), being anti-PEAR (for many reason, such as dependencies, weight, version conflicts, non-catchiness of the name, fructose intolerance, etc.), I just couldn’t leave the sample like that. Yes, this is me again reinventing the wheel.
The original library depended on two PEAR components:
Crypt_HMAC, for authenticationHTTP_Request, for HTTP calls
The Crypt_HMAC replacement
I replaced the Crypt_HMAC dependency by three potential routines: hash PECL, mhash, and Lance’s function (which you will never hear in a Numb3r’s episode, as it has nothing to do with a mathematician, but rather with a smart dude who posted a fully PHP HMAC code snippet in the PHP comments of the mhash() function).
The code goes as follow:
/**
* HMAC function using hash PECL (http://ca.php.net/manual/en/book.hash.php)
*
* @author Tommy Lacroix
* @param string $stringToSign
* @return string base64 encoded hmac
* @internal
*/
private function hmac_hash($stringToSign) {
return base64_encode(pack('H*',hash_hmac('sha1', $stringToSign, $this->secretKey)));
}
/**
* HMAC function using mhash (http://ca.php.net/manual/en/book.hash.php)
*
* @author Tommy Lacroix
* @param string $stringToSign
* @return string base64 encoded hmac
* @internal
*/
private function hmac_mhash($stringToSign) {
return base64_encode(mhash(MHASH_SHA1, $stringToSign, $this->secretKey));
}
/**
* HMAC function using Lance's function (http://ca.php.net/manual/en/function.mhash.php, see comments)
*
* @author Tommy Lacroix
* @param string $stringToSign
* @return string base64 encoded hmac
* @internal
*/
private function hmac_lance($stringToSign) {
$b = 64;
if (strlen($this->secretKey) > $b) {
$key = pack("H*",sha1($this->secretKey));
} else {
$key = $this->secretKey;
}
$key = str_pad($key, $b, chr(0x00));
$ipad = str_pad('', $b, chr(0x36));
$opad = str_pad('', $b, chr(0x5c));
$k_ipad = $key ^ $ipad ;
$k_opad = $key ^ $opad;
return base64_encode(pack('H*', sha1($k_opad . pack("H*",sha1($k_ipad . $stringToSign)))));
}
HTTP_Request replacement
I replaced the HTTP_Request dependency by two potential routines: cURL and URL wrappers.
The code goes as follow:
/**
* Fetch with curl
*
* @author Tommy Lacroix
* @param string $url
* @param string $qs
* @param bool $post
* @return array(output,httpCode)
* @internal
*/
private function fetch_curl($url, $qs, $post) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_POST, $post);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($curl, CURLOPT_URL, $url . (!$post ? '?'.$qs : ''));
if ($post) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $qs);
}
// Execute
$output = array();
$output[0] = curl_exec($curl);
$output[1] = curl_getinfo($curl,CURLINFO_HTTP_CODE);
// Close handle
curl_close($curl);
return $output;
}
/**
* Fetch with urlwrappers
*
* @author Tommy Lacroix
* @param string $url
* @param string $qs
* @param bool $post
* @return array(output,httpCode)
* @internal
*/
private function fetch_urlwrappers($url, $qs, $post) {
$output = array();
if ($post) {
$opts = array(
'http'=>array(
'method'=>"POST",
'header'=>"Content-type: application/x-www-form-urlencoded\r\n" .
"Content-length: " . strlen($qs),
'content'=>$qs
)
);
$context = stream_context_create($opts);
} else {
$opts = array(
'http'=>array(
'method'=>"GET"
)
);
$context = stream_context_create($opts);
$url .= '?'.$qs;
}
$output[0] = '';
$f = @fopen($url,'r',null,$context);
if (!$f) {
$output[0] = '';
$output[1] = 404;
return $output;
}
while (!feof($f)) {
$output[0] .= fread($f,1024);
}
$meta_data = stream_get_meta_data($f);
if (preg_match('/^HTTP\/1\.[01] ([0-9]{3})/',$meta_data['wrapper_data'][0],$m)) {
$output[1] = $m[1];
} else {
$output[1] = false;
}
fclose($f);
$output[2] = 'wrappers';
return $output;
}
Detection and conclusion
Finally, the constructor includes a small detection mechanism that selects an HMAC and a fetching function:
/**
* Constructor
*
* @author Justin@AWS
* @author Tommy Lacroix
* @param string $accessKey
* @param string $secretKey
* @param string $endpoint http://queue.amazonaws.com
* @param string $queueName optional
* @return SQSClient
*/
public function SQSClient($accessKey, $secretKey, $endpoint = 'http://queue.amazonaws.com', $queueName = '')
{
.
.
.
// Select fetch function
// Call it with $this->{$this->fetchFunction}(...)
if (function_exists('curl_init')) $this->fetchFunction = 'fetch_curl';
else $this->fetchFunction = 'fetch_urlwrappers';
// Select hash function
// Call it with $this->{$this->hmacFunction}(...)
if (function_exists('hash_hmac')) {
$this->hmacFunction = 'hmac_hash';
} else if (function_exists('mhash')) {
$this->hmacFunction = 'hmac_mhash';
} else if (class_exists('Crypt_HMAC')) {
$this->hmacFunction = 'hmac_pear';
} else {
$this->hmacFunction = 'hmac_lance';
}
.
.
.
}
The complete modified sqsClient package can be downloaded below. The original Justin@AWS package is available here.
sqsClientPHP-2.1.zip (11k)
If you like this acrticle, leaving a comment, Digging it, adding it to your del.icio.us bookmarks is always appreciated.
Leave a Reply