This commit is contained in:
thosakwe 2017-03-31 21:00:24 -04:00
parent 218465a26f
commit 2f5935d5ad
8 changed files with 127 additions and 38 deletions

View file

@ -1 +1,2 @@
language: dart language: dart
script: ./tool/travis.sh

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
// Place your settings in this file to overwrite default and user settings.
{
}

View file

@ -1,9 +1,9 @@
# angel_framework # angel_framework
[![pub 1.0.0-dev.70](https://img.shields.io/badge/pub-1.0.0--dev.70-red.svg)](https://pub.dartlang.org/packages/angel_framework) [![pub 1.0.0-dev.71](https://img.shields.io/badge/pub-1.0.0--dev.71-red.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) [![build status](https://travis-ci.org/angel-dart/framework.svg)](https://travis-ci.org/angel-dart/framework)
Core libraries for the Angel Framework. A high-powered HTTP server with support for dependency injection, sophisticated routing and more.
```dart ```dart
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';

View file

@ -1,6 +1,9 @@
/// Various libraries useful for creating highly-extensible servers. /// Various libraries useful for creating highly-extensible servers.
library angel_framework.http; library angel_framework.http;
import 'dart:async';
import 'dart:io';
import 'server.dart' show ServerGenerator;
export 'package:angel_route/angel_route.dart'; export 'package:angel_route/angel_route.dart';
export 'package:body_parser/body_parser.dart' show FileUploadInfo; export 'package:body_parser/body_parser.dart' show FileUploadInfo;
export 'angel_base.dart'; export 'angel_base.dart';
@ -21,3 +24,13 @@ export 'server.dart';
export 'service.dart'; export 'service.dart';
export 'typed_service.dart'; export 'typed_service.dart';
/// Boots a shared server instance. Use this if launching multiple isolates
Future<HttpServer> startShared(InternetAddress address, int port) => HttpServer
.bind(address ?? InternetAddress.LOOPBACK_IP_V4, port ?? 0, shared: true);
/// Boots a secure shared server instance. Use this if launching multiple isolates
ServerGenerator startSharedSecure(SecurityContext securityContext) {
return (InternetAddress address, int port) => HttpServer.bindSecure(
address ?? InternetAddress.LOOPBACK_IP_V4, port ?? 0, securityContext,
shared: true);
}

View file

@ -4,6 +4,9 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:mirrors'; import 'dart:mirrors';
import 'package:angel_route/angel_route.dart'; import 'package:angel_route/angel_route.dart';
export 'package:container/container.dart';
import 'package:flatten/flatten.dart';
import 'package:json_god/json_god.dart' as god;
import 'angel_base.dart'; import 'angel_base.dart';
import 'angel_http_exception.dart'; import 'angel_http_exception.dart';
import 'controller.dart'; import 'controller.dart';
@ -12,7 +15,6 @@ import 'request_context.dart';
import 'response_context.dart'; import 'response_context.dart';
import 'routable.dart'; import 'routable.dart';
import 'service.dart'; import 'service.dart';
export 'package:container/container.dart';
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
@ -37,10 +39,14 @@ class Angel extends AngelBase {
StreamController<Controller> _onController = StreamController<Controller> _onController =
new StreamController<Controller>.broadcast(); new StreamController<Controller>.broadcast();
final List<Angel> _children = []; final List<Angel> _children = [];
Router _flattened;
bool _isProduction;
Angel _parent; Angel _parent;
ServerGenerator _serverGenerator = HttpServer.bind; ServerGenerator _serverGenerator = HttpServer.bind;
final Map _injections = {};
final Map<dynamic, InjectionRequest> _preContained = {}; final Map<dynamic, InjectionRequest> _preContained = {};
ResponseSerializer _serializer;
/// Determines whether to allow HTTP request method overrides. /// Determines whether to allow HTTP request method overrides.
bool allowMethodOverrides = true; bool allowMethodOverrides = true;
@ -61,7 +67,15 @@ class Angel extends AngelBase {
/// ///
/// The criteria for this is the `ANGEL_ENV` environment variable being set to /// The criteria for this is the `ANGEL_ENV` environment variable being set to
/// `'production'`. /// `'production'`.
bool get isProduction => Platform.environment['ANGEL_ENV'] == 'production'; ///
/// This value is memoized the first time you call it, so do not change environment
/// configuration at runtime!
bool get isProduction {
if (_isProduction != null)
return _isProduction;
else
return _isProduction = Platform.environment['ANGEL_ENV'] == 'production';
}
/// The function used to bind this instance to an HTTP server. /// The function used to bind this instance to an HTTP server.
ServerGenerator get serverGenerator => _serverGenerator; ServerGenerator get serverGenerator => _serverGenerator;
@ -130,10 +144,22 @@ class Angel extends AngelBase {
await configure(configurer); await configure(configurer);
} }
preprocessRoutes(); optimizeForProduction();
return httpServer..listen(handleRequest); return httpServer..listen(handleRequest);
} }
@override
Route addRoute(String method, String path, Object handler,
{List middleware: const []}) {
if (_flattened != null) {
print(
'WARNING: You added a route ($method $path) to the router, after it has been optimized.');
print('This route will be ignored, and no requests will ever reach it.');
}
return super.addRoute(method, path, handler, middleware: middleware ?? []);
}
/// Loads some base dependencies into the service container. /// Loads some base dependencies into the service container.
void bootstrapContainer() { void bootstrapContainer() {
if (runtimeType != Angel) container.singleton(this, as: Angel); if (runtimeType != Angel) container.singleton(this, as: Angel);
@ -143,20 +169,45 @@ class Angel extends AngelBase {
container.singleton(this); container.singleton(this);
} }
@override
void dumpTree(
{callback(String tree),
String header: 'Dumping route tree:',
String tab: ' ',
bool showMatchers: false}) {
if (isProduction) {
if (_flattened == null) _flattened = flatten(this);
_flattened.dumpTree(
callback: callback,
header: header?.isNotEmpty == true
? header
: (isProduction
? 'Dumping flattened route tree:'
: 'Dumping route tree:'),
tab: tab ?? ' ',
showMatchers: showMatchers == true);
} else {
super.dumpTree(
callback: callback,
header: header?.isNotEmpty == true
? header
: (isProduction
? 'Dumping flattened route tree:'
: 'Dumping route tree:'),
tab: tab ?? ' ',
showMatchers: showMatchers == true);
}
}
/// Shortcut for adding a middleware to inject a key/value pair on every request. /// Shortcut for adding a middleware to inject a key/value pair on every request.
void inject(key, value) { void inject(key, value) {
before.add((RequestContext req, ResponseContext res) async { _injections[key] = value;
req.inject(key, value);
return true;
});
} }
/// Shortcut for adding a middleware to inject a serialize on every request. /// Shortcut for adding a middleware to inject a serialize on every request.
void injectSerializer(ResponseSerializer serializer) { void injectSerializer(ResponseSerializer serializer) {
before.add((RequestContext req, ResponseContext res) async { _serializer = serializer;
res.serializer = serializer;
return true;
});
} }
/// Runs some [handler]. Returns `true` if request execution should continue. /// Runs some [handler]. Returns `true` if request execution should continue.
@ -227,11 +278,14 @@ class Angel extends AngelBase {
Future<RequestContext> createRequestContext(HttpRequest request) { Future<RequestContext> createRequestContext(HttpRequest request) {
_beforeProcessed.add(request); _beforeProcessed.add(request);
return RequestContext.from(request, this); return RequestContext.from(request, this).then((req) {
return req..injections.addAll(_injections ?? {});
});
} }
Future<ResponseContext> createResponseContext(HttpResponse response) async => Future<ResponseContext> createResponseContext(HttpResponse response) async =>
new ResponseContext(response, this); new ResponseContext(response, this)
..serializer = (_serializer ?? god.serialize);
/// Handles a single request. /// Handles a single request.
Future handleRequest(HttpRequest request) async { Future handleRequest(HttpRequest request) async {
@ -242,7 +296,10 @@ class Angel extends AngelBase {
if (requestedUrl.isEmpty) requestedUrl = '/'; if (requestedUrl.isEmpty) requestedUrl = '/';
var resolved = resolveAll(requestedUrl, requestedUrl, method: req.method); Router r =
isProduction ? (_flattened ?? (_flattened = flatten(this))) : this;
var resolved =
r.resolveAll(requestedUrl, requestedUrl, method: req.method);
for (var result in resolved) req.params.addAll(result.allParams); for (var result in resolved) req.params.addAll(result.allParams);
@ -317,29 +374,35 @@ class Angel extends AngelBase {
} }
} }
/// Preprocesses all routes, and eliminates the burden of reflecting handlers /// Runs several optimizations, *if* [isProduction] is `true`.
///
/// * Preprocesses all dependency injection, and eliminates the burden of reflecting handlers
/// at run-time. /// at run-time.
void preprocessRoutes() { /// * [flatten]s the route tree into a linear one.
_add(v) { void optimizeForProduction() {
if (v is Function && !_preContained.containsKey(v)) { if (isProduction == true) {
_preContained[v] = preInject(v); _add(v) {
} if (v is Function && !_preContained.containsKey(v)) {
} _preContained[v] = preInject(v);
}
void _walk(Router router) {
if (router is Angel) {
router..before.forEach(_add)..after.forEach(_add);
} }
router.requestMiddleware.forEach((k, v) => _add(v)); void _walk(Router router) {
router.middleware.forEach(_add); if (router is Angel) {
router.routes router..before.forEach(_add)..after.forEach(_add);
.where((r) => r is SymlinkRoute) }
.map((SymlinkRoute r) => r.router)
.forEach(_walk);
}
_walk(this); router.requestMiddleware.forEach((k, v) => _add(v));
router.middleware.forEach(_add);
router.routes
.where((r) => r is SymlinkRoute)
.map((SymlinkRoute r) => r.router)
.forEach(_walk);
}
_walk(_flattened = flatten(this));
print('Angel is running in production mode.');
}
} }
/// Run a function after injecting from service container. /// Run a function after injecting from service container.

View file

@ -1,6 +1,6 @@
name: angel_framework name: angel_framework
version: 1.0.0-dev.70 version: 1.0.0-dev.71
description: Core libraries for the Angel framework. description: A high-powered HTTP server with DI, routing and more.
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_framework homepage: https://github.com/angel-dart/angel_framework
environment: environment:
@ -9,6 +9,7 @@ dependencies:
angel_route: ^1.0.0-dev angel_route: ^1.0.0-dev
body_parser: ^1.0.0-dev body_parser: ^1.0.0-dev
container: ^0.1.2 container: ^0.1.2
flatten: ^1.0.0
json_god: ^2.0.0-beta json_god: ^2.0.0-beta
merge_map: ^1.0.0 merge_map: ^1.0.0
random_string: ^0.0.1 random_string: ^0.0.1

View file

@ -31,6 +31,11 @@ main() {
nested = new Angel(debug: debug); nested = new Angel(debug: debug);
todos = new Angel(debug: debug); todos = new Angel(debug: debug);
// Lazy-parse in production
[app, nested, todos].forEach((Angel app) {
app.lazyParseBodies = app.isProduction;
});
app app
..registerMiddleware('interceptor', (req, res) async { ..registerMiddleware('interceptor', (req, res) async {
res.write('Middleware'); res.write('Middleware');

3
tool/travis.sh Normal file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env bash
pub run test
ANGEL_ENV=production pub run test