|
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
|
|
|
|
|
20
|
|
|
import http.client |
|
21
|
|
|
import os |
|
22
|
|
|
import socket |
|
23
|
|
|
import sys |
|
24
|
|
|
import urllib.request, urllib.parse, urllib.error |
|
25
|
|
|
import urllib.parse |
|
26
|
|
|
import warnings |
|
27
|
|
|
|
|
28
|
|
|
from io import StringIO |
|
29
|
|
|
|
|
30
|
|
|
from .TTransport import * |
|
31
|
|
|
|
|
32
|
|
|
|
|
33
|
|
|
class THttpClient(TTransportBase): |
|
34
|
|
|
"""Http implementation of TTransport base.""" |
|
35
|
|
|
|
|
36
|
|
|
def __init__(self, uri_or_host, port=None, path=None): |
|
37
|
|
|
"""THttpClient supports two different types constructor parameters. |
|
38
|
|
|
|
|
39
|
|
|
THttpClient(host, port, path) - deprecated |
|
40
|
|
|
THttpClient(uri) |
|
41
|
|
|
|
|
42
|
|
|
Only the second supports https. |
|
43
|
|
|
""" |
|
44
|
|
|
if port is not None: |
|
45
|
|
|
warnings.warn( |
|
46
|
|
|
"Please use the THttpClient('http://host:port/path') syntax", |
|
47
|
|
|
DeprecationWarning, |
|
48
|
|
|
stacklevel=2) |
|
49
|
|
|
self.host = uri_or_host |
|
50
|
|
|
self.port = port |
|
51
|
|
|
assert path |
|
52
|
|
|
self.path = path |
|
53
|
|
|
self.scheme = 'http' |
|
54
|
|
|
else: |
|
55
|
|
|
parsed = urllib.parse.urlparse(uri_or_host) |
|
56
|
|
|
self.scheme = parsed.scheme |
|
57
|
|
|
assert self.scheme in ('http', 'https') |
|
58
|
|
|
if self.scheme == 'http': |
|
59
|
|
|
self.port = parsed.port or http.client.HTTP_PORT |
|
60
|
|
|
elif self.scheme == 'https': |
|
61
|
|
|
self.port = parsed.port or http.client.HTTPS_PORT |
|
62
|
|
|
self.host = parsed.hostname |
|
63
|
|
|
self.path = parsed.path |
|
64
|
|
|
if parsed.query: |
|
65
|
|
|
self.path += '?%s' % parsed.query |
|
66
|
|
|
self.__wbuf = StringIO() |
|
67
|
|
|
self.__http = None |
|
68
|
|
|
self.__timeout = None |
|
69
|
|
|
self.__custom_headers = None |
|
70
|
|
|
|
|
71
|
|
|
def open(self): |
|
72
|
|
|
if self.scheme == 'http': |
|
73
|
|
|
self.__http = http.client.HTTP(self.host, self.port) |
|
74
|
|
|
else: |
|
75
|
|
|
self.__http = http.client.HTTPS(self.host, self.port) |
|
76
|
|
|
|
|
77
|
|
|
def close(self): |
|
78
|
|
|
self.__http.close() |
|
79
|
|
|
self.__http = None |
|
80
|
|
|
|
|
81
|
|
|
def isOpen(self): |
|
82
|
|
|
return self.__http is not None |
|
83
|
|
|
|
|
84
|
|
|
def setTimeout(self, ms): |
|
85
|
|
|
if not hasattr(socket, 'getdefaulttimeout'): |
|
86
|
|
|
raise NotImplementedError |
|
87
|
|
|
|
|
88
|
|
|
if ms is None: |
|
89
|
|
|
self.__timeout = None |
|
90
|
|
|
else: |
|
91
|
|
|
self.__timeout = ms / 1000.0 |
|
92
|
|
|
|
|
93
|
|
|
def setCustomHeaders(self, headers): |
|
94
|
|
|
self.__custom_headers = headers |
|
95
|
|
|
|
|
96
|
|
|
def read(self, sz): |
|
97
|
|
|
return self.__http.file.read(sz) |
|
98
|
|
|
|
|
99
|
|
|
def write(self, buf): |
|
100
|
|
|
self.__wbuf.write(buf) |
|
101
|
|
|
|
|
102
|
|
|
def __withTimeout(f): |
|
103
|
|
|
def _f(*args, **kwargs): |
|
104
|
|
|
orig_timeout = socket.getdefaulttimeout() |
|
105
|
|
|
socket.setdefaulttimeout(args[0].__timeout) |
|
106
|
|
|
result = f(*args, **kwargs) |
|
107
|
|
|
socket.setdefaulttimeout(orig_timeout) |
|
108
|
|
|
return result |
|
109
|
|
|
return _f |
|
110
|
|
|
|
|
111
|
|
|
def flush(self): |
|
112
|
|
|
if self.isOpen(): |
|
113
|
|
|
self.close() |
|
114
|
|
|
self.open() |
|
115
|
|
|
|
|
116
|
|
|
# Pull data out of buffer |
|
117
|
|
|
data = self.__wbuf.getvalue() |
|
118
|
|
|
self.__wbuf = StringIO() |
|
119
|
|
|
|
|
120
|
|
|
# HTTP request |
|
121
|
|
|
self.__http.putrequest('POST', self.path) |
|
122
|
|
|
|
|
123
|
|
|
# Write headers |
|
124
|
|
|
self.__http.putheader('Host', self.host) |
|
125
|
|
|
self.__http.putheader('Content-Type', 'application/x-thrift') |
|
126
|
|
|
self.__http.putheader('Content-Length', str(len(data))) |
|
127
|
|
|
|
|
128
|
|
|
if not self.__custom_headers or 'User-Agent' not in self.__custom_headers: |
|
129
|
|
|
user_agent = 'Python/THttpClient' |
|
130
|
|
|
script = os.path.basename(sys.argv[0]) |
|
131
|
|
|
if script: |
|
132
|
|
|
user_agent = '%s (%s)' % (user_agent, urllib.parse.quote(script)) |
|
133
|
|
|
self.__http.putheader('User-Agent', user_agent) |
|
134
|
|
|
|
|
135
|
|
|
if self.__custom_headers: |
|
136
|
|
|
for key, val in self.__custom_headers.items(): |
|
137
|
|
|
self.__http.putheader(key, val) |
|
138
|
|
|
|
|
139
|
|
|
self.__http.endheaders() |
|
140
|
|
|
|
|
141
|
|
|
# Write payload |
|
142
|
|
|
self.__http.send(data) |
|
143
|
|
|
|
|
144
|
|
|
# Get reply to flush the request |
|
145
|
|
|
self.code, self.message, self.headers = self.__http.getreply() |
|
146
|
|
|
|
|
147
|
|
|
# Decorate if we know how to timeout |
|
148
|
|
|
if hasattr(socket, 'getdefaulttimeout'): |
|
149
|
|
|
flush = __withTimeout(flush) |
|
150
|
|
|
|