133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# Copyright 2014 The Chromium Authors. All rights reserved. 233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# Use of this source code is governed by a BSD-style license that can be 333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# found in the LICENSE file. 433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport errno 633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport httplib 733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport json 833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport socket 933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport sys 1033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 1133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckfrom telemetry.core import exceptions 1233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 1333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 1433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckclass DevToolsClientConnectionError(exceptions.Error): 1533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck pass 1633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 1733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 1833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckclass DevToolsClientUrlError(DevToolsClientConnectionError): 1933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck pass 2033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 2133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 2233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckclass DevToolsHttp(object): 2333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck """A helper class to send and parse DevTools HTTP requests. 2433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 2533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck This class maintains a persistent http connection to Chrome devtools. 2633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck Ideally, owners of instances of this class should call Disconnect() before 2733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck disposing of the instance. Otherwise, the connection will not be closed until 2833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck the instance is garbage collected. 2933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck """ 3033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 3133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck def __init__(self, devtools_port): 3233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck self._devtools_port = devtools_port 3333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck self._conn = None 3433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 3533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck def __del__(self): 3633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck self.Disconnect() 3733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 3833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck def _Connect(self, timeout): 3933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck """Attempts to establish a connection to Chrome devtools.""" 4033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck assert not self._conn 4133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck try: 4233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck host_port = '127.0.0.1:%i' % self._devtools_port 4333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck self._conn = httplib.HTTPConnection(host_port, timeout=timeout) 4433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck except (socket.error, httplib.HTTPException) as e: 4533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck raise DevToolsClientConnectionError, (e,), sys.exc_info()[2] 4633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 4733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck def Disconnect(self): 4833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck """Closes the HTTP connection.""" 4933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck if not self._conn: 5033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck return 5133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 5233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck try: 5333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck self._conn.close() 5433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck except (socket.error, httplib.HTTPException) as e: 5533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck raise DevToolsClientConnectionError, (e,), sys.exc_info()[2] 5633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck finally: 5733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck self._conn = None 5833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 5933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck def Request(self, path, timeout=30): 6033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck """Sends a request to Chrome devtools. 6133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 6233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck This method lazily creates an HTTP connection, if one does not already 6333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck exist. 6433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 6533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck Args: 6633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck path: The DevTools URL path, without the /json/ prefix. 6733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck timeout: Timeout defaults to 30 seconds. 6833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 6933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck Raises: 7033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck DevToolsClientConnectionError: If the connection fails. 7133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck """ 7233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck assert timeout 7333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 7433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck if not self._conn: 7533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck self._Connect(timeout) 7633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 7733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck endpoint = '/json' 7833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck if path: 7933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck endpoint += '/' + path 8033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck if self._conn.sock: 8133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck self._conn.sock.settimeout(timeout) 8233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck else: 8333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck self._conn.timeout = timeout 8433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 8533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck try: 8633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck # By default, httplib avoids going through the default system proxy. 8733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck self._conn.request('GET', endpoint) 8833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck response = self._conn.getresponse() 8933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck return response.read() 9033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck except (socket.error, httplib.HTTPException) as e: 9133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck self.Disconnect() 9233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck if isinstance(e, socket.error) and e.errno == errno.ECONNREFUSED: 9333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck raise DevToolsClientUrlError, (e,), sys.exc_info()[2] 9433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck raise DevToolsClientConnectionError, (e,), sys.exc_info()[2] 9533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 9633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck def RequestJson(self, path, timeout=30): 9733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck """Sends a request and parse the response as JSON. 9833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 9933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck Args: 10033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck path: The DevTools URL path, without the /json/ prefix. 10133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck timeout: Timeout defaults to 30 seconds. 10233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck 10333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck Raises: 10433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck DevToolsClientConnectionError: If the connection fails. 10533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck ValueError: If the response is not a valid JSON. 10633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck """ 10733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck return json.loads(self.Request(path, timeout)) 108