Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
VmServiceBase.java
Go to the documentation of this file.
1/*
2 * Copyright (c) 2015, the Dart project authors.
3 *
4 * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package org.dartlang.vm.service;
15
16import com.google.common.collect.Maps;
17import com.google.gson.JsonElement;
18import com.google.gson.JsonObject;
19import com.google.gson.JsonParser;
20import de.roderick.weberknecht.WebSocket;
21import de.roderick.weberknecht.WebSocketEventHandler;
22import de.roderick.weberknecht.WebSocketException;
23import de.roderick.weberknecht.WebSocketMessage;
24import org.dartlang.vm.service.consumer.*;
25import org.dartlang.vm.service.element.*;
26import org.dartlang.vm.service.internal.RequestSink;
27import org.dartlang.vm.service.internal.VmServiceConst;
28import org.dartlang.vm.service.internal.WebSocketRequestSink;
29import org.dartlang.vm.service.logging.Logging;
30
31import java.io.IOException;
32import java.net.URI;
33import java.net.URISyntaxException;
34import java.util.ArrayList;
35import java.util.List;
36import java.util.Map;
37import java.util.concurrent.CountDownLatch;
38import java.util.concurrent.TimeUnit;
39import java.util.concurrent.atomic.AtomicInteger;
40
41/**
42 * Internal {@link VmService} base class containing non-generated code.
43 */
44@SuppressWarnings({"unused", "WeakerAccess"})
45abstract class VmServiceBase implements VmServiceConst {
46 /**
47 * Connect to the VM observatory service via the specified URI
48 *
49 * @return an API object for interacting with the VM service (not {@code null}).
50 */
51 public static VmService connect(final String url) throws IOException {
52 // Validate URL
53 URI uri;
54 try {
55 uri = new URI(url);
56 } catch (URISyntaxException e) {
57 throw new IOException("Invalid URL: " + url, e);
58 }
59 String wsScheme = uri.getScheme();
60 if (!"ws".equals(wsScheme) && !"wss".equals(wsScheme)) {
61 throw new IOException("Unsupported URL scheme: " + wsScheme);
62 }
63
64 // Create web socket and observatory
65 WebSocket webSocket;
66 try {
67 webSocket = new WebSocket(uri);
68 } catch (WebSocketException e) {
69 throw new IOException("Failed to create websocket: " + url, e);
70 }
71 final VmService vmService = new VmService();
72
73 // Setup event handler for forwarding responses
74 webSocket.setEventHandler(new WebSocketEventHandler() {
75 @Override
76 public void onClose() {
77 Logging.getLogger().logInformation("VM connection closed: " + url);
78
79 vmService.connectionClosed();
80 }
81
82 @Override
83 public void onMessage(WebSocketMessage message) {
84 Logging.getLogger().logInformation("VM message: " + message.getText());
85 try {
86 vmService.processMessage(message.getText());
87 } catch (Exception e) {
88 Logging.getLogger().logError(e.getMessage(), e);
89 }
90 }
91
92 @Override
93 public void onOpen() {
94 vmService.connectionOpened();
95
96 Logging.getLogger().logInformation("VM connection open: " + url);
97 }
98
99 @Override
100 public void onPing() {
101 }
102
103 @Override
104 public void onPong() {
105 }
106 });
107
108 // Establish WebSocket Connection
109 //noinspection TryWithIdenticalCatches
110 try {
111 webSocket.connect();
112 } catch (WebSocketException e) {
113 throw new IOException("Failed to connect: " + url, e);
114 } catch (ArrayIndexOutOfBoundsException e) {
115 // The weberknecht can occasionally throw an array index exception if a connect terminates on initial connect
116 // (de.roderick.weberknecht.WebSocket.connect, WebSocket.java:126).
117 throw new IOException("Failed to connect: " + url, e);
118 }
119 vmService.requestSink = new WebSocketRequestSink(webSocket);
120
121 // Check protocol version
122 final CountDownLatch latch = new CountDownLatch(1);
123 final String[] errMsg = new String[1];
124 vmService.getVersion(new VersionConsumer() {
125 @Override
126 public void onError(RPCError error) {
127 String msg = "Failed to determine protocol version: " + error.getCode() + "\n message: "
128 + error.getMessage() + "\n details: " + error.getDetails();
129 Logging.getLogger().logInformation(msg);
130 errMsg[0] = msg;
131 }
132
133 @Override
134 public void received(Version version) {
135 vmService.runtimeVersion = version;
136
137 latch.countDown();
138 }
139 });
140
141 try {
142 if (!latch.await(5, TimeUnit.SECONDS)) {
143 throw new IOException("Failed to determine protocol version");
144 }
145 if (errMsg[0] != null) {
146 throw new IOException(errMsg[0]);
147 }
148 } catch (InterruptedException e) {
149 throw new RuntimeException("Interrupted while waiting for response", e);
150 }
151
152 return vmService;
153 }
154
155 /**
156 * Connect to the VM observatory service on the given local port.
157 *
158 * @return an API object for interacting with the VM service (not {@code null}).
159 *
160 * @deprecated prefer the Url based constructor {@link VmServiceBase#connect}
161 */
162 @Deprecated
163 public static VmService localConnect(int port) throws IOException {
164 return connect("ws://localhost:" + port + "/ws");
165 }
166
167 /**
168 * A mapping between {@link String} ids' and the associated {@link Consumer} that was passed when
169 * the request was made. Synchronize against {@link #consumerMapLock} before accessing this field.
170 */
171 private final Map<String, Consumer> consumerMap = Maps.newHashMap();
172
173 /**
174 * The object used to synchronize access to {@link #consumerMap}.
175 */
176 private final Object consumerMapLock = new Object();
177
178 /**
179 * The unique ID for the next request.
180 */
181 private final AtomicInteger nextId = new AtomicInteger();
182
183 /**
184 * A list of objects to which {@link Event}s from the VM are forwarded.
185 */
186 private final List<VmServiceListener> vmListeners = new ArrayList<>();
187
188 /**
189 * A list of objects to which {@link Event}s from the VM are forwarded.
190 */
191 private final Map<String, RemoteServiceRunner> remoteServiceRunners = Maps.newHashMap();
192
193 /**
194 * The channel through which observatory requests are made.
195 */
196 RequestSink requestSink;
197
199
200 /**
201 * Add a listener to receive {@link Event}s from the VM.
202 */
204 vmListeners.add(listener);
205 }
206
207 /**
208 * Remove the given listener from the VM.
209 */
211 vmListeners.remove(listener);
212 }
213
214 /**
215 * Add a VM RemoteServiceRunner.
216 */
217 public void addServiceRunner(String service, RemoteServiceRunner runner) {
218 remoteServiceRunners.put(service, runner);
219 }
220
221 /**
222 * Remove a VM RemoteServiceRunner.
223 */
224 public void removeServiceRunner(String service) {
225 remoteServiceRunners.remove(service);
226 }
227
228 /**
229 * Return the VM service protocol version supported by the current debug connection.
230 */
232 return runtimeVersion;
233 }
234
235 /**
236 * Disconnect from the VM observatory service.
237 */
238 public void disconnect() {
239 requestSink.close();
240 }
241
242 /**
243 * Return the instance with the given identifier.
244 */
245 public void getInstance(String isolateId, String instanceId, final GetInstanceConsumer consumer) {
246 getObject(isolateId, instanceId, new GetObjectConsumer() {
247
248 @Override
249 public void onError(RPCError error) {
250 consumer.onError(error);
251 }
252
253 @Override
254 public void received(Obj response) {
255 if (response instanceof Instance) {
256 consumer.received((Instance) response);
257 } else {
258 onError(RPCError.unexpected("Instance", response));
259 }
260 }
261
262 @Override
263 public void received(Sentinel response) {
264 onError(RPCError.unexpected("Instance", response));
265 }
266 });
267 }
268
269 /**
270 * Return the library with the given identifier.
271 */
272 public void getLibrary(String isolateId, String libraryId, final GetLibraryConsumer consumer) {
273 getObject(isolateId, libraryId, new GetObjectConsumer() {
274
275 @Override
276 public void onError(RPCError error) {
277 consumer.onError(error);
278 }
279
280 @Override
281 public void received(Obj response) {
282 if (response instanceof Library) {
283 consumer.received((Library) response);
284 } else {
285 onError(RPCError.unexpected("Library", response));
286 }
287 }
288
289 @Override
290 public void received(Sentinel response) {
291 onError(RPCError.unexpected("Library", response));
292 }
293 });
294 }
295
296 public abstract void getObject(String isolateId, String objectId, GetObjectConsumer consumer);
297
298 /**
299 * Invoke a specific service protocol extension method.
300 * <p>
301 * See https://api.dart.dev/stable/dart-developer/dart-developer-library.html.
302 */
303 public void callServiceExtension(String isolateId, String method, ServiceExtensionConsumer consumer) {
304 JsonObject params = new JsonObject();
305 params.addProperty("isolateId", isolateId);
306 request(method, params, consumer);
307 }
308
309 /**
310 * Invoke a specific service protocol extension method.
311 * <p>
312 * See https://api.dart.dev/stable/dart-developer/dart-developer-library.html.
313 */
314 public void callServiceExtension(String isolateId, String method, JsonObject params, ServiceExtensionConsumer consumer) {
315 params.addProperty("isolateId", isolateId);
316 request(method, params, consumer);
317 }
318
319 /**
320 * Sends the request and associates the request with the passed {@link Consumer}.
321 */
322 protected void request(String method, JsonObject params, Consumer consumer) {
323
324 // Assemble the request
325 String id = Integer.toString(nextId.incrementAndGet());
326 JsonObject request = new JsonObject();
327
328 request.addProperty(JSONRPC, JSONRPC_VERSION);
329 request.addProperty(ID, id);
330 request.addProperty(METHOD, method);
331 request.add(PARAMS, params);
332
333 // Cache the consumer to receive the response
334 synchronized (consumerMapLock) {
335 consumerMap.put(id, consumer);
336 }
337
338 // Send the request
339 requestSink.add(request);
340 }
341
342 public void connectionOpened() {
343 for (VmServiceListener listener : new ArrayList<>(vmListeners)) {
344 try {
345 listener.connectionOpened();
346 } catch (Exception e) {
347 Logging.getLogger().logError("Exception notifying listener", e);
348 }
349 }
350 }
351
352 private void forwardEvent(String streamId, Event event) {
353 for (VmServiceListener listener : new ArrayList<>(vmListeners)) {
354 try {
355 listener.received(streamId, event);
356 } catch (Exception e) {
357 Logging.getLogger().logError("Exception processing event: " + streamId + ", " + event.getJson(), e);
358 }
359 }
360 }
361
362 public void connectionClosed() {
363 for (VmServiceListener listener : new ArrayList<>(vmListeners)) {
364 try {
365 listener.connectionClosed();
366 } catch (Exception e) {
367 Logging.getLogger().logError("Exception notifying listener", e);
368 }
369 }
370 }
371
372 abstract void forwardResponse(Consumer consumer, String type, JsonObject json);
373
374 void logUnknownResponse(Consumer consumer, JsonObject json) {
375 Class<? extends Consumer> consumerClass = consumer.getClass();
376 StringBuilder msg = new StringBuilder();
377 msg.append("Expected response for ").append(consumerClass).append("\n");
378 for (Class<?> interf : consumerClass.getInterfaces()) {
379 msg.append(" implementing ").append(interf).append("\n");
380 }
381 msg.append(" but received ").append(json);
382 Logging.getLogger().logError(msg.toString());
383 }
384
385 /**
386 * Process the response from the VM service and forward that response to the consumer associated
387 * with the response id.
388 */
389 void processMessage(String jsonText) {
390 if (jsonText == null || jsonText.isEmpty()) {
391 return;
392 }
393
394 // Decode the JSON
395 JsonObject json;
396 try {
397 json = (JsonObject) new JsonParser().parse(jsonText);
398 } catch (Exception e) {
399 Logging.getLogger().logError("Parse message failed: " + jsonText, e);
400 return;
401 }
402
403 if (json.has("method")) {
404 if (!json.has(PARAMS)) {
405 final String message = "Missing " + PARAMS;
406 Logging.getLogger().logError(message);
407 final JsonObject response = new JsonObject();
408 response.addProperty(JSONRPC, JSONRPC_VERSION);
409 final JsonObject error = new JsonObject();
410 error.addProperty(CODE, INVALID_REQUEST);
411 error.addProperty(MESSAGE, message);
412 response.add(ERROR, error);
413 requestSink.add(response);
414 return;
415 }
416 if (json.has("id")) {
417 processRequest(json);
418 } else {
419 processNotification(json);
420 }
421 } else if (json.has("result") || json.has("error")) {
422 processResponse(json);
423 } else {
424 Logging.getLogger().logError("Malformed message");
425 }
426 }
427
428 void processRequest(JsonObject json) {
429 final JsonObject response = new JsonObject();
430 response.addProperty(JSONRPC, JSONRPC_VERSION);
431
432 // Get the consumer associated with this request
433 String id;
434 try {
435 id = json.get(ID).getAsString();
436 } catch (Exception e) {
437 final String message = "Request malformed " + ID;
438 Logging.getLogger().logError(message, e);
439 final JsonObject error = new JsonObject();
440 error.addProperty(CODE, INVALID_REQUEST);
441 error.addProperty(MESSAGE, message);
442 response.add(ERROR, error);
443 requestSink.add(response);
444 return;
445 }
446
447 response.addProperty(ID, id);
448
449 String method;
450 try {
451 method = json.get(METHOD).getAsString();
452 } catch (Exception e) {
453 final String message = "Request malformed " + METHOD;
454 Logging.getLogger().logError(message, e);
455 final JsonObject error = new JsonObject();
456 error.addProperty(CODE, INVALID_REQUEST);
457 error.addProperty(MESSAGE, message);
458 response.add(ERROR, error);
459 requestSink.add(response);
460 return;
461 }
462
463 JsonObject params;
464 try {
465 params = json.get(PARAMS).getAsJsonObject();
466 } catch (Exception e) {
467 final String message = "Request malformed " + METHOD;
468 Logging.getLogger().logError(message, e);
469 final JsonObject error = new JsonObject();
470 error.addProperty(CODE, INVALID_REQUEST);
471 error.addProperty(MESSAGE, message);
472 response.add(ERROR, error);
473 requestSink.add(response);
474 return;
475 }
476
477 if (!remoteServiceRunners.containsKey(method)) {
478 final String message = "Unknown service " + method;
479 Logging.getLogger().logError(message);
480 final JsonObject error = new JsonObject();
481 error.addProperty(CODE, METHOD_NOT_FOUND);
482 error.addProperty(MESSAGE, message);
483 response.add(ERROR, error);
484 requestSink.add(response);
485 return;
486 }
487
488 final RemoteServiceRunner runner = remoteServiceRunners.get(method);
489 try {
490 runner.run(params, new RemoteServiceCompleter() {
491 public void result(JsonObject result) {
492 response.add(RESULT, result);
493 requestSink.add(response);
494 }
495
496 public void error(int code, String message, JsonObject data) {
497 final JsonObject error = new JsonObject();
498 error.addProperty(CODE, code);
499 error.addProperty(MESSAGE, message);
500 if (data != null) {
501 error.add(DATA, data);
502 }
503 response.add(ERROR, error);
504 requestSink.add(response);
505 }
506 });
507 } catch (Exception e) {
508 final String message = "Internal Server Error";
509 Logging.getLogger().logError(message, e);
510 final JsonObject error = new JsonObject();
511 error.addProperty(CODE, SERVER_ERROR);
512 error.addProperty(MESSAGE, message);
513 response.add(ERROR, error);
514 requestSink.add(response);
515 }
516 }
517
518 private static final RemoteServiceCompleter ignoreCallback =
520 public void result(JsonObject result) {
521 // ignore
522 }
523
524 public void error(int code, String message, JsonObject data) {
525 // ignore
526 }
527 };
528
529 void processNotification(JsonObject json) {
530 String method;
531 try {
532 method = json.get(METHOD).getAsString();
533 } catch (Exception e) {
534 Logging.getLogger().logError("Request malformed " + METHOD, e);
535 return;
536 }
537 JsonObject params;
538 try {
539 params = json.get(PARAMS).getAsJsonObject();
540 } catch (Exception e) {
541 Logging.getLogger().logError("Event missing " + PARAMS, e);
542 return;
543 }
544 if ("streamNotify".equals(method)) {
545 String streamId;
546 try {
547 streamId = params.get(STREAM_ID).getAsString();
548 } catch (Exception e) {
549 Logging.getLogger().logError("Event missing " + STREAM_ID, e);
550 return;
551 }
552 Event event;
553 try {
554 event = new Event(params.get(EVENT).getAsJsonObject());
555 } catch (Exception e) {
556 Logging.getLogger().logError("Event missing " + EVENT, e);
557 return;
558 }
559 forwardEvent(streamId, event);
560 } else {
561 if (!remoteServiceRunners.containsKey(method)) {
562 Logging.getLogger().logError("Unknown service " + method);
563 return;
564 }
565
566 final RemoteServiceRunner runner = remoteServiceRunners.get(method);
567 try {
568 runner.run(params, ignoreCallback);
569 } catch (Exception e) {
570 Logging.getLogger().logError("Internal Server Error", e);
571 }
572 }
573 }
574
575 protected String removeNewLines(String str) {
576 return str.replaceAll("\r\n", " ").replaceAll("\n", " ");
577 }
578
579 void processResponse(JsonObject json) {
580 JsonElement idElem = json.get(ID);
581 if (idElem == null) {
582 Logging.getLogger().logError("Response missing " + ID);
583 return;
584 }
585
586 // Get the consumer associated with this response
587 String id;
588 try {
589 id = idElem.getAsString();
590 } catch (Exception e) {
591 Logging.getLogger().logError("Response missing " + ID, e);
592 return;
593 }
594 Consumer consumer = consumerMap.remove(id);
595 if (consumer == null) {
596 Logging.getLogger().logError("No consumer associated with " + ID + ": " + id);
597 return;
598 }
599
600 // Forward the response if the request was successfully executed
601 JsonElement resultElem = json.get(RESULT);
602 if (resultElem != null) {
603 JsonObject result;
604 try {
605 result = resultElem.getAsJsonObject();
606 } catch (Exception e) {
607 Logging.getLogger().logError("Response has invalid " + RESULT, e);
608 return;
609 }
610 String responseType = "";
611 if (result.has(TYPE)) {
612 responseType = result.get(TYPE).getAsString();
613 }
614 // ServiceExtensionConsumers do not care about the response type.
615 else if (!(consumer instanceof ServiceExtensionConsumer)) {
616 Logging.getLogger().logError("Response missing " + TYPE + ": " + result.toString());
617 return;
618 }
619 forwardResponse(consumer, responseType, result);
620 return;
621 }
622
623 // Forward an error if the request failed
624 resultElem = json.get(ERROR);
625 if (resultElem != null) {
626 JsonObject error;
627 try {
628 error = resultElem.getAsJsonObject();
629 } catch (Exception e) {
630 Logging.getLogger().logError("Response has invalid " + RESULT, e);
631 return;
632 }
633 consumer.onError(new RPCError(error));
634 return;
635 }
636
637 Logging.getLogger().logError("Response missing " + RESULT + " and " + ERROR);
638 }
639}
Version
static bool equals(T *a, T *b)
#define RESULT(Op)
#define TYPE(t)
void add(sk_sp< SkIDChangeListener > listener) SK_EXCLUDES(fMutex)
void addServiceRunner(String service, RemoteServiceRunner runner)
void getLibrary(String isolateId, String libraryId, final GetLibraryConsumer consumer)
static VmService connect(final String url)
void logUnknownResponse(Consumer consumer, JsonObject json)
void request(String method, JsonObject params, Consumer consumer)
void callServiceExtension(String isolateId, String method, JsonObject params, ServiceExtensionConsumer consumer)
void addVmServiceListener(VmServiceListener listener)
abstract void getObject(String isolateId, String objectId, GetObjectConsumer consumer)
void getInstance(String isolateId, String instanceId, final GetInstanceConsumer consumer)
static VmService localConnect(int port)
void callServiceExtension(String isolateId, String method, ServiceExtensionConsumer consumer)
abstract void forwardResponse(Consumer consumer, String type, JsonObject json)
void removeVmServiceListener(VmServiceListener listener)
const EmbeddedViewParams * params
FlKeyEvent * event
const uint8_t uint32_t uint32_t GError ** error
GAsyncResult * result
void run(JsonObject params, RemoteServiceCompleter completer)
Win32Message message
const CatchEntryMove de[]
const uintptr_t id
#define ERROR(message)