aboutsummaryrefslogtreecommitdiff
path: root/requests/sessions.py
diff options
context:
space:
mode:
Diffstat (limited to 'requests/sessions.py')
-rw-r--r--requests/sessions.py200
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):