• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2019 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {fetchWithTimeout} from '../base/http_utils';
16import {assertExists} from '../base/logging';
17import {StatusResult} from '../protos';
18import {EngineBase, LoadingTracker} from '../trace_processor/engine';
19
20const RPC_CONNECT_TIMEOUT_MS = 2000;
21
22export interface HttpRpcState {
23  connected: boolean;
24  status?: StatusResult;
25  failure?: string;
26}
27
28export class HttpRpcEngine extends EngineBase {
29  readonly id: string;
30  errorHandler: (err: string) => void = () => {};
31  private requestQueue = new Array<Uint8Array>();
32  private websocket?: WebSocket;
33  private connected = false;
34
35  // Can be changed by frontend/index.ts when passing ?rpc_port=1234 .
36  static rpcPort = '9001';
37
38  constructor(id: string, loadingTracker?: LoadingTracker) {
39    super(loadingTracker);
40    this.id = id;
41  }
42
43  rpcSendRequestBytes(data: Uint8Array): void {
44    if (this.websocket === undefined) {
45      const wsUrl = `ws://${HttpRpcEngine.hostAndPort}/websocket`;
46      this.websocket = new WebSocket(wsUrl);
47      this.websocket.onopen = () => this.onWebsocketConnected();
48      this.websocket.onmessage = (e) => this.onWebsocketMessage(e);
49      this.websocket.onclose = (e) =>
50        this.errorHandler(`Websocket closed (${e.code}: ${e.reason})`);
51      this.websocket.onerror = (e) =>
52        this.errorHandler(`WebSocket error: ${e}`);
53    }
54
55    if (this.connected) {
56      this.websocket.send(data);
57    } else {
58      this.requestQueue.push(data); // onWebsocketConnected() will flush this.
59    }
60  }
61
62  private onWebsocketConnected() {
63    for (;;) {
64      const queuedMsg = this.requestQueue.shift();
65      if (queuedMsg === undefined) break;
66      assertExists(this.websocket).send(queuedMsg);
67    }
68    this.connected = true;
69  }
70
71  private onWebsocketMessage(e: MessageEvent) {
72    assertExists(e.data as Blob)
73      .arrayBuffer()
74      .then((buf) => {
75        super.onRpcResponseBytes(new Uint8Array(buf));
76      });
77  }
78
79  static async checkConnection(): Promise<HttpRpcState> {
80    const RPC_URL = `http://${HttpRpcEngine.hostAndPort}/`;
81    const httpRpcState: HttpRpcState = {connected: false};
82    console.info(
83      `It's safe to ignore the ERR_CONNECTION_REFUSED on ${RPC_URL} below. ` +
84        `That might happen while probing the external native accelerator. The ` +
85        `error is non-fatal and unlikely to be the culprit for any UI bug.`,
86    );
87    try {
88      const resp = await fetchWithTimeout(
89        RPC_URL + 'status',
90        {method: 'post', cache: 'no-cache'},
91        RPC_CONNECT_TIMEOUT_MS,
92      );
93      if (resp.status !== 200) {
94        httpRpcState.failure = `${resp.status} - ${resp.statusText}`;
95      } else {
96        const buf = new Uint8Array(await resp.arrayBuffer());
97        httpRpcState.connected = true;
98        httpRpcState.status = StatusResult.decode(buf);
99      }
100    } catch (err) {
101      httpRpcState.failure = `${err}`;
102    }
103    return httpRpcState;
104  }
105
106  static get hostAndPort() {
107    return `127.0.0.1:${HttpRpcEngine.rpcPort}`;
108  }
109}
110