1# Copyright 2014 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import errno 6import httplib 7import json 8import socket 9import sys 10 11from telemetry.core import exceptions 12 13 14class DevToolsClientConnectionError(exceptions.Error): 15 pass 16 17 18class DevToolsClientUrlError(DevToolsClientConnectionError): 19 pass 20 21 22class DevToolsHttp(object): 23 """A helper class to send and parse DevTools HTTP requests. 24 25 This class maintains a persistent http connection to Chrome devtools. 26 Ideally, owners of instances of this class should call Disconnect() before 27 disposing of the instance. Otherwise, the connection will not be closed until 28 the instance is garbage collected. 29 """ 30 31 def __init__(self, devtools_port): 32 self._devtools_port = devtools_port 33 self._conn = None 34 35 def __del__(self): 36 self.Disconnect() 37 38 def _Connect(self, timeout): 39 """Attempts to establish a connection to Chrome devtools.""" 40 assert not self._conn 41 try: 42 host_port = '127.0.0.1:%i' % self._devtools_port 43 self._conn = httplib.HTTPConnection(host_port, timeout=timeout) 44 except (socket.error, httplib.HTTPException) as e: 45 raise DevToolsClientConnectionError, (e,), sys.exc_info()[2] 46 47 def Disconnect(self): 48 """Closes the HTTP connection.""" 49 if not self._conn: 50 return 51 52 try: 53 self._conn.close() 54 except (socket.error, httplib.HTTPException) as e: 55 raise DevToolsClientConnectionError, (e,), sys.exc_info()[2] 56 finally: 57 self._conn = None 58 59 def Request(self, path, timeout=30): 60 """Sends a request to Chrome devtools. 61 62 This method lazily creates an HTTP connection, if one does not already 63 exist. 64 65 Args: 66 path: The DevTools URL path, without the /json/ prefix. 67 timeout: Timeout defaults to 30 seconds. 68 69 Raises: 70 DevToolsClientConnectionError: If the connection fails. 71 """ 72 assert timeout 73 74 if not self._conn: 75 self._Connect(timeout) 76 77 endpoint = '/json' 78 if path: 79 endpoint += '/' + path 80 if self._conn.sock: 81 self._conn.sock.settimeout(timeout) 82 else: 83 self._conn.timeout = timeout 84 85 try: 86 # By default, httplib avoids going through the default system proxy. 87 self._conn.request('GET', endpoint) 88 response = self._conn.getresponse() 89 return response.read() 90 except (socket.error, httplib.HTTPException) as e: 91 self.Disconnect() 92 if isinstance(e, socket.error) and e.errno == errno.ECONNREFUSED: 93 raise DevToolsClientUrlError, (e,), sys.exc_info()[2] 94 raise DevToolsClientConnectionError, (e,), sys.exc_info()[2] 95 96 def RequestJson(self, path, timeout=30): 97 """Sends a request and parse the response as JSON. 98 99 Args: 100 path: The DevTools URL path, without the /json/ prefix. 101 timeout: Timeout defaults to 30 seconds. 102 103 Raises: 104 DevToolsClientConnectionError: If the connection fails. 105 ValueError: If the response is not a valid JSON. 106 """ 107 return json.loads(self.Request(path, timeout)) 108