使用 Delphi XE7 和 Indy 类 创建亚马逊 MWS 签名
Creating an amazon MWS signature with Delphi XE7 and Indy classes
我需要为亚马逊 MWS 生成一个签名,因此决定找到一个仅包含组件和 Delphi 附带的 类 的解决方案。因为我将 Indy 用于 HTTP post 本身,所以使用 Indy 类 计算符合 RFC 2104 的 HMAC 似乎是个好主意。
对于其他从事 amazon 集成工作的人来说,"Canonicalized Query String" 的创建在 amazon 教程中有很好的解释:http://docs.developer.amazonservices.com/en_DE/dev_guide/DG_ClientLibraries.html
小心,只需使用 #10 进行换行,因为 #13#10 或 #13 将因签名错误而失败。将“:443”添加到亚马逊端点(主机)也可能很重要,具体取决于 TIdHttp 版本,如问题 #23573799 中所述。
为了创建一个有效的签名,我们必须使用查询字符串和我们在注册后从亚马逊获得的 SecretKey 计算一个 SHA256 的 HMAC,然后,结果必须用 BASE64 编码。
查询字符串已正确生成,并且与亚马逊 Scratchpad 创建的字符串相同。但是调用失败,因为签名不正确。
经过一些测试,我意识到我从查询字符串中得到的签名与我使用 PHP 生成它时得到的结果不同。 PHP 结果被认为是正确的,因为我的 PHP 解决方案长期以来一直与亚马逊一起使用, Delphi 结果不同,这是不正确的。
为了简化测试,我使用“1234567890”作为查询字符串的值,并使用 'ABCDEFG' 作为 SecretKey 的替代值。当我用Delphi得到的结果和我用PHP得到的结果一样时,我相信问题应该解决了。
这是我如何使用 PHP 获得正确结果的方法:
echo base64_encode(hash_hmac('sha256', '1234567890', 'ABCDEFG', TRUE));
这显示了
的结果
aRGlc3RY1pKmKX0hvorkVKNcPigiJX2rksqXzlAeCLg=
以下Delphi XE7代码return是错误的结果,而使用Delphi XE7附带的indy版本:
uses
IdHash, IdHashSHA, IdHMACSHA1, IdSSLOpenSSL, IdGlobal, IdCoderMIME;
function GenerateSignature(const AData, AKey: string): string;
var
AHMAC: TIdBytes;
begin
IdSSLOpenSSL.LoadOpenSSLLibrary;
With TIdHMACSHA256.Create do
try
Key:= ToBytes(AKey, IndyTextEncoding_UTF16LE);
AHMAC:= HashValue(ToBytes(AData, IndyTextEncoding_UTF16LE));
Result:= TIdEncoderMIME.EncodeBytes(AHMAC);
finally
Free;
end;
end;
这是结果,显示在
的备忘录中
Memo.Lines.Text:= GenerateSignature('1234567890', 'ABCDEFG');
是:
jg6Oddxvv57fFdcCPXrqGWB9YD5rSvtmGnZWL0X+y0Y=
我认为问题与编码有关,因此我对此进行了一些研究。正如亚马逊教程(link 见上文)所述,亚马逊需要 utf8 编码。
由于 Indy 函数 "ToBytes" 需要一个字符串,在我的 Delphi 版本中它是一个 UnicodeString,我停止使用其他字符串类型作为参数或变量的 UTF8String 进行测试,但我只是不这样做知道 utf8 应该放在哪里。我也不知道我在上面的代码中使用的编码是否正确。
我选择 UTF16LE,因为 UnicodeString 是 utf16 编码的(有关详细信息,请参阅 http://docwiki.embarcadero.com/RADStudio/Seattle/en/String_Types_(Delphi))并且 LE(小端)在现代机器上最常用。 Delphi 本身的 TEncodings 也有 "Unicode" 和 "BigEndianUnicode",所以 "Unicode" 似乎是 LE 和某种 "standard" Unicode。
当然,我在上面的代码中测试了使用 IndyTextEncoding_UTF8 而不是 IndyTextEncoding_UTF16LE,但无论如何它都不起作用。
因为
TIdEncoderMIME.EncodeBytes(AHMAC);
首先将 TidBytes 写入 Stream,然后使用 8 位编码读取所有内容,这也可能是问题的根源,所以我也用
进行了测试
Result:= BytesToString(AHMAC, IndyTextEncoding_UTF16LE);
Result:= TIdEncoderMIME.EncodeString(Result, IndyTextEncoding_UTF16LE);
但结果是一样的
如果您想查看创建请求的主要代码,请看这里:
function TgboAmazon.MwsRequest(const AFolder, AVersion: string;
const AParams: TStringList; const AEndPoint: string): string;
var
i: Integer;
SL: TStringList;
AMethod, AHost, AURI, ARequest, AStrToSign, APath, ASignature: string;
AKey, AValue, AQuery: string;
AHTTP: TIdHTTP;
AStream, AResultStream: TStringStream;
begin
AMethod:= 'POST';
AHost:= AEndPoint;
AURI:= '/' + AFolder + '/' + AVersion;
AQuery:= '';
SL:= TStringList.Create;
try
SL.Assign(AParams);
SL.Values['AWSAccessKeyId']:= FAWSAccessKeyId;
SL.Values['SellerId']:= FSellerId;
FOR i:=0 TO FMarketplaceIds.Count-1 DO
begin
SL.Values['MarketplaceId.Id.' + IntToStr(i+1)]:= FMarketplaceIds[i];
end;
SL.Values['Timestamp']:= GenerateTimeStamp(Now);
SL.Values['SignatureMethod']:= 'HmacSHA256';
SL.Values['SignatureVersion']:= '2';
SL.Values['Version']:= AVersion;
FOR i:=0 TO SL.Count-1 DO
begin
AKey:= UrlEncode(SL.Names[i]);
AValue:= UrlEncode(SL.ValueFromIndex[i]);
SL[i]:= AKey + '=' + AValue;
end;
SortList(SL);
SL.Delimiter:= '&';
AQuery:= SL.DelimitedText;
AStrToSign:= AMethod + #10 + AHost + #10 + AURI + #10 + AQuery;
TgboUtil.ShowMessage(AStrToSign);
ASignature:= GenerateSignature(AStrToSign, FAWSSecretKey);
TgboUtil.ShowMessage(ASignature);
APath:= 'https://' + AHost + AURI + '?' + AQuery + '&Signature=' + Urlencode(ASignature);
TgboUtil.ShowMessage(APath);
finally
SL.Free;
end;
AHTTP:= TIdHTTP.Create(nil);
try
AHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(AHTTP);
AHTTP.Request.ContentType:= 'text/xml';
AHTTP.Request.Connection:= 'Close';
AHTTP.Request.CustomHeaders.Add('x-amazon-user-agent: MyApp/1.0 (Language=Delphi/XE7)');
AHTTP.HTTPOptions:= AHTTP.HTTPOptions + [hoKeepOrigProtocol];
AHTTP.ProtocolVersion:= pv1_0;
AStream:= TStringStream.Create;
AResultStream:= TStringStream.Create;
try
AHTTP.Post(APath, AStream, AResultStream);
Result:= AResultStream.DataString;
ShowMessage(Result);
finally
AStream.Free;
AResultStream.Free;
end;
finally
AHTTP.Free;
end;
end;
Urlencode 和 GenerateTimestamp 是我自己的函数,它们按照名称所承诺的那样工作,SortList 是我自己的程序,它按照亚马逊要求的字节顺序对字符串列表进行排序,TgboUtil.ShowMessage 是我自己的 ShowMessage 替代方法,它显示包含所有字符的完整消息,仅用于调试。 http协议是1.0,只是为了测试,因为我之前得到了一个403(permission denied)作为HTTPreturn。正如 indy 文档所说,我只是想排除这个问题,因为服务器答案有问题,协议版本 1.1 被认为是不完整的。
这里有几个关于亚马逊 mws 主题的 post,但那个具体问题似乎是新问题。
这里的这个问题可能会帮助那些还没有走到这一步的人,但我也希望有人能提供一个解决方案,让 Delphi 中的签名值与我在 [=74= 中获得的签名值相同].
提前致谢。
使用 Indy 10 的最新 SVN 快照,我无法重现您的签名问题。使用 UTF-8 时,您的示例键+值数据在 Delphi 中产生与 PHP 输出相同的结果。所以,你的 GenerateSignature()
函数没问题,前提是:
您使用 IndyTextEncoding_UTF8
而不是 IndyTextEncoding_UTF16LE
。
您确保 AData
和 AKey
包含有效的输入数据。
此外,您应该确保 TIdHashSHA256.IsAvailable
return 为真,否则 TIdHashHMACSHA256.HashValue()
将失败。
这可能会发生,例如,如果 OpenSSL 加载失败。
试试这个:
function GenerateSignature(const AData, AKey: string): string;
var
AHMAC: TIdBytes;
begin
IdSSLOpenSSL.LoadOpenSSLLibrary;
if not TIdHashSHA256.IsAvailable then
raise Exception.Create('SHA-256 hashing is not available!');
with TIdHMACSHA256.Create do
try
Key := IndyTextEncoding_UTF8.GetBytes(AKey);
AHMAC := HashValue(IndyTextEncoding_UTF8.GetBytes(AData));
finally
Free;
end;
Result := TIdEncoderMIME.EncodeBytes(AHMAC);
end;
也就是说,您的 MwsRequest()
函数存在很多问题:
您正在泄漏 TIdSSLIOHandlerSocketOpenSSL
对象。您没有为其分配 Owner
,并且 TIdHTTP
在分配给其 IOHandler
属性 时不会取得所有权。事实上,分配 IOHanlder
在您的示例中实际上是可选的,请参阅 New HTTPS functionality for TIdHTTP 了解原因。
您将 AHTTP.Request.ContentType
设置为错误的媒体类型。您没有发送 XML 数据,所以不要将媒体类型设置为 'text/xml'
。在这种情况下,您需要将其设置为'application/x-www-form-urlencoded'
。
调用 AHTTP.Post()
时,您的 AStream
流是空的,因此您实际上并未向服务器发送任何数据。您将 AQuery
数据放在 URL 本身的查询字符串中,但它实际上属于 AStream
。如果要在 URL 查询字符串中发送数据,则必须使用 TIdHTTP.Get()
而不是 TIdHTTP.Post()
,并将 AMethod
值更改为 'GET'
共 'POST'
.
您正在使用填充输出 TStream
的 TIdHTTP.Post()
版本。您正在使用 TStringStream
将响应转换为 String
,而不考虑响应数据使用的实际字符集。由于您没有在 TStringStream
构造函数中指定任何 TEncoding
对象,它将使用 TEncoding.Default
进行解码,这可能不会(也可能不会)匹配响应的实际字符集。您应该使用 Post()
的另一个版本 return 是 String
,这样 TIdHTTP
可以根据 HTTPS 服务器报告的实际字符集解码响应数据。
尝试更像这样的东西:
function TgboAmazon.MwsRequest(const AFolder, AVersion: string;
const AParams: TStringList; const AEndPoint: string): string;
var
i: Integer;
SL: TStringList;
AMethod, AHost, AURI, AQuery, AStrToSign, APath, ASignature: string;
AHTTP: TIdHTTP;
begin
AMethod := 'POST';
AHost := AEndPoint;
AURI := '/' + AFolder + '/' + AVersion;
AQuery := '';
SL := TStringList.Create;
try
SL.Assign(AParams);
SL.Values['AWSAccessKeyId'] := FAWSAccessKeyId;
SL.Values['SellerId'] := FSellerId;
for i := 0 to FMarketplaceIds.Count-1 do
begin
SL.Values['MarketplaceId.Id.' + IntToStr(i+1)] := FMarketplaceIds[i];
end;
SL.Values['Timestamp'] := GenerateTimeStamp(Now);
SL.Values['SignatureMethod'] := 'HmacSHA256';
SL.Values['SignatureVersion'] := '2';
SL.Values['Version'] := AVersion;
SL.Values['Signature'] := '';
SortList(SL);
for i := 0 to SL.Count-1 do
SL[i] := UrlEncode(SL.Names[i]) + '=' + UrlEncode(SL.ValueFromIndex[i]);
SL.Delimiter := '&';
SL.QuoteChar := #0;
SL.StrictDelimiter := True;
AQuery := SL.DelimitedText;
finally
SL.Free;
end;
AStrToSign := AMethod + #10 + Lowercase(AHost) + #10 + AURI + #10 + AQuery;
TgboUtil.ShowMessage(AStrToSign);
ASignature := GenerateSignature(AStrToSign, FAWSSecretKey);
TgboUtil.ShowMessage(ASignature);
APath := 'https://' + AHost + AURI;
TgboUtil.ShowMessage(APath);
AHTTP := TIdHTTP.Create(nil);
try
// this is actually optional in this example...
AHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(AHTTP);
AHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
AHTTP.Request.Connection := 'close';
AHTTP.Request.UserAgent := 'MyApp/1.0 (Language=Delphi/XE7)';
AHTTP.Request.CustomHeaders.Values['x-amazon-user-agent'] := 'MyApp/1.0 (Language=Delphi/XE7)';
AHTTP.HTTPOptions := AHTTP.HTTPOptions + [hoKeepOrigProtocol];
AHTTP.ProtocolVersion := pv1_0;
AStream := TStringStream.Create(AQuery + '&Signature=' + Urlencode(ASignature);
try
Result := AHTTP.Post(APath, AStream);
ShowMessage(Result);
finally
AStream.Free;
end;
finally
AHTTP.Free;
end;
end;
但是,由于响应被记录为 XML,最好将对调用者的响应 return 作为 TStream
(不使用 TStringStream
, though) 或 TBytes
而不是 String
。这样,不是 Indy 解码字节,而是让您的 XML 解析器自行解码 raw 字节。 XML 有自己独立于 HTTP 的字符集规则,所以让 XML 解析器为您完成它的工作:
procedure TgboAmazon.MwsRequest(...; Response: TStream);
var
...
begin
...
AHTTP.Post(APath, AStream, Response);
...
end;
我需要为亚马逊 MWS 生成一个签名,因此决定找到一个仅包含组件和 Delphi 附带的 类 的解决方案。因为我将 Indy 用于 HTTP post 本身,所以使用 Indy 类 计算符合 RFC 2104 的 HMAC 似乎是个好主意。
对于其他从事 amazon 集成工作的人来说,"Canonicalized Query String" 的创建在 amazon 教程中有很好的解释:http://docs.developer.amazonservices.com/en_DE/dev_guide/DG_ClientLibraries.html 小心,只需使用 #10 进行换行,因为 #13#10 或 #13 将因签名错误而失败。将“:443”添加到亚马逊端点(主机)也可能很重要,具体取决于 TIdHttp 版本,如问题 #23573799 中所述。
为了创建一个有效的签名,我们必须使用查询字符串和我们在注册后从亚马逊获得的 SecretKey 计算一个 SHA256 的 HMAC,然后,结果必须用 BASE64 编码。
查询字符串已正确生成,并且与亚马逊 Scratchpad 创建的字符串相同。但是调用失败,因为签名不正确。
经过一些测试,我意识到我从查询字符串中得到的签名与我使用 PHP 生成它时得到的结果不同。 PHP 结果被认为是正确的,因为我的 PHP 解决方案长期以来一直与亚马逊一起使用, Delphi 结果不同,这是不正确的。
为了简化测试,我使用“1234567890”作为查询字符串的值,并使用 'ABCDEFG' 作为 SecretKey 的替代值。当我用Delphi得到的结果和我用PHP得到的结果一样时,我相信问题应该解决了。
这是我如何使用 PHP 获得正确结果的方法:
echo base64_encode(hash_hmac('sha256', '1234567890', 'ABCDEFG', TRUE));
这显示了
的结果aRGlc3RY1pKmKX0hvorkVKNcPigiJX2rksqXzlAeCLg=
以下Delphi XE7代码return是错误的结果,而使用Delphi XE7附带的indy版本:
uses
IdHash, IdHashSHA, IdHMACSHA1, IdSSLOpenSSL, IdGlobal, IdCoderMIME;
function GenerateSignature(const AData, AKey: string): string;
var
AHMAC: TIdBytes;
begin
IdSSLOpenSSL.LoadOpenSSLLibrary;
With TIdHMACSHA256.Create do
try
Key:= ToBytes(AKey, IndyTextEncoding_UTF16LE);
AHMAC:= HashValue(ToBytes(AData, IndyTextEncoding_UTF16LE));
Result:= TIdEncoderMIME.EncodeBytes(AHMAC);
finally
Free;
end;
end;
这是结果,显示在
的备忘录中Memo.Lines.Text:= GenerateSignature('1234567890', 'ABCDEFG');
是:
jg6Oddxvv57fFdcCPXrqGWB9YD5rSvtmGnZWL0X+y0Y=
我认为问题与编码有关,因此我对此进行了一些研究。正如亚马逊教程(link 见上文)所述,亚马逊需要 utf8 编码。
由于 Indy 函数 "ToBytes" 需要一个字符串,在我的 Delphi 版本中它是一个 UnicodeString,我停止使用其他字符串类型作为参数或变量的 UTF8String 进行测试,但我只是不这样做知道 utf8 应该放在哪里。我也不知道我在上面的代码中使用的编码是否正确。 我选择 UTF16LE,因为 UnicodeString 是 utf16 编码的(有关详细信息,请参阅 http://docwiki.embarcadero.com/RADStudio/Seattle/en/String_Types_(Delphi))并且 LE(小端)在现代机器上最常用。 Delphi 本身的 TEncodings 也有 "Unicode" 和 "BigEndianUnicode",所以 "Unicode" 似乎是 LE 和某种 "standard" Unicode。 当然,我在上面的代码中测试了使用 IndyTextEncoding_UTF8 而不是 IndyTextEncoding_UTF16LE,但无论如何它都不起作用。
因为
TIdEncoderMIME.EncodeBytes(AHMAC);
首先将 TidBytes 写入 Stream,然后使用 8 位编码读取所有内容,这也可能是问题的根源,所以我也用
进行了测试Result:= BytesToString(AHMAC, IndyTextEncoding_UTF16LE);
Result:= TIdEncoderMIME.EncodeString(Result, IndyTextEncoding_UTF16LE);
但结果是一样的
如果您想查看创建请求的主要代码,请看这里:
function TgboAmazon.MwsRequest(const AFolder, AVersion: string;
const AParams: TStringList; const AEndPoint: string): string;
var
i: Integer;
SL: TStringList;
AMethod, AHost, AURI, ARequest, AStrToSign, APath, ASignature: string;
AKey, AValue, AQuery: string;
AHTTP: TIdHTTP;
AStream, AResultStream: TStringStream;
begin
AMethod:= 'POST';
AHost:= AEndPoint;
AURI:= '/' + AFolder + '/' + AVersion;
AQuery:= '';
SL:= TStringList.Create;
try
SL.Assign(AParams);
SL.Values['AWSAccessKeyId']:= FAWSAccessKeyId;
SL.Values['SellerId']:= FSellerId;
FOR i:=0 TO FMarketplaceIds.Count-1 DO
begin
SL.Values['MarketplaceId.Id.' + IntToStr(i+1)]:= FMarketplaceIds[i];
end;
SL.Values['Timestamp']:= GenerateTimeStamp(Now);
SL.Values['SignatureMethod']:= 'HmacSHA256';
SL.Values['SignatureVersion']:= '2';
SL.Values['Version']:= AVersion;
FOR i:=0 TO SL.Count-1 DO
begin
AKey:= UrlEncode(SL.Names[i]);
AValue:= UrlEncode(SL.ValueFromIndex[i]);
SL[i]:= AKey + '=' + AValue;
end;
SortList(SL);
SL.Delimiter:= '&';
AQuery:= SL.DelimitedText;
AStrToSign:= AMethod + #10 + AHost + #10 + AURI + #10 + AQuery;
TgboUtil.ShowMessage(AStrToSign);
ASignature:= GenerateSignature(AStrToSign, FAWSSecretKey);
TgboUtil.ShowMessage(ASignature);
APath:= 'https://' + AHost + AURI + '?' + AQuery + '&Signature=' + Urlencode(ASignature);
TgboUtil.ShowMessage(APath);
finally
SL.Free;
end;
AHTTP:= TIdHTTP.Create(nil);
try
AHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(AHTTP);
AHTTP.Request.ContentType:= 'text/xml';
AHTTP.Request.Connection:= 'Close';
AHTTP.Request.CustomHeaders.Add('x-amazon-user-agent: MyApp/1.0 (Language=Delphi/XE7)');
AHTTP.HTTPOptions:= AHTTP.HTTPOptions + [hoKeepOrigProtocol];
AHTTP.ProtocolVersion:= pv1_0;
AStream:= TStringStream.Create;
AResultStream:= TStringStream.Create;
try
AHTTP.Post(APath, AStream, AResultStream);
Result:= AResultStream.DataString;
ShowMessage(Result);
finally
AStream.Free;
AResultStream.Free;
end;
finally
AHTTP.Free;
end;
end;
Urlencode 和 GenerateTimestamp 是我自己的函数,它们按照名称所承诺的那样工作,SortList 是我自己的程序,它按照亚马逊要求的字节顺序对字符串列表进行排序,TgboUtil.ShowMessage 是我自己的 ShowMessage 替代方法,它显示包含所有字符的完整消息,仅用于调试。 http协议是1.0,只是为了测试,因为我之前得到了一个403(permission denied)作为HTTPreturn。正如 indy 文档所说,我只是想排除这个问题,因为服务器答案有问题,协议版本 1.1 被认为是不完整的。
这里有几个关于亚马逊 mws 主题的 post,但那个具体问题似乎是新问题。
这里的这个问题可能会帮助那些还没有走到这一步的人,但我也希望有人能提供一个解决方案,让 Delphi 中的签名值与我在 [=74= 中获得的签名值相同].
提前致谢。
使用 Indy 10 的最新 SVN 快照,我无法重现您的签名问题。使用 UTF-8 时,您的示例键+值数据在 Delphi 中产生与 PHP 输出相同的结果。所以,你的 GenerateSignature()
函数没问题,前提是:
您使用
IndyTextEncoding_UTF8
而不是IndyTextEncoding_UTF16LE
。您确保
AData
和AKey
包含有效的输入数据。
此外,您应该确保 TIdHashSHA256.IsAvailable
return 为真,否则 TIdHashHMACSHA256.HashValue()
将失败。
这可能会发生,例如,如果 OpenSSL 加载失败。
试试这个:
function GenerateSignature(const AData, AKey: string): string;
var
AHMAC: TIdBytes;
begin
IdSSLOpenSSL.LoadOpenSSLLibrary;
if not TIdHashSHA256.IsAvailable then
raise Exception.Create('SHA-256 hashing is not available!');
with TIdHMACSHA256.Create do
try
Key := IndyTextEncoding_UTF8.GetBytes(AKey);
AHMAC := HashValue(IndyTextEncoding_UTF8.GetBytes(AData));
finally
Free;
end;
Result := TIdEncoderMIME.EncodeBytes(AHMAC);
end;
也就是说,您的 MwsRequest()
函数存在很多问题:
您正在泄漏
TIdSSLIOHandlerSocketOpenSSL
对象。您没有为其分配Owner
,并且TIdHTTP
在分配给其IOHandler
属性 时不会取得所有权。事实上,分配IOHanlder
在您的示例中实际上是可选的,请参阅 New HTTPS functionality for TIdHTTP 了解原因。您将
AHTTP.Request.ContentType
设置为错误的媒体类型。您没有发送 XML 数据,所以不要将媒体类型设置为'text/xml'
。在这种情况下,您需要将其设置为'application/x-www-form-urlencoded'
。调用
AHTTP.Post()
时,您的AStream
流是空的,因此您实际上并未向服务器发送任何数据。您将AQuery
数据放在 URL 本身的查询字符串中,但它实际上属于AStream
。如果要在 URL 查询字符串中发送数据,则必须使用TIdHTTP.Get()
而不是TIdHTTP.Post()
,并将AMethod
值更改为'GET'
共'POST'
.您正在使用填充输出
TStream
的TIdHTTP.Post()
版本。您正在使用TStringStream
将响应转换为String
,而不考虑响应数据使用的实际字符集。由于您没有在TStringStream
构造函数中指定任何TEncoding
对象,它将使用TEncoding.Default
进行解码,这可能不会(也可能不会)匹配响应的实际字符集。您应该使用Post()
的另一个版本 return 是String
,这样TIdHTTP
可以根据 HTTPS 服务器报告的实际字符集解码响应数据。
尝试更像这样的东西:
function TgboAmazon.MwsRequest(const AFolder, AVersion: string;
const AParams: TStringList; const AEndPoint: string): string;
var
i: Integer;
SL: TStringList;
AMethod, AHost, AURI, AQuery, AStrToSign, APath, ASignature: string;
AHTTP: TIdHTTP;
begin
AMethod := 'POST';
AHost := AEndPoint;
AURI := '/' + AFolder + '/' + AVersion;
AQuery := '';
SL := TStringList.Create;
try
SL.Assign(AParams);
SL.Values['AWSAccessKeyId'] := FAWSAccessKeyId;
SL.Values['SellerId'] := FSellerId;
for i := 0 to FMarketplaceIds.Count-1 do
begin
SL.Values['MarketplaceId.Id.' + IntToStr(i+1)] := FMarketplaceIds[i];
end;
SL.Values['Timestamp'] := GenerateTimeStamp(Now);
SL.Values['SignatureMethod'] := 'HmacSHA256';
SL.Values['SignatureVersion'] := '2';
SL.Values['Version'] := AVersion;
SL.Values['Signature'] := '';
SortList(SL);
for i := 0 to SL.Count-1 do
SL[i] := UrlEncode(SL.Names[i]) + '=' + UrlEncode(SL.ValueFromIndex[i]);
SL.Delimiter := '&';
SL.QuoteChar := #0;
SL.StrictDelimiter := True;
AQuery := SL.DelimitedText;
finally
SL.Free;
end;
AStrToSign := AMethod + #10 + Lowercase(AHost) + #10 + AURI + #10 + AQuery;
TgboUtil.ShowMessage(AStrToSign);
ASignature := GenerateSignature(AStrToSign, FAWSSecretKey);
TgboUtil.ShowMessage(ASignature);
APath := 'https://' + AHost + AURI;
TgboUtil.ShowMessage(APath);
AHTTP := TIdHTTP.Create(nil);
try
// this is actually optional in this example...
AHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(AHTTP);
AHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
AHTTP.Request.Connection := 'close';
AHTTP.Request.UserAgent := 'MyApp/1.0 (Language=Delphi/XE7)';
AHTTP.Request.CustomHeaders.Values['x-amazon-user-agent'] := 'MyApp/1.0 (Language=Delphi/XE7)';
AHTTP.HTTPOptions := AHTTP.HTTPOptions + [hoKeepOrigProtocol];
AHTTP.ProtocolVersion := pv1_0;
AStream := TStringStream.Create(AQuery + '&Signature=' + Urlencode(ASignature);
try
Result := AHTTP.Post(APath, AStream);
ShowMessage(Result);
finally
AStream.Free;
end;
finally
AHTTP.Free;
end;
end;
但是,由于响应被记录为 XML,最好将对调用者的响应 return 作为 TStream
(不使用 TStringStream
, though) 或 TBytes
而不是 String
。这样,不是 Indy 解码字节,而是让您的 XML 解析器自行解码 raw 字节。 XML 有自己独立于 HTTP 的字符集规则,所以让 XML 解析器为您完成它的工作:
procedure TgboAmazon.MwsRequest(...; Response: TStream);
var
...
begin
...
AHTTP.Post(APath, AStream, Response);
...
end;