问题
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:
- 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.
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:
- 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.
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