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 return PubPackageProvider.create(entrypoint).then((provider) { | |
36 var port; | |
37 try { | |
38 port = int.parse(commandOptions['port']); | |
39 } on FormatException catch(_) { | |
40 log.error('Could not parse port "${commandOptions['port']}"'); | |
41 this.printUsage(); | |
42 exit(exit_codes.USAGE); | |
43 } | |
44 | |
45 var barback = new Barback(provider); | |
46 | |
47 barback.results.listen((result) { | |
48 if (result.succeeded) { | |
49 log.message("Build completed ${_green}successfully$_none"); | |
50 } else { | |
51 log.message("Build completed with " | |
52 "${_red}${result.errors.length}$_none errors."); | |
53 } | |
54 }); | |
55 | |
56 barback.errors.listen((error) { | |
57 log.error("${_red}Build error:\n$error$_none"); | |
58 }); | |
59 | |
60 // Add all of the visible files. | |
61 for (var package in provider.packages) { | |
62 barback.updateSources(provider.listAssets(package)); | |
63 } | |
64 // TODO(rnystrom): Watch file system and update sources again when they | |
65 // are added or modified. | |
66 | |
67 HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, port).then((server) { | |
68 log.message("Serving ${entrypoint.root.name} " | |
69 "on http://localhost:${server.port}"); | |
70 | |
71 server.listen((request) { | |
72 var id = getIdFromUri(request.uri); | |
73 barback.getAssetById(id).then((asset) { | |
74 log.message( | |
75 "$_green${request.method}$_none ${request.uri} -> $asset"); | |
76 // TODO(rnystrom): Set content-type based on asset type. | |
77 return request.response.addStream(asset.read()).then((_) { | |
78 request.response.close(); | |
79 }); | |
80 }).catchError((error) { | |
81 log.error("$_red${request.method}$_none ${request.uri} -> $error"); | |
82 if (error is AssetNotFoundException) { | |
83 request.response.statusCode = 404; | |
84 request.response.reasonPhrase = "Not Found"; | |
85 request.response.write(error); | |
86 } else { | |
87 request.response.statusCode = 500; | |
88 request.response.reasonPhrase = "Internal Server Error"; | |
89 request.response.writeln(error); | |
90 request.response.writeln(getAttachedStackTrace(error)); | |
91 } | |
nweiz
2013/07/29 20:23:15
Error responses should have the content-type set t
| |
92 request.response.close(); | |
93 }); | |
94 }); | |
95 }); | |
96 | |
97 // TODO(rnystrom): Hack! Return a future that never completes to leave | |
98 // pub running. | |
99 return new Completer().future; | |
100 }); | |
101 } | |
102 | |
103 AssetId getIdFromUri(Uri uri) { | |
104 var uriBuilder = new path.Builder(style: path.Style.url); | |
nweiz
2013/07/29 20:23:15
I think the pattern we usually use for this is a t
Bob Nystrom
2013/07/29 21:45:43
Added default builders for each style to path and
| |
105 var parts = uriBuilder.split(uri.path); | |
106 | |
107 // Strip the leading "/" from the URL. | |
108 parts.removeAt(0); | |
109 | |
110 // Special case: if the URL is "/", map it to "web/index.html". | |
111 // TODO(rnystrom): What about directory URLs? Should "foo" or "foo/" map | |
112 // to "foo/index.html"? | |
113 if (parts.isEmpty) parts = ["index.html"]; | |
114 | |
115 // Checks to see if [uri]'s path contains a special directory [name] that | |
116 // identifies an asset within some package. If so, maps the package name | |
117 // and path following that to be within [dir] inside that package. | |
118 AssetId _trySpecialUrl(String name, String dir) { | |
119 // Find the package name and the relative path in the package. | |
120 var index = parts.indexOf(name); | |
121 if (index == -1) return null; | |
122 if (index + 1 >= parts.length) return null; | |
123 | |
124 var package = parts[index + 1]; | |
125 var assetPath = uriBuilder.join(dir, | |
126 uriBuilder.joinAll(parts.skip(index + 2))); | |
127 return new AssetId(package, assetPath); | |
128 } | |
nweiz
2013/07/29 20:23:15
Currently this won't 404 for "/packages" if there'
Bob Nystrom
2013/07/29 21:45:43
Made it do the right thing.
| |
129 | |
130 // See if it's "packages" URL. | |
131 var id = _trySpecialUrl("packages", "lib"); | |
132 if (id != null) return id; | |
133 | |
134 // See if it's an "assets" URL. | |
135 id = _trySpecialUrl("assets", "asset"); | |
136 if (id != null) return id; | |
137 | |
138 // Otherwise, it's a path in current package's web directory. | |
139 return new AssetId(entrypoint.root.name, | |
140 uriBuilder.join("web", uriBuilder.joinAll(parts))); | |
141 } | |
142 } | |
OLD | NEW |