blob: 37b0a4d8ddc8660fb157e2d483244773de35e143 [file] [log] [blame]
Mark Slee89e2bb82007-03-01 00:20:36 +00001#
David Reissea2cba82009-03-30 21:35:00 +00002# 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#
Mark Slee89e2bb82007-03-01 00:20:36 +000019
Nobuaki Sukegawa760511f2015-11-06 21:24:16 +090020from io import BytesIO
Roger Meier3f5a2642012-04-13 14:20:08 +000021import os
James E. King III6f8c99e2018-03-24 16:32:02 -040022import ssl
Roger Meier3f5a2642012-04-13 14:20:08 +000023import sys
Bryan Duxbury69720412012-01-03 17:32:30 +000024import warnings
Martin Wilck1ac0a802016-04-27 09:41:03 +020025import base64
Bryan Duxbury69720412012-01-03 17:32:30 +000026
Nobuaki Sukegawa760511f2015-11-06 21:24:16 +090027from six.moves import urllib
28from six.moves import http_client
Mark Sleebd8b9912007-02-27 20:17:00 +000029
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090030from .TTransport import TTransportBase
Nobuaki Sukegawa760511f2015-11-06 21:24:16 +090031import six
Bryan Duxbury69720412012-01-03 17:32:30 +000032
Mark Sleebd8b9912007-02-27 20:17:00 +000033
34class THttpClient(TTransportBase):
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090035 """Http implementation of TTransport base."""
Mark Sleebd8b9912007-02-27 20:17:00 +000036
James E. King III6f8c99e2018-03-24 16:32:02 -040037 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:
David Reiss2aa28902009-03-26 06:22:18 +000039
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090040 THttpClient(host, port, path) - deprecated
James E. King III6f8c99e2018-03-24 16:32:02 -040041 THttpClient(uri, [port=<n>, path=<s>, cafile=<filename>, cert_file=<filename>, key_file=<filename>, ssl_context=<context>])
David Reiss2aa28902009-03-26 06:22:18 +000042
James E. King III6f8c99e2018-03-24 16:32:02 -040043 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.
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090047 """
48 if port is not None:
49 warnings.warn(
James E. King III6f8c99e2018-03-24 16:32:02 -040050 "Please use the THttpClient('http{s}://host:port/path') constructor",
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090051 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
James E. King III6f8c99e2018-03-24 16:32:02 -040066 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
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090069 self.host = parsed.hostname
70 self.path = parsed.path
71 if parsed.query:
72 self.path += '?%s' % parsed.query
Martin Wilck1ac0a802016-04-27 09:41:03 +020073 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
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090089 self.__wbuf = BytesIO()
90 self.__http = None
91 self.__http_response = None
92 self.__timeout = None
93 self.__custom_headers = None
Mark Sleebd8b9912007-02-27 20:17:00 +000094
Martin Wilck1ac0a802016-04-27 09:41:03 +020095 @staticmethod
96 def basic_proxy_auth_header(proxy):
97 if proxy is None or not proxy.username:
98 return None
99 ap = "%s:%s" % (urllib.parse.unquote(proxy.username),
100 urllib.parse.unquote(proxy.password))
101 cr = base64.b64encode(ap).strip()
102 return "Basic " + cr
103
104 def using_proxy(self):
105 return self.realhost is not None
106
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900107 def open(self):
108 if self.scheme == 'http':
James E. King III6f8c99e2018-03-24 16:32:02 -0400109 self.__http = http_client.HTTPConnection(self.host, self.port,
110 timeout=self.__timeout)
Martin Wilck1ac0a802016-04-27 09:41:03 +0200111 elif self.scheme == 'https':
James E. King III6f8c99e2018-03-24 16:32:02 -0400112 self.__http = http_client.HTTPSConnection(self.host, self.port,
113 key_file=self.keyfile,
114 cert_file=self.certfile,
115 timeout=self.__timeout,
116 context=self.context)
117 if self.using_proxy():
118 self.__http.set_tunnel(self.realhost, self.realport,
119 {"Proxy-Authorization": self.proxy_auth})
Mark Sleebd8b9912007-02-27 20:17:00 +0000120
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900121 def close(self):
122 self.__http.close()
123 self.__http = None
124 self.__http_response = None
David Reiss0c90f6f2008-02-06 22:18:40 +0000125
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900126 def isOpen(self):
127 return self.__http is not None
Mark Sleebd8b9912007-02-27 20:17:00 +0000128
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900129 def setTimeout(self, ms):
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900130 if ms is None:
131 self.__timeout = None
132 else:
133 self.__timeout = ms / 1000.0
David Reissff3d2492010-03-09 05:19:16 +0000134
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900135 def setCustomHeaders(self, headers):
136 self.__custom_headers = headers
Roger Meierfa392e92012-04-11 22:15:15 +0000137
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900138 def read(self, sz):
139 return self.__http_response.read(sz)
Mark Sleebd8b9912007-02-27 20:17:00 +0000140
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900141 def write(self, buf):
142 self.__wbuf.write(buf)
Mark Sleebd8b9912007-02-27 20:17:00 +0000143
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900144 def flush(self):
145 if self.isOpen():
146 self.close()
147 self.open()
David Reiss7c1f6f82009-03-24 20:10:24 +0000148
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900149 # Pull data out of buffer
150 data = self.__wbuf.getvalue()
151 self.__wbuf = BytesIO()
Mark Sleebd8b9912007-02-27 20:17:00 +0000152
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900153 # HTTP request
Martin Wilck1ac0a802016-04-27 09:41:03 +0200154 if self.using_proxy() and self.scheme == "http":
155 # need full URL of real host for HTTP proxy here (HTTPS uses CONNECT tunnel)
156 self.__http.putrequest('POST', "http://%s:%s%s" %
157 (self.realhost, self.realport, self.path))
158 else:
159 self.__http.putrequest('POST', self.path)
Mark Sleebd8b9912007-02-27 20:17:00 +0000160
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900161 # Write headers
162 self.__http.putheader('Content-Type', 'application/x-thrift')
163 self.__http.putheader('Content-Length', str(len(data)))
Martin Wilck1ac0a802016-04-27 09:41:03 +0200164 if self.using_proxy() and self.scheme == "http" and self.proxy_auth is not None:
165 self.__http.putheader("Proxy-Authorization", self.proxy_auth)
Roger Meierfa392e92012-04-11 22:15:15 +0000166
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900167 if not self.__custom_headers or 'User-Agent' not in self.__custom_headers:
168 user_agent = 'Python/THttpClient'
169 script = os.path.basename(sys.argv[0])
170 if script:
171 user_agent = '%s (%s)' % (user_agent, urllib.parse.quote(script))
172 self.__http.putheader('User-Agent', user_agent)
Roger Meier3f5a2642012-04-13 14:20:08 +0000173
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900174 if self.__custom_headers:
175 for key, val in six.iteritems(self.__custom_headers):
176 self.__http.putheader(key, val)
Roger Meierfa392e92012-04-11 22:15:15 +0000177
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900178 self.__http.endheaders()
Mark Sleebd8b9912007-02-27 20:17:00 +0000179
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900180 # Write payload
181 self.__http.send(data)
Mark Sleebd8b9912007-02-27 20:17:00 +0000182
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900183 # Get reply to flush the request
184 self.__http_response = self.__http.getresponse()
185 self.code = self.__http_response.status
186 self.message = self.__http_response.reason
187 self.headers = self.__http_response.msg