1# Copyright 2013 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. 4import logging 5 6from telemetry.core import util 7from telemetry.core.backends.chrome import timeline_recorder 8from telemetry.timeline import inspector_timeline_data 9 10 11class InspectorNetworkException(Exception): 12 pass 13 14 15class InspectorNetworkResponseData(object): 16 def __init__(self, inspector_network, params): 17 self._inspector_network = inspector_network 18 self._request_id = params['requestId'] 19 self._timestamp = params['timestamp'] 20 21 self._response = params['response'] 22 if not self._response: 23 raise InspectorNetworkException('response must exist') 24 25 # Response headers. 26 headers = self._response['headers'] 27 self._header_map = {} 28 for k, v in headers.iteritems(): 29 # Camel-case header keys. 30 self._header_map[k.title()] = v 31 32 # Request headers. 33 self._request_header_map = {} 34 if 'requestHeaders' in self._response: 35 # Camel-case header keys. 36 for k, v in self._response['requestHeaders'].iteritems(): 37 self._request_header_map[k.title()] = v 38 39 self._body = None 40 self._base64_encoded = False 41 if self._inspector_network: 42 self._served_from_cache = ( 43 self._inspector_network.HTTPResponseServedFromCache(self._request_id)) 44 else: 45 self._served_from_cache = False 46 47 # Whether constructed from a timeline event. 48 self._from_event = False 49 50 @property 51 def status(self): 52 return self._response['status'] 53 54 def status_text(self): 55 return self._response['status_text'] 56 57 @property 58 def headers(self): 59 return self._header_map 60 61 @property 62 def request_headers(self): 63 return self._request_header_map 64 65 @property 66 def timestamp(self): 67 return self._timestamp 68 69 @property 70 def timing(self): 71 if 'timing' in self._response: 72 return self._response['timing'] 73 return None 74 75 @property 76 def url(self): 77 return self._response['url'] 78 79 @property 80 def request_id(self): 81 return self._request_id 82 83 @property 84 def served_from_cache(self): 85 return self._served_from_cache 86 87 def GetHeader(self, name): 88 if name in self.headers: 89 return self.headers[name] 90 return None 91 92 def GetBody(self, timeout=60): 93 if not self._body and not self._from_event: 94 self._body, self._base64_encoded = ( 95 self._inspector_network.GetHTTPResponseBody(self._request_id, timeout)) 96 return self._body, self._base64_encoded 97 98 def AsTimelineEvent(self): 99 event = {} 100 event['type'] = 'HTTPResponse' 101 event['startTime'] = self.timestamp 102 # There is no end time. Just return the timestamp instead. 103 event['endTime'] = self.timestamp 104 event['requestId'] = self.request_id 105 event['response'] = self._response 106 event['body'], event['base64_encoded_body'] = self.GetBody() 107 event['served_from_cache'] = self.served_from_cache 108 return event 109 110 @staticmethod 111 def FromTimelineEvent(event): 112 assert event.name == 'HTTPResponse' 113 params = {} 114 params['timestamp'] = event.start 115 params['requestId'] = event.args['requestId'] 116 params['response'] = event.args['response'] 117 recorded = InspectorNetworkResponseData(None, params) 118 recorded._body = event.args['body'] 119 recorded._base64_encoded = event.args['base64_encoded_body'] 120 recorded._served_from_cache = event.args['served_from_cache'] 121 recorded._from_event = True 122 return recorded 123 124 125class InspectorNetwork(object): 126 def __init__(self, inspector_backend): 127 self._inspector_backend = inspector_backend 128 self._http_responses = [] 129 self._served_from_cache = set() 130 self._timeline_recorder = None 131 132 def ClearCache(self, timeout=60): 133 """Clears the browser's disk and memory cache.""" 134 res = self._inspector_backend.SyncRequest({ 135 'method': 'Network.canClearBrowserCache' 136 }, timeout) 137 assert res['result'], 'Cache clearing is not supported by this browser.' 138 self._inspector_backend.SyncRequest({ 139 'method': 'Network.clearBrowserCache' 140 }, timeout) 141 142 def StartMonitoringNetwork(self): 143 """Starts monitoring network notifications and recording HTTP responses.""" 144 self.ClearResponseData() 145 self._inspector_backend.RegisterDomain( 146 'Network', 147 self._OnNetworkNotification, 148 self._OnClose) 149 request = { 150 'method': 'Network.enable' 151 } 152 self._inspector_backend.SyncRequest(request) 153 154 def StopMonitoringNetwork(self): 155 """Stops monitoring network notifications and recording HTTP responses.""" 156 self._inspector_backend.UnregisterDomain('Network') 157 request = { 158 'method': 'Network.disable' 159 } 160 self._inspector_backend.SyncRequest(request) 161 162 def GetResponseData(self): 163 """Returns all recorded HTTP responses.""" 164 return self._http_responses 165 166 def ClearResponseData(self): 167 """Clears recorded HTTP responses.""" 168 self._http_responses = [] 169 self._served_from_cache.clear() 170 171 def _OnNetworkNotification(self, msg): 172 if msg['method'] == 'Network.responseReceived': 173 self._RecordHTTPResponse(msg['params']) 174 elif msg['method'] == 'Network.requestServedFromCache': 175 self._served_from_cache.add(msg['params']['requestId']) 176 177 def _RecordHTTPResponse(self, params): 178 required_fields = ['requestId', 'timestamp', 'response'] 179 for field in required_fields: 180 if field not in params: 181 logging.waring('HTTP Response missing required field: %s', field) 182 return 183 self._http_responses.append(InspectorNetworkResponseData(self, params)) 184 185 def GetHTTPResponseBody(self, request_id, timeout=60): 186 try: 187 res = self._inspector_backend.SyncRequest({ 188 'method': 'Network.getResponseBody', 189 'params': { 190 'requestId': request_id, 191 } 192 }, timeout) 193 except util.TimeoutException: 194 logging.warning('Timeout during fetching body for %s' % request_id) 195 return None, False 196 if 'error' in res: 197 return None, False 198 return res['result']['body'], res['result']['base64Encoded'] 199 200 def HTTPResponseServedFromCache(self, request_id): 201 return request_id and request_id in self._served_from_cache 202 203 def _OnClose(self): 204 pass 205 206 @property 207 def timeline_recorder (self): 208 if not self._timeline_recorder: 209 self._timeline_recorder = TimelineRecorder(self) 210 return self._timeline_recorder 211 212 213class TimelineRecorder(timeline_recorder.TimelineRecorder): 214 def __init__(self, inspector_network): 215 super(TimelineRecorder, self).__init__() 216 self._inspector_network = inspector_network 217 self._is_recording = False 218 219 def Start(self): 220 assert not self._is_recording, 'Start should only be called once.' 221 self._is_recording = True 222 self._inspector_network.StartMonitoringNetwork() 223 224 def Stop(self): 225 if not self._is_recording: 226 return None 227 responses = self._inspector_network.GetResponseData() 228 events = [r.AsTimelineEvent() for r in list(responses)] 229 self._inspector_network.StopMonitoringNetwork() 230 self._is_recording = False 231 if len(events) == 0: 232 return None 233 return inspector_timeline_data.InspectorTimelineData(events) 234