Add the package implementation.
R=jakemac@google.com
Review URL: https://codereview.chromium.org//2171743003 .
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6f5e0ea..fd217bd 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,7 +23,7 @@
### File headers
All files in the project must start with the following header.
- // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+ // Copyright (c) 2016, 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.
diff --git a/LICENSE b/LICENSE
index de31e1a..82e9b52 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright 2015, the Dart project authors. All rights reserved.
+Copyright 2016, the Dart project authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
diff --git a/codereview.settings b/codereview.settings
new file mode 100644
index 0000000..812af63
--- /dev/null
+++ b/codereview.settings
@@ -0,0 +1,3 @@
+CODE_REVIEW_SERVER: http://codereview.chromium.org/
+VIEW_VC: https://github.com/dart-lang/shelf_packages_handler/commit/
+CC_LIST: reviews@dartlang.org
\ No newline at end of file
diff --git a/lib/shelf_packages_handler.dart b/lib/shelf_packages_handler.dart
new file mode 100644
index 0000000..eda6203
--- /dev/null
+++ b/lib/shelf_packages_handler.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2016, 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 shelf_packages_handler;
+
+import 'package:shelf/shelf.dart';
+import 'package:shelf_static/shelf_static.dart';
+import 'package:package_resolver/package_resolver.dart';
+import 'package:path/path.dart' as p;
+
+import 'src/async_handler.dart';
+import 'src/dir_handler.dart';
+import 'src/package_config_handler.dart';
+
+/// A handler that serves the contents of a virtual packages directory.
+///
+/// This effectively serves `package:${request.url}`. It locates packages using
+/// the package resolution logic defined by [resolver]. If [resolver] isn't
+/// passed, it defaults to the current isolate's package resolution logic.
+///
+/// This can only serve assets from `file:` URIs.
+Handler packagesHandler({PackageResolver resolver}) {
+ resolver ??= PackageResolver.current;
+ return new AsyncHandler(resolver.packageRoot.then((packageRoot) {
+ if (packageRoot != null) {
+ return createStaticHandler(p.fromUri(packageRoot),
+ serveFilesOutsidePath: true);
+ } else {
+ return new PackageConfigHandler(resolver);
+ }
+ }));
+}
+
+/// A handler that serves virtual `packages/` directories wherever they're
+/// requested.
+///
+/// This serves the same assets as [packagesHandler] for every URL that contains
+/// `/packages/`. Otherwise, it returns 404s for all requests.
+///
+/// This is useful for ensuring that `package:` imports work for all entrypoints
+/// in Dartium.
+Handler packagesDirHandler({PackageResolver resolver}) =>
+ new DirHandler("packages", packagesHandler(resolver: resolver));
diff --git a/lib/src/async_handler.dart b/lib/src/async_handler.dart
new file mode 100644
index 0000000..1b68f12
--- /dev/null
+++ b/lib/src/async_handler.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2016, 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.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:shelf/shelf.dart';
+
+class AsyncHandler {
+ final ResultFuture<Handler> _future;
+
+ AsyncHandler(Future<Handler> future) : _future = new ResultFuture(future);
+
+ call(Request request) {
+ if (_future.result == null) {
+ return _future.then((handler) => handler(request));
+ }
+
+ if (_future.result.isError) return _future;
+
+ return _future.result.asValue.value(request);
+ }
+}
diff --git a/lib/src/dir_handler.dart b/lib/src/dir_handler.dart
new file mode 100644
index 0000000..3605aa9
--- /dev/null
+++ b/lib/src/dir_handler.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2016, 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 shelf_packages_handler.dir_handler;
+
+import 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart';
+
+/// A utility handler that mounts a sub-handler beneath a directory name,
+/// wherever that directory name appears in a URL.
+///
+/// In practice, this is used to mount a [PackagesHandler] underneath
+/// `packages/` directories.
+class DirHandler {
+ /// The directory name to look for.
+ final String _name;
+
+ /// The inner handler to mount.
+ final Handler _inner;
+
+ DirHandler(this._name, this._inner);
+
+ /// The callback for handling a single request.
+ call(Request request) {
+ var segments = request.url.pathSegments;
+ for (var i = 0; i < segments.length; i++) {
+ if (segments[i] != _name) continue;
+ return _inner(request.change(path: p.url.joinAll(segments.take(i + 1))));
+ }
+
+ return new Response.notFound("Not found.");
+ }
+}
diff --git a/lib/src/package_config_handler.dart b/lib/src/package_config_handler.dart
new file mode 100644
index 0000000..fd79290
--- /dev/null
+++ b/lib/src/package_config_handler.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2016, 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.
+
+import 'package:package_resolver/package_resolver.dart';
+import 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart';
+import 'package:shelf_static/shelf_static.dart';
+
+import 'async_handler.dart';
+
+/// A shelf handler that serves a virtual packages directory based on a package
+/// config.
+class PackageConfigHandler {
+ /// The static handlers for serving entries in the package config, indexed by
+ /// name.
+ final _packageHandlers = new Map<String, Handler>();
+
+ /// The information specifying how to do package resolution.
+ PackageResolver _resolver;
+
+ PackageConfigHandler(this._resolver);
+
+ /// The callback for handling a single request.
+ call(Request request) {
+ var segments = request.url.pathSegments;
+ return _handlerFor(segments.first)(request.change(path: segments.first));
+ }
+
+ /// Creates a handler for [package] based on the package map in [resolver].
+ Handler _handlerFor(String package) {
+ return _packageHandlers.putIfAbsent(package, () {
+ return new AsyncHandler(_resolver.urlFor(package).then((url) {
+ var handler = url == null
+ ? (_) => new Response.notFound("Package $package not found.")
+ : createStaticHandler(p.fromUri(url), serveFilesOutsidePath: true);
+
+ return handler;
+ }));
+ });
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 33b3459..323adb2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: shelf_packages_handler
-version: 1.0.0-dev
+version: 1.0.0
description: A shelf handler for serving a `packages/` directory.
author: Dart Team <misc@dartlang.org>
homepage: https://github.com/dart-lang/shelf_packages_handler
@@ -7,5 +7,12 @@
environment:
sdk: '>=1.9.0 <2.0.0'
+dependencies:
+ async: '^1.1.0'
+ path: '^1.0.0'
+ shelf: '^0.6.0'
+ shelf_static: '^0.2.0'
+ package_resolver: '^1.0.0'
+
dev_dependencies:
test: '^0.12.0'
diff --git a/test/packages_handler_test.dart b/test/packages_handler_test.dart
new file mode 100644
index 0000000..dce58a6
--- /dev/null
+++ b/test/packages_handler_test.dart
@@ -0,0 +1,147 @@
+// Copyright (c) 2016, 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.
+
+import 'dart:io';
+
+import 'package:package_resolver/package_resolver.dart';
+import 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart';
+import 'package:shelf_packages_handler/shelf_packages_handler.dart';
+import 'package:test/test.dart';
+
+void main() {
+ var dir;
+ setUp(() {
+ dir = Directory.systemTemp
+ .createTempSync("shelf_packages_handler_test")
+ .path;
+ new Directory(dir).createSync();
+ new Directory("$dir/foo").createSync();
+ new File("$dir/foo/foo.dart")
+ .writeAsStringSync("void main() => print('in foo');");
+ });
+
+ tearDown(() {
+ new Directory(dir).deleteSync(recursive: true);
+ });
+
+ group("packagesHandler", () {
+ test("defaults to the current method of package resolution", () async {
+ var handler = packagesHandler();
+ var request = new Request(
+ "GET",
+ Uri.parse(
+ "http://example.com/shelf_packages_handler/"
+ "shelf_packages_handler.dart"));
+ var response = await handler(request);
+ expect(response.statusCode, equals(200));
+ expect(await response.readAsString(),
+ contains("Handler packagesHandler"));
+ });
+
+ group("with a package root", () {
+ var resolver;
+ setUp(() => resolver = new PackageResolver.root(p.toUri(dir)));
+
+ test("looks up a real file", () async {
+ var handler = packagesHandler(resolver: resolver);
+ var request = new Request(
+ "GET", Uri.parse("http://example.com/foo/foo.dart"));
+ var response = await handler(request);
+ expect(response.statusCode, equals(200));
+ expect(await response.readAsString(), contains("in foo"));
+ });
+
+ test("404s for a nonexistent file", () async {
+ var handler = packagesHandler(resolver: resolver);
+ var request = new Request(
+ "GET", Uri.parse("http://example.com/foo/bar.dart"));
+ var response = await handler(request);
+ expect(response.statusCode, equals(404));
+ });
+ });
+
+ group("with a package config", () {
+ var resolver;
+ setUp(() {
+ resolver = new PackageResolver.config({"foo": p.toUri("$dir/foo")});
+ });
+
+ test("looks up a real file", () async {
+ var handler = packagesHandler(resolver: resolver);
+ var request = new Request(
+ "GET", Uri.parse("http://example.com/foo/foo.dart"));
+ var response = await handler(request);
+ expect(response.statusCode, equals(200));
+ expect(await response.readAsString(), contains("in foo"));
+ });
+
+ test("404s for a nonexistent package", () async {
+ var handler = packagesHandler(resolver: resolver);
+ var request = new Request(
+ "GET", Uri.parse("http://example.com/bar/foo.dart"));
+ var response = await handler(request);
+ expect(response.statusCode, equals(404));
+ });
+
+ test("404s for a nonexistent file", () async {
+ var handler = packagesHandler(resolver: resolver);
+ var request = new Request(
+ "GET", Uri.parse("http://example.com/foo/bar.dart"));
+ var response = await handler(request);
+ expect(response.statusCode, equals(404));
+ });
+ });
+ });
+
+ group("packagesDirHandler", () {
+ test("supports a directory at the root of the URL", () async {
+ var handler = packagesDirHandler();
+ var request = new Request(
+ "GET",
+ Uri.parse(
+ "http://example.com/packages/shelf_packages_handler/"
+ "shelf_packages_handler.dart"));
+ var response = await handler(request);
+ expect(response.statusCode, equals(200));
+ expect(await response.readAsString(),
+ contains("Handler packagesHandler"));
+ });
+
+ test("supports a directory deep in the URL", () async {
+ var handler = packagesDirHandler();
+ var request = new Request(
+ "GET",
+ Uri.parse(
+ "http://example.com/foo/bar/very/deep/packages/"
+ "shelf_packages_handler/shelf_packages_handler.dart"));
+ var response = await handler(request);
+ expect(response.statusCode, equals(200));
+ expect(await response.readAsString(),
+ contains("Handler packagesHandler"));
+ });
+
+ test("404s for a URL without a packages directory", () async {
+ var handler = packagesDirHandler();
+ var request = new Request(
+ "GET",
+ Uri.parse(
+ "http://example.com/shelf_packages_handler/"
+ "shelf_packages_handler.dart"));
+ var response = await handler(request);
+ expect(response.statusCode, equals(404));
+ });
+
+ test("404s for a non-existent file within a packages directory", () async {
+ var handler = packagesDirHandler();
+ var request = new Request(
+ "GET",
+ Uri.parse(
+ "http://example.com/packages/shelf_packages_handler/"
+ "non_existent.dart"));
+ var response = await handler(request);
+ expect(response.statusCode, equals(404));
+ });
+ });
+}