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

Side by Side Diff: runtime/bin/vmservice/client/lib/service_html.dart

Issue 335463008: Allow Observatory to run as a hosted web service (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 5 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library service_html; 5 library service_html;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:convert'; 8 import 'dart:convert';
9 import 'dart:html'; 9 import 'dart:html';
10 10
11 import 'package:logging/logging.dart'; 11 import 'package:logging/logging.dart';
12 import 'package:observatory/service.dart'; 12 import 'package:observatory/service.dart';
13 13
14 // Export the service library. 14 // Export the service library.
15 export 'package:observatory/service.dart'; 15 export 'package:observatory/service.dart';
16 16
17 class HttpVM extends VM { 17 /// Description of a VM target.
18 String host; 18 class WebSocketVMTarget {
19 19 // Last time this VM has been connected to.
20 bool runningInJavaScript() => identical(1.0, 1); 20 int lastConnectionTime = 0;
21 21 bool get hasEverConnected => lastConnectionTime > 0;
22 HttpVM() : super() { 22
23 if (runningInJavaScript()) { 23 // Chrome VM or standalone;
24 // When we are running as JavaScript use the same hostname:port 24 bool chrome = false;
25 // that the Observatory is loaded from. 25 bool get standalone => !chrome;
26 host = 'http://${window.location.host}/'; 26
27 // User defined name.
28 String name;
29 // Network address of VM.
30 String networkAddress;
31
32 WebSocketVMTarget(this.networkAddress) {
33 name = networkAddress;
34 }
35
36 WebSocketVMTarget.fromMap(Map json) {
37 lastConnectionTime = json['lastConnectionTime'];
38 chrome = json['chrome'];
39 name = json['name'];
40 networkAddress = json['networkAddress'];
41 if (name == null) {
42 name = networkAddress;
43 }
44 }
45
46 Map toJson() {
47 return {
48 'lastConnectionTime': lastConnectionTime,
49 'chrome': chrome,
50 'name': name,
51 'networkAddress': networkAddress,
52 };
53 }
54 }
55
56 class _WebSocketRequest {
57 final String id;
58 final Completer<String> completer;
59 _WebSocketRequest(this.id)
60 : completer = new Completer<String>();
61 }
62
63 /// The [WebSocketVM] communicates with a Dart VM over WebSocket. The Dart VM
64 /// can be embedded in Chromium or standalone. In the case of Chromium, we
65 /// make the service requests via the Chrome Remote Debugging Protocol.
66 class WebSocketVM extends VM {
67 final Completer _connected = new Completer();
68 final Completer _disconnected = new Completer();
69 final WebSocketVMTarget target;
70 final Map<String, _WebSocketRequest> _delayedRequests =
71 new Map<String, _WebSocketRequest>();
72 final Map<String, _WebSocketRequest> _pendingRequests =
73 new Map<String, _WebSocketRequest>();
74 int _requestSerial = 0;
75 WebSocket _webSocket;
76
77 WebSocketVM(this.target) {
78 assert(target != null);
79 }
80
81 void _notifyConnect() {
82 if (!_connected.isCompleted) {
83 Logger.root.info('WebSocketVM connection opened: ${target.networkAddress}' );
84 _connected.complete(this);
85 }
86 }
87 Future get onConnect => _connected.future;
88 void _notifyDisconnect() {
89 if (!_disconnected.isCompleted) {
90 Logger.root.info('WebSocketVM connection error: ${target.networkAddress}') ;
91 _disconnected.complete(this);
92 }
93 }
94 Future get onDisconnect => _disconnected.future;
95
96 void disconnect() {
97 if (_webSocket != null) {
98 _webSocket.close();
99 }
100 _cancelAllRequests();
101 _notifyDisconnect();
102 }
103
104 Future<String> getString(String id) {
105 if (_webSocket == null) {
106 // Create a WebSocket.
107 _webSocket = new WebSocket(target.networkAddress);
108 _webSocket.onClose.listen(_onClose);
109 _webSocket.onError.listen(_onError);
110 _webSocket.onOpen.listen(_onOpen);
111 _webSocket.onMessage.listen(_onMessage);
112 }
113 return _makeRequest(id);
114 }
115
116 /// Add a request for [id] to pending requests.
117 Future<String> _makeRequest(String id) {
118 assert(_webSocket != null);
119 // Create request.
120 String serial = (_requestSerial++).toString();
121 var request = new _WebSocketRequest(id);
122 if (_webSocket.readyState == WebSocket.OPEN) {
123 // Already connected, send request immediately.
124 _sendRequest(serial, request);
27 } else { 125 } else {
28 // Otherwise, assume we are running from the Dart Editor and 126 // Not connected yet, add to delayed requests.
29 // want to connect on the default port. 127 _delayedRequests[serial] = request;
30 host = 'http://127.0.0.1:8181/'; 128 }
31 } 129 return request.completer.future;
32 } 130 }
33 131
34 Future<String> getString(String id) { 132 void _onClose(CloseEvent event) {
35 // Ensure we don't request host//id. 133 _cancelAllRequests();
36 if (host.endsWith('/') && id.startsWith('/')) { 134 _notifyDisconnect();
37 id = id.substring(1); 135 }
38 } 136
39 Logger.root.info('Fetching $id from $host'); 137 // WebSocket error event handler.
40 return HttpRequest.request(host + id, 138 void _onError(Event) {
41 requestHeaders: { 139 _cancelAllRequests();
42 'Observatory-Version': '1.0' 140 _notifyDisconnect();
43 }).then((HttpRequest request) { 141 }
44 return request.responseText; 142
45 }).catchError((error) { 143 // WebSocket open event handler.
46 // If we get an error here, the network request has failed. 144 void _onOpen(Event) {
47 Logger.root.severe('HttpRequest.request failed.'); 145 target.lastConnectionTime = new DateTime.now().millisecondsSinceEpoch;
48 var request = error.target; 146 _notifyConnect();
49 return JSON.encode({ 147 _sendAllDelayedRequests();
turnidge 2014/07/01 19:38:16 It's still worth reversing these two lines, so tha
Cutch 2014/07/01 21:42:55 Done.
50 'type': 'ServiceException', 148 }
51 'id': '', 149
52 'response': request.responseText, 150 // WebSocket message event handler.
53 'kind': 'NetworkException', 151 void _onMessage(MessageEvent event) {
54 'message': 'Could not connect to service (${request.statusText}). ' 152 var map = JSON.decode(event.data);
55 'Check that you started the VM with the following flags: ' 153 if (map == null) {
56 '--observe' 154 Logger.root.severe('WebSocketVM got empty message');
57 }); 155 return;
58 }); 156 }
59 } 157 // Extract serial and response.
60 } 158 var serial;
61 159 var response;
62 class WebSocketVM extends VM { 160 if (target.chrome) {
63 final Map<int, Completer> _pendingRequests = 161 if (map['method'] != 'Dart.observatoryData') {
64 new Map<int, Completer>(); 162 // ignore devtools protocol spam.
65 int _requestSerial = 0; 163 return;
66 164 }
67 String _host; 165 serial = map['params']['id'].toString();
68 Future<WebSocket> _socketFuture; 166 response = map['params']['data'];
69
70 bool runningInJavaScript() => identical(1.0, 1);
71
72 WebSocketVM() : super() {
73 if (runningInJavaScript()) {
74 // When we are running as JavaScript use the same hostname:port
75 // that the Observatory is loaded from.
76 _host = 'ws://${window.location.host}/ws';
77 } else { 167 } else {
78 // Otherwise, assume we are running from the Dart Editor and 168 serial = map['seq'];
79 // want to connect on the default port. 169 response = map['response'];
80 _host = 'ws://127.0.0.1:8181/ws'; 170 }
81 } 171 if (serial == null) {
82
83 var completer = new Completer<WebSocket>();
84 _socketFuture = completer.future;
85 var socket = new WebSocket(_host);
86 socket.onOpen.first.then((_) {
87 socket.onMessage.listen(_handleMessage);
88 socket.onClose.first.then((_) {
89 _socketFuture = null;
90 });
91 completer.complete(socket);
92 });
93 socket.onError.first.then((_) {
94 _socketFuture = null;
95 });
96 }
97
98 void _handleMessage(MessageEvent message) {
99 var map = JSON.decode(message.data);
100 int seq = map['seq'];
101 var response = map['response'];
102 if (seq == null) {
103 // Messages without sequence numbers are asynchronous events 172 // Messages without sequence numbers are asynchronous events
104 // from the vm. 173 // from the vm.
105 postEventMessage(response); 174 postEventMessage(response);
106 return; 175 return;
107 } 176 }
108 var completer = _pendingRequests.remove(seq); 177 // Complete request.
109 if (completer == null) { 178 var request = _pendingRequests.remove(serial);
179 if (request == null) {
110 Logger.root.severe('Received unexpected message: ${map}'); 180 Logger.root.severe('Received unexpected message: ${map}');
181 return;
182 }
183 request.completer.complete(response);
184 }
185
186 String _generateNetworkError(String userMessage) {
187 return JSON.encode({
188 'type': 'ServiceException',
189 'id': '',
190 'kind': 'NetworkException',
191 'message': userMessage
192 });
193 }
194
195 void _cancelRequests(Map<String, _WebSocketRequest> requests) {
196 requests.forEach((String serial, _WebSocketRequest request) {
197 request.completer.complete(
198 _generateNetworkError('WebSocket disconnected'));
199 });
200 requests.clear();
201 }
202
203 /// Cancel all pending and delayed requests by completing them with an error.
204 void _cancelAllRequests() {
205 if (_pendingRequests.length > 0) {
206 Logger.root.info('Cancelling all pending requests.');
207 _cancelRequests(_pendingRequests);
208 }
209 if (_delayedRequests.length > 0) {
210 Logger.root.info('Cancelling all delayed requests.');
211 _cancelRequests(_delayedRequests);
212 }
213 }
214
215 /// Send all delayed requests.
216 void _sendAllDelayedRequests() {
217 assert(_webSocket != null);
218 if (_delayedRequests.length == 0) {
219 return;
220 }
221 Logger.root.info('Sending all delayed requests.');
222 // Send all delayed requests.
223 _delayedRequests.forEach(_sendRequest);
224 // Clear all delayed requests.
225 _delayedRequests.clear();
226 }
227
228 /// Send the request over WebSocket.
229 void _sendRequest(String serial, _WebSocketRequest request) {
230 assert (_webSocket.readyState == WebSocket.OPEN);
231 if (!request.id.endsWith('/profile/tag')) {
232 Logger.root.info('GET ${request.id} from ${target.networkAddress}');
233 }
234 // Mark request as pending.
235 assert(_pendingRequests.containsKey(serial) == false);
236 _pendingRequests[serial] = request;
237 var message;
238 // Encode message.
239 if (target.chrome) {
240 message = JSON.encode({
241 'id': int.parse(serial),
242 'method': 'Dart.observatoryQuery',
243 'params': {
244 'id': serial,
245 'query': request.id
246 }
247 });
111 } else { 248 } else {
112 completer.complete(response); 249 message = JSON.encode({'seq': serial, 'request': request.id});
113 } 250 }
114 } 251 // Send message.
115 252 _webSocket.send(message);
116 Future<String> getString(String id) {
117 if (_socketFuture == null) {
118 var errorResponse = JSON.encode({
119 'type': 'ServiceException',
120 'id': '',
121 'response': '',
122 'kind': 'NetworkException',
123 'message': 'Could not connect to service. Check that you started t he'
124 ' VM with the following flags:\n --enable-vm-service'
125 ' --pause-isolates-on-exit'
126 });
127 return new Future.value(errorResponse);
128 }
129 return _socketFuture.then((socket) {
130 int seq = _requestSerial++;
131 if (!id.endsWith('/profile/tag')) {
132 Logger.root.info('Fetching $id from $_host');
133 }
134 var completer = new Completer<String>();
135 _pendingRequests[seq] = completer;
136 var message = JSON.encode({'seq': seq, 'request': id});
137 socket.send(message);
138 return completer.future;
139 });
140 } 253 }
141 } 254 }
142 255
143 class DartiumVM extends VM { 256 // A VM that communicates with the service via posting messages from DevTools.
257 class PostMessageVM extends VM {
258 final Completer _connected = new Completer();
259 final Completer _disconnected = new Completer();
260 void disconnect() { /* nope */ }
261 Future get onConnect => _connected.future;
262 Future get onDisconnect => _disconnected.future;
144 final Map<String, Completer> _pendingRequests = 263 final Map<String, Completer> _pendingRequests =
145 new Map<String, Completer>(); 264 new Map<String, Completer>();
146 int _requestSerial = 0; 265 int _requestSerial = 0;
147 266
148 DartiumVM() : super() { 267 PostMessageVM() : super() {
149 window.onMessage.listen(_messageHandler); 268 window.onMessage.listen(_messageHandler);
150 Logger.root.info('Connected to DartiumVM'); 269 _connected.complete(this);
151 } 270 }
152 271
153 void _messageHandler(msg) { 272 void _messageHandler(msg) {
154 var id = msg.data['id']; 273 var id = msg.data['id'];
155 var name = msg.data['name']; 274 var name = msg.data['name'];
156 var data = msg.data['data']; 275 var data = msg.data['data'];
157 if (name != 'observatoryData') { 276 if (name != 'observatoryData') {
158 return; 277 return;
159 } 278 }
160 var completer = _pendingRequests[id]; 279 var completer = _pendingRequests[id];
161 assert(completer != null); 280 assert(completer != null);
162 _pendingRequests.remove(id); 281 _pendingRequests.remove(id);
163 completer.complete(data); 282 completer.complete(data);
164 } 283 }
165 284
166 Future<String> getString(String path) { 285 Future<String> getString(String path) {
167 var idString = '$_requestSerial'; 286 var idString = '$_requestSerial';
168 Map message = {}; 287 Map message = {};
169 message['id'] = idString; 288 message['id'] = idString;
170 message['method'] = 'observatoryQuery'; 289 message['method'] = 'observatoryQuery';
171 message['query'] = '$path'; 290 message['query'] = '$path';
172 _requestSerial++; 291 _requestSerial++;
173 var completer = new Completer(); 292 var completer = new Completer();
174 _pendingRequests[idString] = completer; 293 _pendingRequests[idString] = completer;
175 window.parent.postMessage(JSON.encode(message), '*'); 294 window.parent.postMessage(JSON.encode(message), '*');
176 return completer.future; 295 return completer.future;
177 } 296 }
178 } 297 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698