TRESTRequest:是否可以在POST请求中使用自定义媒体类型? [英] TRESTRequest: Is it possible to use custom media types in a POST request?
问题描述
我们有一个API,它期望我们自己的特定于供应商的内容类型,例如 application/vnd.xxxx.custom.custom-data + json
,但在浏览REST.Client的源代码时似乎始终默认为REST中的ContentType类型之一.例如,在我的正文请求中分配 ctNone
时,它将默认为 ctAPPLICATION_X_WWW_FORM_URLENCODED
.
We have an API that expects our own vendor specific content type for example application/vnd.xxxx.custom.custom-data+json
but looking through the source code of REST.Client it seems to always default to one of the ContentTypes in REST.Types for example when assigning ctNone
in my body request it will default to ctAPPLICATION_X_WWW_FORM_URLENCODED
.
我尝试将内容类型直接分配给TRESTClient.ContentType属性,但该内容类型被TRESTRequest.ContentType值覆盖.我还添加了自定义内容类型作为TRESTRequest上的参数,该参数的确得到了认可,但最后仍然附加了 ctAPPLICATION_X_WWW_FORM_URLENCODED
,从而导致无效的mime类型异常.
I've tried assigning the content type directly to the TRESTClient.ContentType property but that gets overwritten by the TRESTRequest.ContentType value. I've also added the custom content type as a parameter on TRESTRequest which does get recognised but still appends ctAPPLICATION_X_WWW_FORM_URLENCODED
on the end causing an invalid mime type exception.
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异常显示为:
To me I would expect there to be a scenario where if a content type has been provided in the header parameters that it would use the one specified rather than any of the preset ones. However, an API exception is raised because an unknown media type was provided. The API exception reads:
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
My understanding is it's recognising my custom content type provided in the params but is also still appending one of the preset content types from REST.Types in that request header causing it to fail. I would expect it to send the body with request header of just application/vnd.xxxx.custom.custom-data+json
excluding application/x-www-form-urlencoded
.
推荐答案
显然 TRestCLient
试图在您的方案中表现得太聪明.但是,有一种常规方法可以解决此问题.关键是:
Aparently TRestCLient
trying to act too smart in your scenario. However there is a regular way around that. The key is:
- 向请求正文添加单个内容,该内容不得为
ctNone
,ctMULTIPART_FORM_DATA
或ctAPPLICATION_X_WWW_FORM_URLENCODED
中的任何一个. - 使用自定义标头值覆盖
Content-Type
.
- to add single content to request body that must not be any of
ctNone
,ctMULTIPART_FORM_DATA
orctAPPLICATION_X_WWW_FORM_URLENCODED
. - to override
Content-Type
using custom header value.
示例代码:
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服务的响应是:
The response from echo service is:
{
"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中使用.
Pay attention to echoed headers, especially Content-Type
of course. I tested the sample in Delphi 10.2 Tokyo, so hopefully it will also work in XE8.
您观察到的行为是错误(RSP-14001),它是<在RAD Studio 10.2 Tokyo中修复的a href ="https://edn.embarcadero.com/article/44747" rel ="nofollow noreferrer"> .
The behaviour you observe is a bug (RSP-14001) that was fixed in RAD Studio 10.2 Tokyo.
有多种解决方法.仅举几例:
There are various ways to resolve that. To name a few:
- 调整您的API 以放弃次要的哑剧类型. 如果您可以放弃
- 将您的客户端实现更改为
TNetHttpClient
. - 升级到RAD Studio 10.2 +.
- 记住!但是强烈建议不要使用此选项,但它可以帮助您更好地了解
TRestClient
实施细节.
TRestClient
提供的所有其他好处,请- Adapt your API to discard secondary mime type.
- Change your client implementation to
TNetHttpClient
instead, if you can give up all additional benefits thatTRestClient
provides. - Upgrade to RAD Studio 10.2+.
- Hack it! This option is however strongly discouraged, but it can help you better understand
TRestClient
implementation details.
最简单的破解方法是修补方法 TCustomRESTRequest.ContentType
(请注意,我们讨论的是带有单个参数的不变式),以返回参数的 ContentType
如果其 AParamsArray
参数包含类型为 pkREQUESTBODY
的单个参数.这将允许我们向 ctNone
类型的请求添加主体,以便修补的方法也将返回 ctNone
,并且这将有效地防止将另一个值附加到 ctNone
输入标题.
The easiest way to hack it would be to patch method TCustomRESTRequest.ContentType
(note we're talking about invariant with a single argument) to return ContentType
of a parameter if its AParamsArray
argument contains single parameter of kind pkREQUESTBODY
. This would allow us to add body to request of type ctNone
so that the patched method would return ctNone
as well and this would effectively prevent appending another value to Content-Type
header.
另一种选择是修补方法 TRESTHTTP.PrepareRequest
,以在推断请求的内容类型之前优先使用自定义的 Content-Type
标头.顺便说一下,这是在RAD Studio 10.2 Tokyo中修复当前实现后的工作方式.此逻辑也适用于其他标头- Accept
, Accept-Charset
, Accept-Encoding
, User-Agent
.修补方法 TRESTHTTP.PrepareRequest
稍难实现,因为它具有 private
可见性.
Another option would be to patch method TRESTHTTP.PrepareRequest
to prefer custom Content-Type
header before inferred content type of the request. This is BTW how the current implementation works after it was fixed in RAD Studio 10.2 Tokyo. This logic is also applied to other headers - Accept
, Accept-Charset
, Accept-Encoding
, User-Agent
. Patching method TRESTHTTP.PrepareRequest
is slightly harder to achieve, because it has private
visibility.
最困难的选择是修补 TWinHTTPRequest.SetHeaderValue
以丢弃辅助内容类型值.这也是最危险的一种,因为它会影响应用程序中与HTTP相关的任何内容(依赖于 THTTPClient
).修补该类也很困难,但并非并非不可能,因为它已完全隐藏在 System.Net.HttpClient.Win.pas
的 implementation
部分中.这是一个巨大的耻辱,因为它还会阻止您创建自定义子类.也许是有充分理由的..谁知道;)
The hardest option would be patching TWinHTTPRequest.SetHeaderValue
to discard secondary content type value. This is also the most dangerous one, because it would have impact to anything HTTP related (that relies on THTTPClient
) in your application. It's also hard, however not impossible, to patch the class, because it's completely hidden in the implementation
section of System.Net.HttpClient.Win.pas
. This is a huge shame, because it also prevents you from creating custom subclasses. Maybe for a good reason .. who knows ;)
这篇关于TRESTRequest:是否可以在POST请求中使用自定义媒体类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!