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.