Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sugar_network/toolkit/http.py
diff options
context:
space:
mode:
Diffstat (limited to 'sugar_network/toolkit/http.py')
-rw-r--r--sugar_network/toolkit/http.py124
1 files changed, 70 insertions, 54 deletions
diff --git a/sugar_network/toolkit/http.py b/sugar_network/toolkit/http.py
index 215ec03..ada63da 100644
--- a/sugar_network/toolkit/http.py
+++ b/sugar_network/toolkit/http.py
@@ -29,6 +29,8 @@ from sugar_network import client, toolkit
from sugar_network.toolkit import coroutine, enforce
+_REDIRECT_CODES = frozenset([301, 302, 303, 307, 308])
+
_logger = logging.getLogger('http')
@@ -45,11 +47,13 @@ class StatusPass(Status):
class NotModified(StatusPass):
status = '304 Not Modified'
+ status_code = 304
class Redirect(StatusPass):
status = '303 See Other'
+ status_code = 303
def __init__(self, location):
StatusPass.__init__(self)
@@ -123,66 +127,66 @@ class Connection(object):
self._session.close()
def exists(self, path):
- response = self.request('GET', path, allowed=[404])
- return response.status_code != 404
+ reply = self.request('GET', path, allowed=[404])
+ return reply.status_code != 404
+
+ def head(self, path_=None, **kwargs):
+ from sugar_network.toolkit.router import Request, Response
+ request = Request(method='HEAD', path=path_, **kwargs)
+ response = Response()
+ self.call(request, response)
+ return response.meta
def get(self, path_=None, query_=None, **kwargs):
- response = self.request('GET', path_, params=kwargs)
- return self._decode_reply(response)
-
- def meta(self, path_=None, query_=None, **kwargs):
- response = self.request('HEAD', path_, params=query_ or kwargs)
- result = {}
- for key, value in response.headers.items():
- if key.startswith('x-sn-'):
- result[key[5:]] = json.loads(value)
- else:
- result[key] = value
- return result
+ reply = self.request('GET', path_, params=query_ or kwargs)
+ return self._decode_reply(reply)
def post(self, path_=None, data_=None, query_=None, **kwargs):
- response = self.request('POST', path_, json.dumps(data_),
+ reply = self.request('POST', path_, json.dumps(data_),
headers={'Content-Type': 'application/json'},
params=query_ or kwargs)
- return self._decode_reply(response)
+ return self._decode_reply(reply)
def put(self, path_=None, data_=None, query_=None, **kwargs):
- response = self.request('PUT', path_, json.dumps(data_),
+ reply = self.request('PUT', path_, json.dumps(data_),
headers={'Content-Type': 'application/json'},
params=query_ or kwargs)
- return self._decode_reply(response)
+ return self._decode_reply(reply)
def delete(self, path_=None, query_=None, **kwargs):
- response = self.request('DELETE', path_, params=query_ or kwargs)
- return self._decode_reply(response)
+ reply = self.request('DELETE', path_, params=query_ or kwargs)
+ return self._decode_reply(reply)
def download(self, path, dst=None):
- response = self.request('GET', path, allow_redirects=True)
+ reply = self.request('GET', path, allow_redirects=True)
- content_length = response.headers.get('Content-Length')
+ content_length = reply.headers.get('Content-Length')
if content_length:
chunk_size = min(int(content_length), toolkit.BUFFER_SIZE)
else:
chunk_size = toolkit.BUFFER_SIZE
if dst is None:
- return response.iter_content(chunk_size=chunk_size)
+ return reply.iter_content(chunk_size=chunk_size)
f = file(dst, 'wb') if isinstance(dst, basestring) else dst
try:
- for chunk in response.iter_content(chunk_size=chunk_size):
+ for chunk in reply.iter_content(chunk_size=chunk_size):
f.write(chunk)
finally:
if isinstance(dst, basestring):
f.close()
def upload(self, path, data, **kwargs):
- with file(data, 'rb') as f:
- response = self.request('POST', path, f, params=kwargs)
- if response.headers.get('Content-Type') == 'application/json':
- return json.loads(response.content)
+ if isinstance(data, basestring):
+ with file(data, 'rb') as f:
+ reply = self.request('POST', path, f, params=kwargs)
+ else:
+ reply = self.request('POST', path, data, params=kwargs)
+ if reply.headers.get('Content-Type') == 'application/json':
+ return json.loads(reply.content)
else:
- return response.raw
+ return reply.raw
def request(self, method, path=None, data=None, headers=None, allowed=None,
params=None, **kwargs):
@@ -198,14 +202,13 @@ class Connection(object):
while True:
a_try += 1
try:
- response = self._session.request(method, path, data=data,
+ reply = self._session.request(method, path, data=data,
headers=headers, params=params, **kwargs)
except SSLError:
_logger.warning('Use --no-check-certificate to avoid checks')
raise
-
- if response.status_code != 200:
- if response.status_code == 401:
+ if reply.status_code != 200:
+ if reply.status_code == 401:
enforce(method not in ('PUT', 'POST') or
not hasattr(data, 'read'),
'Cannot resend data after authentication')
@@ -216,9 +219,9 @@ class Connection(object):
self.post(['user'], self._get_profile())
a_try = 0
continue
- if allowed and response.status_code in allowed:
- return response
- content = response.content
+ if allowed and reply.status_code in allowed:
+ break
+ content = reply.content
try:
error = json.loads(content)['error']
except Exception:
@@ -228,16 +231,17 @@ class Connection(object):
# If so, try to resend request.
if a_try <= self._max_retries and method == 'GET':
continue
- error = content or response.headers.get('x-sn-error') or \
+ error = content or reply.headers.get('x-sn-error') or \
'No error message provided'
- _logger.trace('Request failed, method=%s path=%r params=%r '
+ _logger.debug('Request failed, method=%s path=%r params=%r '
'headers=%r status_code=%s error=%s',
- method, path, params, headers, response.status_code,
+ method, path, params, headers, reply.status_code,
'\n' + error)
- cls = _FORWARD_STATUSES.get(response.status_code, RuntimeError)
+ cls = _FORWARD_STATUSES.get(reply.status_code, RuntimeError)
raise cls(error)
+ break
- return response
+ return reply
def call(self, request, response=None):
if request.content_type == 'application/json':
@@ -268,15 +272,27 @@ class Connection(object):
if value is not None:
headers[key] = value
- reply = self.request(request.method, request.path,
- data=request.content, params=request.query or request,
- headers=headers, allow_redirects=True)
-
- if response is not None:
- if 'transfer-encoding' in reply.headers:
- # `requests` library handles encoding on its own
- del reply.headers['transfer-encoding']
- response.update(reply.headers)
+ path = request.path
+ while True:
+ reply = self.request(request.method, path,
+ data=request.content, params=request.query or request,
+ headers=headers, allowed=_REDIRECT_CODES,
+ allow_redirects=False)
+ resend = reply.status_code in _REDIRECT_CODES
+ if response is not None:
+ if 'transfer-encoding' in reply.headers:
+ # `requests` library handles encoding on its own
+ del reply.headers['transfer-encoding']
+ for key, value in reply.headers.items():
+ if key.startswith('x-sn-'):
+ response.meta[key[5:]] = json.loads(value)
+ elif not resend:
+ response[key] = value
+ if not resend:
+ break
+ path = reply.headers['location']
+ if path.startswith('/'):
+ path = self.api_url + path
if request.method != 'HEAD':
if reply.headers.get('Content-Type') == 'application/json':
@@ -287,11 +303,11 @@ class Connection(object):
def subscribe(self, **condition):
return _Subscription(self, condition)
- def _decode_reply(self, response):
- if response.headers.get('Content-Type') == 'application/json':
- return json.loads(response.content)
+ def _decode_reply(self, reply):
+ if reply.headers.get('Content-Type') == 'application/json':
+ return json.loads(reply.content)
else:
- return response.content
+ return reply.content
class _Subscription(object):