blob: ec8c87f085e671726867ae1f887887711b2f6621 [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,
32 Thrift.Transport,
33 Thrift.Exception,
34 Thrift.Utils,
35 Thrift.WinHTTP,
36 Thrift.Stream;
37
38type
39 TWinHTTPClientImpl = class( TTransportImpl, IHTTPClient)
Jens Geyerfad7fd32019-11-09 23:24:52 +010040 strict private
Jens Geyer02230912019-04-03 01:12:51 +020041 FUri : string;
42 FInputStream : IThriftStream;
43 FOutputMemoryStream : TMemoryStream;
44 FDnsResolveTimeout : Integer;
45 FConnectionTimeout : Integer;
46 FSendTimeout : Integer;
47 FReadTimeout : Integer;
48 FCustomHeaders : IThriftDictionary<string,string>;
Jens Geyer47f63172019-06-06 22:42:58 +020049 FSecureProtocols : TSecureProtocols;
Jens Geyer02230912019-04-03 01:12:51 +020050
51 function CreateRequest: IWinHTTPRequest;
Jens Geyer47f63172019-06-06 22:42:58 +020052 function SecureProtocolsAsWinHTTPFlags : Cardinal;
Jens Geyer02230912019-04-03 01:12:51 +020053
Jens Geyerfad7fd32019-11-09 23:24:52 +010054 strict private
Jens Geyer19fdca82019-06-12 22:09:05 +020055 type
56 TErrorInfo = ( SplitUrl, WinHTTPSession, WinHTTPConnection, WinHTTPRequest, RequestSetup, AutoProxy );
57
Jens Geyer02230912019-04-03 01:12:51 +020058 THTTPResponseStream = class( TThriftStreamImpl)
Jens Geyerfad7fd32019-11-09 23:24:52 +010059 strict private
Jens Geyer02230912019-04-03 01:12:51 +020060 FRequest : IWinHTTPRequest;
Jens Geyer41f47af2019-11-09 23:24:52 +010061 FTransportControl : ITransportControl;
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;
Jens Geyer41f47af2019-11-09 23:24:52 +010065 procedure CheckReadBytesAvailable( const value : Integer); override;
66 procedure ConsumeReadBytes( const count : Integer);
Jens Geyer02230912019-04-03 01:12:51 +020067 procedure Open; override;
68 procedure Close; override;
69 procedure Flush; override;
70 function IsOpen: Boolean; override;
71 function ToArray: TBytes; override;
72 public
Jens Geyer41f47af2019-11-09 23:24:52 +010073 constructor Create( const aRequest : IWinHTTPRequest; const aTransportCtl : ITransportControl);
Jens Geyer02230912019-04-03 01:12:51 +020074 destructor Destroy; override;
75 end;
76
Jens Geyerfad7fd32019-11-09 23:24:52 +010077 strict protected
Jens Geyer02230912019-04-03 01:12:51 +020078 function GetIsOpen: Boolean; override;
79 procedure Open(); override;
80 procedure Close(); override;
81 function Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer; override;
82 procedure Write( const pBuf : Pointer; off, len : Integer); override;
83 procedure Flush; override;
Jens Geyer41f47af2019-11-09 23:24:52 +010084 procedure CheckReadBytesAvailable( const value : Integer); override;
Jens Geyer02230912019-04-03 01:12:51 +020085
86 procedure SetDnsResolveTimeout(const Value: Integer);
87 function GetDnsResolveTimeout: Integer;
88 procedure SetConnectionTimeout(const Value: Integer);
89 function GetConnectionTimeout: Integer;
90 procedure SetSendTimeout(const Value: Integer);
91 function GetSendTimeout: Integer;
92 procedure SetReadTimeout(const Value: Integer);
93 function GetReadTimeout: Integer;
Jens Geyer47f63172019-06-06 22:42:58 +020094 function GetSecureProtocols : TSecureProtocols;
95 procedure SetSecureProtocols( const value : TSecureProtocols);
Jens Geyer02230912019-04-03 01:12:51 +020096
97 function GetCustomHeaders: IThriftDictionary<string,string>;
98 procedure SendRequest;
Jens Geyer47f63172019-06-06 22:42:58 +020099
Jens Geyer02230912019-04-03 01:12:51 +0200100 property DnsResolveTimeout: Integer read GetDnsResolveTimeout write SetDnsResolveTimeout;
101 property ConnectionTimeout: Integer read GetConnectionTimeout write SetConnectionTimeout;
102 property SendTimeout: Integer read GetSendTimeout write SetSendTimeout;
103 property ReadTimeout: Integer read GetReadTimeout write SetReadTimeout;
104 property CustomHeaders: IThriftDictionary<string,string> read GetCustomHeaders;
105 public
Jens Geyer41f47af2019-11-09 23:24:52 +0100106 constructor Create( const AUri: string; const aTransportCtl : ITransportControl = nil);
Jens Geyer02230912019-04-03 01:12:51 +0200107 destructor Destroy; override;
108 end;
109
110implementation
111
112
113{ TWinHTTPClientImpl }
114
Jens Geyer41f47af2019-11-09 23:24:52 +0100115constructor TWinHTTPClientImpl.Create(const AUri: string; const aTransportCtl : ITransportControl);
Jens Geyer02230912019-04-03 01:12:51 +0200116begin
Jens Geyer41f47af2019-11-09 23:24:52 +0100117 inherited Create( aTransportCtl);
Jens Geyer02230912019-04-03 01:12:51 +0200118 FUri := AUri;
119
120 // defaults according to MSDN
121 FDnsResolveTimeout := 0; // no timeout
122 FConnectionTimeout := 60 * 1000;
123 FSendTimeout := 30 * 1000;
124 FReadTimeout := 30 * 1000;
125
Jens Geyer47f63172019-06-06 22:42:58 +0200126 FSecureProtocols := DEFAULT_THRIFT_SECUREPROTOCOLS;
127
Jens Geyer02230912019-04-03 01:12:51 +0200128 FCustomHeaders := TThriftDictionaryImpl<string,string>.Create;
129 FOutputMemoryStream := TMemoryStream.Create;
130end;
131
132destructor TWinHTTPClientImpl.Destroy;
133begin
134 Close;
135 FreeAndNil( FOutputMemoryStream);
136 inherited;
137end;
138
139function TWinHTTPClientImpl.CreateRequest: IWinHTTPRequest;
140var
Jens Geyer19fdca82019-06-12 22:09:05 +0200141 pair : TPair<string,string>;
Jens Geyer02230912019-04-03 01:12:51 +0200142 session : IWinHTTPSession;
143 connect : IWinHTTPConnection;
144 url : IWinHTTPUrl;
145 sPath : string;
Jens Geyer19fdca82019-06-12 22:09:05 +0200146 info : TErrorInfo;
Jens Geyer02230912019-04-03 01:12:51 +0200147begin
Jens Geyer19fdca82019-06-12 22:09:05 +0200148 info := TErrorInfo.SplitUrl;
149 try
150 url := TWinHTTPUrlImpl.Create( FUri);
Jens Geyer02230912019-04-03 01:12:51 +0200151
Jens Geyer19fdca82019-06-12 22:09:05 +0200152 info := TErrorInfo.WinHTTPSession;
153 session := TWinHTTPSessionImpl.Create('Apache Thrift Delphi WinHTTP');
154 session.EnableSecureProtocols( SecureProtocolsAsWinHTTPFlags);
Jens Geyer47f63172019-06-06 22:42:58 +0200155
Jens Geyer19fdca82019-06-12 22:09:05 +0200156 info := TErrorInfo.WinHTTPConnection;
157 connect := session.Connect( url.HostName, url.Port);
Jens Geyer02230912019-04-03 01:12:51 +0200158
Jens Geyer19fdca82019-06-12 22:09:05 +0200159 info := TErrorInfo.WinHTTPRequest;
160 sPath := url.UrlPath + url.ExtraInfo;
161 result := connect.OpenRequest( (url.Scheme = 'https'), 'POST', sPath, THRIFT_MIMETYPE);
Jens Geyer02230912019-04-03 01:12:51 +0200162
Jens Geyer19fdca82019-06-12 22:09:05 +0200163 // setting a timeout value to 0 (zero) means "no timeout" for that setting
164 info := TErrorInfo.RequestSetup;
165 result.SetTimeouts( DnsResolveTimeout, ConnectionTimeout, SendTimeout, ReadTimeout);
Jens Geyer02230912019-04-03 01:12:51 +0200166
Jens Geyer19fdca82019-06-12 22:09:05 +0200167 // headers
168 result.AddRequestHeader( 'Content-Type: '+THRIFT_MIMETYPE, WINHTTP_ADDREQ_FLAG_ADD);
169 for pair in FCustomHeaders do begin
170 Result.AddRequestHeader( pair.Key +': '+ pair.Value, WINHTTP_ADDREQ_FLAG_ADD);
171 end;
172
Jens Geyer19505c32019-06-22 00:59:54 +0200173 // enable automatic gzip,deflate decompression
174 result.EnableAutomaticContentDecompression(TRUE);
175
Jens Geyer19fdca82019-06-12 22:09:05 +0200176 // AutoProxy support
177 info := TErrorInfo.AutoProxy;
178 result.TryAutoProxy( FUri);
179 except
180 on e:TException do raise;
181 on e:Exception do raise TTransportExceptionUnknown.Create( e.Message+' (at '+EnumUtils<TErrorInfo>.ToString(Ord(info))+')');
Jens Geyer02230912019-04-03 01:12:51 +0200182 end;
183end;
184
Jens Geyer47f63172019-06-06 22:42:58 +0200185
186function TWinHTTPClientImpl.SecureProtocolsAsWinHTTPFlags : Cardinal;
187const
188 PROTOCOL_MAPPING : array[TSecureProtocol] of Cardinal = (
189 WINHTTP_FLAG_SECURE_PROTOCOL_SSL2,
190 WINHTTP_FLAG_SECURE_PROTOCOL_SSL3,
191 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1,
192 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1,
193 WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2
194 );
195var
196 prot : TSecureProtocol;
197 protos : TSecureProtocols;
198begin
199 result := 0;
200 protos := GetSecureProtocols;
201 for prot := Low(TSecureProtocol) to High(TSecureProtocol) do begin
202 if prot in protos
203 then result := result or PROTOCOL_MAPPING[prot];
204 end;
205end;
206
207
Jens Geyer02230912019-04-03 01:12:51 +0200208function TWinHTTPClientImpl.GetDnsResolveTimeout: Integer;
209begin
210 Result := FDnsResolveTimeout;
211end;
212
213procedure TWinHTTPClientImpl.SetDnsResolveTimeout(const Value: Integer);
214begin
215 FDnsResolveTimeout := Value;
216end;
217
218function TWinHTTPClientImpl.GetConnectionTimeout: Integer;
219begin
220 Result := FConnectionTimeout;
221end;
222
223procedure TWinHTTPClientImpl.SetConnectionTimeout(const Value: Integer);
224begin
225 FConnectionTimeout := Value;
226end;
227
228function TWinHTTPClientImpl.GetSendTimeout: Integer;
229begin
230 Result := FSendTimeout;
231end;
232
233procedure TWinHTTPClientImpl.SetSendTimeout(const Value: Integer);
234begin
235 FSendTimeout := Value;
236end;
237
238function TWinHTTPClientImpl.GetReadTimeout: Integer;
239begin
240 Result := FReadTimeout;
241end;
242
243procedure TWinHTTPClientImpl.SetReadTimeout(const Value: Integer);
244begin
245 FReadTimeout := Value;
246end;
247
Jens Geyer47f63172019-06-06 22:42:58 +0200248function TWinHTTPClientImpl.GetSecureProtocols : TSecureProtocols;
249begin
250 Result := FSecureProtocols;
251end;
252
253procedure TWinHTTPClientImpl.SetSecureProtocols( const value : TSecureProtocols);
254begin
255 FSecureProtocols := Value;
256end;
257
Jens Geyer02230912019-04-03 01:12:51 +0200258function TWinHTTPClientImpl.GetCustomHeaders: IThriftDictionary<string,string>;
259begin
260 Result := FCustomHeaders;
261end;
262
263function TWinHTTPClientImpl.GetIsOpen: Boolean;
264begin
265 Result := True;
266end;
267
268procedure TWinHTTPClientImpl.Open;
269begin
270 FreeAndNil( FOutputMemoryStream);
271 FOutputMemoryStream := TMemoryStream.Create;
272end;
273
274procedure TWinHTTPClientImpl.Close;
275begin
276 FInputStream := nil;
277 FreeAndNil( FOutputMemoryStream);
278end;
279
280procedure TWinHTTPClientImpl.Flush;
281begin
282 try
283 SendRequest;
284 finally
285 FreeAndNil( FOutputMemoryStream);
286 FOutputMemoryStream := TMemoryStream.Create;
287 ASSERT( FOutputMemoryStream <> nil);
288 end;
289end;
290
Jens Geyer41f47af2019-11-09 23:24:52 +0100291procedure TWinHTTPClientImpl.CheckReadBytesAvailable( const value : Integer);
292begin
293 if FInputStream <> nil
294 then FInputStream.CheckReadBytesAvailable( value)
295 else raise TTransportExceptionNotOpen.Create('No request has been sent');
296end;
297
Jens Geyer02230912019-04-03 01:12:51 +0200298function TWinHTTPClientImpl.Read( const pBuf : Pointer; const buflen : Integer; off: Integer; len: Integer): Integer;
299begin
300 if FInputStream = nil then begin
301 raise TTransportExceptionNotOpen.Create('No request has been sent');
302 end;
303
304 try
Jens Geyer41f47af2019-11-09 23:24:52 +0100305 Result := FInputStream.Read( pBuf, buflen, off, len);
306 ConsumeReadBytes( result);
Jens Geyer02230912019-04-03 01:12:51 +0200307 except
308 on E: Exception
309 do raise TTransportExceptionUnknown.Create(E.Message);
310 end;
311end;
312
313procedure TWinHTTPClientImpl.SendRequest;
314var
Jens Geyer433a6492019-06-19 23:14:08 +0200315 http : IWinHTTPRequest;
Jens Geyer41f47af2019-11-09 23:24:52 +0100316 ctrl : ITransportControl;
Jens Geyer02230912019-04-03 01:12:51 +0200317 pData : PByte;
Jens Geyer433a6492019-06-19 23:14:08 +0200318 len : Integer;
319 error : Cardinal;
320 sMsg : string;
Jens Geyer02230912019-04-03 01:12:51 +0200321begin
322 http := CreateRequest;
323
324 pData := FOutputMemoryStream.Memory;
325 len := FOutputMemoryStream.Size;
326
327 // send all data immediately, since we have it in memory
Jens Geyer433a6492019-06-19 23:14:08 +0200328 if not http.SendRequest( pData, len, 0) then begin
329 error := Cardinal( GetLastError);
330 sMsg := 'WinHTTP send error '+IntToStr(Int64(error))+' '+WinHttpSysErrorMessage(error);
331 raise TTransportExceptionUnknown.Create(sMsg);
332 end;
Jens Geyer02230912019-04-03 01:12:51 +0200333
334 // end request and start receiving
Jens Geyer433a6492019-06-19 23:14:08 +0200335 if not http.FlushAndReceiveResponse then begin
336 error := Cardinal( GetLastError);
337 sMsg := 'WinHTTP recv error '+IntToStr(Int64(error))+' '+WinHttpSysErrorMessage(error);
338 if error = ERROR_WINHTTP_TIMEOUT
339 then raise TTransportExceptionTimedOut.Create( sMsg)
340 else raise TTransportExceptionInterrupted.Create( sMsg);
341 end;
Jens Geyer02230912019-04-03 01:12:51 +0200342
Jens Geyer41f47af2019-11-09 23:24:52 +0100343 ctrl := TTransportControlImpl.Create( TransportControl.MaxAllowedMessageSize);
344 FInputStream := THTTPResponseStream.Create( http, ctrl);
Jens Geyer02230912019-04-03 01:12:51 +0200345end;
346
347procedure TWinHTTPClientImpl.Write( const pBuf : Pointer; off, len : Integer);
348var pTmp : PByte;
349begin
350 pTmp := pBuf;
351 Inc(pTmp,off);
352 FOutputMemoryStream.Write( pTmp^, len);
353end;
354
355
356{ TWinHTTPClientImpl.THTTPResponseStream }
357
Jens Geyer41f47af2019-11-09 23:24:52 +0100358constructor TWinHTTPClientImpl.THTTPResponseStream.Create( const aRequest : IWinHTTPRequest; const aTransportCtl : ITransportControl);
Jens Geyer02230912019-04-03 01:12:51 +0200359begin
360 inherited Create;
361 FRequest := aRequest;
Jens Geyer41f47af2019-11-09 23:24:52 +0100362 FTransportControl := aTransportCtl;
363 ASSERT( FTransportControl <> nil);
Jens Geyer02230912019-04-03 01:12:51 +0200364end;
365
366destructor TWinHTTPClientImpl.THTTPResponseStream.Destroy;
367begin
368 try
369 Close;
370 finally
371 inherited Destroy;
372 end;
373end;
374
375procedure TWinHTTPClientImpl.THTTPResponseStream.Close;
376begin
377 FRequest := nil;
378end;
379
380procedure TWinHTTPClientImpl.THTTPResponseStream.Flush;
381begin
382 raise ENotImplemented(ClassName+'.Flush');
383end;
384
385function TWinHTTPClientImpl.THTTPResponseStream.IsOpen: Boolean;
386begin
387 Result := FRequest <> nil;
388end;
389
390procedure TWinHTTPClientImpl.THTTPResponseStream.Open;
391begin
392 // nothing to do
393end;
394
395procedure TWinHTTPClientImpl.THTTPResponseStream.Write(const pBuf : Pointer; offset, count: Integer);
396begin
397 inherited; // check pointers
398 raise ENotImplemented(ClassName+'.Write');
399end;
400
401function TWinHTTPClientImpl.THTTPResponseStream.Read(const pBuf : Pointer; const buflen : Integer; offset, count: Integer): Integer;
402var pTmp : PByte;
403begin
404 inherited; // check pointers
405
406 if count >= buflen-offset
407 then count := buflen-offset;
408
Jens Geyer41f47af2019-11-09 23:24:52 +0100409 CheckReadBytesAvailable(count);
410
Jens Geyer02230912019-04-03 01:12:51 +0200411 if count > 0 then begin
412 pTmp := pBuf;
413 Inc( pTmp, offset);
414 Result := FRequest.ReadData( pTmp, count);
415 ASSERT( Result >= 0);
416 end
417 else Result := 0;
Jens Geyer41f47af2019-11-09 23:24:52 +0100418
419 ConsumeReadBytes( result);
420end;
421
422procedure TWinHTTPClientImpl.THTTPResponseStream.ConsumeReadBytes( const count : Integer);
423begin
424 if FTransportControl <> nil
425 then FTransportControl.ConsumeReadBytes( count);
426end;
427
428procedure TWinHTTPClientImpl.THTTPResponseStream.CheckReadBytesAvailable( const value : Integer);
429begin
430 if Int64(value) > Int64(FRequest.QueryDataAvailable)
431 then raise TTransportExceptionEndOfFile.Create('Not enough input data');
Jens Geyer02230912019-04-03 01:12:51 +0200432end;
433
434function TWinHTTPClientImpl.THTTPResponseStream.ToArray: TBytes;
435begin
436 raise ENotImplemented(ClassName+'.ToArray');
437end;
438
439
440end.