Repository: googleapis/google-api-python-client

Issue:
http.py should catch more ECONNRESET conditions
Right now http.py implicitly assumes that an `ECONNRESET` error will be raised as a `ConnectionError`.  However, in some cases `urllib3` will raise this a regular OSError with the ernno set to match `ECONNRESET`.  http.py should check for the errnos that correspond to the subclasses of ConnectionError to be extra safe.


Discussion:
Here's an example spotted in the wild, the ECONNRESET is thrown as a `SocketError`, i.e. an `OSError`, not a `ConnectionError`. 

```
File "/usr/local/lib/python3.7/site-packages/googleapiclient/http.py", line 901, in execute
     headers=self.headers,
   File "/usr/local/lib/python3.7/site-packages/googleapiclient/http.py", line 177, in _retry_request
     resp, content = http.request(uri, method, *args, **kwargs)
   File "/usr/local/lib/python3.7/site-packages/google_auth_httplib2.py", line 198, in request
     uri, method, body=body, headers=request_headers, **kwargs)
   File "/usr/local/lib/python3.7/site-packages/httplib2/__init__.py", line 1314, in request
     (response, content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey)
   File "/usr/local/lib/python3.7/site-packages/httplib2/__init__.py", line 1064, in _request
     (response, content) = self._conn_request(conn, request_uri, method, body, headers)
   File "/usr/local/lib/python3.7/site-packages/httplib2shim/__init__.py", line 154, in _conn_request
     raise _map_exception(e)
   File "/usr/local/lib/python3.7/site-packages/httplib2shim/__init__.py", line 148, in _conn_request
     decode_content=decode)
   File "/usr/local/lib/python3.7/site-packages/urllib3/request.py", line 68, in request
     **urlopen_kw)
   File "/usr/local/lib/python3.7/site-packages/urllib3/request.py", line 89, in request_encode_url
     return self.urlopen(method, url, **extra_kw)
   File "/usr/local/lib/python3.7/site-packages/urllib3/poolmanager.py", line 326, in urlopen
     response = conn.urlopen(method, u.request_uri, **kw)
   File "/usr/local/lib/python3.7/site-packages/urllib3/connectionpool.py", line 641, in urlopen
     _stacktrace=sys.exc_info()[2])
   File "/usr/local/lib/python3.7/site-packages/urllib3/util/retry.py", line 344, in increment
     raise six.reraise(type(error), error, _stacktrace)
   File "/usr/local/lib/python3.7/site-packages/urllib3/packages/six.py", line 685, in reraise
     raise value.with_traceback(tb)
   File "/usr/local/lib/python3.7/site-packages/urllib3/connectionpool.py", line 603, in urlopen
     chunked=chunked)
   File "/usr/local/lib/python3.7/site-packages/urllib3/connectionpool.py", line 387, in _make_request
     six.raise_from(e, None)
   File "<string>", line 2, in raise_from
   File "/usr/local/lib/python3.7/site-packages/urllib3/connectionpool.py", line 383, in _make_request
     httplib_response = conn.getresponse()
   File "/usr/local/lib/python3.7/http/client.py", line 1344, in getresponse
     response.begin()
   File "/usr/local/lib/python3.7/http/client.py", line 306, in begin
     version, status, reason = self._read_status()
   File "/usr/local/lib/python3.7/http/client.py", line 267, in _read_status
     line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
   File "/usr/local/lib/python3.7/socket.py", line 589, in readinto
     return self._sock.recv_into(b)
   File "/usr/local/lib/python3.7/site-packages/urllib3/contrib/pyopenssl.py", line 309, in recv_into
     raise SocketError(str(e))
 urllib3.exceptions.ProtocolError: ('Connection aborted.', OSError("(104, 'ECONNRESET')"))
```

Resolving patch:
diff --git a/googleapiclient/http.py b/googleapiclient/http.py
--- a/googleapiclient/http.py
+++ b/googleapiclient/http.py
@@ -28,9 +28,7 @@
 from six import BytesIO, StringIO
 from six.moves.urllib.parse import urlparse, urlunparse, quote, unquote
 
-import base64
 import copy
-import gzip
 import httplib2
 import json
 import logging
@@ -38,7 +36,6 @@
 import os
 import random
 import socket
-import sys
 import time
 import uuid
 
@@ -190,11 +187,16 @@ def _retry_request(
             exception = connection_error
         except socket.error as socket_error:
             # errno's contents differ by platform, so we have to match by name.
+            # Some of these same errors may have been caught above, e.g. ECONNRESET *should* be
+            # raised as a ConnectionError, but some libraries will raise it as a socket.error
+            # with an errno corresponding to ECONNRESET
             if socket.errno.errorcode.get(socket_error.errno) not in {
                 "WSAETIMEDOUT",
                 "ETIMEDOUT",
                 "EPIPE",
                 "ECONNABORTED",
+                "ECONNREFUSED",
+                "ECONNRESET",
             }:
                 raise
             exception = socket_error


Test patch:
diff --git a/tests/test_http.py b/tests/test_http.py
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -174,10 +174,10 @@ def request(self, *args, **kwargs):
             # set errno to a non-retriable value
             try:
                 # For Windows:
-                ex.errno = socket.errno.WSAECONNREFUSED
+                ex.errno = socket.errno.WSAEHOSTUNREACH
             except AttributeError:
                 # For Linux/Mac:
-                ex.errno = socket.errno.ECONNREFUSED
+                ex.errno = socket.errno.EHOSTUNREACH
             # Now raise the correct timeout error.
             raise ex