QUIC - cleanup changes to sync chromium tree with internal source.
[chromium-blink-merge.git] / components / devtools_service / devtools_http_server.cc
blob0c7dcbbc0751688bda4b79bebca0425e8966a1ab
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 std::string GetHeaderValue(const mojo::HttpRequest& request,
51 const std::string& name) {
52 for (size_t i = 0; i < request.headers.size(); ++i) {
53 if (name == request.headers[i]->name)
54 return request.headers[i]->value;
57 return std::string();
60 bool ParseJsonPath(const std::string& path,
61 std::string* command,
62 std::string* target_id) {
63 // Fall back to list in case of empty query.
64 if (path.empty()) {
65 *command = kListCommand;
66 return true;
69 if (path.find("/") != 0) {
70 // Malformed command.
71 return false;
73 *command = path.substr(1);
75 size_t separator_pos = command->find("/");
76 if (separator_pos != std::string::npos) {
77 *target_id = command->substr(separator_pos + 1);
78 *command = command->substr(0, separator_pos);
80 return true;
83 mojo::HttpResponsePtr MakeResponse(uint32_t status_code,
84 const std::string& content_type,
85 const std::string& body) {
86 mojo::HttpResponsePtr response(mojo::HttpResponse::New());
87 response->headers.resize(2);
88 response->headers[0] = mojo::HttpHeader::New();
89 response->headers[0]->name = "Content-Length";
90 response->headers[0]->value =
91 base::StringPrintf("%lu", static_cast<unsigned long>(body.size()));
92 response->headers[1] = mojo::HttpHeader::New();
93 response->headers[1]->name = "Content-Type";
94 response->headers[1]->value = content_type;
96 if (!body.empty()) {
97 uint32_t num_bytes = static_cast<uint32_t>(body.size());
98 MojoCreateDataPipeOptions options;
99 options.struct_size = sizeof(MojoCreateDataPipeOptions);
100 options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
101 options.element_num_bytes = 1;
102 options.capacity_num_bytes = num_bytes;
103 mojo::DataPipe data_pipe(options);
104 response->body = data_pipe.consumer_handle.Pass();
105 MojoResult result =
106 WriteDataRaw(data_pipe.producer_handle.get(), body.data(), &num_bytes,
107 MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
108 CHECK_EQ(MOJO_RESULT_OK, result);
110 return response.Pass();
113 mojo::HttpResponsePtr MakeJsonResponse(uint32_t status_code,
114 base::Value* value,
115 const std::string& message) {
116 // Serialize value and message.
117 std::string json_value;
118 if (value) {
119 base::JSONWriter::WriteWithOptions(
120 *value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_value);
123 return MakeResponse(status_code, "application/json; charset=UTF-8",
124 json_value + message);
127 class WebSocketRelayer : public DevToolsAgentHost::Delegate,
128 public mojo::WebSocketClient {
129 public:
130 // Creates a WebSocketRelayer instance and sets it as the delegate of
131 // |agent_host|.
133 // The object destroys itself when either of the following happens:
134 // - |agent_host| is dead and the object finishes all pending sends (if any)
135 // to the Web socket; or
136 // - the underlying pipe of |web_socket| is closed and the object finishes all
137 // pending receives (if any) from the Web socket.
138 static mojo::WebSocketClientPtr SetUp(
139 DevToolsAgentHost* agent_host,
140 mojo::WebSocketPtr web_socket,
141 mojo::ScopedDataPipeProducerHandle send_stream) {
142 DCHECK(agent_host);
143 DCHECK(web_socket);
144 DCHECK(send_stream.is_valid());
146 mojo::WebSocketClientPtr web_socket_client;
147 new WebSocketRelayer(agent_host, web_socket.Pass(), send_stream.Pass(),
148 &web_socket_client);
149 return web_socket_client.Pass();
152 private:
153 WebSocketRelayer(DevToolsAgentHost* agent_host,
154 mojo::WebSocketPtr web_socket,
155 mojo::ScopedDataPipeProducerHandle send_stream,
156 mojo::WebSocketClientPtr* web_socket_client)
157 : agent_host_(agent_host),
158 binding_(this, web_socket_client),
159 web_socket_(web_socket.Pass()),
160 send_stream_(send_stream.Pass()),
161 write_send_stream_(new mojo::WebSocketWriteQueue(send_stream_.get())),
162 pending_send_count_(0),
163 pending_receive_count_(0) {
164 web_socket_.set_connection_error_handler([this]() { OnConnectionError(); });
165 agent_host->SetDelegate(this);
168 ~WebSocketRelayer() override {
169 if (agent_host_)
170 agent_host_->SetDelegate(nullptr);
173 // DevToolsAgentHost::Delegate implementation.
174 void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
175 const std::string& message) override {
176 if (!web_socket_)
177 return;
179 // TODO(yzshen): It shouldn't be an issue to pass an empty message. However,
180 // WebSocket{Read,Write}Queue doesn't handle that correctly.
181 if (message.empty())
182 return;
184 pending_send_count_++;
185 uint32_t size = static_cast<uint32_t>(message.size());
186 write_send_stream_->Write(
187 &message[0], size,
188 base::Bind(&WebSocketRelayer::OnFinishedWritingSendStream,
189 base::Unretained(this), size));
192 void OnAgentHostClosed(DevToolsAgentHost* agent_host) override {
193 DispatchProtocolMessage(agent_host_,
194 "{ \"method\": \"Inspector.detached\", "
195 "\"params\": { \"reason\": \"target_closed\" } }");
197 // No need to call SetDelegate(nullptr) on |agent_host_| because it is going
198 // away.
199 agent_host_ = nullptr;
201 if (ShouldSelfDestruct())
202 delete this;
205 // WebSocketClient implementation.
206 void DidConnect(const mojo::String& selected_subprotocol,
207 const mojo::String& extensions,
208 mojo::ScopedDataPipeConsumerHandle receive_stream) override {
209 receive_stream_ = receive_stream.Pass();
210 read_receive_stream_.reset(
211 new mojo::WebSocketReadQueue(receive_stream_.get()));
214 void DidReceiveData(bool fin,
215 mojo::WebSocket::MessageType type,
216 uint32_t num_bytes) override {
217 if (!agent_host_)
218 return;
220 // TODO(yzshen): It shouldn't be an issue to pass an empty message. However,
221 // WebSocket{Read,Write}Queue doesn't handle that correctly.
222 if (num_bytes == 0)
223 return;
225 pending_receive_count_++;
226 read_receive_stream_->Read(
227 num_bytes, base::Bind(&WebSocketRelayer::OnFinishedReadingReceiveStream,
228 base::Unretained(this), num_bytes));
231 void DidReceiveFlowControl(int64_t quota) override {}
233 void DidFail(const mojo::String& message) override {}
235 void DidClose(bool was_clean,
236 uint16_t code,
237 const mojo::String& reason) override {}
239 void OnConnectionError() {
240 web_socket_ = nullptr;
241 binding_.Close();
243 if (ShouldSelfDestruct())
244 delete this;
247 void OnFinishedWritingSendStream(uint32_t num_bytes, const char* buffer) {
248 DCHECK_GT(pending_send_count_, 0u);
249 pending_send_count_--;
251 if (web_socket_ && buffer)
252 web_socket_->Send(true, mojo::WebSocket::MESSAGE_TYPE_TEXT, num_bytes);
254 if (ShouldSelfDestruct())
255 delete this;
258 void OnFinishedReadingReceiveStream(uint32_t num_bytes, const char* data) {
259 DCHECK_GT(pending_receive_count_, 0u);
260 pending_receive_count_--;
262 if (agent_host_ && data)
263 agent_host_->SendProtocolMessageToAgent(std::string(data, num_bytes));
265 if (ShouldSelfDestruct())
266 delete this;
269 bool ShouldSelfDestruct() const {
270 return (!agent_host_ && pending_send_count_ == 0) ||
271 (!web_socket_ && pending_receive_count_ == 0);
274 DevToolsAgentHost* agent_host_;
275 mojo::Binding<WebSocketClient> binding_;
276 mojo::WebSocketPtr web_socket_;
278 mojo::ScopedDataPipeProducerHandle send_stream_;
279 scoped_ptr<mojo::WebSocketWriteQueue> write_send_stream_;
280 size_t pending_send_count_;
282 mojo::ScopedDataPipeConsumerHandle receive_stream_;
283 scoped_ptr<mojo::WebSocketReadQueue> read_receive_stream_;
284 size_t pending_receive_count_;
286 DISALLOW_COPY_AND_ASSIGN(WebSocketRelayer);
289 } // namespace
291 class DevToolsHttpServer::HttpConnectionDelegateImpl
292 : public mojo::HttpConnectionDelegate {
293 public:
294 HttpConnectionDelegateImpl(
295 DevToolsHttpServer* owner,
296 mojo::HttpConnectionPtr connection,
297 mojo::InterfaceRequest<HttpConnectionDelegate> delegate_request)
298 : owner_(owner),
299 connection_(connection.Pass()),
300 binding_(this, delegate_request.Pass()) {
301 DCHECK(owner_);
302 DCHECK(connection_);
303 DCHECK(binding_.is_bound());
305 auto error_handler = [this]() { owner_->OnConnectionClosed(this); };
306 connection_.set_connection_error_handler(error_handler);
307 binding_.set_connection_error_handler(error_handler);
310 mojo::HttpConnection* connection() { return connection_.get(); }
312 private:
313 // mojo::HttpConnectionDelegate implementation:
314 void OnReceivedRequest(mojo::HttpRequestPtr request,
315 const OnReceivedRequestCallback& callback) override {
316 owner_->OnReceivedRequest(this, request.Pass(), callback);
319 void OnReceivedWebSocketRequest(
320 mojo::HttpRequestPtr request,
321 const OnReceivedWebSocketRequestCallback& callback) override {
322 owner_->OnReceivedWebSocketRequest(this, request.Pass(), callback);
325 DevToolsHttpServer* const owner_;
326 mojo::HttpConnectionPtr connection_;
327 mojo::Binding<HttpConnectionDelegate> binding_;
329 DISALLOW_COPY_AND_ASSIGN(HttpConnectionDelegateImpl);
332 DevToolsHttpServer::DevToolsHttpServer(DevToolsService* service,
333 uint16_t remote_debugging_port)
334 : service_(service), remote_debugging_port_(remote_debugging_port) {
335 VLOG(1) << "Remote debugging HTTP server is started on port "
336 << remote_debugging_port << ".";
337 mojo::NetworkServicePtr network_service;
338 mojo::URLRequestPtr request(mojo::URLRequest::New());
339 request->url = "mojo:network_service";
340 service_->application()->ConnectToService(request.Pass(), &network_service);
342 mojo::NetAddressPtr local_address(mojo::NetAddress::New());
343 local_address->family = mojo::NET_ADDRESS_FAMILY_IPV4;
344 local_address->ipv4 = mojo::NetAddressIPv4::New();
345 local_address->ipv4->port = remote_debugging_port;
346 local_address->ipv4->addr.resize(4);
347 local_address->ipv4->addr[0] = 127;
348 local_address->ipv4->addr[1] = 0;
349 local_address->ipv4->addr[2] = 0;
350 local_address->ipv4->addr[3] = 1;
352 mojo::HttpServerDelegatePtr http_server_delegate;
353 http_server_delegate_binding_.reset(
354 new mojo::Binding<mojo::HttpServerDelegate>(this, &http_server_delegate));
355 network_service->CreateHttpServer(
356 local_address.Pass(), http_server_delegate.Pass(),
357 mojo::NetworkService::CreateHttpServerCallback());
360 DevToolsHttpServer::~DevToolsHttpServer() {
361 STLDeleteElements(&connections_);
364 void DevToolsHttpServer::OnConnected(
365 mojo::HttpConnectionPtr connection,
366 mojo::InterfaceRequest<mojo::HttpConnectionDelegate> delegate) {
367 connections_.insert(
368 new HttpConnectionDelegateImpl(this, connection.Pass(), delegate.Pass()));
371 void DevToolsHttpServer::OnReceivedRequest(
372 HttpConnectionDelegateImpl* connection,
373 mojo::HttpRequestPtr request,
374 const OnReceivedRequestCallback& callback) {
375 DCHECK(connections_.find(connection) != connections_.end());
377 if (request->url.get().find(kJsonRequestUrlPrefix) == 0) {
378 mojo::HttpResponsePtr response = ProcessJsonRequest(request.Pass());
379 if (response)
380 callback.Run(response.Pass());
381 else
382 OnConnectionClosed(connection);
383 } else {
384 // TODO(yzshen): Implement it.
385 NOTIMPLEMENTED();
386 callback.Run(MakeResponse(404, "text/html", "Not implemented yet!"));
390 void DevToolsHttpServer::OnReceivedWebSocketRequest(
391 HttpConnectionDelegateImpl* connection,
392 mojo::HttpRequestPtr request,
393 const OnReceivedWebSocketRequestCallback& callback) {
394 DCHECK(connections_.find(connection) != connections_.end());
396 std::string path = request->url;
397 size_t browser_pos = path.find(kBrowserUrlPrefix);
398 if (browser_pos == 0) {
399 // TODO(yzshen): Implement it.
400 NOTIMPLEMENTED();
401 callback.Run(nullptr, mojo::ScopedDataPipeConsumerHandle(), nullptr);
402 return;
405 size_t pos = path.find(kPageUrlPrefix);
406 if (pos != 0) {
407 callback.Run(nullptr, mojo::ScopedDataPipeConsumerHandle(), nullptr);
408 return;
411 std::string target_id = path.substr(strlen(kPageUrlPrefix));
412 DevToolsAgentHost* agent = service_->registry()->GetAgentById(target_id);
413 if (!agent || agent->IsAttached()) {
414 callback.Run(nullptr, mojo::ScopedDataPipeConsumerHandle(), nullptr);
415 return;
418 mojo::WebSocketPtr web_socket;
419 mojo::InterfaceRequest<mojo::WebSocket> web_socket_request =
420 mojo::GetProxy(&web_socket);
421 mojo::DataPipe data_pipe;
422 mojo::WebSocketClientPtr web_socket_client = WebSocketRelayer::SetUp(
423 agent, web_socket.Pass(), data_pipe.producer_handle.Pass());
424 callback.Run(web_socket_request.Pass(), data_pipe.consumer_handle.Pass(),
425 web_socket_client.Pass());
428 void DevToolsHttpServer::OnConnectionClosed(
429 HttpConnectionDelegateImpl* connection) {
430 DCHECK(connections_.find(connection) != connections_.end());
432 delete connection;
433 connections_.erase(connection);
436 mojo::HttpResponsePtr DevToolsHttpServer::ProcessJsonRequest(
437 mojo::HttpRequestPtr request) {
438 // Trim "/json".
439 std::string path = request->url.get().substr(strlen(kJsonRequestUrlPrefix));
441 // Trim query.
442 size_t query_pos = path.find("?");
443 if (query_pos != std::string::npos)
444 path = path.substr(0, query_pos);
446 // Trim fragment.
447 size_t fragment_pos = path.find("#");
448 if (fragment_pos != std::string::npos)
449 path = path.substr(0, fragment_pos);
451 std::string command;
452 std::string target_id;
453 if (!ParseJsonPath(path, &command, &target_id))
454 return MakeJsonResponse(404, nullptr,
455 "Malformed query: " + request->url.get());
457 if (command == kVersionCommand || command == kNewCommand ||
458 command == kActivateCommand || command == kCloseCommand) {
459 NOTIMPLEMENTED();
460 return MakeJsonResponse(404, nullptr,
461 "Not implemented yet: " + request->url.get());
464 if (command == kListCommand) {
465 DevToolsRegistryImpl::Iterator iter(service_->registry());
466 if (iter.IsAtEnd()) {
467 // If no agent is available, return a nullptr to indicate that the
468 // connection should be closed.
469 return nullptr;
472 std::string host = GetHeaderValue(*request, "host");
473 if (host.empty()) {
474 host = base::StringPrintf("127.0.0.1:%u",
475 static_cast<unsigned>(remote_debugging_port_));
478 base::ListValue list_value;
479 for (; !iter.IsAtEnd(); iter.Advance()) {
480 scoped_ptr<base::DictionaryValue> dict_value(new base::DictionaryValue());
482 // TODO(yzshen): Add more information.
483 dict_value->SetString(kTargetDescriptionField, std::string());
484 dict_value->SetString(kTargetDevtoolsFrontendUrlField, std::string());
485 dict_value->SetString(kTargetIdField, iter.value()->id());
486 dict_value->SetString(kTargetTitleField, std::string());
487 dict_value->SetString(kTargetTypeField, "page");
488 dict_value->SetString(kTargetUrlField, std::string());
489 dict_value->SetString(
490 kTargetWebSocketDebuggerUrlField,
491 base::StringPrintf("ws://%s%s%s", host.c_str(), kPageUrlPrefix,
492 iter.value()->id().c_str()));
493 list_value.Append(dict_value.Pass());
495 return MakeJsonResponse(200, &list_value, std::string());
498 return MakeJsonResponse(404, nullptr, "Unknown command: " + command);
501 } // namespace devtools_service