Path: blob/master/venv/Lib/site-packages/requests/sessions.py
811 views
# -*- coding: utf-8 -*-12"""3requests.session4~~~~~~~~~~~~~~~~56This module provides a Session object to manage and persist settings across7requests (cookies, auth, proxies).8"""9import os10import sys11import time12from datetime import timedelta13from collections import OrderedDict1415from .auth import _basic_auth_str16from .compat import cookielib, is_py3, urljoin, urlparse, Mapping17from .cookies import (18cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)19from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT20from .hooks import default_hooks, dispatch_hook21from ._internal_utils import to_native_string22from .utils import to_key_val_list, default_headers, DEFAULT_PORTS23from .exceptions import (24TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)2526from .structures import CaseInsensitiveDict27from .adapters import HTTPAdapter2829from .utils import (30requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies,31get_auth_from_url, rewind_body32)3334from .status_codes import codes3536# formerly defined here, reexposed here for backward compatibility37from .models import REDIRECT_STATI3839# Preferred clock, based on which one is more accurate on a given system.40if sys.platform == 'win32':41try: # Python 3.4+42preferred_clock = time.perf_counter43except AttributeError: # Earlier than Python 3.44preferred_clock = time.clock45else:46preferred_clock = time.time474849def merge_setting(request_setting, session_setting, dict_class=OrderedDict):50"""Determines appropriate setting for a given request, taking into account51the explicit setting on that request, and the setting in the session. If a52setting is a dictionary, they will be merged together using `dict_class`53"""5455if session_setting is None:56return request_setting5758if request_setting is None:59return session_setting6061# Bypass if not a dictionary (e.g. verify)62if not (63isinstance(session_setting, Mapping) and64isinstance(request_setting, Mapping)65):66return request_setting6768merged_setting = dict_class(to_key_val_list(session_setting))69merged_setting.update(to_key_val_list(request_setting))7071# Remove keys that are set to None. Extract keys first to avoid altering72# the dictionary during iteration.73none_keys = [k for (k, v) in merged_setting.items() if v is None]74for key in none_keys:75del merged_setting[key]7677return merged_setting787980def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):81"""Properly merges both requests and session hooks.8283This is necessary because when request_hooks == {'response': []}, the84merge breaks Session hooks entirely.85"""86if session_hooks is None or session_hooks.get('response') == []:87return request_hooks8889if request_hooks is None or request_hooks.get('response') == []:90return session_hooks9192return merge_setting(request_hooks, session_hooks, dict_class)939495class SessionRedirectMixin(object):9697def get_redirect_target(self, resp):98"""Receives a Response. Returns a redirect URI or ``None``"""99# Due to the nature of how requests processes redirects this method will100# be called at least once upon the original response and at least twice101# on each subsequent redirect response (if any).102# If a custom mixin is used to handle this logic, it may be advantageous103# to cache the redirect location onto the response object as a private104# attribute.105if resp.is_redirect:106location = resp.headers['location']107# Currently the underlying http module on py3 decode headers108# in latin1, but empirical evidence suggests that latin1 is very109# rarely used with non-ASCII characters in HTTP headers.110# It is more likely to get UTF8 header rather than latin1.111# This causes incorrect handling of UTF8 encoded location headers.112# To solve this, we re-encode the location in latin1.113if is_py3:114location = location.encode('latin1')115return to_native_string(location, 'utf8')116return None117118def should_strip_auth(self, old_url, new_url):119"""Decide whether Authorization header should be removed when redirecting"""120old_parsed = urlparse(old_url)121new_parsed = urlparse(new_url)122if old_parsed.hostname != new_parsed.hostname:123return True124# Special case: allow http -> https redirect when using the standard125# ports. This isn't specified by RFC 7235, but is kept to avoid126# breaking backwards compatibility with older versions of requests127# that allowed any redirects on the same host.128if (old_parsed.scheme == 'http' and old_parsed.port in (80, None)129and new_parsed.scheme == 'https' and new_parsed.port in (443, None)):130return False131132# Handle default port usage corresponding to scheme.133changed_port = old_parsed.port != new_parsed.port134changed_scheme = old_parsed.scheme != new_parsed.scheme135default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)136if (not changed_scheme and old_parsed.port in default_port137and new_parsed.port in default_port):138return False139140# Standard case: root URI must match141return changed_port or changed_scheme142143def resolve_redirects(self, resp, req, stream=False, timeout=None,144verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs):145"""Receives a Response. Returns a generator of Responses or Requests."""146147hist = [] # keep track of history148149url = self.get_redirect_target(resp)150previous_fragment = urlparse(req.url).fragment151while url:152prepared_request = req.copy()153154# Update history and keep track of redirects.155# resp.history must ignore the original request in this loop156hist.append(resp)157resp.history = hist[1:]158159try:160resp.content # Consume socket so it can be released161except (ChunkedEncodingError, ContentDecodingError, RuntimeError):162resp.raw.read(decode_content=False)163164if len(resp.history) >= self.max_redirects:165raise TooManyRedirects('Exceeded {} redirects.'.format(self.max_redirects), response=resp)166167# Release the connection back into the pool.168resp.close()169170# Handle redirection without scheme (see: RFC 1808 Section 4)171if url.startswith('//'):172parsed_rurl = urlparse(resp.url)173url = ':'.join([to_native_string(parsed_rurl.scheme), url])174175# Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)176parsed = urlparse(url)177if parsed.fragment == '' and previous_fragment:178parsed = parsed._replace(fragment=previous_fragment)179elif parsed.fragment:180previous_fragment = parsed.fragment181url = parsed.geturl()182183# Facilitate relative 'location' headers, as allowed by RFC 7231.184# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')185# Compliant with RFC3986, we percent encode the url.186if not parsed.netloc:187url = urljoin(resp.url, requote_uri(url))188else:189url = requote_uri(url)190191prepared_request.url = to_native_string(url)192193self.rebuild_method(prepared_request, resp)194195# https://github.com/psf/requests/issues/1084196if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):197# https://github.com/psf/requests/issues/3490198purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding')199for header in purged_headers:200prepared_request.headers.pop(header, None)201prepared_request.body = None202203headers = prepared_request.headers204headers.pop('Cookie', None)205206# Extract any cookies sent on the response to the cookiejar207# in the new request. Because we've mutated our copied prepared208# request, use the old one that we haven't yet touched.209extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)210merge_cookies(prepared_request._cookies, self.cookies)211prepared_request.prepare_cookies(prepared_request._cookies)212213# Rebuild auth and proxy information.214proxies = self.rebuild_proxies(prepared_request, proxies)215self.rebuild_auth(prepared_request, resp)216217# A failed tell() sets `_body_position` to `object()`. This non-None218# value ensures `rewindable` will be True, allowing us to raise an219# UnrewindableBodyError, instead of hanging the connection.220rewindable = (221prepared_request._body_position is not None and222('Content-Length' in headers or 'Transfer-Encoding' in headers)223)224225# Attempt to rewind consumed file-like object.226if rewindable:227rewind_body(prepared_request)228229# Override the original request.230req = prepared_request231232if yield_requests:233yield req234else:235236resp = self.send(237req,238stream=stream,239timeout=timeout,240verify=verify,241cert=cert,242proxies=proxies,243allow_redirects=False,244**adapter_kwargs245)246247extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)248249# extract redirect url, if any, for the next loop250url = self.get_redirect_target(resp)251yield resp252253def rebuild_auth(self, prepared_request, response):254"""When being redirected we may want to strip authentication from the255request to avoid leaking credentials. This method intelligently removes256and reapplies authentication where possible to avoid credential loss.257"""258headers = prepared_request.headers259url = prepared_request.url260261if 'Authorization' in headers and self.should_strip_auth(response.request.url, url):262# If we get redirected to a new host, we should strip out any263# authentication headers.264del headers['Authorization']265266# .netrc might have more auth for us on our new host.267new_auth = get_netrc_auth(url) if self.trust_env else None268if new_auth is not None:269prepared_request.prepare_auth(new_auth)270271272def rebuild_proxies(self, prepared_request, proxies):273"""This method re-evaluates the proxy configuration by considering the274environment variables. If we are redirected to a URL covered by275NO_PROXY, we strip the proxy configuration. Otherwise, we set missing276proxy keys for this URL (in case they were stripped by a previous277redirect).278279This method also replaces the Proxy-Authorization header where280necessary.281282:rtype: dict283"""284proxies = proxies if proxies is not None else {}285headers = prepared_request.headers286url = prepared_request.url287scheme = urlparse(url).scheme288new_proxies = proxies.copy()289no_proxy = proxies.get('no_proxy')290291bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy)292if self.trust_env and not bypass_proxy:293environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)294295proxy = environ_proxies.get(scheme, environ_proxies.get('all'))296297if proxy:298new_proxies.setdefault(scheme, proxy)299300if 'Proxy-Authorization' in headers:301del headers['Proxy-Authorization']302303try:304username, password = get_auth_from_url(new_proxies[scheme])305except KeyError:306username, password = None, None307308if username and password:309headers['Proxy-Authorization'] = _basic_auth_str(username, password)310311return new_proxies312313def rebuild_method(self, prepared_request, response):314"""When being redirected we may want to change the method of the request315based on certain specs or browser behavior.316"""317method = prepared_request.method318319# https://tools.ietf.org/html/rfc7231#section-6.4.4320if response.status_code == codes.see_other and method != 'HEAD':321method = 'GET'322323# Do what the browsers do, despite standards...324# First, turn 302s into GETs.325if response.status_code == codes.found and method != 'HEAD':326method = 'GET'327328# Second, if a POST is responded to with a 301, turn it into a GET.329# This bizarre behaviour is explained in Issue 1704.330if response.status_code == codes.moved and method == 'POST':331method = 'GET'332333prepared_request.method = method334335336class Session(SessionRedirectMixin):337"""A Requests session.338339Provides cookie persistence, connection-pooling, and configuration.340341Basic Usage::342343>>> import requests344>>> s = requests.Session()345>>> s.get('https://httpbin.org/get')346<Response [200]>347348Or as a context manager::349350>>> with requests.Session() as s:351... s.get('https://httpbin.org/get')352<Response [200]>353"""354355__attrs__ = [356'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify',357'cert', 'adapters', 'stream', 'trust_env',358'max_redirects',359]360361def __init__(self):362363#: A case-insensitive dictionary of headers to be sent on each364#: :class:`Request <Request>` sent from this365#: :class:`Session <Session>`.366self.headers = default_headers()367368#: Default Authentication tuple or object to attach to369#: :class:`Request <Request>`.370self.auth = None371372#: Dictionary mapping protocol or protocol and host to the URL of the proxy373#: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to374#: be used on each :class:`Request <Request>`.375self.proxies = {}376377#: Event-handling hooks.378self.hooks = default_hooks()379380#: Dictionary of querystring data to attach to each381#: :class:`Request <Request>`. The dictionary values may be lists for382#: representing multivalued query parameters.383self.params = {}384385#: Stream response content default.386self.stream = False387388#: SSL Verification default.389self.verify = True390391#: SSL client certificate default, if String, path to ssl client392#: cert file (.pem). If Tuple, ('cert', 'key') pair.393self.cert = None394395#: Maximum number of redirects allowed. If the request exceeds this396#: limit, a :class:`TooManyRedirects` exception is raised.397#: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is398#: 30.399self.max_redirects = DEFAULT_REDIRECT_LIMIT400401#: Trust environment settings for proxy configuration, default402#: authentication and similar.403self.trust_env = True404405#: A CookieJar containing all currently outstanding cookies set on this406#: session. By default it is a407#: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but408#: may be any other ``cookielib.CookieJar`` compatible object.409self.cookies = cookiejar_from_dict({})410411# Default connection adapters.412self.adapters = OrderedDict()413self.mount('https://', HTTPAdapter())414self.mount('http://', HTTPAdapter())415416def __enter__(self):417return self418419def __exit__(self, *args):420self.close()421422def prepare_request(self, request):423"""Constructs a :class:`PreparedRequest <PreparedRequest>` for424transmission and returns it. The :class:`PreparedRequest` has settings425merged from the :class:`Request <Request>` instance and those of the426:class:`Session`.427428:param request: :class:`Request` instance to prepare with this429session's settings.430:rtype: requests.PreparedRequest431"""432cookies = request.cookies or {}433434# Bootstrap CookieJar.435if not isinstance(cookies, cookielib.CookieJar):436cookies = cookiejar_from_dict(cookies)437438# Merge with session cookies439merged_cookies = merge_cookies(440merge_cookies(RequestsCookieJar(), self.cookies), cookies)441442# Set environment's basic authentication if not explicitly set.443auth = request.auth444if self.trust_env and not auth and not self.auth:445auth = get_netrc_auth(request.url)446447p = PreparedRequest()448p.prepare(449method=request.method.upper(),450url=request.url,451files=request.files,452data=request.data,453json=request.json,454headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict),455params=merge_setting(request.params, self.params),456auth=merge_setting(auth, self.auth),457cookies=merged_cookies,458hooks=merge_hooks(request.hooks, self.hooks),459)460return p461462def request(self, method, url,463params=None, data=None, headers=None, cookies=None, files=None,464auth=None, timeout=None, allow_redirects=True, proxies=None,465hooks=None, stream=None, verify=None, cert=None, json=None):466"""Constructs a :class:`Request <Request>`, prepares it and sends it.467Returns :class:`Response <Response>` object.468469:param method: method for the new :class:`Request` object.470:param url: URL for the new :class:`Request` object.471:param params: (optional) Dictionary or bytes to be sent in the query472string for the :class:`Request`.473:param data: (optional) Dictionary, list of tuples, bytes, or file-like474object to send in the body of the :class:`Request`.475:param json: (optional) json to send in the body of the476:class:`Request`.477:param headers: (optional) Dictionary of HTTP Headers to send with the478:class:`Request`.479:param cookies: (optional) Dict or CookieJar object to send with the480:class:`Request`.481:param files: (optional) Dictionary of ``'filename': file-like-objects``482for multipart encoding upload.483:param auth: (optional) Auth tuple or callable to enable484Basic/Digest/Custom HTTP Auth.485:param timeout: (optional) How long to wait for the server to send486data before giving up, as a float, or a :ref:`(connect timeout,487read timeout) <timeouts>` tuple.488:type timeout: float or tuple489:param allow_redirects: (optional) Set to True by default.490:type allow_redirects: bool491:param proxies: (optional) Dictionary mapping protocol or protocol and492hostname to the URL of the proxy.493:param stream: (optional) whether to immediately download the response494content. Defaults to ``False``.495:param verify: (optional) Either a boolean, in which case it controls whether we verify496the server's TLS certificate, or a string, in which case it must be a path497to a CA bundle to use. Defaults to ``True``.498:param cert: (optional) if String, path to ssl client cert file (.pem).499If Tuple, ('cert', 'key') pair.500:rtype: requests.Response501"""502# Create the Request.503req = Request(504method=method.upper(),505url=url,506headers=headers,507files=files,508data=data or {},509json=json,510params=params or {},511auth=auth,512cookies=cookies,513hooks=hooks,514)515prep = self.prepare_request(req)516517proxies = proxies or {}518519settings = self.merge_environment_settings(520prep.url, proxies, stream, verify, cert521)522523# Send the request.524send_kwargs = {525'timeout': timeout,526'allow_redirects': allow_redirects,527}528send_kwargs.update(settings)529resp = self.send(prep, **send_kwargs)530531return resp532533def get(self, url, **kwargs):534r"""Sends a GET request. Returns :class:`Response` object.535536:param url: URL for the new :class:`Request` object.537:param \*\*kwargs: Optional arguments that ``request`` takes.538:rtype: requests.Response539"""540541kwargs.setdefault('allow_redirects', True)542return self.request('GET', url, **kwargs)543544def options(self, url, **kwargs):545r"""Sends a OPTIONS request. Returns :class:`Response` object.546547:param url: URL for the new :class:`Request` object.548:param \*\*kwargs: Optional arguments that ``request`` takes.549:rtype: requests.Response550"""551552kwargs.setdefault('allow_redirects', True)553return self.request('OPTIONS', url, **kwargs)554555def head(self, url, **kwargs):556r"""Sends a HEAD request. Returns :class:`Response` object.557558:param url: URL for the new :class:`Request` object.559:param \*\*kwargs: Optional arguments that ``request`` takes.560:rtype: requests.Response561"""562563kwargs.setdefault('allow_redirects', False)564return self.request('HEAD', url, **kwargs)565566def post(self, url, data=None, json=None, **kwargs):567r"""Sends a POST request. Returns :class:`Response` object.568569:param url: URL for the new :class:`Request` object.570:param data: (optional) Dictionary, list of tuples, bytes, or file-like571object to send in the body of the :class:`Request`.572:param json: (optional) json to send in the body of the :class:`Request`.573:param \*\*kwargs: Optional arguments that ``request`` takes.574:rtype: requests.Response575"""576577return self.request('POST', url, data=data, json=json, **kwargs)578579def put(self, url, data=None, **kwargs):580r"""Sends a PUT request. Returns :class:`Response` object.581582:param url: URL for the new :class:`Request` object.583:param data: (optional) Dictionary, list of tuples, bytes, or file-like584object to send in the body of the :class:`Request`.585:param \*\*kwargs: Optional arguments that ``request`` takes.586:rtype: requests.Response587"""588589return self.request('PUT', url, data=data, **kwargs)590591def patch(self, url, data=None, **kwargs):592r"""Sends a PATCH request. Returns :class:`Response` object.593594:param url: URL for the new :class:`Request` object.595:param data: (optional) Dictionary, list of tuples, bytes, or file-like596object to send in the body of the :class:`Request`.597:param \*\*kwargs: Optional arguments that ``request`` takes.598:rtype: requests.Response599"""600601return self.request('PATCH', url, data=data, **kwargs)602603def delete(self, url, **kwargs):604r"""Sends a DELETE request. Returns :class:`Response` object.605606:param url: URL for the new :class:`Request` object.607:param \*\*kwargs: Optional arguments that ``request`` takes.608:rtype: requests.Response609"""610611return self.request('DELETE', url, **kwargs)612613def send(self, request, **kwargs):614"""Send a given PreparedRequest.615616:rtype: requests.Response617"""618# Set defaults that the hooks can utilize to ensure they always have619# the correct parameters to reproduce the previous request.620kwargs.setdefault('stream', self.stream)621kwargs.setdefault('verify', self.verify)622kwargs.setdefault('cert', self.cert)623kwargs.setdefault('proxies', self.proxies)624625# It's possible that users might accidentally send a Request object.626# Guard against that specific failure case.627if isinstance(request, Request):628raise ValueError('You can only send PreparedRequests.')629630# Set up variables needed for resolve_redirects and dispatching of hooks631allow_redirects = kwargs.pop('allow_redirects', True)632stream = kwargs.get('stream')633hooks = request.hooks634635# Get the appropriate adapter to use636adapter = self.get_adapter(url=request.url)637638# Start time (approximately) of the request639start = preferred_clock()640641# Send the request642r = adapter.send(request, **kwargs)643644# Total elapsed time of the request (approximately)645elapsed = preferred_clock() - start646r.elapsed = timedelta(seconds=elapsed)647648# Response manipulation hooks649r = dispatch_hook('response', hooks, r, **kwargs)650651# Persist cookies652if r.history:653654# If the hooks create history then we want those cookies too655for resp in r.history:656extract_cookies_to_jar(self.cookies, resp.request, resp.raw)657658extract_cookies_to_jar(self.cookies, request, r.raw)659660# Resolve redirects if allowed.661if allow_redirects:662# Redirect resolving generator.663gen = self.resolve_redirects(r, request, **kwargs)664history = [resp for resp in gen]665else:666history = []667668# Shuffle things around if there's history.669if history:670# Insert the first (original) request at the start671history.insert(0, r)672# Get the last request made673r = history.pop()674r.history = history675676# If redirects aren't being followed, store the response on the Request for Response.next().677if not allow_redirects:678try:679r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs))680except StopIteration:681pass682683if not stream:684r.content685686return r687688def merge_environment_settings(self, url, proxies, stream, verify, cert):689"""690Check the environment and merge it with some settings.691692:rtype: dict693"""694# Gather clues from the surrounding environment.695if self.trust_env:696# Set environment's proxies.697no_proxy = proxies.get('no_proxy') if proxies is not None else None698env_proxies = get_environ_proxies(url, no_proxy=no_proxy)699for (k, v) in env_proxies.items():700proxies.setdefault(k, v)701702# Look for requests environment configuration and be compatible703# with cURL.704if verify is True or verify is None:705verify = (os.environ.get('REQUESTS_CA_BUNDLE') or706os.environ.get('CURL_CA_BUNDLE'))707708# Merge all the kwargs.709proxies = merge_setting(proxies, self.proxies)710stream = merge_setting(stream, self.stream)711verify = merge_setting(verify, self.verify)712cert = merge_setting(cert, self.cert)713714return {'verify': verify, 'proxies': proxies, 'stream': stream,715'cert': cert}716717def get_adapter(self, url):718"""719Returns the appropriate connection adapter for the given URL.720721:rtype: requests.adapters.BaseAdapter722"""723for (prefix, adapter) in self.adapters.items():724725if url.lower().startswith(prefix.lower()):726return adapter727728# Nothing matches :-/729raise InvalidSchema("No connection adapters were found for {!r}".format(url))730731def close(self):732"""Closes all adapters and as such the session"""733for v in self.adapters.values():734v.close()735736def mount(self, prefix, adapter):737"""Registers a connection adapter to a prefix.738739Adapters are sorted in descending order by prefix length.740"""741self.adapters[prefix] = adapter742keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]743744for key in keys_to_move:745self.adapters[key] = self.adapters.pop(key)746747def __getstate__(self):748state = {attr: getattr(self, attr, None) for attr in self.__attrs__}749return state750751def __setstate__(self, state):752for attr, value in state.items():753setattr(self, attr, value)754755756def session():757"""758Returns a :class:`Session` for context-management.759760.. deprecated:: 1.0.0761762This method has been deprecated since version 1.0.0 and is only kept for763backwards compatibility. New code should use :class:`~requests.sessions.Session`764to create a session. This may be removed at a future date.765766:rtype: Session767"""768return Session()769770771