TRESTRequest: Is it possible to use custom media types in a POST request?

蹲街弑〆低调 提交于 2021-02-10 12:25:35

问题


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.

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;

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"

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.


回答1:


Aparently TRestCLient trying to act too smart in your scenario. However there is a regular way around that. The key is:

  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.

Sample code:

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;

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"
}

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.

Edit

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. 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.

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.

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.

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 ;)



来源:https://stackoverflow.com/questions/56430564/trestrequest-is-it-possible-to-use-custom-media-types-in-a-post-request

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!