2020-09-09 02:00:08 +02:00
|
|
|
|
// ***************************************************************************
|
|
|
|
|
//
|
|
|
|
|
// Delphi MVC Framework
|
|
|
|
|
//
|
2021-08-15 18:39:55 +02:00
|
|
|
|
// Copyright (c) 2010-2021 Daniele Teti and the DMVCFramework Team
|
2020-09-09 02:00:08 +02:00
|
|
|
|
//
|
|
|
|
|
// https://github.com/danieleteti/delphimvcframework
|
|
|
|
|
//
|
|
|
|
|
// Collaborators on this file:
|
|
|
|
|
// Jo<4A>o Ant<6E>nio Duarte (https://github.com/joaoduarte19)
|
|
|
|
|
//
|
|
|
|
|
// ***************************************************************************
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
//
|
|
|
|
|
// *************************************************************************** }
|
|
|
|
|
|
|
|
|
|
unit MVCFramework.RESTClient.Commons;
|
|
|
|
|
|
|
|
|
|
{$I dmvcframework.inc}
|
|
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
|
|
uses
|
2020-09-10 02:40:14 +02:00
|
|
|
|
System.NetEncoding,
|
|
|
|
|
System.SysUtils,
|
2020-09-18 01:06:24 +02:00
|
|
|
|
System.Classes,
|
2020-09-24 20:03:11 +02:00
|
|
|
|
MVCFramework.Commons,
|
|
|
|
|
System.Net.HttpClient;
|
2020-09-09 02:00:08 +02:00
|
|
|
|
|
|
|
|
|
type
|
|
|
|
|
|
|
|
|
|
{$SCOPEDENUMS ON}
|
|
|
|
|
TMVCRESTParamType = (Header, Path, Query, FormURLEncoded, Cookie);
|
|
|
|
|
|
|
|
|
|
TMVCRESTParam = record
|
|
|
|
|
/// <summary>Parameter type</summary>
|
|
|
|
|
&Type: TMVCRESTParamType;
|
|
|
|
|
/// <summary>Parameter name</summary>
|
|
|
|
|
Name: string;
|
|
|
|
|
/// <summary>Parameter value</summary>
|
|
|
|
|
Value: string;
|
|
|
|
|
/// <summary>Initializes a TMVCRESTParam</summary>
|
|
|
|
|
constructor Create(const aType: TMVCRESTParamType; const aName, aValue: string);
|
|
|
|
|
end;
|
|
|
|
|
|
2020-09-10 02:40:14 +02:00
|
|
|
|
TMVCRESTClientHelper = class sealed
|
2020-09-12 03:24:38 +02:00
|
|
|
|
private
|
|
|
|
|
class function DecompressWithZlib(aContentStream, aOutStream: TStream; const aContentEncoding: string): Boolean;
|
|
|
|
|
class function DecompressWithIndyZlib(aContentStream, aOutStream: TStream; const aContentEncoding: string): Boolean;
|
2020-09-10 02:40:14 +02:00
|
|
|
|
public
|
|
|
|
|
class function URIEncode(const aURI: string): string;
|
2020-09-11 19:55:26 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Convert response content to byte array. If the response is compressed, it is decompressed in the process.
|
|
|
|
|
/// </summary>
|
2020-09-10 02:40:14 +02:00
|
|
|
|
class function GetResponseContentAsRawBytes(aContentStream: TStream; const aContentEncoding: string): TArray<Byte>;
|
2020-09-11 19:55:26 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get the response string, if it is of any type of text.
|
|
|
|
|
/// </summary>
|
2020-09-10 02:40:14 +02:00
|
|
|
|
class function GetResponseContentAsString(aContentRawBytes: TArray<Byte>; const aContentType: string): string;
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
EMVCRESTClientException = class(Exception);
|
|
|
|
|
|
2020-09-09 02:00:08 +02:00
|
|
|
|
TMVCRESTClientConsts = record
|
|
|
|
|
public const
|
|
|
|
|
DEFAULT_ACCEPT_ENCODING = 'gzip,deflate';
|
2020-09-18 01:06:24 +02:00
|
|
|
|
DEFAULT_ACCEPT = TMVCMediaType.APPLICATION_JSON + ', ' + TMVCMediaType.TEXT_PLAIN + ', ' + TMVCMediaType.TEXT_HTML;
|
|
|
|
|
DEFAULT_USER_AGENT = 'DelphiMVCFramework RESTClient/' + DMVCFRAMEWORK_VERSION;
|
2020-09-09 02:00:08 +02:00
|
|
|
|
DEFAULT_FILE_NAME = 'file';
|
|
|
|
|
AUTHORIZATION_HEADER = 'Authorization';
|
|
|
|
|
BASIC_AUTH_PREFIX = 'Basic ';
|
|
|
|
|
BEARER_AUTH_PREFIX = 'Bearer ';
|
2020-09-10 02:40:14 +02:00
|
|
|
|
SERVER_HEADER = 'server';
|
2020-09-23 01:26:13 +02:00
|
|
|
|
DEFAULT_MAX_REDIRECTS = 5;
|
2020-09-11 19:55:26 +02:00
|
|
|
|
REST_UNSAFE_CHARS: TURLEncoding.TUnsafeChars = [Ord('"'), Ord(''''), Ord(':'), Ord(';'), Ord('<'), Ord('='),
|
2020-09-09 02:00:08 +02:00
|
|
|
|
Ord('>'), Ord('@'), Ord('['), Ord(']'), Ord('^'), Ord('`'), Ord('{'), Ord('}'), Ord('|'), Ord('/'), Ord('\'),
|
|
|
|
|
Ord('?'), Ord('#'), Ord('&'), Ord('!'), Ord('$'), Ord('('), Ord(')'), Ord(','), Ord('~'), Ord(' '), Ord('*'),
|
|
|
|
|
Ord('+')];
|
2020-09-11 19:55:26 +02:00
|
|
|
|
PATH_UNSAFE_CHARS: TURLEncoding.TUnsafeChars = [Ord('"'), Ord('<'), Ord('>'), Ord('^'), Ord('`'), Ord('{'),
|
|
|
|
|
Ord('}'), Ord('|'), Ord('/'), Ord('\'), Ord('?'), Ord('#'), Ord('+'), Ord('.')];
|
2020-09-09 02:00:08 +02:00
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
implementation
|
|
|
|
|
|
2020-09-10 02:40:14 +02:00
|
|
|
|
uses
|
|
|
|
|
IdCompressorZLib,
|
2020-09-12 03:24:38 +02:00
|
|
|
|
System.ZLib,
|
2020-09-24 20:03:11 +02:00
|
|
|
|
System.Net.Mime,
|
|
|
|
|
System.Rtti;
|
2020-09-09 02:00:08 +02:00
|
|
|
|
|
|
|
|
|
{ TMVCRESTParam }
|
|
|
|
|
|
|
|
|
|
constructor TMVCRESTParam.Create(const aType: TMVCRESTParamType; const aName, aValue: string);
|
|
|
|
|
begin
|
|
|
|
|
&Type := aType;
|
|
|
|
|
Name := aName;
|
|
|
|
|
Value := aValue;
|
|
|
|
|
end;
|
|
|
|
|
|
2020-09-10 02:40:14 +02:00
|
|
|
|
{ TMVCRESTClientHelper }
|
|
|
|
|
|
2020-09-12 03:24:38 +02:00
|
|
|
|
class function TMVCRESTClientHelper.DecompressWithIndyZlib(aContentStream, aOutStream: TStream;
|
|
|
|
|
const aContentEncoding: string): Boolean;
|
2020-09-10 02:40:14 +02:00
|
|
|
|
var
|
|
|
|
|
lDecompressor: TIdCompressorZLib;
|
|
|
|
|
begin
|
|
|
|
|
try
|
2020-09-12 03:24:38 +02:00
|
|
|
|
aContentStream.Position := 0;
|
2020-09-10 02:40:14 +02:00
|
|
|
|
lDecompressor := TIdCompressorZLib.Create(nil);
|
|
|
|
|
try
|
2020-09-12 03:24:38 +02:00
|
|
|
|
if SameText(aContentEncoding, 'gzip') then
|
2020-09-10 02:40:14 +02:00
|
|
|
|
begin
|
2020-09-12 03:24:38 +02:00
|
|
|
|
lDecompressor.DecompressGZipStream(aContentStream, aOutStream);
|
2020-09-10 02:40:14 +02:00
|
|
|
|
end
|
2020-09-12 03:24:38 +02:00
|
|
|
|
else if SameText(aContentEncoding, 'deflate') then
|
2020-09-10 02:40:14 +02:00
|
|
|
|
begin
|
2020-09-12 03:24:38 +02:00
|
|
|
|
lDecompressor.DecompressHTTPDeflate(aContentStream, aOutStream);
|
2020-09-10 02:40:14 +02:00
|
|
|
|
end;
|
2020-09-12 03:24:38 +02:00
|
|
|
|
Result := True;
|
2020-09-10 02:40:14 +02:00
|
|
|
|
finally
|
|
|
|
|
FreeAndNil(lDecompressor);
|
|
|
|
|
end;
|
2020-09-12 03:24:38 +02:00
|
|
|
|
except
|
|
|
|
|
Result := False;
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
class function TMVCRESTClientHelper.DecompressWithZlib(aContentStream, aOutStream: TStream;
|
|
|
|
|
const aContentEncoding: string): Boolean;
|
|
|
|
|
var
|
|
|
|
|
lCompressionType: TMVCCompressionType;
|
|
|
|
|
lDecompressor: TZDecompressionStream;
|
|
|
|
|
begin
|
|
|
|
|
if aContentEncoding = 'deflate' then
|
|
|
|
|
begin
|
|
|
|
|
lCompressionType := TMVCCompressionType.ctDeflate;
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
begin
|
|
|
|
|
lCompressionType := TMVCCompressionType.ctGZIP;
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
aContentStream.Position := 0;
|
|
|
|
|
try
|
|
|
|
|
{$IF Defined(SeattleOrBetter)}
|
|
|
|
|
lDecompressor := TZDecompressionStream.Create(aContentStream,
|
|
|
|
|
MVC_COMPRESSION_ZLIB_WINDOW_BITS[lCompressionType], False);
|
|
|
|
|
{$ELSE}
|
|
|
|
|
lDecompressor := TZDecompressionStream.Create(aContentStream, MVC_COMPRESSION_ZLIB_WINDOW_BITS[lCompressionType]);
|
|
|
|
|
{$ENDIF}
|
|
|
|
|
try
|
|
|
|
|
aOutStream.CopyFrom(lDecompressor, 0);
|
|
|
|
|
Result := True;
|
|
|
|
|
finally
|
|
|
|
|
FreeAndNil(lDecompressor);
|
|
|
|
|
end;
|
|
|
|
|
except
|
|
|
|
|
Result := False;
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
class function TMVCRESTClientHelper.GetResponseContentAsRawBytes(aContentStream: TStream;
|
|
|
|
|
const aContentEncoding: string): TArray<Byte>;
|
|
|
|
|
var
|
|
|
|
|
lDecompressed: TMemoryStream;
|
|
|
|
|
begin
|
|
|
|
|
lDecompressed := TMemoryStream.Create;
|
|
|
|
|
try
|
|
|
|
|
if SameText(aContentEncoding, 'gzip') or SameText(aContentEncoding, 'deflate') then
|
|
|
|
|
begin
|
|
|
|
|
/// Certain types of deflate compression cannot be decompressed by the standard Zlib,
|
|
|
|
|
/// but are decompressed by Indy's Zlib.
|
|
|
|
|
/// Examples:
|
|
|
|
|
/// The deflate compression of the DMVC server is not decompressed by the Indy Zlib decompressor,
|
|
|
|
|
/// only by the standard Zlib.
|
|
|
|
|
/// The deflate compression of the server of the Embarcadero website (https://www.embarcadero.com/)
|
|
|
|
|
/// is only decompressed with the Indy Zlib decompressor.
|
|
|
|
|
/// Note: I think we can improve this later
|
|
|
|
|
|
|
|
|
|
if not (DecompressWithZlib(aContentStream, lDecompressed, aContentEncoding) or
|
|
|
|
|
DecompressWithIndyZlib(aContentStream, lDecompressed, aContentEncoding)) then
|
|
|
|
|
raise EMVCRESTClientException.Create('Could not decompress response content');
|
|
|
|
|
end
|
2020-10-20 00:44:28 +02:00
|
|
|
|
else if aContentEncoding.IsEmpty or SameText(aContentEncoding, 'identity') then // No encoding
|
2020-09-12 03:24:38 +02:00
|
|
|
|
begin
|
|
|
|
|
lDecompressed.CopyFrom(aContentStream, 0);
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
begin
|
|
|
|
|
raise EMVCRESTClientException.CreateFmt('Content-Encoding not supported [%s]', [aContentEncoding]);
|
|
|
|
|
end;
|
2020-09-10 02:40:14 +02:00
|
|
|
|
|
|
|
|
|
SetLength(Result, lDecompressed.Size);
|
|
|
|
|
lDecompressed.Position := 0;
|
|
|
|
|
lDecompressed.Read(Result, lDecompressed.Size);
|
|
|
|
|
finally
|
|
|
|
|
FreeAndNil(lDecompressed);
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
class function TMVCRESTClientHelper.GetResponseContentAsString(aContentRawBytes: TArray<Byte>;
|
|
|
|
|
const aContentType: string): string;
|
|
|
|
|
var
|
|
|
|
|
lContentIsString: Boolean;
|
|
|
|
|
lEncoding: TEncoding;
|
|
|
|
|
lContentType: string;
|
|
|
|
|
lCharset: string;
|
2020-09-23 01:26:13 +02:00
|
|
|
|
{$IF defined(RIOORBETTER)}
|
2020-09-10 02:40:14 +02:00
|
|
|
|
lExt: string;
|
|
|
|
|
lMimeKind: TMimeTypes.TKind;
|
2020-09-23 01:26:13 +02:00
|
|
|
|
{$ENDIF}
|
2020-09-10 02:40:14 +02:00
|
|
|
|
lReader: TStringStream;
|
|
|
|
|
begin
|
|
|
|
|
Result := '';
|
|
|
|
|
lContentIsString := False;
|
|
|
|
|
SplitContentMediaTypeAndCharset(aContentType, lContentType, lCharset);
|
|
|
|
|
|
|
|
|
|
if not lCharset.IsEmpty then
|
|
|
|
|
begin
|
|
|
|
|
lContentIsString := True
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
begin
|
2020-09-23 01:26:13 +02:00
|
|
|
|
{$IF defined(RIOORBETTER)}
|
2020-09-10 02:40:14 +02:00
|
|
|
|
TMimeTypes.Default.GetTypeInfo(lContentType.ToLower, lExt, lMimeKind);
|
|
|
|
|
if lMimeKind = TMimeTypes.TKind.Text then
|
|
|
|
|
lContentIsString := True;
|
2020-09-23 01:26:13 +02:00
|
|
|
|
{$ELSE}
|
|
|
|
|
if not (lContentType.StartsWith('image', True) or
|
|
|
|
|
lContentType.StartsWith('video', True) or
|
|
|
|
|
lContentType.StartsWith('audio', True) or
|
|
|
|
|
lContentType.ToLower.Equals('application/octet-stream') or
|
|
|
|
|
lContentType.ToLower.Equals('application/pdf')) then
|
|
|
|
|
lContentIsString := True;
|
|
|
|
|
{$ENDIF}
|
2020-09-10 02:40:14 +02:00
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
if lContentIsString then
|
|
|
|
|
begin
|
|
|
|
|
if lCharset.isEmpty then
|
|
|
|
|
begin
|
2020-09-11 19:55:26 +02:00
|
|
|
|
lCharset := TMVCCharSet.UTF_8;
|
2020-09-10 02:40:14 +02:00
|
|
|
|
end;
|
|
|
|
|
lEncoding := TEncoding.GetEncoding(lCharset);
|
|
|
|
|
|
|
|
|
|
lReader := TStringStream.Create('', lEncoding);
|
|
|
|
|
try
|
2020-09-11 19:55:26 +02:00
|
|
|
|
lReader.Write(aContentRawBytes, Length(aContentRawBytes));
|
2020-09-10 02:40:14 +02:00
|
|
|
|
Result := lReader.DataString;
|
|
|
|
|
finally
|
|
|
|
|
FreeAndNil(lReader);
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
class function TMVCRESTClientHelper.URIEncode(const aURI: string): string;
|
|
|
|
|
begin
|
|
|
|
|
Result := TNetEncoding.URL.Encode(aURI, TMVCRESTClientConsts.REST_UNSAFE_CHARS,
|
|
|
|
|
[TURLEncoding.TEncodeOption.EncodePercent]);
|
|
|
|
|
end;
|
|
|
|
|
|
2020-09-09 02:00:08 +02:00
|
|
|
|
end.
|