1
+ import base64
1
2
import logging
2
- from typing import Dict
3
-
3
+ import urllib . parse
4
+ from typing import Dict , Union
4
5
6
+ import six
5
7
import thrift
6
8
7
- import urllib .parse , six , base64
8
-
9
9
logger = logging .getLogger (__name__ )
10
10
11
+ import ssl
12
+ import warnings
13
+ from http .client import HTTPResponse
14
+ from io import BytesIO
15
+
16
+ from urllib3 import HTTPConnectionPool , HTTPSConnectionPool , ProxyManager
17
+
11
18
12
19
class THttpClient (thrift .transport .THttpClient .THttpClient ):
13
20
def __init__ (
@@ -20,22 +27,152 @@ def __init__(
20
27
cert_file = None ,
21
28
key_file = None ,
22
29
ssl_context = None ,
30
+ max_connections : int = 1 ,
23
31
):
24
- super ().__init__ (
25
- uri_or_host , port , path , cafile , cert_file , key_file , ssl_context
26
- )
32
+ if port is not None :
33
+ warnings .warn (
34
+ "Please use the THttpClient('http{s}://host:port/path') constructor" ,
35
+ DeprecationWarning ,
36
+ stacklevel = 2 ,
37
+ )
38
+ self .host = uri_or_host
39
+ self .port = port
40
+ assert path
41
+ self .path = path
42
+ self .scheme = "http"
43
+ else :
44
+ parsed = urllib .parse .urlsplit (uri_or_host )
45
+ self .scheme = parsed .scheme
46
+ assert self .scheme in ("http" , "https" )
47
+ if self .scheme == "https" :
48
+ self .certfile = cert_file
49
+ self .keyfile = key_file
50
+ self .context = (
51
+ ssl .create_default_context (cafile = cafile )
52
+ if (cafile and not ssl_context )
53
+ else ssl_context
54
+ )
55
+ self .port = parsed .port
56
+ self .host = parsed .hostname
57
+ self .path = parsed .path
58
+ if parsed .query :
59
+ self .path += "?%s" % parsed .query
60
+ try :
61
+ proxy = urllib .request .getproxies ()[self .scheme ]
62
+ except KeyError :
63
+ proxy = None
64
+ else :
65
+ if urllib .request .proxy_bypass (self .host ):
66
+ proxy = None
67
+ if proxy :
68
+ parsed = urllib .parse .urlparse (proxy )
69
+
70
+ # realhost and realport are the host and port of the actual request
71
+ self .realhost = self .host
72
+ self .realport = self .port
73
+
74
+ # this is passed to ProxyManager
75
+ self .proxy_uri : str = proxy
76
+ self .host = parsed .hostname
77
+ self .port = parsed .port
78
+ self .proxy_auth = self .basic_proxy_auth_header (parsed )
79
+ else :
80
+ self .realhost = self .realport = self .proxy_auth = None
81
+
82
+ self .max_connections = max_connections
83
+
84
+ self .__wbuf = BytesIO ()
85
+ self .__resp : Union [None , HTTPResponse ] = None
86
+ self .__timeout = None
87
+ self .__custom_headers = None
88
+
27
89
self .__auth_provider = auth_provider
28
90
29
91
def setCustomHeaders (self , headers : Dict [str , str ]):
30
92
self ._headers = headers
31
93
super ().setCustomHeaders (headers )
32
94
95
+ def open (self ):
96
+
97
+ # self.__pool replaces the self.__http used by the original THttpClient
98
+ if self .scheme == "http" :
99
+ pool_class = HTTPConnectionPool
100
+ elif self .scheme == "https" :
101
+ pool_class = HTTPSConnectionPool
102
+
103
+ _pool_kwargs = {"maxsize" : self .max_connections }
104
+
105
+ if self .using_proxy ():
106
+ proxy_manager = ProxyManager (
107
+ self .proxy_uri ,
108
+ num_pools = 1 ,
109
+ headers = {"Proxy-Authorization" : self .proxy_auth },
110
+ )
111
+ self .__pool = proxy_manager .connection_from_host (
112
+ self .host , self .port , pool_kwargs = _pool_kwargs
113
+ )
114
+ else :
115
+ self .__pool = pool_class (self .host , self .port , ** _pool_kwargs )
116
+
117
+ def close (self ):
118
+ self .__resp and self .__resp .release_conn ()
119
+ self .__resp = None
120
+
121
+ def read (self , sz ):
122
+ return self .__resp .read (sz )
123
+
124
+ def isOpen (self ):
125
+ return self .__resp is not None
126
+
33
127
def flush (self ):
128
+
129
+ # Pull data out of buffer that will be sent in this request
130
+ data = self .__wbuf .getvalue ()
131
+ self .__wbuf = BytesIO ()
132
+
133
+ # Header handling
134
+
34
135
headers = dict (self ._headers )
35
136
self .__auth_provider .add_headers (headers )
36
137
self ._headers = headers
37
138
self .setCustomHeaders (self ._headers )
38
- super ().flush ()
139
+
140
+ # Note: we don't set User-Agent explicitly in this class because PySQL
141
+ # should always provide one. Unlike the original THttpClient class, our version
142
+ # doesn't define a default User-Agent and so should raise an exception if one
143
+ # isn't provided.
144
+ assert self .__custom_headers and "User-Agent" in self .__custom_headers
145
+
146
+ headers = {
147
+ "Content-Type" : "application/x-thrift" ,
148
+ "Content-Length" : str (len (data )),
149
+ }
150
+
151
+ if self .using_proxy () and self .scheme == "http" and self .proxy_auth is not None :
152
+ headers ["Proxy-Authorization" : self .proxy_auth ]
153
+
154
+ if self .__custom_headers :
155
+ custom_headers = {key : val for key , val in self .__custom_headers .items ()}
156
+ headers .update (** custom_headers )
157
+
158
+ # HTTP request
159
+ self .__resp = self .__pool .request (
160
+ "POST" ,
161
+ url = self .path ,
162
+ body = data ,
163
+ headers = headers ,
164
+ preload_content = False ,
165
+ timeout = self .__timeout ,
166
+ )
167
+
168
+ # Get reply to flush the request
169
+ self .code = self .__resp .status
170
+ self .message = self .__resp .reason
171
+ self .headers = self .__resp .headers
172
+
173
+ # Saves the cookie sent by the server response
174
+ if "Set-Cookie" in self .headers :
175
+ self .setCustomHeaders (dict ("Cookie" , self .headers ["Set-Cookie" ]))
39
176
40
177
@staticmethod
41
178
def basic_proxy_auth_header (proxy ):
0 commit comments