| 66 | raise TypeError(repr(o) + " is not JSON serializable") |
| 67 | |
| 68 | class AuthServiceProxy(): |
| 69 | __id_count = 0 |
| 70 | |
| 71 | # ensure_ascii: escape unicode as \uXXXX, passed to json.dumps |
| 72 | def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None, ensure_ascii=True): |
| 73 | self.__service_url = service_url |
| 74 | self._service_name = service_name |
| 75 | self.ensure_ascii = ensure_ascii # can be toggled on the fly by tests |
| 76 | self.__url = urllib.parse.urlparse(service_url) |
| 77 | user = None if self.__url.username is None else self.__url.username.encode('utf8') |
| 78 | passwd = None if self.__url.password is None else self.__url.password.encode('utf8') |
| 79 | authpair = user + b':' + passwd |
| 80 | self.__auth_header = b'Basic ' + base64.b64encode(authpair) |
| 81 | self.timeout = timeout |
| 82 | self._set_conn(connection) |
| 83 | |
| 84 | def __getattr__(self, name): |
| 85 | if name.startswith('__') and name.endswith('__'): |
| 86 | # Python internal stuff |
| 87 | raise AttributeError |
| 88 | if self._service_name is not None: |
| 89 | name = "%s.%s" % (self._service_name, name) |
| 90 | return AuthServiceProxy(self.__service_url, name, connection=self.__conn) |
| 91 | |
| 92 | def _request(self, method, path, postdata): |
| 93 | ''' |
| 94 | Do a HTTP request, with retry if we get disconnected (e.g. due to a timeout). |
| 95 | This is a workaround for https://bugs.python.org/issue3566 which is fixed in Python 3.5. |
| 96 | ''' |
| 97 | headers = {'Host': self.__url.hostname, |
| 98 | 'User-Agent': USER_AGENT, |
| 99 | 'Authorization': self.__auth_header, |
| 100 | 'Content-type': 'application/json'} |
| 101 | if os.name == 'nt': |
| 102 | # Windows somehow does not like to re-use connections |
| 103 | # TODO: Find out why the connection would disconnect occasionally and make it reusable on Windows |
| 104 | # Avoid "ConnectionAbortedError: [WinError 10053] An established connection was aborted by the software in your host machine" |
| 105 | self._set_conn() |
| 106 | try: |
| 107 | self.__conn.request(method, path, postdata, headers) |
| 108 | return self._get_response() |
| 109 | except (BrokenPipeError, ConnectionResetError): |
| 110 | # Python 3.5+ raises BrokenPipeError when the connection was reset |
| 111 | # ConnectionResetError happens on FreeBSD |
| 112 | self.__conn.close() |
| 113 | self.__conn.request(method, path, postdata, headers) |
| 114 | return self._get_response() |
| 115 | except OSError as e: |
| 116 | # Workaround for a bug on macOS. See https://bugs.python.org/issue33450 |
| 117 | retry = '[Errno 41] Protocol wrong type for socket' in str(e) |
| 118 | if retry: |
| 119 | self.__conn.close() |
| 120 | self.__conn.request(method, path, postdata, headers) |
| 121 | return self._get_response() |
| 122 | else: |
| 123 | raise |
| 124 | |
| 125 | def get_request(self, *args, **argsn): |
no outgoing calls
no test coverage detected