blob: b0f32ef3b81bd8e32317f530772b139862560205 [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;
44 FOutputMemoryStream : TMemoryStream;
45 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 Geyer02230912019-04-03 01:12:51 +020054
Jens Geyerfad7fd32019-11-09 23:24:52 +010055 strict private
Jens Geyer19fdca82019-06-12 22:09:05 +020056 type
57 TErrorInfo = ( SplitUrl, WinHTTPSession, WinHTTPConnection, WinHTTPRequest, RequestSetup, AutoProxy );
58
Jens Geyer02230912019-04-03 01:12:51 +020059 THTTPResponseStream = class( TThriftStreamImpl)
Jens Geyerfad7fd32019-11-09 23:24:52 +010060 strict private
Jens Geyer02230912019-04-03 01:12:51 +020061 FRequest : IWinHTTPRequest;
Jens Geyerfad7fd32019-11-09 23:24:52 +010062 strict protected
Jens Geyer02230912019-04-03 01:12:51 +020063 procedure Write( const pBuf : Pointer; offset: Integer; count: Integer); override;
64 function Read( const pBuf : Pointer; const buflen : Integer; offset: Integer; count: Integer): Integer; override;
65 procedure Open; override;
66 procedure Close; override;
67 procedure Flush; override;
68 function IsOpen: Boolean; override;
69 function ToArray: TBytes; override;
70 public
Jens Geyera019cda2019-11-09 23:24:52 +010071 constructor Create( const aRequest : IWinHTTPRequest);
Jens Geyer02230912019-04-03 01:12:51 +020072 destructor Destroy; override;
73 end;
74
Jens Geyerfad7fd32019-11-09 23:24:52 +010075 strict protected
Jens Geyer02230912019-04-03 01:12:51 +020076 function GetIsOpen: Boolean; override;
77 procedure Open(); override;
78 procedure Close(); override;
79 function Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; override;
80 procedure Write( const pBuf : Pointer; off, len : Integer); override;
81 procedure Flush; override;
82
83 procedure SetDnsResolveTimeout(const Value: Integer);
84 function GetDnsResolveTimeout: Integer;
85 procedure SetConnectionTimeout(const Value: Integer);
86 function GetConnectionTimeout: Integer;
87 procedure SetSendTimeout(const Value: Integer);
88 function GetSendTimeout: Integer;
89 procedure SetReadTimeout(const Value: Integer);
90 function GetReadTimeout: Integer;
Jens Geyer47f63172019-06-06 22:42:58 +020091 function GetSecureProtocols : TSecureProtocols;
92 procedure SetSecureProtocols( const value : TSecureProtocols);
Jens Geyer02230912019-04-03 01:12:51 +020093
94 function GetCustomHeaders: IThriftDictionary<string,string>;
95 procedure SendRequest;
Jens Geyer47f63172019-06-06 22:42:58 +020096
Jens Geyer02230912019-04-03 01:12:51 +020097 property DnsResolveTimeout: Integer read GetDnsResolveTimeout write SetDnsResolveTimeout;
98 property ConnectionTimeout: Integer read GetConnectionTimeout write SetConnectionTimeout;
99 property SendTimeout: Integer read GetSendTimeout write SetSendTimeout;
100 property ReadTimeout: Integer read GetReadTimeout write SetReadTimeout;
101 property CustomHeaders: IThriftDictionary<string,string> read GetCustomHeaders;
102 public
Jens Geyera019cda2019-11-09 23:24:52 +0100103 constructor Create( const aUri: string; const aConfig : IThriftConfiguration = nil);
Jens Geyer02230912019-04-03 01:12:51 +0200104 destructor Destroy; override;
105 end;
106
107implementation
108
Jens Geyera019cda2019-11-09 23:24:52 +0100109const
110 WINHTTP_CONNECTION_TIMEOUT = 60 * 1000;
111 WINHTTP_SENDRECV_TIMEOUT = 30 * 1000;
112
Jens Geyer02230912019-04-03 01:12:51 +0200113
114{ TWinHTTPClientImpl }
115
Jens Geyera019cda2019-11-09 23:24:52 +0100116constructor TWinHTTPClientImpl.Create( const aUri: string; const aConfig : IThriftConfiguration);
Jens Geyer02230912019-04-03 01:12:51 +0200117begin
Jens Geyera019cda2019-11-09 23:24:52 +0100118 inherited Create( aConfig);
Jens Geyer02230912019-04-03 01:12:51 +0200119 FUri := AUri;
120
121 // defaults according to MSDN
122 FDnsResolveTimeout := 0; // no timeout
Jens Geyera019cda2019-11-09 23:24:52 +0100123 FConnectionTimeout := WINHTTP_CONNECTION_TIMEOUT;
124 FSendTimeout := WINHTTP_SENDRECV_TIMEOUT;
125 FReadTimeout := WINHTTP_SENDRECV_TIMEOUT;
Jens Geyer02230912019-04-03 01:12:51 +0200126
Jens Geyer47f63172019-06-06 22:42:58 +0200127 FSecureProtocols := DEFAULT_THRIFT_SECUREPROTOCOLS;
128
Jens Geyer02230912019-04-03 01:12:51 +0200129 FCustomHeaders := TThriftDictionaryImpl<string,string>.Create;
130 FOutputMemoryStream := TMemoryStream.Create;
131end;
132
133destructor TWinHTTPClientImpl.Destroy;
134begin
135 Close;
136 FreeAndNil( FOutputMemoryStream);
137 inherited;
138end;
139
140function TWinHTTPClientImpl.CreateRequest: IWinHTTPRequest;
141var
Jens Geyer19fdca82019-06-12 22:09:05 +0200142 pair : TPair<string,string>;
Jens Geyer02230912019-04-03 01:12:51 +0200143 session : IWinHTTPSession;
144 connect : IWinHTTPConnection;
145 url : IWinHTTPUrl;
146 sPath : string;
Jens Geyer19fdca82019-06-12 22:09:05 +0200147 info : TErrorInfo;
Jens Geyer02230912019-04-03 01:12:51 +0200148begin
Jens Geyer19fdca82019-06-12 22:09:05 +0200149 info := TErrorInfo.SplitUrl;
150 try
151 url := TWinHTTPUrlImpl.Create( FUri);
Jens Geyer02230912019-04-03 01:12:51 +0200152
Jens Geyer19fdca82019-06-12 22:09:05 +0200153 info := TErrorInfo.WinHTTPSession;
154 session := TWinHTTPSessionImpl.Create('Apache Thrift Delphi WinHTTP');
155 session.EnableSecureProtocols( SecureProtocolsAsWinHTTPFlags);
Jens Geyer47f63172019-06-06 22:42:58 +0200156
Jens Geyer19fdca82019-06-12 22:09:05 +0200157 info := TErrorInfo.WinHTTPConnection;
158 connect := session.Connect( url.HostName, url.Port);
Jens Geyer02230912019-04-03 01:12:51 +0200159
Jens Geyer19fdca82019-06-12 22:09:05 +0200160 info := TErrorInfo.WinHTTPRequest;
161 sPath := url.UrlPath + url.ExtraInfo;
162 result := connect.OpenRequest( (url.Scheme = 'https'), 'POST', sPath, THRIFT_MIMETYPE);
Jens Geyer02230912019-04-03 01:12:51 +0200163
Jens Geyer19fdca82019-06-12 22:09:05 +0200164 // setting a timeout value to 0 (zero) means "no timeout" for that setting
165 info := TErrorInfo.RequestSetup;
166 result.SetTimeouts( DnsResolveTimeout, ConnectionTimeout, SendTimeout, ReadTimeout);
Jens Geyer02230912019-04-03 01:12:51 +0200167
Jens Geyer19fdca82019-06-12 22:09:05 +0200168 // headers
169 result.AddRequestHeader( 'Content-Type: '+THRIFT_MIMETYPE, WINHTTP_ADDREQ_FLAG_ADD);
170 for pair in FCustomHeaders do begin
171 Result.AddRequestHeader( pair.Key +': '+ pair.Value, WINHTTP_ADDREQ_FLAG_ADD);
172 end;
173
Jens Geyer19505c32019-06-22 00:59:54 +0200174 // enable automatic gzip,deflate decompression
175 result.EnableAutomaticContentDecompression(TRUE);
176
Jens Geyer19fdca82019-06-12 22:09:05 +0200177 // AutoProxy support
178 info := TErrorInfo.AutoProxy;
179 result.TryAutoProxy( FUri);
180 except
181 on e:TException do raise;
182 on e:Exception do raise TTransportExceptionUnknown.Create( e.Message+' (at '+EnumUtils<TErrorInfo>.ToString(Ord(info))+')');
Jens Geyer02230912019-04-03 01:12:51 +0200183 end;
184end;
185
Jens Geyer47f63172019-06-06 22:42:58 +0200186
187function TWinHTTPClientImpl.SecureProtocolsAsWinHTTPFlags : Cardinal;
188const
189 PROTOCOL_MAPPING : array[TSecureProtocol] of Cardinal = (
190 WINHTTP_FLAG_SECURE_PROTOCOL_SSL2,
191 WINHTTP_FLAG_SECURE_PROTOCOL_SSL3,
192 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1,
193 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1,
194 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2
195 );
196var
197 prot : TSecureProtocol;
198 protos : TSecureProtocols;
199begin
200 result := 0;
201 protos := GetSecureProtocols;
202 for prot := Low(TSecureProtocol) to High(TSecureProtocol) do begin
203 if prot in protos
204 then result := result or PROTOCOL_MAPPING[prot];
205 end;
206end;
207
208
Jens Geyer02230912019-04-03 01:12:51 +0200209function TWinHTTPClientImpl.GetDnsResolveTimeout: Integer;
210begin
211 Result := FDnsResolveTimeout;
212end;
213
214procedure TWinHTTPClientImpl.SetDnsResolveTimeout(const Value: Integer);
215begin
216 FDnsResolveTimeout := Value;
217end;
218
219function TWinHTTPClientImpl.GetConnectionTimeout: Integer;
220begin
221 Result := FConnectionTimeout;
222end;
223
224procedure TWinHTTPClientImpl.SetConnectionTimeout(const Value: Integer);
225begin
226 FConnectionTimeout := Value;
227end;
228
229function TWinHTTPClientImpl.GetSendTimeout: Integer;
230begin
231 Result := FSendTimeout;
232end;
233
234procedure TWinHTTPClientImpl.SetSendTimeout(const Value: Integer);
235begin
236 FSendTimeout := Value;
237end;
238
239function TWinHTTPClientImpl.GetReadTimeout: Integer;
240begin
241 Result := FReadTimeout;
242end;
243
244procedure TWinHTTPClientImpl.SetReadTimeout(const Value: Integer);
245begin
246 FReadTimeout := Value;
247end;
248
Jens Geyer47f63172019-06-06 22:42:58 +0200249function TWinHTTPClientImpl.GetSecureProtocols : TSecureProtocols;
250begin
251 Result := FSecureProtocols;
252end;
253
254procedure TWinHTTPClientImpl.SetSecureProtocols( const value : TSecureProtocols);
255begin
256 FSecureProtocols := Value;
257end;
258
Jens Geyer02230912019-04-03 01:12:51 +0200259function TWinHTTPClientImpl.GetCustomHeaders: IThriftDictionary<string,string>;
260begin
261 Result := FCustomHeaders;
262end;
263
264function TWinHTTPClientImpl.GetIsOpen: Boolean;
265begin
Jens Geyer528a0f02019-11-18 20:17:03 +0100266 Result := Assigned( FOutputMemoryStream);
Jens Geyer02230912019-04-03 01:12:51 +0200267end;
268
269procedure TWinHTTPClientImpl.Open;
270begin
271 FreeAndNil( FOutputMemoryStream);
272 FOutputMemoryStream := TMemoryStream.Create;
273end;
274
275procedure TWinHTTPClientImpl.Close;
276begin
277 FInputStream := nil;
278 FreeAndNil( FOutputMemoryStream);
279end;
280
281procedure TWinHTTPClientImpl.Flush;
282begin
283 try
284 SendRequest;
285 finally
286 FreeAndNil( FOutputMemoryStream);
287 FOutputMemoryStream := TMemoryStream.Create;
288 ASSERT( FOutputMemoryStream <> nil);
289 end;
290end;
291
292function TWinHTTPClientImpl.Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer;
293begin
294 if FInputStream = nil then begin
295 raise TTransportExceptionNotOpen.Create('No request has been sent');
296 end;
297
298 try
Jens Geyer41f47af2019-11-09 23:24:52 +0100299 Result := FInputStream.Read( pBuf, buflen, off, len);
Jens Geyera019cda2019-11-09 23:24:52 +0100300 CountConsumedMessageBytes( result);
Jens Geyer02230912019-04-03 01:12:51 +0200301 except
302 on E: Exception
303 do raise TTransportExceptionUnknown.Create(E.Message);
304 end;
305end;
306
307procedure TWinHTTPClientImpl.SendRequest;
308var
Jens Geyer433a6492019-06-19 23:14:08 +0200309 http : IWinHTTPRequest;
Jens Geyer02230912019-04-03 01:12:51 +0200310 pData : PByte;
Jens Geyer433a6492019-06-19 23:14:08 +0200311 len : Integer;
Jens Geyerb0123182020-02-12 12:16:19 +0100312 error, dwSize : Cardinal;
Jens Geyer433a6492019-06-19 23:14:08 +0200313 sMsg : string;
Jens Geyer02230912019-04-03 01:12:51 +0200314begin
315 http := CreateRequest;
316
317 pData := FOutputMemoryStream.Memory;
318 len := FOutputMemoryStream.Size;
319
320 // send all data immediately, since we have it in memory
Jens Geyer433a6492019-06-19 23:14:08 +0200321 if not http.SendRequest( pData, len, 0) then begin
322 error := Cardinal( GetLastError);
323 sMsg := 'WinHTTP send error '+IntToStr(Int64(error))+' '+WinHttpSysErrorMessage(error);
324 raise TTransportExceptionUnknown.Create(sMsg);
325 end;
Jens Geyer02230912019-04-03 01:12:51 +0200326
327 // end request and start receiving
Jens Geyer433a6492019-06-19 23:14:08 +0200328 if not http.FlushAndReceiveResponse then begin
329 error := Cardinal( GetLastError);
330 sMsg := 'WinHTTP recv error '+IntToStr(Int64(error))+' '+WinHttpSysErrorMessage(error);
331 if error = ERROR_WINHTTP_TIMEOUT
332 then raise TTransportExceptionTimedOut.Create( sMsg)
333 else raise TTransportExceptionInterrupted.Create( sMsg);
334 end;
Jens Geyer02230912019-04-03 01:12:51 +0200335
Jens Geyer6762cad2020-10-30 17:15:18 +0100336 // we're about to receive a new message, so reset everyting
337 ResetConsumedMessageSize(-1);
Jens Geyera019cda2019-11-09 23:24:52 +0100338 FInputStream := THTTPResponseStream.Create( http);
Jens Geyerb0123182020-02-12 12:16:19 +0100339 if http.QueryTotalResponseSize( dwSize) // FALSE indicates "no info available"
340 then UpdateKnownMessageSize( dwSize);
Jens Geyer02230912019-04-03 01:12:51 +0200341end;
342
343procedure TWinHTTPClientImpl.Write( const pBuf : Pointer; off, len : Integer);
344var pTmp : PByte;
345begin
346 pTmp := pBuf;
347 Inc(pTmp,off);
348 FOutputMemoryStream.Write( pTmp^, len);
349end;
350
351
352{ TWinHTTPClientImpl.THTTPResponseStream }
353
Jens Geyera019cda2019-11-09 23:24:52 +0100354constructor TWinHTTPClientImpl.THTTPResponseStream.Create( const aRequest : IWinHTTPRequest);
Jens Geyer02230912019-04-03 01:12:51 +0200355begin
356 inherited Create;
357 FRequest := aRequest;
358end;
359
360destructor TWinHTTPClientImpl.THTTPResponseStream.Destroy;
361begin
362 try
363 Close;
364 finally
365 inherited Destroy;
366 end;
367end;
368
369procedure TWinHTTPClientImpl.THTTPResponseStream.Close;
370begin
371 FRequest := nil;
372end;
373
374procedure TWinHTTPClientImpl.THTTPResponseStream.Flush;
375begin
376 raise ENotImplemented(ClassName+'.Flush');
377end;
378
379function TWinHTTPClientImpl.THTTPResponseStream.IsOpen: Boolean;
380begin
381 Result := FRequest <> nil;
382end;
383
384procedure TWinHTTPClientImpl.THTTPResponseStream.Open;
385begin
386 // nothing to do
387end;
388
389procedure TWinHTTPClientImpl.THTTPResponseStream.Write(const pBuf : Pointer; offset, count: Integer);
390begin
391 inherited; // check pointers
392 raise ENotImplemented(ClassName+'.Write');
393end;
394
395function TWinHTTPClientImpl.THTTPResponseStream.Read(const pBuf : Pointer; const buflen : Integer; offset, count: Integer): Integer;
396var pTmp : PByte;
397begin
398 inherited; // check pointers
399
400 if count >= buflen-offset
401 then count := buflen-offset;
402
403 if count > 0 then begin
404 pTmp := pBuf;
405 Inc( pTmp, offset);
406 Result := FRequest.ReadData( pTmp, count);
407 ASSERT( Result >= 0);
408 end
409 else Result := 0;
Jens Geyer02230912019-04-03 01:12:51 +0200410end;
411
412function TWinHTTPClientImpl.THTTPResponseStream.ToArray: TBytes;
413begin
414 raise ENotImplemented(ClassName+'.ToArray');
415end;
416
417
418end.