blob: 5a6f2134415958cffa264149ac8fd4a5dae9969c [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,
fcprete28113f42025-06-10 02:54:38 +0200195 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2,
196 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3
Jens Geyer47f63172019-06-06 22:42:58 +0200197 );
198var
199 prot : TSecureProtocol;
200 protos : TSecureProtocols;
201begin
202 result := 0;
203 protos := GetSecureProtocols;
204 for prot := Low(TSecureProtocol) to High(TSecureProtocol) do begin
205 if prot in protos
206 then result := result or PROTOCOL_MAPPING[prot];
207 end;
208end;
209
210
Jens Geyer02230912019-04-03 01:12:51 +0200211function TWinHTTPClientImpl.GetDnsResolveTimeout: Integer;
212begin
213 Result := FDnsResolveTimeout;
214end;
215
216procedure TWinHTTPClientImpl.SetDnsResolveTimeout(const Value: Integer);
217begin
218 FDnsResolveTimeout := Value;
219end;
220
221function TWinHTTPClientImpl.GetConnectionTimeout: Integer;
222begin
223 Result := FConnectionTimeout;
224end;
225
226procedure TWinHTTPClientImpl.SetConnectionTimeout(const Value: Integer);
227begin
228 FConnectionTimeout := Value;
229end;
230
231function TWinHTTPClientImpl.GetSendTimeout: Integer;
232begin
233 Result := FSendTimeout;
234end;
235
236procedure TWinHTTPClientImpl.SetSendTimeout(const Value: Integer);
237begin
238 FSendTimeout := Value;
239end;
240
241function TWinHTTPClientImpl.GetReadTimeout: Integer;
242begin
243 Result := FReadTimeout;
244end;
245
246procedure TWinHTTPClientImpl.SetReadTimeout(const Value: Integer);
247begin
248 FReadTimeout := Value;
249end;
250
Jens Geyer47f63172019-06-06 22:42:58 +0200251function TWinHTTPClientImpl.GetSecureProtocols : TSecureProtocols;
252begin
253 Result := FSecureProtocols;
254end;
255
256procedure TWinHTTPClientImpl.SetSecureProtocols( const value : TSecureProtocols);
257begin
258 FSecureProtocols := Value;
259end;
260
Jens Geyer02230912019-04-03 01:12:51 +0200261function TWinHTTPClientImpl.GetCustomHeaders: IThriftDictionary<string,string>;
262begin
263 Result := FCustomHeaders;
264end;
265
266function TWinHTTPClientImpl.GetIsOpen: Boolean;
267begin
Jens Geyer528a0f02019-11-18 20:17:03 +0100268 Result := Assigned( FOutputMemoryStream);
Jens Geyer02230912019-04-03 01:12:51 +0200269end;
270
271procedure TWinHTTPClientImpl.Open;
272begin
273 FreeAndNil( FOutputMemoryStream);
Jens Geyerf726ae32021-06-04 11:17:26 +0200274 FOutputMemoryStream := TThriftMemoryStream.Create;
Jens Geyer02230912019-04-03 01:12:51 +0200275end;
276
277procedure TWinHTTPClientImpl.Close;
278begin
279 FInputStream := nil;
280 FreeAndNil( FOutputMemoryStream);
281end;
282
283procedure TWinHTTPClientImpl.Flush;
284begin
285 try
286 SendRequest;
287 finally
288 FreeAndNil( FOutputMemoryStream);
Jens Geyerf726ae32021-06-04 11:17:26 +0200289 FOutputMemoryStream := TThriftMemoryStream.Create;
Jens Geyer02230912019-04-03 01:12:51 +0200290 ASSERT( FOutputMemoryStream <> nil);
291 end;
292end;
293
294function TWinHTTPClientImpl.Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer;
295begin
296 if FInputStream = nil then begin
297 raise TTransportExceptionNotOpen.Create('No request has been sent');
298 end;
299
300 try
Jens Geyer41f47af2019-11-09 23:24:52 +0100301 Result := FInputStream.Read( pBuf, buflen, off, len);
Jens Geyera019cda2019-11-09 23:24:52 +0100302 CountConsumedMessageBytes( result);
Jens Geyer02230912019-04-03 01:12:51 +0200303 except
304 on E: Exception
305 do raise TTransportExceptionUnknown.Create(E.Message);
306 end;
307end;
308
309procedure TWinHTTPClientImpl.SendRequest;
310var
Jens Geyer433a6492019-06-19 23:14:08 +0200311 http : IWinHTTPRequest;
Jens Geyer02230912019-04-03 01:12:51 +0200312 pData : PByte;
Jens Geyer433a6492019-06-19 23:14:08 +0200313 len : Integer;
Jens Geyerb0123182020-02-12 12:16:19 +0100314 error, dwSize : Cardinal;
Jens Geyer433a6492019-06-19 23:14:08 +0200315 sMsg : string;
Jens Geyer02230912019-04-03 01:12:51 +0200316begin
317 http := CreateRequest;
318
319 pData := FOutputMemoryStream.Memory;
320 len := FOutputMemoryStream.Size;
321
322 // send all data immediately, since we have it in memory
Jens Geyer433a6492019-06-19 23:14:08 +0200323 if not http.SendRequest( pData, len, 0) then begin
324 error := Cardinal( GetLastError);
325 sMsg := 'WinHTTP send error '+IntToStr(Int64(error))+' '+WinHttpSysErrorMessage(error);
326 raise TTransportExceptionUnknown.Create(sMsg);
327 end;
Jens Geyer02230912019-04-03 01:12:51 +0200328
329 // end request and start receiving
Jens Geyer433a6492019-06-19 23:14:08 +0200330 if not http.FlushAndReceiveResponse then begin
331 error := Cardinal( GetLastError);
332 sMsg := 'WinHTTP recv error '+IntToStr(Int64(error))+' '+WinHttpSysErrorMessage(error);
333 if error = ERROR_WINHTTP_TIMEOUT
334 then raise TTransportExceptionTimedOut.Create( sMsg)
335 else raise TTransportExceptionInterrupted.Create( sMsg);
336 end;
Jens Geyer02230912019-04-03 01:12:51 +0200337
Jens Geyer6762cad2020-10-30 17:15:18 +0100338 // we're about to receive a new message, so reset everyting
Jens Geyer5a781c22025-02-04 23:35:55 +0100339 ResetMessageSizeAndConsumedBytes(-1);
Jens Geyer72c81112025-03-10 21:46:20 +0100340 EnsureSuccessHttpStatus(http); // throws if not
Jens Geyera019cda2019-11-09 23:24:52 +0100341 FInputStream := THTTPResponseStream.Create( http);
Jens Geyerb0123182020-02-12 12:16:19 +0100342 if http.QueryTotalResponseSize( dwSize) // FALSE indicates "no info available"
343 then UpdateKnownMessageSize( dwSize);
Jens Geyer02230912019-04-03 01:12:51 +0200344end;
345
346procedure TWinHTTPClientImpl.Write( const pBuf : Pointer; off, len : Integer);
347var pTmp : PByte;
348begin
349 pTmp := pBuf;
350 Inc(pTmp,off);
351 FOutputMemoryStream.Write( pTmp^, len);
352end;
353
354
Jens Geyer72c81112025-03-10 21:46:20 +0100355class procedure TWinHTTPClientImpl.EnsureSuccessHttpStatus( const aRequest : IWinHTTPRequest);
356var dwStatus : Cardinal;
357 sText : string;
358begin
359 if (aRequest <> nil)
360 then aRequest.QueryHttpStatus( dwStatus, sText)
361 else raise TTransportExceptionNotOpen.Create('Invalid HTTP request data');
362
363 if (200 > dwStatus) or (dwStatus > 299)
364 then raise TTransportExceptionEndOfFile.Create('HTTP '+UIntToStr(dwStatus)+' '+sText);
365end;
366
Jens Geyer02230912019-04-03 01:12:51 +0200367{ TWinHTTPClientImpl.THTTPResponseStream }
368
Jens Geyera019cda2019-11-09 23:24:52 +0100369constructor TWinHTTPClientImpl.THTTPResponseStream.Create( const aRequest : IWinHTTPRequest);
Jens Geyer02230912019-04-03 01:12:51 +0200370begin
371 inherited Create;
372 FRequest := aRequest;
373end;
374
375destructor TWinHTTPClientImpl.THTTPResponseStream.Destroy;
376begin
377 try
378 Close;
379 finally
380 inherited Destroy;
381 end;
382end;
383
384procedure TWinHTTPClientImpl.THTTPResponseStream.Close;
385begin
386 FRequest := nil;
387end;
388
389procedure TWinHTTPClientImpl.THTTPResponseStream.Flush;
390begin
391 raise ENotImplemented(ClassName+'.Flush');
392end;
393
394function TWinHTTPClientImpl.THTTPResponseStream.IsOpen: Boolean;
395begin
396 Result := FRequest <> nil;
397end;
398
399procedure TWinHTTPClientImpl.THTTPResponseStream.Open;
400begin
401 // nothing to do
402end;
403
404procedure TWinHTTPClientImpl.THTTPResponseStream.Write(const pBuf : Pointer; offset, count: Integer);
405begin
406 inherited; // check pointers
407 raise ENotImplemented(ClassName+'.Write');
408end;
409
410function TWinHTTPClientImpl.THTTPResponseStream.Read(const pBuf : Pointer; const buflen : Integer; offset, count: Integer): Integer;
411var pTmp : PByte;
412begin
413 inherited; // check pointers
414
415 if count >= buflen-offset
416 then count := buflen-offset;
417
418 if count > 0 then begin
419 pTmp := pBuf;
420 Inc( pTmp, offset);
421 Result := FRequest.ReadData( pTmp, count);
422 ASSERT( Result >= 0);
423 end
424 else Result := 0;
Jens Geyer02230912019-04-03 01:12:51 +0200425end;
426
427function TWinHTTPClientImpl.THTTPResponseStream.ToArray: TBytes;
428begin
429 raise ENotImplemented(ClassName+'.ToArray');
430end;
431
432
433end.