Monday, October 1, 2012

Simple Mechanical Turk example using C#


I started looking at the AWS API and specifically the Mechanical Turk Requester API the other day. After reading the overview and understanding the basics I wanted to test making a simple call to get a feel for it. I set up an account and got my access credentials (getting credentials is a whole other story as it involves two different websites and seems overly complicated but it's done for now). I figured the GetAccountBalance operation seemed like the best first option as I didn't want to create a HIT (Human Intelligence Task - their term for tasks you can create) and manage that. Since I hadn't added any money it should be easy for them to return a zero to me.
The web services are REST based and calling them seems pretty straightforward. You figure out the operation you want to perform, set some querystring parameters and make a web request. The complicated bit is calculating the signature (a SHA1 hash of the operation, service and current time using your secret access id as the key). Amazon doesn't have a nice simple example of constructing this in C# and even the other language examples don't look simple. There is a SDK for .NET it looks like it does a good job wrapping the Amazon endpoints but I wanted to figure out the basics for myself. So by starting with the examples from the SDK I was able to simplify the code down to the following:
using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;

namespace TurkTest {
 class Program {

  static void Main(string[] args) {
   const string SERVICE_NAME = "AWSMechanicalTurkRequester";  // requester service for MTurk
   const string TIMESTAMP_FORMAT = "yyyy-MM-ddTHH:mm:ss.fffZ";

   // Modify these with your values.
   const string operation = "GetAccountBalance";
   const string accessKey = "[Your access key]";
   const string secretAccessKey = "[Your secret access key]";

   // Millisecond values in the timestamp string can result in intermittent BadClaimsSupplied errors.
   // Get the current UTC time and use that to create a new time with milliseconds set to zero to avoid this case.
   DateTime now = DateTime.UtcNow;
   now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, DateTimeKind.Utc);
   string timeStamp = now.ToString(TIMESTAMP_FORMAT, CultureInfo.InvariantCulture);

   // Create the hash-based messaged authentication algorithm (SHA1) using our secret access key as the key.
   var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(secretAccessKey));

   // Combine the service name, operation and timestamp and then hash them to produce the signature.
   var dataBytes = Encoding.UTF8.GetBytes(SERVICE_NAME + operation + timeStamp);
   string signature = Convert.ToBase64String(hmac.ComputeHash(dataBytes));

   // Build the URL to send to Amazon
   string url =
    @"https://mechanicalturk.amazonaws.com/?Service=AWSMechanicalTurkRequester&AWSAccessKeyId={0}&Version=2012-03-25&Operation={1}&Signature={2}&Timestamp={3}";
   url = string.Format(url, accessKey, operation, signature, timeStamp);

   // Send the request and write the respose to the console
   using (WebClient client = new WebClient()) {
    using (StreamReader reader = new StreamReader(client.OpenRead(url))) {
     Console.WriteLine(reader.ReadToEnd());
    }
   }

   Console.Read();
  }
 }
}
After a couple of false starts I was able to get it to return my zero balance. Hope this helps the next person get started.