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.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 |