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 |