| 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 /** |
| 6 * This library provides a simple API for routing HttpRequests based on thier |
| 7 * URL. |
| 8 */ |
| 9 library route.server; |
| 10 |
| 11 import 'dart:async'; |
| 12 import 'dart:collection'; |
| 13 import 'dart:io'; |
| 14 import 'package:route_hierarchical/src/async_utils.dart'; |
| 15 export 'url_pattern.dart'; |
| 16 import 'pattern.dart'; |
| 17 |
| 18 typedef Future<bool> Filter(HttpRequest request); |
| 19 |
| 20 /** |
| 21 * A request router that makes it easier to handle [HttpRequest]s from an |
| 22 * [HttpServer]. |
| 23 * |
| 24 * [serve] creates a new [Stream] of requests whose paths match against the |
| 25 * given pattern. Matching requests are not sent to any other streams created by |
| 26 * a server() call. |
| 27 * |
| 28 * [filter] registers a [Filter] function to run against matching requests. On |
| 29 * each request the filters that match are run in order, waiting for each to |
| 30 * complete since filters return a Future. If any filter completes false, the |
| 31 * subsequent filters and request handlers are not run. This way a filter can |
| 32 * prevent further processing, like needed for authentication. |
| 33 * |
| 34 * Requests not matched by a call to [serve] are sent to the [defaultStream]. |
| 35 * If there's no subscriber to the defaultStream then a 404 is sent to the |
| 36 * response. |
| 37 * |
| 38 * Example: |
| 39 * import 'package:route/server.dart'; |
| 40 * import 'package:route/pattern.dart'; |
| 41 * |
| 42 * HttpServer.bind().then((server) { |
| 43 * var router = new Router(server); |
| 44 * router.filter(matchesAny(['/foo', '/bar']), authFilter); |
| 45 * router.serve('/foo').listen(fooHandler); |
| 46 * router.serve('/bar').listen(barHandler); |
| 47 * router.defaultStream.listen(send404); |
| 48 * }); |
| 49 */ |
| 50 class Router { |
| 51 final Stream<HttpRequest> _incoming; |
| 52 |
| 53 final List<_Route> _routes = <_Route>[]; |
| 54 |
| 55 final Map<Pattern, Filter> _filters = new LinkedHashMap<Pattern, Filter>(); |
| 56 |
| 57 final StreamController<HttpRequest> _defaultController = |
| 58 new StreamController<HttpRequest>(); |
| 59 |
| 60 /** |
| 61 * Create a new Router that listens to the [incoming] stream, usually an |
| 62 * instance of [HttpServer]. |
| 63 */ |
| 64 Router(Stream<HttpRequest> incoming) : _incoming = incoming { |
| 65 _incoming.listen(_handleRequest); |
| 66 } |
| 67 |
| 68 /** |
| 69 * Request whose URI matches [url] and [method] (if provided) are sent to the |
| 70 * stream created by this method, and not sent to any other router streams. |
| 71 */ |
| 72 Stream<HttpRequest> serve(Pattern url, {String method}) { |
| 73 var controller = new StreamController<HttpRequest>(); |
| 74 _routes.add(new _Route(controller, url, method:method)); |
| 75 return controller.stream; |
| 76 } |
| 77 |
| 78 /** |
| 79 * A [Filter] returns a [Future<bool>] that tells the router whether to apply |
| 80 * the remaining filters and send requests to the streams created by [serve]. |
| 81 * |
| 82 * If the filter returns true, the request is passed to the next filter, and |
| 83 * then to the first matching server stream. If the filter returns false, it's |
| 84 * assumed that the filter is handling the request and it's not forwarded. |
| 85 */ |
| 86 void filter(Pattern url, Filter filter) { |
| 87 _filters[url] = filter; |
| 88 } |
| 89 |
| 90 Stream<HttpRequest> get defaultStream => _defaultController.stream; |
| 91 |
| 92 void _handleRequest(HttpRequest req) { |
| 93 bool cont = true; |
| 94 doWhile(_filters.keys, (Pattern pattern) { |
| 95 if (matchesFull(pattern, req.uri.path)) { |
| 96 return _filters[pattern](req).then((c) { |
| 97 cont = c; |
| 98 return c; |
| 99 }); |
| 100 } |
| 101 return new Future.value(true); |
| 102 }).then((_) { |
| 103 if (cont) { |
| 104 bool handled = false; |
| 105 var matches = _routes.where((r) => r.matches(req)); |
| 106 if (!matches.isEmpty) { |
| 107 matches.first.controller.add(req); |
| 108 } else { |
| 109 if (_defaultController.hasListener) { |
| 110 _defaultController.add(req); |
| 111 } else { |
| 112 send404(req); |
| 113 } |
| 114 } |
| 115 } |
| 116 }); |
| 117 } |
| 118 } |
| 119 |
| 120 void send404(HttpRequest req) { |
| 121 req.response.statusCode = HttpStatus.NOT_FOUND; |
| 122 req.response.write("Not Found"); |
| 123 req.response.close(); |
| 124 } |
| 125 |
| 126 class _Route { |
| 127 final Pattern url; |
| 128 final String method; |
| 129 final StreamController controller; |
| 130 _Route(this.controller, this.url, {this.method}); |
| 131 |
| 132 bool matches(HttpRequest request) => matchesFull(url, request.uri.path) && |
| 133 (method == null || request.method.toUpperCase() == method); |
| 134 } |
| OLD | NEW |