We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
PRAW does not work with custom SSL cert bundle
Because of the proxy I'm using, I need to use a custom SSL cert bundle. Normally using the requests library, this is achievable in one of 2 ways:

Explicitly setting `verify` to the path of the bundle:

```
requests.get('https://google.com', verify='/path/to/cacert.pem')
```

Or setting an environment variable so that all requests use it:

```
export REQUESTS_CA_BUNDLE='/path/to/cacert.pem'

requests.get('https://google.com')
```

The environment variable is preferred because this allows the requests library to work when called from other packages that I did not write.

However, this does not work with PRAW. The problem I see is severalfold:

Using `Session.request` from requests library gets the environment variable properly through the `merge_environment_settings` method:

https://github.com/kennethreitz/requests/blob/fb014560611f6ebb97e7deb03ad8336c3c8f2db1/requests/sessions.py#L461
https://github.com/kennethreitz/requests/blob/fb014560611f6ebb97e7deb03ad8336c3c8f2db1/requests/sessions.py#L617-L629

But this code is never reached since PRAW builds its own request and uses Session.send which does not pull the environment variable:

https://github.com/praw-dev/praw/blob/3902dc24b0f42e487e26481aae46352806e3e6a8/praw/handlers.py#L101-L102

PRAW does support a setting for `validate_certs` which gets passed along as the `verify` parameter to requests library. The issue here is that PRAW only allows a boolean. Setting this variable to the path of a `cacert.pem` file evaluates to False and turns off SSL verification:

https://github.com/praw-dev/praw/blob/3902dc24b0f42e487e26481aae46352806e3e6a8/praw/__init__.py#L222-L223

There are a couple ways to solve this that I can think of. I would be glad to help out with a fix if that is something that is desirable.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `praw/handlers.py`
Content:
```
1 """Provides classes that handle request dispatching."""
2 
3 from __future__ import print_function, unicode_literals
4 
5 import socket
6 import sys
7 import time
8 from functools import wraps
9 from praw.errors import ClientException
10 from praw.helpers import normalize_url
11 from requests import Session
12 from six import text_type
13 from six.moves import cPickle  # pylint: disable=F0401
14 from threading import Lock
15 from timeit import default_timer as timer
16 
17 
18 class RateLimitHandler(object):
19     """The base handler that provides thread-safe rate limiting enforcement.
20 
21     While this handler is threadsafe, PRAW is not thread safe when the same
22     `Reddit` instance is being utilized from multiple threads.
23 
24     """
25 
26     last_call = {}  # Stores a two-item list: [lock, previous_call_time]
27     rl_lock = Lock()  # lock used for adding items to last_call
28 
29     @staticmethod
30     def rate_limit(function):
31         """Return a decorator that enforces API request limit guidelines.
32 
33         We are allowed to make a API request every api_request_delay seconds as
34         specified in praw.ini. This value may differ from reddit to reddit. For
35         reddit.com it is 2. Any function decorated with this will be forced to
36         delay _rate_delay seconds from the calling of the last function
37         decorated with this before executing.
38 
39         This decorator must be applied to a RateLimitHandler class method or
40         instance method as it assumes `rl_lock` and `last_call` are available.
41 
42         """
43         @wraps(function)
44         def wrapped(cls, _rate_domain, _rate_delay, **kwargs):
45             cls.rl_lock.acquire()
46             lock_last = cls.last_call.setdefault(_rate_domain, [Lock(), 0])
47             with lock_last[0]:  # Obtain the domain specific lock
48                 cls.rl_lock.release()
49                 # Sleep if necessary, then perform the request
50                 now = timer()
51                 delay = lock_last[1] + _rate_delay - now
52                 if delay > 0:
53                     now += delay
54                     time.sleep(delay)
55                 lock_last[1] = now
56                 return function(cls, **kwargs)
57         return wrapped
58 
59     @classmethod
60     def evict(cls, urls):  # pylint: disable=W0613
61         """Method utilized to evict entries for the given urls.
62 
63         :param urls: An iterable containing normalized urls.
64         :returns: The number of items removed from the cache.
65 
66         By default this method returns False as a cache need not be present.
67 
68         """
69         return 0
70 
71     def __del__(self):
72         """Cleanup the HTTP session."""
73         if self.http:
74             try:
75                 self.http.close()
76             except:  # Never fail  pylint: disable=W0702
77                 pass
78 
79     def __init__(self):
80         """Establish the HTTP session."""
81         self.http = Session()  # Each instance should have its own session
82 
83     def request(self, request, proxies, timeout, verify, **_):
84         """Responsible for dispatching the request and returning the result.
85 
86         Network level exceptions should be raised and only
87         ``requests.Response`` should be returned.
88 
89         :param request: A ``requests.PreparedRequest`` object containing all
90             the data necessary to perform the request.
91         :param proxies: A dictionary of proxy settings to be utilized for the
92             request.
93         :param timeout: Specifies the maximum time that the actual HTTP request
94             can take.
95         :param verify: Specifies if SSL certificates should be validated.
96 
97         ``**_`` should be added to the method call to ignore the extra
98         arguments intended for the cache handler.
99 
100         """
101         return self.http.send(request, proxies=proxies, timeout=timeout,
102                               allow_redirects=False, verify=verify)
103 RateLimitHandler.request = RateLimitHandler.rate_limit(
104     RateLimitHandler.request)
105 
106 
107 class DefaultHandler(RateLimitHandler):
108     """Extends the RateLimitHandler to add thread-safe caching support."""
109 
110     ca_lock = Lock()
111     cache = {}
112     cache_hit_callback = None
113     timeouts = {}
114 
115     @staticmethod
116     def with_cache(function):
117         """Return a decorator that interacts with a handler's cache.
118 
119         This decorator must be applied to a DefaultHandler class method or
120         instance method as it assumes `cache`, `ca_lock` and `timeouts` are
121         available.
122 
123         """
124         @wraps(function)
125         def wrapped(cls, _cache_key, _cache_ignore, _cache_timeout, **kwargs):
126             def clear_timeouts():
127                 """Clear the cache of timed out results."""
128                 for key in list(cls.timeouts):
129                     if timer() - cls.timeouts[key] > _cache_timeout:
130                         del cls.timeouts[key]
131                         del cls.cache[key]
132 
133             if _cache_ignore:
134                 return function(cls, **kwargs)
135             with cls.ca_lock:
136                 clear_timeouts()
137                 if _cache_key in cls.cache:
138                     if cls.cache_hit_callback:
139                         cls.cache_hit_callback(_cache_key)
140                     return cls.cache[_cache_key]
141             # Releasing the lock before actually making the request allows for
142             # the possibility of more than one thread making the same request
143             # to get through. Without having domain-specific caching (under the
144             # assumption only one request to a domain can be made at a
145             # time), there isn't a better way to handle this.
146             result = function(cls, **kwargs)
147             # The handlers don't call `raise_for_status` so we need to ignore
148             # status codes that will result in an exception that should not be
149             # cached.
150             if result.status_code not in (200, 302):
151                 return result
152             with cls.ca_lock:
153                 cls.timeouts[_cache_key] = timer()
154                 cls.cache[_cache_key] = result
155                 return result
156         return wrapped
157 
158     @classmethod
159     def clear_cache(cls):
160         """Remove all items from the cache."""
161         with cls.ca_lock:
162             cls.cache = {}
163             cls.timeouts = {}
164 
165     @classmethod
166     def evict(cls, urls):
167         """Remove items from cache matching URLs.
168 
169         Return the number of items removed.
170 
171         """
172         if isinstance(urls, text_type):
173             urls = [urls]
174         urls = set(normalize_url(url) for url in urls)
175         retval = 0
176         with cls.ca_lock:
177             for key in list(cls.cache):
178                 if key[0] in urls:
179                     retval += 1
180                     del cls.cache[key]
181                     del cls.timeouts[key]
182         return retval
183 DefaultHandler.request = DefaultHandler.with_cache(RateLimitHandler.request)
184 
185 
186 class MultiprocessHandler(object):
187     """A PRAW handler to interact with the PRAW multi-process server."""
188 
189     def __init__(self, host='localhost', port=10101):
190         """Construct an instance of the MultiprocessHandler."""
191         self.host = host
192         self.port = port
193 
194     def _relay(self, **kwargs):
195         """Send the request through the server and return the HTTP response."""
196         retval = None
197         delay_time = 2  # For connection retries
198         read_attempts = 0  # For reading from socket
199         while retval is None:  # Evict can return False
200             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
201             sock_fp = sock.makefile('rwb')  # Used for pickle
202             try:
203                 sock.connect((self.host, self.port))
204                 cPickle.dump(kwargs, sock_fp, cPickle.HIGHEST_PROTOCOL)
205                 sock_fp.flush()
206                 retval = cPickle.load(sock_fp)
207             except:  # pylint: disable=W0702
208                 exc_type, exc, _ = sys.exc_info()
209                 socket_error = exc_type is socket.error
210                 if socket_error and exc.errno == 111:  # Connection refused
211                     sys.stderr.write('Cannot connect to multiprocess server. I'
212                                      's it running? Retrying in {0} seconds.\n'
213                                      .format(delay_time))
214                     time.sleep(delay_time)
215                     delay_time = min(64, delay_time * 2)
216                 elif exc_type is EOFError or socket_error and exc.errno == 104:
217                     # Failure during socket READ
218                     if read_attempts >= 3:
219                         raise ClientException('Successive failures reading '
220                                               'from the multiprocess server.')
221                     sys.stderr.write('Lost connection with multiprocess server'
222                                      ' during read. Trying again.\n')
223                     read_attempts += 1
224                 else:
225                     raise
226             finally:
227                 sock_fp.close()
228                 sock.close()
229         if isinstance(retval, Exception):
230             raise retval  # pylint: disable=E0702
231         return retval
232 
233     def evict(self, urls):
234         """Forward the eviction to the server and return its response."""
235         return self._relay(method='evict', urls=urls)
236 
237     def request(self, **kwargs):
238         """Forward the request to the server and return its HTTP response."""
239         return self._relay(method='request', **kwargs)
240
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/praw/handlers.py b/praw/handlers.py
--- a/praw/handlers.py
+++ b/praw/handlers.py
@@ -98,8 +98,12 @@
         arguments intended for the cache handler.
 
         """
-        return self.http.send(request, proxies=proxies, timeout=timeout,
-                              allow_redirects=False, verify=verify)
+        settings = self.http.merge_environment_settings(
+            request.url, proxies, False, verify, None
+        )
+        return self.http.send(request, timeout=timeout, allow_redirects=False,
+                              **settings)
+
 RateLimitHandler.request = RateLimitHandler.rate_limit(
     RateLimitHandler.request)

{"golden_diff": "diff --git a/praw/handlers.py b/praw/handlers.py\n--- a/praw/handlers.py\n+++ b/praw/handlers.py\n@@ -98,8 +98,12 @@\n         arguments intended for the cache handler.\n \n         \"\"\"\n-        return self.http.send(request, proxies=proxies, timeout=timeout,\n-                              allow_redirects=False, verify=verify)\n+        settings = self.http.merge_environment_settings(\n+            request.url, proxies, False, verify, None\n+        )\n+        return self.http.send(request, timeout=timeout, allow_redirects=False,\n+                              **settings)\n+\n RateLimitHandler.request = RateLimitHandler.rate_limit(\n     RateLimitHandler.request)\n", "issue": "PRAW does not work with custom SSL cert bundle\nBecause of the proxy I'm using, I need to use a custom SSL cert bundle. Normally using the requests library, this is achievable in one of 2 ways:\n\nExplicitly setting `verify` to the path of the bundle:\n\n```\nrequests.get('https://google.com', verify='/path/to/cacert.pem')\n```\n\nOr setting an environment variable so that all requests use it:\n\n```\nexport REQUESTS_CA_BUNDLE='/path/to/cacert.pem'\n\nrequests.get('https://google.com')\n```\n\nThe environment variable is preferred because this allows the requests library to work when called from other packages that I did not write.\n\nHowever, this does not work with PRAW. The problem I see is severalfold:\n\nUsing `Session.request` from requests library gets the environment variable properly through the `merge_environment_settings` method:\n\nhttps://github.com/kennethreitz/requests/blob/fb014560611f6ebb97e7deb03ad8336c3c8f2db1/requests/sessions.py#L461\nhttps://github.com/kennethreitz/requests/blob/fb014560611f6ebb97e7deb03ad8336c3c8f2db1/requests/sessions.py#L617-L629\n\nBut this code is never reached since PRAW builds its own request and uses Session.send which does not pull the environment variable:\n\nhttps://github.com/praw-dev/praw/blob/3902dc24b0f42e487e26481aae46352806e3e6a8/praw/handlers.py#L101-L102\n\nPRAW does support a setting for `validate_certs` which gets passed along as the `verify` parameter to requests library. The issue here is that PRAW only allows a boolean. Setting this variable to the path of a `cacert.pem` file evaluates to False and turns off SSL verification:\n\nhttps://github.com/praw-dev/praw/blob/3902dc24b0f42e487e26481aae46352806e3e6a8/praw/__init__.py#L222-L223\n\nThere are a couple ways to solve this that I can think of. I would be glad to help out with a fix if that is something that is desirable.\n\n", "before_files": [{"content": "\"\"\"Provides classes that handle request dispatching.\"\"\"\n\nfrom __future__ import print_function, unicode_literals\n\nimport socket\nimport sys\nimport time\nfrom functools import wraps\nfrom praw.errors import ClientException\nfrom praw.helpers import normalize_url\nfrom requests import Session\nfrom six import text_type\nfrom six.moves import cPickle  # pylint: disable=F0401\nfrom threading import Lock\nfrom timeit import default_timer as timer\n\n\nclass RateLimitHandler(object):\n    \"\"\"The base handler that provides thread-safe rate limiting enforcement.\n\n    While this handler is threadsafe, PRAW is not thread safe when the same\n    `Reddit` instance is being utilized from multiple threads.\n\n    \"\"\"\n\n    last_call = {}  # Stores a two-item list: [lock, previous_call_time]\n    rl_lock = Lock()  # lock used for adding items to last_call\n\n    @staticmethod\n    def rate_limit(function):\n        \"\"\"Return a decorator that enforces API request limit guidelines.\n\n        We are allowed to make a API request every api_request_delay seconds as\n        specified in praw.ini. This value may differ from reddit to reddit. For\n        reddit.com it is 2. Any function decorated with this will be forced to\n        delay _rate_delay seconds from the calling of the last function\n        decorated with this before executing.\n\n        This decorator must be applied to a RateLimitHandler class method or\n        instance method as it assumes `rl_lock` and `last_call` are available.\n\n        \"\"\"\n        @wraps(function)\n        def wrapped(cls, _rate_domain, _rate_delay, **kwargs):\n            cls.rl_lock.acquire()\n            lock_last = cls.last_call.setdefault(_rate_domain, [Lock(), 0])\n            with lock_last[0]:  # Obtain the domain specific lock\n                cls.rl_lock.release()\n                # Sleep if necessary, then perform the request\n                now = timer()\n                delay = lock_last[1] + _rate_delay - now\n                if delay > 0:\n                    now += delay\n                    time.sleep(delay)\n                lock_last[1] = now\n                return function(cls, **kwargs)\n        return wrapped\n\n    @classmethod\n    def evict(cls, urls):  # pylint: disable=W0613\n        \"\"\"Method utilized to evict entries for the given urls.\n\n        :param urls: An iterable containing normalized urls.\n        :returns: The number of items removed from the cache.\n\n        By default this method returns False as a cache need not be present.\n\n        \"\"\"\n        return 0\n\n    def __del__(self):\n        \"\"\"Cleanup the HTTP session.\"\"\"\n        if self.http:\n            try:\n                self.http.close()\n            except:  # Never fail  pylint: disable=W0702\n                pass\n\n    def __init__(self):\n        \"\"\"Establish the HTTP session.\"\"\"\n        self.http = Session()  # Each instance should have its own session\n\n    def request(self, request, proxies, timeout, verify, **_):\n        \"\"\"Responsible for dispatching the request and returning the result.\n\n        Network level exceptions should be raised and only\n        ``requests.Response`` should be returned.\n\n        :param request: A ``requests.PreparedRequest`` object containing all\n            the data necessary to perform the request.\n        :param proxies: A dictionary of proxy settings to be utilized for the\n            request.\n        :param timeout: Specifies the maximum time that the actual HTTP request\n            can take.\n        :param verify: Specifies if SSL certificates should be validated.\n\n        ``**_`` should be added to the method call to ignore the extra\n        arguments intended for the cache handler.\n\n        \"\"\"\n        return self.http.send(request, proxies=proxies, timeout=timeout,\n                              allow_redirects=False, verify=verify)\nRateLimitHandler.request = RateLimitHandler.rate_limit(\n    RateLimitHandler.request)\n\n\nclass DefaultHandler(RateLimitHandler):\n    \"\"\"Extends the RateLimitHandler to add thread-safe caching support.\"\"\"\n\n    ca_lock = Lock()\n    cache = {}\n    cache_hit_callback = None\n    timeouts = {}\n\n    @staticmethod\n    def with_cache(function):\n        \"\"\"Return a decorator that interacts with a handler's cache.\n\n        This decorator must be applied to a DefaultHandler class method or\n        instance method as it assumes `cache`, `ca_lock` and `timeouts` are\n        available.\n\n        \"\"\"\n        @wraps(function)\n        def wrapped(cls, _cache_key, _cache_ignore, _cache_timeout, **kwargs):\n            def clear_timeouts():\n                \"\"\"Clear the cache of timed out results.\"\"\"\n                for key in list(cls.timeouts):\n                    if timer() - cls.timeouts[key] > _cache_timeout:\n                        del cls.timeouts[key]\n                        del cls.cache[key]\n\n            if _cache_ignore:\n                return function(cls, **kwargs)\n            with cls.ca_lock:\n                clear_timeouts()\n                if _cache_key in cls.cache:\n                    if cls.cache_hit_callback:\n                        cls.cache_hit_callback(_cache_key)\n                    return cls.cache[_cache_key]\n            # Releasing the lock before actually making the request allows for\n            # the possibility of more than one thread making the same request\n            # to get through. Without having domain-specific caching (under the\n            # assumption only one request to a domain can be made at a\n            # time), there isn't a better way to handle this.\n            result = function(cls, **kwargs)\n            # The handlers don't call `raise_for_status` so we need to ignore\n            # status codes that will result in an exception that should not be\n            # cached.\n            if result.status_code not in (200, 302):\n                return result\n            with cls.ca_lock:\n                cls.timeouts[_cache_key] = timer()\n                cls.cache[_cache_key] = result\n                return result\n        return wrapped\n\n    @classmethod\n    def clear_cache(cls):\n        \"\"\"Remove all items from the cache.\"\"\"\n        with cls.ca_lock:\n            cls.cache = {}\n            cls.timeouts = {}\n\n    @classmethod\n    def evict(cls, urls):\n        \"\"\"Remove items from cache matching URLs.\n\n        Return the number of items removed.\n\n        \"\"\"\n        if isinstance(urls, text_type):\n            urls = [urls]\n        urls = set(normalize_url(url) for url in urls)\n        retval = 0\n        with cls.ca_lock:\n            for key in list(cls.cache):\n                if key[0] in urls:\n                    retval += 1\n                    del cls.cache[key]\n                    del cls.timeouts[key]\n        return retval\nDefaultHandler.request = DefaultHandler.with_cache(RateLimitHandler.request)\n\n\nclass MultiprocessHandler(object):\n    \"\"\"A PRAW handler to interact with the PRAW multi-process server.\"\"\"\n\n    def __init__(self, host='localhost', port=10101):\n        \"\"\"Construct an instance of the MultiprocessHandler.\"\"\"\n        self.host = host\n        self.port = port\n\n    def _relay(self, **kwargs):\n        \"\"\"Send the request through the server and return the HTTP response.\"\"\"\n        retval = None\n        delay_time = 2  # For connection retries\n        read_attempts = 0  # For reading from socket\n        while retval is None:  # Evict can return False\n            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n            sock_fp = sock.makefile('rwb')  # Used for pickle\n            try:\n                sock.connect((self.host, self.port))\n                cPickle.dump(kwargs, sock_fp, cPickle.HIGHEST_PROTOCOL)\n                sock_fp.flush()\n                retval = cPickle.load(sock_fp)\n            except:  # pylint: disable=W0702\n                exc_type, exc, _ = sys.exc_info()\n                socket_error = exc_type is socket.error\n                if socket_error and exc.errno == 111:  # Connection refused\n                    sys.stderr.write('Cannot connect to multiprocess server. I'\n                                     's it running? Retrying in {0} seconds.\\n'\n                                     .format(delay_time))\n                    time.sleep(delay_time)\n                    delay_time = min(64, delay_time * 2)\n                elif exc_type is EOFError or socket_error and exc.errno == 104:\n                    # Failure during socket READ\n                    if read_attempts >= 3:\n                        raise ClientException('Successive failures reading '\n                                              'from the multiprocess server.')\n                    sys.stderr.write('Lost connection with multiprocess server'\n                                     ' during read. Trying again.\\n')\n                    read_attempts += 1\n                else:\n                    raise\n            finally:\n                sock_fp.close()\n                sock.close()\n        if isinstance(retval, Exception):\n            raise retval  # pylint: disable=E0702\n        return retval\n\n    def evict(self, urls):\n        \"\"\"Forward the eviction to the server and return its response.\"\"\"\n        return self._relay(method='evict', urls=urls)\n\n    def request(self, **kwargs):\n        \"\"\"Forward the request to the server and return its HTTP response.\"\"\"\n        return self._relay(method='request', **kwargs)\n", "path": "praw/handlers.py"}], "after_files": [{"content": "\"\"\"Provides classes that handle request dispatching.\"\"\"\n\nfrom __future__ import print_function, unicode_literals\n\nimport socket\nimport sys\nimport time\nfrom functools import wraps\nfrom praw.errors import ClientException\nfrom praw.helpers import normalize_url\nfrom requests import Session\nfrom six import text_type\nfrom six.moves import cPickle  # pylint: disable=F0401\nfrom threading import Lock\nfrom timeit import default_timer as timer\n\n\nclass RateLimitHandler(object):\n    \"\"\"The base handler that provides thread-safe rate limiting enforcement.\n\n    While this handler is threadsafe, PRAW is not thread safe when the same\n    `Reddit` instance is being utilized from multiple threads.\n\n    \"\"\"\n\n    last_call = {}  # Stores a two-item list: [lock, previous_call_time]\n    rl_lock = Lock()  # lock used for adding items to last_call\n\n    @staticmethod\n    def rate_limit(function):\n        \"\"\"Return a decorator that enforces API request limit guidelines.\n\n        We are allowed to make a API request every api_request_delay seconds as\n        specified in praw.ini. This value may differ from reddit to reddit. For\n        reddit.com it is 2. Any function decorated with this will be forced to\n        delay _rate_delay seconds from the calling of the last function\n        decorated with this before executing.\n\n        This decorator must be applied to a RateLimitHandler class method or\n        instance method as it assumes `rl_lock` and `last_call` are available.\n\n        \"\"\"\n        @wraps(function)\n        def wrapped(cls, _rate_domain, _rate_delay, **kwargs):\n            cls.rl_lock.acquire()\n            lock_last = cls.last_call.setdefault(_rate_domain, [Lock(), 0])\n            with lock_last[0]:  # Obtain the domain specific lock\n                cls.rl_lock.release()\n                # Sleep if necessary, then perform the request\n                now = timer()\n                delay = lock_last[1] + _rate_delay - now\n                if delay > 0:\n                    now += delay\n                    time.sleep(delay)\n                lock_last[1] = now\n                return function(cls, **kwargs)\n        return wrapped\n\n    @classmethod\n    def evict(cls, urls):  # pylint: disable=W0613\n        \"\"\"Method utilized to evict entries for the given urls.\n\n        :param urls: An iterable containing normalized urls.\n        :returns: The number of items removed from the cache.\n\n        By default this method returns False as a cache need not be present.\n\n        \"\"\"\n        return 0\n\n    def __del__(self):\n        \"\"\"Cleanup the HTTP session.\"\"\"\n        if self.http:\n            try:\n                self.http.close()\n            except:  # Never fail  pylint: disable=W0702\n                pass\n\n    def __init__(self):\n        \"\"\"Establish the HTTP session.\"\"\"\n        self.http = Session()  # Each instance should have its own session\n\n    def request(self, request, proxies, timeout, verify, **_):\n        \"\"\"Responsible for dispatching the request and returning the result.\n\n        Network level exceptions should be raised and only\n        ``requests.Response`` should be returned.\n\n        :param request: A ``requests.PreparedRequest`` object containing all\n            the data necessary to perform the request.\n        :param proxies: A dictionary of proxy settings to be utilized for the\n            request.\n        :param timeout: Specifies the maximum time that the actual HTTP request\n            can take.\n        :param verify: Specifies if SSL certificates should be validated.\n\n        ``**_`` should be added to the method call to ignore the extra\n        arguments intended for the cache handler.\n\n        \"\"\"\n        settings = self.http.merge_environment_settings(\n            request.url, proxies, False, verify, None\n        )\n        return self.http.send(request, timeout=timeout, allow_redirects=False,\n                              **settings)\n\nRateLimitHandler.request = RateLimitHandler.rate_limit(\n    RateLimitHandler.request)\n\n\nclass DefaultHandler(RateLimitHandler):\n    \"\"\"Extends the RateLimitHandler to add thread-safe caching support.\"\"\"\n\n    ca_lock = Lock()\n    cache = {}\n    cache_hit_callback = None\n    timeouts = {}\n\n    @staticmethod\n    def with_cache(function):\n        \"\"\"Return a decorator that interacts with a handler's cache.\n\n        This decorator must be applied to a DefaultHandler class method or\n        instance method as it assumes `cache`, `ca_lock` and `timeouts` are\n        available.\n\n        \"\"\"\n        @wraps(function)\n        def wrapped(cls, _cache_key, _cache_ignore, _cache_timeout, **kwargs):\n            def clear_timeouts():\n                \"\"\"Clear the cache of timed out results.\"\"\"\n                for key in list(cls.timeouts):\n                    if timer() - cls.timeouts[key] > _cache_timeout:\n                        del cls.timeouts[key]\n                        del cls.cache[key]\n\n            if _cache_ignore:\n                return function(cls, **kwargs)\n            with cls.ca_lock:\n                clear_timeouts()\n                if _cache_key in cls.cache:\n                    if cls.cache_hit_callback:\n                        cls.cache_hit_callback(_cache_key)\n                    return cls.cache[_cache_key]\n            # Releasing the lock before actually making the request allows for\n            # the possibility of more than one thread making the same request\n            # to get through. Without having domain-specific caching (under the\n            # assumption only one request to a domain can be made at a\n            # time), there isn't a better way to handle this.\n            result = function(cls, **kwargs)\n            # The handlers don't call `raise_for_status` so we need to ignore\n            # status codes that will result in an exception that should not be\n            # cached.\n            if result.status_code not in (200, 302):\n                return result\n            with cls.ca_lock:\n                cls.timeouts[_cache_key] = timer()\n                cls.cache[_cache_key] = result\n                return result\n        return wrapped\n\n    @classmethod\n    def clear_cache(cls):\n        \"\"\"Remove all items from the cache.\"\"\"\n        with cls.ca_lock:\n            cls.cache = {}\n            cls.timeouts = {}\n\n    @classmethod\n    def evict(cls, urls):\n        \"\"\"Remove items from cache matching URLs.\n\n        Return the number of items removed.\n\n        \"\"\"\n        if isinstance(urls, text_type):\n            urls = [urls]\n        urls = set(normalize_url(url) for url in urls)\n        retval = 0\n        with cls.ca_lock:\n            for key in list(cls.cache):\n                if key[0] in urls:\n                    retval += 1\n                    del cls.cache[key]\n                    del cls.timeouts[key]\n        return retval\nDefaultHandler.request = DefaultHandler.with_cache(RateLimitHandler.request)\n\n\nclass MultiprocessHandler(object):\n    \"\"\"A PRAW handler to interact with the PRAW multi-process server.\"\"\"\n\n    def __init__(self, host='localhost', port=10101):\n        \"\"\"Construct an instance of the MultiprocessHandler.\"\"\"\n        self.host = host\n        self.port = port\n\n    def _relay(self, **kwargs):\n        \"\"\"Send the request through the server and return the HTTP response.\"\"\"\n        retval = None\n        delay_time = 2  # For connection retries\n        read_attempts = 0  # For reading from socket\n        while retval is None:  # Evict can return False\n            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n            sock_fp = sock.makefile('rwb')  # Used for pickle\n            try:\n                sock.connect((self.host, self.port))\n                cPickle.dump(kwargs, sock_fp, cPickle.HIGHEST_PROTOCOL)\n                sock_fp.flush()\n                retval = cPickle.load(sock_fp)\n            except:  # pylint: disable=W0702\n                exc_type, exc, _ = sys.exc_info()\n                socket_error = exc_type is socket.error\n                if socket_error and exc.errno == 111:  # Connection refused\n                    sys.stderr.write('Cannot connect to multiprocess server. I'\n                                     's it running? Retrying in {0} seconds.\\n'\n                                     .format(delay_time))\n                    time.sleep(delay_time)\n                    delay_time = min(64, delay_time * 2)\n                elif exc_type is EOFError or socket_error and exc.errno == 104:\n                    # Failure during socket READ\n                    if read_attempts >= 3:\n                        raise ClientException('Successive failures reading '\n                                              'from the multiprocess server.')\n                    sys.stderr.write('Lost connection with multiprocess server'\n                                     ' during read. Trying again.\\n')\n                    read_attempts += 1\n                else:\n                    raise\n            finally:\n                sock_fp.close()\n                sock.close()\n        if isinstance(retval, Exception):\n            raise retval  # pylint: disable=E0702\n        return retval\n\n    def evict(self, urls):\n        \"\"\"Forward the eviction to the server and return its response.\"\"\"\n        return self._relay(method='evict', urls=urls)\n\n    def request(self, **kwargs):\n        \"\"\"Forward the request to the server and return its HTTP response.\"\"\"\n        return self._relay(method='request', **kwargs)\n", "path": "praw/handlers.py"}]}