| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 part of dart_controller_service_isolate; | |
| 6 | |
| 7 class WebSocketClient extends Client { | |
| 8 static const int PARSE_ERROR_CODE = 4000; | |
| 9 static const int BINARY_MESSAGE_ERROR_CODE = 4001; | |
| 10 static const int NOT_MAP_ERROR_CODE = 4002; | |
| 11 final WebSocket socket; | |
| 12 | |
| 13 WebSocketClient(this.socket, VMService service) : super(service) { | |
| 14 socket.listen((message) => onWebSocketMessage(message)); | |
| 15 socket.done.then((_) => close()); | |
| 16 } | |
| 17 | |
| 18 onWebSocketMessage(message) { | |
| 19 if (message is String) { | |
| 20 var map; | |
| 21 try { | |
| 22 map = JSON.decode(message); | |
| 23 } catch (e) { | |
| 24 socket.close(PARSE_ERROR_CODE, 'Message parse error: $e'); | |
| 25 return; | |
| 26 } | |
| 27 if (map is! Map) { | |
| 28 socket.close(NOT_MAP_ERROR_CODE, 'Message must be a JSON map.'); | |
| 29 return; | |
| 30 } | |
| 31 var serial = map['id']; | |
| 32 onMessage(serial, new Message.fromJsonRpc(this, map)); | |
| 33 } else { | |
| 34 socket.close(BINARY_MESSAGE_ERROR_CODE, 'Message must be a string.'); | |
| 35 } | |
| 36 } | |
| 37 | |
| 38 post(dynamic result) { | |
| 39 try { | |
| 40 socket.add(result); | |
| 41 } catch (_) { | |
| 42 print("Ignoring error posting over WebSocket."); | |
| 43 } | |
| 44 } | |
| 45 | |
| 46 dynamic toJson() { | |
| 47 Map map = super.toJson(); | |
| 48 map['type'] = 'WebSocketClient'; | |
| 49 map['socket'] = '$socket'; | |
| 50 return map; | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 | |
| 55 class HttpRequestClient extends Client { | |
| 56 static ContentType jsonContentType = | |
| 57 new ContentType("application", "json", charset: "utf-8"); | |
| 58 final HttpRequest request; | |
| 59 | |
| 60 HttpRequestClient(this.request, VMService service) : super(service); | |
| 61 | |
| 62 post(String result) { | |
| 63 request.response..headers.contentType = jsonContentType | |
| 64 ..write(result) | |
| 65 ..close(); | |
| 66 close(); | |
| 67 } | |
| 68 | |
| 69 dynamic toJson() { | |
| 70 Map map = super.toJson(); | |
| 71 map['type'] = 'HttpRequestClient'; | |
| 72 map['request'] = '$request'; | |
| 73 return map; | |
| 74 } | |
| 75 } | |
| 76 | |
| 77 class Server { | |
| 78 static const WEBSOCKET_PATH = '/ws'; | |
| 79 static const ROOT_REDIRECT_PATH = '/index.html'; | |
| 80 | |
| 81 final VMService _service; | |
| 82 final String _ip; | |
| 83 final int _port; | |
| 84 | |
| 85 HttpServer _server; | |
| 86 bool get running => _server != null; | |
| 87 bool _displayMessages = false; | |
| 88 | |
| 89 Server(this._service, this._ip, this._port) { | |
| 90 _displayMessages = (_ip != '127.0.0.1' || _port != 8181); | |
| 91 } | |
| 92 | |
| 93 _onServerShutdown() { | |
| 94 } | |
| 95 | |
| 96 _serverError(error, stackTrace) { | |
| 97 _onServerShutdown(); | |
| 98 } | |
| 99 | |
| 100 _serverDone() { | |
| 101 _onServerShutdown(); | |
| 102 } | |
| 103 | |
| 104 _requestHandler(HttpRequest request) { | |
| 105 // Allow cross origin requests with 'observatory' header. | |
| 106 request.response.headers.add('Access-Control-Allow-Origin', '*'); | |
| 107 request.response.headers.add('Access-Control-Allow-Headers', | |
| 108 'Observatory-Version'); | |
| 109 | |
| 110 if (request.method != 'GET') { | |
| 111 // Not a GET request. Do nothing. | |
| 112 request.response.close(); | |
| 113 return; | |
| 114 } | |
| 115 | |
| 116 final String path = | |
| 117 request.uri.path == '/' ? ROOT_REDIRECT_PATH : request.uri.path; | |
| 118 | |
| 119 if (path == WEBSOCKET_PATH) { | |
| 120 WebSocketTransformer.upgrade(request, | |
| 121 compression: CompressionOptions.OFF).then( | |
| 122 (WebSocket webSocket) { | |
| 123 new WebSocketClient(webSocket, _service); | |
| 124 }); | |
| 125 return; | |
| 126 } | |
| 127 | |
| 128 Asset asset = assets[path]; | |
| 129 if (asset != null) { | |
| 130 // Serving up a static resource (e.g. .css, .html, .png). | |
| 131 request.response.headers.contentType = | |
| 132 ContentType.parse(asset.mimeType); | |
| 133 request.response.add(asset.data); | |
| 134 request.response.close(); | |
| 135 return; | |
| 136 } | |
| 137 var client = new HttpRequestClient(request, _service); | |
| 138 var message = new Message.fromUri(client, request.uri); | |
| 139 client.onMessage(null, message); | |
| 140 } | |
| 141 | |
| 142 Future startup() { | |
| 143 if (_server != null) { | |
| 144 // Already running. | |
| 145 return new Future.value(this); | |
| 146 } | |
| 147 | |
| 148 // Startup HTTP server. | |
| 149 var address = new InternetAddress('127.0.0.1'); | |
| 150 return HttpServer.bind(address, _port).then((s) { | |
| 151 _server = s; | |
| 152 _server.listen(_requestHandler, | |
| 153 onError: _serverError, | |
| 154 onDone: _serverDone, | |
| 155 cancelOnError: true); | |
| 156 var ip = _server.address.address.toString(); | |
| 157 if (_displayMessages) { | |
| 158 var port = _server.port.toString(); | |
| 159 print('Observatory listening on http://$ip:$port'); | |
| 160 } | |
| 161 // Server is up and running. | |
| 162 _notifyServerState(ip, _server.port); | |
| 163 return this; | |
| 164 }).catchError((e, st) { | |
| 165 print('Could not start Observatory HTTP server:\n$e\n$st\n'); | |
| 166 _notifyServerState("", 0); | |
| 167 return this; | |
| 168 }); | |
| 169 } | |
| 170 | |
| 171 close(bool force) { | |
| 172 if (_server == null) { | |
| 173 return new Future.value(null); | |
| 174 } | |
| 175 return _server.close(force: force); | |
| 176 } | |
| 177 | |
| 178 Future shutdown(bool forced) { | |
| 179 if (_server == null) { | |
| 180 // Not started. | |
| 181 return new Future.value(this); | |
| 182 } | |
| 183 | |
| 184 // Force displaying of status messages if we are forcibly shutdown. | |
| 185 _displayMessages = _displayMessages || forced; | |
| 186 | |
| 187 // Shutdown HTTP server and subscription. | |
| 188 var ip = _server.address.address.toString(); | |
| 189 var port = _server.port.toString(); | |
| 190 return close(forced).then((_) { | |
| 191 if (_displayMessages) { | |
| 192 print('Observatory no longer listening on http://$ip:$port'); | |
| 193 } | |
| 194 _server = null; | |
| 195 _notifyServerState("", 0); | |
| 196 return this; | |
| 197 }).catchError((e, st) { | |
| 198 _server = null; | |
| 199 print('Could not shutdown Observatory HTTP server:\n$e\n$st\n'); | |
| 200 _notifyServerState("", 0); | |
| 201 return this; | |
| 202 }); | |
| 203 } | |
| 204 | |
| 205 } | |
| 206 | |
| 207 _notifyServerState(String ip, int port) | |
| 208 native "ServiceIsolate_NotifyServerState"; | |
| OLD | NEW |