Implement multiple alternative services per origin.
[chromium-blink-merge.git] / components / devtools_http_handler / devtools_http_handler.cc
blob16e7d04cf2955170b3204573dd57045c2483f56f
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/common/url_constants.h"
27 #include "content/public/common/user_agent.h"
28 #include "net/base/escape.h"
29 #include "net/base/io_buffer.h"
30 #include "net/base/ip_endpoint.h"
31 #include "net/base/net_errors.h"
32 #include "net/server/http_server.h"
33 #include "net/server/http_server_request_info.h"
34 #include "net/server/http_server_response_info.h"
35 #include "net/socket/server_socket.h"
37 #if defined(OS_ANDROID)
38 #include "base/android/build_info.h"
39 #endif
41 using content::BrowserThread;
42 using content::DevToolsAgentHost;
43 using content::DevToolsAgentHostClient;
44 using devtools_discovery::DevToolsTargetDescriptor;
46 namespace devtools_http_handler {
48 namespace {
50 const base::FilePath::CharType kDevToolsActivePortFileName[] =
51 FILE_PATH_LITERAL("DevToolsActivePort");
53 const char kDevToolsHandlerThreadName[] = "Chrome_DevToolsHandlerThread";
55 const char kThumbUrlPrefix[] = "/thumb/";
56 const char kPageUrlPrefix[] = "/devtools/page/";
58 const char kTargetIdField[] = "id";
59 const char kTargetParentIdField[] = "parentId";
60 const char kTargetTypeField[] = "type";
61 const char kTargetTitleField[] = "title";
62 const char kTargetDescriptionField[] = "description";
63 const char kTargetUrlField[] = "url";
64 const char kTargetThumbnailUrlField[] = "thumbnailUrl";
65 const char kTargetFaviconUrlField[] = "faviconUrl";
66 const char kTargetWebSocketDebuggerUrlField[] = "webSocketDebuggerUrl";
67 const char kTargetDevtoolsFrontendUrlField[] = "devtoolsFrontendUrl";
69 // Maximum write buffer size of devtools http/websocket connections.
70 // TODO(rmcilroy/pfieldman): Reduce this back to 100Mb when we have
71 // added back pressure on the TraceComplete message protocol - crbug.com/456845.
72 const int32 kSendBufferSizeForDevTools = 256 * 1024 * 1024; // 256Mb
74 } // namespace
76 // ServerWrapper -------------------------------------------------------------
77 // All methods in this class are only called on handler thread.
78 class ServerWrapper : net::HttpServer::Delegate {
79 public:
80 ServerWrapper(base::WeakPtr<DevToolsHttpHandler> handler,
81 scoped_ptr<net::ServerSocket> socket,
82 const base::FilePath& frontend_dir,
83 bool bundles_resources);
85 int GetLocalAddress(net::IPEndPoint* address);
87 void AcceptWebSocket(int connection_id,
88 const net::HttpServerRequestInfo& request);
89 void SendOverWebSocket(int connection_id, const std::string& message);
90 void SendResponse(int connection_id,
91 const net::HttpServerResponseInfo& response);
92 void Send200(int connection_id,
93 const std::string& data,
94 const std::string& mime_type);
95 void Send404(int connection_id);
96 void Send500(int connection_id, const std::string& message);
97 void Close(int connection_id);
99 void WriteActivePortToUserProfile(const base::FilePath& output_directory);
101 virtual ~ServerWrapper() {}
103 private:
104 // net::HttpServer::Delegate implementation.
105 void OnConnect(int connection_id) override {}
106 void OnHttpRequest(int connection_id,
107 const net::HttpServerRequestInfo& info) override;
108 void OnWebSocketRequest(int connection_id,
109 const net::HttpServerRequestInfo& info) override;
110 void OnWebSocketMessage(int connection_id,
111 const std::string& data) override;
112 void OnClose(int connection_id) override;
114 base::WeakPtr<DevToolsHttpHandler> handler_;
115 scoped_ptr<net::HttpServer> server_;
116 base::FilePath frontend_dir_;
117 bool bundles_resources_;
120 ServerWrapper::ServerWrapper(base::WeakPtr<DevToolsHttpHandler> handler,
121 scoped_ptr<net::ServerSocket> socket,
122 const base::FilePath& frontend_dir,
123 bool bundles_resources)
124 : handler_(handler),
125 server_(new net::HttpServer(socket.Pass(), this)),
126 frontend_dir_(frontend_dir),
127 bundles_resources_(bundles_resources) {
130 int ServerWrapper::GetLocalAddress(net::IPEndPoint* address) {
131 return server_->GetLocalAddress(address);
134 void ServerWrapper::AcceptWebSocket(int connection_id,
135 const net::HttpServerRequestInfo& request) {
136 server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools);
137 server_->AcceptWebSocket(connection_id, request);
140 void ServerWrapper::SendOverWebSocket(int connection_id,
141 const std::string& message) {
142 server_->SendOverWebSocket(connection_id, message);
145 void ServerWrapper::SendResponse(int connection_id,
146 const net::HttpServerResponseInfo& response) {
147 server_->SendResponse(connection_id, response);
150 void ServerWrapper::Send200(int connection_id,
151 const std::string& data,
152 const std::string& mime_type) {
153 server_->Send200(connection_id, data, mime_type);
156 void ServerWrapper::Send404(int connection_id) {
157 server_->Send404(connection_id);
160 void ServerWrapper::Send500(int connection_id,
161 const std::string& message) {
162 server_->Send500(connection_id, message);
165 void ServerWrapper::Close(int connection_id) {
166 server_->Close(connection_id);
169 // Thread and ServerWrapper lifetime management ------------------------------
171 void TerminateOnUI(base::Thread* thread,
172 ServerWrapper* server_wrapper,
173 DevToolsHttpHandler::ServerSocketFactory* socket_factory) {
174 DCHECK_CURRENTLY_ON(BrowserThread::UI);
175 if (server_wrapper) {
176 DCHECK(thread);
177 thread->message_loop()->DeleteSoon(FROM_HERE, server_wrapper);
179 if (socket_factory) {
180 DCHECK(thread);
181 thread->message_loop()->DeleteSoon(FROM_HERE, socket_factory);
183 if (thread) {
184 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, thread);
188 void ServerStartedOnUI(
189 base::WeakPtr<DevToolsHttpHandler> handler,
190 base::Thread* thread,
191 ServerWrapper* server_wrapper,
192 DevToolsHttpHandler::ServerSocketFactory* socket_factory,
193 scoped_ptr<net::IPEndPoint> ip_address) {
194 DCHECK_CURRENTLY_ON(BrowserThread::UI);
195 if (handler && thread && server_wrapper) {
196 handler->ServerStarted(thread, server_wrapper, socket_factory,
197 ip_address.Pass());
198 } else {
199 TerminateOnUI(thread, server_wrapper, socket_factory);
203 void StartServerOnHandlerThread(
204 base::WeakPtr<DevToolsHttpHandler> handler,
205 base::Thread* thread,
206 DevToolsHttpHandler::ServerSocketFactory* server_socket_factory,
207 const base::FilePath& output_directory,
208 const base::FilePath& frontend_dir,
209 bool bundles_resources) {
210 DCHECK_EQ(thread->message_loop(), base::MessageLoop::current());
211 ServerWrapper* server_wrapper = nullptr;
212 scoped_ptr<net::ServerSocket> server_socket =
213 server_socket_factory->CreateForHttpServer();
214 scoped_ptr<net::IPEndPoint> ip_address(new net::IPEndPoint);
215 if (server_socket) {
216 server_wrapper = new ServerWrapper(handler, server_socket.Pass(),
217 frontend_dir, bundles_resources);
218 if (!output_directory.empty())
219 server_wrapper->WriteActivePortToUserProfile(output_directory);
221 if (server_wrapper->GetLocalAddress(ip_address.get()) != net::OK)
222 ip_address.reset();
223 } else {
224 ip_address.reset();
225 LOG(ERROR) << "Cannot start http server for devtools. Stop devtools.";
227 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
228 base::Bind(&ServerStartedOnUI,
229 handler,
230 thread,
231 server_wrapper,
232 server_socket_factory,
233 base::Passed(&ip_address)));
236 void StartServerOnFile(
237 base::WeakPtr<DevToolsHttpHandler> handler,
238 DevToolsHttpHandler::ServerSocketFactory* server_socket_factory,
239 const base::FilePath& output_directory,
240 const base::FilePath& frontend_dir,
241 bool bundles_resources) {
242 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
243 scoped_ptr<base::Thread> thread(new base::Thread(kDevToolsHandlerThreadName));
244 base::Thread::Options options;
245 options.message_loop_type = base::MessageLoop::TYPE_IO;
246 if (thread->StartWithOptions(options)) {
247 base::MessageLoop* message_loop = thread->message_loop();
248 message_loop->task_runner()->PostTask(
249 FROM_HERE,
250 base::Bind(&StartServerOnHandlerThread, handler,
251 base::Unretained(thread.release()), server_socket_factory,
252 output_directory, frontend_dir, bundles_resources));
256 // DevToolsAgentHostClientImpl -----------------------------------------------
257 // An internal implementation of DevToolsAgentHostClient that delegates
258 // messages sent to a DebuggerShell instance.
259 class DevToolsAgentHostClientImpl : public DevToolsAgentHostClient {
260 public:
261 DevToolsAgentHostClientImpl(base::MessageLoop* message_loop,
262 ServerWrapper* server_wrapper,
263 int connection_id,
264 scoped_refptr<DevToolsAgentHost> agent_host)
265 : message_loop_(message_loop),
266 server_wrapper_(server_wrapper),
267 connection_id_(connection_id),
268 agent_host_(agent_host) {
269 DCHECK_CURRENTLY_ON(BrowserThread::UI);
270 agent_host_->AttachClient(this);
273 ~DevToolsAgentHostClientImpl() override {
274 DCHECK_CURRENTLY_ON(BrowserThread::UI);
275 if (agent_host_.get())
276 agent_host_->DetachClient();
279 void AgentHostClosed(DevToolsAgentHost* agent_host,
280 bool replaced_with_another_client) override {
281 DCHECK_CURRENTLY_ON(BrowserThread::UI);
282 DCHECK(agent_host == agent_host_.get());
284 std::string message = base::StringPrintf(
285 "{ \"method\": \"Inspector.detached\", "
286 "\"params\": { \"reason\": \"%s\"} }",
287 replaced_with_another_client ?
288 "replaced_with_devtools" : "target_closed");
289 DispatchProtocolMessage(agent_host, message);
291 agent_host_ = nullptr;
292 message_loop_->task_runner()->PostTask(
293 FROM_HERE,
294 base::Bind(&ServerWrapper::Close, base::Unretained(server_wrapper_),
295 connection_id_));
298 void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
299 const std::string& message) override {
300 DCHECK_CURRENTLY_ON(BrowserThread::UI);
301 DCHECK(agent_host == agent_host_.get());
302 message_loop_->task_runner()->PostTask(
303 FROM_HERE,
304 base::Bind(&ServerWrapper::SendOverWebSocket,
305 base::Unretained(server_wrapper_), connection_id_, message));
308 void OnMessage(const std::string& message) {
309 DCHECK_CURRENTLY_ON(BrowserThread::UI);
310 if (agent_host_.get())
311 agent_host_->DispatchProtocolMessage(message);
314 private:
315 base::MessageLoop* const message_loop_;
316 ServerWrapper* const server_wrapper_;
317 const int connection_id_;
318 scoped_refptr<DevToolsAgentHost> agent_host_;
321 static bool TimeComparator(const DevToolsTargetDescriptor* desc1,
322 const DevToolsTargetDescriptor* desc2) {
323 return desc1->GetLastActivityTime() > desc2->GetLastActivityTime();
326 // DevToolsHttpHandler::ServerSocketFactory ----------------------------------
328 scoped_ptr<net::ServerSocket>
329 DevToolsHttpHandler::ServerSocketFactory::CreateForHttpServer() {
330 return scoped_ptr<net::ServerSocket>();
333 scoped_ptr<net::ServerSocket>
334 DevToolsHttpHandler::ServerSocketFactory::CreateForTethering(
335 std::string* name) {
336 return scoped_ptr<net::ServerSocket>();
339 // DevToolsHttpHandler -------------------------------------------------------
341 DevToolsHttpHandler::~DevToolsHttpHandler() {
342 TerminateOnUI(thread_, server_wrapper_, socket_factory_);
343 STLDeleteValues(&descriptor_map_);
344 STLDeleteValues(&connection_to_client_);
347 GURL DevToolsHttpHandler::GetFrontendURL(const std::string& path) {
348 if (!server_ip_address_)
349 return GURL();
350 return GURL(std::string("http://") + server_ip_address_->ToString() +
351 (path.empty() ? frontend_url_ : path));
354 static std::string PathWithoutParams(const std::string& path) {
355 size_t query_position = path.find("?");
356 if (query_position != std::string::npos)
357 return path.substr(0, query_position);
358 return path;
361 static std::string GetMimeType(const std::string& filename) {
362 if (base::EndsWith(filename, ".html", false)) {
363 return "text/html";
364 } else if (base::EndsWith(filename, ".css", false)) {
365 return "text/css";
366 } else if (base::EndsWith(filename, ".js", false)) {
367 return "application/javascript";
368 } else if (base::EndsWith(filename, ".png", false)) {
369 return "image/png";
370 } else if (base::EndsWith(filename, ".gif", false)) {
371 return "image/gif";
372 } else if (base::EndsWith(filename, ".json", false)) {
373 return "application/json";
375 LOG(ERROR) << "GetMimeType doesn't know mime type for: "
376 << filename
377 << " text/plain will be returned";
378 NOTREACHED();
379 return "text/plain";
382 void ServerWrapper::OnHttpRequest(int connection_id,
383 const net::HttpServerRequestInfo& info) {
384 server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools);
386 if (info.path.find("/json") == 0) {
387 BrowserThread::PostTask(
388 BrowserThread::UI,
389 FROM_HERE,
390 base::Bind(&DevToolsHttpHandler::OnJsonRequest,
391 handler_,
392 connection_id,
393 info));
394 return;
397 if (info.path.find(kThumbUrlPrefix) == 0) {
398 // Thumbnail request.
399 const std::string target_id = info.path.substr(strlen(kThumbUrlPrefix));
400 BrowserThread::PostTask(
401 BrowserThread::UI,
402 FROM_HERE,
403 base::Bind(&DevToolsHttpHandler::OnThumbnailRequest,
404 handler_,
405 connection_id,
406 target_id));
407 return;
410 if (info.path.empty() || info.path == "/") {
411 // Discovery page request.
412 BrowserThread::PostTask(
413 BrowserThread::UI,
414 FROM_HERE,
415 base::Bind(&DevToolsHttpHandler::OnDiscoveryPageRequest,
416 handler_,
417 connection_id));
418 return;
421 if (info.path.find("/devtools/") != 0) {
422 server_->Send404(connection_id);
423 return;
426 std::string filename = PathWithoutParams(info.path.substr(10));
427 std::string mime_type = GetMimeType(filename);
429 if (!frontend_dir_.empty()) {
430 base::FilePath path = frontend_dir_.AppendASCII(filename);
431 std::string data;
432 base::ReadFileToString(path, &data);
433 server_->Send200(connection_id, data, mime_type);
434 return;
437 if (bundles_resources_) {
438 BrowserThread::PostTask(
439 BrowserThread::UI,
440 FROM_HERE,
441 base::Bind(&DevToolsHttpHandler::OnFrontendResourceRequest,
442 handler_,
443 connection_id,
444 filename));
445 return;
447 server_->Send404(connection_id);
450 void ServerWrapper::OnWebSocketRequest(
451 int connection_id,
452 const net::HttpServerRequestInfo& request) {
453 BrowserThread::PostTask(
454 BrowserThread::UI,
455 FROM_HERE,
456 base::Bind(
457 &DevToolsHttpHandler::OnWebSocketRequest,
458 handler_,
459 connection_id,
460 request));
463 void ServerWrapper::OnWebSocketMessage(int connection_id,
464 const std::string& data) {
465 BrowserThread::PostTask(
466 BrowserThread::UI,
467 FROM_HERE,
468 base::Bind(
469 &DevToolsHttpHandler::OnWebSocketMessage,
470 handler_,
471 connection_id,
472 data));
475 void ServerWrapper::OnClose(int connection_id) {
476 BrowserThread::PostTask(
477 BrowserThread::UI,
478 FROM_HERE,
479 base::Bind(
480 &DevToolsHttpHandler::OnClose,
481 handler_,
482 connection_id));
485 std::string DevToolsHttpHandler::GetFrontendURLInternal(
486 const std::string id,
487 const std::string& host) {
488 return base::StringPrintf(
489 "%s%sws=%s%s%s",
490 frontend_url_.c_str(),
491 frontend_url_.find("?") == std::string::npos ? "?" : "&",
492 host.c_str(),
493 kPageUrlPrefix,
494 id.c_str());
497 static bool ParseJsonPath(
498 const std::string& path,
499 std::string* command,
500 std::string* target_id) {
502 // Fall back to list in case of empty query.
503 if (path.empty()) {
504 *command = "list";
505 return true;
508 if (path.find("/") != 0) {
509 // Malformed command.
510 return false;
512 *command = path.substr(1);
514 size_t separator_pos = command->find("/");
515 if (separator_pos != std::string::npos) {
516 *target_id = command->substr(separator_pos + 1);
517 *command = command->substr(0, separator_pos);
519 return true;
522 void DevToolsHttpHandler::OnJsonRequest(
523 int connection_id,
524 const net::HttpServerRequestInfo& info) {
525 // Trim /json
526 std::string path = info.path.substr(5);
528 // Trim fragment and query
529 std::string query;
530 size_t query_pos = path.find("?");
531 if (query_pos != std::string::npos) {
532 query = path.substr(query_pos + 1);
533 path = path.substr(0, query_pos);
536 size_t fragment_pos = path.find("#");
537 if (fragment_pos != std::string::npos)
538 path = path.substr(0, fragment_pos);
540 std::string command;
541 std::string target_id;
542 if (!ParseJsonPath(path, &command, &target_id)) {
543 SendJson(connection_id,
544 net::HTTP_NOT_FOUND,
545 NULL,
546 "Malformed query: " + info.path);
547 return;
550 if (command == "version") {
551 base::DictionaryValue version;
552 version.SetString("Protocol-Version",
553 DevToolsAgentHost::GetProtocolVersion().c_str());
554 version.SetString("WebKit-Version", content::GetWebKitVersion());
555 version.SetString("Browser", product_name_);
556 version.SetString("User-Agent", user_agent_);
557 #if defined(OS_ANDROID)
558 version.SetString("Android-Package",
559 base::android::BuildInfo::GetInstance()->package_name());
560 #endif
561 SendJson(connection_id, net::HTTP_OK, &version, std::string());
562 return;
565 if (command == "list") {
566 std::string host = info.headers["host"];
567 DevToolsTargetDescriptor::List descriptors =
568 devtools_discovery::DevToolsDiscoveryManager::GetInstance()->
569 GetDescriptors();
570 std::sort(descriptors.begin(), descriptors.end(), TimeComparator);
571 STLDeleteValues(&descriptor_map_);
572 base::ListValue list_value;
573 for (DevToolsTargetDescriptor* descriptor : descriptors) {
574 descriptor_map_[descriptor->GetId()] = descriptor;
575 list_value.Append(SerializeDescriptor(*descriptor, host));
577 SendJson(connection_id, net::HTTP_OK, &list_value, std::string());
578 return;
581 if (command == "new") {
582 GURL url(net::UnescapeURLComponent(
583 query, net::UnescapeRule::URL_SPECIAL_CHARS));
584 if (!url.is_valid())
585 url = GURL(url::kAboutBlankURL);
586 scoped_ptr<DevToolsTargetDescriptor> descriptor =
587 devtools_discovery::DevToolsDiscoveryManager::GetInstance()->
588 CreateNew(url);
589 if (!descriptor) {
590 SendJson(connection_id,
591 net::HTTP_INTERNAL_SERVER_ERROR,
592 NULL,
593 "Could not create new page");
594 return;
596 std::string host = info.headers["host"];
597 scoped_ptr<base::DictionaryValue> dictionary(
598 SerializeDescriptor(*descriptor.get(), host));
599 SendJson(connection_id, net::HTTP_OK, dictionary.get(), std::string());
600 const std::string target_id = descriptor->GetId();
601 descriptor_map_[target_id] = descriptor.release();
602 return;
605 if (command == "activate" || command == "close") {
606 DevToolsTargetDescriptor* descriptor = GetDescriptor(target_id);
607 if (!descriptor) {
608 SendJson(connection_id,
609 net::HTTP_NOT_FOUND,
610 NULL,
611 "No such target id: " + target_id);
612 return;
615 if (command == "activate") {
616 if (descriptor->Activate()) {
617 SendJson(connection_id, net::HTTP_OK, NULL, "Target activated");
618 } else {
619 SendJson(connection_id,
620 net::HTTP_INTERNAL_SERVER_ERROR,
621 NULL,
622 "Could not activate target id: " + target_id);
624 return;
627 if (command == "close") {
628 if (descriptor->Close()) {
629 SendJson(connection_id, net::HTTP_OK, NULL, "Target is closing");
630 } else {
631 SendJson(connection_id,
632 net::HTTP_INTERNAL_SERVER_ERROR,
633 NULL,
634 "Could not close target id: " + target_id);
636 return;
639 SendJson(connection_id,
640 net::HTTP_NOT_FOUND,
641 NULL,
642 "Unknown command: " + command);
643 return;
646 DevToolsTargetDescriptor* DevToolsHttpHandler::GetDescriptor(
647 const std::string& target_id) {
648 DescriptorMap::const_iterator it = descriptor_map_.find(target_id);
649 if (it == descriptor_map_.end())
650 return nullptr;
651 return it->second;
654 void DevToolsHttpHandler::OnThumbnailRequest(
655 int connection_id, const std::string& target_id) {
656 DevToolsTargetDescriptor* descriptor = GetDescriptor(target_id);
657 GURL page_url;
658 if (descriptor)
659 page_url = descriptor->GetURL();
660 std::string data = delegate_->GetPageThumbnailData(page_url);
661 if (!data.empty())
662 Send200(connection_id, data, "image/png");
663 else
664 Send404(connection_id);
667 void DevToolsHttpHandler::OnDiscoveryPageRequest(int connection_id) {
668 std::string response = delegate_->GetDiscoveryPageHTML();
669 Send200(connection_id, response, "text/html; charset=UTF-8");
672 void DevToolsHttpHandler::OnFrontendResourceRequest(
673 int connection_id, const std::string& path) {
674 Send200(connection_id,
675 delegate_->GetFrontendResource(path),
676 GetMimeType(path));
679 void DevToolsHttpHandler::OnWebSocketRequest(
680 int connection_id,
681 const net::HttpServerRequestInfo& request) {
682 if (!thread_)
683 return;
685 std::string browser_prefix = "/devtools/browser";
686 size_t browser_pos = request.path.find(browser_prefix);
687 if (browser_pos == 0) {
688 scoped_refptr<DevToolsAgentHost> browser_agent =
689 DevToolsAgentHost::CreateForBrowser(
690 thread_->task_runner(),
691 base::Bind(&ServerSocketFactory::CreateForTethering,
692 base::Unretained(socket_factory_)));
693 connection_to_client_[connection_id] = new DevToolsAgentHostClientImpl(
694 thread_->message_loop(), server_wrapper_, connection_id, browser_agent);
695 AcceptWebSocket(connection_id, request);
696 return;
699 size_t pos = request.path.find(kPageUrlPrefix);
700 if (pos != 0) {
701 Send404(connection_id);
702 return;
705 std::string target_id = request.path.substr(strlen(kPageUrlPrefix));
706 DevToolsTargetDescriptor* descriptor = GetDescriptor(target_id);
707 scoped_refptr<DevToolsAgentHost> agent =
708 descriptor ? descriptor->GetAgentHost() : nullptr;
709 if (!agent.get()) {
710 Send500(connection_id, "No such target id: " + target_id);
711 return;
714 if (agent->IsAttached()) {
715 Send500(connection_id,
716 "Target with given id is being inspected: " + target_id);
717 return;
720 DevToolsAgentHostClientImpl* client_host = new DevToolsAgentHostClientImpl(
721 thread_->message_loop(), server_wrapper_, connection_id, agent);
722 connection_to_client_[connection_id] = client_host;
724 AcceptWebSocket(connection_id, request);
727 void DevToolsHttpHandler::OnWebSocketMessage(
728 int connection_id,
729 const std::string& data) {
730 ConnectionToClientMap::iterator it =
731 connection_to_client_.find(connection_id);
732 if (it != connection_to_client_.end())
733 it->second->OnMessage(data);
736 void DevToolsHttpHandler::OnClose(int connection_id) {
737 ConnectionToClientMap::iterator it =
738 connection_to_client_.find(connection_id);
739 if (it != connection_to_client_.end()) {
740 delete it->second;
741 connection_to_client_.erase(connection_id);
745 DevToolsHttpHandler::DevToolsHttpHandler(
746 scoped_ptr<ServerSocketFactory> server_socket_factory,
747 const std::string& frontend_url,
748 DevToolsHttpHandlerDelegate* delegate,
749 const base::FilePath& output_directory,
750 const base::FilePath& debug_frontend_dir,
751 const std::string& product_name,
752 const std::string& user_agent)
753 : thread_(nullptr),
754 frontend_url_(frontend_url),
755 product_name_(product_name),
756 user_agent_(user_agent),
757 server_wrapper_(nullptr),
758 delegate_(delegate),
759 socket_factory_(nullptr),
760 weak_factory_(this) {
761 bool bundles_resources = frontend_url_.empty();
762 if (frontend_url_.empty())
763 frontend_url_ = "/devtools/inspector.html";
765 BrowserThread::PostTask(
766 BrowserThread::FILE, FROM_HERE,
767 base::Bind(&StartServerOnFile,
768 weak_factory_.GetWeakPtr(),
769 server_socket_factory.release(),
770 output_directory,
771 debug_frontend_dir,
772 bundles_resources));
775 void DevToolsHttpHandler::ServerStarted(
776 base::Thread* thread,
777 ServerWrapper* server_wrapper,
778 ServerSocketFactory* socket_factory,
779 scoped_ptr<net::IPEndPoint> ip_address) {
780 thread_ = thread;
781 server_wrapper_ = server_wrapper;
782 socket_factory_ = socket_factory;
783 server_ip_address_.swap(ip_address);
786 void ServerWrapper::WriteActivePortToUserProfile(
787 const base::FilePath& output_directory) {
788 DCHECK(!output_directory.empty());
789 net::IPEndPoint endpoint;
790 int err;
791 if ((err = server_->GetLocalAddress(&endpoint)) != net::OK) {
792 LOG(ERROR) << "Error " << err << " getting local address";
793 return;
796 // Write this port to a well-known file in the profile directory
797 // so Telemetry can pick it up.
798 base::FilePath path = output_directory.Append(kDevToolsActivePortFileName);
799 std::string port_string = base::IntToString(endpoint.port());
800 if (base::WriteFile(path, port_string.c_str(),
801 static_cast<int>(port_string.length())) < 0) {
802 LOG(ERROR) << "Error writing DevTools active port to file";
806 void DevToolsHttpHandler::SendJson(int connection_id,
807 net::HttpStatusCode status_code,
808 base::Value* value,
809 const std::string& message) {
810 if (!thread_)
811 return;
813 // Serialize value and message.
814 std::string json_value;
815 if (value) {
816 base::JSONWriter::WriteWithOptions(
817 *value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_value);
819 std::string json_message;
820 base::JSONWriter::Write(base::StringValue(message), &json_message);
822 net::HttpServerResponseInfo response(status_code);
823 response.SetBody(json_value + message, "application/json; charset=UTF-8");
825 thread_->task_runner()->PostTask(
826 FROM_HERE,
827 base::Bind(&ServerWrapper::SendResponse,
828 base::Unretained(server_wrapper_), connection_id, response));
831 void DevToolsHttpHandler::Send200(int connection_id,
832 const std::string& data,
833 const std::string& mime_type) {
834 if (!thread_)
835 return;
836 thread_->task_runner()->PostTask(
837 FROM_HERE,
838 base::Bind(&ServerWrapper::Send200, base::Unretained(server_wrapper_),
839 connection_id, data, mime_type));
842 void DevToolsHttpHandler::Send404(int connection_id) {
843 if (!thread_)
844 return;
845 thread_->task_runner()->PostTask(
846 FROM_HERE, base::Bind(&ServerWrapper::Send404,
847 base::Unretained(server_wrapper_), connection_id));
850 void DevToolsHttpHandler::Send500(int connection_id,
851 const std::string& message) {
852 if (!thread_)
853 return;
854 thread_->task_runner()->PostTask(
855 FROM_HERE,
856 base::Bind(&ServerWrapper::Send500, base::Unretained(server_wrapper_),
857 connection_id, message));
860 void DevToolsHttpHandler::AcceptWebSocket(
861 int connection_id,
862 const net::HttpServerRequestInfo& request) {
863 if (!thread_)
864 return;
865 thread_->task_runner()->PostTask(
866 FROM_HERE,
867 base::Bind(&ServerWrapper::AcceptWebSocket,
868 base::Unretained(server_wrapper_), connection_id, request));
871 base::DictionaryValue* DevToolsHttpHandler::SerializeDescriptor(
872 const DevToolsTargetDescriptor& descriptor,
873 const std::string& host) {
874 base::DictionaryValue* dictionary = new base::DictionaryValue;
876 std::string id = descriptor.GetId();
877 dictionary->SetString(kTargetIdField, id);
878 std::string parent_id = descriptor.GetParentId();
879 if (!parent_id.empty())
880 dictionary->SetString(kTargetParentIdField, parent_id);
881 dictionary->SetString(kTargetTypeField, descriptor.GetType());
882 dictionary->SetString(kTargetTitleField,
883 net::EscapeForHTML(descriptor.GetTitle()));
884 dictionary->SetString(kTargetDescriptionField, descriptor.GetDescription());
886 GURL url = descriptor.GetURL();
887 dictionary->SetString(kTargetUrlField, url.spec());
889 GURL favicon_url = descriptor.GetFaviconURL();
890 if (favicon_url.is_valid())
891 dictionary->SetString(kTargetFaviconUrlField, favicon_url.spec());
893 if (!delegate_->GetPageThumbnailData(url).empty()) {
894 dictionary->SetString(kTargetThumbnailUrlField,
895 std::string(kThumbUrlPrefix) + id);
898 if (!descriptor.IsAttached()) {
899 dictionary->SetString(kTargetWebSocketDebuggerUrlField,
900 base::StringPrintf("ws://%s%s%s",
901 host.c_str(),
902 kPageUrlPrefix,
903 id.c_str()));
904 std::string devtools_frontend_url = GetFrontendURLInternal(
905 id.c_str(),
906 host);
907 dictionary->SetString(
908 kTargetDevtoolsFrontendUrlField, devtools_frontend_url);
911 return dictionary;
914 } // namespace devtools_http_handler