API for binary_dependencies, and import changes to call the new API.
[chromium-blink-merge.git] / components / devtools_service / devtools_http_server.cc
blob22d32581865a380da982d700ed1d2f86cd9c344f
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:
120 // Creates a WebSocketRelayer instance and sets it as the delegate of
121 // |agent_host|.
123 // The object destroys itself when either of the following happens:
124 // - |agent_host| is dead and the object finishes all pending sends (if any)
125 // to the Web socket; or
126 // - the underlying pipe of |web_socket| is closed and the object finishes all
127 // pending receives (if any) from the Web socket.
128 static mojo::WebSocketClientPtr SetUp(
129 DevToolsAgentHost* agent_host,
130 mojo::WebSocketPtr web_socket,
131 mojo::ScopedDataPipeProducerHandle send_stream) {
132 DCHECK(agent_host);
133 DCHECK(web_socket);
134 DCHECK(send_stream.is_valid());
136 mojo::WebSocketClientPtr web_socket_client;
137 new WebSocketRelayer(agent_host, web_socket.Pass(), send_stream.Pass(),
138 &web_socket_client);
139 return web_socket_client.Pass();
142 private:
143 WebSocketRelayer(DevToolsAgentHost* agent_host,
144 mojo::WebSocketPtr web_socket,
145 mojo::ScopedDataPipeProducerHandle send_stream,
146 mojo::WebSocketClientPtr* web_socket_client)
147 : agent_host_(agent_host),
148 binding_(this, web_socket_client),
149 web_socket_(web_socket.Pass()),
150 send_stream_(send_stream.Pass()),
151 write_send_stream_(new mojo::WebSocketWriteQueue(send_stream_.get())),
152 pending_send_count_(0),
153 pending_receive_count_(0) {
154 web_socket_.set_connection_error_handler([this]() { OnConnectionError(); });
155 agent_host->SetDelegate(this);
158 ~WebSocketRelayer() override {
159 if (agent_host_)
160 agent_host_->SetDelegate(nullptr);
163 // DevToolsAgentHost::Delegate implementation.
164 void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
165 const std::string& message) override {
166 if (!web_socket_)
167 return;
169 // TODO(yzshen): It shouldn't be an issue to pass an empty message. However,
170 // WebSocket{Read,Write}Queue doesn't handle that correctly.
171 if (message.empty())
172 return;
174 pending_send_count_++;
175 uint32_t size = static_cast<uint32_t>(message.size());
176 write_send_stream_->Write(
177 &message[0], size,
178 base::Bind(&WebSocketRelayer::OnFinishedWritingSendStream,
179 base::Unretained(this), size));
182 void OnAgentHostClosed(DevToolsAgentHost* agent_host) override {
183 DispatchProtocolMessage(agent_host_,
184 "{ \"method\": \"Inspector.detached\", "
185 "\"params\": { \"reason\": \"target_closed\" } }");
187 // No need to call SetDelegate(nullptr) on |agent_host_| because it is going
188 // away.
189 agent_host_ = nullptr;
191 if (ShouldSelfDestruct())
192 delete this;
195 // WebSocketClient implementation.
196 void DidConnect(const mojo::String& selected_subprotocol,
197 const mojo::String& extensions,
198 mojo::ScopedDataPipeConsumerHandle receive_stream) override {
199 receive_stream_ = receive_stream.Pass();
200 read_receive_stream_.reset(
201 new mojo::WebSocketReadQueue(receive_stream_.get()));
204 void DidReceiveData(bool fin,
205 mojo::WebSocket::MessageType type,
206 uint32_t num_bytes) override {
207 if (!agent_host_)
208 return;
210 // TODO(yzshen): It shouldn't be an issue to pass an empty message. However,
211 // WebSocket{Read,Write}Queue doesn't handle that correctly.
212 if (num_bytes == 0)
213 return;
215 pending_receive_count_++;
216 read_receive_stream_->Read(
217 num_bytes, base::Bind(&WebSocketRelayer::OnFinishedReadingReceiveStream,
218 base::Unretained(this), num_bytes));
221 void DidReceiveFlowControl(int64_t quota) override {}
223 void DidFail(const mojo::String& message) override {}
225 void DidClose(bool was_clean,
226 uint16_t code,
227 const mojo::String& reason) override {}
229 void OnConnectionError() {
230 web_socket_ = nullptr;
231 binding_.Close();
233 if (ShouldSelfDestruct())
234 delete this;
237 void OnFinishedWritingSendStream(uint32_t num_bytes, const char* buffer) {
238 DCHECK_GT(pending_send_count_, 0u);
239 pending_send_count_--;
241 if (web_socket_ && buffer)
242 web_socket_->Send(true, mojo::WebSocket::MESSAGE_TYPE_TEXT, num_bytes);
244 if (ShouldSelfDestruct())
245 delete this;
248 void OnFinishedReadingReceiveStream(uint32_t num_bytes, const char* data) {
249 DCHECK_GT(pending_receive_count_, 0u);
250 pending_receive_count_--;
252 if (agent_host_ && data)
253 agent_host_->SendProtocolMessageToAgent(std::string(data, num_bytes));
255 if (ShouldSelfDestruct())
256 delete this;
259 bool ShouldSelfDestruct() const {
260 return (!agent_host_ && pending_send_count_ == 0) ||
261 (!web_socket_ && pending_receive_count_ == 0);
264 DevToolsAgentHost* agent_host_;
265 mojo::Binding<WebSocketClient> binding_;
266 mojo::WebSocketPtr web_socket_;
268 mojo::ScopedDataPipeProducerHandle send_stream_;
269 scoped_ptr<mojo::WebSocketWriteQueue> write_send_stream_;
270 size_t pending_send_count_;
272 mojo::ScopedDataPipeConsumerHandle receive_stream_;
273 scoped_ptr<mojo::WebSocketReadQueue> read_receive_stream_;
274 size_t pending_receive_count_;
276 DISALLOW_COPY_AND_ASSIGN(WebSocketRelayer);
279 } // namespace
281 class DevToolsHttpServer::HttpConnectionDelegateImpl
282 : public mojo::HttpConnectionDelegate {
283 public:
284 HttpConnectionDelegateImpl(
285 DevToolsHttpServer* owner,
286 mojo::HttpConnectionPtr connection,
287 mojo::InterfaceRequest<HttpConnectionDelegate> delegate_request)
288 : owner_(owner),
289 connection_(connection.Pass()),
290 binding_(this, delegate_request.Pass()) {
291 DCHECK(owner_);
292 DCHECK(connection_);
293 DCHECK(binding_.is_bound());
295 auto error_handler = [this]() { owner_->OnConnectionClosed(this); };
296 connection_.set_connection_error_handler(error_handler);
297 binding_.set_connection_error_handler(error_handler);
300 mojo::HttpConnection* connection() { return connection_.get(); }
302 private:
303 // mojo::HttpConnectionDelegate implementation:
304 void OnReceivedRequest(mojo::HttpRequestPtr request,
305 const OnReceivedRequestCallback& callback) override {
306 owner_->OnReceivedRequest(this, request.Pass(), callback);
309 void OnReceivedWebSocketRequest(
310 mojo::HttpRequestPtr request,
311 const OnReceivedWebSocketRequestCallback& callback) override {
312 owner_->OnReceivedWebSocketRequest(this, request.Pass(), callback);
315 DevToolsHttpServer* const owner_;
316 mojo::HttpConnectionPtr connection_;
317 mojo::Binding<HttpConnectionDelegate> binding_;
319 DISALLOW_COPY_AND_ASSIGN(HttpConnectionDelegateImpl);
322 DevToolsHttpServer::DevToolsHttpServer(DevToolsService* service,
323 uint16_t remote_debugging_port)
324 : service_(service), remote_debugging_port_(remote_debugging_port) {
325 VLOG(1) << "Remote debugging HTTP server is started on port "
326 << remote_debugging_port << ".";
327 mojo::NetworkServicePtr network_service;
328 mojo::URLRequestPtr request(mojo::URLRequest::New());
329 request->url = "mojo:network_service";
330 service_->application()->ConnectToService(request.Pass(), &network_service);
332 mojo::NetAddressPtr local_address(mojo::NetAddress::New());
333 local_address->family = mojo::NET_ADDRESS_FAMILY_IPV4;
334 local_address->ipv4 = mojo::NetAddressIPv4::New();
335 local_address->ipv4->port = remote_debugging_port;
336 local_address->ipv4->addr.resize(4);
337 local_address->ipv4->addr[0] = 127;
338 local_address->ipv4->addr[1] = 0;
339 local_address->ipv4->addr[2] = 0;
340 local_address->ipv4->addr[3] = 1;
342 mojo::HttpServerDelegatePtr http_server_delegate;
343 http_server_delegate_binding_.reset(
344 new mojo::Binding<mojo::HttpServerDelegate>(this, &http_server_delegate));
345 network_service->CreateHttpServer(
346 local_address.Pass(), http_server_delegate.Pass(),
347 mojo::NetworkService::CreateHttpServerCallback());
350 DevToolsHttpServer::~DevToolsHttpServer() {
351 STLDeleteElements(&connections_);
354 void DevToolsHttpServer::OnConnected(
355 mojo::HttpConnectionPtr connection,
356 mojo::InterfaceRequest<mojo::HttpConnectionDelegate> delegate) {
357 connections_.insert(
358 new HttpConnectionDelegateImpl(this, connection.Pass(), delegate.Pass()));
361 void DevToolsHttpServer::OnReceivedRequest(
362 HttpConnectionDelegateImpl* connection,
363 mojo::HttpRequestPtr request,
364 const OnReceivedRequestCallback& callback) {
365 DCHECK(connections_.find(connection) != connections_.end());
367 if (request->url.get().find(kJsonRequestUrlPrefix) == 0) {
368 mojo::HttpResponsePtr response = ProcessJsonRequest(request.Pass());
369 if (response)
370 callback.Run(response.Pass());
371 else
372 OnConnectionClosed(connection);
373 } else {
374 // TODO(yzshen): Implement it.
375 NOTIMPLEMENTED();
376 callback.Run(MakeResponse(404, "text/html", "Not implemented yet!"));
380 void DevToolsHttpServer::OnReceivedWebSocketRequest(
381 HttpConnectionDelegateImpl* connection,
382 mojo::HttpRequestPtr request,
383 const OnReceivedWebSocketRequestCallback& callback) {
384 DCHECK(connections_.find(connection) != connections_.end());
386 std::string path = request->url;
387 size_t browser_pos = path.find(kBrowserUrlPrefix);
388 if (browser_pos == 0) {
389 // TODO(yzshen): Implement it.
390 NOTIMPLEMENTED();
391 callback.Run(nullptr, mojo::ScopedDataPipeConsumerHandle(), nullptr);
392 return;
395 size_t pos = path.find(kPageUrlPrefix);
396 if (pos != 0) {
397 callback.Run(nullptr, mojo::ScopedDataPipeConsumerHandle(), nullptr);
398 return;
401 std::string target_id = path.substr(strlen(kPageUrlPrefix));
402 DevToolsAgentHost* agent = service_->registry()->GetAgentById(target_id);
403 if (!agent || agent->IsAttached()) {
404 callback.Run(nullptr, mojo::ScopedDataPipeConsumerHandle(), nullptr);
405 return;
408 mojo::WebSocketPtr web_socket;
409 mojo::InterfaceRequest<mojo::WebSocket> web_socket_request =
410 mojo::GetProxy(&web_socket);
411 mojo::DataPipe data_pipe;
412 mojo::WebSocketClientPtr web_socket_client = WebSocketRelayer::SetUp(
413 agent, web_socket.Pass(), data_pipe.producer_handle.Pass());
414 callback.Run(web_socket_request.Pass(), data_pipe.consumer_handle.Pass(),
415 web_socket_client.Pass());
418 void DevToolsHttpServer::OnConnectionClosed(
419 HttpConnectionDelegateImpl* connection) {
420 DCHECK(connections_.find(connection) != connections_.end());
422 delete connection;
423 connections_.erase(connection);
426 mojo::HttpResponsePtr DevToolsHttpServer::ProcessJsonRequest(
427 mojo::HttpRequestPtr request) {
428 // Trim "/json".
429 std::string path = request->url.get().substr(strlen(kJsonRequestUrlPrefix));
431 // Trim query.
432 size_t query_pos = path.find("?");
433 if (query_pos != std::string::npos)
434 path = path.substr(0, query_pos);
436 // Trim fragment.
437 size_t fragment_pos = path.find("#");
438 if (fragment_pos != std::string::npos)
439 path = path.substr(0, fragment_pos);
441 std::string command;
442 std::string target_id;
443 if (!ParseJsonPath(path, &command, &target_id))
444 return MakeJsonResponse(404, nullptr,
445 "Malformed query: " + request->url.get());
447 if (command == kVersionCommand || command == kNewCommand ||
448 command == kActivateCommand || command == kCloseCommand) {
449 NOTIMPLEMENTED();
450 return MakeJsonResponse(404, nullptr,
451 "Not implemented yet: " + request->url.get());
454 if (command == kListCommand) {
455 DevToolsRegistryImpl::Iterator iter(service_->registry());
456 if (iter.IsAtEnd()) {
457 // If no agent is available, return a nullptr to indicate that the
458 // connection should be closed.
459 return nullptr;
462 base::ListValue list_value;
463 for (; !iter.IsAtEnd(); iter.Advance()) {
464 scoped_ptr<base::DictionaryValue> dict_value(new base::DictionaryValue());
466 // TODO(yzshen): Add more information.
467 dict_value->SetString(kTargetDescriptionField, std::string());
468 dict_value->SetString(kTargetDevtoolsFrontendUrlField, std::string());
469 dict_value->SetString(kTargetIdField, iter.value()->id());
470 dict_value->SetString(kTargetTitleField, std::string());
471 dict_value->SetString(kTargetTypeField, "page");
472 dict_value->SetString(kTargetUrlField, std::string());
473 dict_value->SetString(
474 kTargetWebSocketDebuggerUrlField,
475 base::StringPrintf("ws://127.0.0.1:%u%s%s",
476 static_cast<unsigned>(remote_debugging_port_),
477 kPageUrlPrefix, iter.value()->id().c_str()));
478 list_value.Append(dict_value.Pass());
480 return MakeJsonResponse(200, &list_value, std::string());
483 return MakeJsonResponse(404, nullptr, "Unknown command: " + command);
486 } // namespace devtools_service