blob: fb33421d75c51b80b9a27078772b365308d52698 [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
Bryan Duxbury69720412012-01-03 17:32:30 +000022import socket
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
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090037 def __init__(self, uri_or_host, port=None, path=None):
38 """THttpClient supports two different types constructor parameters.
David Reiss2aa28902009-03-26 06:22:18 +000039
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090040 THttpClient(host, port, path) - deprecated
41 THttpClient(uri)
David Reiss2aa28902009-03-26 06:22:18 +000042
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090043 Only the second supports https.
44 """
45 if port is not None:
46 warnings.warn(
47 "Please use the THttpClient('http://host:port/path') syntax",
48 DeprecationWarning,
49 stacklevel=2)
50 self.host = uri_or_host
51 self.port = port
52 assert path
53 self.path = path
54 self.scheme = 'http'
55 else:
56 parsed = urllib.parse.urlparse(uri_or_host)
57 self.scheme = parsed.scheme
58 assert self.scheme in ('http', 'https')
59 if self.scheme == 'http':
60 self.port = parsed.port or http_client.HTTP_PORT
61 elif self.scheme == 'https':
62 self.port = parsed.port or http_client.HTTPS_PORT
63 self.host = parsed.hostname
64 self.path = parsed.path
65 if parsed.query:
66 self.path += '?%s' % parsed.query
Martin Wilck1ac0a802016-04-27 09:41:03 +020067 try:
68 proxy = urllib.request.getproxies()[self.scheme]
69 except KeyError:
70 proxy = None
71 else:
72 if urllib.request.proxy_bypass(self.host):
73 proxy = None
74 if proxy:
75 parsed = urllib.parse.urlparse(proxy)
76 self.realhost = self.host
77 self.realport = self.port
78 self.host = parsed.hostname
79 self.port = parsed.port
80 self.proxy_auth = self.basic_proxy_auth_header(parsed)
81 else:
82 self.realhost = self.realport = self.proxy_auth = None
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090083 self.__wbuf = BytesIO()
84 self.__http = None
85 self.__http_response = None
86 self.__timeout = None
87 self.__custom_headers = None
Mark Sleebd8b9912007-02-27 20:17:00 +000088
Martin Wilck1ac0a802016-04-27 09:41:03 +020089 @staticmethod
90 def basic_proxy_auth_header(proxy):
91 if proxy is None or not proxy.username:
92 return None
93 ap = "%s:%s" % (urllib.parse.unquote(proxy.username),
94 urllib.parse.unquote(proxy.password))
95 cr = base64.b64encode(ap).strip()
96 return "Basic " + cr
97
98 def using_proxy(self):
99 return self.realhost is not None
100
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900101 def open(self):
102 if self.scheme == 'http':
103 self.__http = http_client.HTTPConnection(self.host, self.port)
Martin Wilck1ac0a802016-04-27 09:41:03 +0200104 elif self.scheme == 'https':
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900105 self.__http = http_client.HTTPSConnection(self.host, self.port)
Martin Wilck1ac0a802016-04-27 09:41:03 +0200106 if self.using_proxy():
107 self.__http.set_tunnel(self.realhost, self.realport,
Nobuaki Sukegawa042ce7e2016-09-28 09:47:05 +0900108 {"Proxy-Authorization": self.proxy_auth})
Mark Sleebd8b9912007-02-27 20:17:00 +0000109
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900110 def close(self):
111 self.__http.close()
112 self.__http = None
113 self.__http_response = None
David Reiss0c90f6f2008-02-06 22:18:40 +0000114
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900115 def isOpen(self):
116 return self.__http is not None
Mark Sleebd8b9912007-02-27 20:17:00 +0000117
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900118 def setTimeout(self, ms):
119 if not hasattr(socket, 'getdefaulttimeout'):
120 raise NotImplementedError
David Reissff3d2492010-03-09 05:19:16 +0000121
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900122 if ms is None:
123 self.__timeout = None
124 else:
125 self.__timeout = ms / 1000.0
David Reissff3d2492010-03-09 05:19:16 +0000126
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900127 def setCustomHeaders(self, headers):
128 self.__custom_headers = headers
Roger Meierfa392e92012-04-11 22:15:15 +0000129
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900130 def read(self, sz):
131 return self.__http_response.read(sz)
Mark Sleebd8b9912007-02-27 20:17:00 +0000132
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900133 def write(self, buf):
134 self.__wbuf.write(buf)
Mark Sleebd8b9912007-02-27 20:17:00 +0000135
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900136 def __withTimeout(f):
137 def _f(*args, **kwargs):
138 orig_timeout = socket.getdefaulttimeout()
139 socket.setdefaulttimeout(args[0].__timeout)
140 try:
141 result = f(*args, **kwargs)
142 finally:
143 socket.setdefaulttimeout(orig_timeout)
144 return result
145 return _f
David Reissff3d2492010-03-09 05:19:16 +0000146
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900147 def flush(self):
148 if self.isOpen():
149 self.close()
150 self.open()
David Reiss7c1f6f82009-03-24 20:10:24 +0000151
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900152 # Pull data out of buffer
153 data = self.__wbuf.getvalue()
154 self.__wbuf = BytesIO()
Mark Sleebd8b9912007-02-27 20:17:00 +0000155
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900156 # HTTP request
Martin Wilck1ac0a802016-04-27 09:41:03 +0200157 if self.using_proxy() and self.scheme == "http":
158 # need full URL of real host for HTTP proxy here (HTTPS uses CONNECT tunnel)
159 self.__http.putrequest('POST', "http://%s:%s%s" %
160 (self.realhost, self.realport, self.path))
161 else:
162 self.__http.putrequest('POST', self.path)
Mark Sleebd8b9912007-02-27 20:17:00 +0000163
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900164 # Write headers
165 self.__http.putheader('Content-Type', 'application/x-thrift')
166 self.__http.putheader('Content-Length', str(len(data)))
Martin Wilck1ac0a802016-04-27 09:41:03 +0200167 if self.using_proxy() and self.scheme == "http" and self.proxy_auth is not None:
168 self.__http.putheader("Proxy-Authorization", self.proxy_auth)
Roger Meierfa392e92012-04-11 22:15:15 +0000169
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900170 if not self.__custom_headers or 'User-Agent' not in self.__custom_headers:
171 user_agent = 'Python/THttpClient'
172 script = os.path.basename(sys.argv[0])
173 if script:
174 user_agent = '%s (%s)' % (user_agent, urllib.parse.quote(script))
175 self.__http.putheader('User-Agent', user_agent)
Roger Meier3f5a2642012-04-13 14:20:08 +0000176
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900177 if self.__custom_headers:
178 for key, val in six.iteritems(self.__custom_headers):
179 self.__http.putheader(key, val)
Roger Meierfa392e92012-04-11 22:15:15 +0000180
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900181 self.__http.endheaders()
Mark Sleebd8b9912007-02-27 20:17:00 +0000182
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900183 # Write payload
184 self.__http.send(data)
Mark Sleebd8b9912007-02-27 20:17:00 +0000185
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900186 # Get reply to flush the request
187 self.__http_response = self.__http.getresponse()
188 self.code = self.__http_response.status
189 self.message = self.__http_response.reason
190 self.headers = self.__http_response.msg
David Reissff3d2492010-03-09 05:19:16 +0000191
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900192 # Decorate if we know how to timeout
193 if hasattr(socket, 'getdefaulttimeout'):
194 flush = __withTimeout(flush)