rest - 德尔福 10.1 : How to get JSON string stored in the body parameter of a REST request (TCustomRESTRequest. 主体)?

标签 rest delphi delphi-10.1-berlin

我正在实现一个 REST 客户端应用程序,它以 JSON 格式(www.coinbase.com 和 https://docs.gdax.com/#introduction)与 Coinbase GDAX 交易 API 通信。我在使用 POST 签署 REST 请求消息时遇到问题。使用 GET 签署 REST 请求消息工作正常。主要是因为 GET 消息没有主体参数,如果可用,主体参数将成为签名的一部分。这个正文参数让我发疯:-) 我正在努力获取存储在 REST 请求 (TCustomRESTRequest.Body) 的正文参数中的 JSON 字符串。我需要这个 JSON 字符串来正确签署 REST 请求消息。如果我在 TCustomRESTRequest.Body 之外传递正文 JSON 字符串(丑陋的解决方法),我会收到 HTTP 错误 400(错误请求)和附加信息“无效签名”。我假设 TCustomRESTRequest.Body 中的 JSON 字符串以某种方式从原始 JSON 字符串更改而来。我想实现的是直接从 TCustomRESTRequest.Body 中读出 JSON 字符串,然后进行签名。

我在类 TCoinbaseAuthenticator(继承自 TCustomAuthenticator)中进行的所有身份验证和签名,或在 DoAuthenticate 方法中更具体:

procedure TCoinbaseAuthenticator.DoAuthenticate(ARequest: TCustomRESTRequest); 
var 
  DateTimeUnix: Int64; 
  DateTimeUnixStr: string; 
  Sign: string; 
  HttpMethod: string; 
  BodyStr: string; 
begin 
  inherited; 

   ARequest.Params.BeginUpdate; 
   try 
     DateTimeUnix := DateTimeToUnix(TTimeZone.Local.ToUniversalTime(Now)); 
     DateTimeUnixStr := IntToStr(DateTimeUnix); 

     HttpMethod := HttpMethodToString(ARequest.Method); 

     // BodyStr := ARequest.Body ... << here I'm strugging to get the JSON string 

     Sign := GenerateSignature(DateTimeUnixStr, HttpMethod, '/' + ARequest.Resource, BodyStr); 

     ARequest.AddAuthParameter('CB-ACCESS-KEY', FAPIKey, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]); 
     ARequest.AddAuthParameter('CB-ACCESS-SIGN', Sign, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]); 
     ARequest.AddAuthParameter('CB-ACCESS-TIMESTAMP', DateTimeUnixStr, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]); 
     ARequest.AddAuthParameter('CB-ACCESS-PASSPHRASE', FAPIPassphrase, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]); 
     ARequest.AddAuthParameter('CB-VERSION', '2015-07-22', TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]); 
     ARequest.AddAuthParameter('Content-Type', 'application/json', TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]); 
   finally 
     ARequest.Params.EndUpdate; 
  end; 
end; 

这是我的签名生成函数(我认为代码没问题,因为如果我不必考虑 body 参数,例如 GET 请求,它就可以正常工作):

function TCoinbaseAuthenticator.GenerateSignature(ATimeStamp: string; AMethod: string; AURL: string; ABody: string): string; 
var 
  s: string; 
  SignStr: string; 
  BitDigest: T256BitDigest; 
  key: string; 
begin 
  s := ATimeStamp+AMethod+AURL+ABody; 
  key := MimeDecodeString(FAPISecret); 
  BitDigest := CalcHMAC_SHA256(key, s); 
  SignStr := SHA256DigestAsString(BitDigest); 
  SignStr := MimeEncodeStringNoCRLF(SignStr); 
  Result := SignStr; 
end; 

Coinbase (GDAX) 消息签名过程的更多见解:

所有 REST 请求必须包含以下 header : CB-ACCESS-KEY:作为字符串的 api key (由 Coinbase 创建) CB-ACCESS-SIGN:base64 编码的签名 CB-ACCESS-TIMESTAMP:请求的时间戳 CB-ACCESS-PASSPHRASE:您在创建 API key 时指定的密码

CB-ACCESS-SIGN header 是通过使用预哈希字符串上的 base64 解码 key 创建 sha256 HMAC 生成的并对输出进行 base64 编码。时间戳值与 CB-ACCESS-TIMESTAMP header 相同。

正文是请求正文字符串,如果没有请求正文(通常用于 GET 请求),则省略。

非常感谢您的帮助!

最佳答案

非常感谢您的评论,雷米!我确实在签名计算中使用了错误的字符集。所以至少我的 super 丑陋的解决方法(将 TCustomRESTRequest.Body 之外的正文 JSON 字符串传递到身份验证器类中)是有效的。

对于问题的第一部分,我认为没有简单的解决方案。我从 Embarcadero 得到了一些支持,我想与其他有类似问题的人分享。

查看用于 Body 属性的 TBody 类,它看起来只有要写入的属性 - 没有任何有助于读回它的东西。我发现,如果我将主体添加到 TRequest,则会创建一个名称为 body 的 TRestRequestParameter。因此,我建议您可以通过 RestRequest1.Params.ParameterByName('body');

不幸的是,这个提议行不通。原因:

参数“body”存储在主体请求参数列表(TCustomRESTRequest.FBody.FParams)中,而不是在RESTRequest参数列表(TCustomRESTRequest.Params)中。由于 TCustomRESTRequest.FBody.FParams 没有公共(public)属性,我无法从外部访问该字段。稍后,参数从 TCustomRESTRequest.FBody.FParams 复制到 TCustomRESTRequest.Params,但这对我来说为时已晚,因为身份验证器(我计算签名的地方)调用较早。在我的 TCoinbaseAuthenticator.DoAuthenticate(ARequest: TCustomRESTRequest) 方法中,ARequest 中的参数列表仍然是空的。

关于rest - 德尔福 10.1 : How to get JSON string stored in the body parameter of a REST request (TCustomRESTRequest. 主体)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41572452/

相关文章:

multithreading - 退出 Parallel.For 循环的最快方法?

delphi - 如何使用 like 子句和元音变音来过滤数据集中的字符串字段?

java - 使用 JavaEE 7 中的 JSON 处理创建业务对象

php - 如何在 Web 应用程序上实现 RESTful 服务?

rest - Office 365 事件日志 API

德尔福编辑器。用制表符代替空格

c# - 在 C# 或 ASP.Net 中从何处启动 REST Web 服务

delphi - 确保始终选中 TPopup 菜单中的至少一项

delphi - 如何在没有冗余列表的情况下迭代父/子层次结构中的对象?

delphi - 是否可以在一种 FMX 表单上使用不同的 Delphi 版本?