Index: sdk/lib/_internal/pub/lib/src/barback/server.dart |
diff --git a/sdk/lib/_internal/pub/lib/src/barback/server.dart b/sdk/lib/_internal/pub/lib/src/barback/server.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3db42e582085c2b3d0f7ac528576a9fd93cafbc5 |
--- /dev/null |
+++ b/sdk/lib/_internal/pub/lib/src/barback/server.dart |
@@ -0,0 +1,227 @@ |
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library pub.barback.server; |
+ |
+import 'dart:async'; |
+import 'dart:io'; |
+ |
+import 'package:barback/barback.dart'; |
+import 'package:path/path.dart' as path; |
+import 'package:stack_trace/stack_trace.dart'; |
+ |
+import '../log.dart' as log; |
+import '../utils.dart'; |
+ |
+/// A server that serves assets transformed by barback. |
+class BarbackServer { |
+ /// The underlying HTTP server. |
+ final HttpServer _server; |
+ |
+ /// The name of the root package, from whose `web` directory root assets will |
+ /// be served. |
+ final String _rootPackage; |
+ |
+ /// The barback instance from which this serves assets. |
+ final Barback barback; |
+ |
+ /// The server's port. |
+ final int port; |
+ |
+ /// The results of requests handled by the server. |
+ /// |
+ /// These can be used to provide visual feedback for the server's processing. |
+ /// This stream is also used to emit any programmatic errors that occur in the |
+ /// server. |
+ Stream<BarbackServerResult> get results => _resultsController.stream; |
+ final _resultsController = |
+ new StreamController<BarbackServerResult>.broadcast(); |
+ |
+ /// Creates a new server and binds it to [port] of [host]. |
+ /// |
+ /// This server will serve assets from [barback], and use [rootPackage] as the |
+ /// root package. |
+ static Future<BarbackServer> bind(String host, int port, Barback barback, |
+ String rootPackage) { |
+ return HttpServer.bind(host, port) |
+ .then((server) => new BarbackServer._(server, barback, rootPackage)); |
+ } |
+ |
+ BarbackServer._(HttpServer server, this.barback, this._rootPackage) |
+ : _server = server, |
+ port = server.port { |
+ _server.listen(_handleRequest, onError: (error) { |
+ _resultsController.addError(error); |
+ close(); |
+ }); |
+ } |
+ |
+ /// Closes this server. |
+ Future close() { |
+ _server.close(); |
+ _resultsController.close(); |
+ } |
+ |
+ /// Handles an HTTP request. |
+ void _handleRequest(HttpRequest request) { |
+ if (request.method != "GET" && request.method != "HEAD") { |
+ _methodNotAllowed(request); |
+ return; |
+ } |
+ |
+ var id = _getIdFromUri(request.uri); |
+ if (id == null) { |
+ _notFound(request, "Path ${request.uri.path} is not valid."); |
+ return; |
+ } |
+ |
+ _logRequest(request, "Loading $id"); |
+ barback.getAssetById(id).then((asset) { |
+ return validateStream(asset.read()).then((stream) { |
+ _resultsController.add( |
+ new BarbackServerResult._success(request.uri, id)); |
+ // TODO(rnystrom): Set content-type based on asset type. |
+ return request.response.addStream(stream).then((_) { |
+ // Log successful requests both so we can provide debugging |
+ // information and so scheduled_test knows we haven't timed out while |
+ // loading transformers. |
+ _logRequest(request, "Served $id"); |
+ request.response.close(); |
+ }); |
+ }).catchError((error) { |
+ _resultsController.add( |
+ new BarbackServerResult._failure(request.uri, id, error)); |
+ |
+ // If we couldn't read the asset, handle the error gracefully. |
+ if (error is FileException) { |
+ // Assume this means the asset was a file-backed source asset |
+ // and we couldn't read it, so treat it like a missing asset. |
+ _notFound(request, error); |
+ return; |
+ } |
+ |
+ var trace = new Trace.from(getAttachedStackTrace(error)); |
+ _logRequest(request, "$error\n$trace"); |
+ |
+ // Otherwise, it's some internal error. |
+ request.response.statusCode = 500; |
+ request.response.reasonPhrase = "Internal Error"; |
+ request.response.write(error); |
+ request.response.close(); |
+ }); |
+ }).catchError((error) { |
+ if (error is! AssetNotFoundException) { |
+ var trace = new Trace.from(getAttachedStackTrace(error)); |
+ _logRequest(request, "$error\n$trace"); |
+ |
+ _resultsController.addError(error); |
+ close(); |
+ return; |
+ } |
+ |
+ _resultsController.add( |
+ new BarbackServerResult._failure(request.uri, id, error)); |
+ _notFound(request, error); |
+ }); |
+ } |
+ |
+ /// Responds to [request] with a 405 response and closes it. |
+ void _methodNotAllowed(HttpRequest request) { |
+ _logRequest(request, "405 Method Not Allowed"); |
+ request.response.statusCode = 405; |
+ request.response.reasonPhrase = "Method Not Allowed"; |
+ request.response.headers.add('Allow', 'GET, HEAD'); |
+ request.response.write( |
+ "The ${request.method} method is not allowed for ${request.uri}."); |
+ request.response.close(); |
+ } |
+ |
+ /// Responds to [request] with a 404 response and closes it. |
+ void _notFound(HttpRequest request, message) { |
+ _logRequest(request, "404 Not Found"); |
+ request.response.statusCode = 404; |
+ request.response.reasonPhrase = "Not Found"; |
+ request.response.write(message); |
+ request.response.close(); |
+ } |
+ |
+ /// Converts a request [uri] into an [AssetId] that can be requested from |
+ /// barback. |
+ AssetId _getIdFromUri(Uri uri) { |
+ var parts = path.url.split(uri.path); |
+ |
+ // Strip the leading "/" from the URL. |
+ parts.removeAt(0); |
+ |
+ var isSpecial = false; |
+ |
+ // Checks to see if [uri]'s path contains a special directory [name] that |
+ // identifies an asset within some package. If so, maps the package name |
+ // and path following that to be within [dir] inside that package. |
+ AssetId _trySpecialUrl(String name, String dir) { |
+ // Find the package name and the relative path in the package. |
+ var index = parts.indexOf(name); |
+ if (index == -1) return null; |
+ |
+ // If we got here, the path *did* contain the special directory, which |
+ // means we should not interpret it as a regular path, even if it's |
+ // missing the package name after it, which makes it invalid here. |
+ isSpecial = true; |
+ if (index + 1 >= parts.length) return null; |
+ |
+ var package = parts[index + 1]; |
+ var assetPath = path.url.join(dir, |
+ path.url.joinAll(parts.skip(index + 2))); |
+ return new AssetId(package, assetPath); |
+ } |
+ |
+ // See if it's "packages" URL. |
+ var id = _trySpecialUrl("packages", "lib"); |
+ if (id != null) return id; |
+ |
+ // See if it's an "assets" URL. |
+ id = _trySpecialUrl("assets", "asset"); |
+ if (id != null) return id; |
+ |
+ // If we got here, we had a path like "/packages" which is a special |
+ // directory, but not a valid path since it lacks a following package name. |
+ if (isSpecial) return null; |
+ |
+ // Otherwise, it's a path in current package's web directory. |
+ return new AssetId(_rootPackage, |
+ path.url.join("web", path.url.joinAll(parts))); |
+ } |
+ |
+ /// Log [message] at [log.Level.FINE] with metadata about [request]. |
+ void _logRequest(HttpRequest request, String message) => |
+ log.fine("BarbackServer ${request.method} ${request.uri}\n$message"); |
+} |
+ |
+/// The result of the server handling a URL. |
+/// |
+/// Only requests for which an asset was requested from barback will emit a |
+/// result. Malformed requests will be handled internally. |
+class BarbackServerResult { |
+ /// The requested url. |
+ final Url url; |
+ |
+ /// The id that [url] identifies. |
+ final AssetId id; |
+ |
+ /// The error thrown by barback. |
+ /// |
+ /// If the request was served successfully, this will be null. |
+ final error; |
+ |
+ /// Whether the request was served successfully. |
+ bool get isSuccess => error == null; |
+ |
+ /// Whether the request was served unsuccessfully. |
+ bool get isFailure => !isSuccess; |
+ |
+ BarbackServerResult._success(this.url, this.id) |
+ : error = null; |
+ |
+ BarbackServerResult._failure(this.url, this.id, this.error); |
+} |