Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/delphi/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
使用Delphi/Indy的Amazon MWS API调用_Delphi_Delphi 2010_Indy_Indy10_Amazon Mws - Fatal编程技术网

使用Delphi/Indy的Amazon MWS API调用

使用Delphi/Indy的Amazon MWS API调用,delphi,delphi-2010,indy,indy10,amazon-mws,Delphi,Delphi 2010,Indy,Indy10,Amazon Mws,我正在开发一个与AmazonMWSAPI“对话”的简单应用程序。因为这里有很多现有的代码,所以我需要在Delphi2010中使用Indy10(10.5.5)组件来完成这项工作,我过去曾成功地将其与许多其他API集成。然而,amazonapi似乎对最小的细节非常敏感,以至于我的所有调用都被拒绝,并出现了臭名昭著的“signaturedesnotmatch”错误消息 以下是我迄今为止所取得的成就: 1) 我的应用程序将组装一个请求,使用HMAC-SHA256(使用OpenSSL库)对其进行签名,并将

我正在开发一个与AmazonMWSAPI“对话”的简单应用程序。因为这里有很多现有的代码,所以我需要在Delphi2010中使用Indy10(10.5.5)组件来完成这项工作,我过去曾成功地将其与许多其他API集成。然而,amazonapi似乎对最小的细节非常敏感,以至于我的所有调用都被拒绝,并出现了臭名昭著的“signaturedesnotmatch”错误消息

以下是我迄今为止所取得的成就:

1) 我的应用程序将组装一个请求,使用HMAC-SHA256(使用OpenSSL库)对其进行签名,并将其发送到Amazon服务器端点

2) HMAC签名本身就是一个挑战,但它现在100%的时间都正常工作(通过Amazon Scrachpad生成的请求进行验证)

然而,正如我前面指出的,我的请求总是被MWS服务器以SignatureDesNotMatch错误拒绝,即使它们是可验证的正确的。我能想到的唯一可能导致问题的是Indy处理POST请求的方式,特别是文本编码过程

是否有人成功地将德尔福/印地客户机连接到MWS?如果是,使用了什么类型的HTTP设置?以下是我所拥有的:

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;
如果我在Scratchpad中组装一个GetOrder调用,然后将该调用的时间戳粘贴到上面的代码中,我在这里得到完全相同的查询字符串,具有相同的签名和大小,等等。但是我的Indy请求必须以不同的方式编码,因为MWS服务器不喜欢该调用

我知道MWS至少在“读取”查询字符串,因为如果我将时间戳更改为旧日期,它将返回一个“RequestExpired”错误

亚马逊的技术支持毫无头绪,每天都会发布一条信息,上面写着“确保密钥正确”(就好像没有有效密钥就可以获得HMAC-SHA256和MD5签名一样!!!!)

还有一件事:如果我使用Wireshark“观察”来自上述代码和C-Sharp Amazon示例代码的原始请求,我也看不出有什么区别。但是,我不确定Wireshark是否区分UTF-8和ASCII,或者显示的文本是否有任何编码。我仍然认为这与糟糕的UTC-8编码或类似的东西有关

欢迎并赞赏关于如何正确编码API调用以取悦亚马逊诸神的想法和建议。

发现了问题:Indy(以及Synapse)将端口号添加到“主机”标题行中,直到我用Fiddler更仔细地观察标题(谢谢,@Graymatter!!!!),我才意识到额外的一点


当我将端点更改为mws.amazonservices.com:443(而不仅仅是mws.amazonservices)时,我的签名的计算方式与AWS服务器的方式相同,并且一切工作都很完美。

您是否尝试过使用类似fiddler的工具来询问从其他工具到服务的调用?然后,您可以将生成的请求与它们的请求进行比较。@Graymatter:我使用Wireshark监视流量,还将我的应用程序和Amazon库示例中的呼叫重定向到我自己的web服务器(以便比较请求)。在这两种情况下,请求和签名完全匹配。这就是为什么我认为这可能是一个编码问题。我建议fiddler的原因是,他们有许多不同的视图,包括十六进制,可以让你看到传递的原始数据。有趣。我也来看看这个选项。
TIdHTTP
仅当您在80以外的端口上请求HTTP url,或在443以外的端口上请求HTTPS url时,才会将端口添加到
Host
头中。否则它将忽略端口,因为正在使用默认端口。@Remy:不确定这是否正确。我使用的是Indy 10.5.5,上面的示例甚至没有指定端口,但是添加了:443。Synapse库也会发生这种情况(当我不确定发生了什么时,我用它来排除故障)。但是,.NET或JavaScript调用(IE和Chrome)没有添加任何端口号。事实上,我只是创建了一个简单的测试用例,将一个TIdHTTP和一个TidsLioHandlerSocketOpenSSL放到表单中,然后向Amazon服务器发出GET请求。下面是捕获到的请求原始数据:GET HTTP/1.1 Host:mws.amazonservices.com:443 Accept:text/html,/Accept Encoding:identity User Agent:Mozilla/3.0(兼容;Indy Library)可能是某个地方的设置使我的Indy的行为与预期不同吗?主机:端口出现在
GET
行本身意味着您必须通过代理,否则
TIdHTTP
根本不会将主机:端口放在该行上。这就不同了。顺便说一句,您没有使用Indy的最新版本,即10.6.0.5137。从10.5.5开始,TIdHTTP的内部结构已经有了一些变化,现在已经有好几年了。我也考虑过代理,但事实是:用443对请求进行签名使其与Amazon自己的计算相匹配。如果没有端口号,签名将不匹配,因此远程主机也必须获得这样的主机头。是的,我知道我应该使用最新版本,所以也许这是一个很好的机会