Monday, November 08, 2010 at 12:01 AM.
system.verbs.apps.s3.httpClient
on httpClient(method="GET", resource="/", adrParams=nil, content=nil, adrMeta=nil, acl=nil, content_type="", idaccount="default", debug=false, flHttpMessages=false) {
<<Changes
<<7/10/06; 8:38:52 PM by DW
<<Added flHttpMessages optional param, default false. Determines if HTTP calls display messages in the About window.
<<4/11/06; 7:11:53 AM by DW
<<Changed name of the compiled xml structure to xstruct. It's generally not a good idea to use the names of built-in tables in naming data or code, although this naming couldn't have hurt anything because it's in a table.
<<Fixed the error-handling code. If the server returns an error, we throw a scriptError, but before doing that, we set various fields of the returned table to provide information about the error in case the caller wants to process errors.
<<4/10/06; 7:04:01 AM by DW
<<Adapted from Les's script.
<<3/28/06; 7:37:23 AM by LMO
<<Added metadata header handling
<<3/27/06; 11:38:19 PM by LMO
<<Initial Revision
on hmac (data, key) {
<<Changes
<<3/27/06; 5:57:47 PM by LMO
<<Initial implementation, stolen from Digest::HMAC in Perl
local (BLOCK_SIZE=64);
on sha1 (data) {
<<See: http://www.spicynoodles.net/projects/crypto.html
return (crypto.hashSHA1(data, false))};
local(k_ipad, k_opad, i);
if string.length (key) > BLOCK_SIZE {
key = sha1 (key)};
for i = 0 to BLOCK_SIZE-1 {
if i < string.length(key) {
<<XOR characters of the key
k_char = string.nthChar(key, i+1);
k_ipad = k_ipad + char(bit.logicalXor(k_char, char(0x36)));
k_opad = k_opad + char(bit.logicalXor(k_char, char(0x5c)))}
else {
<<Pad out the rest of the block size length
k_ipad = k_ipad + char(0x36);
k_opad = k_opad + char(0x5c)}};
return (sha1 (k_opad + sha1 (k_ipad + data)))};
local (adrdata = s3.init ());
local (timeOutTicks = 60 * adrdata^.accounts.[idaccount].timeOutSecs);
local (adraccount = @adrdata^.accounts.[idaccount]);
local (nowstring = date.netstandardstring (clock.now ()));
local (hdrs, rv, params);
bundle { // Set up authentication and HTTP headers
local (s, signature, string_to_sign, acl_header_to_sign, meta_to_sign);
new (tabletype, @hdrs);
local (content_MD5 = "");
<<Huh. The Perl s3curl script defines a $contentMD5, but it never uses it.
<<if content != nil
<<content_MD5 = string.hashMD5(content)
if acl != nil {
hdrs.["x-amz-acl"] = acl;
acl_header_to_sign = "x-amz-acl:" + acl + "\n"};
if adrMeta != nil {
meta_to_sign = "";
for i = 1 to sizeof (adrMeta^) {
hdrs["x-amz-meta-"+nameof(adrMeta^[i])] = adrMeta^[i];
<<Note: No space between header name, colon, and value. This was a gotcha.
meta_to_sign = meta_to_sign + "x-amz-meta-"+nameof(adrMeta^[i])+":"+adrMeta^[i]+"\n"}};
bundle { // Build signature for authentication
s = method + "\n";
s = s + content_MD5 + "\n";
s = s + content_type + "\n";
s = s + nowstring + "\n";
s = s + acl_header_to_sign;
s = s + meta_to_sign;
s = s + resource;
if debug {
wp.newTextObject (s, @system.temp.s3signatureString)};
signature = base64.encode(hmac (s, adraccount^.SecretAccessKey), 0);
if debug {
wp.newTextObject (signature, @system.temp.s3signature)}};
hdrs.Date = nowstring;
hdrs.Authorization = "AWS " + adraccount^.AWSAccessKeyId + ":" + signature};
bundle { // Make the HTTP request
local(u, http_rv);
url = adraccount^.apiUrl + resource;
if adrParams != nil {
if sizeOf (adrParams) > 0 {
url = url + "?" + webserver.encodeArgs (adrParams)}};
u = string.urlsplit (url);
if content != nil {
http_rv = tcp.httpClient (method, u[2], path:u[3], adrHdrTable:@hdrs, data:content, datatype:content_type, debug:debug, timeOutTicks:timeOutTicks, flMessages:flHttpMessages)}
else {
http_rv = tcp.httpClient(method, u[2], path:u[3], adrHdrTable:@hdrs, debug:debug, timeOutTicks:timeOutTicks, flMessages:flHttpMessages)};
new (tabletype, @rv);
rv.errorcode = ""; //if non-empty, the code returned by the S3 server
rv.errorstring = ""; //human-readable error string
rv.data = string.httpResultSplit (http_rv, @rv.headers);
try { //parse the data as an XML doc
xml.compile (rv.data, @rv.xstruct);
rv.flerror = false}
else {
rv.errorstring = tryerror;
rv.flerror = true};
if not rv.flerror { //look for an <Error> root element
try {
local (adrError = xml.getAddress (@rv.xstruct, "Error"));
local (errorstring = xml.getValue (adrError, "Message"));
rv.errorstring = "Can't process the request because the S3 server reported an error: \"" + errorstring + "\"";
rv.errorcode = xml.getValue (adrError, "Code");
rv.flerror = true};
if rv.flerror {
scriptError (rv.errorstring)}}};
return (rv)};
bundle { //test code
s3.getMyBuckets (@scratchpad.myBuckets);
edit (@scratchpad.myBuckets)}
This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.