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.
Aparently
TRestCLienttrying to act too smart in your scenario. However there is a regular way around that. The key is:ctNone,ctMULTIPART_FORM_DATAorctAPPLICATION_X_WWW_FORM_URLENCODED.Content-Typeusing custom header value.Sample code:
The response from echo service is:
Pay attention to echoed headers, especially
Content-Typeof 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:
TNetHttpClientinstead, if you can give up all additional benefits thatTRestClientprovides.TRestClientimplementation 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 returnContentTypeof a parameter if itsAParamsArrayargument contains single parameter of kindpkREQUESTBODY. This would allow us to add body to request of typectNoneso that the patched method would returnctNoneas well and this would effectively prevent appending another value toContent-Typeheader.Another option would be to patch method
TRESTHTTP.PrepareRequestto prefer customContent-Typeheader 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 methodTRESTHTTP.PrepareRequestis slightly harder to achieve, because it hasprivatevisibility.The hardest option would be patching
TWinHTTPRequest.SetHeaderValueto discard secondary content type value. This is also the most dangerous one, because it would have impact to anything HTTP related (that relies onTHTTPClient) in your application. It's also hard, however not impossible, to patch the class, because it's completely hidden in theimplementationsection ofSystem.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 ;)