blob: 2d18ca1683a41f5d87b974740c6dd7984e72bcc5 [file] [log] [blame]
Jens Geyer02230912019-04-03 01:12:51 +02001(*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 *)
19unit Thrift.Transport.WinHTTP;
20
21{$I Thrift.Defines.inc}
22{$SCOPEDENUMS ON}
23
24interface
25
26uses
27 Classes,
28 SysUtils,
29 Math,
30 Generics.Collections,
31 Thrift.Collections,
Jens Geyera019cda2019-11-09 23:24:52 +010032 Thrift.Configuration,
Jens Geyer02230912019-04-03 01:12:51 +020033 Thrift.Transport,
34 Thrift.Exception,
35 Thrift.Utils,
36 Thrift.WinHTTP,
37 Thrift.Stream;
38
39type
Jens Geyera019cda2019-11-09 23:24:52 +010040 TWinHTTPClientImpl = class( TEndpointTransportBase, IHTTPClient)
Jens Geyerfad7fd32019-11-09 23:24:52 +010041 strict private
Jens Geyer02230912019-04-03 01:12:51 +020042 FUri : string;
43 FInputStream : IThriftStream;
Jens Geyerf726ae32021-06-04 11:17:26 +020044 FOutputMemoryStream : TThriftMemoryStream;
Jens Geyer02230912019-04-03 01:12:51 +020045 FDnsResolveTimeout : Integer;
46 FConnectionTimeout : Integer;
47 FSendTimeout : Integer;
48 FReadTimeout : Integer;
49 FCustomHeaders : IThriftDictionary<string,string>;
Jens Geyer47f63172019-06-06 22:42:58 +020050 FSecureProtocols : TSecureProtocols;
Jens Geyer02230912019-04-03 01:12:51 +020051
52 function CreateRequest: IWinHTTPRequest;
Jens Geyer47f63172019-06-06 22:42:58 +020053 function SecureProtocolsAsWinHTTPFlags : Cardinal;
Jens Geyer72c81112025-03-10 21:46:20 +010054 class procedure EnsureSuccessHttpStatus( const aRequest : IWinHTTPRequest);
Jens Geyer02230912019-04-03 01:12:51 +020055
Jens Geyerfad7fd32019-11-09 23:24:52 +010056 strict private
Jens Geyer19fdca82019-06-12 22:09:05 +020057 type
58 TErrorInfo = ( SplitUrl, WinHTTPSession, WinHTTPConnection, WinHTTPRequest, RequestSetup, AutoProxy );
59
Jens Geyer02230912019-04-03 01:12:51 +020060 THTTPResponseStream = class( TThriftStreamImpl)
Jens Geyerfad7fd32019-11-09 23:24:52 +010061 strict private
Jens Geyer02230912019-04-03 01:12:51 +020062 FRequest : IWinHTTPRequest;
Jens Geyerfad7fd32019-11-09 23:24:52 +010063 strict protected
Jens Geyer02230912019-04-03 01:12:51 +020064 procedure Write( const pBuf : Pointer; offset: Integer; count: Integer); override;
65 function Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; override;
66 procedure Open; override;
67 procedure Close; override;
68 procedure Flush; override;
69 function IsOpen: Boolean; override;
70 function ToArray: TBytes; override;
71 public
Jens Geyera019cda2019-11-09 23:24:52 +010072 constructor Create( const aRequest : IWinHTTPRequest);
Jens Geyer02230912019-04-03 01:12:51 +020073 destructor Destroy; override;
74 end;
75
Jens Geyerfad7fd32019-11-09 23:24:52 +010076 strict protected
Jens Geyer02230912019-04-03 01:12:51 +020077 function GetIsOpen: Boolean; override;
78 procedure Open(); override;
79 procedure Close(); override;
80 function Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; override;
81 procedure Write( const pBuf : Pointer; off, len : Integer); override;
82 procedure Flush; override;
83
84 procedure SetDnsResolveTimeout(const Value: Integer);
85 function GetDnsResolveTimeout: Integer;
86 procedure SetConnectionTimeout(const Value: Integer);
87 function GetConnectionTimeout: Integer;
88 procedure SetSendTimeout(const Value: Integer);
89 function GetSendTimeout: Integer;
90 procedure SetReadTimeout(const Value: Integer);
91 function GetReadTimeout: Integer;
Jens Geyer47f63172019-06-06 22:42:58 +020092 function GetSecureProtocols : TSecureProtocols;
93 procedure SetSecureProtocols( const value : TSecureProtocols);
Jens Geyer02230912019-04-03 01:12:51 +020094
95 function GetCustomHeaders: IThriftDictionary<string,string>;
96 procedure SendRequest;
Jens Geyer47f63172019-06-06 22:42:58 +020097
Jens Geyer02230912019-04-03 01:12:51 +020098 property DnsResolveTimeout: Integer read GetDnsResolveTimeout write SetDnsResolveTimeout;
99 property ConnectionTimeout: Integer read GetConnectionTimeout write SetConnectionTimeout;
100 property SendTimeout: Integer read GetSendTimeout write SetSendTimeout;
101 property ReadTimeout: Integer read GetReadTimeout write SetReadTimeout;
102 property CustomHeaders: IThriftDictionary<string,string> read GetCustomHeaders;
103 public
Jens Geyera019cda2019-11-09 23:24:52 +0100104 constructor Create( const aUri: string; const aConfig : IThriftConfiguration = nil);
Jens Geyer02230912019-04-03 01:12:51 +0200105 destructor Destroy; override;
106 end;
107
108implementation
109
Jens Geyera019cda2019-11-09 23:24:52 +0100110const
111 WINHTTP_CONNECTION_TIMEOUT = 60 * 1000;
112 WINHTTP_SENDRECV_TIMEOUT = 30 * 1000;
113
Jens Geyer02230912019-04-03 01:12:51 +0200114
115{ TWinHTTPClientImpl }
116
Jens Geyera019cda2019-11-09 23:24:52 +0100117constructor TWinHTTPClientImpl.Create( const aUri: string; const aConfig : IThriftConfiguration);
Jens Geyer02230912019-04-03 01:12:51 +0200118begin
Jens Geyera019cda2019-11-09 23:24:52 +0100119 inherited Create( aConfig);
Jens Geyer02230912019-04-03 01:12:51 +0200120 FUri := AUri;
121
122 // defaults according to MSDN
123 FDnsResolveTimeout := 0; // no timeout
Jens Geyera019cda2019-11-09 23:24:52 +0100124 FConnectionTimeout := WINHTTP_CONNECTION_TIMEOUT;
125 FSendTimeout := WINHTTP_SENDRECV_TIMEOUT;
126 FReadTimeout := WINHTTP_SENDRECV_TIMEOUT;
Jens Geyer02230912019-04-03 01:12:51 +0200127
Jens Geyer47f63172019-06-06 22:42:58 +0200128 FSecureProtocols := DEFAULT_THRIFT_SECUREPROTOCOLS;
129
Jens Geyer02230912019-04-03 01:12:51 +0200130 FCustomHeaders := TThriftDictionaryImpl<string,string>.Create;
Jens Geyerf726ae32021-06-04 11:17:26 +0200131 FOutputMemoryStream := TThriftMemoryStream.Create;
Jens Geyer02230912019-04-03 01:12:51 +0200132end;
133
134destructor TWinHTTPClientImpl.Destroy;
135begin
136 Close;
137 FreeAndNil( FOutputMemoryStream);
138 inherited;
139end;
140
141function TWinHTTPClientImpl.CreateRequest: IWinHTTPRequest;
142var
Jens Geyer19fdca82019-06-12 22:09:05 +0200143 pair : TPair<string,string>;
Jens Geyer02230912019-04-03 01:12:51 +0200144 session : IWinHTTPSession;
145 connect : IWinHTTPConnection;
146 url : IWinHTTPUrl;
147 sPath : string;
Jens Geyer19fdca82019-06-12 22:09:05 +0200148 info : TErrorInfo;
Jens Geyer02230912019-04-03 01:12:51 +0200149begin
Jens Geyer19fdca82019-06-12 22:09:05 +0200150 info := TErrorInfo.SplitUrl;
151 try
152 url := TWinHTTPUrlImpl.Create( FUri);
Jens Geyer02230912019-04-03 01:12:51 +0200153
Jens Geyer19fdca82019-06-12 22:09:05 +0200154 info := TErrorInfo.WinHTTPSession;
Jens Geyeraad75832022-06-01 22:06:29 +0200155 session := TWinHTTPSessionImpl.Create('ApacheThriftDelphi/WinHTTP');
Jens Geyer19fdca82019-06-12 22:09:05 +0200156 session.EnableSecureProtocols( SecureProtocolsAsWinHTTPFlags);
Jens Geyer47f63172019-06-06 22:42:58 +0200157
Jens Geyer19fdca82019-06-12 22:09:05 +0200158 info := TErrorInfo.WinHTTPConnection;
159 connect := session.Connect( url.HostName, url.Port);
Jens Geyer02230912019-04-03 01:12:51 +0200160
Jens Geyer19fdca82019-06-12 22:09:05 +0200161 info := TErrorInfo.WinHTTPRequest;
162 sPath := url.UrlPath + url.ExtraInfo;
163 result := connect.OpenRequest( (url.Scheme = 'https'), 'POST', sPath, THRIFT_MIMETYPE);
Jens Geyer02230912019-04-03 01:12:51 +0200164
Jens Geyer19fdca82019-06-12 22:09:05 +0200165 // setting a timeout value to 0 (zero) means "no timeout" for that setting
166 info := TErrorInfo.RequestSetup;
167 result.SetTimeouts( DnsResolveTimeout, ConnectionTimeout, SendTimeout, ReadTimeout);
Jens Geyer02230912019-04-03 01:12:51 +0200168
Jens Geyer19fdca82019-06-12 22:09:05 +0200169 // headers
170 result.AddRequestHeader( 'Content-Type: '+THRIFT_MIMETYPE, WINHTTP_ADDREQ_FLAG_ADD);
171 for pair in FCustomHeaders do begin
172 Result.AddRequestHeader( pair.Key +': '+ pair.Value, WINHTTP_ADDREQ_FLAG_ADD);
173 end;
174
Jens Geyer19505c32019-06-22 00:59:54 +0200175 // enable automatic gzip,deflate decompression
176 result.EnableAutomaticContentDecompression(TRUE);
177
Jens Geyer19fdca82019-06-12 22:09:05 +0200178 // AutoProxy support
179 info := TErrorInfo.AutoProxy;
180 result.TryAutoProxy( FUri);
181 except
182 on e:TException do raise;
183 on e:Exception do raise TTransportExceptionUnknown.Create( e.Message+' (at '+EnumUtils<TErrorInfo>.ToString(Ord(info))+')');
Jens Geyer02230912019-04-03 01:12:51 +0200184 end;
185end;
186
Jens Geyer47f63172019-06-06 22:42:58 +0200187
188function TWinHTTPClientImpl.SecureProtocolsAsWinHTTPFlags : Cardinal;
189const
190 PROTOCOL_MAPPING : array[TSecureProtocol] of Cardinal = (
191 WINHTTP_FLAG_SECURE_PROTOCOL_SSL2,
192 WINHTTP_FLAG_SECURE_PROTOCOL_SSL3,
193 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1,
194 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1,
195 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2
196 );
197var
198 prot : TSecureProtocol;
199 protos : TSecureProtocols;
200begin
201 result := 0;
202 protos := GetSecureProtocols;
203 for prot := Low(TSecureProtocol) to High(TSecureProtocol) do begin
204 if prot in protos
205 then result := result or PROTOCOL_MAPPING[prot];
206 end;
207end;
208
209
Jens Geyer02230912019-04-03 01:12:51 +0200210function TWinHTTPClientImpl.GetDnsResolveTimeout: Integer;
211begin
212 Result := FDnsResolveTimeout;
213end;
214
215procedure TWinHTTPClientImpl.SetDnsResolveTimeout(const Value: Integer);
216begin
217 FDnsResolveTimeout := Value;
218end;
219
220function TWinHTTPClientImpl.GetConnectionTimeout: Integer;
221begin
222 Result := FConnectionTimeout;
223end;
224
225procedure TWinHTTPClientImpl.SetConnectionTimeout(const Value: Integer);
226begin
227 FConnectionTimeout := Value;
228end;
229
230function TWinHTTPClientImpl.GetSendTimeout: Integer;
231begin
232 Result := FSendTimeout;
233end;
234
235procedure TWinHTTPClientImpl.SetSendTimeout(const Value: Integer);
236begin
237 FSendTimeout := Value;
238end;
239
240function TWinHTTPClientImpl.GetReadTimeout: Integer;
241begin
242 Result := FReadTimeout;
243end;
244
245procedure TWinHTTPClientImpl.SetReadTimeout(const Value: Integer);
246begin
247 FReadTimeout := Value;
248end;
249
Jens Geyer47f63172019-06-06 22:42:58 +0200250function TWinHTTPClientImpl.GetSecureProtocols : TSecureProtocols;
251begin
252 Result := FSecureProtocols;
253end;
254
255procedure TWinHTTPClientImpl.SetSecureProtocols( const value : TSecureProtocols);
256begin
257 FSecureProtocols := Value;
258end;
259
Jens Geyer02230912019-04-03 01:12:51 +0200260function TWinHTTPClientImpl.GetCustomHeaders: IThriftDictionary<string,string>;
261begin
262 Result := FCustomHeaders;
263end;
264
265function TWinHTTPClientImpl.GetIsOpen: Boolean;
266begin
Jens Geyer528a0f02019-11-18 20:17:03 +0100267 Result := Assigned( FOutputMemoryStream);
Jens Geyer02230912019-04-03 01:12:51 +0200268end;
269
270procedure TWinHTTPClientImpl.Open;
271begin
272 FreeAndNil( FOutputMemoryStream);
Jens Geyerf726ae32021-06-04 11:17:26 +0200273 FOutputMemoryStream := TThriftMemoryStream.Create;
Jens Geyer02230912019-04-03 01:12:51 +0200274end;
275
276procedure TWinHTTPClientImpl.Close;
277begin
278 FInputStream := nil;
279 FreeAndNil( FOutputMemoryStream);
280end;
281
282procedure TWinHTTPClientImpl.Flush;
283begin
284 try
285 SendRequest;
286 finally
287 FreeAndNil( FOutputMemoryStream);
Jens Geyerf726ae32021-06-04 11:17:26 +0200288 FOutputMemoryStream := TThriftMemoryStream.Create;
Jens Geyer02230912019-04-03 01:12:51 +0200289 ASSERT( FOutputMemoryStream <> nil);
290 end;
291end;
292
293function TWinHTTPClientImpl.Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer;
294begin
295 if FInputStream = nil then begin
296 raise TTransportExceptionNotOpen.Create('No request has been sent');
297 end;
298
299 try
Jens Geyer41f47af2019-11-09 23:24:52 +0100300 Result := FInputStream.Read( pBuf, buflen, off, len);
Jens Geyera019cda2019-11-09 23:24:52 +0100301 CountConsumedMessageBytes( result);
Jens Geyer02230912019-04-03 01:12:51 +0200302 except
303 on E: Exception
304 do raise TTransportExceptionUnknown.Create(E.Message);
305 end;
306end;
307
308procedure TWinHTTPClientImpl.SendRequest;
309var
Jens Geyer433a6492019-06-19 23:14:08 +0200310 http : IWinHTTPRequest;
Jens Geyer02230912019-04-03 01:12:51 +0200311 pData : PByte;
Jens Geyer433a6492019-06-19 23:14:08 +0200312 len : Integer;
Jens Geyerb0123182020-02-12 12:16:19 +0100313 error, dwSize : Cardinal;
Jens Geyer433a6492019-06-19 23:14:08 +0200314 sMsg : string;
Jens Geyer02230912019-04-03 01:12:51 +0200315begin
316 http := CreateRequest;
317
318 pData := FOutputMemoryStream.Memory;
319 len := FOutputMemoryStream.Size;
320
321 // send all data immediately, since we have it in memory
Jens Geyer433a6492019-06-19 23:14:08 +0200322 if not http.SendRequest( pData, len, 0) then begin
323 error := Cardinal( GetLastError);
324 sMsg := 'WinHTTP send error '+IntToStr(Int64(error))+' '+WinHttpSysErrorMessage(error);
325 raise TTransportExceptionUnknown.Create(sMsg);
326 end;
Jens Geyer02230912019-04-03 01:12:51 +0200327
328 // end request and start receiving
Jens Geyer433a6492019-06-19 23:14:08 +0200329 if not http.FlushAndReceiveResponse then begin
330 error := Cardinal( GetLastError);
331 sMsg := 'WinHTTP recv error '+IntToStr(Int64(error))+' '+WinHttpSysErrorMessage(error);
332 if error = ERROR_WINHTTP_TIMEOUT
333 then raise TTransportExceptionTimedOut.Create( sMsg)
334 else raise TTransportExceptionInterrupted.Create( sMsg);
335 end;
Jens Geyer02230912019-04-03 01:12:51 +0200336
Jens Geyer6762cad2020-10-30 17:15:18 +0100337 // we're about to receive a new message, so reset everyting
Jens Geyer5a781c22025-02-04 23:35:55 +0100338 ResetMessageSizeAndConsumedBytes(-1);
Jens Geyer72c81112025-03-10 21:46:20 +0100339 EnsureSuccessHttpStatus(http); // throws if not
Jens Geyera019cda2019-11-09 23:24:52 +0100340 FInputStream := THTTPResponseStream.Create( http);
Jens Geyerb0123182020-02-12 12:16:19 +0100341 if http.QueryTotalResponseSize( dwSize) // FALSE indicates "no info available"
342 then UpdateKnownMessageSize( dwSize);
Jens Geyer02230912019-04-03 01:12:51 +0200343end;
344
345procedure TWinHTTPClientImpl.Write( const pBuf : Pointer; off, len : Integer);
346var pTmp : PByte;
347begin
348 pTmp := pBuf;
349 Inc(pTmp,off);
350 FOutputMemoryStream.Write( pTmp^, len);
351end;
352
353
Jens Geyer72c81112025-03-10 21:46:20 +0100354class procedure TWinHTTPClientImpl.EnsureSuccessHttpStatus( const aRequest : IWinHTTPRequest);
355var dwStatus : Cardinal;
356 sText : string;
357begin
358 if (aRequest <> nil)
359 then aRequest.QueryHttpStatus( dwStatus, sText)
360 else raise TTransportExceptionNotOpen.Create('Invalid HTTP request data');
361
362 if (200 > dwStatus) or (dwStatus > 299)
363 then raise TTransportExceptionEndOfFile.Create('HTTP '+UIntToStr(dwStatus)+' '+sText);
364end;
365
Jens Geyer02230912019-04-03 01:12:51 +0200366{ TWinHTTPClientImpl.THTTPResponseStream }
367
Jens Geyera019cda2019-11-09 23:24:52 +0100368constructor TWinHTTPClientImpl.THTTPResponseStream.Create( const aRequest : IWinHTTPRequest);
Jens Geyer02230912019-04-03 01:12:51 +0200369begin
370 inherited Create;
371 FRequest := aRequest;
372end;
373
374destructor TWinHTTPClientImpl.THTTPResponseStream.Destroy;
375begin
376 try
377 Close;
378 finally
379 inherited Destroy;
380 end;
381end;
382
383procedure TWinHTTPClientImpl.THTTPResponseStream.Close;
384begin
385 FRequest := nil;
386end;
387
388procedure TWinHTTPClientImpl.THTTPResponseStream.Flush;
389begin
390 raise ENotImplemented(ClassName+'.Flush');
391end;
392
393function TWinHTTPClientImpl.THTTPResponseStream.IsOpen: Boolean;
394begin
395 Result := FRequest <> nil;
396end;
397
398procedure TWinHTTPClientImpl.THTTPResponseStream.Open;
399begin
400 // nothing to do
401end;
402
403procedure TWinHTTPClientImpl.THTTPResponseStream.Write(const pBuf : Pointer; offset, count: Integer);
404begin
405 inherited; // check pointers
406 raise ENotImplemented(ClassName+'.Write');
407end;
408
409function TWinHTTPClientImpl.THTTPResponseStream.Read(const pBuf : Pointer; const buflen : Integer; offset, count: Integer): Integer;
410var pTmp : PByte;
411begin
412 inherited; // check pointers
413
414 if count >= buflen-offset
415 then count := buflen-offset;
416
417 if count > 0 then begin
418 pTmp := pBuf;
419 Inc( pTmp, offset);
420 Result := FRequest.ReadData( pTmp, count);
421 ASSERT( Result >= 0);
422 end
423 else Result := 0;
Jens Geyer02230912019-04-03 01:12:51 +0200424end;
425
426function TWinHTTPClientImpl.THTTPResponseStream.ToArray: TBytes;
427begin
428 raise ENotImplemented(ClassName+'.ToArray');
429end;
430
431
432end.