+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/.travis.yml b/framework/.travis.yml
new file mode 100644
index 00000000..14148ad6
--- /dev/null
+++ b/framework/.travis.yml
@@ -0,0 +1,6 @@
+language: dart
+dart:
+ - dev
+ - stable
+before_script: chmod +x ./tool/travis.sh
+script: ./tool/travis.sh
\ No newline at end of file
diff --git a/framework/.vscode/settings.json b/framework/.vscode/settings.json
new file mode 100644
index 00000000..20af2f68
--- /dev/null
+++ b/framework/.vscode/settings.json
@@ -0,0 +1,3 @@
+// Place your settings in this file to overwrite default and user settings.
+{
+}
\ No newline at end of file
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
new file mode 100644
index 00000000..a3b932db
--- /dev/null
+++ b/framework/CHANGELOG.md
@@ -0,0 +1,282 @@
+# 2.1.1
+* `AngelHttp.uri` now returns an empty `Uri` if the server is not listening.
+
+# 2.1.0
+* This release was originally planned to be `2.0.5`, but it adds several features, and has
+therefore been bumped to `2.1.0`.
+* Fix a new (did not appear before 2.6/2.7) type error causing compilation to fail.
+https://github.com/angel-dart/framework/issues/249
+
+# 2.0.5-beta
+* Make `@Expose()` in `Controller` optional. https://github.com/angel-dart/angel/issues/107
+* Add `allowHttp1` to `AngelHttp2` constructors. https://github.com/angel-dart/angel/issues/108
+* Add `deserializeBody` and `decodeBody` to `RequestContext`. https://github.com/angel-dart/angel/issues/109
+* Add `HostnameRouter`, which allows for routing based on hostname. https://github.com/angel-dart/angel/issues/110
+* Default to using `ThrowingReflector`, instead of `EmptyReflector`. This will give a more descriptive
+error when trying to use controllers, etc. without reflection enabled.
+* `mountController` returns the mounted controller.
+
+# 2.0.4+1
+* Run `Controller.configureRoutes` before mounting `@Expose` routes.
+* Make `Controller.configureServer` always return a `Future`.
+
+# 2.0.4
+* Prepare for Dart SDK change to `Stream>` that are now
+ `Stream`.
+* Accept any content type if accept header is missing. See
+[this PR](https://github.com/angel-dart/framework/pull/239).
+
+# 2.0.3
+* Patch up a bug caused by an upstream change to Dart's stream semantics.
+See more: https://github.com/angel-dart/angel/issues/106#issuecomment-499564485
+
+# 2.0.2+1
+* Fix a bug in the implementation of `Controller.applyRoutes`.
+
+# 2.0.2
+* Make `ResponseContext` *explicitly* implement `StreamConsumer` (though technically it already did???)
+* Split `Controller.configureServer` to create `Controller.applyRoutes`.
+
+# 2.0.1
+* Tracked down a bug in `Driver.runPipeline` that allowed fallback
+handlers to run, even after the response was closed.
+* Add `RequestContext.shutdownHooks`.
+* Call `RequestContext.close` in `Driver.sendResponse`.
+* AngelConfigurer is now `FutureOr`, instead of just `FutureOr`.
+* Use a `Container.has` check in `Driver.sendResponse`.
+* Remove unnecessary `new` and `const`.
+
+# 2.0.0
+* Angel 2! :angel: :rocket:
+
+# 2.0.0-rc.10
+* Fix an error that prevented `AngelHttp2.custom` from working properly.
+* Add `startSharedHttp2`.
+
+# 2.0.0-rc.9
+* Fix some bugs in the `HookedService` implementation that skipped
+the outputs of `before` events.
+
+# 2.0.0-rc.8
+* Fix `MapService` flaw where clients could remove all records, even if `allowRemoveAll` were `false`.
+
+# 2.0.0-rc.7
+* `AnonymousService` can override `readData`.
+* `Service.map` now overrides `readData`.
+* `HookedService.readData` forwards to `inner`.
+
+# 2.0.0-rc.6
+* Make `redirect` and `download` methods asynchronous.
+
+# 2.0.0-rc.5
+* Make `serializer` `FutureOr Function(Object)`.
+* Make `ResponseContext.serialize` return `Future`.
+
+# 2.0.0-rc.4
+* Support resolution of asynchronous injections in controllers and `ioc`.
+* Inject `RequestContext` and `ResponseContext` into requests.
+
+# 2.0.0-rc.3
+* `MapService.modify` was not actually modifying items.
+
+# 2.0.0-rc.2
+* Fixes Pub analyzer lints (see `angel_route@3.0.6`)
+
+# 2.0.0-rc.1
+* Fix logic error that allowed content to be written to streaming responses after `close` was closed.
+
+# 2.0.0-rc.0
+* Log a warning when no `reflector` is provided.
+* Add `AngelEnvironment` class.
+ * Add `Angel.environment`.
+ * Deprecated `app.isProduction` in favor of `app.environment.isProduction`.
+* Allow setting of `bodyAsObject`, `bodyAsMap`, or `bodyAsList` **exactly once**.
+* Resolve named singletons in `resolveInjection`.
+* Fix a bug where `Service.parseId` would attempt to parse an `int`.
+* Replace as Data cast in Service.dart with a method that throws a 400 on error.
+
+# 2.0.0-alpha.24
+* Add `AngelEnv` class to `core`.
+* Deprecate `Angel.isProduction`, in favor of `AngelEnv`.
+
+# 2.0.0-alpha.23
+* `ResponseContext.render` sets `charset` to `utf8` in `contentType`.
+
+# 2.0.0-alpha.22
+* Update pipeline handling mechanism, and inject a `MiddlewarePipelineIterator`.
+ * This allows routes to know where in the resolution process they exist, at runtime.
+
+# 2.0.0-alpha.21
+* Update for `angel_route@3.0.4` compatibility.
+* Add `readAsBytes` and `readAsString` to `UploadedFile`.
+* URI-decode path components in HTTP2.
+
+# 2.0.0-alpha.20
+* Inject the `MiddlewarePipeline` into requests.
+
+# 2.0.0-alpha.19
+* `parseBody` checks for null content type, and throws a `400` if none was given.
+* Add `ResponseContext.contentLength`.
+* Update `streamFile` to set content length, and also to work on `HEAD` requests.
+
+# 2.0.0-alpha.18
+* Upgrade `http2` dependency.
+* Upgrade `uuid` dependency.
+* Fixed a bug that prevented body parsing from ever completing with `http2`.
+* Add `Providers.hashCode`.
+
+# 2.0.0-alpha.17
+* Revert the migration to `lumberjack` for now. In the future, when it's more
+stable, there'll be a conversion, perhaps.
+
+# 2.0.0-alpha.16
+* Use `package:lumberjack` for logging.
+
+# 2.0.0-alpha.15
+* Remove dependency on `body_parser`.
+* `RequestContext` now exposes a `Stream> get body` getter.
+ * Calling `RequestContext.parseBody()` parses its contents.
+ * Added `bodyAsMap`, `bodyAsList`, `bodyAsObject`, and `uploadedFiles` to `RequestContext`.
+ * Removed `Angel.keepRawRequestBuffers` and anything that had to do with buffering request bodies.
+
+# 2.0.0-alpha.14
+* Patch `HttpResponseContext._openStream` to send content-length.
+
+# 2.0.0-alpha.13
+
+- Fixed a logic error in `HttpResponseContext` that prevented status codes from being sent.
+
+# 2.0.0-alpha.12
+
+- Remove `ResponseContext.sendFile`.
+- Add `Angel.mimeTypeResolver`.
+- Fix a bug where an unknown MIME type on `streamFile` would return a 500.
+
+# 2.0.0-alpha.11
+
+- Add `readMany` to `Service`.
+- Allow `ResponseContext.redirect` to take a `Uri`.
+- Add `Angel.mountController`.
+- Add `Angel.findServiceOf`.
+- Roll in HTTP/2. See `pkg:angel_framework/http2.dart`.
+
+# 2.0.0-alpha.10
+
+- All calls to `Service.parseId` are now affixed with the `` argument.
+- Added `uri` getter to `AngelHttp`.
+- The default for `parseQuery` now wraps query parameters in `Map.from`.
+ This resolves a bug in `package:angel_validate`.
+
+# 2.0.0-alpha.9
+
+- Add `Service.map`.
+
+# 2.0.0-alpha.8
+
+- No longer export HTTP-specific code from `angel_framework.dart`.
+ An import of `import 'package:angel_framework/http.dart';` will be necessary in most cases now.
+
+# 2.0.0-alpha.7
+
+- Force a tigher contract on services. They now must return `Data` on all
+ methods except for `index`, which returns a `List`.
+
+# 2.0.0-alpha.6
+
+- Allow passing a custom `Container` to `handleContained` and co.
+
+# 2.0.0-alpha.5
+
+- `MapService` methods now explicitly return `Map`.
+
+# 2.0.0-alpha.4
+
+- Renamed `waterfall` to `chain`.
+- Renamed `Routable.service` to `Routable.findService`.
+ - Also `Routable.findHookedService`.
+
+# 2.0.0-alpha.3
+
+- Added `` type parameters to `Service`.
+- `HookedService` now follows suit, and takes a third parameter, pointing to the inner service.
+- `Routable.use` now uses the generic parameters added to `Service`.
+- Added generic usage to `HookedServiceListener`, etc.
+- All service methods take `Map` as `params` now.
+
+# 2.0.0-alpha.2
+
+- Added `ResponseContext.detach`.
+
+# 2.0.0-alpha.1
+
+- Removed `Angel.injectEncoders`.
+- Added `Providers.toJson`.
+- Moved `Providers.graphql` to `Providers.graphQL`.
+- `Angel.optimizeForProduction` no longer calls `preInject`,
+ as it does not need to.
+- Rename `ResponseContext.enableBuffer` to `ResponseContext.useBuffer`.
+
+# 2.0.0-alpha
+
+- Removed `random_string` dependency.
+- Moved reflection to `package:angel_container`.
+- Upgraded `package:file` to `5.0.0`.
+- `ResponseContext.sendFile` now uses `package:file`.
+- Abandon `ContentType` in favor of `MediaType`.
+- Changed view engine to use `Map`.
+- Remove dependency on `package:json_god` by default.
+- Remove dependency on `package:dart2_constant`.
+- Moved `lib/hooks.dart` into `package:angel_hooks`.
+- Moved `TypedService` into `package:angel_typed_service`.
+- Completely removed the `AngelBase` class.
+- Removed all `@deprecated` symbols.
+- `Service.toId` was renamed to `Service.parseId`; it also now uses its
+ single type argument to determine how to parse a value. \* In addition, this method was also made `static`.
+- `RequestContext` and `ResponseContext` are now generic, and take a
+ single type argument pointing to the underlying request/response type,
+ respectively.
+- `RequestContext.io` and `ResponseContext.io` are now permanently
+ gone.
+- `HttpRequestContextImpl` and `HttpResponseContextImpl` were renamed to
+ `HttpRequestContext` and `HttpResponseContext`.
+- Lazy-parsing request bodies is now the default; `Angel.lazyParseBodies` was replaced
+ with `Angel.eagerParseRequestBodies`.
+- `Angel.storeOriginalBuffer` -> `Angel.storeRawRequestBuffers`.
+- The methods `lazyBody`, `lazyFiles`, and `lazyOriginalBuffer` on `ResponseContext` were all
+ replaced with `parseBody`, `parseUploadedFiles`, and `parseRawRequestBuffer`, respectively.
+- Removed the synchronous equivalents of the above methods (`body`, `files`, and `originalBuffer`),
+ as well as `query`.
+- Removed `Angel.injections` and `RequestContext.injections`.
+- Removed `Angel.inject` and `RequestContext.inject`.
+- Removed a dependency on `package:pool`, which also meant removing `AngelHttp.throttle`.
+- Remove the `RequestMiddleware` typedef; from now on, one should use `ResponseContext.end`
+ exclusively to close responses.
+- `waterfall` will now only accept `RequestHandler`.
+- `Routable`, and all of its subclasses, now extend `Router`, and therefore only
+ take routes in the form of `FutureOr myFunc(RequestContext, ResponseContext res)`.
+- `@Middleware` now takes an `Iterable` of `RequestHandler`s.
+- `@Expose.path` now _must_ be a `String`, not just any `Pattern`.
+- `@Expose.middleware` now takes `Iterable`, instead of just `List`.
+- `createDynamicHandler` was renamed to `ioc`, and is now used to run IoC-aware handlers in a
+ type-safe manner.
+- `RequestContext.params` is now a `Map`, rather than just a `Map`.
+- Removed `RequestContext.grab`.
+- Removed `RequestContext.properties`.
+- Removed the defunct `debug` property where it still existed.
+- `Routable.use` now only accepts a `Service`.
+- Removed `Angel.createZoneForRequest`.
+- Removed `Angel.defaultZoneCreator`.
+- Added all flags to the `Angel` constructor, ex. `Angel.eagerParseBodies`.
+- Fix a bug where synchronous errors in `handleRequest` would not be caught.
+- `AngelHttp.useZone` now defaults to `false`.
+- `ResponseContext` now starts in streaming mode by default; the response buffer is opt-in,
+ as in many cases it is unnecessary and slows down response time.
+- `ResponseContext.streaming` was replaced by `ResponseContext.isBuffered`.
+- Made `LockableBytesBuilder` public.
+- Removed the now-obsolete `ResponseContext.willCloseItself`.
+- Removed `ResponseContext.dispose`.
+- Removed the now-obsolete `ResponseContext.end`.
+- Removed the now-obsolete `ResponseContext.releaseCorrespondingRequest`.
+- `preInject` now takes a `Reflector` as its second argument.
+- `Angel.reflector` defaults to `const EmptyReflector()`, disabling
+ reflection out-of-the-box.
diff --git a/framework/LICENSE b/framework/LICENSE
new file mode 100644
index 00000000..32e25c1c
--- /dev/null
+++ b/framework/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 The Angel Framework
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/framework/README.md b/framework/README.md
new file mode 100644
index 00000000..7aecfc36
--- /dev/null
+++ b/framework/README.md
@@ -0,0 +1,61 @@
+# angel_framework
+
+[![Pub](https://img.shields.io/pub/v/angel_framework.svg)](https://pub.dartlang.org/packages/angel_framework)
+[![build status](https://travis-ci.org/angel-dart/framework.svg)](https://travis-ci.org/angel-dart/framework)
+
+A high-powered HTTP server with support for dependency injection, sophisticated routing and more.
+
+This is the core of the [Angel](https://github.com/angel-dart/angel) framework.
+To build real-world applications, please see the [homepage](https://angel-dart.dev).
+
+```dart
+import 'package:angel_container/mirrors.dart';
+import 'package:angel_framework/angel_framework.dart';
+
+main() async {
+ var app = Angel(reflector: MirrorsReflector());
+
+ // Index route. Returns JSON.
+ app.get('/', (req, res) => res.write('Welcome to Angel!'));
+
+ // Accepts a URL like /greet/foo or /greet/bob.
+ app.get(
+ '/greet/:name',
+ (req, res) {
+ var name = req.params['name'];
+ res
+ ..write('Hello, $name!')
+ ..close();
+ },
+ );
+
+ // Pattern matching - only call this handler if the query value of `name` equals 'emoji'.
+ app.get(
+ '/greet',
+ ioc((@Query('name', match: 'emoji') String name) => '😇🔥🔥🔥'),
+ );
+
+ // Handle any other query value of `name`.
+ app.get(
+ '/greet',
+ ioc((@Query('name') String name) => 'Hello, $name!'),
+ );
+
+ // Simple fallback to throw a 404 on unknown paths.
+ app.fallback((req, res) {
+ throw AngelHttpException.notFound(
+ message: 'Unknown path: "${req.uri.path}"',
+ );
+ });
+
+ var http = AngelHttp(app);
+ var server = await http.startServer('127.0.0.1', 3000);
+ var url = 'http://${server.address.address}:${server.port}';
+ print('Listening at $url');
+ print('Visit these pages to see Angel in action:');
+ print('* $url/greet/bob');
+ print('* $url/greet/?name=emoji');
+ print('* $url/greet/?name=jack');
+ print('* $url/nonexistent_page');
+}
+```
diff --git a/framework/TODO.md b/framework/TODO.md
new file mode 100644
index 00000000..3381eeda
--- /dev/null
+++ b/framework/TODO.md
@@ -0,0 +1,7 @@
+* Support for [Trestle](https://github.com/dart-bridge/trestle), use this as default, set up migration system around this
+* Angel CLI
+* Angel bootstrap project
+* More docs
+* Make tutorials, videos
+* Launch!
+* Get a nice launch process, so we can pre-compile things before running. Also support a sort of hot-reload
diff --git a/framework/analysis_options.yaml b/framework/analysis_options.yaml
new file mode 100644
index 00000000..42d44a85
--- /dev/null
+++ b/framework/analysis_options.yaml
@@ -0,0 +1,15 @@
+# include: package:pedantic/analysis_options.yaml
+analyzer:
+ errors:
+ always_declare_return_types: ignore
+ omit_local_variable_types: ignore
+ prefer_single_quotes: ignore
+ prefer_spread_collections: ignore
+ strong-mode:
+ implicit-casts: false
+linter:
+ rules:
+ - avoid_slow_async_io
+ - curly_braces_in_flow_control_structures
+ - unnecessary_const
+ - unnecessary_new
\ No newline at end of file
diff --git a/framework/dev.key b/framework/dev.key
new file mode 100644
index 00000000..5d49ae7e
--- /dev/null
+++ b/framework/dev.key
@@ -0,0 +1,29 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIE5DAcBgoqhkiG9w0BDAEBMA4ECL7L6rj6uEHGAgIIAASCBMLbucyfqAkgCbhP
+xNSHYllPMAv/dsIjtnsBwepCXPGkCBCuOAw/2FaCHjN9hBqL5V7fkrKeaemhm2YE
+ycPtlHJYPDf3kEkyMjdZ9rIY6kePGfQizs2uJPcXj4YPyQ4HsfVXpOicKfQrouf5
+Mze9bGzeMN065q3iP4dYUMwHAyZYteXCsanQNHlqvsWli0W+H8St8fdsXefZhnv1
+qVatKWdNdWQ9t5MuljgNU2Vv56sHKEYXI0yLxk2QUMk8KlJfnmt8foYUsnPUXHmc
+gIjLKwwVkpdololnEHSNu0cEOUPowjgJru+uMpn7vdNl7TPEQ9jbEgdNg4JwoYzU
+0nao8WzjaSp7kzvZz0VFwKnk5AjstGvvuAWckADdq23QElbn/mF7AG1m/TBpYxzF
+gTt37UdndS/AcvVznWVVrRP5iTSIawdIwvqI4s7rqsoE0GCcak+RhchgAz2gWKkS
+oODUo0JL6pPVbJ3l4ebbaO6c99nDVc8dViPtc1EkStJEJ2O4kI4xgLSCr4Y9ahKn
+oAaoSkX7Xxq3aQm+BzqSpLjdGL8atsqR/YVOIHYIl3gThvP0NfZGx1xHyvO5mCdZ
+kHxSA7tKWxauZ3eQ2clbnzeRsl4El0WMHy/5K1ovene4v7sunmoXVtghBC8hK6eh
+zMO9orex2PNQ/VQC7HCvtytunOVx1lkSBoNo7hR70igg6rW9H7UyoAoBOwMpT1xa
+J6V62nqruTKOqFNfur7aHJGpHGtDb5/ickHeYCyPTvmGp67u4wChzKReeg02oECe
+d1E5FKAcIa8s9TVOB6Z+HvTRNQZu2PsI6TJnjQRowvY9DAHiWTlJZBBY/pko3hxX
+TsIeybpvRdEHpDWv86/iqtw1hv9CUxS/8ZTWUgBo+osShHW79FeDASr9FC4/Zn76
+ZDERTgV4YWlW/klVWcG2lFo7jix+OPXAB+ZQavLhlN1xdWBcIz1AUWjAM4hdPylW
+HCX4PB9CQIPl2E7F+Y2p6nMcMWSJVBi5UIH7E9LfaBguXSzMmTk2Fw5p1aOQ6wfN
+goVAMVwi8ppAVs741PfHdZ295xMmK/1LCxz5DeAdD/tsA/SYfT753GotioDuC7im
+EyJ5JyvTr5I6RFFBuqt3NlUb3Hp16wP3B2x9DZiB6jxr0l341/NHgsyeBXkuIy9j
+ON2mvpBPCJhS8kgWo3G0UyyKnx64tcgpGuSvZhGwPz843B6AbYyE6pMRfSWRMkMS
+YZYa+VNKhR4ixdj07ocFZEWLVjCH7kxkE8JZXKt8jKYmkWd0lS1QVjgaKlO6lRa3
+q6SPJkhW6pvqobvcqVNXwi1XuzpZeEbuh0B7OTekFTTxx5g9XeDl56M8SVQ1KEhT
+Q1t7H2Nba18WCB7cf+6PN0F0K0Jz1Kq7ZWaqEI/grX1m4RQuvNF5807sB/QKMO/Z
+Gz3NXvHg5xTJRd/567lxPGkor0cE7qD1EZfmJ2HrBYXQ91bhgA7LToBuMZo6ZRXH
+QfsanjbP4FPLMiGdQigLjj3A35L/f4sQOOVac/sRaFnm7pzcxsMvyVU/YtvGcjYE
+xaOOVnamg661Wo0wksXoDjeSz/JIyyKO3Gwp1FSm2wGLjjy/Ehmqcqy8rvHuf07w
+AUukhVtTNn4=
+-----END ENCRYPTED PRIVATE KEY-----
\ No newline at end of file
diff --git a/framework/dev.pem b/framework/dev.pem
new file mode 100644
index 00000000..01756b25
--- /dev/null
+++ b/framework/dev.pem
@@ -0,0 +1,57 @@
+-----BEGIN CERTIFICATE-----
+MIIDKTCCAhGgAwIBAgIJAOWmjTS+OnTEMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV
+BAMMDGludGVybWVkaWF0ZTAeFw0xNTA1MTgwOTAwNDBaFw0yMzA4MDQwOTAwNDBa
+MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALlcwQJuzd+xH8QFgfJSn5tRlvhkldSX98cE7NiA602NBbnAVyUrkRXq
+Ni75lgt0kwjYfA9z674m8WSVbgpLPintPCla9CYky1TH0keIs8Rz6cGWHryWEHiu
+EDuljQynu2b3sAFuHu9nfWurbJwZnFakBKpdQ9m4EyOZCHC/jHYY7HacKSXg1Cki
+we2ca0BWDrcqy8kLy0dZ5oC6IZG8O8drAK8f3f44CRYw59D3sOKBrKXaabpvyEcb
+N7Wk2HDBVwHpUJo1reVwtbM8dhqQayYSD8oXnGpP3RQNu/e2rzlXRyq/BfcDY1JI
+7TbC4t/7/N4EcPSpGsTcSOC9A7FpzvECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglg
+hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O
+BBYEFCnwiEMMFZh7NhCr+qA8K0w4Q+AOMB8GA1UdIwQYMBaAFB0h1Evsaw2vfrmS
+YuoCTmC4EE6ZMA0GCSqGSIb3DQEBCwUAA4IBAQAcFmHMaXRxyoNaeOowQ6iQWoZd
+AUbvG7SHr7I6Pi2aqdqofsKWts7Ytm5WsS0M2nN+sW504houu0iCPeJJX8RQw2q4
+CCcNOs9IXk+2uMzlpocHpv+yYoUiD5DxgWh7eghQMLyMpf8FX3Gy4VazeuXznHOM
+4gE4L417xkDzYOzqVTp0FTyAPUv6G2euhNCD6TMru9REcRhYul+K9kocjA5tt2KG
+MH6y28LXbLyq4YJUxSUU9gY/xlnbbZS48KDqEcdYC9zjW9nQ0qS+XQuQuFIcwjJ5
+V4kAUYxDu6FoTpyQjgsrmBbZlKNxH7Nj4NDlcdJhp/zeSKHqWa5hSWjjKIxp
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAeqgAwIBAgIJAOWmjTS+OnTDMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
+BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw
+WjAXMRUwEwYDVQQDDAxpbnRlcm1lZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDSrAO1CoPvUllgLOzDm5nG0skDF7vh1DUgAIDVGz0ecD0JFbQx
+EF79pju/6MbtpTW2FYvRp11t/G7rGtX923ybOHY/1MNFQrdIvPlO1VV7IGKjoMwP
+DNeb0fIGjHoE9QxaDxR8NX8xQbItpsw+TUtRfc9SLkR+jaYJfVRoM21BOncZbSHE
+YKiZlEbpecB/+EtwVpgvl+8mPD5U07Fi4fp/lza3WXInXQPyiTVllIEJCt4PKmlu
+MocNaJOW38bysL7i0PzDpVZtOxLHOTaW68yF3FckIHNCaA7k1ABEEEegjFMmIao7
+B9w7A0jvr4jZVvNmui5Djjn+oJxwEVVgyf8LAgMBAAGjUDBOMB0GA1UdDgQWBBQd
+IdRL7GsNr365kmLqAk5guBBOmTAfBgNVHSMEGDAWgBRk81s9d0ZbiZhh44KckwPb
+oTc0XzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBZQTK0plfdB5PC
+cC5icut4EmrByJa1RbU7ayuEE70e7hla6KVmVjVdCBGltI4jBYwfhKbRItHiAJ/8
+x+XZKBG8DLPFuDb7lAa1ObhAYF7YThUFPQYaBhfzKcWrdmWDBFpvNv6E0Mm364dZ
+e7Yxmbe5S4agkYPoxEzgEYmcUk9jbjdR6eTbs8laG169ljrECXfEU9RiAcqz5iSX
+NLSewqB47hn3B9qgKcQn+PsgO2j7M+rfklhNgeGJeWmy7j6clSOuCsIjWHU0RLQ4
+0W3SB/rpEAJ7fgQbYUPTIUNALSOWi/o1tDX2mXPRjBoxqAv7I+vYk1lZPmSzkyRh
+FKvRDxsW
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAzCCAeugAwIBAgIJAJ0MomS4Ck+8MA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
+BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw
+WjAYMRYwFAYDVQQDDA1yb290YXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAts1ijtBV92S2cOvpUMOSTp9c6A34nIGr0T5Nhz6XiqRVT+gv
+dQgmkdKJQjbvR60y6jzltYFsI2MpGVXY8h/oAL81D/k7PDB2aREgyBfTPAhBHyGw
+siR+2xYt5b/Zs99q5RdRqQNzNpLPJriIKvUsRyQWy1UiG2s7pRXQeA8qB0XtJdCj
+kFIi+G2bDsaffspGeDOCqt7t+yqvRXfSES0c/l7DIHaiMbbp4//ZNML3RNgAjPz2
+hCezZ+wOYajOIyoSPK8IgICrhYFYxvgWxwbLDBEfC5B3jOQsySe10GoRAKZz1gBV
+DmgReu81tYJmdgkc9zknnQtIFdA0ex+GvZlfWQIDAQABo1AwTjAdBgNVHQ4EFgQU
+ZPNbPXdGW4mYYeOCnJMD26E3NF8wHwYDVR0jBBgwFoAUZPNbPXdGW4mYYeOCnJMD
+26E3NF8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATzkZ97K777uZ
+lQcduNX3ey4IbCiEzFA2zO5Blj+ilfIwNbZXNOgm/lqNvVGDYs6J1apJJe30vL3X
+J+t2zsZWzzQzb9uIU37zYemt6m0fHrSrx/iy5lGNqt3HMfqEcOqSCOIK3PCTMz2/
+uyGe1iw33PVeWsm1JUybQ9IrU/huJjbgOHU4wab+8SJCM49ipArp68Fr6j4lcEaE
+4rfRg1ZsvxiOyUB3qPn6wyL/JB8kOJ+QCBe498376eaem8AEFk0kQRh6hDaWtq/k
+t6IIXQLjx+EBDVP/veK0UnVhKRP8YTOoV8ZiG1NcdlJmX/Uk7iAfevP7CkBfSN8W
+r6AL284qtw==
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/framework/example/controller.dart b/framework/example/controller.dart
new file mode 100644
index 00000000..1e0da928
--- /dev/null
+++ b/framework/example/controller.dart
@@ -0,0 +1,59 @@
+import 'package:angel_container/mirrors.dart';
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_framework/http.dart';
+import 'package:logging/logging.dart';
+
+main() async {
+ // Logging set up/boilerplate
+ Logger.root.onRecord.listen(print);
+
+ // Create our server.
+ var app = Angel(logger: Logger('angel'), reflector: MirrorsReflector());
+ var http = AngelHttp(app);
+
+ await app.mountController();
+
+ // Simple fallback to throw a 404 on unknown paths.
+ app.fallback((req, res) {
+ throw AngelHttpException.notFound(
+ message: 'Unknown path: "${req.uri.path}"',
+ );
+ });
+
+ app.errorHandler = (e, req, res) => e.toJson();
+
+ await http.startServer('127.0.0.1', 3000);
+ print('Listening at ${http.uri}');
+ app.dumpTree();
+}
+
+class ArtistsController extends Controller {
+ List index() {
+ return ['Elvis', 'Stevie', 'Van Gogh'];
+ }
+
+ String getById(int id, RequestContext req) {
+ return 'You fetched ID: $id from IP: ${req.ip}';
+ }
+
+ @Expose.post
+ form(RequestContext req) async {
+ // Deserialize the body into an artist.
+ var artist = await req.deserializeBody((m) {
+ return Artist(name: m['name'] as String ?? '(unknown name)');
+ });
+
+ // Return it (it will be serialized to JSON).
+ return artist;
+ }
+}
+
+class Artist {
+ final String name;
+
+ Artist({this.name});
+
+ Map toJson() {
+ return {'name': name};
+ }
+}
diff --git a/framework/example/handle_error.dart b/framework/example/handle_error.dart
new file mode 100644
index 00000000..e159a3ce
--- /dev/null
+++ b/framework/example/handle_error.dart
@@ -0,0 +1,25 @@
+import 'dart:async';
+import 'dart:io';
+import 'package:angel_container/mirrors.dart';
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_framework/http.dart';
+import 'package:logging/logging.dart';
+
+main() async {
+ var app = Angel(reflector: MirrorsReflector())
+ ..logger = (Logger('angel')
+ ..onRecord.listen((rec) {
+ print(rec);
+ if (rec.error != null) print(rec.error);
+ if (rec.stackTrace != null) print(rec.stackTrace);
+ }))
+ ..encoders.addAll({'gzip': gzip.encoder});
+
+ app.fallback(
+ (req, res) => Future.error('Throwing just because I feel like!'));
+
+ var http = AngelHttp(app);
+ var server = await http.startServer('127.0.0.1', 3000);
+ var url = 'http://${server.address.address}:${server.port}';
+ print('Listening at $url');
+}
diff --git a/framework/example/hostname.dart b/framework/example/hostname.dart
new file mode 100644
index 00000000..80628642
--- /dev/null
+++ b/framework/example/hostname.dart
@@ -0,0 +1,47 @@
+import 'dart:async';
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_framework/http.dart';
+import 'package:logging/logging.dart';
+import 'package:pretty_logging/pretty_logging.dart';
+
+Future apiConfigurer(Angel app) async {
+ app.get('/', (req, res) => 'Hello, API!');
+ app.fallback((req, res) {
+ return 'fallback on ${req.uri} (within the API)';
+ });
+}
+
+Future frontendConfigurer(Angel app) async {
+ app.fallback((req, res) => '(usually an index page would be shown here.)');
+}
+
+main() async {
+ // Logging set up/boilerplate
+ hierarchicalLoggingEnabled = true;
+ Logger.root.onRecord.listen(prettyLog);
+
+ var app = Angel(logger: Logger('angel'));
+ var http = AngelHttp(app);
+ var multiHost = HostnameRouter.configure({
+ 'api.localhost:3000': apiConfigurer,
+ 'localhost:3000': frontendConfigurer,
+ });
+
+ app
+ ..fallback(multiHost.handleRequest)
+ ..fallback((req, res) {
+ res.write('Uncaught hostname: ${req.hostname}');
+ });
+
+ app.errorHandler = (e, req, res) {
+ print(e.message ?? e.error ?? e);
+ print(e.stackTrace);
+ return e.toJson();
+ };
+
+ await http.startServer('127.0.0.1', 3000);
+ print('Listening at ${http.uri}');
+ print('See what happens when you visit http://localhost:3000 instead '
+ 'of http://127.0.0.1:3000. Then, try '
+ 'http://api.localhost:3000.');
+}
diff --git a/framework/example/http2/body_parsing.dart b/framework/example/http2/body_parsing.dart
new file mode 100644
index 00000000..a0c27a9c
--- /dev/null
+++ b/framework/example/http2/body_parsing.dart
@@ -0,0 +1,46 @@
+import 'dart:io';
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_framework/http.dart';
+import 'package:angel_framework/http2.dart';
+import 'package:file/local.dart';
+import 'package:logging/logging.dart';
+
+main() async {
+ var app = Angel();
+ app.logger = Logger('angel')
+ ..onRecord.listen((rec) {
+ print(rec);
+ if (rec.error != null) print(rec.error);
+ if (rec.stackTrace != null) print(rec.stackTrace);
+ });
+
+ var publicDir = Directory('example/public');
+ var indexHtml =
+ const LocalFileSystem().file(publicDir.uri.resolve('body_parsing.html'));
+
+ app.get('/', (req, res) => res.streamFile(indexHtml));
+
+ app.post('/', (req, res) => req.parseBody().then((_) => req.bodyAsMap));
+
+ var ctx = SecurityContext()
+ ..useCertificateChain('dev.pem')
+ ..usePrivateKey('dev.key', password: 'dartdart');
+
+ try {
+ ctx.setAlpnProtocols(['h2'], true);
+ } catch (e, st) {
+ app.logger.severe(
+ 'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.',
+ e,
+ st);
+ }
+
+ var http1 = AngelHttp(app);
+ var http2 = AngelHttp2(app, ctx);
+
+ // HTTP/1.x requests will fallback to `AngelHttp`
+ http2.onHttp1.listen(http1.handleRequest);
+
+ var server = await http2.startServer('127.0.0.1', 3000);
+ print('Listening at https://${server.address.address}:${server.port}');
+}
diff --git a/framework/example/http2/common.dart b/framework/example/http2/common.dart
new file mode 100644
index 00000000..b937b43f
--- /dev/null
+++ b/framework/example/http2/common.dart
@@ -0,0 +1,7 @@
+import 'package:logging/logging.dart';
+
+void dumpError(LogRecord rec) {
+ print(rec);
+ if (rec.error != null) print(rec.error);
+ if (rec.stackTrace != null) print(rec.stackTrace);
+}
diff --git a/framework/example/http2/dev.key b/framework/example/http2/dev.key
new file mode 100644
index 00000000..5d49ae7e
--- /dev/null
+++ b/framework/example/http2/dev.key
@@ -0,0 +1,29 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIE5DAcBgoqhkiG9w0BDAEBMA4ECL7L6rj6uEHGAgIIAASCBMLbucyfqAkgCbhP
+xNSHYllPMAv/dsIjtnsBwepCXPGkCBCuOAw/2FaCHjN9hBqL5V7fkrKeaemhm2YE
+ycPtlHJYPDf3kEkyMjdZ9rIY6kePGfQizs2uJPcXj4YPyQ4HsfVXpOicKfQrouf5
+Mze9bGzeMN065q3iP4dYUMwHAyZYteXCsanQNHlqvsWli0W+H8St8fdsXefZhnv1
+qVatKWdNdWQ9t5MuljgNU2Vv56sHKEYXI0yLxk2QUMk8KlJfnmt8foYUsnPUXHmc
+gIjLKwwVkpdololnEHSNu0cEOUPowjgJru+uMpn7vdNl7TPEQ9jbEgdNg4JwoYzU
+0nao8WzjaSp7kzvZz0VFwKnk5AjstGvvuAWckADdq23QElbn/mF7AG1m/TBpYxzF
+gTt37UdndS/AcvVznWVVrRP5iTSIawdIwvqI4s7rqsoE0GCcak+RhchgAz2gWKkS
+oODUo0JL6pPVbJ3l4ebbaO6c99nDVc8dViPtc1EkStJEJ2O4kI4xgLSCr4Y9ahKn
+oAaoSkX7Xxq3aQm+BzqSpLjdGL8atsqR/YVOIHYIl3gThvP0NfZGx1xHyvO5mCdZ
+kHxSA7tKWxauZ3eQ2clbnzeRsl4El0WMHy/5K1ovene4v7sunmoXVtghBC8hK6eh
+zMO9orex2PNQ/VQC7HCvtytunOVx1lkSBoNo7hR70igg6rW9H7UyoAoBOwMpT1xa
+J6V62nqruTKOqFNfur7aHJGpHGtDb5/ickHeYCyPTvmGp67u4wChzKReeg02oECe
+d1E5FKAcIa8s9TVOB6Z+HvTRNQZu2PsI6TJnjQRowvY9DAHiWTlJZBBY/pko3hxX
+TsIeybpvRdEHpDWv86/iqtw1hv9CUxS/8ZTWUgBo+osShHW79FeDASr9FC4/Zn76
+ZDERTgV4YWlW/klVWcG2lFo7jix+OPXAB+ZQavLhlN1xdWBcIz1AUWjAM4hdPylW
+HCX4PB9CQIPl2E7F+Y2p6nMcMWSJVBi5UIH7E9LfaBguXSzMmTk2Fw5p1aOQ6wfN
+goVAMVwi8ppAVs741PfHdZ295xMmK/1LCxz5DeAdD/tsA/SYfT753GotioDuC7im
+EyJ5JyvTr5I6RFFBuqt3NlUb3Hp16wP3B2x9DZiB6jxr0l341/NHgsyeBXkuIy9j
+ON2mvpBPCJhS8kgWo3G0UyyKnx64tcgpGuSvZhGwPz843B6AbYyE6pMRfSWRMkMS
+YZYa+VNKhR4ixdj07ocFZEWLVjCH7kxkE8JZXKt8jKYmkWd0lS1QVjgaKlO6lRa3
+q6SPJkhW6pvqobvcqVNXwi1XuzpZeEbuh0B7OTekFTTxx5g9XeDl56M8SVQ1KEhT
+Q1t7H2Nba18WCB7cf+6PN0F0K0Jz1Kq7ZWaqEI/grX1m4RQuvNF5807sB/QKMO/Z
+Gz3NXvHg5xTJRd/567lxPGkor0cE7qD1EZfmJ2HrBYXQ91bhgA7LToBuMZo6ZRXH
+QfsanjbP4FPLMiGdQigLjj3A35L/f4sQOOVac/sRaFnm7pzcxsMvyVU/YtvGcjYE
+xaOOVnamg661Wo0wksXoDjeSz/JIyyKO3Gwp1FSm2wGLjjy/Ehmqcqy8rvHuf07w
+AUukhVtTNn4=
+-----END ENCRYPTED PRIVATE KEY-----
\ No newline at end of file
diff --git a/framework/example/http2/dev.pem b/framework/example/http2/dev.pem
new file mode 100644
index 00000000..01756b25
--- /dev/null
+++ b/framework/example/http2/dev.pem
@@ -0,0 +1,57 @@
+-----BEGIN CERTIFICATE-----
+MIIDKTCCAhGgAwIBAgIJAOWmjTS+OnTEMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV
+BAMMDGludGVybWVkaWF0ZTAeFw0xNTA1MTgwOTAwNDBaFw0yMzA4MDQwOTAwNDBa
+MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALlcwQJuzd+xH8QFgfJSn5tRlvhkldSX98cE7NiA602NBbnAVyUrkRXq
+Ni75lgt0kwjYfA9z674m8WSVbgpLPintPCla9CYky1TH0keIs8Rz6cGWHryWEHiu
+EDuljQynu2b3sAFuHu9nfWurbJwZnFakBKpdQ9m4EyOZCHC/jHYY7HacKSXg1Cki
+we2ca0BWDrcqy8kLy0dZ5oC6IZG8O8drAK8f3f44CRYw59D3sOKBrKXaabpvyEcb
+N7Wk2HDBVwHpUJo1reVwtbM8dhqQayYSD8oXnGpP3RQNu/e2rzlXRyq/BfcDY1JI
+7TbC4t/7/N4EcPSpGsTcSOC9A7FpzvECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglg
+hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O
+BBYEFCnwiEMMFZh7NhCr+qA8K0w4Q+AOMB8GA1UdIwQYMBaAFB0h1Evsaw2vfrmS
+YuoCTmC4EE6ZMA0GCSqGSIb3DQEBCwUAA4IBAQAcFmHMaXRxyoNaeOowQ6iQWoZd
+AUbvG7SHr7I6Pi2aqdqofsKWts7Ytm5WsS0M2nN+sW504houu0iCPeJJX8RQw2q4
+CCcNOs9IXk+2uMzlpocHpv+yYoUiD5DxgWh7eghQMLyMpf8FX3Gy4VazeuXznHOM
+4gE4L417xkDzYOzqVTp0FTyAPUv6G2euhNCD6TMru9REcRhYul+K9kocjA5tt2KG
+MH6y28LXbLyq4YJUxSUU9gY/xlnbbZS48KDqEcdYC9zjW9nQ0qS+XQuQuFIcwjJ5
+V4kAUYxDu6FoTpyQjgsrmBbZlKNxH7Nj4NDlcdJhp/zeSKHqWa5hSWjjKIxp
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAeqgAwIBAgIJAOWmjTS+OnTDMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
+BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw
+WjAXMRUwEwYDVQQDDAxpbnRlcm1lZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDSrAO1CoPvUllgLOzDm5nG0skDF7vh1DUgAIDVGz0ecD0JFbQx
+EF79pju/6MbtpTW2FYvRp11t/G7rGtX923ybOHY/1MNFQrdIvPlO1VV7IGKjoMwP
+DNeb0fIGjHoE9QxaDxR8NX8xQbItpsw+TUtRfc9SLkR+jaYJfVRoM21BOncZbSHE
+YKiZlEbpecB/+EtwVpgvl+8mPD5U07Fi4fp/lza3WXInXQPyiTVllIEJCt4PKmlu
+MocNaJOW38bysL7i0PzDpVZtOxLHOTaW68yF3FckIHNCaA7k1ABEEEegjFMmIao7
+B9w7A0jvr4jZVvNmui5Djjn+oJxwEVVgyf8LAgMBAAGjUDBOMB0GA1UdDgQWBBQd
+IdRL7GsNr365kmLqAk5guBBOmTAfBgNVHSMEGDAWgBRk81s9d0ZbiZhh44KckwPb
+oTc0XzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBZQTK0plfdB5PC
+cC5icut4EmrByJa1RbU7ayuEE70e7hla6KVmVjVdCBGltI4jBYwfhKbRItHiAJ/8
+x+XZKBG8DLPFuDb7lAa1ObhAYF7YThUFPQYaBhfzKcWrdmWDBFpvNv6E0Mm364dZ
+e7Yxmbe5S4agkYPoxEzgEYmcUk9jbjdR6eTbs8laG169ljrECXfEU9RiAcqz5iSX
+NLSewqB47hn3B9qgKcQn+PsgO2j7M+rfklhNgeGJeWmy7j6clSOuCsIjWHU0RLQ4
+0W3SB/rpEAJ7fgQbYUPTIUNALSOWi/o1tDX2mXPRjBoxqAv7I+vYk1lZPmSzkyRh
+FKvRDxsW
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAzCCAeugAwIBAgIJAJ0MomS4Ck+8MA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
+BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw
+WjAYMRYwFAYDVQQDDA1yb290YXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAts1ijtBV92S2cOvpUMOSTp9c6A34nIGr0T5Nhz6XiqRVT+gv
+dQgmkdKJQjbvR60y6jzltYFsI2MpGVXY8h/oAL81D/k7PDB2aREgyBfTPAhBHyGw
+siR+2xYt5b/Zs99q5RdRqQNzNpLPJriIKvUsRyQWy1UiG2s7pRXQeA8qB0XtJdCj
+kFIi+G2bDsaffspGeDOCqt7t+yqvRXfSES0c/l7DIHaiMbbp4//ZNML3RNgAjPz2
+hCezZ+wOYajOIyoSPK8IgICrhYFYxvgWxwbLDBEfC5B3jOQsySe10GoRAKZz1gBV
+DmgReu81tYJmdgkc9zknnQtIFdA0ex+GvZlfWQIDAQABo1AwTjAdBgNVHQ4EFgQU
+ZPNbPXdGW4mYYeOCnJMD26E3NF8wHwYDVR0jBBgwFoAUZPNbPXdGW4mYYeOCnJMD
+26E3NF8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATzkZ97K777uZ
+lQcduNX3ey4IbCiEzFA2zO5Blj+ilfIwNbZXNOgm/lqNvVGDYs6J1apJJe30vL3X
+J+t2zsZWzzQzb9uIU37zYemt6m0fHrSrx/iy5lGNqt3HMfqEcOqSCOIK3PCTMz2/
+uyGe1iw33PVeWsm1JUybQ9IrU/huJjbgOHU4wab+8SJCM49ipArp68Fr6j4lcEaE
+4rfRg1ZsvxiOyUB3qPn6wyL/JB8kOJ+QCBe498376eaem8AEFk0kQRh6hDaWtq/k
+t6IIXQLjx+EBDVP/veK0UnVhKRP8YTOoV8ZiG1NcdlJmX/Uk7iAfevP7CkBfSN8W
+r6AL284qtw==
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/framework/example/http2/main.dart b/framework/example/http2/main.dart
new file mode 100644
index 00000000..d8218ae2
--- /dev/null
+++ b/framework/example/http2/main.dart
@@ -0,0 +1,43 @@
+import 'dart:io';
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_framework/http.dart';
+import 'package:angel_framework/http2.dart';
+import 'package:logging/logging.dart';
+import 'common.dart';
+
+main() async {
+ var app = Angel()
+ ..encoders.addAll({
+ 'gzip': gzip.encoder,
+ 'deflate': zlib.encoder,
+ });
+ app.logger = Logger('angel')..onRecord.listen(dumpError);
+
+ app.get('/', (req, res) => 'Hello HTTP/2!!!');
+
+ app.fallback((req, res) => throw AngelHttpException.notFound(
+ message: 'No file exists at ${req.uri}'));
+
+ var ctx = SecurityContext()
+ ..useCertificateChain('dev.pem')
+ ..usePrivateKey('dev.key', password: 'dartdart');
+
+ try {
+ ctx.setAlpnProtocols(['h2'], true);
+ } catch (e, st) {
+ app.logger.severe(
+ 'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.',
+ e,
+ st,
+ );
+ }
+
+ var http1 = AngelHttp(app);
+ var http2 = AngelHttp2(app, ctx);
+
+ // HTTP/1.x requests will fallback to `AngelHttp`
+ http2.onHttp1.listen(http1.handleRequest);
+
+ await http2.startServer('127.0.0.1', 3000);
+ print('Listening at ${http2.uri}');
+}
diff --git a/framework/example/http2/pretty_logging.dart b/framework/example/http2/pretty_logging.dart
new file mode 100644
index 00000000..14f55e3f
--- /dev/null
+++ b/framework/example/http2/pretty_logging.dart
@@ -0,0 +1,9 @@
+import 'package:logging/logging.dart';
+
+/// Prints the contents of a [LogRecord] with pretty colors.
+void prettyLog(LogRecord record) {
+ print(record.toString());
+
+ if (record.error != null) print(record.error.toString());
+ if (record.stackTrace != null) print(record.stackTrace.toString());
+}
diff --git a/framework/example/http2/public/app.js b/framework/example/http2/public/app.js
new file mode 100644
index 00000000..036c6dc3
--- /dev/null
+++ b/framework/example/http2/public/app.js
@@ -0,0 +1,27 @@
+window.onload = function() {
+ var $app = document.getElementById('app');
+ var $loading = document.getElementById('loading');
+ $app.removeChild($loading);
+ var $button = document.createElement('button');
+ var $h1 = document.createElement('h1');
+ $app.appendChild($h1);
+ $app.appendChild($button);
+
+ $h1.textContent = '~Angel HTTP/2 server push~';
+
+ $button.textContent = 'Change color';
+ $button.onclick = function() {
+ var color = Math.floor(Math.random() * 0xffffff);
+ $h1.style.color = '#' + color.toString(16);
+ };
+
+ $button.onclick();
+
+ window.setInterval($button.onclick, 2000);
+
+ var rotation = 0;
+ window.setInterval(function() {
+ rotation += .6;
+ $button.style.transform = 'rotate(' + rotation + 'deg)';
+ }, 10);
+};
\ No newline at end of file
diff --git a/framework/example/http2/public/body_parsing.html b/framework/example/http2/public/body_parsing.html
new file mode 100644
index 00000000..941d21c1
--- /dev/null
+++ b/framework/example/http2/public/body_parsing.html
@@ -0,0 +1,21 @@
+
+
+
+
+ Angel HTTP/2
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/example/http2/public/index.html b/framework/example/http2/public/index.html
new file mode 100644
index 00000000..f0fe7160
--- /dev/null
+++ b/framework/example/http2/public/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Angel HTTP/2
+
+
+
+
Loading...
+
+
+
\ No newline at end of file
diff --git a/framework/example/http2/public/style.css b/framework/example/http2/public/style.css
new file mode 100644
index 00000000..e4348132
--- /dev/null
+++ b/framework/example/http2/public/style.css
@@ -0,0 +1,20 @@
+button {
+ margin-top: 2em;
+}
+
+html, body {
+ background-color: #000;
+}
+
+#app {
+ text-align: center;
+}
+
+#app h1 {
+ font-style: italic;
+ text-decoration: underline;
+}
+
+#loading {
+ color: red;
+}
\ No newline at end of file
diff --git a/framework/example/http2/server_push.dart b/framework/example/http2/server_push.dart
new file mode 100644
index 00000000..41a1170f
--- /dev/null
+++ b/framework/example/http2/server_push.dart
@@ -0,0 +1,62 @@
+import 'dart:io';
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_framework/http.dart';
+import 'package:angel_framework/http2.dart';
+import 'package:file/local.dart';
+import 'package:logging/logging.dart';
+
+main() async {
+ var app = Angel();
+ app.logger = Logger('angel')
+ ..onRecord.listen((rec) {
+ print(rec);
+ if (rec.error != null) print(rec.error);
+ if (rec.stackTrace != null) print(rec.stackTrace);
+ });
+
+ var publicDir = Directory('example/http2/public');
+ var indexHtml =
+ const LocalFileSystem().file(publicDir.uri.resolve('index.html'));
+ var styleCss =
+ const LocalFileSystem().file(publicDir.uri.resolve('style.css'));
+ var appJs = const LocalFileSystem().file(publicDir.uri.resolve('app.js'));
+
+ // Send files when requested
+ app
+ ..get('/style.css', (req, res) => res.streamFile(styleCss))
+ ..get('/app.js', (req, res) => res.streamFile(appJs));
+
+ app.get('/', (req, res) async {
+ // Regardless of whether we pushed other resources, let's still send /index.html.
+ await res.streamFile(indexHtml);
+
+ // If the client is HTTP/2 and supports server push, let's
+ // send down /style.css and /app.js as well, to improve initial load time.
+ if (res is Http2ResponseContext && res.canPush) {
+ await res.push('/style.css').streamFile(styleCss);
+ await res.push('/app.js').streamFile(appJs);
+ }
+ });
+
+ var ctx = SecurityContext()
+ ..useCertificateChain('dev.pem')
+ ..usePrivateKey('dev.key', password: 'dartdart');
+
+ try {
+ ctx.setAlpnProtocols(['h2'], true);
+ } catch (e, st) {
+ app.logger.severe(
+ 'Cannot set ALPN protocol on server to `h2`. The server will only serve HTTP/1.x.',
+ e,
+ st);
+ }
+
+ var http1 = AngelHttp(app);
+ var http2 = AngelHttp2(app, ctx);
+
+ // HTTP/1.x requests will fallback to `AngelHttp`
+ http2.onHttp1.listen(http1.handleRequest);
+
+ var server = await http2.startServer('127.0.0.1', 3000);
+ print('Listening at https://${server.address.address}:${server.port}');
+}
diff --git a/framework/example/json.dart b/framework/example/json.dart
new file mode 100644
index 00000000..f71a48dc
--- /dev/null
+++ b/framework/example/json.dart
@@ -0,0 +1,53 @@
+import 'dart:async';
+import 'dart:io';
+import 'dart:isolate';
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_framework/http.dart';
+
+main() async {
+ int x = 0;
+ var c = Completer();
+ var exit = ReceivePort();
+ List isolates = [];
+
+ exit.listen((_) {
+ if (++x >= 50) {
+ c.complete();
+ }
+ });
+
+ for (int i = 1; i < Platform.numberOfProcessors; i++) {
+ var isolate = await Isolate.spawn(serverMain, null);
+ isolates.add(isolate);
+ print('Spawned isolate #${i + 1}...');
+
+ isolate.addOnExitListener(exit.sendPort);
+ }
+
+ serverMain(null);
+
+ print('Angel listening at http://localhost:3000');
+ await c.future;
+}
+
+serverMain(_) async {
+ var app = Angel();
+ var http =
+ AngelHttp.custom(app, startShared, useZone: false); // Run a cluster
+
+ app.get('/', (req, res) {
+ return res.serialize({
+ "foo": "bar",
+ "one": [2, "three"],
+ "bar": {"baz": "quux"}
+ });
+ });
+
+ app.errorHandler = (e, req, res) {
+ print(e.message ?? e.error ?? e);
+ print(e.stackTrace);
+ };
+
+ var server = await http.startServer('127.0.0.1', 3000);
+ print('Listening at http://${server.address.address}:${server.port}');
+}
diff --git a/framework/example/main.dart b/framework/example/main.dart
new file mode 100644
index 00000000..f500270b
--- /dev/null
+++ b/framework/example/main.dart
@@ -0,0 +1,59 @@
+import 'package:angel_container/mirrors.dart';
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_framework/http.dart';
+import 'package:logging/logging.dart';
+import 'package:pretty_logging/pretty_logging.dart';
+
+main() async {
+ // Logging set up/boilerplate
+ Logger.root.onRecord.listen(prettyLog);
+
+ // Create our server.
+ var app = Angel(
+ logger: Logger('angel'),
+ reflector: MirrorsReflector(),
+ );
+
+ // Index route. Returns JSON.
+ app.get('/', (req, res) => 'Welcome to Angel!');
+
+ // Accepts a URL like /greet/foo or /greet/bob.
+ app.get(
+ '/greet/:name',
+ (req, res) {
+ var name = req.params['name'];
+ res
+ ..write('Hello, $name!')
+ ..close();
+ },
+ );
+
+ // Pattern matching - only call this handler if the query value of `name` equals 'emoji'.
+ app.get(
+ '/greet',
+ ioc((@Query('name', match: 'emoji') String name) => '😇🔥🔥🔥'),
+ );
+
+ // Handle any other query value of `name`.
+ app.get(
+ '/greet',
+ ioc((@Query('name') String name) => 'Hello, $name!'),
+ );
+
+ // Simple fallback to throw a 404 on unknown paths.
+ app.fallback((req, res) {
+ throw AngelHttpException.notFound(
+ message: 'Unknown path: "${req.uri.path}"',
+ );
+ });
+
+ var http = AngelHttp(app);
+ var server = await http.startServer('127.0.0.1', 3000);
+ var url = 'http://${server.address.address}:${server.port}';
+ print('Listening at $url');
+ print('Visit these pages to see Angel in action:');
+ print('* $url/greet/bob');
+ print('* $url/greet/?name=emoji');
+ print('* $url/greet/?name=jack');
+ print('* $url/nonexistent_page');
+}
diff --git a/framework/example/map_service.dart b/framework/example/map_service.dart
new file mode 100644
index 00000000..4650dd09
--- /dev/null
+++ b/framework/example/map_service.dart
@@ -0,0 +1,22 @@
+import 'package:angel_container/mirrors.dart';
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_framework/http.dart';
+import 'package:logging/logging.dart';
+
+main() async {
+ // Logging set up/boilerplate
+ Logger.root.onRecord.listen(print);
+
+ // Create our server.
+ var app = Angel(
+ logger: Logger('angel'),
+ reflector: MirrorsReflector(),
+ );
+
+ // Create a RESTful service that manages an in-memory collection.
+ app.use('/api/todos', MapService());
+
+ var http = AngelHttp(app);
+ await http.startServer('127.0.0.1', 0);
+ print('Listening at ${http.uri}');
+}
diff --git a/framework/example/status.dart b/framework/example/status.dart
new file mode 100644
index 00000000..a434dd90
--- /dev/null
+++ b/framework/example/status.dart
@@ -0,0 +1,14 @@
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_framework/http.dart';
+
+main() async {
+ var app = Angel();
+ var http = AngelHttp(app);
+
+ app.fallback((req, res) {
+ res.statusCode = 304;
+ });
+
+ await http.startServer('127.0.0.1', 3000);
+ print('Listening at ${http.uri}');
+}
diff --git a/framework/example/view.dart b/framework/example/view.dart
new file mode 100644
index 00000000..c90fd3cd
--- /dev/null
+++ b/framework/example/view.dart
@@ -0,0 +1,18 @@
+import 'package:angel_container/mirrors.dart';
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_framework/http.dart';
+
+main() async {
+ var app = Angel(reflector: MirrorsReflector());
+
+ app.viewGenerator = (name, [data]) async =>
+ 'View generator invoked with name $name and data: $data';
+
+ // Index route. Returns JSON.
+ app.get('/', (req, res) => res.render('index', {'foo': 'bar'}));
+
+ var http = AngelHttp(app);
+ var server = await http.startServer('127.0.0.1', 3000);
+ var url = 'http://${server.address.address}:${server.port}';
+ print('Listening at $url');
+}
diff --git a/framework/example/views/index.jl b/framework/example/views/index.jl
new file mode 100644
index 00000000..53ac639b
--- /dev/null
+++ b/framework/example/views/index.jl
@@ -0,0 +1,9 @@
+
+
+
+ Title
+
+
+
Hello!
+
+
\ No newline at end of file
diff --git a/framework/lib/angel_framework.dart b/framework/lib/angel_framework.dart
new file mode 100644
index 00000000..60a495e6
--- /dev/null
+++ b/framework/lib/angel_framework.dart
@@ -0,0 +1,7 @@
+/// An easily-extensible web server framework in Dart.
+library angel_framework;
+
+export 'package:angel_http_exception/angel_http_exception.dart';
+export 'package:angel_model/angel_model.dart';
+export 'package:angel_route/angel_route.dart';
+export 'src/core/core.dart';
diff --git a/framework/lib/http.dart b/framework/lib/http.dart
new file mode 100644
index 00000000..5f00efff
--- /dev/null
+++ b/framework/lib/http.dart
@@ -0,0 +1 @@
+export 'src/http/http.dart';
diff --git a/framework/lib/http2.dart b/framework/lib/http2.dart
new file mode 100644
index 00000000..e21891f2
--- /dev/null
+++ b/framework/lib/http2.dart
@@ -0,0 +1,3 @@
+export 'src/http2/angel_http2.dart';
+export 'src/http2/http2_request_context.dart';
+export 'src/http2/http2_response_context.dart';
diff --git a/framework/lib/src/core/anonymous_service.dart b/framework/lib/src/core/anonymous_service.dart
new file mode 100644
index 00000000..f4d86f72
--- /dev/null
+++ b/framework/lib/src/core/anonymous_service.dart
@@ -0,0 +1,59 @@
+import 'dart:async';
+import 'request_context.dart';
+import 'response_context.dart';
+import 'service.dart';
+
+/// An easy helper class to create one-off services without having to create an entire class.
+///
+/// Well-suited for testing.
+class AnonymousService extends Service {
+ FutureOr> Function([Map]) _index;
+ FutureOr Function(Id, [Map]) _read, _remove;
+ FutureOr Function(Data, [Map]) _create;
+ FutureOr Function(Id, Data, [Map]) _modify, _update;
+
+ AnonymousService(
+ {FutureOr> index([Map params]),
+ FutureOr read(Id id, [Map params]),
+ FutureOr create(Data data, [Map params]),
+ FutureOr modify(Id id, Data data, [Map params]),
+ FutureOr update(Id id, Data data, [Map params]),
+ FutureOr remove(Id id, [Map params]),
+ FutureOr Function(RequestContext, ResponseContext) readData})
+ : super(readData: readData) {
+ _index = index;
+ _read = read;
+ _create = create;
+ _modify = modify;
+ _update = update;
+ _remove = remove;
+ }
+
+ @override
+ index([Map params]) =>
+ Future.sync(() => _index != null ? _index(params) : super.index(params));
+
+ @override
+ read(Id id, [Map params]) => Future.sync(
+ () => _read != null ? _read(id, params) : super.read(id, params));
+
+ @override
+ create(Data data, [Map params]) => Future.sync(() =>
+ _create != null ? _create(data, params) : super.create(data, params));
+
+ @override
+ modify(Id id, Data data, [Map params]) =>
+ Future.sync(() => _modify != null
+ ? _modify(id, data, params)
+ : super.modify(id, data, params));
+
+ @override
+ update(Id id, Data data, [Map params]) =>
+ Future.sync(() => _update != null
+ ? _update(id, data, params)
+ : super.update(id, data, params));
+
+ @override
+ remove(Id id, [Map params]) => Future.sync(
+ () => _remove != null ? _remove(id, params) : super.remove(id, params));
+}
diff --git a/framework/lib/src/core/controller.dart b/framework/lib/src/core/controller.dart
new file mode 100644
index 00000000..e531b12b
--- /dev/null
+++ b/framework/lib/src/core/controller.dart
@@ -0,0 +1,233 @@
+library angel_framework.http.controller;
+
+import 'dart:async';
+import 'package:angel_container/angel_container.dart';
+import 'package:angel_route/angel_route.dart';
+import 'package:meta/meta.dart';
+import 'package:recase/recase.dart';
+import '../core/core.dart';
+
+/// Supports grouping routes with shared functionality.
+class Controller {
+ Angel _app;
+
+ /// The [Angel] application powering this controller.
+ Angel get app => _app;
+
+ /// If `true` (default), this class will inject itself as a singleton into the [app]'s container when bootstrapped.
+ final bool injectSingleton;
+
+ /// Middleware to run before all handlers in this class.
+ List middleware = [];
+
+ /// A mapping of route paths to routes, produced from the [Expose] annotations on this class.
+ Map routeMappings = {};
+
+ SymlinkRoute _mountPoint;
+
+ /// The route at which this controller is mounted on the server.
+ SymlinkRoute get mountPoint => _mountPoint;
+
+ Controller({this.injectSingleton = true});
+
+ /// Applies routes, DI, and other configuration to an [app].
+ @mustCallSuper
+ Future configureServer(Angel app) async {
+ _app = app;
+
+ if (injectSingleton != false) {
+ if (!app.container.has(runtimeType)) {
+ _app.container.registerSingleton(this, as: runtimeType);
+ }
+ }
+
+ var name = await applyRoutes(app, app.container.reflector);
+ app.controllers[name] = this;
+ return null;
+ }
+
+ /// Applies the routes from this [Controller] to some [router].
+ Future applyRoutes(
+ Router router, Reflector reflector) async {
+ // Load global expose decl
+ var classMirror = reflector.reflectClass(this.runtimeType);
+ Expose exposeDecl = findExpose(reflector);
+
+ if (exposeDecl == null) {
+ throw Exception("All controllers must carry an @Expose() declaration.");
+ }
+
+ var routable = Routable();
+ _mountPoint = router.mount(exposeDecl.path, routable);
+ var typeMirror = reflector.reflectType(this.runtimeType);
+
+ // Pre-reflect methods
+ var instanceMirror = reflector.reflectInstance(this);
+ final handlers = []
+ ..addAll(exposeDecl.middleware)
+ ..addAll(middleware);
+ final routeBuilder =
+ _routeBuilder(reflector, instanceMirror, routable, handlers);
+ await configureRoutes(routable);
+ classMirror.declarations.forEach(routeBuilder);
+
+ // Return the name.
+ return exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : typeMirror.name;
+ }
+
+ void Function(ReflectedDeclaration) _routeBuilder(
+ Reflector reflector,
+ ReflectedInstance instanceMirror,
+ Routable routable,
+ Iterable handlers) {
+ return (ReflectedDeclaration decl) {
+ var methodName = decl.name;
+
+ // Ignore built-in methods.
+ if (methodName != 'toString' &&
+ methodName != 'noSuchMethod' &&
+ methodName != 'call' &&
+ methodName != 'equals' &&
+ methodName != '==') {
+ var exposeDecl = decl.function.annotations
+ .map((m) => m.reflectee)
+ .firstWhere((r) => r is Expose, orElse: () => null) as Expose;
+
+ if (exposeDecl == null) {
+ // If this has a @noExpose, return null.
+ if (decl.function.annotations.any((m) => m.reflectee is NoExpose)) {
+ return;
+ } else {
+ // Otherwise, create an @Expose.
+ exposeDecl = Expose(null);
+ }
+ }
+
+ var reflectedMethod =
+ instanceMirror.getField(methodName).reflectee as Function;
+ var middleware = []
+ ..addAll(handlers)
+ ..addAll(exposeDecl.middleware);
+ String name =
+ exposeDecl.as?.isNotEmpty == true ? exposeDecl.as : methodName;
+
+ // Check if normal
+ var method = decl.function;
+ if (method.parameters.length == 2 &&
+ method.parameters[0].type.reflectedType == RequestContext &&
+ method.parameters[1].type.reflectedType == ResponseContext) {
+ // Create a regular route
+ routeMappings[name] = routable
+ .addRoute(exposeDecl.method, exposeDecl.path,
+ (RequestContext req, ResponseContext res) {
+ var result = reflectedMethod(req, res);
+ return result is RequestHandler ? result(req, res) : result;
+ }, middleware: middleware);
+ return;
+ }
+
+ var injection = preInject(reflectedMethod, reflector);
+
+ if (exposeDecl?.allowNull?.isNotEmpty == true) {
+ injection.optional?.addAll(exposeDecl.allowNull);
+ }
+
+ // If there is no path, reverse-engineer one.
+ var path = exposeDecl.path;
+ var httpMethod = exposeDecl.method ?? 'GET';
+ if (path == null) {
+ // Try to build a route path by finding all potential
+ // path segments, and then joining them.
+ var parts = [];
+
+ // If the name starts with get/post/patch, etc., then that
+ // should be the path.
+ var methodMatch = _methods.firstMatch(method.name);
+ if (methodMatch != null) {
+ var rest = method.name.replaceAll(_methods, '');
+ var restPath = ReCase(rest.isEmpty ? 'index' : rest)
+ .snakeCase
+ .replaceAll(_rgxMultipleUnderscores, '_');
+ httpMethod = methodMatch[1].toUpperCase();
+
+ if (['index', 'by_id'].contains(restPath)) {
+ parts.add('/');
+ } else {
+ parts.add(restPath);
+ }
+ }
+ // If the name does NOT start with get/post/patch, etc. then
+ // snake_case-ify the name, and add it to the list of segments.
+ // If the name is index, though, add "/".
+ else {
+ if (method.name == 'index') {
+ parts.add('/');
+ } else {
+ parts.add(ReCase(method.name)
+ .snakeCase
+ .replaceAll(_rgxMultipleUnderscores, '_'));
+ }
+ }
+
+ // Try to infer String, int, or double. We called
+ // preInject() earlier, so we can figure out the types
+ // of required parameters, and add those to the path.
+ for (var p in injection.required) {
+ if (p is List && p.length == 2 && p[0] is String && p[1] is Type) {
+ var name = p[0] as String;
+ var type = p[1] as Type;
+ if (type == String) {
+ parts.add(':$name');
+ } else if (type == int) {
+ parts.add('int:$name');
+ } else if (type == double) {
+ parts.add('double:$name');
+ }
+ }
+ }
+
+ path = parts.join('/');
+ if (!path.startsWith('/')) path = '/$path';
+ }
+
+ routeMappings[name] = routable.addRoute(
+ httpMethod, path, handleContained(reflectedMethod, injection),
+ middleware: middleware);
+ }
+ };
+ }
+
+ /// Used to add additional routes or middlewares to the router from within
+ /// a [Controller].
+ ///
+ /// ```dart
+ /// @override
+ /// FutureOr configureRoutes(Routable routable) {
+ /// routable.all('*', myMiddleware);
+ /// }
+ /// ```
+ FutureOr configureRoutes(Routable routable) {}
+
+ static final RegExp _methods = RegExp(r'^(get|post|patch|delete)');
+ static final RegExp _rgxMultipleUnderscores = RegExp(r'__+');
+
+ /// Finds the [Expose] declaration for this class.
+ ///
+ /// If [concreteOnly] is `false`, then if there is no actual
+ /// [Expose], one will be automatically created.
+ Expose findExpose(Reflector reflector, {bool concreteOnly = false}) {
+ var existing = reflector
+ .reflectClass(runtimeType)
+ .annotations
+ .map((m) => m.reflectee)
+ .firstWhere((r) => r is Expose, orElse: () => null) as Expose;
+ return existing ??
+ (concreteOnly
+ ? null
+ : Expose(ReCase(runtimeType.toString())
+ .snakeCase
+ .replaceAll('_controller', '')
+ .replaceAll('_ctrl', '')
+ .replaceAll(_rgxMultipleUnderscores, '_')));
+ }
+}
diff --git a/framework/lib/src/core/core.dart b/framework/lib/src/core/core.dart
new file mode 100644
index 00000000..a7a0da4f
--- /dev/null
+++ b/framework/lib/src/core/core.dart
@@ -0,0 +1,14 @@
+export 'anonymous_service.dart';
+export 'controller.dart';
+export 'driver.dart';
+export 'env.dart';
+export 'hooked_service.dart';
+export 'hostname_parser.dart';
+export 'hostname_router.dart';
+export 'map_service.dart';
+export 'metadata.dart';
+export 'request_context.dart';
+export 'response_context.dart';
+export 'routable.dart';
+export 'server.dart';
+export 'service.dart';
diff --git a/framework/lib/src/core/driver.dart b/framework/lib/src/core/driver.dart
new file mode 100644
index 00000000..b15df8e5
--- /dev/null
+++ b/framework/lib/src/core/driver.dart
@@ -0,0 +1,372 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io' show stderr, Cookie;
+import 'package:angel_http_exception/angel_http_exception.dart';
+import 'package:angel_route/angel_route.dart';
+import 'package:combinator/combinator.dart';
+import 'package:stack_trace/stack_trace.dart';
+import 'package:tuple/tuple.dart';
+import 'core.dart';
+
+/// Base driver class for Angel implementations.
+///
+/// Powers both AngelHttp and AngelHttp2.
+abstract class Driver<
+ Request,
+ Response,
+ Server extends Stream,
+ RequestContextType extends RequestContext,
+ ResponseContextType extends ResponseContext> {
+ final Angel app;
+ final bool useZone;
+ bool _closed = false;
+ Server _server;
+ StreamSubscription _sub;
+
+ /// The function used to bind this instance to a server..
+ final Future Function(dynamic, int) serverGenerator;
+
+ Driver(this.app, this.serverGenerator, {this.useZone = true});
+
+ /// The path at which this server is listening for requests.
+ Uri get uri;
+
+ /// The native server running this instance.
+ Server get server => _server;
+
+ Future generateServer(address, int port) =>
+ serverGenerator(address, port);
+
+ /// Starts, and returns the server.
+ Future startServer([address, int port]) {
+ var host = address ?? '127.0.0.1';
+ return generateServer(host, port ?? 0).then((server) {
+ _server = server;
+ return Future.wait(app.startupHooks.map(app.configure)).then((_) {
+ app.optimizeForProduction();
+ _sub = server.listen((request) {
+ var stream = createResponseStreamFromRawRequest(request);
+ stream.listen((response) {
+ return handleRawRequest(request, response);
+ });
+ });
+ return _server;
+ });
+ });
+ }
+
+ /// Shuts down the underlying server.
+ Future close() {
+ if (_closed) return Future.value(_server);
+ _closed = true;
+ _sub?.cancel();
+ return app.close().then((_) =>
+ Future.wait(app.shutdownHooks.map(app.configure)).then((_) => _server));
+ }
+
+ Future createRequestContext(
+ Request request, Response response);
+
+ Future createResponseContext(
+ Request request, Response response,
+ [RequestContextType correspondingRequest]);
+
+ void setHeader(Response response, String key, String value);
+
+ void setContentLength(Response response, int length);
+
+ void setChunkedEncoding(Response response, bool value);
+
+ void setStatusCode(Response response, int value);
+
+ void addCookies(Response response, Iterable cookies);
+
+ void writeStringToResponse(Response response, String value);
+
+ void writeToResponse(Response response, List data);
+
+ Future closeResponse(Response response);
+
+ Stream createResponseStreamFromRawRequest(Request request);
+
+ /// Handles a single request.
+ Future handleRawRequest(Request request, Response response) {
+ return createRequestContext(request, response).then((req) {
+ return createResponseContext(request, response, req).then((res) {
+ handle() {
+ var path = req.path;
+ if (path == '/') path = '';
+
+ Tuple4, ParseResult,
+ MiddlewarePipeline> resolveTuple() {
+ var r = app.optimizedRouter;
+ var resolved =
+ r.resolveAbsolute(path, method: req.method, strip: false);
+ var pipeline = MiddlewarePipeline(resolved);
+ return Tuple4(
+ pipeline.handlers,
+ resolved.fold