Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1778)

Side by Side Diff: components/devtools_service/devtools_http_server.cc

Issue 1164383002: Mandoline DevTools service: remote debugging protocol over HTTP/WebSocket. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/devtools_service/devtools_http_server.h" 5 #include "components/devtools_service/devtools_http_server.h"
6 6
7 #include <string.h>
pfeldman 2015/06/10 09:28:21 Why do we build a new universe here? Is there a wa
yzshen1 2015/06/10 15:34:39 Thanks for the comment! I feel hard to reuse devt
8
9 #include <string>
10
11 #include "base/bind.h"
12 #include "base/json/json_writer.h"
7 #include "base/logging.h" 13 #include "base/logging.h"
8 #include "base/stl_util.h" 14 #include "base/stl_util.h"
9 #include "base/strings/stringprintf.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"
10 #include "components/devtools_service/devtools_service.h" 19 #include "components/devtools_service/devtools_service.h"
11 #include "mojo/application/public/cpp/application_impl.h" 20 #include "mojo/application/public/cpp/application_impl.h"
12 #include "mojo/services/network/public/interfaces/http_message.mojom.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"
13 #include "mojo/services/network/public/interfaces/net_address.mojom.h" 23 #include "mojo/services/network/public/interfaces/net_address.mojom.h"
14 #include "mojo/services/network/public/interfaces/network_service.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"
15 27
16 namespace devtools_service { 28 namespace devtools_service {
17 29
30 namespace {
31
32 const char kPageUrlPrefix[] = "/devtools/page/";
33 const char kBrowserUrlPrefix[] = "/devtools/browser";
34 const char kJsonRequestUrlPrefix[] = "/json";
35
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";
41
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";
49
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;
57 }
58
59 if (path.find("/") != 0) {
60 // Malformed command.
61 return false;
62 }
63 *command = path.substr(1);
64
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);
69 }
70 return true;
71 }
72
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;
85
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);
99 }
100 return response.Pass();
101 }
102
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);
111 }
112 std::string json_message;
113 base::JSONWriter::Write(base::StringValue(message), &json_message);
114
115 return MakeResponse(status_code, "application/json; charset=UTF-8",
116 json_value + json_message);
117 }
118
119 class WebSocketRelayer : public DevToolsAgentHost::Delegate,
120 public mojo::WebSocketClient,
121 public mojo::ErrorHandler {
122 public:
123 // Creates a WebSocketRelayer instance and sets it as the delegate of
124 // |agent_host|.
125 //
126 // The object destroys itself when either of the following happens:
127 // - |agent_host| is dead and the object finishes all pending sends (if any)
128 // to the Web socket; or
129 // - the underlying pipe of |web_socket| is closed and the object finishes all
130 // pending receives (if any) from the Web socket.
131 static mojo::WebSocketClientPtr SetUp(
132 DevToolsAgentHost* agent_host,
133 mojo::WebSocketPtr web_socket,
134 mojo::ScopedDataPipeProducerHandle send_stream) {
135 DCHECK(agent_host);
136 DCHECK(web_socket);
137 DCHECK(send_stream.is_valid());
138
139 mojo::WebSocketClientPtr web_socket_client;
140 new WebSocketRelayer(agent_host, web_socket.Pass(), send_stream.Pass(),
141 &web_socket_client);
142 return web_socket_client.Pass();
143 }
144
145 private:
146 WebSocketRelayer(DevToolsAgentHost* agent_host,
147 mojo::WebSocketPtr web_socket,
148 mojo::ScopedDataPipeProducerHandle send_stream,
149 mojo::WebSocketClientPtr* web_socket_client)
150 : agent_host_(agent_host),
151 binding_(this, web_socket_client),
152 web_socket_(web_socket.Pass()),
153 send_stream_(send_stream.Pass()),
154 write_send_stream_(new mojo::WebSocketWriteQueue(send_stream_.get())),
155 pending_send_count_(0),
156 pending_receive_count_(0) {
157 web_socket_.set_error_handler(this);
158 agent_host->SetDelegate(this);
159 }
160
161 ~WebSocketRelayer() override {
162 if (agent_host_)
163 agent_host_->SetDelegate(nullptr);
164 }
165
166 // DevToolsAgentHost::Delegate implementation.
167 void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
168 const std::string& message) {
169 if (!web_socket_)
170 return;
171
172 // TODO(yzshen): It shouldn't be an issue to pass an empty message. However,
173 // WebSocket{Read,Write}Queue doesn't handle that correctly.
174 if (message.empty())
175 return;
176
177 pending_send_count_++;
178 uint32_t size = static_cast<uint32_t>(message.size());
179 write_send_stream_->Write(
180 &message[0], size,
181 base::Bind(&WebSocketRelayer::OnFinishedWritingSendStream,
182 base::Unretained(this), size));
183 }
184
185 void OnAgentHostClosed(DevToolsAgentHost* agent_host) override {
186 DispatchProtocolMessage(agent_host_,
187 "{ \"method\": \"Inspector.detached\", "
188 "\"params\": { \"reason\": \"target_closed\" } }");
189
190 // No need to call SetDelegate(nullptr) on |agent_host_| because it is going
191 // away.
192 agent_host_ = nullptr;
193
194 if (ShouldSelfDestruct())
195 delete this;
196 }
197
198 // WebSocketClient implementation.
199 void DidConnect(const mojo::String& selected_subprotocol,
200 const mojo::String& extensions,
201 mojo::ScopedDataPipeConsumerHandle receive_stream) override {
202 receive_stream_ = receive_stream.Pass();
203 read_receive_stream_.reset(
204 new mojo::WebSocketReadQueue(receive_stream_.get()));
205 }
206
207 void DidReceiveData(bool fin,
208 mojo::WebSocket::MessageType type,
209 uint32_t num_bytes) override {
210 if (!agent_host_)
211 return;
212
213 // TODO(yzshen): It shouldn't be an issue to pass an empty message. However,
214 // WebSocket{Read,Write}Queue doesn't handle that correctly.
215 if (num_bytes == 0)
216 return;
217
218 pending_receive_count_++;
219 read_receive_stream_->Read(
220 num_bytes, base::Bind(&WebSocketRelayer::OnFinishedReadingReceiveStream,
221 base::Unretained(this), num_bytes));
222 }
223
224 void DidReceiveFlowControl(int64_t quota) override {}
225
226 void DidFail(const mojo::String& message) override {}
227
228 void DidClose(bool was_clean,
229 uint16_t code,
230 const mojo::String& reason) override {}
231
232 // mojo::ErrorHandler implementation.
233 void OnConnectionError() override {
234 web_socket_ = nullptr;
235 binding_.Close();
236
237 if (ShouldSelfDestruct())
238 delete this;
239 }
240
241 void OnFinishedWritingSendStream(uint32_t num_bytes, const char* buffer) {
242 DCHECK_GT(pending_send_count_, 0u);
243 pending_send_count_--;
244
245 if (web_socket_ && buffer)
246 web_socket_->Send(true, mojo::WebSocket::MESSAGE_TYPE_TEXT, num_bytes);
247
248 if (ShouldSelfDestruct())
249 delete this;
250 }
251
252 void OnFinishedReadingReceiveStream(uint32_t num_bytes, const char* data) {
253 DCHECK_GT(pending_receive_count_, 0u);
254 pending_receive_count_--;
255
256 if (agent_host_ && data)
257 agent_host_->SendProtocolMessageToAgent(std::string(data, num_bytes));
258
259 if (ShouldSelfDestruct())
260 delete this;
261 }
262
263 bool ShouldSelfDestruct() const {
264 return (!agent_host_ && pending_send_count_ == 0) ||
265 (!web_socket_ && pending_receive_count_ == 0);
266 }
267
268 DevToolsAgentHost* agent_host_;
269 mojo::Binding<WebSocketClient> binding_;
270 mojo::WebSocketPtr web_socket_;
271
272 mojo::ScopedDataPipeProducerHandle send_stream_;
273 scoped_ptr<mojo::WebSocketWriteQueue> write_send_stream_;
274 size_t pending_send_count_;
275
276 mojo::ScopedDataPipeConsumerHandle receive_stream_;
277 scoped_ptr<mojo::WebSocketReadQueue> read_receive_stream_;
278 size_t pending_receive_count_;
279
280 DISALLOW_COPY_AND_ASSIGN(WebSocketRelayer);
281 };
282
283 } // namespace
284
18 class DevToolsHttpServer::HttpConnectionDelegateImpl 285 class DevToolsHttpServer::HttpConnectionDelegateImpl
19 : public mojo::HttpConnectionDelegate, 286 : public mojo::HttpConnectionDelegate,
20 public mojo::ErrorHandler { 287 public mojo::ErrorHandler {
21 public: 288 public:
22 HttpConnectionDelegateImpl( 289 HttpConnectionDelegateImpl(
23 DevToolsHttpServer* owner, 290 DevToolsHttpServer* owner,
24 mojo::HttpConnectionPtr connection, 291 mojo::HttpConnectionPtr connection,
25 mojo::InterfaceRequest<HttpConnectionDelegate> delegate_request) 292 mojo::InterfaceRequest<HttpConnectionDelegate> delegate_request)
26 : owner_(owner), 293 : owner_(owner),
27 connection_(connection.Pass()), 294 connection_(connection.Pass()),
(...skipping 26 matching lines...) Expand all
54 321
55 DevToolsHttpServer* const owner_; 322 DevToolsHttpServer* const owner_;
56 mojo::HttpConnectionPtr connection_; 323 mojo::HttpConnectionPtr connection_;
57 mojo::Binding<HttpConnectionDelegate> binding_; 324 mojo::Binding<HttpConnectionDelegate> binding_;
58 325
59 DISALLOW_COPY_AND_ASSIGN(HttpConnectionDelegateImpl); 326 DISALLOW_COPY_AND_ASSIGN(HttpConnectionDelegateImpl);
60 }; 327 };
61 328
62 DevToolsHttpServer::DevToolsHttpServer(DevToolsService* service, 329 DevToolsHttpServer::DevToolsHttpServer(DevToolsService* service,
63 uint16_t remote_debugging_port) 330 uint16_t remote_debugging_port)
64 : service_(service) { 331 : service_(service), remote_debugging_port_(remote_debugging_port) {
65 VLOG(1) << "Remote debugging HTTP server is started on port " 332 VLOG(1) << "Remote debugging HTTP server is started on port "
66 << remote_debugging_port << "."; 333 << remote_debugging_port << ".";
67 mojo::NetworkServicePtr network_service; 334 mojo::NetworkServicePtr network_service;
68 mojo::URLRequestPtr request(mojo::URLRequest::New()); 335 mojo::URLRequestPtr request(mojo::URLRequest::New());
69 request->url = "mojo:network_service"; 336 request->url = "mojo:network_service";
70 service_->application()->ConnectToService(request.Pass(), &network_service); 337 service_->application()->ConnectToService(request.Pass(), &network_service);
71 338
72 mojo::NetAddressPtr local_address(mojo::NetAddress::New()); 339 mojo::NetAddressPtr local_address(mojo::NetAddress::New());
73 local_address->family = mojo::NET_ADDRESS_FAMILY_IPV4; 340 local_address->family = mojo::NET_ADDRESS_FAMILY_IPV4;
74 local_address->ipv4 = mojo::NetAddressIPv4::New(); 341 local_address->ipv4 = mojo::NetAddressIPv4::New();
(...skipping 22 matching lines...) Expand all
97 connections_.insert( 364 connections_.insert(
98 new HttpConnectionDelegateImpl(this, connection.Pass(), delegate.Pass())); 365 new HttpConnectionDelegateImpl(this, connection.Pass(), delegate.Pass()));
99 } 366 }
100 367
101 void DevToolsHttpServer::OnReceivedRequest( 368 void DevToolsHttpServer::OnReceivedRequest(
102 HttpConnectionDelegateImpl* connection, 369 HttpConnectionDelegateImpl* connection,
103 mojo::HttpRequestPtr request, 370 mojo::HttpRequestPtr request,
104 const OnReceivedRequestCallback& callback) { 371 const OnReceivedRequestCallback& callback) {
105 DCHECK(connections_.find(connection) != connections_.end()); 372 DCHECK(connections_.find(connection) != connections_.end());
106 373
107 // TODO(yzshen): Implement it. 374 if (request->url.get().find(kJsonRequestUrlPrefix) == 0) {
108 static const char kNotImplemented[] = "Not implemented yet!"; 375 callback.Run(ProcessJsonRequest(request.Pass()));
109 mojo::HttpResponsePtr response(mojo::HttpResponse::New()); 376 } else {
110 response->headers.resize(2); 377 // TODO(yzshen): Implement it.
111 response->headers[0] = mojo::HttpHeader::New(); 378 NOTIMPLEMENTED();
112 response->headers[0]->name = "Content-Length"; 379 callback.Run(MakeResponse(404, "text/html", "Not implemented yet!"));
113 response->headers[0]->value = base::StringPrintf( 380 }
114 "%lu", static_cast<unsigned long>(sizeof(kNotImplemented)));
115 response->headers[1] = mojo::HttpHeader::New();
116 response->headers[1]->name = "Content-Type";
117 response->headers[1]->value = "text/html";
118
119 uint32_t num_bytes = sizeof(kNotImplemented);
120 MojoCreateDataPipeOptions options;
121 options.struct_size = sizeof(MojoCreateDataPipeOptions);
122 options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
123 options.element_num_bytes = 1;
124 options.capacity_num_bytes = num_bytes;
125 mojo::DataPipe data_pipe(options);
126 response->body = data_pipe.consumer_handle.Pass();
127 WriteDataRaw(data_pipe.producer_handle.get(), kNotImplemented, &num_bytes,
128 MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
129
130 callback.Run(response.Pass());
131 } 381 }
132 382
133 void DevToolsHttpServer::OnReceivedWebSocketRequest( 383 void DevToolsHttpServer::OnReceivedWebSocketRequest(
134 HttpConnectionDelegateImpl* connection, 384 HttpConnectionDelegateImpl* connection,
135 mojo::HttpRequestPtr request, 385 mojo::HttpRequestPtr request,
136 const OnReceivedWebSocketRequestCallback& callback) { 386 const OnReceivedWebSocketRequestCallback& callback) {
137 DCHECK(connections_.find(connection) != connections_.end()); 387 DCHECK(connections_.find(connection) != connections_.end());
138 388
139 // TODO(yzshen): Implement it. 389 std::string path = request->url;
140 NOTIMPLEMENTED(); 390 size_t browser_pos = path.find(kBrowserUrlPrefix);
391 if (browser_pos == 0) {
392 // TODO(yzshen): Implement it.
393 NOTIMPLEMENTED();
394 callback.Run(nullptr, mojo::ScopedDataPipeConsumerHandle(), nullptr);
395 return;
396 }
397
398 size_t pos = path.find(kPageUrlPrefix);
399 if (pos != 0) {
400 callback.Run(nullptr, mojo::ScopedDataPipeConsumerHandle(), nullptr);
401 return;
402 }
403
404 std::string target_id = path.substr(strlen(kPageUrlPrefix));
405 DevToolsAgentHost* agent = service_->registry()->GetAgentById(target_id);
406 if (!agent || agent->IsAttached()) {
407 callback.Run(nullptr, mojo::ScopedDataPipeConsumerHandle(), nullptr);
408 return;
409 }
410
411 mojo::WebSocketPtr web_socket;
412 mojo::InterfaceRequest<mojo::WebSocket> web_socket_request =
413 mojo::GetProxy(&web_socket);
414 mojo::DataPipe data_pipe;
415 mojo::WebSocketClientPtr web_socket_client = WebSocketRelayer::SetUp(
416 agent, web_socket.Pass(), data_pipe.producer_handle.Pass());
417 callback.Run(web_socket_request.Pass(), data_pipe.consumer_handle.Pass(),
418 web_socket_client.Pass());
141 } 419 }
142 420
143 void DevToolsHttpServer::OnConnectionClosed( 421 void DevToolsHttpServer::OnConnectionClosed(
144 HttpConnectionDelegateImpl* connection) { 422 HttpConnectionDelegateImpl* connection) {
145 DCHECK(connections_.find(connection) != connections_.end()); 423 DCHECK(connections_.find(connection) != connections_.end());
146 424
147 delete connection; 425 delete connection;
148 connections_.erase(connection); 426 connections_.erase(connection);
149 } 427 }
150 428
429 mojo::HttpResponsePtr DevToolsHttpServer::ProcessJsonRequest(
430 mojo::HttpRequestPtr request) {
431 // Trim "/json".
432 std::string path = request->url.get().substr(strlen(kJsonRequestUrlPrefix));
433
434 // Trim query.
435 size_t query_pos = path.find("?");
436 if (query_pos != std::string::npos)
437 path = path.substr(0, query_pos);
438
439 // Trim fragment.
440 size_t fragment_pos = path.find("#");
441 if (fragment_pos != std::string::npos)
442 path = path.substr(0, fragment_pos);
443
444 std::string command;
445 std::string target_id;
446 if (!ParseJsonPath(path, &command, &target_id))
447 return MakeJsonResponse(404, nullptr,
448 "Malformed query: " + request->url.get());
449
450 if (command == kVersionCommand || command == kNewCommand ||
451 command == kActivateCommand || command == kCloseCommand) {
452 NOTIMPLEMENTED();
453 return MakeJsonResponse(404, nullptr,
454 "Not implemented yet: " + request->url.get());
455 }
456
457 if (command == kListCommand) {
458 base::ListValue list_value;
459 for (DevToolsRegistryImpl::Iterator iter(service_->registry());
460 !iter.IsAtEnd(); iter.Advance()) {
461 scoped_ptr<base::DictionaryValue> dict_value(new base::DictionaryValue());
462
463 // TODO(yzshen): Add more information.
464 dict_value->SetString(kTargetDescriptionField, std::string());
465 dict_value->SetString(kTargetDevtoolsFrontendUrlField, std::string());
466 dict_value->SetString(kTargetIdField, iter.value()->id());
467 dict_value->SetString(kTargetTitleField, std::string());
468 dict_value->SetString(kTargetTypeField, "page");
469 dict_value->SetString(kTargetUrlField, std::string());
470 dict_value->SetString(
471 kTargetWebSocketDebuggerUrlField,
472 base::StringPrintf("ws://127.0.0.1:%u%s%s",
473 static_cast<unsigned>(remote_debugging_port_),
474 kPageUrlPrefix, iter.value()->id().c_str()));
475 list_value.Append(dict_value.Pass());
476 }
477 return MakeJsonResponse(200, &list_value, std::string());
478 }
479
480 return MakeJsonResponse(404, nullptr, "Unknown command: " + command);
481 }
482
151 } // namespace devtools_service 483 } // namespace devtools_service
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698