Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(54)

Side by Side Diff: dart/pkg/dart2js_incremental/lib/server.dart

Issue 845643004: Remove unneeded stuff from server (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge
Patch Set: Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library trydart.projectServer; 5 library dart2js_incremental.server;
6 6
7 import 'dart:io'; 7 import 'dart:io';
8 8
9 import 'dart:async' show 9 import 'dart:async' show
10 Future, 10 Future,
11 Stream; 11 Stream;
12 12
13 import 'dart:convert' show 13 import 'dart:convert' show
14 HtmlEscape, 14 HtmlEscape,
15 JSON, 15 JSON,
16 UTF8; 16 UTF8;
17 17
18 class WatchHandler {
19 final WebSocket socket;
20
21 final Set<String> watchedFiles;
22
23 static final Set<WatchHandler> handlers = new Set<WatchHandler>();
24
25 static const Map<int, String> fsEventNames = const <int, String>{
26 FileSystemEvent.CREATE: 'create',
27 FileSystemEvent.DELETE: 'delete',
28 FileSystemEvent.MODIFY: 'modify',
29 FileSystemEvent.MOVE: 'move',
30 };
31
32 WatchHandler(this.socket, Iterable<String> watchedFiles)
33 : this.watchedFiles = watchedFiles.toSet();
34
35 handleFileSystemEvent(FileSystemEvent event) {
36 if (event.isDirectory) return;
37 String type = fsEventNames[event.type];
38 if (type == null) type = 'unknown';
39 String path = new Uri.file(event.path).pathSegments.last;
40 shouldIgnore(type, path).then((bool ignored) {
41 if (ignored) return;
42 socket.add(JSON.encode({type: [path]}));
43 });
44 }
45
46 Future<bool> shouldIgnore(String type, String path) {
47 switch (type) {
48 case 'create':
49 return new Future<bool>.value(!watchedFiles.contains(path));
50 case 'delete':
51 return Conversation.listProjectFiles().then((List<String> files) {
52 watchedFiles
53 ..retainAll(files)
54 ..addAll(files);
55 return watchedFiles.contains(path);
56 });
57 case 'modify':
58 return new Future<bool>.value(false);
59 default:
60 print('Unhandled fs-event for $path ($type).');
61 return new Future<bool>.value(true);
62 }
63 }
64
65 onData(_) {
66 // TODO(ahe): Move POST code here?
67 }
68
69 onDone() {
70 handlers.remove(this);
71 }
72
73 static handleWebSocket(WebSocket socket) {
74 Conversation.ensureProjectWatcher();
75 Conversation.listProjectFiles().then((List<String> files) {
76 socket.add(JSON.encode({'create': files}));
77 WatchHandler handler = new WatchHandler(socket, files);
78 handlers.add(handler);
79 socket.listen(
80 handler.onData, cancelOnError: true, onDone: handler.onDone);
81 });
82 }
83
84 static onFileSystemEvent(FileSystemEvent event) {
85 for (WatchHandler handler in handlers) {
86 handler.handleFileSystemEvent(event);
87 }
88 }
89 }
90
91 /// Represents a "project" command. These commands are accessed from the URL
92 /// "/project?name".
93 class ProjectCommand {
94 final String name;
95
96 /// For each query parameter, this map describes rules for validating them.
97 final Map<String, String> rules;
98
99 final Function handle;
100
101 const ProjectCommand(this.name, this.rules, this.handle);
102 }
103
104 class Conversation { 18 class Conversation {
105 HttpRequest request; 19 HttpRequest request;
106 HttpResponse response; 20 HttpResponse response;
107 21
108 static const String PROJECT_PATH = '/project';
109
110 static const String PACKAGES_PATH = '/packages'; 22 static const String PACKAGES_PATH = '/packages';
111 23
112 static const String CONTENT_TYPE = HttpHeaders.CONTENT_TYPE; 24 static const String CONTENT_TYPE = HttpHeaders.CONTENT_TYPE;
113 25
114 static const String GIT_TAG = 'try_dart_backup';
115
116 static const String COMMIT_MESSAGE = """
117 Automated backup.
118
119 It is safe to delete tag '$GIT_TAG' if you don't need the backup.""";
120
121 static Uri documentRoot = Uri.base; 26 static Uri documentRoot = Uri.base;
122 27
123 static Uri projectRoot = Uri.base.resolve('site/try/src/'); 28 static Uri packageRoot = Uri.base.resolve('packages/');
124
125 static Uri packageRoot = Uri.base.resolve('sdk/lib/_internal/');
126
127 static const List<ProjectCommand> COMMANDS = const <ProjectCommand>[
128 const ProjectCommand('list', const {'list': null}, handleProjectList),
129 ];
130
131 static Stream<FileSystemEvent> projectChanges;
132
133 static final Map<String, String> gitEnv = computeGitEnv();
134 29
135 Conversation(this.request, this.response); 30 Conversation(this.request, this.response);
136 31
137 onClosed(_) { 32 onClosed(_) {
138 if (response.statusCode == HttpStatus.OK) return; 33 if (response.statusCode == HttpStatus.OK) return;
139 print('Request for ${request.uri} ${response.statusCode}'); 34 print('Request for ${request.uri} ${response.statusCode}');
140 } 35 }
141 36
142 notFound(path) { 37 notFound(path) {
143 response.statusCode = HttpStatus.NOT_FOUND; 38 response.statusCode = HttpStatus.NOT_FOUND;
(...skipping 11 matching lines...) Expand all
155 50
156 internalError(error, stack) { 51 internalError(error, stack) {
157 print(error); 52 print(error);
158 if (stack != null) print(stack); 53 if (stack != null) print(stack);
159 response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR; 54 response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
160 response.write(htmlInfo("Internal Server Error", 55 response.write(htmlInfo("Internal Server Error",
161 "Internal Server Error: $error\n$stack")); 56 "Internal Server Error: $error\n$stack"));
162 response.close(); 57 response.close();
163 } 58 }
164 59
165 bool validate(Map<String, String> parameters, Map<String, String> rules) {
166 Iterable<String> problems = rules.keys
167 .where((name) => !parameters.containsKey(name))
168 .map((name) => "Missing parameter: '$name'.");
169 if (!problems.isEmpty) {
170 badRequest(problems.first);
171 return false;
172 }
173 Set extra = new Set.from(parameters.keys)..removeAll(rules.keys);
174 if (extra.isEmpty) return true;
175 String extraString = (extra.toList()..sort()).join("', '");
176 badRequest("Extra parameters: '$extraString'.");
177 return false;
178 }
179
180 static Future<List<String>> listProjectFiles() {
181 String nativeDir = projectRoot.toFilePath();
182 Directory dir = new Directory(nativeDir);
183 var future = dir.list(recursive: true, followLinks: false).toList();
184 return future.then((List<FileSystemEntity> entries) {
185 return entries
186 .map((e) => e.path)
187 .where((p) => p.endsWith('.dart') && p.startsWith(nativeDir))
188 .map((p) => p.substring(nativeDir.length))
189 .map((p) => new Uri.file(p).path).toList();
190 });
191 }
192
193 static handleProjectList(Conversation self) {
194 listProjectFiles().then((List<String> files) {
195 self.response
196 ..write(JSON.encode(files))
197 ..close();
198 });
199 }
200
201 handleProjectRequest() {
202 Map<String, String> parameters = request.uri.queryParameters;
203 for (ProjectCommand command in COMMANDS) {
204 if (parameters.containsKey(command.name)) {
205 if (validate(parameters, command.rules)) {
206 (command.handle)(this);
207 }
208 return;
209 }
210 }
211 String commands = COMMANDS.map((c) => c.name).join("', '");
212 badRequest("Valid commands are: '$commands'");
213 }
214
215 handleSocket() { 60 handleSocket() {
216 if (request.uri.path == '/ws/watch') { 61 if (false && request.uri.path == '/ws/watch') {
217 WebSocketTransformer.upgrade(request).then(WatchHandler.handleWebSocket); 62 WebSocketTransformer.upgrade(request).then((WebSocket socket) {
63 socket.add(JSON.encode({'create': []}));
64 // WatchHandler handler = new WatchHandler(socket, files);
65 // handlers.add(handler);
66 // socket.listen(
67 // handler.onData, cancelOnError: true, onDone: handler.onDone);
68 });
218 } else { 69 } else {
219 response.done 70 response.done
220 .then(onClosed) 71 .then(onClosed)
221 .catchError(onError); 72 .catchError(onError);
222 notFound(request.uri.path); 73 notFound(request.uri.path);
223 } 74 }
224 } 75 }
225 76
226 handle() { 77 handle() {
227 response.done 78 response.done
228 .then(onClosed) 79 .then(onClosed)
229 .catchError(onError); 80 .catchError(onError);
230 81
231 Uri uri = request.uri; 82 Uri uri = request.uri;
232 if (uri.path == PROJECT_PATH) {
233 return handleProjectRequest();
234 }
235 if (uri.path.endsWith('/')) { 83 if (uri.path.endsWith('/')) {
236 uri = uri.resolve('index.html'); 84 uri = uri.resolve('index.html');
237 } 85 }
238 if (uri.path == '/css/fonts/fontawesome-webfont.woff') {
239 uri = uri.resolve('/fontawesome-webfont.woff');
240 }
241 if (uri.path.contains('..') || uri.path.contains('%')) { 86 if (uri.path.contains('..') || uri.path.contains('%')) {
242 return notFound(uri.path); 87 return notFound(uri.path);
243 } 88 }
244 String path = uri.path; 89 String path = uri.path;
245 Uri root = documentRoot; 90 Uri root = documentRoot;
246 String dartType = 'application/dart'; 91 String dartType = 'application/dart';
247 if (path.startsWith('/project/packages/')) { 92 if (path.startsWith('${PACKAGES_PATH}/')) {
248 root = packageRoot;
249 path = path.substring('/project/packages'.length);
250 } else if (path.startsWith('${PROJECT_PATH}/')) {
251 root = projectRoot;
252 path = path.substring(PROJECT_PATH.length);
253 dartType = 'text/plain';
254 } else if (path.startsWith('${PACKAGES_PATH}/')) {
255 root = packageRoot; 93 root = packageRoot;
256 path = path.substring(PACKAGES_PATH.length); 94 path = path.substring(PACKAGES_PATH.length);
257 } 95 }
258 96
259 String filePath = root.resolve('.$path').toFilePath(); 97 String filePath = root.resolve('.$path').toFilePath();
260 switch (request.method) { 98 switch (request.method) {
261 case 'GET': 99 case 'GET':
262 return handleGet(filePath, dartType); 100 return handleGet(filePath, dartType);
263 case 'POST':
264 return handlePost(filePath);
265 default: 101 default:
266 String method = const HtmlEscape().convert(request.method); 102 String method = const HtmlEscape().convert(request.method);
267 return badRequest("Unsupported method: '$method'"); 103 return badRequest("Unsupported method: '$method'");
268 } 104 }
269 } 105 }
270 106
271 void handleGet(String path, String dartType) { 107 void handleGet(String path, String dartType) {
272 var f = new File(path); 108 var f = new File(path);
273 f.exists().then((bool exists) { 109 f.exists().then((bool exists) {
274 if (!exists) return notFound(request.uri); 110 if (!exists) return notFound(request.uri);
275 if (path.endsWith('.html')) { 111 if (path.endsWith('.html')) {
276 response.headers.set(CONTENT_TYPE, 'text/html'); 112 response.headers.set(CONTENT_TYPE, 'text/html');
277 } else if (path.endsWith('.dart')) { 113 } else if (path.endsWith('.dart')) {
278 response.headers.set(CONTENT_TYPE, dartType); 114 response.headers.set(CONTENT_TYPE, dartType);
279 } else if (path.endsWith('.js')) { 115 } else if (path.endsWith('.js')) {
280 response.headers.set(CONTENT_TYPE, 'application/javascript'); 116 response.headers.set(CONTENT_TYPE, 'application/javascript');
281 } else if (path.endsWith('.ico')) { 117 } else if (path.endsWith('.ico')) {
282 response.headers.set(CONTENT_TYPE, 'image/x-icon'); 118 response.headers.set(CONTENT_TYPE, 'image/x-icon');
283 } else if (path.endsWith('.appcache')) { 119 } else if (path.endsWith('.appcache')) {
284 response.headers.set(CONTENT_TYPE, 'text/cache-manifest'); 120 response.headers.set(CONTENT_TYPE, 'text/cache-manifest');
285 } 121 }
286 f.openRead().pipe(response).catchError(onError); 122 f.openRead().pipe(response).catchError(onError);
287 }); 123 });
288 } 124 }
289 125
290 handlePost(String path) {
291 // The data is sent using a dart:html HttpRequest (aka XMLHttpRequest).
292 // According to http://xhr.spec.whatwg.org/, strings are always encoded as
293 // UTF-8.
294 request.transform(UTF8.decoder).join().then((String data) {
295 // The rest of this method is synchronous. This guarantees that we don't
296 // make conflicting git changes in response to multiple POST requests.
297 try {
298 backup(path);
299 } catch (e, stack) {
300 return internalError(e, stack);
301 }
302
303 new File(path).writeAsStringSync(data);
304
305 response
306 ..statusCode = HttpStatus.OK
307 ..close();
308 });
309 }
310
311 // Back up the file [path] using git.
312 static void backup(String path) {
313 // Reset the index.
314 git('read-tree', ['HEAD']);
315
316 // Save modifications in index.
317 git('update-index', ['--add', path]);
318
319 // If the file isn't modified, don't back it up.
320 if (checkGit('diff', ['--cached', '--quiet'])) return;
321
322 String localModifications = git('write-tree');
323
324 String tag = 'refs/tags/$GIT_TAG';
325 var arguments = ['-m', COMMIT_MESSAGE, localModifications];
326
327 if (checkGit('rev-parse', ['-q', '--verify', tag])) {
328 // The tag already exists.
329
330 if (checkGit('diff-tree', ['--quiet', localModifications, tag])) {
331 // localModifications are identical to the last backup.
332 return;
333 }
334
335 // Use the tag as a parent.
336 arguments = ['-p', tag]..addAll(arguments);
337
338 String headCommit = git('rev-parse', ['HEAD']);
339 String mergeBase = git('merge-base', [tag, 'HEAD']);
340 if (headCommit != mergeBase) {
341 arguments = ['-p', 'HEAD']..addAll(arguments);
342 }
343 } else {
344 arguments = ['-p', 'HEAD']..addAll(arguments);
345 }
346
347 // Commit the local modifcations.
348 String commit = git('commit-tree', arguments);
349
350 // Create or update the tag.
351 git('tag', ['-f', GIT_TAG, commit]);
352 }
353
354 static String git(String command,
355 [List<String> arguments = const <String> []]) {
356 ProcessResult result =
357 run('git', <String>[command]..addAll(arguments), gitEnv);
358 if (result.exitCode != 0) {
359 throw 'git error: ${result.stdout}\n${result.stderr}';
360 }
361 return result.stdout.trim();
362 }
363
364 static bool checkGit(String command,
365 [List<String> arguments = const <String> []]) {
366 return
367 run('git', <String>[command]..addAll(arguments), gitEnv).exitCode == 0;
368 }
369
370 static Map<String, String> computeGitEnv() {
371 ProcessResult result = run('git', ['rev-parse', '--git-dir'], null);
372 if (result.exitCode != 0) {
373 throw 'git error: ${result.stdout}\n${result.stderr}';
374 }
375 String gitDir = result.stdout.trim();
376 return <String, String>{ 'GIT_INDEX_FILE': '$gitDir/try_dart_backup' };
377 }
378
379 static ProcessResult run(String executable,
380 List<String> arguments,
381 Map<String, String> environment) {
382 // print('Running $executable ${arguments.join(" ")}');
383 return Process.runSync(executable, arguments, environment: environment);
384 }
385
386 static onRequest(HttpRequest request) { 126 static onRequest(HttpRequest request) {
387 Conversation conversation = new Conversation(request, request.response); 127 Conversation conversation = new Conversation(request, request.response);
388 if (WebSocketTransformer.isUpgradeRequest(request)) { 128 if (WebSocketTransformer.isUpgradeRequest(request)) {
389 conversation.handleSocket(); 129 conversation.handleSocket();
390 } else { 130 } else {
391 conversation.handle(); 131 conversation.handle();
392 } 132 }
393 } 133 }
394 134
395 static ensureProjectWatcher() {
396 if (projectChanges != null) return;
397 String nativeDir = projectRoot.toFilePath();
398 Directory dir = new Directory(nativeDir);
399 projectChanges = dir.watch();
400 projectChanges.listen(WatchHandler.onFileSystemEvent);
401 }
402
403 static onError(error) { 135 static onError(error) {
404 if (error is HttpException) { 136 if (error is HttpException) {
405 print('Error: ${error.message}'); 137 print('Error: ${error.message}');
406 } else { 138 } else {
407 print('Error: ${error}'); 139 print('Error: ${error}');
408 } 140 }
409 } 141 }
410 142
411 String htmlInfo(String title, String text) { 143 String htmlInfo(String title, String text) {
412 // No script injection, please. 144 // No script injection, please.
(...skipping 20 matching lines...) Expand all
433 } 165 }
434 var host = '127.0.0.1'; 166 var host = '127.0.0.1';
435 if (arguments.length > 1) { 167 if (arguments.length > 1) {
436 host = arguments[1]; 168 host = arguments[1];
437 } 169 }
438 int port = 0; 170 int port = 0;
439 if (arguments.length > 2) { 171 if (arguments.length > 2) {
440 port = int.parse(arguments[2]); 172 port = int.parse(arguments[2]);
441 } 173 }
442 if (arguments.length > 3) { 174 if (arguments.length > 3) {
443 Conversation.projectRoot = Uri.base.resolve(arguments[3]);
444 }
445 if (arguments.length > 4) {
446 Conversation.packageRoot = Uri.base.resolve(arguments[4]); 175 Conversation.packageRoot = Uri.base.resolve(arguments[4]);
447 } 176 }
448 HttpServer.bind(host, port).then((HttpServer server) { 177 HttpServer.bind(host, port).then((HttpServer server) {
449 print('HTTP server started on http://$host:${server.port}/'); 178 print('HTTP server started on http://$host:${server.port}/');
450 server.listen(Conversation.onRequest, onError: Conversation.onError); 179 server.listen(Conversation.onRequest, onError: Conversation.onError);
451 }).catchError((e) { 180 }).catchError((e) {
452 print("HttpServer.bind error: $e"); 181 print("HttpServer.bind error: $e");
453 exit(1); 182 exit(1);
454 }); 183 });
455 } 184 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698