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.barback.server; | |
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 '../entrypoint.dart'; | |
14 import '../utils.dart'; | |
15 | |
16 /// A server that serves assets transformed by barback. | |
17 class BarbackServer { | |
18 /// The underlying HTTP server. | |
19 final HttpServer _server; | |
20 | |
21 /// The name of the root package, from whose `web` directory root assets will | |
22 /// be served. | |
23 final String _rootPackage; | |
24 | |
25 /// The barback instance from which this serves assets. | |
26 final Barback barback; | |
27 | |
28 /// The server's port. | |
29 final int port; | |
30 | |
31 /// The results of requests handled by the server. | |
32 /// | |
33 /// These can be used to provide visual feedback for the server's processing. | |
34 /// This stream is also used to emit any programmatic errors that occur in the | |
35 /// server. | |
36 Stream<BarbackServerResult> get results => _resultsController.stream; | |
37 final _resultsController = | |
38 new StreamController<BarbackServerResult>.broadcast(); | |
39 | |
40 /// Creates a new server and binds it to [port] of [host]. | |
41 /// | |
42 /// This server will serve assets from [barback], and use [entrypoint] as the | |
Bob Nystrom
2013/08/27 22:12:30
"entrypoint" -> "rootPackage".
nweiz
2013/08/28 20:45:23
Done.
| |
43 /// root package. | |
44 static Future<BarbackServer> bind(String host, int port, Barback barback, | |
45 String rootPackage) { | |
46 return HttpServer.bind(host, port) | |
47 .then((server) => new BarbackServer._(server, barback, rootPackage)); | |
48 } | |
49 | |
50 BarbackServer._(HttpServer server, this.barback, this._rootPackage) | |
51 : _server = server, | |
52 port = server.port { | |
53 _server.listen(_handleRequest, onError: (error) { | |
54 _resultsController.addError(error); | |
55 close(); | |
56 }); | |
57 } | |
58 | |
59 /// Closes this server. | |
60 Future close() { | |
61 _server.close(); | |
62 _resultsController.close(); | |
63 } | |
64 | |
65 /// Handles an HTTP request. | |
66 void _handleRequest(HttpRequest request) { | |
67 if (request.method != "GET" && request.method != "HEAD") { | |
68 _methodNotAllowed(request); | |
Bob Nystrom
2013/08/27 22:12:30
Should add a test for this.
nweiz
2013/08/28 20:45:23
Done.
| |
69 return; | |
70 } | |
71 | |
72 var id = _getIdFromUri(request.uri); | |
73 if (id == null) { | |
74 _notFound(request, "Path ${request.uri.path} is not valid."); | |
75 return; | |
76 } | |
77 | |
78 barback.getAssetById(id).then((asset) { | |
79 return validateStream(asset.read()).then((stream) { | |
80 _resultsController.add( | |
81 new BarbackServerResult._success(request.uri, id)); | |
82 // TODO(rnystrom): Set content-type based on asset type. | |
83 return request.response.addStream(stream).then((_) { | |
84 request.response.close(); | |
85 }); | |
86 }).catchError((error) { | |
87 _resultsController.add( | |
88 new BarbackServerResult._failure(request.uri, id, error)); | |
89 | |
90 // If we couldn't read the asset, handle the error gracefully. | |
91 if (error is FileException) { | |
92 // Assume this means the asset was a file-backed source asset | |
93 // and we couldn't read it, so treat it like a missing asset. | |
94 _notFound(request, error); | |
95 return; | |
96 } | |
97 | |
98 // Otherwise, it's some internal error. | |
99 request.response.statusCode = 500; | |
100 request.response.reasonPhrase = "Internal Error"; | |
101 request.response.write(error); | |
102 request.response.close(); | |
103 }); | |
104 }).catchError((error) { | |
105 if (error is! AssetNotFoundException) { | |
106 _resultsController.addError(error); | |
107 close(); | |
108 return; | |
109 } | |
110 | |
111 _resultsController.add( | |
112 new BarbackServerResult._failure(request.uri, id, error)); | |
113 _notFound(request, error); | |
114 }); | |
115 } | |
116 | |
117 /// Responds to [request] with a 405 response and closes it. | |
118 void _methodNotAllowed(HttpRequest request) { | |
119 request.response.statusCode = 405; | |
120 request.response.reasonPhrase = "Method Not Allowed"; | |
121 request.response.headers['Allow'] = 'GET, HEAD'; | |
122 request.response.write( | |
123 "The ${request.method} method is not allowed for ${request.uri}."); | |
124 request.response.close(); | |
125 } | |
126 | |
127 /// Responds to [request] with a 404 response and closes it. | |
128 void _notFound(HttpRequest request, message) { | |
129 request.response.statusCode = 404; | |
130 request.response.reasonPhrase = "Not Found"; | |
131 request.response.write(message); | |
132 request.response.close(); | |
133 } | |
134 | |
135 /// Converts a request [uri] into an [AssetId] that can be requested from | |
136 /// barback. | |
137 AssetId _getIdFromUri(Uri uri) { | |
138 var parts = path.url.split(uri.path); | |
139 | |
140 // Strip the leading "/" from the URL. | |
141 parts.removeAt(0); | |
142 | |
143 var isSpecial = false; | |
144 | |
145 // Checks to see if [uri]'s path contains a special directory [name] that | |
146 // identifies an asset within some package. If so, maps the package name | |
147 // and path following that to be within [dir] inside that package. | |
148 AssetId _trySpecialUrl(String name, String dir) { | |
149 // Find the package name and the relative path in the package. | |
150 var index = parts.indexOf(name); | |
151 if (index == -1) return null; | |
152 | |
153 // If we got here, the path *did* contain the special directory, which | |
154 // means we should not interpret it as a regular path, even if it's | |
155 // missing the package name after it, which makes it invalid here. | |
156 isSpecial = true; | |
157 if (index + 1 >= parts.length) return null; | |
158 | |
159 var package = parts[index + 1]; | |
160 var assetPath = path.url.join(dir, | |
161 path.url.joinAll(parts.skip(index + 2))); | |
162 return new AssetId(package, assetPath); | |
163 } | |
164 | |
165 // See if it's "packages" URL. | |
166 var id = _trySpecialUrl("packages", "lib"); | |
167 if (id != null) return id; | |
168 | |
169 // See if it's an "assets" URL. | |
170 id = _trySpecialUrl("assets", "asset"); | |
171 if (id != null) return id; | |
172 | |
173 // If we got here, we had a path like "/packages" which is a special | |
174 // directory, but not a valid path since it lacks a following package name. | |
175 if (isSpecial) return null; | |
176 | |
177 // Otherwise, it's a path in current package's web directory. | |
178 return new AssetId(_rootPackage, | |
179 path.url.join("web", path.url.joinAll(parts))); | |
180 } | |
181 } | |
182 | |
183 /// The result of the server handling a URL. | |
184 /// | |
185 /// Only requests for which an asset was requested from barback will emit a | |
186 /// result. Malformed requests will be handled internally. | |
187 class BarbackServerResult { | |
188 /// The requested url. | |
189 final Url url; | |
190 | |
191 /// The id that [url] identifies. | |
192 final AssetId id; | |
193 | |
194 /// The error thrown by barback. | |
195 /// | |
196 /// If the request was served successfully, this will be null. | |
197 final error; | |
198 | |
199 /// Whether the request was served successfully. | |
200 bool get isSuccess => error == null; | |
201 | |
202 /// Whether the request was served unsuccessfully. | |
203 bool get isFailure => !isSuccess; | |
204 | |
205 BarbackServerResult._success(this.url, this.id) | |
206 : error = null; | |
207 | |
208 BarbackServerResult._failure(this.url, this.id, this.error); | |
209 } | |
OLD | NEW |