Chromium Code Reviews| 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 |