我正在開發一個簡單的應用程序來與亞馬遜MWS API「交談」。由於很多現有的代碼都在這裏發揮作用,我需要在Delphi 2010中使用Indy 10(10.5.5)組件完成此項工作,而這些組件在過去已成功與其他許多API集成。但是,亞馬遜API似乎對最小的細節非常敏感,以至於我的所有電話都被拒絕,並且已經出現了臭名昭着的「SignatureDoesNotMatch」錯誤消息。使用Delphi/Indy的亞馬遜MWS API調用
這裏是我迄今完成:
1)我的應用程序將彙編的請求,與HMAC-SHA256簽名(使用OpenSSL庫),並將其發送到亞馬遜的服務器端點。
2)單獨證明HMAC簽名本身就是一項挑戰,但它現在可以在100%的時間內正常工作(如通過Amazon Scrachpad生成的請求進行驗證)。
但是,正如我前面指出的那樣,我的請求總是被具有SignatureDoesNotMatch錯誤的MWS服務器拒絕,即使它們是可驗證的正確的。我能想到的唯一可能導致問題的方式是Indy可能處理POST請求的方式,特別是文本編碼過程。
有沒有人成功地將Delphi/Indy客戶端連接到MWS?如果是這樣,那麼使用什麼樣的TIdHTTP設置?下面是我有:
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呼叫,那麼該呼叫的時間戳粘貼到上面的代碼,我得到完全相同的查詢字符串這裏,具有相同簽名和大小等但是我的Indy請求必須以不同的方式進行編碼,因爲MWS服務器不喜歡這個呼叫。
我知道MWS至少「讀取」查詢字符串,因爲如果我將時間戳更改爲舊日期,它將返回「請求過期」錯誤。
亞馬遜的技術支持無能爲力,每天都會發布一條消息,內容是「確保密鑰正確」等基本信息(就像使用HMAC-SHA256和MD5獲得簽名無需有效密鑰!!!! )。還有一件事:如果我使用Wireshark從上面的代碼和C-Sharp亞馬遜示例代碼中「觀察」原始請求,我也無法區分。但是,我不確定Wireshark是否區分UTF-8和ASCII或者顯示的文本是什麼編碼。我仍然認爲它與壞的UTC-8編碼或類似的東西有關。
有關如何正確編碼API調用以取悅亞馬遜神的想法和建議,歡迎和讚賞。
您是否嘗試過使用類似提琴手的方式來詢問其他工具對服務的調用?然後,您可以將生成的請求與其中的請求進行比較 – Graymatter
@Graymatter:我使用Wireshark來觀察流量,並且還將來自應用和亞馬遜庫樣本的調用重定向到我自己的Web服務器(這樣我就可以比較這些請求)。在這兩種情況下,請求和簽名完全匹配。這就是爲什麼我認爲這可能是一個編碼問題。 – user1595471
我建議小提琴手的原因是他們有許多不同的視圖,包括十六進制,它可以讓你看到傳遞的原始數據。 – Graymatter