TRESTRequest:是否可以在POST请求中使用自定义媒体类型? [英] TRESTRequest: Is it possible to use custom media types in a POST request?

查看:118
本文介绍了TRESTRequest:是否可以在POST请求中使用自定义媒体类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有一个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:

  1. 向请求正文添加单个内容,该内容不得为 ctNone ctMULTIPART_FORM_DATA ctAPPLICATION_X_WWW_FORM_URLENCODED 中的任何一个.
  2. 使用自定义标头值覆盖 Content-Type .
  1. to add single content to request body that must not be any of ctNone, ctMULTIPART_FORM_DATA or ctAPPLICATION_X_WWW_FORM_URLENCODED.
  2. 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:

  1. 调整您的API 以放弃次要的哑剧类型.
  2. 如果您可以放弃 TRestClient 提供的所有其他好处,请
  3. 将您的客户端实现更改为 TNetHttpClient .
  4. 升级到RAD Studio 10.2 +.
  5. 记住!但是强烈建议不要使用此选项,但它可以帮助您更好地了解 TRestClient 实施细节.
  1. Adapt your API to discard secondary mime type.
  2. Change your client implementation to TNetHttpClient instead, if you can give up all additional benefits that TRestClient provides.
  3. Upgrade to RAD Studio 10.2+.
  4. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆