Amazon MWS API Call Using Delphi / Indy

I am developing a simple application for "communicating" with the Amazon MWS API. Since there is a lot of existing code here, I need to do this in Delphi 2010 with the Indy 10 components (10.5.5) that I have successfully used to integrate with many other APIs in the past. However, the Amazon API seems incredibly sensitive to the smallest detail, to the point where all my calls are denied the already infamous "SignatureDoesNotMatch" error message.

Here is what I have done so far:

1) My application will collect the request, sign it using HMAC-SHA256 (using the OpenSSL libraries) and send it to the endpoint of the Amazon server.

2) The HMAC signature alone turned out to be an end in itself, but now it works correctly in 100% of cases (as verified by requests generated by Amazon Scrachpad).

However, as I mentioned earlier, my requests are always rejected by the MWS server with a SignatureDoesNotMatch error, although they are reliably correct. The only thing I can think of that can cause problems is how Indy can handle POST requests, in particular the text encoding process.

Has anyone been successful in connecting a Delphi / Indy client to MWS? If so, what TIdHTTP settings were used? Here is what I have:

procedure TAmazon.TestGetOrder(OrderID:String);

const AwsAccessKey = 'MyAccessKey';
      AwsSecretKey = 'MySecretKey';
      MerchantID = 'MyMerchantID';
      MarketplaceID = 'MyMarketplaceID';
      ApiVersion = '2013-09-01';
      CallUri = '/Orders/2013-09-01';

var HTTP:TIdHTTP;
    SSL:TIdSSLIOHandlerSocketOpenSSL;
    SS:TStringStream;
    Params:TStringList;
    S,Timestamp,QueryString,Key,Value:String;
    i:Integer;

begin
   HTTP:=TIdHTTP.Create(nil);
   SSL:=TIdSSLIOHandlerSocketOpenSSL.Create(nil);
   Params:=TStringList.Create;
   try
      Params.Delimiter:='&';
      Params.StrictDelimiter:=True;

      // HTTP Client Options
      HTTP.HTTPOptions:=HTTP.HTTPOptions+[hoKeepOrigProtocol]-[hoForceEncodeParams];
      HTTP.ConnectTimeout:=5000;
      HTTP.ReadTimeout:=20000;
      HTTP.ProtocolVersion:=pv1_1;
      HTTP.IOHandler:=SSL;
      HTTP.HandleRedirects:=True;
      HTTP.Request.Accept:='text/plain, */*';
      HTTP.Request.AcceptLanguage:='en-US';
      HTTP.Request.ContentType:='application/x-www-form-urlencoded';
      HTTP.Request.CharSet:='utf-8';
      HTTP.Request.UserAgent:='MyApp/1.0 (Language=Delphi)';
      HTTP.Request.CustomHeaders.AddValue('x-amazon-user-agent',HTTP.Request.UserAgent);

      // generate the timestamp per Amazon specs
      Timestamp:=TIso8601.UtcDateTimeToIso8601(TIso8601.ToUtc(Now));
      // we can change the timestamp to match a value from the Scratchpad as a way to validate the signature:
      //Timestamp:='2014-05-09T20:32:28Z';

      // add required parameters from API function GetOrder
      Params.Add('Action=GetOrder');
      Params.Add('SellerId='+MerchantID);
      Params.Add('AWSAccessKeyId='+AwsAccessKey);
      Params.Add('Timestamp='+Timestamp);
      Params.Add('Version='+ApiVersion);
      Params.Add('SignatureVersion=2');
      Params.Add('SignatureMethod=HmacSHA256');
      Params.Add('AmazonOrderId.Id.1='+OrderID);
      // generate the signature using the parameters above
      Params.Add('Signature='+GetSignature(Params.Text,CallUri));

      // after generating the signature, make sure all values are properly URL-Encoded
      for i:=0 to Params.Count-1 do begin
         Key:=Params.Names[i];
         Value:=ParamEnc(Params.ValueFromIndex[i]);
         QueryString:=QueryString+Key+'='+Value+'&';
      end;
      Delete(QueryString,Length(QueryString),1);

      // there are two ways to make the call...
      // #1: according to the documentation, all parameters are supposed to be in
      // the URL, and the body stream is supposed to be empty
      SS:=TStringStream.Create;
      try
         try
            Log('POST '+CallUri+'?'+QueryString);
            S:=HTTP.Post('https://mws.amazonservices.com'+CallUri+'?'+QueryString,SS);
         except
            on E1:EIdHTTPProtocolException do begin
               Log('RawHeaders='+#$D#$A+HTTP.Request.RawHeaders.Text);
               Log('Protocol Exception:'+#$D#$A+StringReplace(E1.ErrorMessage,#10,#$D#$A,[rfReplaceAll]));
            end;
            on E2:Exception do
               Log('Unknown Exception: '+E2.Message);
         end;
         Log('ResponseText='+S);
      finally
         SS.Free;
      end;

      // #2: both the Scratchpad and the CSharp client sample provided by Amazon
      // do things in a different way, though... they POST the parameters in the
      // body of the call, not in the query string
      SS:=TStringStream.Create(QueryString,TEncoding.UTF8);
      try
         try
            SS.Seek(0,0);
            Log('POST '+CallUri+' (parameters in body/stream)');
            S:=HTTP.Post('https://mws.amazonservices.com'+CallUri,SS);
         except
            on E1:EIdHTTPProtocolException do begin
               Log('RawHeaders='+#$D#$A+HTTP.Request.RawHeaders.Text);
               Log('Protocol Exception:'+#$D#$A+StringReplace(E1.ErrorMessage,#10,#$D#$A,[rfReplaceAll]));
            end;
            on E2:Exception do
               Log('Unknown Exception: '+E2.Message);
         end;
         Log('ResponseText='+S);
      finally
         SS.Free;
      end;
   finally
      Params.Free;
      SSL.Free;
      HTTP.Free;
   end;
end;

GetOrder Scratchpad, , , .. Indy , MWS .

, MWS, , "" , , , " ".

Amazon , , ", " ( HMAC-SHA256 MD5 !!!!).

: Wireshark "" , C-Sharp Amazon, . , , Wireshark UTF-8 ASCII - . , UTC-8 - .

, API, , .

+2
1

: Indy ( Synapse ) "Host", , Fiddler (, @Graymatter!!!!).

mws.amazonservices.com:443 ( mws.amazonservices), , AWS, .

+2

All Articles