-
Notifications
You must be signed in to change notification settings - Fork 27
/
Pkg.Json.SerializableObject.pas
258 lines (213 loc) · 7.37 KB
/
Pkg.Json.SerializableObject.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
unit Pkg.Json.SerializableObject;
interface
uses Generics.Collections, Rest.Json, IdUri, IdHttp,
IdSSLOpenSSL, System.Json, SysUtils, Classes, Pkg.Json.DTO;
type
/// Represents a serializable object with HTTP/REST capabilities (via Indy)
/// HTTPS connections require OpenSSL binaries!
/// Use the "AOnBeforeRequest" event to setup HTTP client's parameters like timeout, encoding etc.
TUGitHubSerializableObject = class
protected
// As per http://www.restapitutorial.com/lessons/httpmethods.html
class procedure EnsureHttpResponseCode(AHttpResponseCode: integer; aUrl: string; AValidValues: array of integer);
class procedure EnsureHttpContentType(AHttp: TIdHttp);
class procedure DoOnError<T: TJsonDTO>(var aObject: T; aErrorProc: TProc<string>; aMessage: string);
public
// Generic Web Request method
class function WebRequest(aUrl: string; AOnRequest: TProc<TIdHttp>): integer;
// Returns an instance of T from a JSON string via GET request. AArrayProperty is intended for internal use only!
// HttpGet is reintroduced in descendant classes to return concrete instance
class function HttpGet<T: TJsonDTO, constructor>(aUrl: string; AOnBeforeRequest: TProc<TIdHttp> = nil): T;
class function RestRequest<T: TJsonDTO, constructor>(aUrl: string; aErrorProc: TProc<string> = nil): T;
// Performs POST request, sends the current object as JSON string and returns server's response as text.
function HttpPost(aUrl: string; AOnBeforeRequest: TProc<TIdHttp> = nil): string;
// Performs PUT request, sends the current object as JSON string and returns server's response as text.
function HttpPut(aUrl: string; AOnBeforeRequest: TProc<TIdHttp> = nil): string;
// Performs DELETE request and returns server's response as text. This method exists just REST compliance.
function HttpDelete(aUrl: string; AOnBeforeRequest: TProc<TIdHttp> = nil): string;
end;
implementation
uses
Rest.Client;
{ TUGitHubSerializableObject }
class procedure TUGitHubSerializableObject.DoOnError<T>(var aObject: T; aErrorProc: TProc<string>; aMessage: string);
begin
FreeAndNil(aObject);
if Assigned(aErrorProc) then
aErrorProc(aMessage);
end;
class procedure TUGitHubSerializableObject.EnsureHttpContentType(AHttp: TIdHttp);
begin
if AHttp.Response.ContentType <> 'application/json' then
raise Exception.CreateFmt('Invalid content type %s!', [AHttp.Response.ContentType]);
end;
class procedure TUGitHubSerializableObject.EnsureHttpResponseCode(AHttpResponseCode: integer; aUrl: string; AValidValues: array of integer);
var
LValue: integer;
begin
for LValue in AValidValues do
if LValue = AHttpResponseCode then
exit;
raise Exception.CreateFmt('The request to %s has failed with code %d', [aUrl, AHttpResponseCode]);
end;
function TUGitHubSerializableObject.HttpDelete(aUrl: string; AOnBeforeRequest: TProc<TIdHttp>): string;
var
LResult: string;
begin
WebRequest(aUrl,
procedure(LHttp: TIdHttp)
begin
// Allow HTTP client pre-configuration
if Assigned(AOnBeforeRequest) then
AOnBeforeRequest(LHttp);
LResult := LHttp.Delete(aUrl);
EnsureHttpResponseCode(LHttp.ResponseCode, aUrl, [200, 204]);
end);
Result := LResult;
end;
class function TUGitHubSerializableObject.RestRequest<T>(aUrl: string; aErrorProc: TProc<string> = nil): T;
var
LRestClient: TRESTClient;
LRestRequest: TRESTRequest;
LRestResponse: TRESTResponse;
LResult: T;
begin
LResult := nil;
LRestClient := TRESTClient.Create('');
LRestResponse := TRESTResponse.Create(nil);
LRestRequest := TRESTRequest.Create(nil);
try
LRestClient.BaseURL := aUrl;
LRestRequest.Client := LRestClient;
LRestRequest.Response := LRestResponse;
LRestRequest.Timeout := 10000;
try
LRestRequest.Execute;
if LRestResponse.StatusCode = 200 then
begin
try
LResult := T.Create;
LResult.AsJson := LRestResponse.Content;
except
on e: Exception do
DoOnError(LResult, aErrorProc, e.message);
end;
end
else
DoOnError(LResult, aErrorProc, LRestResponse.Content);
except
on e: Exception do
DoOnError(LResult, aErrorProc, e.message);
end;
finally
LRestResponse.Free;
LRestRequest.Free;
LRestClient.Free;
end;
Result := LResult;
end;
class function TUGitHubSerializableObject.HttpGet<T>(aUrl: string; AOnBeforeRequest: TProc<TIdHttp>): T;
var
LResult: T;
begin
WebRequest(aUrl,
procedure(LHttp: TIdHttp)
var
Respons: string;
begin
try // Allow HTTP client pre-configuration
if Assigned(AOnBeforeRequest) then
AOnBeforeRequest(LHttp);
Respons := LHttp.Get(aUrl);
EnsureHttpResponseCode(LHttp.ResponseCode, aUrl, [200, 304]);
EnsureHttpContentType(LHttp);
LResult := T.Create;
LResult.AsJson := Respons;
except
FreeAndNil(LResult);
end;
end);
Result := LResult;
end;
function TUGitHubSerializableObject.HttpPost(aUrl: string; AOnBeforeRequest: TProc<TIdHttp>): string;
var
LResult: string;
begin
WebRequest(aUrl,
procedure(LHttp: TIdHttp)
var
LStringStream: TStringStream;
begin
// Allow HTTP client pre-configuration
if Assigned(AOnBeforeRequest) then
AOnBeforeRequest(LHttp);
LResult := TJson.ObjectToJsonString(Self);
LStringStream := TStringStream.Create(LResult, TEncoding.GetEncoding(LHttp.Request.ContentEncoding));
try
LResult := LHttp.Post(aUrl, LStringStream);
EnsureHttpResponseCode(LHttp.ResponseCode, aUrl, [200, 201, 202, 204]);
EnsureHttpContentType(LHttp);
finally
LStringStream.Free;
end;
end);
Result := LResult;
end;
function TUGitHubSerializableObject.HttpPut(aUrl: string; AOnBeforeRequest: TProc<TIdHttp>): string;
var
LResult: string;
begin
WebRequest(aUrl,
procedure(LHttp: TIdHttp)
var
LStringStream: TStringStream;
begin
// Allow HTTP client pre-configuration
if Assigned(AOnBeforeRequest) then
AOnBeforeRequest(LHttp);
LResult := TJson.ObjectToJsonString(Self);
LStringStream := TStringStream.Create(LResult, TEncoding.GetEncoding(LHttp.Request.ContentEncoding));
try
LResult := LHttp.Put(aUrl, LStringStream);
EnsureHttpResponseCode(LHttp.ResponseCode, aUrl, [200, 204]);
EnsureHttpContentType(LHttp);
finally
LStringStream.Free;
end;
end);
Result := LResult;
end;
class function TUGitHubSerializableObject.WebRequest(aUrl: string; AOnRequest: TProc<TIdHttp>): integer;
var
LUri: TIdUri;
LHttp: TIdHttp;
LSslIoHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
LUri := TIdUri.Create(aUrl);
try
LHttp := TIdHttp.Create;
try
LHttp.HandleRedirects := true;
// Default encoding
LHttp.Request.ContentEncoding := 'utf-8';
// Specify Content-Type header
LHttp.Request.ContentType := 'application/json';
// Replace default IOHandler with TIdSSLIOHandlerSocketOpenSSL if the connection is SSL based
if LUri.Protocol.ToLower = 'https' then
begin
LSslIoHandler := TIdSSLIOHandlerSocketOpenSSL.Create(LHttp);
LHttp.IOHandler := LSslIoHandler;
end;
try
AOnRequest(LHttp);
finally
Result := LHttp.ResponseCode;
end;
finally
LHttp.Free;
end;
finally
LUri.Free;
end;
end;
end.