1 // Copyright (c) 2012 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
5 #include "content/browser/devtools/devtools_netlog_observer.h"
6
7 #include "base/strings/string_util.h"
8 #include "base/values.h"
9 #include "content/public/browser/browser_thread.h"
10 #include "content/public/browser/content_browser_client.h"
11 #include "content/public/common/resource_response.h"
12 #include "net/base/load_flags.h"
13 #include "net/http/http_response_headers.h"
14 #include "net/http/http_util.h"
15 #include "net/spdy/spdy_header_block.h"
16 #include "net/url_request/url_request.h"
17 #include "net/url_request/url_request_netlog_params.h"
18
19 namespace content {
20 const size_t kMaxNumEntries = 1000;
21
22 DevToolsNetLogObserver* DevToolsNetLogObserver::instance_ = NULL;
23
DevToolsNetLogObserver()24 DevToolsNetLogObserver::DevToolsNetLogObserver() {
25 }
26
~DevToolsNetLogObserver()27 DevToolsNetLogObserver::~DevToolsNetLogObserver() {
28 }
29
30 DevToolsNetLogObserver::ResourceInfo*
GetResourceInfo(uint32 id)31 DevToolsNetLogObserver::GetResourceInfo(uint32 id) {
32 RequestToInfoMap::iterator it = request_to_info_.find(id);
33 if (it != request_to_info_.end())
34 return it->second.get();
35 return NULL;
36 }
37
OnAddEntry(const net::NetLog::Entry & entry)38 void DevToolsNetLogObserver::OnAddEntry(const net::NetLog::Entry& entry) {
39 // The events that the Observer is interested in only occur on the IO thread.
40 if (!BrowserThread::CurrentlyOn(BrowserThread::IO))
41 return;
42
43 if (entry.source().type == net::NetLog::SOURCE_URL_REQUEST)
44 OnAddURLRequestEntry(entry);
45 else if (entry.source().type == net::NetLog::SOURCE_HTTP_STREAM_JOB)
46 OnAddHTTPStreamJobEntry(entry);
47 else if (entry.source().type == net::NetLog::SOURCE_SOCKET)
48 OnAddSocketEntry(entry);
49 }
50
OnAddURLRequestEntry(const net::NetLog::Entry & entry)51 void DevToolsNetLogObserver::OnAddURLRequestEntry(
52 const net::NetLog::Entry& entry) {
53 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
54
55 bool is_begin = entry.phase() == net::NetLog::PHASE_BEGIN;
56 bool is_end = entry.phase() == net::NetLog::PHASE_END;
57
58 if (entry.type() == net::NetLog::TYPE_URL_REQUEST_START_JOB) {
59 if (is_begin) {
60 int load_flags;
61 scoped_ptr<base::Value> event_param(entry.ParametersToValue());
62 if (!net::StartEventLoadFlagsFromEventParams(event_param.get(),
63 &load_flags)) {
64 return;
65 }
66
67 if (!(load_flags & net::LOAD_REPORT_RAW_HEADERS))
68 return;
69
70 if (request_to_info_.size() > kMaxNumEntries) {
71 LOG(WARNING) << "The raw headers observer url request count has grown "
72 "larger than expected, resetting";
73 request_to_info_.clear();
74 }
75
76 request_to_info_[entry.source().id] = new ResourceInfo();
77
78 if (request_to_encoded_data_length_.size() > kMaxNumEntries) {
79 LOG(WARNING) << "The encoded data length observer url request count "
80 "has grown larger than expected, resetting";
81 request_to_encoded_data_length_.clear();
82 }
83
84 request_to_encoded_data_length_[entry.source().id] = 0;
85 }
86 return;
87 } else if (entry.type() == net::NetLog::TYPE_REQUEST_ALIVE) {
88 // Cleanup records based on the TYPE_REQUEST_ALIVE entry.
89 if (is_end) {
90 request_to_info_.erase(entry.source().id);
91 request_to_encoded_data_length_.erase(entry.source().id);
92 }
93 return;
94 }
95
96 ResourceInfo* info = GetResourceInfo(entry.source().id);
97 if (!info)
98 return;
99
100 switch (entry.type()) {
101 case net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_HEADERS: {
102 scoped_ptr<base::Value> event_params(entry.ParametersToValue());
103 std::string request_line;
104 net::HttpRequestHeaders request_headers;
105
106 if (!net::HttpRequestHeaders::FromNetLogParam(event_params.get(),
107 &request_headers,
108 &request_line)) {
109 NOTREACHED();
110 }
111
112 // We need to clear headers in case the same url_request is reused for
113 // several http requests (e.g. see http://crbug.com/80157).
114 info->request_headers.clear();
115
116 for (net::HttpRequestHeaders::Iterator it(request_headers);
117 it.GetNext();) {
118 info->request_headers.push_back(std::make_pair(it.name(), it.value()));
119 }
120 info->request_headers_text = request_line + request_headers.ToString();
121 break;
122 }
123 case net::NetLog::TYPE_HTTP_TRANSACTION_SPDY_SEND_REQUEST_HEADERS: {
124 scoped_ptr<base::Value> event_params(entry.ParametersToValue());
125 net::SpdyHeaderBlock request_headers;
126
127 if (!net::SpdyHeaderBlockFromNetLogParam(event_params.get(),
128 &request_headers)) {
129 NOTREACHED();
130 }
131
132 // We need to clear headers in case the same url_request is reused for
133 // several http requests (e.g. see http://crbug.com/80157).
134 info->request_headers.clear();
135
136 for (net::SpdyHeaderBlock::const_iterator it = request_headers.begin();
137 it != request_headers.end(); ++it) {
138 info->request_headers.push_back(std::make_pair(it->first, it->second));
139 }
140 info->request_headers_text = "";
141 break;
142 }
143 case net::NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS: {
144 scoped_ptr<base::Value> event_params(entry.ParametersToValue());
145
146 scoped_refptr<net::HttpResponseHeaders> response_headers;
147
148 if (!net::HttpResponseHeaders::FromNetLogParam(event_params.get(),
149 &response_headers)) {
150 NOTREACHED();
151 }
152
153 info->http_status_code = response_headers->response_code();
154 info->http_status_text = response_headers->GetStatusText();
155 std::string name, value;
156
157 // We need to clear headers in case the same url_request is reused for
158 // several http requests (e.g. see http://crbug.com/80157).
159 info->response_headers.clear();
160
161 for (void* it = NULL;
162 response_headers->EnumerateHeaderLines(&it, &name, &value); ) {
163 info->response_headers.push_back(std::make_pair(name, value));
164 }
165 info->response_headers_text =
166 net::HttpUtil::ConvertHeadersBackToHTTPResponse(
167 response_headers->raw_headers());
168 break;
169 }
170 case net::NetLog::TYPE_HTTP_STREAM_REQUEST_BOUND_TO_JOB: {
171 scoped_ptr<base::Value> event_params(entry.ParametersToValue());
172 net::NetLog::Source http_stream_job_source;
173 if (!net::NetLog::Source::FromEventParameters(event_params.get(),
174 &http_stream_job_source)) {
175 NOTREACHED();
176 break;
177 }
178
179 uint32 http_stream_job_id = http_stream_job_source.id;
180 HTTPStreamJobToSocketMap::iterator it =
181 http_stream_job_to_socket_.find(http_stream_job_id);
182 if (it == http_stream_job_to_socket_.end())
183 return;
184 uint32 socket_id = it->second;
185
186 if (socket_to_request_.size() > kMaxNumEntries) {
187 LOG(WARNING) << "The url request observer socket count has grown "
188 "larger than expected, resetting";
189 socket_to_request_.clear();
190 }
191
192 socket_to_request_[socket_id] = entry.source().id;
193 http_stream_job_to_socket_.erase(http_stream_job_id);
194 break;
195 }
196 default:
197 break;
198 }
199 }
200
OnAddHTTPStreamJobEntry(const net::NetLog::Entry & entry)201 void DevToolsNetLogObserver::OnAddHTTPStreamJobEntry(
202 const net::NetLog::Entry& entry) {
203 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
204
205 if (entry.type() == net::NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET) {
206 scoped_ptr<base::Value> event_params(entry.ParametersToValue());
207 net::NetLog::Source socket_source;
208 if (!net::NetLog::Source::FromEventParameters(event_params.get(),
209 &socket_source)) {
210 NOTREACHED();
211 return;
212 }
213
214 // Prevents us from passively growing the memory unbounded in
215 // case something went wrong. Should not happen.
216 if (http_stream_job_to_socket_.size() > kMaxNumEntries) {
217 LOG(WARNING) << "The load timing observer http stream job count "
218 "has grown larger than expected, resetting";
219 http_stream_job_to_socket_.clear();
220 }
221 http_stream_job_to_socket_[entry.source().id] = socket_source.id;
222 }
223 }
224
OnAddSocketEntry(const net::NetLog::Entry & entry)225 void DevToolsNetLogObserver::OnAddSocketEntry(
226 const net::NetLog::Entry& entry) {
227 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
228
229 bool is_end = entry.phase() == net::NetLog::PHASE_END;
230
231 SocketToRequestMap::iterator it =
232 socket_to_request_.find(entry.source().id);
233 if (it == socket_to_request_.end())
234 return;
235 uint32 request_id = it->second;
236
237 if (entry.type() == net::NetLog::TYPE_SOCKET_IN_USE) {
238 if (is_end)
239 socket_to_request_.erase(entry.source().id);
240 return;
241 }
242
243 RequestToEncodedDataLengthMap::iterator encoded_data_length_it =
244 request_to_encoded_data_length_.find(request_id);
245 if (encoded_data_length_it == request_to_encoded_data_length_.end())
246 return;
247
248 if (net::NetLog::TYPE_SOCKET_BYTES_RECEIVED == entry.type()) {
249 int byte_count = 0;
250 scoped_ptr<base::Value> value(entry.ParametersToValue());
251 if (!value->IsType(base::Value::TYPE_DICTIONARY))
252 return;
253
254 base::DictionaryValue* dValue =
255 static_cast<base::DictionaryValue*>(value.get());
256 if (!dValue->GetInteger("byte_count", &byte_count))
257 return;
258
259 encoded_data_length_it->second += byte_count;
260 }
261 }
262
Attach()263 void DevToolsNetLogObserver::Attach() {
264 DCHECK(!instance_);
265 net::NetLog* net_log = GetContentClient()->browser()->GetNetLog();
266 if (net_log) {
267 instance_ = new DevToolsNetLogObserver();
268 net_log->AddThreadSafeObserver(instance_, net::NetLog::LOG_ALL_BUT_BYTES);
269 }
270 }
271
Detach()272 void DevToolsNetLogObserver::Detach() {
273 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
274
275 if (instance_) {
276 // Safest not to do this in the destructor to maintain thread safety across
277 // refactorings.
278 instance_->net_log()->RemoveThreadSafeObserver(instance_);
279 delete instance_;
280 instance_ = NULL;
281 }
282 }
283
GetInstance()284 DevToolsNetLogObserver* DevToolsNetLogObserver::GetInstance() {
285 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
286
287 return instance_;
288 }
289
290 // static
PopulateResponseInfo(net::URLRequest * request,ResourceResponse * response)291 void DevToolsNetLogObserver::PopulateResponseInfo(
292 net::URLRequest* request,
293 ResourceResponse* response) {
294 if (!(request->load_flags() & net::LOAD_REPORT_RAW_HEADERS))
295 return;
296
297 uint32 source_id = request->net_log().source().id;
298 DevToolsNetLogObserver* dev_tools_net_log_observer =
299 DevToolsNetLogObserver::GetInstance();
300 if (dev_tools_net_log_observer == NULL)
301 return;
302 response->head.devtools_info =
303 dev_tools_net_log_observer->GetResourceInfo(source_id);
304 }
305
306 // static
GetAndResetEncodedDataLength(net::URLRequest * request)307 int DevToolsNetLogObserver::GetAndResetEncodedDataLength(
308 net::URLRequest* request) {
309 if (!(request->load_flags() & net::LOAD_REPORT_RAW_HEADERS))
310 return -1;
311
312 uint32 source_id = request->net_log().source().id;
313 DevToolsNetLogObserver* dev_tools_net_log_observer =
314 DevToolsNetLogObserver::GetInstance();
315 if (dev_tools_net_log_observer == NULL)
316 return -1;
317
318 RequestToEncodedDataLengthMap::iterator it =
319 dev_tools_net_log_observer->request_to_encoded_data_length_.find(
320 source_id);
321 if (it == dev_tools_net_log_observer->request_to_encoded_data_length_.end())
322 return -1;
323 int encoded_data_length = it->second;
324 it->second = 0;
325 return encoded_data_length;
326 }
327
328 } // namespace content
329