blob: 60ff226a90606292645877345a99884bf55f9941 [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
James E. King III6f8c99e2018-03-24 16:32:02 -040023import ssl
Roger Meier3f5a2642012-04-13 14:20:08 +000024import sys
Bryan Duxbury69720412012-01-03 17:32:30 +000025import warnings
Martin Wilck1ac0a802016-04-27 09:41:03 +020026import base64
Bryan Duxbury69720412012-01-03 17:32:30 +000027
Nobuaki Sukegawa760511f2015-11-06 21:24:16 +090028from six.moves import urllib
29from six.moves import http_client
Mark Sleebd8b9912007-02-27 20:17:00 +000030
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090031from .TTransport import TTransportBase
Nobuaki Sukegawa760511f2015-11-06 21:24:16 +090032import six
Bryan Duxbury69720412012-01-03 17:32:30 +000033
Mark Sleebd8b9912007-02-27 20:17:00 +000034
35class THttpClient(TTransportBase):
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090036 """Http implementation of TTransport base."""
Mark Sleebd8b9912007-02-27 20:17:00 +000037
James E. King III6f8c99e2018-03-24 16:32:02 -040038 def __init__(self, uri_or_host, port=None, path=None, cafile=None, cert_file=None, key_file=None, ssl_context=None):
39 """THttpClient supports two different types of construction:
David Reiss2aa28902009-03-26 06:22:18 +000040
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090041 THttpClient(host, port, path) - deprecated
James E. King III6f8c99e2018-03-24 16:32:02 -040042 THttpClient(uri, [port=<n>, path=<s>, cafile=<filename>, cert_file=<filename>, key_file=<filename>, ssl_context=<context>])
David Reiss2aa28902009-03-26 06:22:18 +000043
James E. King III6f8c99e2018-03-24 16:32:02 -040044 Only the second supports https. To properly authenticate against the server,
45 provide the client's identity by specifying cert_file and key_file. To properly
46 authenticate the server, specify either cafile or ssl_context with a CA defined.
47 NOTE: if both cafile and ssl_context are defined, ssl_context will override cafile.
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090048 """
49 if port is not None:
50 warnings.warn(
James E. King III6f8c99e2018-03-24 16:32:02 -040051 "Please use the THttpClient('http{s}://host:port/path') constructor",
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090052 DeprecationWarning,
53 stacklevel=2)
54 self.host = uri_or_host
55 self.port = port
56 assert path
57 self.path = path
58 self.scheme = 'http'
59 else:
60 parsed = urllib.parse.urlparse(uri_or_host)
61 self.scheme = parsed.scheme
62 assert self.scheme in ('http', 'https')
63 if self.scheme == 'http':
64 self.port = parsed.port or http_client.HTTP_PORT
65 elif self.scheme == 'https':
66 self.port = parsed.port or http_client.HTTPS_PORT
James E. King III6f8c99e2018-03-24 16:32:02 -040067 self.certfile = cert_file
68 self.keyfile = key_file
69 self.context = ssl.create_default_context(cafile=cafile) if (cafile and not ssl_context) else ssl_context
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090070 self.host = parsed.hostname
71 self.path = parsed.path
72 if parsed.query:
73 self.path += '?%s' % parsed.query
Martin Wilck1ac0a802016-04-27 09:41:03 +020074 try:
75 proxy = urllib.request.getproxies()[self.scheme]
76 except KeyError:
77 proxy = None
78 else:
79 if urllib.request.proxy_bypass(self.host):
80 proxy = None
81 if proxy:
82 parsed = urllib.parse.urlparse(proxy)
83 self.realhost = self.host
84 self.realport = self.port
85 self.host = parsed.hostname
86 self.port = parsed.port
87 self.proxy_auth = self.basic_proxy_auth_header(parsed)
88 else:
89 self.realhost = self.realport = self.proxy_auth = None
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090090 self.__wbuf = BytesIO()
91 self.__http = None
92 self.__http_response = None
93 self.__timeout = None
94 self.__custom_headers = None
Mark Sleebd8b9912007-02-27 20:17:00 +000095
Martin Wilck1ac0a802016-04-27 09:41:03 +020096 @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).strip()
103 return "Basic " + cr
104
105 def using_proxy(self):
106 return self.realhost is not None
107
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900108 def open(self):
109 if self.scheme == 'http':
James E. King III6f8c99e2018-03-24 16:32:02 -0400110 self.__http = http_client.HTTPConnection(self.host, self.port,
111 timeout=self.__timeout)
Martin Wilck1ac0a802016-04-27 09:41:03 +0200112 elif self.scheme == 'https':
James E. King III6f8c99e2018-03-24 16:32:02 -0400113 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})
Mark Sleebd8b9912007-02-27 20:17:00 +0000121
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900122 def close(self):
123 self.__http.close()
124 self.__http = None
125 self.__http_response = None
David Reiss0c90f6f2008-02-06 22:18:40 +0000126
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900127 def isOpen(self):
128 return self.__http is not None
Mark Sleebd8b9912007-02-27 20:17:00 +0000129
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900130 def setTimeout(self, ms):
131 if not hasattr(socket, 'getdefaulttimeout'):
132 raise NotImplementedError
David Reissff3d2492010-03-09 05:19:16 +0000133
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900134 if ms is None:
135 self.__timeout = None
136 else:
137 self.__timeout = ms / 1000.0
David Reissff3d2492010-03-09 05:19:16 +0000138
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900139 def setCustomHeaders(self, headers):
140 self.__custom_headers = headers
Roger Meierfa392e92012-04-11 22:15:15 +0000141
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900142 def read(self, sz):
143 return self.__http_response.read(sz)
Mark Sleebd8b9912007-02-27 20:17:00 +0000144
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900145 def write(self, buf):
146 self.__wbuf.write(buf)
Mark Sleebd8b9912007-02-27 20:17:00 +0000147
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900148 def __withTimeout(f):
149 def _f(*args, **kwargs):
150 orig_timeout = socket.getdefaulttimeout()
151 socket.setdefaulttimeout(args[0].__timeout)
152 try:
153 result = f(*args, **kwargs)
154 finally:
155 socket.setdefaulttimeout(orig_timeout)
156 return result
157 return _f
David Reissff3d2492010-03-09 05:19:16 +0000158
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900159 def flush(self):
160 if self.isOpen():
161 self.close()
162 self.open()
David Reiss7c1f6f82009-03-24 20:10:24 +0000163
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900164 # Pull data out of buffer
165 data = self.__wbuf.getvalue()
166 self.__wbuf = BytesIO()
Mark Sleebd8b9912007-02-27 20:17:00 +0000167
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900168 # HTTP request
Martin Wilck1ac0a802016-04-27 09:41:03 +0200169 if self.using_proxy() and self.scheme == "http":
170 # need full URL of real host for HTTP proxy here (HTTPS uses CONNECT tunnel)
171 self.__http.putrequest('POST', "http://%s:%s%s" %
172 (self.realhost, self.realport, self.path))
173 else:
174 self.__http.putrequest('POST', self.path)
Mark Sleebd8b9912007-02-27 20:17:00 +0000175
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900176 # Write headers
177 self.__http.putheader('Content-Type', 'application/x-thrift')
178 self.__http.putheader('Content-Length', str(len(data)))
Martin Wilck1ac0a802016-04-27 09:41:03 +0200179 if self.using_proxy() and self.scheme == "http" and self.proxy_auth is not None:
180 self.__http.putheader("Proxy-Authorization", self.proxy_auth)
Roger Meierfa392e92012-04-11 22:15:15 +0000181
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900182 if not self.__custom_headers or 'User-Agent' not in self.__custom_headers:
183 user_agent = 'Python/THttpClient'
184 script = os.path.basename(sys.argv[0])
185 if script:
186 user_agent = '%s (%s)' % (user_agent, urllib.parse.quote(script))
187 self.__http.putheader('User-Agent', user_agent)
Roger Meier3f5a2642012-04-13 14:20:08 +0000188
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900189 if self.__custom_headers:
190 for key, val in six.iteritems(self.__custom_headers):
191 self.__http.putheader(key, val)
Roger Meierfa392e92012-04-11 22:15:15 +0000192
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900193 self.__http.endheaders()
Mark Sleebd8b9912007-02-27 20:17:00 +0000194
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900195 # Write payload
196 self.__http.send(data)
Mark Sleebd8b9912007-02-27 20:17:00 +0000197
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900198 # Get reply to flush the request
199 self.__http_response = self.__http.getresponse()
200 self.code = self.__http_response.status
201 self.message = self.__http_response.reason
202 self.headers = self.__http_response.msg
David Reissff3d2492010-03-09 05:19:16 +0000203
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900204 # Decorate if we know how to timeout
205 if hasattr(socket, 'getdefaulttimeout'):
206 flush = __withTimeout(flush)