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 '../common/protos'; 18 19import {Engine, LoadingTracker} from './engine'; 20 21export const RPC_URL = 'http://127.0.0.1:9001/'; 22export const WS_URL = 'ws://127.0.0.1:9001/websocket'; 23 24const RPC_CONNECT_TIMEOUT_MS = 2000; 25 26export interface HttpRpcState { 27 connected: boolean; 28 status?: StatusResult; 29 failure?: string; 30} 31 32export class HttpRpcEngine extends Engine { 33 readonly id: string; 34 errorHandler: (err: string) => void = () => {}; 35 private requestQueue = new Array<Uint8Array>(); 36 private websocket?: WebSocket; 37 private connected = false; 38 39 constructor(id: string, loadingTracker?: LoadingTracker) { 40 super(loadingTracker); 41 this.id = id; 42 } 43 44 rpcSendRequestBytes(data: Uint8Array): void { 45 if (this.websocket === undefined) { 46 this.websocket = new WebSocket(WS_URL); 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).arrayBuffer().then(buf => { 73 super.onRpcResponseBytes(new Uint8Array(buf)); 74 }); 75 } 76 77 static async checkConnection(): Promise<HttpRpcState> { 78 const httpRpcState: HttpRpcState = {connected: false}; 79 console.info( 80 `It's safe to ignore the ERR_CONNECTION_REFUSED on ${RPC_URL} below. ` + 81 `That might happen while probing the external native accelerator. The ` + 82 `error is non-fatal and unlikely to be the culprit for any UI bug.`); 83 try { 84 const resp = await fetchWithTimeout( 85 RPC_URL + 'status', 86 {method: 'post', cache: 'no-cache'}, 87 RPC_CONNECT_TIMEOUT_MS); 88 if (resp.status !== 200) { 89 httpRpcState.failure = `${resp.status} - ${resp.statusText}`; 90 } else { 91 const buf = new Uint8Array(await resp.arrayBuffer()); 92 httpRpcState.connected = true; 93 httpRpcState.status = StatusResult.decode(buf); 94 } 95 } catch (err) { 96 httpRpcState.failure = `${err}`; 97 } 98 return httpRpcState; 99 } 100} 101