Clean up extension confirmation prompts and make them consistent between Views and...
[chromium-blink-merge.git] / components / devtools_service / devtools_http_server.cc
blobef17bcd4009afee9fb1bf8f5dd5f821a6f6ea2d2
1 // Copyright 2015 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 "components/devtools_service/devtools_http_server.h"
7 #include <string.h>
9 #include <string>
11 #include "base/bind.h"
12 #include "base/json/json_writer.h"
13 #include "base/logging.h"
14 #include "base/stl_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/values.h"
17 #include "components/devtools_service/devtools_agent_host.h"
18 #include "components/devtools_service/devtools_registry_impl.h"
19 #include "components/devtools_service/devtools_service.h"
20 #include "mojo/application/public/cpp/application_impl.h"
21 #include "mojo/services/network/public/cpp/web_socket_read_queue.h"
22 #include "mojo/services/network/public/cpp/web_socket_write_queue.h"
23 #include "mojo/services/network/public/interfaces/net_address.mojom.h"
24 #include "mojo/services/network/public/interfaces/network_service.mojom.h"
25 #include "mojo/services/network/public/interfaces/web_socket.mojom.h"
26 #include "third_party/mojo/src/mojo/public/cpp/system/data_pipe.h"
28 namespace devtools_service {
30 namespace {
32 const char kPageUrlPrefix[] = "/devtools/page/";
33 const char kBrowserUrlPrefix[] = "/devtools/browser";
34 const char kJsonRequestUrlPrefix[] = "/json";
36 const char kActivateCommand[] = "activate";
37 const char kCloseCommand[] = "close";
38 const char kListCommand[] = "list";
39 const char kNewCommand[] = "new";
40 const char kVersionCommand[] = "version";
42 const char kTargetIdField[] = "id";
43 const char kTargetTypeField[] = "type";
44 const char kTargetTitleField[] = "title";
45 const char kTargetDescriptionField[] = "description";
46 const char kTargetUrlField[] = "url";
47 const char kTargetWebSocketDebuggerUrlField[] = "webSocketDebuggerUrl";
48 const char kTargetDevtoolsFrontendUrlField[] = "devtoolsFrontendUrl";
50 bool ParseJsonPath(const std::string& path,
51 std::string* command,
52 std::string* target_id) {
53 // Fall back to list in case of empty query.
54 if (path.empty()) {
55 *command = kListCommand;
56 return true;
59 if (path.find("/") != 0) {
60 // Malformed command.
61 return false;
63 *command = path.substr(1);
65 size_t separator_pos = command->find("/");
66 if (separator_pos != std::string::npos) {
67 *target_id = command->substr(separator_pos + 1);
68 *command = command->substr(0, separator_pos);
70 return true;
73 mojo::HttpResponsePtr MakeResponse(uint32_t status_code,
74 const std::string& content_type,
75 const std::string& body) {
76 mojo::HttpResponsePtr response(mojo::HttpResponse::New());
77 response->headers.resize(2);
78 response->headers[0] = mojo::HttpHeader::New();
79 response->headers[0]->name = "Content-Length";
80 response->headers[0]->value =
81 base::StringPrintf("%lu", static_cast<unsigned long>(body.size()));
82 response->headers[1] = mojo::HttpHeader::New();
83 response->headers[1]->name = "Content-Type";
84 response->headers[1]->value = content_type;
86 if (!body.empty()) {
87 uint32_t num_bytes = static_cast<uint32_t>(body.size());
88 MojoCreateDataPipeOptions options;
89 options.struct_size = sizeof(MojoCreateDataPipeOptions);
90 options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
91 options.element_num_bytes = 1;
92 options.capacity_num_bytes = num_bytes;
93 mojo::DataPipe data_pipe(options);
94 response->body = data_pipe.consumer_handle.Pass();
95 MojoResult result =
96 WriteDataRaw(data_pipe.producer_handle.get(), body.data(), &num_bytes,
97 MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
98 CHECK_EQ(MOJO_RESULT_OK, result);
100 return response.Pass();
103 mojo::HttpResponsePtr MakeJsonResponse(uint32_t status_code,
104 base::Value* value,
105 const std::string& message) {
106 // Serialize value and message.
107 std::string json_value;
108 if (value) {
109 base::JSONWriter::WriteWithOptions(
110 *value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_value);
113 return MakeResponse(status_code, "application/json; charset=UTF-8",
114 json_value + message);
117 class WebSocketRelayer : public DevToolsAgentHost::Delegate,
118 public mojo::WebSocketClient,
119 public mojo::ErrorHandler {
120 public:
121 // Creates a WebSocketRelayer instance and sets it as the delegate of
122 // |agent_host|.
124 // The object destroys itself when either of the following happens:
125 // - |agent_host| is dead and the object finishes all pending sends (if any)
126 // to the Web socket; or
127 // - the underlying pipe of |web_socket| is closed and the object finishes all
128 // pending receives (if any) from the Web socket.
129 static mojo::WebSocketClientPtr SetUp(
130 DevToolsAgentHost* agent_host,
131 mojo::WebSocketPtr web_socket,
132 mojo::ScopedDataPipeProducerHandle send_stream) {
133 DCHECK(agent_host);
134 DCHECK(web_socket);
135 DCHECK(send_stream.is_valid());
137 mojo::WebSocketClientPtr web_socket_client;
138 new WebSocketRelayer(agent_host, web_socket.Pass(), send_stream.Pass(),
139 &web_socket_client);
140 return web_socket_client.Pass();
143 private:
144 WebSocketRelayer(DevToolsAgentHost* agent_host,
145 mojo::WebSocketPtr web_socket,
146 mojo::ScopedDataPipeProducerHandle send_stream,
147 mojo::WebSocketClientPtr* web_socket_client)
148 : agent_host_(agent_host),
149 binding_(this, web_socket_client),
150 web_socket_(web_socket.Pass()),
151 send_stream_(send_stream.Pass()),
152 write_send_stream_(new mojo::WebSocketWriteQueue(send_stream_.get())),
153 pending_send_count_(0),
154 pending_receive_count_(0) {
155 web_socket_.set_error_handler(this);
156 agent_host->SetDelegate(this);
159 ~WebSocketRelayer() override {
160 if (agent_host_)
161 agent_host_->SetDelegate(nullptr);
164 // DevToolsAgentHost::Delegate implementation.
165 void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
166 const std::string& message) override {
167 if (!web_socket_)
168 return;
170 // TODO(yzshen): It shouldn't be an issue to pass an empty message. However,
171 // WebSocket{Read,Write}Queue doesn't handle that correctly.
172 if (message.empty())
173 return;
175 pending_send_count_++;
176 uint32_t size = static_cast<uint32_t>(message.size());
177 write_send_stream_->Write(
178 &message[0], size,
179 base::Bind(&WebSocketRelayer::OnFinishedWritingSendStream,
180 base::Unretained(this), size));
183 void OnAgentHostClosed(DevToolsAgentHost* agent_host) override {
184 DispatchProtocolMessage(agent_host_,
185 "{ \"method\": \"Inspector.detached\", "
186 "\"params\": { \"reason\": \"target_closed\" } }");
188 // No need to call SetDelegate(nullptr) on |agent_host_| because it is going
189 // away.
190 agent_host_ = nullptr;
192 if (ShouldSelfDestruct())
193 delete this;
196 // WebSocketClient implementation.
197 void DidConnect(const mojo::String& selected_subprotocol,
198 const mojo::String& extensions,
199 mojo::ScopedDataPipeConsumerHandle receive_stream) override {
200 receive_stream_ = receive_stream.Pass();
201 read_receive_stream_.reset(
202 new mojo::WebSocketReadQueue(receive_stream_.get()));
205 void DidReceiveData(bool fin,
206 mojo::WebSocket::MessageType type,
207 uint32_t num_bytes) override {
208 if (!agent_host_)
209 return;
211 // TODO(yzshen): It shouldn't be an issue to pass an empty message. However,
212 // WebSocket{Read,Write}Queue doesn't handle that correctly.
213 if (num_bytes == 0)
214 return;
216 pending_receive_count_++;
217 read_receive_stream_->Read(
218 num_bytes, base::Bind(&WebSocketRelayer::OnFinishedReadingReceiveStream,
219 base::Unretained(this), num_bytes));
222 void DidReceiveFlowControl(int64_t quota) override {}
224 void DidFail(const mojo::String& message) override {}
226 void DidClose(bool was_clean,
227 uint16_t code,
228 const mojo::String& reason) override {}
230 // mojo::ErrorHandler implementation.
231 void OnConnectionError() override {
232 web_socket_ = nullptr;
233 binding_.Close();
235 if (ShouldSelfDestruct())
236 delete this;
239 void OnFinishedWritingSendStream(uint32_t num_bytes, const char* buffer) {
240 DCHECK_GT(pending_send_count_, 0u);
241 pending_send_count_--;
243 if (web_socket_ && buffer)
244 web_socket_->Send(true, mojo::WebSocket::MESSAGE_TYPE_TEXT, num_bytes);
246 if (ShouldSelfDestruct())
247 delete this;
250 void OnFinishedReadingReceiveStream(uint32_t num_bytes, const char* data) {
251 DCHECK_GT(pending_receive_count_, 0u);
252 pending_receive_count_--;
254 if (agent_host_ && data)
255 agent_host_->SendProtocolMessageToAgent(std::string(data, num_bytes));
257 if (ShouldSelfDestruct())
258 delete this;
261 bool ShouldSelfDestruct() const {
262 return (!agent_host_ && pending_send_count_ == 0) ||
263 (!web_socket_ && pending_receive_count_ == 0);
266 DevToolsAgentHost* agent_host_;
267 mojo::Binding<WebSocketClient> binding_;
268 mojo::WebSocketPtr web_socket_;
270 mojo::ScopedDataPipeProducerHandle send_stream_;
271 scoped_ptr<mojo::WebSocketWriteQueue> write_send_stream_;
272 size_t pending_send_count_;
274 mojo::ScopedDataPipeConsumerHandle receive_stream_;
275 scoped_ptr<mojo::WebSocketReadQueue> read_receive_stream_;
276 size_t pending_receive_count_;
278 DISALLOW_COPY_AND_ASSIGN(WebSocketRelayer);
281 } // namespace
283 class DevToolsHttpServer::HttpConnectionDelegateImpl
284 : public mojo::HttpConnectionDelegate,
285 public mojo::ErrorHandler {
286 public:
287 HttpConnectionDelegateImpl(
288 DevToolsHttpServer* owner,
289 mojo::HttpConnectionPtr connection,
290 mojo::InterfaceRequest<HttpConnectionDelegate> delegate_request)
291 : owner_(owner),
292 connection_(connection.Pass()),
293 binding_(this, delegate_request.Pass()) {
294 DCHECK(owner_);
295 DCHECK(connection_);
296 DCHECK(binding_.is_bound());
298 connection_.set_error_handler(this);
299 binding_.set_error_handler(this);
302 mojo::HttpConnection* connection() { return connection_.get(); }
304 private:
305 // mojo::HttpConnectionDelegate implementation:
306 void OnReceivedRequest(mojo::HttpRequestPtr request,
307 const OnReceivedRequestCallback& callback) override {
308 owner_->OnReceivedRequest(this, request.Pass(), callback);
311 void OnReceivedWebSocketRequest(
312 mojo::HttpRequestPtr request,
313 const OnReceivedWebSocketRequestCallback& callback) override {
314 owner_->OnReceivedWebSocketRequest(this, request.Pass(), callback);
317 // mojo::ErrorHandler implementation.
318 void OnConnectionError() override { owner_->OnConnectionClosed(this); }
320 DevToolsHttpServer* const owner_;
321 mojo::HttpConnectionPtr connection_;
322 mojo::Binding<HttpConnectionDelegate> binding_;
324 DISALLOW_COPY_AND_ASSIGN(HttpConnectionDelegateImpl);
327 DevToolsHttpServer::DevToolsHttpServer(DevToolsService* service,
328 uint16_t remote_debugging_port)
329 : service_(service), remote_debugging_port_(remote_debugging_port) {
330 VLOG(1) << "Remote debugging HTTP server is started on port "
331 << remote_debugging_port << ".";
332 mojo::NetworkServicePtr network_service;
333 mojo::URLRequestPtr request(mojo::URLRequest::New());
334 request->url = "mojo:network_service";
335 service_->application()->ConnectToService(request.Pass(), &network_service);
337 mojo::NetAddressPtr local_address(mojo::NetAddress::New());
338 local_address->family = mojo::NET_ADDRESS_FAMILY_IPV4;
339 local_address->ipv4 = mojo::NetAddressIPv4::New();
340 local_address->ipv4->port = remote_debugging_port;
341 local_address->ipv4->addr.resize(4);
342 local_address->ipv4->addr[0] = 127;
343 local_address->ipv4->addr[1] = 0;
344 local_address->ipv4->addr[2] = 0;
345 local_address->ipv4->addr[3] = 1;
347 mojo::HttpServerDelegatePtr http_server_delegate;
348 http_server_delegate_binding_.reset(
349 new mojo::Binding<mojo::HttpServerDelegate>(this, &http_server_delegate));
350 network_service->CreateHttpServer(
351 local_address.Pass(), http_server_delegate.Pass(),
352 mojo::NetworkService::CreateHttpServerCallback());
355 DevToolsHttpServer::~DevToolsHttpServer() {
356 STLDeleteElements(&connections_);
359 void DevToolsHttpServer::OnConnected(
360 mojo::HttpConnectionPtr connection,
361 mojo::InterfaceRequest<mojo::HttpConnectionDelegate> delegate) {
362 connections_.insert(
363 new HttpConnectionDelegateImpl(this, connection.Pass(), delegate.Pass()));
366 void DevToolsHttpServer::OnReceivedRequest(
367 HttpConnectionDelegateImpl* connection,
368 mojo::HttpRequestPtr request,
369 const OnReceivedRequestCallback& callback) {
370 DCHECK(connections_.find(connection) != connections_.end());
372 if (request->url.get().find(kJsonRequestUrlPrefix) == 0) {
373 callback.Run(ProcessJsonRequest(request.Pass()));
374 } else {
375 // TODO(yzshen): Implement it.
376 NOTIMPLEMENTED();
377 callback.Run(MakeResponse(404, "text/html", "Not implemented yet!"));
381 void DevToolsHttpServer::OnReceivedWebSocketRequest(
382 HttpConnectionDelegateImpl* connection,
383 mojo::HttpRequestPtr request,
384 const OnReceivedWebSocketRequestCallback& callback) {
385 DCHECK(connections_.find(connection) != connections_.end());
387 std::string path = request->url;
388 size_t browser_pos = path.find(kBrowserUrlPrefix);
389 if (browser_pos == 0) {
390 // TODO(yzshen): Implement it.
391 NOTIMPLEMENTED();
392 callback.Run(nullptr, mojo::ScopedDataPipeConsumerHandle(), nullptr);
393 return;
396 size_t pos = path.find(kPageUrlPrefix);
397 if (pos != 0) {
398 callback.Run(nullptr, mojo::ScopedDataPipeConsumerHandle(), nullptr);
399 return;
402 std::string target_id = path.substr(strlen(kPageUrlPrefix));
403 DevToolsAgentHost* agent = service_->registry()->GetAgentById(target_id);
404 if (!agent || agent->IsAttached()) {
405 callback.Run(nullptr, mojo::ScopedDataPipeConsumerHandle(), nullptr);
406 return;
409 mojo::WebSocketPtr web_socket;
410 mojo::InterfaceRequest<mojo::WebSocket> web_socket_request =
411 mojo::GetProxy(&web_socket);
412 mojo::DataPipe data_pipe;
413 mojo::WebSocketClientPtr web_socket_client = WebSocketRelayer::SetUp(
414 agent, web_socket.Pass(), data_pipe.producer_handle.Pass());
415 callback.Run(web_socket_request.Pass(), data_pipe.consumer_handle.Pass(),
416 web_socket_client.Pass());
419 void DevToolsHttpServer::OnConnectionClosed(
420 HttpConnectionDelegateImpl* connection) {
421 DCHECK(connections_.find(connection) != connections_.end());
423 delete connection;
424 connections_.erase(connection);
427 mojo::HttpResponsePtr DevToolsHttpServer::ProcessJsonRequest(
428 mojo::HttpRequestPtr request) {
429 // Trim "/json".
430 std::string path = request->url.get().substr(strlen(kJsonRequestUrlPrefix));
432 // Trim query.
433 size_t query_pos = path.find("?");
434 if (query_pos != std::string::npos)
435 path = path.substr(0, query_pos);
437 // Trim fragment.
438 size_t fragment_pos = path.find("#");
439 if (fragment_pos != std::string::npos)
440 path = path.substr(0, fragment_pos);
442 std::string command;
443 std::string target_id;
444 if (!ParseJsonPath(path, &command, &target_id))
445 return MakeJsonResponse(404, nullptr,
446 "Malformed query: " + request->url.get());
448 if (command == kVersionCommand || command == kNewCommand ||
449 command == kActivateCommand || command == kCloseCommand) {
450 NOTIMPLEMENTED();
451 return MakeJsonResponse(404, nullptr,
452 "Not implemented yet: " + request->url.get());
455 if (command == kListCommand) {
456 base::ListValue list_value;
457 for (DevToolsRegistryImpl::Iterator iter(service_->registry());
458 !iter.IsAtEnd(); iter.Advance()) {
459 scoped_ptr<base::DictionaryValue> dict_value(new base::DictionaryValue());
461 // TODO(yzshen): Add more information.
462 dict_value->SetString(kTargetDescriptionField, std::string());
463 dict_value->SetString(kTargetDevtoolsFrontendUrlField, std::string());
464 dict_value->SetString(kTargetIdField, iter.value()->id());
465 dict_value->SetString(kTargetTitleField, std::string());
466 dict_value->SetString(kTargetTypeField, "page");
467 dict_value->SetString(kTargetUrlField, std::string());
468 dict_value->SetString(
469 kTargetWebSocketDebuggerUrlField,
470 base::StringPrintf("ws://127.0.0.1:%u%s%s",
471 static_cast<unsigned>(remote_debugging_port_),
472 kPageUrlPrefix, iter.value()->id().c_str()));
473 list_value.Append(dict_value.Pass());
475 return MakeJsonResponse(200, &list_value, std::string());
478 return MakeJsonResponse(404, nullptr, "Unknown command: " + command);
481 } // namespace devtools_service