1# 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# 19 20from io import BytesIO 21import os 22import ssl 23import sys 24import warnings 25import base64 26 27from six.moves import urllib 28from six.moves import http_client 29 30from .TTransport import TTransportBase 31import six 32 33 34class THttpClient(TTransportBase): 35 """Http implementation of TTransport base.""" 36 37 def __init__(self, uri_or_host, port=None, path=None, cafile=None, cert_file=None, key_file=None, ssl_context=None): 38 """THttpClient supports two different types of construction: 39 40 THttpClient(host, port, path) - deprecated 41 THttpClient(uri, [port=<n>, path=<s>, cafile=<filename>, cert_file=<filename>, key_file=<filename>, ssl_context=<context>]) 42 43 Only the second supports https. To properly authenticate against the server, 44 provide the client's identity by specifying cert_file and key_file. To properly 45 authenticate the server, specify either cafile or ssl_context with a CA defined. 46 NOTE: if both cafile and ssl_context are defined, ssl_context will override cafile. 47 """ 48 if port is not None: 49 warnings.warn( 50 "Please use the THttpClient('http{s}://host:port/path') constructor", 51 DeprecationWarning, 52 stacklevel=2) 53 self.host = uri_or_host 54 self.port = port 55 assert path 56 self.path = path 57 self.scheme = 'http' 58 else: 59 parsed = urllib.parse.urlparse(uri_or_host) 60 self.scheme = parsed.scheme 61 assert self.scheme in ('http', 'https') 62 if self.scheme == 'http': 63 self.port = parsed.port or http_client.HTTP_PORT 64 elif self.scheme == 'https': 65 self.port = parsed.port or http_client.HTTPS_PORT 66 self.certfile = cert_file 67 self.keyfile = key_file 68 self.context = ssl.create_default_context(cafile=cafile) if (cafile and not ssl_context) else ssl_context 69 self.host = parsed.hostname 70 self.path = parsed.path 71 if parsed.query: 72 self.path += '?%s' % parsed.query 73 try: 74 proxy = urllib.request.getproxies()[self.scheme] 75 except KeyError: 76 proxy = None 77 else: 78 if urllib.request.proxy_bypass(self.host): 79 proxy = None 80 if proxy: 81 parsed = urllib.parse.urlparse(proxy) 82 self.realhost = self.host 83 self.realport = self.port 84 self.host = parsed.hostname 85 self.port = parsed.port 86 self.proxy_auth = self.basic_proxy_auth_header(parsed) 87 else: 88 self.realhost = self.realport = self.proxy_auth = None 89 self.__wbuf = BytesIO() 90 self.__http = None 91 self.__http_response = None 92 self.__timeout = None 93 self.__custom_headers = None 94 self.headers = None 95 96 @staticmethod 97 def basic_proxy_auth_header(proxy): 98 if proxy is None or not proxy.username: 99 return None 100 ap = "%s:%s" % (urllib.parse.unquote(proxy.username), 101 urllib.parse.unquote(proxy.password)) 102 cr = base64.b64encode(ap.encode()).strip() 103 return "Basic " + cr 104 105 def using_proxy(self): 106 return self.realhost is not None 107 108 def open(self): 109 if self.scheme == 'http': 110 self.__http = http_client.HTTPConnection(self.host, self.port, 111 timeout=self.__timeout) 112 elif self.scheme == 'https': 113 self.__http = http_client.HTTPSConnection(self.host, self.port, 114 key_file=self.keyfile, 115 cert_file=self.certfile, 116 timeout=self.__timeout, 117 context=self.context) 118 if self.using_proxy(): 119 self.__http.set_tunnel(self.realhost, self.realport, 120 {"Proxy-Authorization": self.proxy_auth}) 121 122 def close(self): 123 self.__http.close() 124 self.__http = None 125 self.__http_response = None 126 127 def isOpen(self): 128 return self.__http is not None 129 130 def setTimeout(self, ms): 131 if ms is None: 132 self.__timeout = None 133 else: 134 self.__timeout = ms / 1000.0 135 136 def setCustomHeaders(self, headers): 137 self.__custom_headers = headers 138 139 def read(self, sz): 140 return self.__http_response.read(sz) 141 142 def write(self, buf): 143 self.__wbuf.write(buf) 144 145 def flush(self): 146 if self.isOpen(): 147 self.close() 148 self.open() 149 150 # Pull data out of buffer 151 data = self.__wbuf.getvalue() 152 self.__wbuf = BytesIO() 153 154 # HTTP request 155 if self.using_proxy() and self.scheme == "http": 156 # need full URL of real host for HTTP proxy here (HTTPS uses CONNECT tunnel) 157 self.__http.putrequest('POST', "http://%s:%s%s" % 158 (self.realhost, self.realport, self.path)) 159 else: 160 self.__http.putrequest('POST', self.path) 161 162 # Write headers 163 self.__http.putheader('Content-Type', 'application/x-thrift') 164 self.__http.putheader('Content-Length', str(len(data))) 165 if self.using_proxy() and self.scheme == "http" and self.proxy_auth is not None: 166 self.__http.putheader("Proxy-Authorization", self.proxy_auth) 167 168 if not self.__custom_headers or 'User-Agent' not in self.__custom_headers: 169 user_agent = 'Python/THttpClient' 170 script = os.path.basename(sys.argv[0]) 171 if script: 172 user_agent = '%s (%s)' % (user_agent, urllib.parse.quote(script)) 173 self.__http.putheader('User-Agent', user_agent) 174 175 if self.__custom_headers: 176 for key, val in six.iteritems(self.__custom_headers): 177 self.__http.putheader(key, val) 178 179 # Saves the cookie sent by the server in the previous response. 180 # HTTPConnection.putheader can only be called after a request has been 181 # started, and before it's been sent. 182 if self.headers and 'Set-Cookie' in self.headers: 183 self.__http.putheader('Cookie', self.headers['Set-Cookie']) 184 185 self.__http.endheaders() 186 187 # Write payload 188 self.__http.send(data) 189 190 # Get reply to flush the request 191 self.__http_response = self.__http.getresponse() 192 self.code = self.__http_response.status 193 self.message = self.__http_response.reason 194 self.headers = self.__http_response.msg 195