Improvements in the submission of the request body and corrections in handling the response.

This commit is contained in:
João Antônio Duarte 2020-09-11 14:55:26 -03:00
parent 465a7ffad4
commit 715a31c0f6
6 changed files with 403 additions and 59 deletions

View File

@ -55,7 +55,13 @@ type
TMVCRESTClientHelper = class sealed TMVCRESTClientHelper = class sealed
public public
class function URIEncode(const aURI: string): string; class function URIEncode(const aURI: string): string;
/// <summary>
/// Convert response content to byte array. If the response is compressed, it is decompressed in the process.
/// </summary>
class function GetResponseContentAsRawBytes(aContentStream: TStream; const aContentEncoding: string): TArray<Byte>; class function GetResponseContentAsRawBytes(aContentStream: TStream; const aContentEncoding: string): TArray<Byte>;
/// <summary>
/// Get the response string, if it is of any type of text.
/// </summary>
class function GetResponseContentAsString(aContentRawBytes: TArray<Byte>; const aContentType: string): string; class function GetResponseContentAsString(aContentRawBytes: TArray<Byte>; const aContentType: string): string;
end; end;
@ -68,22 +74,21 @@ type
AUTHORIZATION_HEADER = 'Authorization'; AUTHORIZATION_HEADER = 'Authorization';
BASIC_AUTH_PREFIX = 'Basic '; BASIC_AUTH_PREFIX = 'Basic ';
BEARER_AUTH_PREFIX = 'Bearer '; BEARER_AUTH_PREFIX = 'Bearer ';
HEADER_RESPONSE_COOKIES = 'Cookies';
SERVER_HEADER = 'server'; SERVER_HEADER = 'server';
REST_UNSAFE_CHARS: TURLEncoding.TUnsafeChars = [Ord('"'), Ord('<'), Ord('>'), Ord('^'), Ord('`'), Ord('{'), REST_UNSAFE_CHARS: TURLEncoding.TUnsafeChars = [Ord('"'), Ord(''''), Ord(':'), Ord(';'), Ord('<'), Ord('='),
Ord('}'), Ord('|'), Ord('/'), Ord('\'), Ord('?'), Ord('#'), Ord('+'), Ord('.')];
QUERY_NAME_UNSAFE_CHARS: TURLEncoding.TUnsafeChars = [Ord('"'), Ord(''''), Ord(':'), Ord(';'), Ord('<'), Ord('='),
Ord('>'), Ord('@'), Ord('['), Ord(']'), Ord('^'), Ord('`'), Ord('{'), Ord('}'), Ord('|'), Ord('/'), Ord('\'), Ord('>'), Ord('@'), Ord('['), Ord(']'), Ord('^'), Ord('`'), Ord('{'), Ord('}'), Ord('|'), Ord('/'), Ord('\'),
Ord('?'), Ord('#'), Ord('&'), Ord('!'), Ord('$'), Ord('('), Ord(')'), Ord(','), Ord('~'), Ord(' '), Ord('*'), Ord('?'), Ord('#'), Ord('&'), Ord('!'), Ord('$'), Ord('('), Ord(')'), Ord(','), Ord('~'), Ord(' '), Ord('*'),
Ord('+')]; Ord('+')];
PATH_UNSAFE_CHARS: TURLEncoding.TUnsafeChars = [Ord('"'), Ord('<'), Ord('>'), Ord('^'), Ord('`'), Ord('{'),
Ord('}'), Ord('|'), Ord('/'), Ord('\'), Ord('?'), Ord('#'), Ord('+'), Ord('.')];
end; end;
implementation implementation
uses uses
IdCompressorZLib, IdCompressorZLib,
MVCFramework.Commons, System.Net.Mime; MVCFramework.Commons,
System.Net.Mime;
{ TMVCRESTParam } { TMVCRESTParam }
@ -107,11 +112,11 @@ begin
try try
lDecompressor := TIdCompressorZLib.Create(nil); lDecompressor := TIdCompressorZLib.Create(nil);
try try
if SameText(aContentEncoding, 'gzip') then if SameText(aContentEncoding, MVC_COMPRESSION_TYPE_AS_STRING[TMVCCompressionType.ctGZIP]) then
begin begin
lDecompressor.DecompressGZipStream(aContentStream, lDecompressed); lDecompressor.DecompressGZipStream(aContentStream, lDecompressed);
end end
else if SameText(aContentEncoding, 'deflate') then else if SameText(aContentEncoding, MVC_COMPRESSION_TYPE_AS_STRING[TMVCCompressionType.ctDeflate]) then
begin begin
lDecompressor.DecompressHTTPDeflate(aContentStream, lDecompressed); lDecompressor.DecompressHTTPDeflate(aContentStream, lDecompressed);
end end
@ -162,12 +167,13 @@ begin
begin begin
if lCharset.isEmpty then if lCharset.isEmpty then
begin begin
lCharset := 'utf-8'; lCharset := TMVCCharSet.UTF_8;
end; end;
lEncoding := TEncoding.GetEncoding(lCharset); lEncoding := TEncoding.GetEncoding(lCharset);
lReader := TStringStream.Create('', lEncoding); lReader := TStringStream.Create('', lEncoding);
try try
lReader.Write(aContentRawBytes, Length(aContentRawBytes));
Result := lReader.DataString; Result := lReader.DataString;
finally finally
FreeAndNil(lReader); FreeAndNil(lReader);

View File

@ -69,7 +69,8 @@ type
function UserAgent: string; overload; function UserAgent: string; overload;
/// <summary> /// <summary>
/// Clears all parameters, except authorization headers. This method is executed after each request is completed. /// Clears all parameters (headers, body, path params and query params). This method is executed after each
/// request is completed.
/// </summary> /// </summary>
function ClearAllParams: IMVCRESTClient; function ClearAllParams: IMVCRESTClient;
@ -93,7 +94,7 @@ type
/// <summary> /// <summary>
/// Add bearer authorization header. Authorization = Bearer &lt;Token&gt; /// Add bearer authorization header. Authorization = Bearer &lt;Token&gt;
/// </summary> /// </summary>
function SetBearerAuthorization(const aToken: string): IMVCRESTClient; function SetBearerAuthorization(const aAccessToken: string): IMVCRESTClient;
/// <summary> /// <summary>
/// Add a header. /// Add a header.
@ -229,7 +230,7 @@ type
const aContentType: string = ''): IMVCRESTClient; overload; const aContentType: string = ''): IMVCRESTClient; overload;
/// <summary> /// <summary>
/// Add a field to the x-www-form-urlencoded body. You must set ContentType to application/x-www-form-urlencoded /// Add a field to the x-www-form-urlencoded body. You must set ContentType to application/x-www-form-urlencoded
/// </summary> /// </summary>
function AddBodyFieldURLEncoded(const aName, aValue: string): IMVCRESTClient; function AddBodyFieldURLEncoded(const aName, aValue: string): IMVCRESTClient;
@ -257,16 +258,16 @@ type
function Get: IMVCRESTResponse; overload; function Get: IMVCRESTResponse; overload;
/// <summary> /// <summary>
/// Execute a Post request. /// Execute a Post request.
/// </summary> /// </summary>
/// <param name="aResource"> /// <param name="aResource">
/// Resource path /// Resource path
/// </param> /// </param>
/// <param name="aBody"> /// <param name="aBody">
/// Object to be serialized. It can be a simple object or a list of objects (TObjectList &lt;T&gt;) /// Object to be serialized. It can be a simple object or a list of objects (TObjectList &lt;T&gt;)
/// </param> /// </param>
/// <param name="aOwnsBody"> /// <param name="aOwnsBody">
/// If OwnsBody is true, Body will be destroyed by IMVCRESTClient. <br /> /// If OwnsBody is true, Body will be destroyed by IMVCRESTClient. <br />
/// </param> /// </param>
function Post(const aResource: string; aBody: TObject; const aOwnsBody: Boolean = True): IMVCRESTResponse; overload; function Post(const aResource: string; aBody: TObject; const aOwnsBody: Boolean = True): IMVCRESTResponse; overload;
function Post(const aResource: string; const aBody: string = ''; function Post(const aResource: string; const aBody: string = '';

View File

@ -77,6 +77,7 @@ type
fHTTPClient: THTTPClient; fHTTPClient: THTTPClient;
fBaseURL: string; fBaseURL: string;
fResource: string; fResource: string;
fInternalContentType: string;
fProxySettings: TProxySettings; fProxySettings: TProxySettings;
fParameters: TList<TMVCRESTParam>; fParameters: TList<TMVCRESTParam>;
fRawBody: TStringStream; fRawBody: TStringStream;
@ -84,7 +85,6 @@ type
fSerializer: IMVCSerializer; fSerializer: IMVCSerializer;
fRttiContext: TRttiContext; fRttiContext: TRttiContext;
function GetBodyFormData: TMultipartFormData; function GetBodyFormData: TMultipartFormData;
function GetContentTypeCharset(const aContentType: string): string;
function ObjectIsList(aObject: TObject): Boolean; function ObjectIsList(aObject: TObject): Boolean;
function SerializeObject(aObject: TObject): string; function SerializeObject(aObject: TObject): string;
procedure SetContentType(const aContentType: string); procedure SetContentType(const aContentType: string);
@ -99,6 +99,7 @@ type
procedure DoApplyHeaders; procedure DoApplyHeaders;
procedure DoApplyCookies(const aURL: string); procedure DoApplyCookies(const aURL: string);
procedure DoEncodeURL(var aURL: string); procedure DoEncodeURL(var aURL: string);
procedure DoPrepareBodyRequest(var aBodyStream: TStream);
function ExecuteRequest(const aMethod: TMVCHTTPMethodType): IMVCRESTResponse; function ExecuteRequest(const aMethod: TMVCHTTPMethodType): IMVCRESTResponse;
public public
@ -130,7 +131,8 @@ type
function UserAgent: string; overload; function UserAgent: string; overload;
/// <summary> /// <summary>
/// Clears all parameters, except authorization headers. This method is executed after each request is completed. /// Clears all parameters (headers, body, path params and query params). This method is executed after each
/// request is completed.
/// </summary> /// </summary>
function ClearAllParams: IMVCRESTClient; function ClearAllParams: IMVCRESTClient;
@ -154,7 +156,7 @@ type
/// <summary> /// <summary>
/// Add bearer authorization header. Authorization = Bearer &lt;Token&gt; /// Add bearer authorization header. Authorization = Bearer &lt;Token&gt;
/// </summary> /// </summary>
function SetBearerAuthorization(const aToken: string): IMVCRESTClient; function SetBearerAuthorization(const aAccessToken: string): IMVCRESTClient;
/// <summary> /// <summary>
/// Add a header. /// Add a header.
@ -464,12 +466,12 @@ var
lContentCharset: string; lContentCharset: string;
lEncoding: TEncoding; lEncoding: TEncoding;
lBytes: TArray<Byte>; lBytes: TArray<Byte>;
lContentType: string;
begin begin
Result := Self; Result := Self;
SetContentType(aContentType); SplitContentMediaTypeAndCharset(aContentType, lContentType, lContentCharset);
lContentCharset := GetContentTypeCharset(aContentType);
if lContentCharset.IsEmpty then if lContentCharset.IsEmpty then
begin begin
lContentCharset := TMVCCharSet.UTF_8; lContentCharset := TMVCCharSet.UTF_8;
@ -477,8 +479,11 @@ begin
lEncoding := TEncoding.GetEncoding(lContentCharset); lEncoding := TEncoding.GetEncoding(lContentCharset);
try try
fRawBody.Clear; fRawBody.Clear;
lBytes := TEncoding.Convert(TEncoding.Default, lEncoding, TEncoding.Default.GetBytes(aBody)); lBytes := TEncoding.Convert(TEncoding.Default, lEncoding, TEncoding.Default.GetBytes(aBody));
lBytes := lEncoding.GetBytes(aBody);
fRawBody.WriteData(lBytes, Length(lBytes)); fRawBody.WriteData(lBytes, Length(lBytes));
SetContentType(BuildContentType(lContentType, lContentCharset));
finally finally
FreeAndNil(lEncoding); FreeAndNil(lEncoding);
end; end;
@ -657,7 +662,7 @@ end;
function TMVCRESTClient.Async(aCompletionHandler: TProc<IMVCRESTResponse>; function TMVCRESTClient.Async(aCompletionHandler: TProc<IMVCRESTResponse>;
aCompletionHandlerWithError: TProc<Exception>; const aSynchronized: Boolean): IMVCRESTClient; aCompletionHandlerWithError: TProc<Exception>; const aSynchronized: Boolean): IMVCRESTClient;
begin begin
// Needs implementation
end; end;
function TMVCRESTClient.BaseURL(const aBaseURL: string): IMVCRESTClient; function TMVCRESTClient.BaseURL(const aBaseURL: string): IMVCRESTClient;
@ -685,6 +690,7 @@ function TMVCRESTClient.ClearAllParams: IMVCRESTClient;
begin begin
Result := Self; Result := Self;
fParameters.Clear; fParameters.Clear;
ClearBody;
end; end;
function TMVCRESTClient.ClearBody: IMVCRESTClient; function TMVCRESTClient.ClearBody: IMVCRESTClient;
@ -693,7 +699,9 @@ begin
if Assigned(fBodyFormData) then if Assigned(fBodyFormData) then
FreeAndNil(fBodyFormData); FreeAndNil(fBodyFormData);
Result := Self;
ClearParameters(TMVCRESTParamType.FormURLEncoded); ClearParameters(TMVCRESTParamType.FormURLEncoded);
fInternalContentType := '';
end; end;
function TMVCRESTClient.ClearCookies: IMVCRESTClient; function TMVCRESTClient.ClearCookies: IMVCRESTClient;
@ -829,6 +837,7 @@ procedure TMVCRESTClient.DoApplyHeaders;
var var
lParam: TMVCRESTParam; lParam: TMVCRESTParam;
begin begin
fHTTPClient.CustHeaders.Clear;
for lParam in fParameters do for lParam in fParameters do
begin begin
if lParam.&Type = TMVCRESTParamType.Header then if lParam.&Type = TMVCRESTParamType.Header then
@ -849,7 +858,8 @@ begin
if lParam.&Type = TMVCRESTParamType.Path then if lParam.&Type = TMVCRESTParamType.Path then
begin begin
lReplace := '{' + lParam.Name + '}'; lReplace := '{' + lParam.Name + '}';
lEncodedParam := TMVCRESTClientHelper.URIEncode(lParam.Value); lEncodedParam := TNetEncoding.URL.Encode(lParam.Value, TMVCRESTClientConsts.PATH_UNSAFE_CHARS,
[TURLEncoding.TEncodeOption.EncodePercent]);
aURL := aURL.Replace(lReplace, lEncodedParam, [rfReplaceAll, rfIgnoreCase]); aURL := aURL.Replace(lReplace, lEncodedParam, [rfReplaceAll, rfIgnoreCase]);
end; end;
end; end;
@ -866,8 +876,7 @@ begin
begin begin
if lParam.&Type = TMVCRESTParamType.Query then if lParam.&Type = TMVCRESTParamType.Query then
begin begin
lName := TNetEncoding.URL.Encode(lParam.Name, TMVCRESTClientConsts.QUERY_NAME_UNSAFE_CHARS, lName := TMVCRESTClientHelper.URIEncode(lParam.Name);
[TURLEncoding.TEncodeOption.EncodePercent]);
lValue := TNetEncoding.URL.EncodeForm(lParam.Value); lValue := TNetEncoding.URL.EncodeForm(lParam.Value);
if aURL.Contains('?') then if aURL.Contains('?') then
@ -896,11 +905,55 @@ begin
aURL := TURI.Create(aURL).Encode; aURL := TURI.Create(aURL).Encode;
end; end;
procedure TMVCRESTClient.DoPrepareBodyRequest(var aBodyStream: TStream);
var
lContentType: string;
lContentCharset: string;
lParam: TMVCRESTParam;
lName: string;
lValue: string;
lBody: string;
begin
SplitContentMediaTypeAndCharset(fInternalContentType, lContentType, lContentCharset);
if SameText(lContentType, TMVCMediaType.MULTIPART_FORM_DATA) then
begin
aBodyStream := GetBodyFormData.Stream;
SetContentType(GetBodyFormData.MimeTypeHeader);
fHTTPClient.CustHeaders[sContentType] := GetBodyFormData.MimeTypeHeader;
end
else if SameText(lContentType, TMVCMediaType.APPLICATION_FORM_URLENCODED) then
begin
lBody := '';
for lParam in fParameters do
begin
if lParam.&Type = TMVCRESTParamType.FormURLEncoded then
begin
lName := TMVCRESTClientHelper.URIEncode(lParam.Name);
lValue := TNetEncoding.URL.EncodeForm(lParam.Value);
if not lBody.IsEmpty then
lBody := lBody + '&';
lBody := lBody + lName + '=' + lValue;
end;
end;
AddBody(lBody, fInternalContentType);
aBodyStream := fRawBody;
end
else
begin
aBodyStream := fRawBody;
end;
aBodyStream.Position := 0;
end;
function TMVCRESTClient.ExecuteRequest(const aMethod: TMVCHTTPMethodType): IMVCRESTResponse; function TMVCRESTClient.ExecuteRequest(const aMethod: TMVCHTTPMethodType): IMVCRESTResponse;
var var
lURL: string; lURL: string;
lResponse: IHTTPResponse; lResponse: IHTTPResponse;
lBodyStream: TStream;
begin begin
fHTTPClient.ProxySettings := fProxySettings;
lURL := GetFullURL; lURL := GetFullURL;
DoConvertMVCPathParamsToRESTParams(lURL); DoConvertMVCPathParamsToRESTParams(lURL);
DoApplyPathParams(lURL); DoApplyPathParams(lURL);
@ -909,23 +962,30 @@ begin
DoApplyHeaders; DoApplyHeaders;
DoApplyCookies(lURL); DoApplyCookies(lURL);
lBodyStream := nil;
DoPrepareBodyRequest(lBodyStream);
case aMethod of case aMethod of
httpGET: httpGET:
lResponse := fHTTPClient.Get(lURL, nil, []); begin
lResponse := fHTTPClient.Get(lURL, nil, [])
end;
httpPOST: httpPOST:
; begin
lResponse := fHTTPClient.Post(lURL, lBodyStream, nil, []);
end;
httpPUT: httpPUT:
; begin
httpDELETE: lResponse := fHTTPClient.Put(lURL, lBodyStream, nil, []);
; end;
httpHEAD:
;
httpOPTIONS:
;
httpPATCH: httpPATCH:
; begin
httpTRACE: lResponse := fHTTPClient.Patch(lURL, lBodyStream, nil, []);
; end;
httpDELETE:
begin
lResponse := fHTTPClient.Delete(lURL, nil, []);
end;
end; end;
Result := TMVCRESTResponse.Create(lResponse); Result := TMVCRESTResponse.Create(lResponse);
@ -946,13 +1006,6 @@ begin
Result := fBodyFormData; Result := fBodyFormData;
end; end;
function TMVCRESTClient.GetContentTypeCharset(const aContentType: string): string;
var
lContentType: string;
begin
SplitContentMediaTypeAndCharset(aContentType, lContentType, Result);
end;
function TMVCRESTClient.GetFullURL: string; function TMVCRESTClient.GetFullURL: string;
var var
lResource: string; lResource: string;
@ -1206,17 +1259,29 @@ begin
end; end;
function TMVCRESTClient.SetBasicAuthorization(const aUsername, aPassword: string): IMVCRESTClient; function TMVCRESTClient.SetBasicAuthorization(const aUsername, aPassword: string): IMVCRESTClient;
var
lBase64: TNetEncoding;
lAuthValue: string;
begin begin
// Do not use TNetEncoding.Base64 here, because it may break long line
lBase64 := TBase64Encoding.Create(0, '');
try
lAuthValue := TMVCRESTClientConsts.BASIC_AUTH_PREFIX + lBase64.Encode(aUsername + ':' + aPassword);
finally
FreeAndNil(lBase64);
end;
Result := AddHeader(TMVCRESTClientConsts.AUTHORIZATION_HEADER, lAuthValue);
end; end;
function TMVCRESTClient.SetBearerAuthorization(const aToken: string): IMVCRESTClient; function TMVCRESTClient.SetBearerAuthorization(const aAccessToken: string): IMVCRESTClient;
begin begin
Result := AddHeader(TMVCRESTClientConsts.AUTHORIZATION_HEADER, TMVCRESTClientConsts.BEARER_AUTH_PREFIX +
aAccessToken);
end; end;
procedure TMVCRESTClient.SetContentType(const aContentType: string); procedure TMVCRESTClient.SetContentType(const aContentType: string);
begin begin
fInternalContentType := aContentType;
AddHeader(sContentType, aContentType); AddHeader(sContentType, aContentType);
end; end;
@ -1302,7 +1367,7 @@ begin
fServer := aHTTPResponse.HeaderValue[TMVCRESTClientConsts.SERVER_HEADER]; fServer := aHTTPResponse.HeaderValue[TMVCRESTClientConsts.SERVER_HEADER];
fRawBytes := TMVCRESTClientHelper.GetResponseContentAsRawBytes(aHTTPResponse.ContentStream, fRawBytes := TMVCRESTClientHelper.GetResponseContentAsRawBytes(aHTTPResponse.ContentStream,
aHTTPResponse.ContentEncoding); aHTTPResponse.ContentEncoding);
fContent := TMVCRESTClientHelper.GetResponseContentAsString(fRawBytes, aHTTPResponse.ContentCharSet); fContent := TMVCRESTClientHelper.GetResponseContentAsString(fRawBytes, aHTTPResponse.HeaderValue[sContentType]);
fContentType := aHTTPResponse.HeaderValue[sContentType]; fContentType := aHTTPResponse.HeaderValue[sContentType];
fContentEncoding := aHTTPResponse.ContentEncoding; fContentEncoding := aHTTPResponse.ContentEncoding;
fContentLength := aHTTPResponse.ContentLength; fContentLength := aHTTPResponse.ContentLength;

View File

@ -1,7 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<ProjectGuid>{0582DE6A-D716-46D3-8CBD-84AD73A4B536}</ProjectGuid> <ProjectGuid>{0582DE6A-D716-46D3-8CBD-84AD73A4B536}</ProjectGuid>
<ProjectVersion>19.0</ProjectVersion> <ProjectVersion>19.1</ProjectVersion>
<FrameworkType>VCL</FrameworkType> <FrameworkType>VCL</FrameworkType>
<Base>True</Base> <Base>True</Base>
<Config Condition="'$(Config)'==''">GUI</Config> <Config Condition="'$(Config)'==''">GUI</Config>
@ -709,6 +709,32 @@
<Operation>0</Operation> <Operation>0</Operation>
</Platform> </Platform>
</DeployClass> </DeployClass>
<DeployClass Name="iOS_AppStore1024">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon152">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon167">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Launch1024"> <DeployClass Name="iPad_Launch1024">
<Platform Name="iOSDevice"> <Platform Name="iOSDevice">
<Operation>1</Operation> <Operation>1</Operation>
@ -871,6 +897,56 @@
<Operation>1</Operation> <Operation>1</Operation>
</Platform> </Platform>
</DeployClass> </DeployClass>
<DeployClass Name="iPad_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_SpotLight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon180">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch1125"> <DeployClass Name="iPhone_Launch1125">
<Platform Name="iOSDevice32"> <Platform Name="iOSDevice32">
<Operation>1</Operation> <Operation>1</Operation>
@ -1065,6 +1141,66 @@
<Operation>1</Operation> <Operation>1</Operation>
</Platform> </Platform>
</DeployClass> </DeployClass>
<DeployClass Name="iPhone_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification60">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting87">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectAndroidManifest"> <DeployClass Name="ProjectAndroidManifest">
<Platform Name="Android"> <Platform Name="Android">
<Operation>1</Operation> <Operation>1</Operation>

View File

@ -678,7 +678,7 @@ begin
try try
// no request body // no request body
lRes := RESTClient lRes := RESTClient
.AddBody('', False, TMVCMediaType.APPLICATION_JSON) // To define a Content-Type add an empty body with the type. .AddBody('', TMVCMediaType.APPLICATION_JSON) // To define a Content-Type add an empty body with the type.
.Post('/system/users/logged'); .Post('/system/users/logged');
Assert.areEqual<Integer>(HTTP_STATUS.BadRequest, lRes.StatusCode, Assert.areEqual<Integer>(HTTP_STATUS.BadRequest, lRes.StatusCode,
'Empty request body doesn''t return HTTP 400 Bad Request'); 'Empty request body doesn''t return HTTP 400 Bad Request');
@ -1153,7 +1153,7 @@ procedure TServerTest.TestProducesConsumes02;
var var
res: IMVCRESTResponse; res: IMVCRESTResponse;
begin begin
res := RESTClient.Accept('text/plain').Post('/testconsumes', 'Hello World', False, TMVCMediaType.TEXT_PLAIN); res := RESTClient.Accept('text/plain').Post('/testconsumes', 'Hello World', TMVCMediaType.TEXT_PLAIN);
Assert.areEqual('Hello World', res.Content); Assert.areEqual('Hello World', res.Content);
Assert.areEqual(TMVCMediaType.TEXT_PLAIN, res.ContentType, True); Assert.areEqual(TMVCMediaType.TEXT_PLAIN, res.ContentType, True);
@ -1173,7 +1173,7 @@ begin
// Assert.areEqual(BuildContentType(TMVCMediaType.TEXT_PLAIN, TMVCCharSet.ISO88591), res.ContentType, True); // Assert.areEqual(BuildContentType(TMVCMediaType.TEXT_PLAIN, TMVCCharSet.ISO88591), res.ContentType, True);
res := RESTClient.Accept(TMVCMediaType.TEXT_PLAIN) res := RESTClient.Accept(TMVCMediaType.TEXT_PLAIN)
.Post('/testconsumes/textiso8859_1', 'this is an iso8859-1 text', False, TMVCMediaType.TEXT_PLAIN); .Post('/testconsumes/textiso8859_1', 'this is an iso8859-1 text', TMVCMediaType.TEXT_PLAIN);
Assert.areEqual<Integer>(HTTP_STATUS.OK, res.StatusCode); Assert.areEqual<Integer>(HTTP_STATUS.OK, res.StatusCode);
Assert.areEqual('this is an iso8859-1 text', res.Content); Assert.areEqual('this is an iso8859-1 text', res.Content);
Assert.areEqual(TMVCMediaType.TEXT_PLAIN, res.ContentType, True); Assert.areEqual(TMVCMediaType.TEXT_PLAIN, res.ContentType, True);

View File

@ -7,7 +7,7 @@
<TargetedPlatforms>129</TargetedPlatforms> <TargetedPlatforms>129</TargetedPlatforms>
<AppType>Console</AppType> <AppType>Console</AppType>
<FrameworkType>None</FrameworkType> <FrameworkType>None</FrameworkType>
<ProjectVersion>19.0</ProjectVersion> <ProjectVersion>19.1</ProjectVersion>
<Platform Condition="'$(Platform)'==''">Win32</Platform> <Platform Condition="'$(Platform)'==''">Win32</Platform>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''"> <PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
@ -146,12 +146,6 @@
<Overwrite>true</Overwrite> <Overwrite>true</Overwrite>
</Platform> </Platform>
</DeployFile> </DeployFile>
<DeployFile LocalName="TestServer" Configuration="CI" Class="ProjectOutput">
<Platform Name="Linux64">
<RemoteName>TestServer</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="bin\www\index.html" Configuration="CI" Class="File"> <DeployFile LocalName="bin\www\index.html" Configuration="CI" Class="File">
<Platform Name="Linux64"> <Platform Name="Linux64">
<RemoteDir>.\www</RemoteDir> <RemoteDir>.\www</RemoteDir>
@ -159,6 +153,12 @@
<Overwrite>true</Overwrite> <Overwrite>true</Overwrite>
</Platform> </Platform>
</DeployFile> </DeployFile>
<DeployFile LocalName="TestServer" Configuration="CI" Class="ProjectOutput">
<Platform Name="Linux64">
<RemoteName>TestServer</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="bin\customers.json" Configuration="CI" Class="File"> <DeployFile LocalName="bin\customers.json" Configuration="CI" Class="File">
<Platform Name="Linux64"> <Platform Name="Linux64">
<RemoteName>customers.json</RemoteName> <RemoteName>customers.json</RemoteName>
@ -541,6 +541,32 @@
<Operation>0</Operation> <Operation>0</Operation>
</Platform> </Platform>
</DeployClass> </DeployClass>
<DeployClass Name="iOS_AppStore1024">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon152">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon167">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Launch1024x768"> <DeployClass Name="iPad_Launch1024x768">
<Platform Name="iOSDevice32"> <Platform Name="iOSDevice32">
<Operation>1</Operation> <Operation>1</Operation>
@ -671,6 +697,56 @@
<Operation>1</Operation> <Operation>1</Operation>
</Platform> </Platform>
</DeployClass> </DeployClass>
<DeployClass Name="iPad_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_SpotLight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon180">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch1125"> <DeployClass Name="iPhone_Launch1125">
<Platform Name="iOSDevice32"> <Platform Name="iOSDevice32">
<Operation>1</Operation> <Operation>1</Operation>
@ -865,6 +941,66 @@
<Operation>1</Operation> <Operation>1</Operation>
</Platform> </Platform>
</DeployClass> </DeployClass>
<DeployClass Name="iPhone_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification60">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting87">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimulator">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectAndroidManifest"> <DeployClass Name="ProjectAndroidManifest">
<Platform Name="Android"> <Platform Name="Android">
<Operation>1</Operation> <Operation>1</Operation>