delphimvcframework/sources/MVCFramework.RESTClient.Commons.pas

274 lines
8.6 KiB
ObjectPascal
Raw Normal View History

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
System.NetEncoding,
System.SysUtils,
System.Classes,
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;
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;
public
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>;
/// <summary>
/// Get the response string, if it is of any type of text.
/// </summary>
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';
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 ';
SERVER_HEADER = 'server';
2020-09-23 01:26:13 +02:00
DEFAULT_MAX_REDIRECTS = 5;
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('+')];
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
uses
IdCompressorZLib,
2020-09-12 03:24:38 +02:00
System.ZLib,
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;
{ TMVCRESTClientHelper }
2020-09-12 03:24:38 +02:00
class function TMVCRESTClientHelper.DecompressWithIndyZlib(aContentStream, aOutStream: TStream;
const aContentEncoding: string): Boolean;
var
lDecompressor: TIdCompressorZLib;
begin
try
2020-09-12 03:24:38 +02:00
aContentStream.Position := 0;
lDecompressor := TIdCompressorZLib.Create(nil);
try
2020-09-12 03:24:38 +02:00
if SameText(aContentEncoding, 'gzip') then
begin
2020-09-12 03:24:38 +02:00
lDecompressor.DecompressGZipStream(aContentStream, aOutStream);
end
2020-09-12 03:24:38 +02:00
else if SameText(aContentEncoding, 'deflate') then
begin
2020-09-12 03:24:38 +02:00
lDecompressor.DecompressHTTPDeflate(aContentStream, aOutStream);
end;
2020-09-12 03:24:38 +02:00
Result := True;
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
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;
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)}
lExt: string;
lMimeKind: TMimeTypes.TKind;
2020-09-23 01:26:13 +02:00
{$ENDIF}
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)}
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}
end;
if lContentIsString then
begin
if lCharset.isEmpty then
begin
lCharset := TMVCCharSet.UTF_8;
end;
lEncoding := TEncoding.GetEncoding(lCharset);
lReader := TStringStream.Create('', lEncoding);
try
lReader.Write(aContentRawBytes, Length(aContentRawBytes));
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.