diff options
Diffstat (limited to 'requests/sessions.py')
-rw-r--r-- | requests/sessions.py | 200 |
1 files changed, 154 insertions, 46 deletions
diff --git a/requests/sessions.py b/requests/sessions.py index d65877c..de0d9d6 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -9,11 +9,12 @@ requests (cookies, auth, proxies). """ import os +from datetime import datetime from .compat import cookielib from .cookies import cookiejar_from_dict -from .models import Request -from .hooks import dispatch_hook, default_hooks +from .models import Request, PreparedRequest +from .hooks import default_hooks, dispatch_hook from .utils import from_key_val_list, default_headers from .exceptions import TooManyRedirects, InvalidSchema @@ -23,7 +24,12 @@ from .adapters import HTTPAdapter from .utils import requote_uri, get_environ_proxies, get_netrc_auth from .status_codes import codes -REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved) +REDIRECT_STATI = ( + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_moved, # 307 +) DEFAULT_REDIRECT_LIMIT = 30 @@ -73,11 +79,21 @@ def merge_kwargs(local_kwarg, default_kwarg): class SessionRedirectMixin(object): - - def resolve_redirects(self, resp, req, stream=False, timeout=None, verify=True, cert=None, proxies=None): + def resolve_redirects(self, resp, req, stream=False, timeout=None, + verify=True, cert=None, proxies=None): """Receives a Response. Returns a generator of Responses.""" i = 0 + prepared_request = PreparedRequest() + prepared_request.body = req.body + prepared_request.headers = req.headers.copy() + prepared_request.hooks = req.hooks + prepared_request.method = req.method + prepared_request.url = req.url + + cookiejar = cookiejar_from_dict({}) + cookiejar.update(self.cookies) + cookiejar.update(resp.cookies) # ((resp.status_code is codes.see_other)) while (('location' in resp.headers and resp.status_code in REDIRECT_STATI)): @@ -91,7 +107,7 @@ class SessionRedirectMixin(object): resp.close() url = resp.headers['location'] - method = req.method + method = prepared_request.method # Handle redirection without scheme (see: RFC 1808 Section 4) if url.startswith('//'): @@ -104,38 +120,52 @@ class SessionRedirectMixin(object): # Compliant with RFC3986, we percent encode the url. url = urljoin(resp.url, requote_uri(url)) + prepared_request.url = url + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4 - if resp.status_code is codes.see_other and req.method != 'HEAD': + if (resp.status_code == codes.see_other and + prepared_request.method != 'HEAD'): method = 'GET' # Do what the browsers do, despite standards... - if resp.status_code in (codes.moved, codes.found) and req.method == 'POST': + if (resp.status_code in (codes.moved, codes.found) and + prepared_request.method == 'POST'): method = 'GET' - # Remove the cookie headers that were sent. - headers = req.headers + prepared_request.method = method + + # https://github.com/kennethreitz/requests/issues/1084 + if resp.status_code not in (codes.temporary, codes.resume): + if 'Content-Length' in prepared_request.headers: + del prepared_request.headers['Content-Length'] + + prepared_request.body = None + + headers = prepared_request.headers try: del headers['Cookie'] except KeyError: pass - resp = self.request( - url=url, - method=method, - headers=headers, - auth=req.auth, - cookies=req.cookies, - allow_redirects=False, - stream=stream, - timeout=timeout, - verify=verify, - cert=cert, - proxies=proxies - ) + prepared_request.prepare_cookies(cookiejar) + + resp = self.send( + prepared_request, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + allow_redirects=False, + ) + + cookiejar.update(resp.cookies) i += 1 yield resp + resp.cookies.update(cookiejar) + class Session(SessionRedirectMixin): """A Requests session. @@ -150,6 +180,11 @@ class Session(SessionRedirectMixin): 200 """ + __attrs__ = [ + 'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks', + 'params', 'verify', 'cert', 'prefetch', 'adapters', 'stream', + 'trust_env', 'max_redirects'] + def __init__(self): #: A case-insensitive dictionary of headers to be sent on each @@ -217,6 +252,39 @@ class Session(SessionRedirectMixin): stream=None, verify=None, cert=None): + """Constructs a :class:`Request <Request>`, prepares it and sends it. + Returns :class:`Response <Response>` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query + string for the :class:`Request`. + :param data: (optional) Dictionary or bytes to send in the body of the + :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the + :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the + :class:`Request`. + :param files: (optional) Dictionary of 'filename': file-like-objects + for multipart encoding upload. + :param auth: (optional) Auth tuple or callable to enable + Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) Float describing the timeout of the + request. + :param allow_redirects: (optional) Boolean. Set to True by default. + :param proxies: (optional) Dictionary mapping protocol to the URL of + the proxy. + :param return_response: (optional) If False, an un-sent Request object + will returned. + :param config: (optional) A configuration dictionary. See + ``request.defaults`` for allowed keys and their default values. + :param prefetch: (optional) whether to immediately download the response + content. Defaults to ``True``. + :param verify: (optional) if ``True``, the SSL cert will be verified. + A CA_BUNDLE path can also be provided. + :param cert: (optional) if String, path to ssl client cert file (.pem). + If Tuple, ('cert', 'key') pair. + """ cookies = cookies or {} proxies = proxies or {} @@ -225,9 +293,10 @@ class Session(SessionRedirectMixin): if not isinstance(cookies, cookielib.CookieJar): cookies = cookiejar_from_dict(cookies) - # Bubble down session cookies. - for cookie in self.cookies: - cookies.set_cookie(cookie) + # Merge with session cookies + merged_cookies = self.cookies.copy() + merged_cookies.update(cookies) + cookies = merged_cookies # Gather clues from the surrounding environment. if self.trust_env: @@ -248,7 +317,6 @@ class Session(SessionRedirectMixin): if not verify and verify is not False: verify = os.environ.get('CURL_CA_BUNDLE') - # Merge all the kwargs. params = merge_kwargs(params, self.params) headers = merge_kwargs(headers, self.headers) @@ -259,7 +327,6 @@ class Session(SessionRedirectMixin): verify = merge_kwargs(verify, self.verify) cert = merge_kwargs(cert, self.cert) - # Create the Request. req = Request() req.method = method.upper() @@ -276,26 +343,18 @@ class Session(SessionRedirectMixin): prep = req.prepare() # Send the request. - resp = self.send(prep, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies) + send_kwargs = { + 'stream': stream, + 'timeout': timeout, + 'verify': verify, + 'cert': cert, + 'proxies': proxies, + 'allow_redirects': allow_redirects, + } + resp = self.send(prep, **send_kwargs) # Persist cookies. - for cookie in resp.cookies: - self.cookies.set_cookie(cookie) - - # Redirect resolving generator. - gen = self.resolve_redirects(resp, req, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies) - - # Resolve redirects if allowed. - history = [r for r in gen] if allow_redirects else [] - - # Shuffle things around if there's history. - if history: - history.insert(0, resp) - resp = history.pop() - resp.history = tuple(history) - - # Response manipulation hook. - self.response = dispatch_hook('response', hooks, resp) + self.cookies.update(resp.cookies) return resp @@ -370,8 +429,57 @@ class Session(SessionRedirectMixin): def send(self, request, **kwargs): """Send a given PreparedRequest.""" + # Set defaults that the hooks can utilize to ensure they always have + # the correct parameters to reproduce the previous request. + kwargs.setdefault('stream', self.stream) + kwargs.setdefault('verify', self.verify) + kwargs.setdefault('cert', self.cert) + kwargs.setdefault('proxies', self.proxies) + + # It's possible that users might accidentally send a Request object. + # Guard against that specific failure case. + if getattr(request, 'prepare', None): + raise ValueError('You can only send PreparedRequests.') + + # Set up variables needed for resolve_redirects and dispatching of + # hooks + allow_redirects = kwargs.pop('allow_redirects', True) + stream = kwargs.get('stream') + timeout = kwargs.get('timeout') + verify = kwargs.get('verify') + cert = kwargs.get('cert') + proxies = kwargs.get('proxies') + hooks = request.hooks + + # Get the appropriate adapter to use adapter = self.get_adapter(url=request.url) + + # Start time (approximately) of the request + start = datetime.utcnow() + # Send the request r = adapter.send(request, **kwargs) + # Total elapsed time of the request (approximately) + r.elapsed = datetime.utcnow() - start + + # Response manipulation hooks + r = dispatch_hook('response', hooks, r, **kwargs) + + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, stream=stream, + timeout=timeout, verify=verify, cert=cert, + proxies=proxies) + + # Resolve redirects if allowed. + history = [resp for resp in gen] if allow_redirects else [] + + # Shuffle things around if there's history. + if history: + # Insert the first (original) request at the start + history.insert(0, r) + # Get the last request made + r = history.pop() + r.history = tuple(history) + return r def get_adapter(self, url): |