Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / devtools_http_handler / devtools_http_handler.cc
blobfc4bbaea3d37a4003f15a7a3436b096e85dc027b
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.
5 #include <algorithm>
6 #include <utility>
8 #include "base/bind.h"
9 #include "base/compiler_specific.h"
10 #include "base/files/file_util.h"
11 #include "base/json/json_writer.h"
12 #include "base/location.h"
13 #include "base/logging.h"
14 #include "base/single_thread_task_runner.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/threading/thread.h"
20 #include "base/values.h"
21 #include "components/devtools_discovery/devtools_discovery_manager.h"
22 #include "components/devtools_http_handler/devtools_http_handler.h"
23 #include "components/devtools_http_handler/devtools_http_handler_delegate.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/devtools_agent_host.h"
26 #include "content/public/browser/devtools_external_agent_proxy_delegate.h"
27 #include "content/public/common/url_constants.h"
28 #include "content/public/common/user_agent.h"
29 #include "net/base/escape.h"
30 #include "net/base/io_buffer.h"
31 #include "net/base/ip_endpoint.h"
32 #include "net/base/net_errors.h"
33 #include "net/server/http_server.h"
34 #include "net/server/http_server_request_info.h"
35 #include "net/server/http_server_response_info.h"
36 #include "net/socket/server_socket.h"
38 #if defined(OS_ANDROID)
39 #include "base/android/build_info.h"
40 #endif
42 using content::BrowserThread;
43 using content::DevToolsAgentHost;
44 using content::DevToolsAgentHostClient;
45 using devtools_discovery::DevToolsTargetDescriptor;
47 namespace devtools_http_handler {
49 namespace {
51 const base::FilePath::CharType kDevToolsActivePortFileName[] =
52 FILE_PATH_LITERAL("DevToolsActivePort");
54 const char kDevToolsHandlerThreadName[] = "Chrome_DevToolsHandlerThread";
56 const char kThumbUrlPrefix[] = "/thumb/";
57 const char kPageUrlPrefix[] = "/devtools/page/";
59 const char kTargetIdField[] = "id";
60 const char kTargetParentIdField[] = "parentId";
61 const char kTargetTypeField[] = "type";
62 const char kTargetTitleField[] = "title";
63 const char kTargetDescriptionField[] = "description";
64 const char kTargetUrlField[] = "url";
65 const char kTargetThumbnailUrlField[] = "thumbnailUrl";
66 const char kTargetFaviconUrlField[] = "faviconUrl";
67 const char kTargetWebSocketDebuggerUrlField[] = "webSocketDebuggerUrl";
68 const char kTargetDevtoolsFrontendUrlField[] = "devtoolsFrontendUrl";
70 // Maximum write buffer size of devtools http/websocket connections.
71 // TODO(rmcilroy/pfieldman): Reduce this back to 100Mb when we have
72 // added back pressure on the TraceComplete message protocol - crbug.com/456845.
73 const int32 kSendBufferSizeForDevTools = 256 * 1024 * 1024; // 256Mb
75 } // namespace
77 // ServerWrapper -------------------------------------------------------------
78 // All methods in this class are only called on handler thread.
79 class ServerWrapper : net::HttpServer::Delegate {
80 public:
81 ServerWrapper(base::WeakPtr<DevToolsHttpHandler> handler,
82 scoped_ptr<net::ServerSocket> socket,
83 const base::FilePath& frontend_dir,
84 bool bundles_resources);
86 int GetLocalAddress(net::IPEndPoint* address);
88 void AcceptWebSocket(int connection_id,
89 const net::HttpServerRequestInfo& request);
90 void SendOverWebSocket(int connection_id, const std::string& message);
91 void SendResponse(int connection_id,
92 const net::HttpServerResponseInfo& response);
93 void Send200(int connection_id,
94 const std::string& data,
95 const std::string& mime_type);
96 void Send404(int connection_id);
97 void Send500(int connection_id, const std::string& message);
98 void Close(int connection_id);
100 void WriteActivePortToUserProfile(const base::FilePath& output_directory);
102 ~ServerWrapper() override {}
104 private:
105 // net::HttpServer::Delegate implementation.
106 void OnConnect(int connection_id) override {}
107 void OnHttpRequest(int connection_id,
108 const net::HttpServerRequestInfo& info) override;
109 void OnWebSocketRequest(int connection_id,
110 const net::HttpServerRequestInfo& info) override;
111 void OnWebSocketMessage(int connection_id,
112 const std::string& data) override;
113 void OnClose(int connection_id) override;
115 base::WeakPtr<DevToolsHttpHandler> handler_;
116 scoped_ptr<net::HttpServer> server_;
117 base::FilePath frontend_dir_;
118 bool bundles_resources_;
121 ServerWrapper::ServerWrapper(base::WeakPtr<DevToolsHttpHandler> handler,
122 scoped_ptr<net::ServerSocket> socket,
123 const base::FilePath& frontend_dir,
124 bool bundles_resources)
125 : handler_(handler),
126 server_(new net::HttpServer(socket.Pass(), this)),
127 frontend_dir_(frontend_dir),
128 bundles_resources_(bundles_resources) {
131 int ServerWrapper::GetLocalAddress(net::IPEndPoint* address) {
132 return server_->GetLocalAddress(address);
135 void ServerWrapper::AcceptWebSocket(int connection_id,
136 const net::HttpServerRequestInfo& request) {
137 server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools);
138 server_->AcceptWebSocket(connection_id, request);
141 void ServerWrapper::SendOverWebSocket(int connection_id,
142 const std::string& message) {
143 server_->SendOverWebSocket(connection_id, message);
146 void ServerWrapper::SendResponse(int connection_id,
147 const net::HttpServerResponseInfo& response) {
148 server_->SendResponse(connection_id, response);
151 void ServerWrapper::Send200(int connection_id,
152 const std::string& data,
153 const std::string& mime_type) {
154 server_->Send200(connection_id, data, mime_type);
157 void ServerWrapper::Send404(int connection_id) {
158 server_->Send404(connection_id);
161 void ServerWrapper::Send500(int connection_id,
162 const std::string& message) {
163 server_->Send500(connection_id, message);
166 void ServerWrapper::Close(int connection_id) {
167 server_->Close(connection_id);
170 // Thread and ServerWrapper lifetime management ------------------------------
172 void TerminateOnUI(base::Thread* thread,
173 ServerWrapper* server_wrapper,
174 DevToolsHttpHandler::ServerSocketFactory* socket_factory) {
175 DCHECK_CURRENTLY_ON(BrowserThread::UI);
176 if (server_wrapper) {
177 DCHECK(thread);
178 thread->message_loop()->DeleteSoon(FROM_HERE, server_wrapper);
180 if (socket_factory) {
181 DCHECK(thread);
182 thread->message_loop()->DeleteSoon(FROM_HERE, socket_factory);
184 if (thread) {
185 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, thread);
189 void ServerStartedOnUI(
190 base::WeakPtr<DevToolsHttpHandler> handler,
191 base::Thread* thread,
192 ServerWrapper* server_wrapper,
193 DevToolsHttpHandler::ServerSocketFactory* socket_factory,
194 scoped_ptr<net::IPEndPoint> ip_address) {
195 DCHECK_CURRENTLY_ON(BrowserThread::UI);
196 if (handler && thread && server_wrapper) {
197 handler->ServerStarted(thread, server_wrapper, socket_factory,
198 ip_address.Pass());
199 } else {
200 TerminateOnUI(thread, server_wrapper, socket_factory);
204 void StartServerOnHandlerThread(
205 base::WeakPtr<DevToolsHttpHandler> handler,
206 base::Thread* thread,
207 DevToolsHttpHandler::ServerSocketFactory* server_socket_factory,
208 const base::FilePath& output_directory,
209 const base::FilePath& frontend_dir,
210 bool bundles_resources) {
211 DCHECK_EQ(thread->message_loop(), base::MessageLoop::current());
212 ServerWrapper* server_wrapper = nullptr;
213 scoped_ptr<net::ServerSocket> server_socket =
214 server_socket_factory->CreateForHttpServer();
215 scoped_ptr<net::IPEndPoint> ip_address(new net::IPEndPoint);
216 if (server_socket) {
217 server_wrapper = new ServerWrapper(handler, server_socket.Pass(),
218 frontend_dir, bundles_resources);
219 if (!output_directory.empty())
220 server_wrapper->WriteActivePortToUserProfile(output_directory);
222 if (server_wrapper->GetLocalAddress(ip_address.get()) != net::OK)
223 ip_address.reset();
224 } else {
225 ip_address.reset();
226 LOG(ERROR) << "Cannot start http server for devtools. Stop devtools.";
228 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
229 base::Bind(&ServerStartedOnUI,
230 handler,
231 thread,
232 server_wrapper,
233 server_socket_factory,
234 base::Passed(&ip_address)));
237 void StartServerOnFile(
238 base::WeakPtr<DevToolsHttpHandler> handler,
239 DevToolsHttpHandler::ServerSocketFactory* server_socket_factory,
240 const base::FilePath& output_directory,
241 const base::FilePath& frontend_dir,
242 bool bundles_resources) {
243 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
244 scoped_ptr<base::Thread> thread(new base::Thread(kDevToolsHandlerThreadName));
245 base::Thread::Options options;
246 options.message_loop_type = base::MessageLoop::TYPE_IO;
247 if (thread->StartWithOptions(options)) {
248 base::MessageLoop* message_loop = thread->message_loop();
249 message_loop->task_runner()->PostTask(
250 FROM_HERE,
251 base::Bind(&StartServerOnHandlerThread, handler,
252 base::Unretained(thread.release()), server_socket_factory,
253 output_directory, frontend_dir, bundles_resources));
257 // DevToolsAgentHostClientImpl -----------------------------------------------
258 // An internal implementation of DevToolsAgentHostClient that delegates
259 // messages sent to a DebuggerShell instance.
260 class DevToolsAgentHostClientImpl : public DevToolsAgentHostClient {
261 public:
262 DevToolsAgentHostClientImpl(base::MessageLoop* message_loop,
263 ServerWrapper* server_wrapper,
264 int connection_id,
265 scoped_refptr<DevToolsAgentHost> agent_host)
266 : message_loop_(message_loop),
267 server_wrapper_(server_wrapper),
268 connection_id_(connection_id),
269 agent_host_(agent_host) {
270 DCHECK_CURRENTLY_ON(BrowserThread::UI);
271 agent_host_->AttachClient(this);
274 ~DevToolsAgentHostClientImpl() override {
275 DCHECK_CURRENTLY_ON(BrowserThread::UI);
276 if (agent_host_.get())
277 agent_host_->DetachClient();
280 void AgentHostClosed(DevToolsAgentHost* agent_host,
281 bool replaced_with_another_client) override {
282 DCHECK_CURRENTLY_ON(BrowserThread::UI);
283 DCHECK(agent_host == agent_host_.get());
285 std::string message = base::StringPrintf(
286 "{ \"method\": \"Inspector.detached\", "
287 "\"params\": { \"reason\": \"%s\"} }",
288 replaced_with_another_client ?
289 "replaced_with_devtools" : "target_closed");
290 DispatchProtocolMessage(agent_host, message);
292 agent_host_ = nullptr;
293 message_loop_->task_runner()->PostTask(
294 FROM_HERE,
295 base::Bind(&ServerWrapper::Close, base::Unretained(server_wrapper_),
296 connection_id_));
299 void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
300 const std::string& message) override {
301 DCHECK_CURRENTLY_ON(BrowserThread::UI);
302 DCHECK(agent_host == agent_host_.get());
303 message_loop_->task_runner()->PostTask(
304 FROM_HERE,
305 base::Bind(&ServerWrapper::SendOverWebSocket,
306 base::Unretained(server_wrapper_), connection_id_, message));
309 void OnMessage(const std::string& message) {
310 DCHECK_CURRENTLY_ON(BrowserThread::UI);
311 if (agent_host_.get())
312 agent_host_->DispatchProtocolMessage(message);
315 private:
316 base::MessageLoop* const message_loop_;
317 ServerWrapper* const server_wrapper_;
318 const int connection_id_;
319 scoped_refptr<DevToolsAgentHost> agent_host_;
322 static bool TimeComparator(const DevToolsTargetDescriptor* desc1,
323 const DevToolsTargetDescriptor* desc2) {
324 return desc1->GetLastActivityTime() > desc2->GetLastActivityTime();
327 // DevToolsHttpHandler::ServerSocketFactory ----------------------------------
329 scoped_ptr<net::ServerSocket>
330 DevToolsHttpHandler::ServerSocketFactory::CreateForHttpServer() {
331 return scoped_ptr<net::ServerSocket>();
334 scoped_ptr<net::ServerSocket>
335 DevToolsHttpHandler::ServerSocketFactory::CreateForTethering(
336 std::string* name) {
337 return scoped_ptr<net::ServerSocket>();
340 // DevToolsHttpHandler -------------------------------------------------------
342 DevToolsHttpHandler::~DevToolsHttpHandler() {
343 TerminateOnUI(thread_, server_wrapper_, socket_factory_);
344 STLDeleteValues(&descriptor_map_);
345 STLDeleteValues(&connection_to_client_);
348 GURL DevToolsHttpHandler::GetFrontendURL(const std::string& path) {
349 if (!server_ip_address_)
350 return GURL();
351 return GURL(std::string("http://") + server_ip_address_->ToString() +
352 (path.empty() ? frontend_url_ : path));
355 static std::string PathWithoutParams(const std::string& path) {
356 size_t query_position = path.find("?");
357 if (query_position != std::string::npos)
358 return path.substr(0, query_position);
359 return path;
362 static std::string GetMimeType(const std::string& filename) {
363 if (base::EndsWith(filename, ".html", base::CompareCase::INSENSITIVE_ASCII)) {
364 return "text/html";
365 } else if (base::EndsWith(filename, ".css",
366 base::CompareCase::INSENSITIVE_ASCII)) {
367 return "text/css";
368 } else if (base::EndsWith(filename, ".js",
369 base::CompareCase::INSENSITIVE_ASCII)) {
370 return "application/javascript";
371 } else if (base::EndsWith(filename, ".png",
372 base::CompareCase::INSENSITIVE_ASCII)) {
373 return "image/png";
374 } else if (base::EndsWith(filename, ".gif",
375 base::CompareCase::INSENSITIVE_ASCII)) {
376 return "image/gif";
377 } else if (base::EndsWith(filename, ".json",
378 base::CompareCase::INSENSITIVE_ASCII)) {
379 return "application/json";
381 LOG(ERROR) << "GetMimeType doesn't know mime type for: "
382 << filename
383 << " text/plain will be returned";
384 NOTREACHED();
385 return "text/plain";
388 void ServerWrapper::OnHttpRequest(int connection_id,
389 const net::HttpServerRequestInfo& info) {
390 server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools);
392 if (info.path.find("/json") == 0) {
393 BrowserThread::PostTask(
394 BrowserThread::UI,
395 FROM_HERE,
396 base::Bind(&DevToolsHttpHandler::OnJsonRequest,
397 handler_,
398 connection_id,
399 info));
400 return;
403 if (info.path.find(kThumbUrlPrefix) == 0) {
404 // Thumbnail request.
405 const std::string target_id = info.path.substr(strlen(kThumbUrlPrefix));
406 BrowserThread::PostTask(
407 BrowserThread::UI,
408 FROM_HERE,
409 base::Bind(&DevToolsHttpHandler::OnThumbnailRequest,
410 handler_,
411 connection_id,
412 target_id));
413 return;
416 if (info.path.empty() || info.path == "/") {
417 // Discovery page request.
418 BrowserThread::PostTask(
419 BrowserThread::UI,
420 FROM_HERE,
421 base::Bind(&DevToolsHttpHandler::OnDiscoveryPageRequest,
422 handler_,
423 connection_id));
424 return;
427 if (info.path.find("/devtools/") != 0) {
428 server_->Send404(connection_id);
429 return;
432 std::string filename = PathWithoutParams(info.path.substr(10));
433 std::string mime_type = GetMimeType(filename);
435 if (!frontend_dir_.empty()) {
436 base::FilePath path = frontend_dir_.AppendASCII(filename);
437 std::string data;
438 base::ReadFileToString(path, &data);
439 server_->Send200(connection_id, data, mime_type);
440 return;
443 if (bundles_resources_) {
444 BrowserThread::PostTask(
445 BrowserThread::UI,
446 FROM_HERE,
447 base::Bind(&DevToolsHttpHandler::OnFrontendResourceRequest,
448 handler_,
449 connection_id,
450 filename));
451 return;
453 server_->Send404(connection_id);
456 void ServerWrapper::OnWebSocketRequest(
457 int connection_id,
458 const net::HttpServerRequestInfo& request) {
459 BrowserThread::PostTask(
460 BrowserThread::UI,
461 FROM_HERE,
462 base::Bind(
463 &DevToolsHttpHandler::OnWebSocketRequest,
464 handler_,
465 connection_id,
466 request));
469 void ServerWrapper::OnWebSocketMessage(int connection_id,
470 const std::string& data) {
471 BrowserThread::PostTask(
472 BrowserThread::UI,
473 FROM_HERE,
474 base::Bind(
475 &DevToolsHttpHandler::OnWebSocketMessage,
476 handler_,
477 connection_id,
478 data));
481 void ServerWrapper::OnClose(int connection_id) {
482 BrowserThread::PostTask(
483 BrowserThread::UI,
484 FROM_HERE,
485 base::Bind(
486 &DevToolsHttpHandler::OnClose,
487 handler_,
488 connection_id));
491 std::string DevToolsHttpHandler::GetFrontendURLInternal(
492 const std::string id,
493 const std::string& host) {
494 return base::StringPrintf(
495 "%s%sws=%s%s%s",
496 frontend_url_.c_str(),
497 frontend_url_.find("?") == std::string::npos ? "?" : "&",
498 host.c_str(),
499 kPageUrlPrefix,
500 id.c_str());
503 static bool ParseJsonPath(
504 const std::string& path,
505 std::string* command,
506 std::string* target_id) {
508 // Fall back to list in case of empty query.
509 if (path.empty()) {
510 *command = "list";
511 return true;
514 if (path.find("/") != 0) {
515 // Malformed command.
516 return false;
518 *command = path.substr(1);
520 size_t separator_pos = command->find("/");
521 if (separator_pos != std::string::npos) {
522 *target_id = command->substr(separator_pos + 1);
523 *command = command->substr(0, separator_pos);
525 return true;
528 void DevToolsHttpHandler::OnJsonRequest(
529 int connection_id,
530 const net::HttpServerRequestInfo& info) {
531 // Trim /json
532 std::string path = info.path.substr(5);
534 // Trim fragment and query
535 std::string query;
536 size_t query_pos = path.find("?");
537 if (query_pos != std::string::npos) {
538 query = path.substr(query_pos + 1);
539 path = path.substr(0, query_pos);
542 size_t fragment_pos = path.find("#");
543 if (fragment_pos != std::string::npos)
544 path = path.substr(0, fragment_pos);
546 std::string command;
547 std::string target_id;
548 if (!ParseJsonPath(path, &command, &target_id)) {
549 SendJson(connection_id,
550 net::HTTP_NOT_FOUND,
551 NULL,
552 "Malformed query: " + info.path);
553 return;
556 if (command == "version") {
557 base::DictionaryValue version;
558 version.SetString("Protocol-Version",
559 DevToolsAgentHost::GetProtocolVersion().c_str());
560 version.SetString("WebKit-Version", content::GetWebKitVersion());
561 version.SetString("Browser", product_name_);
562 version.SetString("User-Agent", user_agent_);
563 #if defined(OS_ANDROID)
564 version.SetString("Android-Package",
565 base::android::BuildInfo::GetInstance()->package_name());
566 #endif
567 SendJson(connection_id, net::HTTP_OK, &version, std::string());
568 return;
571 if (command == "list") {
572 std::string host = info.headers["host"];
573 DevToolsTargetDescriptor::List descriptors =
574 devtools_discovery::DevToolsDiscoveryManager::GetInstance()->
575 GetDescriptors();
576 std::sort(descriptors.begin(), descriptors.end(), TimeComparator);
577 STLDeleteValues(&descriptor_map_);
578 base::ListValue list_value;
579 for (DevToolsTargetDescriptor* descriptor : descriptors) {
580 descriptor_map_[descriptor->GetId()] = descriptor;
581 list_value.Append(SerializeDescriptor(*descriptor, host));
583 SendJson(connection_id, net::HTTP_OK, &list_value, std::string());
584 return;
587 if (command == "new") {
588 GURL url(net::UnescapeURLComponent(
589 query, net::UnescapeRule::URL_SPECIAL_CHARS));
590 if (!url.is_valid())
591 url = GURL(url::kAboutBlankURL);
592 scoped_ptr<DevToolsTargetDescriptor> descriptor =
593 devtools_discovery::DevToolsDiscoveryManager::GetInstance()->
594 CreateNew(url);
595 if (!descriptor) {
596 SendJson(connection_id,
597 net::HTTP_INTERNAL_SERVER_ERROR,
598 NULL,
599 "Could not create new page");
600 return;
602 std::string host = info.headers["host"];
603 scoped_ptr<base::DictionaryValue> dictionary(
604 SerializeDescriptor(*descriptor.get(), host));
605 SendJson(connection_id, net::HTTP_OK, dictionary.get(), std::string());
606 const std::string target_id = descriptor->GetId();
607 descriptor_map_[target_id] = descriptor.release();
608 return;
611 if (command == "activate" || command == "close") {
612 DevToolsTargetDescriptor* descriptor = GetDescriptor(target_id);
613 if (!descriptor) {
614 SendJson(connection_id,
615 net::HTTP_NOT_FOUND,
616 NULL,
617 "No such target id: " + target_id);
618 return;
621 if (command == "activate") {
622 if (descriptor->Activate()) {
623 SendJson(connection_id, net::HTTP_OK, NULL, "Target activated");
624 } else {
625 SendJson(connection_id,
626 net::HTTP_INTERNAL_SERVER_ERROR,
627 NULL,
628 "Could not activate target id: " + target_id);
630 return;
633 if (command == "close") {
634 if (descriptor->Close()) {
635 SendJson(connection_id, net::HTTP_OK, NULL, "Target is closing");
636 } else {
637 SendJson(connection_id,
638 net::HTTP_INTERNAL_SERVER_ERROR,
639 NULL,
640 "Could not close target id: " + target_id);
642 return;
645 SendJson(connection_id,
646 net::HTTP_NOT_FOUND,
647 NULL,
648 "Unknown command: " + command);
649 return;
652 DevToolsTargetDescriptor* DevToolsHttpHandler::GetDescriptor(
653 const std::string& target_id) {
654 DescriptorMap::const_iterator it = descriptor_map_.find(target_id);
655 if (it == descriptor_map_.end())
656 return nullptr;
657 return it->second;
660 void DevToolsHttpHandler::OnThumbnailRequest(
661 int connection_id, const std::string& target_id) {
662 DevToolsTargetDescriptor* descriptor = GetDescriptor(target_id);
663 GURL page_url;
664 if (descriptor)
665 page_url = descriptor->GetURL();
666 std::string data = delegate_->GetPageThumbnailData(page_url);
667 if (!data.empty())
668 Send200(connection_id, data, "image/png");
669 else
670 Send404(connection_id);
673 void DevToolsHttpHandler::OnDiscoveryPageRequest(int connection_id) {
674 std::string response = delegate_->GetDiscoveryPageHTML();
675 Send200(connection_id, response, "text/html; charset=UTF-8");
678 void DevToolsHttpHandler::OnFrontendResourceRequest(
679 int connection_id, const std::string& path) {
680 Send200(connection_id,
681 delegate_->GetFrontendResource(path),
682 GetMimeType(path));
685 void DevToolsHttpHandler::OnWebSocketRequest(
686 int connection_id,
687 const net::HttpServerRequestInfo& request) {
688 if (!thread_)
689 return;
691 std::string browser_prefix = "/devtools/browser";
692 size_t browser_pos = request.path.find(browser_prefix);
693 if (browser_pos == 0) {
694 scoped_refptr<DevToolsAgentHost> browser_agent =
695 DevToolsAgentHost::CreateForBrowser(
696 thread_->task_runner(),
697 base::Bind(&ServerSocketFactory::CreateForTethering,
698 base::Unretained(socket_factory_)));
699 connection_to_client_[connection_id] = new DevToolsAgentHostClientImpl(
700 thread_->message_loop(), server_wrapper_, connection_id, browser_agent);
701 AcceptWebSocket(connection_id, request);
702 return;
705 // Handle external connections (such as frontend api) on the embedder level.
706 content::DevToolsExternalAgentProxyDelegate* external_delegate =
707 delegate_->HandleWebSocketConnection(request.path);
708 if (external_delegate) {
709 scoped_refptr<DevToolsAgentHost> agent_host =
710 DevToolsAgentHost::Create(external_delegate);
711 connection_to_client_[connection_id] = new DevToolsAgentHostClientImpl(
712 thread_->message_loop(), server_wrapper_, connection_id, agent_host);
713 AcceptWebSocket(connection_id, request);
714 return;
717 size_t pos = request.path.find(kPageUrlPrefix);
718 if (pos != 0) {
719 Send404(connection_id);
720 return;
723 std::string target_id = request.path.substr(strlen(kPageUrlPrefix));
724 DevToolsTargetDescriptor* descriptor = GetDescriptor(target_id);
725 scoped_refptr<DevToolsAgentHost> agent =
726 descriptor ? descriptor->GetAgentHost() : nullptr;
727 if (!agent.get()) {
728 Send500(connection_id, "No such target id: " + target_id);
729 return;
732 if (agent->IsAttached()) {
733 Send500(connection_id,
734 "Target with given id is being inspected: " + target_id);
735 return;
738 DevToolsAgentHostClientImpl* client_host = new DevToolsAgentHostClientImpl(
739 thread_->message_loop(), server_wrapper_, connection_id, agent);
740 connection_to_client_[connection_id] = client_host;
742 AcceptWebSocket(connection_id, request);
745 void DevToolsHttpHandler::OnWebSocketMessage(
746 int connection_id,
747 const std::string& data) {
748 ConnectionToClientMap::iterator it =
749 connection_to_client_.find(connection_id);
750 if (it != connection_to_client_.end())
751 it->second->OnMessage(data);
754 void DevToolsHttpHandler::OnClose(int connection_id) {
755 ConnectionToClientMap::iterator it =
756 connection_to_client_.find(connection_id);
757 if (it != connection_to_client_.end()) {
758 delete it->second;
759 connection_to_client_.erase(connection_id);
763 DevToolsHttpHandler::DevToolsHttpHandler(
764 scoped_ptr<ServerSocketFactory> server_socket_factory,
765 const std::string& frontend_url,
766 DevToolsHttpHandlerDelegate* delegate,
767 const base::FilePath& output_directory,
768 const base::FilePath& debug_frontend_dir,
769 const std::string& product_name,
770 const std::string& user_agent)
771 : thread_(nullptr),
772 frontend_url_(frontend_url),
773 product_name_(product_name),
774 user_agent_(user_agent),
775 server_wrapper_(nullptr),
776 delegate_(delegate),
777 socket_factory_(nullptr),
778 weak_factory_(this) {
779 bool bundles_resources = frontend_url_.empty();
780 if (frontend_url_.empty())
781 frontend_url_ = "/devtools/inspector.html";
783 BrowserThread::PostTask(
784 BrowserThread::FILE, FROM_HERE,
785 base::Bind(&StartServerOnFile,
786 weak_factory_.GetWeakPtr(),
787 server_socket_factory.release(),
788 output_directory,
789 debug_frontend_dir,
790 bundles_resources));
793 void DevToolsHttpHandler::ServerStarted(
794 base::Thread* thread,
795 ServerWrapper* server_wrapper,
796 ServerSocketFactory* socket_factory,
797 scoped_ptr<net::IPEndPoint> ip_address) {
798 thread_ = thread;
799 server_wrapper_ = server_wrapper;
800 socket_factory_ = socket_factory;
801 server_ip_address_.swap(ip_address);
804 void ServerWrapper::WriteActivePortToUserProfile(
805 const base::FilePath& output_directory) {
806 DCHECK(!output_directory.empty());
807 net::IPEndPoint endpoint;
808 int err;
809 if ((err = server_->GetLocalAddress(&endpoint)) != net::OK) {
810 LOG(ERROR) << "Error " << err << " getting local address";
811 return;
814 // Write this port to a well-known file in the profile directory
815 // so Telemetry can pick it up.
816 base::FilePath path = output_directory.Append(kDevToolsActivePortFileName);
817 std::string port_string = base::IntToString(endpoint.port());
818 if (base::WriteFile(path, port_string.c_str(),
819 static_cast<int>(port_string.length())) < 0) {
820 LOG(ERROR) << "Error writing DevTools active port to file";
824 void DevToolsHttpHandler::SendJson(int connection_id,
825 net::HttpStatusCode status_code,
826 base::Value* value,
827 const std::string& message) {
828 if (!thread_)
829 return;
831 // Serialize value and message.
832 std::string json_value;
833 if (value) {
834 base::JSONWriter::WriteWithOptions(
835 *value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_value);
837 std::string json_message;
838 base::JSONWriter::Write(base::StringValue(message), &json_message);
840 net::HttpServerResponseInfo response(status_code);
841 response.SetBody(json_value + message, "application/json; charset=UTF-8");
843 thread_->task_runner()->PostTask(
844 FROM_HERE,
845 base::Bind(&ServerWrapper::SendResponse,
846 base::Unretained(server_wrapper_), connection_id, response));
849 void DevToolsHttpHandler::Send200(int connection_id,
850 const std::string& data,
851 const std::string& mime_type) {
852 if (!thread_)
853 return;
854 thread_->task_runner()->PostTask(
855 FROM_HERE,
856 base::Bind(&ServerWrapper::Send200, base::Unretained(server_wrapper_),
857 connection_id, data, mime_type));
860 void DevToolsHttpHandler::Send404(int connection_id) {
861 if (!thread_)
862 return;
863 thread_->task_runner()->PostTask(
864 FROM_HERE, base::Bind(&ServerWrapper::Send404,
865 base::Unretained(server_wrapper_), connection_id));
868 void DevToolsHttpHandler::Send500(int connection_id,
869 const std::string& message) {
870 if (!thread_)
871 return;
872 thread_->task_runner()->PostTask(
873 FROM_HERE,
874 base::Bind(&ServerWrapper::Send500, base::Unretained(server_wrapper_),
875 connection_id, message));
878 void DevToolsHttpHandler::AcceptWebSocket(
879 int connection_id,
880 const net::HttpServerRequestInfo& request) {
881 if (!thread_)
882 return;
883 thread_->task_runner()->PostTask(
884 FROM_HERE,
885 base::Bind(&ServerWrapper::AcceptWebSocket,
886 base::Unretained(server_wrapper_), connection_id, request));
889 base::DictionaryValue* DevToolsHttpHandler::SerializeDescriptor(
890 const DevToolsTargetDescriptor& descriptor,
891 const std::string& host) {
892 base::DictionaryValue* dictionary = new base::DictionaryValue;
894 std::string id = descriptor.GetId();
895 dictionary->SetString(kTargetIdField, id);
896 std::string parent_id = descriptor.GetParentId();
897 if (!parent_id.empty())
898 dictionary->SetString(kTargetParentIdField, parent_id);
899 dictionary->SetString(kTargetTypeField, descriptor.GetType());
900 dictionary->SetString(kTargetTitleField,
901 net::EscapeForHTML(descriptor.GetTitle()));
902 dictionary->SetString(kTargetDescriptionField, descriptor.GetDescription());
904 GURL url = descriptor.GetURL();
905 dictionary->SetString(kTargetUrlField, url.spec());
907 GURL favicon_url = descriptor.GetFaviconURL();
908 if (favicon_url.is_valid())
909 dictionary->SetString(kTargetFaviconUrlField, favicon_url.spec());
911 if (!delegate_->GetPageThumbnailData(url).empty()) {
912 dictionary->SetString(kTargetThumbnailUrlField,
913 std::string(kThumbUrlPrefix) + id);
916 if (!descriptor.IsAttached()) {
917 dictionary->SetString(kTargetWebSocketDebuggerUrlField,
918 base::StringPrintf("ws://%s%s%s",
919 host.c_str(),
920 kPageUrlPrefix,
921 id.c_str()));
922 std::string devtools_frontend_url = GetFrontendURLInternal(
923 id.c_str(),
924 host);
925 dictionary->SetString(
926 kTargetDevtoolsFrontendUrlField, devtools_frontend_url);
929 return dictionary;
932 } // namespace devtools_http_handler