1 // Copyright (c) 2011 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 "chrome/browser/debugger/devtools_http_protocol_handler.h" 6 7 #include <utility> 8 9 #include "base/compiler_specific.h" 10 #include "base/json/json_writer.h" 11 #include "base/logging.h" 12 #include "base/message_loop_proxy.h" 13 #include "base/string_number_conversions.h" 14 #include "base/threading/thread.h" 15 #include "base/utf_string_conversions.h" 16 #include "base/values.h" 17 #include "chrome/browser/debugger/devtools_client_host.h" 18 #include "chrome/browser/debugger/devtools_manager.h" 19 #include "chrome/browser/profiles/profile.h" 20 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 21 #include "chrome/browser/ui/webui/devtools_ui.h" 22 #include "chrome/common/devtools_messages.h" 23 #include "content/browser/browser_thread.h" 24 #include "content/browser/tab_contents/tab_contents.h" 25 #include "googleurl/src/gurl.h" 26 #include "net/base/io_buffer.h" 27 #include "net/server/http_server_request_info.h" 28 #include "net/url_request/url_request_context.h" 29 #include "net/url_request/url_request_context_getter.h" 30 31 const int kBufferSize = 16 * 1024; 32 33 namespace { 34 35 // An internal implementation of DevToolsClientHost that delegates 36 // messages sent for DevToolsClient to a DebuggerShell instance. 37 class DevToolsClientHostImpl : public DevToolsClientHost { 38 public: 39 DevToolsClientHostImpl( 40 net::HttpServer* server, 41 int connection_id) 42 : server_(server), 43 connection_id_(connection_id) { 44 } 45 ~DevToolsClientHostImpl() {} 46 47 // DevToolsClientHost interface 48 virtual void InspectedTabClosing() { 49 BrowserThread::PostTask( 50 BrowserThread::IO, 51 FROM_HERE, 52 NewRunnableMethod(server_, 53 &net::HttpServer::Close, 54 connection_id_)); 55 } 56 57 virtual void SendMessageToClient(const IPC::Message& msg) { 58 IPC_BEGIN_MESSAGE_MAP(DevToolsClientHostImpl, msg) 59 IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend, 60 OnDispatchOnInspectorFrontend); 61 IPC_MESSAGE_UNHANDLED_ERROR() 62 IPC_END_MESSAGE_MAP() 63 } 64 65 virtual void TabReplaced(TabContentsWrapper* new_tab) { 66 } 67 68 void NotifyCloseListener() { 69 DevToolsClientHost::NotifyCloseListener(); 70 } 71 private: 72 // Message handling routines 73 void OnDispatchOnInspectorFrontend(const std::string& data) { 74 BrowserThread::PostTask( 75 BrowserThread::IO, 76 FROM_HERE, 77 NewRunnableMethod(server_, 78 &net::HttpServer::SendOverWebSocket, 79 connection_id_, 80 data)); 81 } 82 83 virtual void FrameNavigating(const std::string& url) {} 84 net::HttpServer* server_; 85 int connection_id_; 86 }; 87 88 } // namespace 89 90 91 // static 92 scoped_refptr<DevToolsHttpProtocolHandler> DevToolsHttpProtocolHandler::Start( 93 const std::string& ip, 94 int port, 95 const std::string& frontend_url, 96 TabContentsProvider* provider) { 97 scoped_refptr<DevToolsHttpProtocolHandler> http_handler = 98 new DevToolsHttpProtocolHandler(ip, port, frontend_url, provider); 99 http_handler->Start(); 100 return http_handler; 101 } 102 103 DevToolsHttpProtocolHandler::~DevToolsHttpProtocolHandler() { 104 // Stop() must be called prior to this being called 105 DCHECK(server_.get() == NULL); 106 } 107 108 void DevToolsHttpProtocolHandler::Start() { 109 BrowserThread::PostTask( 110 BrowserThread::IO, FROM_HERE, 111 NewRunnableMethod(this, &DevToolsHttpProtocolHandler::Init)); 112 } 113 114 void DevToolsHttpProtocolHandler::Stop() { 115 BrowserThread::PostTask( 116 BrowserThread::IO, FROM_HERE, 117 NewRunnableMethod(this, &DevToolsHttpProtocolHandler::Teardown)); 118 } 119 120 void DevToolsHttpProtocolHandler::OnHttpRequest( 121 int connection_id, 122 const net::HttpServerRequestInfo& info) { 123 if (info.path == "" || info.path == "/") { 124 // Pages discovery request. 125 BrowserThread::PostTask( 126 BrowserThread::UI, 127 FROM_HERE, 128 NewRunnableMethod(this, 129 &DevToolsHttpProtocolHandler::OnRootRequestUI, 130 connection_id, 131 info)); 132 return; 133 } 134 135 if (info.path == "/json") { 136 // Pages discovery json request. 137 BrowserThread::PostTask( 138 BrowserThread::UI, 139 FROM_HERE, 140 NewRunnableMethod(this, 141 &DevToolsHttpProtocolHandler::OnJsonRequestUI, 142 connection_id, 143 info)); 144 return; 145 } 146 147 size_t pos = info.path.find("/devtools/"); 148 if (pos != 0) { 149 server_->Send404(connection_id); 150 return; 151 } 152 153 // Proxy static files from chrome-devtools://devtools/*. 154 if (!Profile::GetDefaultRequestContext()) { 155 server_->Send404(connection_id); 156 return; 157 } 158 159 // Make sure DevTools data source is registered. 160 DevToolsUI::RegisterDevToolsDataSource(); 161 162 net::URLRequest* request = new net::URLRequest( 163 GURL("chrome-devtools:/" + info.path), this); 164 Bind(request, connection_id); 165 request->set_context( 166 Profile::GetDefaultRequestContext()->GetURLRequestContext()); 167 request->Start(); 168 } 169 170 void DevToolsHttpProtocolHandler::OnWebSocketRequest( 171 int connection_id, 172 const net::HttpServerRequestInfo& request) { 173 BrowserThread::PostTask( 174 BrowserThread::UI, 175 FROM_HERE, 176 NewRunnableMethod( 177 this, 178 &DevToolsHttpProtocolHandler::OnWebSocketRequestUI, 179 connection_id, 180 request)); 181 } 182 183 void DevToolsHttpProtocolHandler::OnWebSocketMessage( 184 int connection_id, 185 const std::string& data) { 186 BrowserThread::PostTask( 187 BrowserThread::UI, 188 FROM_HERE, 189 NewRunnableMethod( 190 this, 191 &DevToolsHttpProtocolHandler::OnWebSocketMessageUI, 192 connection_id, 193 data)); 194 } 195 196 void DevToolsHttpProtocolHandler::OnClose(int connection_id) { 197 ConnectionToRequestsMap::iterator it = 198 connection_to_requests_io_.find(connection_id); 199 if (it != connection_to_requests_io_.end()) { 200 // Dispose delegating socket. 201 for (std::set<net::URLRequest*>::iterator it2 = it->second.begin(); 202 it2 != it->second.end(); ++it2) { 203 net::URLRequest* request = *it2; 204 request->Cancel(); 205 request_to_connection_io_.erase(request); 206 request_to_buffer_io_.erase(request); 207 delete request; 208 } 209 connection_to_requests_io_.erase(connection_id); 210 } 211 212 BrowserThread::PostTask( 213 BrowserThread::UI, 214 FROM_HERE, 215 NewRunnableMethod( 216 this, 217 &DevToolsHttpProtocolHandler::OnCloseUI, 218 connection_id)); 219 } 220 221 struct PageInfo 222 { 223 int id; 224 std::string url; 225 bool attached; 226 std::string title; 227 std::string favicon_url; 228 }; 229 typedef std::vector<PageInfo> PageList; 230 231 static PageList GeneratePageList( 232 DevToolsHttpProtocolHandler::TabContentsProvider* tab_contents_provider, 233 int connection_id, 234 const net::HttpServerRequestInfo& info) { 235 typedef DevToolsHttpProtocolHandler::InspectableTabs Tabs; 236 Tabs inspectable_tabs = tab_contents_provider->GetInspectableTabs(); 237 238 PageList page_list; 239 for (Tabs::iterator it = inspectable_tabs.begin(); 240 it != inspectable_tabs.end(); ++it) { 241 242 TabContentsWrapper* tab_contents = *it; 243 NavigationController& controller = tab_contents->controller(); 244 245 NavigationEntry* entry = controller.GetActiveEntry(); 246 if (entry == NULL || !entry->url().is_valid()) 247 continue; 248 249 DevToolsClientHost* client_host = DevToolsManager::GetInstance()-> 250 GetDevToolsClientHostFor(tab_contents->tab_contents()-> 251 render_view_host()); 252 PageInfo page_info; 253 page_info.id = controller.session_id().id(); 254 page_info.attached = client_host != NULL; 255 page_info.url = entry->url().spec(); 256 page_info.title = UTF16ToUTF8(entry->title()); 257 page_info.favicon_url = entry->favicon().url().spec(); 258 page_list.push_back(page_info); 259 } 260 return page_list; 261 } 262 263 void DevToolsHttpProtocolHandler::OnRootRequestUI( 264 int connection_id, 265 const net::HttpServerRequestInfo& info) { 266 std::string host = info.headers["Host"]; 267 std::string response = "<html><body>"; 268 PageList page_list = GeneratePageList(tab_contents_provider_.get(), 269 connection_id, info); 270 for (PageList::iterator i = page_list.begin(); 271 i != page_list.end(); ++i) { 272 273 std::string frontendURL = StringPrintf("%s?host=%s&page=%d", 274 overriden_frontend_url_.c_str(), 275 host.c_str(), 276 i->id); 277 response += "<div>"; 278 response += StringPrintf( 279 "<img style=\"margin-right:5px;width:16px;height:16px\" src=\"%s\">", 280 i->favicon_url.c_str()); 281 282 if (i->attached) { 283 response += i->url.c_str(); 284 } else { 285 response += StringPrintf("<a href=\"%s\">%s</a><br>", 286 frontendURL.c_str(), 287 i->url.c_str()); 288 } 289 response += "</div>"; 290 } 291 response += "</body></html>"; 292 Send200(connection_id, response, "text/html; charset=UTF-8"); 293 } 294 295 void DevToolsHttpProtocolHandler::OnJsonRequestUI( 296 int connection_id, 297 const net::HttpServerRequestInfo& info) { 298 PageList page_list = GeneratePageList(tab_contents_provider_.get(), 299 connection_id, info); 300 ListValue json_pages_list; 301 std::string host = info.headers["Host"]; 302 for (PageList::iterator i = page_list.begin(); 303 i != page_list.end(); ++i) { 304 305 DictionaryValue* page_info = new DictionaryValue; 306 json_pages_list.Append(page_info); 307 page_info->SetString("title", i->title); 308 page_info->SetString("url", i->url); 309 page_info->SetString("faviconUrl", i->favicon_url); 310 if (!i->attached) { 311 page_info->SetString("webSocketDebuggerUrl", 312 StringPrintf("ws://%s/devtools/page/%d", 313 host.c_str(), 314 i->id)); 315 page_info->SetString( 316 "devtoolsFrontendUrl", 317 StringPrintf("http://%s/devtools/devtools.html?page=%d", 318 host.c_str(), 319 i->id)); 320 } 321 } 322 323 std::string response; 324 base::JSONWriter::Write(&json_pages_list, true, &response); 325 Send200(connection_id, response, "application/json; charset=UTF-8"); 326 } 327 328 void DevToolsHttpProtocolHandler::OnWebSocketRequestUI( 329 int connection_id, 330 const net::HttpServerRequestInfo& request) { 331 std::string prefix = "/devtools/page/"; 332 size_t pos = request.path.find(prefix); 333 if (pos != 0) { 334 Send404(connection_id); 335 return; 336 } 337 std::string page_id = request.path.substr(prefix.length()); 338 int id = 0; 339 if (!base::StringToInt(page_id, &id)) { 340 Send500(connection_id, "Invalid page id: " + page_id); 341 return; 342 } 343 344 TabContents* tab_contents = GetTabContents(id); 345 if (tab_contents == NULL) { 346 Send500(connection_id, "No such page id: " + page_id); 347 return; 348 } 349 350 DevToolsManager* manager = DevToolsManager::GetInstance(); 351 if (manager->GetDevToolsClientHostFor(tab_contents->render_view_host())) { 352 Send500(connection_id, "Page with given id is being inspected: " + page_id); 353 return; 354 } 355 356 DevToolsClientHostImpl* client_host = 357 new DevToolsClientHostImpl(server_, connection_id); 358 connection_to_client_host_ui_[connection_id] = client_host; 359 360 manager->RegisterDevToolsClientHostFor( 361 tab_contents->render_view_host(), 362 client_host); 363 manager->ForwardToDevToolsAgent( 364 client_host, 365 DevToolsAgentMsg_FrontendLoaded()); 366 367 AcceptWebSocket(connection_id, request); 368 } 369 370 void DevToolsHttpProtocolHandler::OnWebSocketMessageUI( 371 int connection_id, 372 const std::string& data) { 373 ConnectionToClientHostMap::iterator it = 374 connection_to_client_host_ui_.find(connection_id); 375 if (it == connection_to_client_host_ui_.end()) 376 return; 377 378 DevToolsManager* manager = DevToolsManager::GetInstance(); 379 manager->ForwardToDevToolsAgent( 380 it->second, 381 DevToolsAgentMsg_DispatchOnInspectorBackend(data)); 382 } 383 384 void DevToolsHttpProtocolHandler::OnCloseUI(int connection_id) { 385 ConnectionToClientHostMap::iterator it = 386 connection_to_client_host_ui_.find(connection_id); 387 if (it != connection_to_client_host_ui_.end()) { 388 DevToolsClientHostImpl* client_host = 389 static_cast<DevToolsClientHostImpl*>(it->second); 390 client_host->NotifyCloseListener(); 391 delete client_host; 392 connection_to_client_host_ui_.erase(connection_id); 393 } 394 } 395 396 void DevToolsHttpProtocolHandler::OnResponseStarted(net::URLRequest* request) { 397 RequestToSocketMap::iterator it = request_to_connection_io_.find(request); 398 if (it == request_to_connection_io_.end()) 399 return; 400 401 int connection_id = it->second; 402 403 std::string content_type; 404 request->GetMimeType(&content_type); 405 406 if (request->status().is_success()) { 407 server_->Send(connection_id, StringPrintf("HTTP/1.1 200 OK\r\n" 408 "Content-Type:%s\r\n" 409 "Transfer-Encoding: chunked\r\n" 410 "\r\n", 411 content_type.c_str())); 412 } else { 413 server_->Send404(connection_id); 414 } 415 416 int bytes_read = 0; 417 // Some servers may treat HEAD requests as GET requests. To free up the 418 // network connection as soon as possible, signal that the request has 419 // completed immediately, without trying to read any data back (all we care 420 // about is the response code and headers, which we already have). 421 net::IOBuffer* buffer = request_to_buffer_io_[request].get(); 422 if (request->status().is_success()) 423 request->Read(buffer, kBufferSize, &bytes_read); 424 OnReadCompleted(request, bytes_read); 425 } 426 427 void DevToolsHttpProtocolHandler::OnReadCompleted(net::URLRequest* request, 428 int bytes_read) { 429 RequestToSocketMap::iterator it = request_to_connection_io_.find(request); 430 if (it == request_to_connection_io_.end()) 431 return; 432 433 int connection_id = it->second; 434 435 net::IOBuffer* buffer = request_to_buffer_io_[request].get(); 436 do { 437 if (!request->status().is_success() || bytes_read <= 0) 438 break; 439 std::string chunk_size = StringPrintf("%X\r\n", bytes_read); 440 server_->Send(connection_id, chunk_size); 441 server_->Send(connection_id, buffer->data(), bytes_read); 442 server_->Send(connection_id, "\r\n"); 443 } while (request->Read(buffer, kBufferSize, &bytes_read)); 444 445 446 // See comments re: HEAD requests in OnResponseStarted(). 447 if (!request->status().is_io_pending()) { 448 server_->Send(connection_id, "0\r\n\r\n"); 449 RequestCompleted(request); 450 } 451 } 452 453 DevToolsHttpProtocolHandler::DevToolsHttpProtocolHandler( 454 const std::string& ip, 455 int port, 456 const std::string& frontend_host, 457 TabContentsProvider* provider) 458 : ip_(ip), 459 port_(port), 460 overriden_frontend_url_(frontend_host), 461 tab_contents_provider_(provider) { 462 if (overriden_frontend_url_.empty()) 463 overriden_frontend_url_ = "/devtools/devtools.html"; 464 } 465 466 void DevToolsHttpProtocolHandler::Init() { 467 server_ = new net::HttpServer(ip_, port_, this); 468 } 469 470 // Run on I/O thread 471 void DevToolsHttpProtocolHandler::Teardown() { 472 server_ = NULL; 473 } 474 475 void DevToolsHttpProtocolHandler::Bind(net::URLRequest* request, 476 int connection_id) { 477 request_to_connection_io_[request] = connection_id; 478 ConnectionToRequestsMap::iterator it = 479 connection_to_requests_io_.find(connection_id); 480 if (it == connection_to_requests_io_.end()) { 481 std::pair<int, std::set<net::URLRequest*> > value( 482 connection_id, 483 std::set<net::URLRequest*>()); 484 it = connection_to_requests_io_.insert(value).first; 485 } 486 it->second.insert(request); 487 request_to_buffer_io_[request] = new net::IOBuffer(kBufferSize); 488 } 489 490 void DevToolsHttpProtocolHandler::RequestCompleted(net::URLRequest* request) { 491 RequestToSocketMap::iterator it = request_to_connection_io_.find(request); 492 if (it == request_to_connection_io_.end()) 493 return; 494 495 int connection_id = it->second; 496 request_to_connection_io_.erase(request); 497 ConnectionToRequestsMap::iterator it2 = 498 connection_to_requests_io_.find(connection_id); 499 it2->second.erase(request); 500 request_to_buffer_io_.erase(request); 501 delete request; 502 } 503 504 void DevToolsHttpProtocolHandler::Send200(int connection_id, 505 const std::string& data, 506 const std::string& mime_type) { 507 BrowserThread::PostTask( 508 BrowserThread::IO, FROM_HERE, 509 NewRunnableMethod(server_.get(), 510 &net::HttpServer::Send200, 511 connection_id, 512 data, 513 mime_type)); 514 } 515 516 void DevToolsHttpProtocolHandler::Send404(int connection_id) { 517 BrowserThread::PostTask( 518 BrowserThread::IO, FROM_HERE, 519 NewRunnableMethod(server_.get(), 520 &net::HttpServer::Send404, 521 connection_id)); 522 } 523 524 void DevToolsHttpProtocolHandler::Send500(int connection_id, 525 const std::string& message) { 526 BrowserThread::PostTask( 527 BrowserThread::IO, FROM_HERE, 528 NewRunnableMethod(server_.get(), 529 &net::HttpServer::Send500, 530 connection_id, 531 message)); 532 } 533 534 void DevToolsHttpProtocolHandler::AcceptWebSocket( 535 int connection_id, 536 const net::HttpServerRequestInfo& request) { 537 BrowserThread::PostTask( 538 BrowserThread::IO, FROM_HERE, 539 NewRunnableMethod(server_.get(), 540 &net::HttpServer::AcceptWebSocket, 541 connection_id, 542 request)); 543 } 544 545 TabContents* DevToolsHttpProtocolHandler::GetTabContents(int session_id) { 546 InspectableTabs inspectable_tabs = 547 tab_contents_provider_->GetInspectableTabs(); 548 549 for (InspectableTabs::iterator it = inspectable_tabs.begin(); 550 it != inspectable_tabs.end(); ++it) { 551 TabContentsWrapper* tab_contents = *it; 552 NavigationController& controller = 553 tab_contents->controller(); 554 if (controller.session_id().id() == session_id) 555 return controller.tab_contents(); 556 } 557 return NULL; 558 } 559