我们有一个API,它期望我们自己的特定于供应商的内容类型,例如application/vnd.xxxx.custom.custom-data+json
,但在查看REST.Client的源代码时,它似乎总是默认为REST.Types中的ContentTypes之一,例如在分配时ctNone
在我的正文请求中将默认为ctAPPLICATION_X_WWW_FORM_URLENCODED
。
我尝试将内容类型直接分配给TRESTClient.ContentType属性,但是会被TRESTRequest.ContentType值覆盖。我还在TRESTRequest上添加了自定义内容类型作为参数,该参数的确得到了认可,但最后仍附加了ctAPPLICATION_X_WWW_FORM_URLENCODED
,导致无效的mime类型异常。
begin
APIClient := TRESTClient.Create(API_URL);
APIRequest := TRESTRequest.Create(nil);
try
JsonToSend := TStringStream.Create(strJson, TEncoding.UTF8);
APIClient.Accept := 'application/vnd.xxxx.custom.custom-data+json';
// Below line gets overwritten
APIClient.ContentType := 'application/vnd.xxxx.custom.custom-data+json';
APIRequest.Client := APIClient;
APIRequest.Resource := 'ENDPOINT_URL';
APIRequest.Accept := 'application/vnd.xxxx.custom.custom-data+json';
APIRequest.AddParameter(
'Content-Type',
'application/vnd.xxxx.custom.custom-data+json',
pkHTTPHEADER,
[poDoNotEncode]
); // This includes the custom CT in the request but appends the preset one as well so in this case ctAPPLICATION_X_WWW_FORM_URLENCODED when ctNone is set
APIRequest.AddBody(JsonToSend, ctNone);
APIRequest.Method := rmPost;
try
APIRequest.Execute;
except
on E: Exception do
ShowMessage('Error on request: '#13#10 + e.Message);
end;
finally
JsonToSend.Free;
end;
end;
对我来说,我希望会出现一种情况,如果在标头参数中提供了内容类型,它将使用指定的一种而不是任何一种预设的类型。但是,由于提供了未知的媒体类型,因此引发了API异常。 API异常显示为:
Invalid mime type "application/vnd.xxxx.custom.custom-data+json, application/x-www-form-urlencoded": Invalid token character ',' in token "vnd.xxxx.custom.custom-data+json, application/x-www-form-urlencoded"
我的理解是,它可以识别参数中提供的自定义内容类型,但仍会在请求标头中附加来自REST.Types的预设内容类型之一,从而导致其失败。我希望它发送带有application/vnd.xxxx.custom.custom-data+json
(不包括application/x-www-form-urlencoded
)的请求标头的正文。
答案 0 :(得分:2)
显然TRestCLient
试图在您的情况下表现得太聪明。但是,有一种常规的解决方法。关键是:
ctNone
,ctMULTIPART_FORM_DATA
或ctAPPLICATION_X_WWW_FORM_URLENCODED
中的任何一个。Content-Type
。示例代码:
uses
System.NetConsts;
RESTClient1.BaseURL := 'https://postman-echo.com/post';
RESTRequest1.Method := rmPOST;
RESTRequest1.Body.Add('{ "some": "data" }', ctAPPLICATION_JSON);
RESTRequest1.AddParameter(sContentType, 'application/vnd.hmlr.corres.corres-data+json',
pkHTTPHEADER, [poDoNotEncode]);
RESTRequest1.Execute;
echo服务的响应是:
{
"args":{
},
"data":{
"some":"data"
},
"files":{
},
"form":{
},
"headers":{
"x-forwarded-proto":"https",
"host":"postman-echo.com",
"content-length":"18",
"accept":"application/json, text/plain; q=0.9, text/html;q=0.8,",
"accept-charset":"UTF-8, *;q=0.8",
"content-type":"application/vnd.hmlr.corres.corres-data+json",
"user-agent":"Embarcadero RESTClient/1.0",
"x-forwarded-port":"443"
},
"json":{
"some":"data"
},
"url":"https://postman-echo.com/post"
}
请注意回显的标头,尤其是Content-Type
。我在Delphi 10.2 Tokyo中测试了该示例,因此希望它也可以在XE8中使用。
您观察到的行为是bug (RSP-14001)的fixed in RAD Studio 10.2 Tokyo。
有多种解决方法。仅举几例:
TNetHttpClient
提供的所有其他好处,请TRestClient
。TRestClient
实施细节。最简单的破解方法是修补方法TCustomRESTRequest.ContentType
(请注意,我们讨论的是带有单个参数的不变式),以返回参数ContentType
的{{1}}参数包含类型为AParamsArray
的单个参数。这将允许我们将主体添加到类型为pkREQUESTBODY
的请求中,以便修补的方法也将返回ctNone
,这将有效地防止将另一个值附加到ctNone
标头中。
另一种选择是在推断请求的内容类型之前对方法Content-Type
进行补丁,以偏爱自定义TRESTHTTP.PrepareRequest
头。顺便说一下,这是在RAD Studio 10.2 Tokyo中修复当前实现后的工作方式。此逻辑也适用于其他标头-Content-Type
,Accept
,Accept-Charset
,Accept-Encoding
。修补方法User-Agent
的可视性较难实现,因为它具有TRESTHTTP.PrepareRequest
的可见性。
最困难的选择是修补private
以丢弃辅助内容类型值。这也是最危险的一种,因为它会影响应用程序中与HTTP相关的任何内容(依赖于TWinHTTPRequest.SetHeaderValue
)。修补该类也很困难,但并非并非不可能,因为它已完全隐藏在THTTPClient
的{{1}}部分中。这是一个巨大的耻辱,因为它还会阻止您创建自定义子类。也许是有充分理由的..谁知道;)