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

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 NetworkVMTarget {
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 everConnected => lastConnectionTime > 0;
turnidge 2014/07/01 00:27:47 Maybe "hasConnected" or "hasEverConnected"?
Cutch 2014/07/01 17:14:51 Done.
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 NetworkVMTarget(this.networkAddress) {
33 name = networkAddress;
34 }
35
36 NetworkVMTarget.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
turnidge 2014/07/01 00:27:47 Maybe a class-level comment about NetworkVM. Also
Cutch 2014/07/01 17:14:51 Done.
63 class NetworkVM extends VM {
64 final Completer _connected = new Completer();
65 final Completer _disconnected = new Completer();
66 final NetworkVMTarget target;
67 final Map<String, _WebSocketRequest> _pendingRequests =
68 new Map<String, _WebSocketRequest>();
69 int _requestSerial = 0;
70 WebSocket _webSocket;
71
72 NetworkVM(this.target) {
73 assert(target != null);
74 }
75
76 void _notifyConnect() {
77 if (!_connected.isCompleted) {
78 Logger.root.info('NetworkVM connection opened: ${target.networkAddress}');
79 _connected.complete(this);
80 }
81 }
82 Future get onConnect => _connected.future;
83 void _notifyDisconnect() {
84 if (!_disconnected.isCompleted) {
85 Logger.root.info('NetworkVM connection error: ${target.networkAddress}');
86 _disconnected.complete(this);
87 }
88 }
89 Future get onDisconnect => _disconnected.future;
90
91 void disconnect() {
92 if (_webSocket != null) {
93 _webSocket.close();
94 }
95 _cancelAllPendingRequests();
96 _notifyDisconnect();
97 }
98
99 Future<String> getString(String id) {
100 if (_webSocket == null) {
101 // Create a WebSocket.
102 _webSocket = new WebSocket(target.networkAddress);
103 _webSocket.onClose.listen(_onClose);
104 _webSocket.onError.listen(_onError);
105 _webSocket.onOpen.listen(_onOpen);
106 _webSocket.onMessage.listen(_onMessage);
107 }
108 return _makeRequest(id);
109 }
110
111 void _onClose(CloseEvent event) {
112 _cancelAllPendingRequests();
113 _notifyDisconnect();
114 }
115
116 // WebSocket error event handler.
117 void _onError(Event) {
118 _cancelAllPendingRequests();
119 _notifyDisconnect();
120 }
121
122 // WebSocket open event handler.
123 void _onOpen(Event) {
124 target.lastConnectionTime = new DateTime.now().millisecondsSinceEpoch;
125 _notifyConnect();
126 _sendAllPendingRequests();
turnidge 2014/07/01 00:27:47 I'm wondering if we should move the call to _sendA
turnidge 2014/07/01 00:30:02 This comment got garbled during editing -- it shou
Cutch 2014/07/01 17:14:51 Good catch. I've gone ahead and split pendingReque
127 }
128
129 // WebSocket message event handler.
130 void _onMessage(MessageEvent event) {
131 var map = JSON.decode(event.data);
132 if (map == null) {
133 Logger.root.severe('NetworkVM got empty message');
134 return;
135 }
136 // Extract serial and response.
137 var serial;
138 var response;
139 if (target.chrome) {
140 if (map['method'] != 'Dart.observatoryData') {
141 // ignore devtools protocol spam.
142 return;
143 }
144 serial = map['params']['id'].toString();
145 response = map['params']['data'];
27 } else { 146 } else {
28 // Otherwise, assume we are running from the Dart Editor and 147 serial = map['seq'];
29 // want to connect on the default port. 148 response = map['response'];
turnidge 2014/07/01 00:27:47 Should we consider changing our naming to be consi
Cutch 2014/07/01 17:14:51 The ['params']['id'] and ['params']['data'] are di
30 host = 'http://127.0.0.1:8181/'; 149 }
31 } 150 // Complete request.
32 } 151 var request = _pendingRequests.remove(serial);
33 152 if (request == null) {
34 Future<String> getString(String id) { 153 Logger.root.severe('Received unexpected message: ${map}');
35 // Ensure we don't request host//id. 154 return;
36 if (host.endsWith('/') && id.startsWith('/')) { 155 }
37 id = id.substring(1); 156 request.completer.complete(response);
38 } 157 }
39 Logger.root.info('Fetching $id from $host'); 158
40 return HttpRequest.request(host + id, 159 String _generateNetworkError(String userMessage) {
41 requestHeaders: { 160 return JSON.encode({
42 'Observatory-Version': '1.0' 161 'type': 'ServiceException',
43 }).then((HttpRequest request) { 162 'id': '',
44 return request.responseText; 163 'kind': 'NetworkException',
45 }).catchError((error) { 164 'message': userMessage
46 // If we get an error here, the network request has failed.
47 Logger.root.severe('HttpRequest.request failed.');
48 var request = error.target;
49 return JSON.encode({
50 'type': 'ServiceException',
51 'id': '',
52 'response': request.responseText,
53 'kind': 'NetworkException',
54 'message': 'Could not connect to service (${request.statusText}). '
55 'Check that you started the VM with the following flags: '
56 '--observe'
57 });
58 }); 165 });
59 } 166 }
167
168 /// Cancel all pending requests by completing them with an error.
169 void _cancelAllPendingRequests() {
170 if (_pendingRequests.length == 0) {
171 return;
172 }
173 Logger.root.info('Cancelling all pending requests.');
174 _pendingRequests.forEach((String serial, _WebSocketRequest request) {
175 request.completer.complete(
176 _generateNetworkError('WebSocket disconnected'));
177 });
178 _pendingRequests.clear();
179 }
180
181 /// Send all pending requests.
182 void _sendAllPendingRequests() {
183 assert(_webSocket != null);
184 if (_pendingRequests.length == 0) {
185 return;
186 }
187 Logger.root.info('Sending all pending requests.');
188 _pendingRequests.forEach(_sendRequest);
189 }
190
191 /// Send the request over WebSocket.
192 void _sendRequest(String serial, _WebSocketRequest request) {
193 assert (_webSocket.readyState == WebSocket.OPEN);
194 if (!request.id.endsWith('/profile/tag')) {
195 Logger.root.info('GET ${request.id} from ${target.networkAddress}');
196 }
197 var message;
198 // Encode message.
199 if (target.chrome) {
200 message = JSON.encode({
201 'id': int.parse(serial),
202 'method': 'Dart.observatoryQuery',
203 'params': {
204 'id': serial,
205 'query': request.id
206 }
207 });
208 } else {
209 message = JSON.encode({'seq': serial, 'request': request.id});
210 }
211 // Send message.
212 _webSocket.send(message);
213 }
214
215 /// Add a request for [id] to pending requests.
216 Future<String> _makeRequest(String id) {
turnidge 2014/07/01 00:27:47 I would move this function closer to getString --
Cutch 2014/07/01 17:14:51 Done.
217 assert(_webSocket != null);
218 // Create request.
219 String serial = (_requestSerial++).toString();
220 var request = new _WebSocketRequest(id);
221 _pendingRequests[serial] = request;
222 if (_webSocket.readyState == WebSocket.OPEN) {
223 // Already connected, send request immediately.
224 _sendRequest(serial, request);
225 }
226 return request.completer.future;
227 }
60 } 228 }
61 229
62 class WebSocketVM extends VM { 230 // A VM that communicates with the service via posting messages from DevTools.
63 final Map<int, Completer> _pendingRequests = 231 class PostMessageVM extends VM {
64 new Map<int, Completer>(); 232 final Completer _connected = new Completer();
65 int _requestSerial = 0; 233 final Completer _disconnected = new Completer();
66 234 void disconnect() { /* nope */ }
67 String _host; 235 Future get onConnect => _connected.future;
68 Future<WebSocket> _socketFuture; 236 Future get onDisconnect => _disconnected.future;
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 {
78 // Otherwise, assume we are running from the Dart Editor and
79 // want to connect on the default port.
80 _host = 'ws://127.0.0.1:8181/ws';
81 }
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 event) {
99 var map = JSON.decode(event.data);
100 int seq = map['seq'];
101 var response = map['response'];
102 var completer = _pendingRequests.remove(seq);
103 if (completer == null) {
104 Logger.root.severe('Received unexpected message: ${map}');
105 } else {
106 completer.complete(response);
107 }
108 }
109
110 Future<String> getString(String id) {
111 if (_socketFuture == null) {
112 var errorResponse = JSON.encode({
113 'type': 'ServiceException',
114 'id': '',
115 'response': '',
116 'kind': 'NetworkException',
117 'message': 'Could not connect to service. Check that you started t he'
118 ' VM with the following flags:\n --enable-vm-service'
119 ' --pause-isolates-on-exit'
120 });
121 return new Future.value(errorResponse);
122 }
123 return _socketFuture.then((socket) {
124 int seq = _requestSerial++;
125 if (!id.endsWith('/profile/tag')) {
126 Logger.root.info('Fetching $id from $_host');
127 }
128 var completer = new Completer<String>();
129 _pendingRequests[seq] = completer;
130 var message = JSON.encode({'seq': seq, 'request': id});
131 socket.send(message);
132 return completer.future;
133 });
134 }
135 }
136
137 class DartiumVM extends VM {
138 final Map<String, Completer> _pendingRequests = 237 final Map<String, Completer> _pendingRequests =
139 new Map<String, Completer>(); 238 new Map<String, Completer>();
140 int _requestSerial = 0; 239 int _requestSerial = 0;
141 240
142 DartiumVM() : super() { 241 PostMessageVM() : super() {
143 window.onMessage.listen(_messageHandler); 242 window.onMessage.listen(_messageHandler);
144 Logger.root.info('Connected to DartiumVM'); 243 _connected.complete(this);
145 } 244 }
146 245
147 void _messageHandler(msg) { 246 void _messageHandler(msg) {
148 var id = msg.data['id']; 247 var id = msg.data['id'];
149 var name = msg.data['name']; 248 var name = msg.data['name'];
150 var data = msg.data['data']; 249 var data = msg.data['data'];
151 if (name != 'observatoryData') { 250 if (name != 'observatoryData') {
152 return; 251 return;
153 } 252 }
154 var completer = _pendingRequests[id]; 253 var completer = _pendingRequests[id];
155 assert(completer != null); 254 assert(completer != null);
156 _pendingRequests.remove(id); 255 _pendingRequests.remove(id);
157 completer.complete(data); 256 completer.complete(data);
158 } 257 }
159 258
160 Future<String> getString(String path) { 259 Future<String> getString(String path) {
161 var idString = '$_requestSerial'; 260 var idString = '$_requestSerial';
162 Map message = {}; 261 Map message = {};
163 message['id'] = idString; 262 message['id'] = idString;
164 message['method'] = 'observatoryQuery'; 263 message['method'] = 'observatoryQuery';
165 message['query'] = '$path'; 264 message['query'] = '$path';
166 _requestSerial++; 265 _requestSerial++;
167 var completer = new Completer(); 266 var completer = new Completer();
168 _pendingRequests[idString] = completer; 267 _pendingRequests[idString] = completer;
169 window.parent.postMessage(JSON.encode(message), '*'); 268 window.parent.postMessage(JSON.encode(message), '*');
170 return completer.future; 269 return completer.future;
171 } 270 }
172 } 271 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698