Skip to content

Commit 0fc7f1b

Browse files
authored
Merge pull request #434 from qiniu/feat/middleware-and-backup-domains
add http client middleware and getting region hosts with backup domains
2 parents 4891d46 + 972bed1 commit 0fc7f1b

File tree

11 files changed

+631
-120
lines changed

11 files changed

+631
-120
lines changed

qiniu/auth.py

+33-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# -*- coding: utf-8 -*-
22
import base64
3+
from datetime import datetime
34
import hmac
5+
import os
46
import time
57
from hashlib import sha1
68
from requests.auth import AuthBase
@@ -242,6 +244,14 @@ def __token(self, data):
242244
hashed = hmac.new(self.__secret_key, data, sha1)
243245
return urlsafe_base64_encode(hashed.digest())
244246

247+
@property
248+
def should_sign_with_timestamp(self):
249+
if self.disable_qiniu_timestamp_signature is not None:
250+
return not self.disable_qiniu_timestamp_signature
251+
if os.getenv('DISABLE_QINIU_TIMESTAMP_SIGNATURE', '').lower() == 'true':
252+
return False
253+
return True
254+
245255
def token_of_request(
246256
self,
247257
method,
@@ -294,12 +304,12 @@ def token_of_request(
294304
return '{0}:{1}'.format(self.__access_key, self.__token(data))
295305

296306
def qiniu_headers(self, headers):
297-
qiniu_fields = list(filter(
298-
lambda k: k.startswith(self.qiniu_header_prefix) and len(k) > len(self.qiniu_header_prefix),
299-
headers,
300-
))
301-
return "\n".join([
302-
"%s: %s" % (canonical_mime_header_key(key), headers.get(key)) for key in sorted(qiniu_fields)
307+
qiniu_fields = [
308+
key for key in headers
309+
if key.startswith(self.qiniu_header_prefix) and len(key) > len(self.qiniu_header_prefix)
310+
]
311+
return '\n'.join([
312+
'%s: %s' % (canonical_mime_header_key(key), headers.get(key)) for key in sorted(qiniu_fields)
303313
])
304314

305315
@staticmethod
@@ -309,15 +319,30 @@ def __checkKey(access_key, secret_key):
309319

310320

311321
class QiniuMacRequestsAuth(AuthBase):
322+
"""
323+
Attributes:
324+
auth (QiniuMacAuth):
325+
"""
312326
def __init__(self, auth):
327+
"""
328+
Args:
329+
auth (QiniuMacAuth):
330+
"""
313331
self.auth = auth
314332

315333
def __call__(self, r):
316334
if r.headers.get('Content-Type', None) is None:
317335
r.headers['Content-Type'] = 'application/x-www-form-urlencoded'
336+
337+
if self.auth.should_sign_with_timestamp:
338+
x_qiniu_date = datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')
339+
r.headers['X-Qiniu-Date'] = x_qiniu_date
340+
318341
token = self.auth.token_of_request(
319-
r.method, r.headers.get('Host', None),
320-
r.url, self.auth.qiniu_headers(r.headers),
342+
r.method,
343+
r.headers.get('Host', None),
344+
r.url,
345+
self.auth.qiniu_headers(r.headers),
321346
r.headers.get('Content-Type', None),
322347
r.body
323348
)

qiniu/config.py

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
# -*- coding: utf-8 -*-
2-
3-
from qiniu import zone
2+
from qiniu import region
43

54
RS_HOST = 'http://rs.qiniu.com' # 管理操作Host
65
RSF_HOST = 'http://rsf.qbox.me' # 列举操作Host
76
API_HOST = 'http://api.qiniuapi.com' # 数据处理操作Host
8-
UC_HOST = 'https://uc.qbox.me' # 获取空间信息Host
7+
UC_HOST = region.UC_HOST # 获取空间信息Host
98

109
_BLOCK_SIZE = 1024 * 1024 * 4 # 断点续传分块大小,该参数为接口规格,暂不支持修改
1110

1211
_config = {
13-
'default_zone': zone.Zone(),
12+
'default_zone': region.Region(),
1413
'default_rs_host': RS_HOST,
1514
'default_rsf_host': RSF_HOST,
1615
'default_api_host': API_HOST,
1716
'default_uc_host': UC_HOST,
17+
'default_uc_backup_hosts': [
18+
'kodo-config.qiniuapi.com',
19+
'api.qiniu.com'
20+
],
21+
'default_uc_backup_retry_times': 2,
1822
'connection_timeout': 30, # 链接超时为时间为30s
1923
'connection_retries': 3, # 链接重试次数为3次
2024
'connection_pool': 10, # 链接池个数为10
@@ -27,6 +31,8 @@
2731
'default_rsf_host': False,
2832
'default_api_host': False,
2933
'default_uc_host': False,
34+
'default_uc_backup_hosts': False,
35+
'default_uc_backup_retry_times': False,
3036
'connection_timeout': False,
3137
'connection_retries': False,
3238
'connection_pool': False,
@@ -45,7 +51,8 @@ def get_default(key):
4551
def set_default(
4652
default_zone=None, connection_retries=None, connection_pool=None,
4753
connection_timeout=None, default_rs_host=None, default_uc_host=None,
48-
default_rsf_host=None, default_api_host=None, default_upload_threshold=None):
54+
default_rsf_host=None, default_api_host=None, default_upload_threshold=None,
55+
default_uc_backup_hosts=None, default_uc_backup_retry_times=None):
4956
if default_zone:
5057
_config['default_zone'] = default_zone
5158
_is_customized_default['default_zone'] = True
@@ -61,6 +68,14 @@ def set_default(
6168
if default_uc_host:
6269
_config['default_uc_host'] = default_uc_host
6370
_is_customized_default['default_uc_host'] = True
71+
_config['default_uc_backup_hosts'] = []
72+
_is_customized_default['default_uc_backup_hosts'] = True
73+
if default_uc_backup_hosts:
74+
_config['default_uc_backup_hosts'] = default_uc_backup_hosts
75+
_is_customized_default['default_uc_backup_hosts'] = True
76+
if default_uc_backup_retry_times:
77+
_config['default_uc_backup_retry_times'] = default_uc_backup_retry_times
78+
_is_customized_default['default_uc_backup_retry_times'] = True
6479
if connection_retries:
6580
_config['connection_retries'] = connection_retries
6681
_is_customized_default['connection_retries'] = True

qiniu/http.py renamed to qiniu/http/__init__.py

+22-100
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
# -*- coding: utf-8 -*-
22
import logging
3-
import os
43
import platform
5-
from datetime import datetime
64

75
import requests
6+
from requests.adapters import HTTPAdapter
87
from requests.auth import AuthBase
98

10-
from qiniu.compat import is_py2, is_py3
11-
from qiniu import config
9+
from qiniu import config, __version__
1210
import qiniu.auth
13-
from . import __version__
11+
12+
from .client import HTTPClient
13+
from .response import ResponseInfo
14+
from .middleware import UserAgentMiddleware
15+
16+
17+
qn_http_client = HTTPClient(
18+
middlewares=[
19+
UserAgentMiddleware(__version__)
20+
]
21+
)
22+
1423

1524
_sys_info = '{0}; {1}'.format(platform.system(), platform.machine())
1625
_python_ver = platform.python_version()
@@ -22,19 +31,6 @@
2231
_headers = {'User-Agent': USER_AGENT}
2332

2433

25-
def __add_auth_headers(headers, auth):
26-
x_qiniu_date = datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')
27-
if auth.disable_qiniu_timestamp_signature is not None:
28-
if not auth.disable_qiniu_timestamp_signature:
29-
headers['X-Qiniu-Date'] = x_qiniu_date
30-
elif os.getenv('DISABLE_QINIU_TIMESTAMP_SIGNATURE'):
31-
if os.getenv('DISABLE_QINIU_TIMESTAMP_SIGNATURE').lower() != 'true':
32-
headers['X-Qiniu-Date'] = x_qiniu_date
33-
else:
34-
headers['X-Qiniu-Date'] = x_qiniu_date
35-
return headers
36-
37-
3834
def __return_wrapper(resp):
3935
if resp.status_code != 200 or resp.headers.get('X-Reqid') is None:
4036
return None, ResponseInfo(resp)
@@ -48,14 +44,15 @@ def __return_wrapper(resp):
4844

4945

5046
def _init():
51-
session = requests.Session()
52-
adapter = requests.adapters.HTTPAdapter(
47+
global _session
48+
if _session is None:
49+
_session = qn_http_client.session
50+
51+
adapter = HTTPAdapter(
5352
pool_connections=config.get_default('connection_pool'),
5453
pool_maxsize=config.get_default('connection_pool'),
5554
max_retries=config.get_default('connection_retries'))
56-
session.mount('http://', adapter)
57-
global _session
58-
_session = session
55+
_session.mount('http://', adapter)
5956

6057

6158
def _post(url, data, files, auth, headers=None):
@@ -170,18 +167,16 @@ def _post_with_qiniu_mac(url, data, auth):
170167
qn_auth = qiniu.auth.QiniuMacRequestsAuth(
171168
auth
172169
) if auth is not None else None
173-
headers = __add_auth_headers({}, auth)
174170

175-
return _post(url, data, None, qn_auth, headers=headers)
171+
return _post(url, data, None, qn_auth)
176172

177173

178174
def _get_with_qiniu_mac(url, params, auth):
179175
qn_auth = qiniu.auth.QiniuMacRequestsAuth(
180176
auth
181177
) if auth is not None else None
182-
headers = __add_auth_headers({}, auth)
183178

184-
return _get(url, params, qn_auth, headers=headers)
179+
return _get(url, params, qn_auth)
185180

186181

187182
def _get_with_qiniu_mac_and_headers(url, params, auth, headers):
@@ -229,76 +224,3 @@ def _delete_with_qiniu_mac_and_headers(url, params, auth, headers):
229224
except Exception as e:
230225
return None, ResponseInfo(None, e)
231226
return __return_wrapper(r)
232-
233-
234-
class ResponseInfo(object):
235-
"""七牛HTTP请求返回信息类
236-
237-
该类主要是用于获取和解析对七牛发起各种请求后的响应包的header和body。
238-
239-
Attributes:
240-
status_code: 整数变量,响应状态码
241-
text_body: 字符串变量,响应的body
242-
req_id: 字符串变量,七牛HTTP扩展字段,参考 https://developer.qiniu.com/kodo/3924/common-request-headers
243-
x_log: 字符串变量,七牛HTTP扩展字段,参考 https://developer.qiniu.com/kodo/3924/common-request-headers
244-
error: 字符串变量,响应的错误内容
245-
"""
246-
247-
def __init__(self, response, exception=None):
248-
"""用响应包和异常信息初始化ResponseInfo类"""
249-
self.__response = response
250-
self.exception = exception
251-
if response is None:
252-
self.status_code = -1
253-
self.text_body = None
254-
self.req_id = None
255-
self.x_log = None
256-
self.error = str(exception)
257-
else:
258-
self.status_code = response.status_code
259-
self.text_body = response.text
260-
self.req_id = response.headers.get('X-Reqid')
261-
self.x_log = response.headers.get('X-Log')
262-
if self.status_code >= 400:
263-
if self.__check_json(response):
264-
ret = response.json() if response.text != '' else None
265-
if ret is None:
266-
self.error = 'unknown'
267-
else:
268-
self.error = response.text
269-
else:
270-
self.error = response.text
271-
if self.req_id is None and self.status_code == 200:
272-
self.error = 'server is not qiniu'
273-
274-
def ok(self):
275-
return self.status_code == 200 and self.req_id is not None
276-
277-
def need_retry(self):
278-
if self.__response is None or self.req_id is None:
279-
return True
280-
code = self.status_code
281-
if (code // 100 == 5 and code != 579) or code == 996:
282-
return True
283-
return False
284-
285-
def connect_failed(self):
286-
return self.__response is None or self.req_id is None
287-
288-
def __str__(self):
289-
if is_py2:
290-
return ', '.join(
291-
['%s:%s' % item for item in self.__dict__.items()]).encode('utf-8')
292-
elif is_py3:
293-
return ', '.join(['%s:%s' %
294-
item for item in self.__dict__.items()])
295-
296-
def __repr__(self):
297-
return self.__str__()
298-
299-
def __check_json(self, reponse):
300-
try:
301-
reponse.json()
302-
return True
303-
except Exception:
304-
return False

0 commit comments

Comments
 (0)