OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
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. | |
4 | |
5 library pub.command.serve; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:io'; | |
9 | |
10 import 'package:barback/barback.dart'; | |
11 import 'package:path/path.dart' as path; | |
12 | |
13 import '../command.dart'; | |
14 import '../entrypoint.dart'; | |
15 import '../exit_codes.dart' as exit_codes; | |
16 import '../log.dart' as log; | |
17 import '../pub_package_provider.dart'; | |
18 import '../utils.dart'; | |
19 | |
20 final _green = getPlatformString('\u001b[32m'); | |
21 final _red = getPlatformString('\u001b[31m'); | |
22 final _none = getPlatformString('\u001b[0m'); | |
23 | |
24 /// Handles the `serve` pub command. | |
25 class ServeCommand extends PubCommand { | |
26 String get description => "Run a local web development server."; | |
27 String get usage => 'pub serve'; | |
28 | |
29 ServeCommand() { | |
30 commandParser.addOption('port', defaultsTo: '8080', | |
31 help: 'The port to listen on.'); | |
32 } | |
33 | |
34 Future onRun() { | |
35 // The completer for the top-level future returned by the command. Only | |
36 // used to keep pub running (by not completing) and to pipe fatal errors | |
37 // to pub's top-level error-handling machinery. | |
38 var completer = new Completer(); | |
39 | |
40 return PubPackageProvider.create(entrypoint).then((provider) { | |
41 var port; | |
42 try { | |
43 port = int.parse(commandOptions['port']); | |
44 } on FormatException catch(_) { | |
45 log.error('Could not parse port "${commandOptions['port']}"'); | |
46 this.printUsage(); | |
47 exit(exit_codes.USAGE); | |
48 } | |
49 | |
50 var barback = new Barback(provider); | |
51 | |
52 barback.results.listen((result) { | |
53 if (result.succeeded) { | |
54 // TODO(rnystrom): Report using growl/inotify-send where available. | |
55 log.message("Build completed ${_green}successfully$_none"); | |
56 } else { | |
57 log.message("Build completed with " | |
58 "${_red}${result.errors.length}$_none errors."); | |
59 } | |
60 }); | |
61 | |
62 barback.errors.listen((error) { | |
63 log.error("${_red}Build error:\n$error$_none"); | |
64 }); | |
65 | |
66 // TODO(rnystrom): Watch file system and update sources again when they | |
67 // are added or modified. | |
68 | |
69 HttpServer.bind("localhost", port).then((server) { | |
70 log.message("Serving ${entrypoint.root.name} " | |
71 "on http://localhost:${server.port}"); | |
72 | |
73 // Add all of the visible files. | |
74 for (var package in provider.packages) { | |
75 barback.updateSources(provider.listAssets(package)); | |
76 } | |
77 | |
78 server.listen((request) { | |
79 var id = getIdFromUri(request.uri); | |
80 if (id == null) { | |
81 return notFound(request, "Path ${request.uri.path} is not valid."); | |
82 } | |
83 | |
84 barback.getAssetById(id).then((asset) { | |
85 log.message( | |
86 "$_green${request.method}$_none ${request.uri} -> $asset"); | |
87 // TODO(rnystrom): Set content-type based on asset type. | |
88 return request.response.addStream(asset.read()).then((_) { | |
89 request.response.close(); | |
90 }); | |
nweiz
2013/07/29 23:54:44
If we're not doing the 500 thing for errors coming
Bob Nystrom
2013/07/30 01:04:27
Done.
| |
91 }).catchError((error) { | |
92 log.error("$_red${request.method}$_none ${request.uri} -> $error"); | |
93 if (error is! AssetNotFoundException) { | |
94 completer.completeError(error); | |
95 return; | |
96 } | |
97 | |
98 notFound(request, error); | |
99 }); | |
100 }); | |
101 }); | |
102 | |
103 return completer.future; | |
104 }); | |
105 } | |
106 | |
107 /// Responds to [request] with a 404 response and closes it. | |
108 void notFound(HttpRequest request, message) { | |
109 request.response.statusCode = 404; | |
110 request.response.reasonPhrase = "Not Found"; | |
111 request.response.write(message); | |
112 request.response.close(); | |
113 } | |
114 | |
115 AssetId getIdFromUri(Uri uri) { | |
116 var parts = path.url.split(uri.path); | |
117 | |
118 // Strip the leading "/" from the URL. | |
119 parts.removeAt(0); | |
120 | |
121 var isSpecial = false; | |
122 | |
123 // Checks to see if [uri]'s path contains a special directory [name] that | |
124 // identifies an asset within some package. If so, maps the package name | |
125 // and path following that to be within [dir] inside that package. | |
126 AssetId _trySpecialUrl(String name, String dir) { | |
127 // Find the package name and the relative path in the package. | |
128 var index = parts.indexOf(name); | |
129 if (index == -1) return null; | |
130 | |
131 // If we got here, the path *did* contain the special directory, which | |
132 // means we should not interpret it as a regular path, even if it's | |
133 // missing the package name after it, which makes it invalid here. | |
134 isSpecial = true; | |
135 if (index + 1 >= parts.length) return null; | |
136 | |
137 var package = parts[index + 1]; | |
138 var assetPath = path.url.join(dir, | |
139 path.url.joinAll(parts.skip(index + 2))); | |
140 return new AssetId(package, assetPath); | |
141 } | |
142 | |
143 // See if it's "packages" URL. | |
144 var id = _trySpecialUrl("packages", "lib"); | |
145 if (id != null) return id; | |
146 | |
147 // See if it's an "assets" URL. | |
148 id = _trySpecialUrl("assets", "asset"); | |
149 if (id != null) return id; | |
150 | |
151 // If we got here, we had a path like "/packages" which is a special | |
152 // directory, but not a valid path since it lacks a following package name. | |
153 if (isSpecial) return null; | |
154 | |
155 // Otherwise, it's a path in current package's web directory. | |
156 return new AssetId(entrypoint.root.name, | |
157 path.url.join("web", path.url.joinAll(parts))); | |
158 } | |
159 } | |
OLD | NEW |