Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / devtools / devtools_http_handler_impl.cc
blobfb25825d56e391db507abfa75ed5ae8c72bb5251
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/logging.h"
13 #include "base/message_loop/message_loop_proxy.h"
14 #include "base/stl_util.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/threading/thread.h"
17 #include "base/values.h"
18 #include "content/browser/devtools/devtools_manager.h"
19 #include "content/browser/devtools/protocol/devtools_protocol_handler.h"
20 #include "content/browser/devtools/protocol/system_info_handler.h"
21 #include "content/browser/devtools/protocol/tethering_handler.h"
22 #include "content/browser/devtools/protocol/tracing_handler.h"
23 #include "content/common/devtools_messages.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/devtools_agent_host.h"
26 #include "content/public/browser/devtools_http_handler.h"
27 #include "content/public/browser/devtools_http_handler_delegate.h"
28 #include "content/public/browser/devtools_target.h"
29 #include "content/public/common/content_client.h"
30 #include "content/public/common/url_constants.h"
31 #include "content/public/common/user_agent.h"
32 #include "grit/devtools_resources_map.h"
33 #include "net/base/escape.h"
34 #include "net/base/io_buffer.h"
35 #include "net/base/ip_endpoint.h"
36 #include "net/base/net_errors.h"
37 #include "net/server/http_server.h"
38 #include "net/server/http_server_request_info.h"
39 #include "net/server/http_server_response_info.h"
40 #include "net/socket/server_socket.h"
42 #if defined(OS_ANDROID)
43 #include "base/android/build_info.h"
44 #endif
46 namespace content {
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 class BrowserTarget;
75 class DevToolsAgentHostClientImpl;
76 class ServerWrapper;
78 // DevToolsHttpHandlerImpl ---------------------------------------------------
80 class DevToolsHttpHandlerImpl : public DevToolsHttpHandler {
81 public:
82 DevToolsHttpHandlerImpl(scoped_ptr<ServerSocketFactory> server_socket_factory,
83 const std::string& frontend_url,
84 DevToolsHttpHandlerDelegate* delegate,
85 const base::FilePath& output_directory);
86 ~DevToolsHttpHandlerImpl() override;
88 void OnJsonRequest(int connection_id,
89 const net::HttpServerRequestInfo& info);
90 void OnThumbnailRequest(int connection_id, const std::string& target_id);
91 void OnDiscoveryPageRequest(int connection_id);
93 void OnWebSocketRequest(int connection_id,
94 const net::HttpServerRequestInfo& info);
95 void OnWebSocketMessage(int connection_id, const std::string& data);
96 void OnClose(int connection_id);
98 void ServerStarted(base::Thread* thread,
99 ServerWrapper* server_wrapper,
100 ServerSocketFactory* socket_factory,
101 scoped_ptr<net::IPEndPoint> ip_address);
103 private:
104 // DevToolsHttpHandler implementation.
105 GURL GetFrontendURL(const std::string& path) override;
107 static void OnTargetListReceivedWeak(
108 base::WeakPtr<DevToolsHttpHandlerImpl> handler,
109 int connection_id,
110 const std::string& host,
111 const DevToolsManagerDelegate::TargetList& targets);
112 void OnTargetListReceived(
113 int connection_id,
114 const std::string& host,
115 const DevToolsManagerDelegate::TargetList& targets);
117 DevToolsTarget* GetTarget(const std::string& id);
119 void SendJson(int connection_id,
120 net::HttpStatusCode status_code,
121 base::Value* value,
122 const std::string& message);
123 void Send200(int connection_id,
124 const std::string& data,
125 const std::string& mime_type);
126 void Send404(int connection_id);
127 void Send500(int connection_id,
128 const std::string& message);
129 void AcceptWebSocket(int connection_id,
130 const net::HttpServerRequestInfo& request);
132 // Returns the front end url without the host at the beginning.
133 std::string GetFrontendURLInternal(const std::string target_id,
134 const std::string& host);
136 base::DictionaryValue* SerializeTarget(const DevToolsTarget& target,
137 const std::string& host);
139 // The thread used by the devtools handler to run server socket.
140 base::Thread* thread_;
142 std::string frontend_url_;
143 ServerWrapper* server_wrapper_;
144 scoped_ptr<net::IPEndPoint> server_ip_address_;
145 typedef std::map<int, DevToolsAgentHostClientImpl*> ConnectionToClientMap;
146 ConnectionToClientMap connection_to_client_;
147 const scoped_ptr<DevToolsHttpHandlerDelegate> delegate_;
148 ServerSocketFactory* socket_factory_;
149 typedef std::map<std::string, DevToolsTarget*> TargetMap;
150 TargetMap target_map_;
151 typedef std::map<int, BrowserTarget*> BrowserTargets;
152 BrowserTargets browser_targets_;
153 base::WeakPtrFactory<DevToolsHttpHandlerImpl> weak_factory_;
155 DISALLOW_COPY_AND_ASSIGN(DevToolsHttpHandlerImpl);
158 // ServerWrapper -------------------------------------------------------------
159 // All methods in this class are only called on handler thread.
160 class ServerWrapper : net::HttpServer::Delegate {
161 public:
162 ServerWrapper(base::WeakPtr<DevToolsHttpHandlerImpl> handler,
163 scoped_ptr<net::ServerSocket> socket,
164 const base::FilePath& frontend_dir,
165 bool bundles_resources);
167 int GetLocalAddress(net::IPEndPoint* address);
169 void AcceptWebSocket(int connection_id,
170 const net::HttpServerRequestInfo& request);
171 void SendOverWebSocket(int connection_id, const std::string& message);
172 void SendResponse(int connection_id,
173 const net::HttpServerResponseInfo& response);
174 void Send200(int connection_id,
175 const std::string& data,
176 const std::string& mime_type);
177 void Send404(int connection_id);
178 void Send500(int connection_id, const std::string& message);
179 void Close(int connection_id);
181 void WriteActivePortToUserProfile(const base::FilePath& output_directory);
183 virtual ~ServerWrapper() {}
185 private:
186 // net::HttpServer::Delegate implementation.
187 void OnConnect(int connection_id) override {}
188 void OnHttpRequest(int connection_id,
189 const net::HttpServerRequestInfo& info) override;
190 void OnWebSocketRequest(int connection_id,
191 const net::HttpServerRequestInfo& info) override;
192 void OnWebSocketMessage(int connection_id,
193 const std::string& data) override;
194 void OnClose(int connection_id) override;
196 base::WeakPtr<DevToolsHttpHandlerImpl> handler_;
197 scoped_ptr<net::HttpServer> server_;
198 base::FilePath frontend_dir_;
199 bool bundles_resources_;
202 ServerWrapper::ServerWrapper(base::WeakPtr<DevToolsHttpHandlerImpl> handler,
203 scoped_ptr<net::ServerSocket> socket,
204 const base::FilePath& frontend_dir,
205 bool bundles_resources)
206 : handler_(handler),
207 server_(new net::HttpServer(socket.Pass(), this)),
208 frontend_dir_(frontend_dir),
209 bundles_resources_(bundles_resources) {
212 int ServerWrapper::GetLocalAddress(net::IPEndPoint* address) {
213 return server_->GetLocalAddress(address);
216 void ServerWrapper::AcceptWebSocket(int connection_id,
217 const net::HttpServerRequestInfo& request) {
218 server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools);
219 server_->AcceptWebSocket(connection_id, request);
222 void ServerWrapper::SendOverWebSocket(int connection_id,
223 const std::string& message) {
224 server_->SendOverWebSocket(connection_id, message);
227 void ServerWrapper::SendResponse(int connection_id,
228 const net::HttpServerResponseInfo& response) {
229 server_->SendResponse(connection_id, response);
232 void ServerWrapper::Send200(int connection_id,
233 const std::string& data,
234 const std::string& mime_type) {
235 server_->Send200(connection_id, data, mime_type);
238 void ServerWrapper::Send404(int connection_id) {
239 server_->Send404(connection_id);
242 void ServerWrapper::Send500(int connection_id,
243 const std::string& message) {
244 server_->Send500(connection_id, message);
247 void ServerWrapper::Close(int connection_id) {
248 server_->Close(connection_id);
251 // Thread and ServerWrapper lifetime management ------------------------------
253 void TerminateOnUI(base::Thread* thread,
254 ServerWrapper* server_wrapper,
255 DevToolsHttpHandler::ServerSocketFactory* socket_factory) {
256 DCHECK_CURRENTLY_ON(BrowserThread::UI);
257 if (server_wrapper) {
258 DCHECK(thread);
259 thread->message_loop()->DeleteSoon(FROM_HERE, server_wrapper);
261 if (socket_factory) {
262 DCHECK(thread);
263 thread->message_loop()->DeleteSoon(FROM_HERE, socket_factory);
265 if (thread) {
266 BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, thread);
270 void ServerStartedOnUI(
271 base::WeakPtr<DevToolsHttpHandlerImpl> handler,
272 base::Thread* thread,
273 ServerWrapper* server_wrapper,
274 DevToolsHttpHandler::ServerSocketFactory* socket_factory,
275 scoped_ptr<net::IPEndPoint> ip_address) {
276 DCHECK_CURRENTLY_ON(BrowserThread::UI);
277 if (handler && thread && server_wrapper) {
278 handler->ServerStarted(thread, server_wrapper, socket_factory,
279 ip_address.Pass());
280 } else {
281 TerminateOnUI(thread, server_wrapper, socket_factory);
285 void StartServerOnHandlerThread(
286 base::WeakPtr<DevToolsHttpHandlerImpl> handler,
287 base::Thread* thread,
288 DevToolsHttpHandler::ServerSocketFactory* server_socket_factory,
289 const base::FilePath& output_directory,
290 const base::FilePath& frontend_dir,
291 bool bundles_resources) {
292 DCHECK_EQ(thread->message_loop(), base::MessageLoop::current());
293 ServerWrapper* server_wrapper = nullptr;
294 scoped_ptr<net::ServerSocket> server_socket =
295 server_socket_factory->CreateForHttpServer();
296 scoped_ptr<net::IPEndPoint> ip_address(new net::IPEndPoint);
297 if (server_socket) {
298 server_wrapper = new ServerWrapper(handler, server_socket.Pass(),
299 frontend_dir, bundles_resources);
300 if (!output_directory.empty())
301 server_wrapper->WriteActivePortToUserProfile(output_directory);
303 if (server_wrapper->GetLocalAddress(ip_address.get()) != net::OK)
304 ip_address.reset();
305 } else {
306 ip_address.reset();
307 LOG(ERROR) << "Cannot start http server for devtools. Stop devtools.";
309 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
310 base::Bind(&ServerStartedOnUI,
311 handler,
312 thread,
313 server_wrapper,
314 server_socket_factory,
315 base::Passed(&ip_address)));
318 void StartServerOnFile(
319 base::WeakPtr<DevToolsHttpHandlerImpl> handler,
320 DevToolsHttpHandler::ServerSocketFactory* server_socket_factory,
321 const base::FilePath& output_directory,
322 const base::FilePath& frontend_dir,
323 bool bundles_resources) {
324 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
325 scoped_ptr<base::Thread> thread(new base::Thread(kDevToolsHandlerThreadName));
326 base::Thread::Options options;
327 options.message_loop_type = base::MessageLoop::TYPE_IO;
328 if (thread->StartWithOptions(options)) {
329 base::MessageLoop* message_loop = thread->message_loop();
330 message_loop->PostTask(FROM_HERE,
331 base::Bind(&StartServerOnHandlerThread,
332 handler,
333 base::Unretained(thread.release()),
334 server_socket_factory,
335 output_directory,
336 frontend_dir,
337 bundles_resources));
341 // DevToolsAgentHostClientImpl -----------------------------------------------
342 // An internal implementation of DevToolsAgentHostClient that delegates
343 // messages sent to a DebuggerShell instance.
344 class DevToolsAgentHostClientImpl : public DevToolsAgentHostClient {
345 public:
346 DevToolsAgentHostClientImpl(base::MessageLoop* message_loop,
347 ServerWrapper* server_wrapper,
348 int connection_id,
349 DevToolsAgentHost* agent_host)
350 : message_loop_(message_loop),
351 server_wrapper_(server_wrapper),
352 connection_id_(connection_id),
353 agent_host_(agent_host) {
354 agent_host_->AttachClient(this);
357 ~DevToolsAgentHostClientImpl() override {
358 if (agent_host_.get())
359 agent_host_->DetachClient();
362 void AgentHostClosed(DevToolsAgentHost* agent_host,
363 bool replaced_with_another_client) override {
364 DCHECK(agent_host == agent_host_.get());
366 base::Callback<void(const std::string&)> raw_message_callback(
367 base::Bind(&DevToolsAgentHostClientImpl::DispatchProtocolMessage,
368 base::Unretained(this), base::Unretained(agent_host)));
369 devtools::inspector::Client inspector(raw_message_callback);
370 inspector.Detached(devtools::inspector::DetachedParams::Create()
371 ->set_reason(replaced_with_another_client ?
372 "replaced_with_devtools" : "target_closed"));
374 agent_host_ = nullptr;
375 message_loop_->PostTask(
376 FROM_HERE,
377 base::Bind(&ServerWrapper::Close,
378 base::Unretained(server_wrapper_),
379 connection_id_));
382 void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
383 const std::string& message) override {
384 DCHECK(agent_host == agent_host_.get());
385 message_loop_->PostTask(
386 FROM_HERE,
387 base::Bind(&ServerWrapper::SendOverWebSocket,
388 base::Unretained(server_wrapper_),
389 connection_id_,
390 message));
393 void OnMessage(const std::string& message) {
394 if (agent_host_.get())
395 agent_host_->DispatchProtocolMessage(message);
398 private:
399 base::MessageLoop* const message_loop_;
400 ServerWrapper* const server_wrapper_;
401 const int connection_id_;
402 scoped_refptr<DevToolsAgentHost> agent_host_;
405 static bool TimeComparator(const DevToolsTarget* target1,
406 const DevToolsTarget* target2) {
407 return target1->GetLastActivityTime() > target2->GetLastActivityTime();
410 // BrowserTarget -------------------------------------------------------------
412 class BrowserTarget {
413 public:
414 BrowserTarget(base::MessageLoop* message_loop,
415 ServerWrapper* server_wrapper,
416 DevToolsHttpHandler::ServerSocketFactory* socket_factory,
417 int connection_id)
418 : message_loop_(message_loop),
419 server_wrapper_(server_wrapper),
420 connection_id_(connection_id),
421 system_info_handler_(new devtools::system_info::SystemInfoHandler()),
422 tethering_handler_(new devtools::tethering::TetheringHandler(
423 socket_factory, message_loop->message_loop_proxy())),
424 tracing_handler_(new devtools::tracing::TracingHandler(
425 devtools::tracing::TracingHandler::Browser)),
426 protocol_handler_(new DevToolsProtocolHandler(
427 base::Bind(&BrowserTarget::Respond, base::Unretained(this)))) {
428 DevToolsProtocolDispatcher* dispatcher = protocol_handler_->dispatcher();
429 dispatcher->SetSystemInfoHandler(system_info_handler_.get());
430 dispatcher->SetTetheringHandler(tethering_handler_.get());
431 dispatcher->SetTracingHandler(tracing_handler_.get());
434 void HandleMessage(const std::string& message) {
435 DCHECK_CURRENTLY_ON(BrowserThread::UI);
436 std::string error_response;
437 scoped_ptr<base::DictionaryValue> command =
438 protocol_handler_->ParseCommand(message);
439 if (command)
440 protocol_handler_->HandleCommand(command.Pass());
443 void Respond(const std::string& message) {
444 DCHECK_CURRENTLY_ON(BrowserThread::UI);
445 message_loop_->PostTask(
446 FROM_HERE,
447 base::Bind(&ServerWrapper::SendOverWebSocket,
448 base::Unretained(server_wrapper_),
449 connection_id_,
450 message));
453 private:
454 base::MessageLoop* const message_loop_;
455 ServerWrapper* const server_wrapper_;
456 const int connection_id_;
457 scoped_ptr<devtools::system_info::SystemInfoHandler> system_info_handler_;
458 scoped_ptr<devtools::tethering::TetheringHandler> tethering_handler_;
459 scoped_ptr<devtools::tracing::TracingHandler> tracing_handler_;
460 scoped_ptr<DevToolsProtocolHandler> protocol_handler_;
463 } // namespace
465 // DevToolsHttpHandler -------------------------------------------------------
467 // static
468 bool DevToolsHttpHandler::IsSupportedProtocolVersion(
469 const std::string& version) {
470 return devtools::IsSupportedProtocolVersion(version);
473 // static
474 int DevToolsHttpHandler::GetFrontendResourceId(const std::string& name) {
475 for (size_t i = 0; i < kDevtoolsResourcesSize; ++i) {
476 if (name == kDevtoolsResources[i].name)
477 return kDevtoolsResources[i].value;
479 return -1;
482 // static
483 DevToolsHttpHandler* DevToolsHttpHandler::Start(
484 scoped_ptr<ServerSocketFactory> server_socket_factory,
485 const std::string& frontend_url,
486 DevToolsHttpHandlerDelegate* delegate,
487 const base::FilePath& active_port_output_directory) {
488 DevToolsHttpHandlerImpl* http_handler =
489 new DevToolsHttpHandlerImpl(server_socket_factory.Pass(),
490 frontend_url,
491 delegate,
492 active_port_output_directory);
493 return http_handler;
496 // DevToolsHttpHandler::ServerSocketFactory ----------------------------------
498 scoped_ptr<net::ServerSocket>
499 DevToolsHttpHandler::ServerSocketFactory::CreateForHttpServer() {
500 return scoped_ptr<net::ServerSocket>();
503 scoped_ptr<net::ServerSocket>
504 DevToolsHttpHandler::ServerSocketFactory::CreateForTethering(
505 std::string* name) {
506 return scoped_ptr<net::ServerSocket>();
509 // DevToolsHttpHandlerImpl ---------------------------------------------------
511 DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() {
512 TerminateOnUI(thread_, server_wrapper_, socket_factory_);
513 STLDeleteValues(&target_map_);
514 STLDeleteValues(&browser_targets_);
515 STLDeleteValues(&connection_to_client_);
518 GURL DevToolsHttpHandlerImpl::GetFrontendURL(const std::string& path) {
519 if (!server_ip_address_)
520 return GURL();
521 return GURL(std::string("http://") + server_ip_address_->ToString() +
522 (path.empty() ? frontend_url_ : path));
525 static std::string PathWithoutParams(const std::string& path) {
526 size_t query_position = path.find("?");
527 if (query_position != std::string::npos)
528 return path.substr(0, query_position);
529 return path;
532 static std::string GetMimeType(const std::string& filename) {
533 if (EndsWith(filename, ".html", false)) {
534 return "text/html";
535 } else if (EndsWith(filename, ".css", false)) {
536 return "text/css";
537 } else if (EndsWith(filename, ".js", false)) {
538 return "application/javascript";
539 } else if (EndsWith(filename, ".png", false)) {
540 return "image/png";
541 } else if (EndsWith(filename, ".gif", false)) {
542 return "image/gif";
543 } else if (EndsWith(filename, ".json", false)) {
544 return "application/json";
546 LOG(ERROR) << "GetMimeType doesn't know mime type for: "
547 << filename
548 << " text/plain will be returned";
549 NOTREACHED();
550 return "text/plain";
553 void ServerWrapper::OnHttpRequest(int connection_id,
554 const net::HttpServerRequestInfo& info) {
555 server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools);
557 if (info.path.find("/json") == 0) {
558 BrowserThread::PostTask(
559 BrowserThread::UI,
560 FROM_HERE,
561 base::Bind(&DevToolsHttpHandlerImpl::OnJsonRequest,
562 handler_,
563 connection_id,
564 info));
565 return;
568 if (info.path.find(kThumbUrlPrefix) == 0) {
569 // Thumbnail request.
570 const std::string target_id = info.path.substr(strlen(kThumbUrlPrefix));
571 BrowserThread::PostTask(
572 BrowserThread::UI,
573 FROM_HERE,
574 base::Bind(&DevToolsHttpHandlerImpl::OnThumbnailRequest,
575 handler_,
576 connection_id,
577 target_id));
578 return;
581 if (info.path.empty() || info.path == "/") {
582 // Discovery page request.
583 BrowserThread::PostTask(
584 BrowserThread::UI,
585 FROM_HERE,
586 base::Bind(&DevToolsHttpHandlerImpl::OnDiscoveryPageRequest,
587 handler_,
588 connection_id));
589 return;
592 if (info.path.find("/devtools/") != 0) {
593 server_->Send404(connection_id);
594 return;
597 std::string filename = PathWithoutParams(info.path.substr(10));
598 std::string mime_type = GetMimeType(filename);
600 if (!frontend_dir_.empty()) {
601 base::FilePath path = frontend_dir_.AppendASCII(filename);
602 std::string data;
603 base::ReadFileToString(path, &data);
604 server_->Send200(connection_id, data, mime_type);
605 return;
607 if (bundles_resources_) {
608 int resource_id = DevToolsHttpHandler::GetFrontendResourceId(filename);
609 if (resource_id != -1) {
610 base::StringPiece data = GetContentClient()->GetDataResource(
611 resource_id, ui::SCALE_FACTOR_NONE);
612 server_->Send200(connection_id, data.as_string(), mime_type);
613 return;
616 server_->Send404(connection_id);
619 void ServerWrapper::OnWebSocketRequest(
620 int connection_id,
621 const net::HttpServerRequestInfo& request) {
622 BrowserThread::PostTask(
623 BrowserThread::UI,
624 FROM_HERE,
625 base::Bind(
626 &DevToolsHttpHandlerImpl::OnWebSocketRequest,
627 handler_,
628 connection_id,
629 request));
632 void ServerWrapper::OnWebSocketMessage(int connection_id,
633 const std::string& data) {
634 BrowserThread::PostTask(
635 BrowserThread::UI,
636 FROM_HERE,
637 base::Bind(
638 &DevToolsHttpHandlerImpl::OnWebSocketMessage,
639 handler_,
640 connection_id,
641 data));
644 void ServerWrapper::OnClose(int connection_id) {
645 BrowserThread::PostTask(
646 BrowserThread::UI,
647 FROM_HERE,
648 base::Bind(
649 &DevToolsHttpHandlerImpl::OnClose,
650 handler_,
651 connection_id));
654 std::string DevToolsHttpHandlerImpl::GetFrontendURLInternal(
655 const std::string id,
656 const std::string& host) {
657 return base::StringPrintf(
658 "%s%sws=%s%s%s",
659 frontend_url_.c_str(),
660 frontend_url_.find("?") == std::string::npos ? "?" : "&",
661 host.c_str(),
662 kPageUrlPrefix,
663 id.c_str());
666 static bool ParseJsonPath(
667 const std::string& path,
668 std::string* command,
669 std::string* target_id) {
671 // Fall back to list in case of empty query.
672 if (path.empty()) {
673 *command = "list";
674 return true;
677 if (path.find("/") != 0) {
678 // Malformed command.
679 return false;
681 *command = path.substr(1);
683 size_t separator_pos = command->find("/");
684 if (separator_pos != std::string::npos) {
685 *target_id = command->substr(separator_pos + 1);
686 *command = command->substr(0, separator_pos);
688 return true;
691 void DevToolsHttpHandlerImpl::OnJsonRequest(
692 int connection_id,
693 const net::HttpServerRequestInfo& info) {
694 // Trim /json
695 std::string path = info.path.substr(5);
697 // Trim fragment and query
698 std::string query;
699 size_t query_pos = path.find("?");
700 if (query_pos != std::string::npos) {
701 query = path.substr(query_pos + 1);
702 path = path.substr(0, query_pos);
705 size_t fragment_pos = path.find("#");
706 if (fragment_pos != std::string::npos)
707 path = path.substr(0, fragment_pos);
709 std::string command;
710 std::string target_id;
711 if (!ParseJsonPath(path, &command, &target_id)) {
712 SendJson(connection_id,
713 net::HTTP_NOT_FOUND,
714 NULL,
715 "Malformed query: " + info.path);
716 return;
719 if (command == "version") {
720 base::DictionaryValue version;
721 version.SetString("Protocol-Version", devtools::kProtocolVersion);
722 version.SetString("WebKit-Version", GetWebKitVersion());
723 version.SetString("Browser", GetContentClient()->GetProduct());
724 version.SetString("User-Agent", GetContentClient()->GetUserAgent());
725 #if defined(OS_ANDROID)
726 version.SetString("Android-Package",
727 base::android::BuildInfo::GetInstance()->package_name());
728 #endif
729 SendJson(connection_id, net::HTTP_OK, &version, std::string());
730 return;
733 if (command == "list") {
734 std::string host = info.headers["host"];
735 DevToolsManagerDelegate* manager_delegate =
736 DevToolsManager::GetInstance()->delegate();
737 if (manager_delegate) {
738 manager_delegate->EnumerateTargets(
739 base::Bind(&DevToolsHttpHandlerImpl::OnTargetListReceivedWeak,
740 weak_factory_.GetWeakPtr(), connection_id, host));
741 } else {
742 DevToolsManagerDelegate::TargetList empty_list;
743 OnTargetListReceived(connection_id, host, empty_list);
745 return;
748 if (command == "new") {
749 GURL url(net::UnescapeURLComponent(
750 query, net::UnescapeRule::URL_SPECIAL_CHARS));
751 if (!url.is_valid())
752 url = GURL(url::kAboutBlankURL);
753 scoped_ptr<DevToolsTarget> target;
754 DevToolsManagerDelegate* manager_delegate =
755 DevToolsManager::GetInstance()->delegate();
756 if (manager_delegate)
757 target = manager_delegate->CreateNewTarget(url);
758 if (!target) {
759 SendJson(connection_id,
760 net::HTTP_INTERNAL_SERVER_ERROR,
761 NULL,
762 "Could not create new page");
763 return;
765 std::string host = info.headers["host"];
766 scoped_ptr<base::DictionaryValue> dictionary(
767 SerializeTarget(*target.get(), host));
768 SendJson(connection_id, net::HTTP_OK, dictionary.get(), std::string());
769 const std::string target_id = target->GetId();
770 target_map_[target_id] = target.release();
771 return;
774 if (command == "activate" || command == "close") {
775 DevToolsTarget* target = GetTarget(target_id);
776 if (!target) {
777 SendJson(connection_id,
778 net::HTTP_NOT_FOUND,
779 NULL,
780 "No such target id: " + target_id);
781 return;
784 if (command == "activate") {
785 if (target->Activate()) {
786 SendJson(connection_id, net::HTTP_OK, NULL, "Target activated");
787 } else {
788 SendJson(connection_id,
789 net::HTTP_INTERNAL_SERVER_ERROR,
790 NULL,
791 "Could not activate target id: " + target_id);
793 return;
796 if (command == "close") {
797 if (target->Close()) {
798 SendJson(connection_id, net::HTTP_OK, NULL, "Target is closing");
799 } else {
800 SendJson(connection_id,
801 net::HTTP_INTERNAL_SERVER_ERROR,
802 NULL,
803 "Could not close target id: " + target_id);
805 return;
808 SendJson(connection_id,
809 net::HTTP_NOT_FOUND,
810 NULL,
811 "Unknown command: " + command);
812 return;
815 // static
816 void DevToolsHttpHandlerImpl::OnTargetListReceivedWeak(
817 base::WeakPtr<DevToolsHttpHandlerImpl> handler,
818 int connection_id,
819 const std::string& host,
820 const DevToolsManagerDelegate::TargetList& targets) {
821 if (handler) {
822 handler->OnTargetListReceived(connection_id, host, targets);
823 } else {
824 STLDeleteContainerPointers(targets.begin(), targets.end());
828 void DevToolsHttpHandlerImpl::OnTargetListReceived(
829 int connection_id,
830 const std::string& host,
831 const DevToolsManagerDelegate::TargetList& targets) {
832 DevToolsManagerDelegate::TargetList sorted_targets = targets;
833 std::sort(sorted_targets.begin(), sorted_targets.end(), TimeComparator);
835 STLDeleteValues(&target_map_);
836 base::ListValue list_value;
837 for (DevToolsManagerDelegate::TargetList::const_iterator it =
838 sorted_targets.begin(); it != sorted_targets.end(); ++it) {
839 DevToolsTarget* target = *it;
840 target_map_[target->GetId()] = target;
841 list_value.Append(SerializeTarget(*target, host));
843 SendJson(connection_id, net::HTTP_OK, &list_value, std::string());
846 DevToolsTarget* DevToolsHttpHandlerImpl::GetTarget(const std::string& id) {
847 TargetMap::const_iterator it = target_map_.find(id);
848 if (it == target_map_.end())
849 return NULL;
850 return it->second;
853 void DevToolsHttpHandlerImpl::OnThumbnailRequest(
854 int connection_id, const std::string& target_id) {
855 DevToolsTarget* target = GetTarget(target_id);
856 GURL page_url;
857 if (target)
858 page_url = target->GetURL();
859 DevToolsManagerDelegate* manager_delegate =
860 DevToolsManager::GetInstance()->delegate();
861 std::string data =
862 manager_delegate ? manager_delegate->GetPageThumbnailData(page_url) : "";
863 if (!data.empty())
864 Send200(connection_id, data, "image/png");
865 else
866 Send404(connection_id);
869 void DevToolsHttpHandlerImpl::OnDiscoveryPageRequest(int connection_id) {
870 std::string response = delegate_->GetDiscoveryPageHTML();
871 Send200(connection_id, response, "text/html; charset=UTF-8");
874 void DevToolsHttpHandlerImpl::OnWebSocketRequest(
875 int connection_id,
876 const net::HttpServerRequestInfo& request) {
877 if (!thread_)
878 return;
880 std::string browser_prefix = "/devtools/browser";
881 size_t browser_pos = request.path.find(browser_prefix);
882 if (browser_pos == 0) {
883 browser_targets_[connection_id] = new BrowserTarget(thread_->message_loop(),
884 server_wrapper_,
885 socket_factory_,
886 connection_id);
887 AcceptWebSocket(connection_id, request);
888 return;
891 size_t pos = request.path.find(kPageUrlPrefix);
892 if (pos != 0) {
893 Send404(connection_id);
894 return;
897 std::string page_id = request.path.substr(strlen(kPageUrlPrefix));
898 DevToolsTarget* target = GetTarget(page_id);
899 scoped_refptr<DevToolsAgentHost> agent =
900 target ? target->GetAgentHost() : NULL;
901 if (!agent.get()) {
902 Send500(connection_id, "No such target id: " + page_id);
903 return;
906 if (agent->IsAttached()) {
907 Send500(connection_id,
908 "Target with given id is being inspected: " + page_id);
909 return;
912 DevToolsAgentHostClientImpl* client_host = new DevToolsAgentHostClientImpl(
913 thread_->message_loop(), server_wrapper_, connection_id, agent.get());
914 connection_to_client_[connection_id] = client_host;
916 AcceptWebSocket(connection_id, request);
919 void DevToolsHttpHandlerImpl::OnWebSocketMessage(
920 int connection_id,
921 const std::string& data) {
922 BrowserTargets::iterator browser_it = browser_targets_.find(connection_id);
923 if (browser_it != browser_targets_.end()) {
924 browser_it->second->HandleMessage(data);
925 return;
928 ConnectionToClientMap::iterator it =
929 connection_to_client_.find(connection_id);
930 if (it != connection_to_client_.end())
931 it->second->OnMessage(data);
934 void DevToolsHttpHandlerImpl::OnClose(int connection_id) {
935 BrowserTargets::iterator browser_it = browser_targets_.find(connection_id);
936 if (browser_it != browser_targets_.end()) {
937 delete browser_it->second;
938 browser_targets_.erase(connection_id);
939 return;
942 ConnectionToClientMap::iterator it =
943 connection_to_client_.find(connection_id);
944 if (it != connection_to_client_.end()) {
945 delete it->second;
946 connection_to_client_.erase(connection_id);
950 DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl(
951 scoped_ptr<ServerSocketFactory> server_socket_factory,
952 const std::string& frontend_url,
953 DevToolsHttpHandlerDelegate* delegate,
954 const base::FilePath& output_directory)
955 : thread_(nullptr),
956 frontend_url_(frontend_url),
957 server_wrapper_(nullptr),
958 delegate_(delegate),
959 socket_factory_(nullptr),
960 weak_factory_(this) {
961 if (frontend_url_.empty())
962 frontend_url_ = "/devtools/inspector.html";
964 BrowserThread::PostTask(
965 BrowserThread::FILE, FROM_HERE,
966 base::Bind(&StartServerOnFile,
967 weak_factory_.GetWeakPtr(),
968 server_socket_factory.release(),
969 output_directory,
970 delegate_->GetDebugFrontendDir(),
971 delegate_->BundlesFrontendResources()));
974 void DevToolsHttpHandlerImpl::ServerStarted(
975 base::Thread* thread,
976 ServerWrapper* server_wrapper,
977 ServerSocketFactory* socket_factory,
978 scoped_ptr<net::IPEndPoint> ip_address) {
979 thread_ = thread;
980 server_wrapper_ = server_wrapper;
981 socket_factory_ = socket_factory;
982 server_ip_address_.swap(ip_address);
985 void ServerWrapper::WriteActivePortToUserProfile(
986 const base::FilePath& output_directory) {
987 DCHECK(!output_directory.empty());
988 net::IPEndPoint endpoint;
989 int err;
990 if ((err = server_->GetLocalAddress(&endpoint)) != net::OK) {
991 LOG(ERROR) << "Error " << err << " getting local address";
992 return;
995 // Write this port to a well-known file in the profile directory
996 // so Telemetry can pick it up.
997 base::FilePath path = output_directory.Append(kDevToolsActivePortFileName);
998 std::string port_string = base::IntToString(endpoint.port());
999 if (base::WriteFile(path, port_string.c_str(), port_string.length()) < 0) {
1000 LOG(ERROR) << "Error writing DevTools active port to file";
1004 void DevToolsHttpHandlerImpl::SendJson(int connection_id,
1005 net::HttpStatusCode status_code,
1006 base::Value* value,
1007 const std::string& message) {
1008 if (!thread_)
1009 return;
1011 // Serialize value and message.
1012 std::string json_value;
1013 if (value) {
1014 base::JSONWriter::WriteWithOptions(value,
1015 base::JSONWriter::OPTIONS_PRETTY_PRINT,
1016 &json_value);
1018 std::string json_message;
1019 scoped_ptr<base::Value> message_object(new base::StringValue(message));
1020 base::JSONWriter::Write(message_object.get(), &json_message);
1022 net::HttpServerResponseInfo response(status_code);
1023 response.SetBody(json_value + message, "application/json; charset=UTF-8");
1025 thread_->message_loop()->PostTask(
1026 FROM_HERE,
1027 base::Bind(&ServerWrapper::SendResponse,
1028 base::Unretained(server_wrapper_),
1029 connection_id,
1030 response));
1033 void DevToolsHttpHandlerImpl::Send200(int connection_id,
1034 const std::string& data,
1035 const std::string& mime_type) {
1036 if (!thread_)
1037 return;
1038 thread_->message_loop()->PostTask(
1039 FROM_HERE,
1040 base::Bind(&ServerWrapper::Send200,
1041 base::Unretained(server_wrapper_),
1042 connection_id,
1043 data,
1044 mime_type));
1047 void DevToolsHttpHandlerImpl::Send404(int connection_id) {
1048 if (!thread_)
1049 return;
1050 thread_->message_loop()->PostTask(
1051 FROM_HERE,
1052 base::Bind(&ServerWrapper::Send404,
1053 base::Unretained(server_wrapper_),
1054 connection_id));
1057 void DevToolsHttpHandlerImpl::Send500(int connection_id,
1058 const std::string& message) {
1059 if (!thread_)
1060 return;
1061 thread_->message_loop()->PostTask(
1062 FROM_HERE,
1063 base::Bind(&ServerWrapper::Send500,
1064 base::Unretained(server_wrapper_),
1065 connection_id,
1066 message));
1069 void DevToolsHttpHandlerImpl::AcceptWebSocket(
1070 int connection_id,
1071 const net::HttpServerRequestInfo& request) {
1072 if (!thread_)
1073 return;
1074 thread_->message_loop()->PostTask(
1075 FROM_HERE,
1076 base::Bind(&ServerWrapper::AcceptWebSocket,
1077 base::Unretained(server_wrapper_),
1078 connection_id,
1079 request));
1082 base::DictionaryValue* DevToolsHttpHandlerImpl::SerializeTarget(
1083 const DevToolsTarget& target,
1084 const std::string& host) {
1085 base::DictionaryValue* dictionary = new base::DictionaryValue;
1087 std::string id = target.GetId();
1088 dictionary->SetString(kTargetIdField, id);
1089 std::string parent_id = target.GetParentId();
1090 if (!parent_id.empty())
1091 dictionary->SetString(kTargetParentIdField, parent_id);
1092 dictionary->SetString(kTargetTypeField, target.GetType());
1093 dictionary->SetString(kTargetTitleField,
1094 net::EscapeForHTML(target.GetTitle()));
1095 dictionary->SetString(kTargetDescriptionField, target.GetDescription());
1097 GURL url = target.GetURL();
1098 dictionary->SetString(kTargetUrlField, url.spec());
1100 GURL favicon_url = target.GetFaviconURL();
1101 if (favicon_url.is_valid())
1102 dictionary->SetString(kTargetFaviconUrlField, favicon_url.spec());
1104 DevToolsManagerDelegate* manager_delegate =
1105 DevToolsManager::GetInstance()->delegate();
1106 if (manager_delegate &&
1107 !manager_delegate->GetPageThumbnailData(url).empty()) {
1108 dictionary->SetString(kTargetThumbnailUrlField,
1109 std::string(kThumbUrlPrefix) + id);
1112 if (!target.IsAttached()) {
1113 dictionary->SetString(kTargetWebSocketDebuggerUrlField,
1114 base::StringPrintf("ws://%s%s%s",
1115 host.c_str(),
1116 kPageUrlPrefix,
1117 id.c_str()));
1118 std::string devtools_frontend_url = GetFrontendURLInternal(
1119 id.c_str(),
1120 host);
1121 dictionary->SetString(
1122 kTargetDevtoolsFrontendUrlField, devtools_frontend_url);
1125 return dictionary;
1128 } // namespace content