Comments, multi-thread update, bye Mongo

This commit is contained in:
thosakwe 2017-06-10 11:24:36 -04:00
parent f78b30a522
commit c1b50e4584
16 changed files with 326 additions and 68 deletions

View file

@ -0,0 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Load-Balanced Server (PRODUCTION)" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
<option name="checkedMode" value="false" />
<option name="envs">
<entry key="ANGEL_ENV" value="production" />
</option>
<option name="filePath" value="$PROJECT_DIR$/bin/multi_server.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$" />
<method />
</configuration>
</component>

View file

@ -0,0 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Multithreaded Server (PRODUCTION)" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
<option name="checkedMode" value="false" />
<option name="envs">
<entry key="ANGEL_ENV" value="production" />
</option>
<option name="filePath" value="$PROJECT_DIR$/bin/scaled_server.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$" />
<method />
</configuration>
</component>

View file

@ -7,6 +7,5 @@ import 'common.dart';
import 'dart:isolate';
main(args, SendPort sendPort) async {
runZoned(startServer(args, clustered: true, sendPort: sendPort),
onError: onError);
runZoned(startServer(args, sendPort: sendPort), onError: onError);
}

View file

@ -5,7 +5,11 @@ import 'package:angel_diagnostics/angel_diagnostics.dart';
import 'package:angel_hot/angel_hot.dart';
import 'package:intl/intl.dart';
startServer(args, {bool clustered: false, SendPort sendPort}) {
/// Start a single instance of this application.
///
/// If a [sendPort] is provided, then the URL of the mounted server will be sent through the port.
/// Use this if you are starting multiple instances of your server.
startServer(args, {SendPort sendPort}) {
return () async {
var app = await createServer();
var dateFormat = new DateFormat("y-MM-dd");
@ -13,26 +17,36 @@ startServer(args, {bool clustered: false, SendPort sendPort}) {
InternetAddress host;
int port;
if (!clustered) {
host = new InternetAddress(app.properties['host']);
port = app.properties['port'];
} else {
host = InternetAddress.LOOPBACK_IP_V4;
port = 0;
}
// Load the right host and port from application config.
host = new InternetAddress(app.properties['host']);
// Listen on port 0 if we are using the load balancer.
port = sendPort != null ? 0 : app.properties['port'];
// Log requests and errors to a log file.
await app.configure(logRequests(logFile));
HttpServer server;
// Use `package:angel_hot` in any case, EXCEPT if starting in production mode.
//
// With hot-reloading, our server will automatically reload in-place on file changes,
// for a faster development cycle. :)
if (Platform.environment['ANGEL_ENV'] == 'production')
server = await app.startServer(host, port);
else {
var hot = new HotReloader(() async {
// If we are hot-reloading, we need to provide a callback
// to use to start a fresh instance on-the-fly.
var app = await createServer();
await app.configure(logRequests(logFile));
return app;
}, [new Directory('config'), new Directory('lib')]);
},
// Paths we might want to listen for changes on...
[
new Directory('config'),
new Directory('lib'),
new Directory('views')
]);
server = await hot.startServer(host, port);
}

View file

@ -2,7 +2,9 @@
/// This is intended to replace Nginx in your web stack.
/// Either use this or another reverse proxy, but there is no real
/// reason to use them together.
library angel.multiserver;
///
/// In most cases, you should run `scaled_server.dart` instead of this file.
library angel.multi_server;
import 'dart:io';
import 'package:angel_compress/angel_compress.dart';
@ -10,6 +12,10 @@ import 'package:angel_multiserver/angel_multiserver.dart';
final Uri cluster = Platform.script.resolve('cluster.dart');
/// The number of isolates to spawn. You might consider starting one instance
/// per processor core on your machine.
final int nInstances = Platform.numberOfProcessors;
main() async {
var app = new LoadBalancer();
// Or, for SSL:
@ -21,8 +27,8 @@ main() async {
// Cache static assets - just to lower response time
await app.configure(cacheResponses(filters: [new RegExp(r'images/\.*')]));
// Start up 5 instances of our main application
await app.spawnIsolates(cluster, count: 5);
// Start up multiple instances of our main application.
await app.spawnIsolates(cluster, count: nInstances);
app.onCrash.listen((_) async {
// Boot up a new instance on crash
@ -33,4 +39,5 @@ main() async {
var port = 3000;
var server = await app.startServer(host, port);
print('Listening at http://${server.address.address}:${server.port}');
print('Load-balancing $nInstances instance(s)');
}

67
bin/scaled_server.dart Normal file
View file

@ -0,0 +1,67 @@
#!/usr/bin/env dart
/// Most Angel applications will not need to use the load balancer.
/// Instead, you can start up a multi-threaded cluster.
library angel.scaled_server;
import 'dart:io';
import 'dart:isolate';
import 'package:angel_compress/angel_compress.dart';
import 'package:angel_multiserver/angel_multiserver.dart';
import 'package:angel/angel.dart';
/// The number of isolates to spawn. You might consider starting one instance
/// per processor core on your machine.
final int nInstances = Platform.numberOfProcessors;
main() {
var startupPort = new ReceivePort();
List<String> startupMessages = [];
// Start up multiple instances of our application.
for (int i = 0; i < nInstances; i++) {
Isolate.spawn(isolateMain, [i, startupPort.sendPort]);
}
int nStarted = 0;
// Listen for notifications of application startup...
startupPort.listen((String startupMessage) {
startupMessages.add(startupMessage);
if (++nStarted == nInstances) {
// Keep track of how many instances successfully started up,
// and print a success message when they all boot.
startupMessages.forEach(print);
print('Spawned $nInstances instance(s) of Angel.');
}
});
}
void isolateMain(List args) {
int instanceId = args[0];
SendPort startupPort = args[1];
createServer().then((app) async {
// Response compression via GZIP.
//
// See the documentation here:
// https://github.com/angel-dart/compress
app.responseFinalizers.add(gzip());
// Cache static assets - just to lower response time.
//
// See the documentation here:
// https://github.com/angel-dart/multiserver
//
// Here is an example of response caching:
// https://github.com/angel-dart/multiserver/blob/master/example/cache.dart
await app.configure(cacheResponses(filters: [new RegExp(r'images/\.*')]));
var server = await app.startServer(
InternetAddress.ANY_IP_V4, app.properties['port'] ?? 3000);
// Send a notification back to the main isolate
startupPort.send('Instance #$instanceId listening at http://${server.address.address}:${server.port}');
});
}

View file

@ -9,8 +9,13 @@ import 'src/services/services.dart' as services;
/// Creates and configures the server instance.
Future<Angel> createServer() async {
Angel app = new Angel();
/// Passing `startShared` to the constructor allows us to start multiple
/// instances of our application concurrently, listening on a single port.
///
/// This effectively lets us multi-thread the application.
var app = new Angel.custom(startShared);
/// Set up our application, using three plug-ins defined with this project.
await app.configure(configuration.configureServer);
await app.configure(services.configureServer);
await app.configure(routes.configureServer);

View file

@ -3,26 +3,27 @@ library angel.config;
import 'dart:io';
import 'package:angel_common/angel_common.dart';
// import 'package:angel_multiserver/angel_multiserver.dart';
import 'package:mongo_dart/mongo_dart.dart';
import 'plugins/plugins.dart' as plugins;
/// This is a perfect place to include configuration and load plug-ins.
configureServer(Angel app) async {
await app.configure(loadConfigurationFile());
var db = new Db(app.mongo_db);
await db.open();
app.container.singleton(db);
await app.configure(mustache(new Directory('views')));
await plugins.configureServer(app);
// Uncomment this to enable session synchronization across instances.
// This will add the overhead of querying a database at the beginning
// and end of every request. Thus, it should only be activated if necessary.
// Load configuration from the `config/` directory.
//
// For applications of scale, it is better to steer clear of session use
// entirely.
// await app.configure(new MongoSessionSynchronizer(db.collection('sessions')));
// See: https://github.com/angel-dart/configuration
await app.configure(loadConfigurationFile());
// Configure our application to render Mustache templates from the `views/` directory.
//
// See: https://github.com/angel-dart/mustache
await app.configure(mustache(new Directory('views')));
// Apply another plug-ins, i.e. ones that *you* have written.
//
// Typically, the plugins in `lib/src/config/plugins/plugins.dart` are plug-ins
// that add functionality specific to your application.
//
// If you write a plug-in that you plan to use again, or are
// using one created by the community, include it in
// `lib/src/config/config.dart`.
await plugins.configureServer(app);
}

View file

@ -2,6 +2,26 @@ library angel.models.user;
import 'package:angel_framework/common.dart';
// Model classes in Angel, as a convention, should extend the `Model`
// class.
//
// Angel doesn't box you into using a specific ORM. In fact, you might not
// need one at all.
//
// The out-of-the-box configuration for Angel is to assume
// all data you handle is a Map. You might consider leaving it this way, and just
// (de)serializing data when you need typing support.
//
// If you use a `TypedService`, then Angel will perform (de)serialization for you automatically:
// https://github.com/angel-dart/angel/wiki/TypedService
//
// You also have the option of using a source-generated serialization library.
// Consider `package:owl` (not affiliated with Angel):
// https://github.com/agilord/owl
//
// The `Model` class has no server-side dependency, and thus you can use it as-is, cross-platform.
// This is good for full-stack applications, as you do not have to maintain duplicate class files. ;)
class User extends Model {
@override
String id;

View file

@ -1,43 +1,92 @@
/// This app's route configuration.
library angel.routes;
import 'dart:io';
import 'package:angel_common/angel_common.dart';
import 'controllers/controllers.dart' as controllers;
/// Adds global middleware to the application.
///
/// Use these to apply functionality to requests before business logic is run.
///
/// More on the request lifecycle:
/// https://github.com/angel-dart/angel/wiki/Request-Lifecycle
configureBefore(Angel app) async {
app.before.add(cors());
}
/// Put your app routes here!
///
/// See the wiki for information about routing, requests, and responses:
/// * https://github.com/angel-dart/angel/wiki/Basic-Routing
/// * https://github.com/angel-dart/angel/wiki/Requests-&-Responses
configureRoutes(Angel app) async {
// Render `views/hello.mustache` when a user visits the application root.
app.get('/', (req, ResponseContext res) => res.render('hello'));
}
/// Configures fallback middleware.
///
/// Use these to run generic actions on requests that were not terminated by
/// earlier request handlers.
///
/// Note that these middleware might not always run.
///
/// More on the request lifecycle: https://github.com/angel-dart/angel/wiki/Request-Lifecycle
configureAfter(Angel app) async {
// Uncomment this to proxy over pub serve while in development:
// await app.configure(new PubServeLayer());
// Static server at /web or /build/web, depending on if in production
// Uncomment this to proxy over `pub serve` while in development.
// This is a useful feature for full-stack applications, especially if you
// are using Angular2.
//
// In production, `Cache-Control` headers will also be enabled.
// For documentation, see `package:angel_proxy`:
// https://github.com/angel-dart/proxy
//
// await app.configure(new PubServeLayer());
// Mount static server at /web or /build/web, depending on if
// you are running in production mode. `Cache-Control` headers will also be enabled.
//
// Read the following two sources for documentation:
// * https://medium.com/the-angel-framework/serving-static-files-with-the-angel-framework-2ddc7a2b84ae
// * https://github.com/angel-dart/static
await app.configure(new CachingVirtualDirectory());
// Set our application up to handle different errors.
//
// Read the following two sources for documentation:
// * https://github.com/angel-dart/angel/wiki/Error-Handling
// * https://github.com/angel-dart/errors
var errors = new ErrorHandler(handlers: {
// Display a 404 page if no resource is found.
404: (req, res) async =>
res.render('error', {'message': 'No file exists at ${req.path}.'}),
// On generic errors, give information about why the application failed.
//
// An `AngelHttpException` instance will be present in `req.properties`
// as `error`.
500: (req, res) async => res.render('error', {'message': req.error.message})
});
// Use a fatal error handler to attempt to resolve any issues that
// result in Angel not being able to send the user a response.
errors.fatalErrorHandler = (AngelFatalError e) async {
var req = await RequestContext.from(e.request, app);
var res = new ResponseContext(e.request.response, app);
res.render('error', {'message': 'Internal Server Error: ${e.error}'});
await app.sendResponse(e.request, req, res);
try {
// Manually create a request and response context.
var req = await RequestContext.from(e.request, app);
var res = new ResponseContext(e.request.response, app);
// *Attempt* to render an error page.
res.render('error', {'message': 'Internal Server Error: ${e.error}'});
await app.sendResponse(e.request, req, res);
} catch (_) {
// If execution fails here, there is nothing we can do.
stderr..writeln('Fatal error: ${e.error}')..writeln(e.stack);
}
};
// Throw a 404 if no route matched the request
app.after.add(errors.throwError());
// Throw a 404 if no route matched the request.
app.after.add(() => throw new AngelHttpException.notFound());
// Handle errors when they occur, based on outgoing status code.
// By default, requests will go through the 500 handler, unless
@ -45,19 +94,45 @@ configureAfter(Angel app) async {
// registered.
app.after.add(errors.middleware());
// Pass AngelHttpExceptions through handler as well
await app.configure(errors);
// Compress via GZIP
// Ideally you'll run this on a `multiserver` instance, but if not,
// feel free to knock yourself out!
// Pass AngelHttpExceptions through handler as well.
//
// app.responseFinalizers.add(gzip());
// Again, here is the error handling documentation:
// * https://github.com/angel-dart/angel/wiki/Error-Handling
// * https://github.com/angel-dart/errors
await app.configure(errors);
}
/// Adds response finalizers to the application.
///
/// These run after every request.
///
/// See more on the request lifecycle here:
/// https://github.com/angel-dart/angel/wiki/Request-Lifecycle
configureFinalizers(Angel app) async {}
/// Adds routes to our application.
///
/// See the wiki for information about routing, requests, and responses:
/// * https://github.com/angel-dart/angel/wiki/Basic-Routing
/// * https://github.com/angel-dart/angel/wiki/Requests-&-Responses
configureServer(Angel app) async {
// The order in which we run these plug-ins is relatively significant.
// Try not to change it.
// Add global middleware.
await configureBefore(app);
// Typically, you want to mount controllers first, after any global middleware.
await app.configure(controllers.configureServer);
// Next, you can add any supplemental routes.
await configureRoutes(app);
// Add handlers to run after requests are handled.
//
// See the request lifecycle docs to find out why these two
// are separate:
// https://github.com/angel-dart/angel/wiki/Request-Lifecycle
await configureAfter(app);
await configureFinalizers(app);
}

View file

@ -2,10 +2,16 @@
library angel.services;
import 'package:angel_common/angel_common.dart';
import 'package:mongo_dart/mongo_dart.dart';
import 'user.dart' as user;
/// Configure our application to use *services*.
/// Services must be wired to the app via `app.use`.
///
/// They provide many benefits, such as instant REST API generation,
/// and respond to both REST and WebSockets.
///
/// Read more here:
/// https://github.com/angel-dart/angel/wiki/Service-Basics
configureServer(Angel app) async {
Db db = app.container.make(Db);
await app.configure(user.configureServer(db));
await app.configure(user.configureServer());
}

View file

@ -1,16 +1,29 @@
import 'package:angel_common/angel_common.dart';
import 'package:angel_framework/hooks.dart' as hooks;
import 'package:crypto/crypto.dart' show sha256;
import 'package:mongo_dart/mongo_dart.dart';
import 'package:random_string/random_string.dart' as rs;
import '../models/user.dart';
import '../validators/user.dart';
export '../models/user.dart';
configureServer(Db db) {
/// Sets up a service mounted at `api/users`.
///
/// In the real world, you will want to hook this up to a database.
/// However, for the sake of the boilerplate, an in-memory service is used,
/// so that users are not tied into using just one database. :)
configureServer() {
return (Angel app) async {
app.use('/api/users',
new TypedService<User>(new MongoService(db.collection('users'))));
// A TypedService can be used to serialize and deserialize data to a class, somewhat like an ORM.
//
// See here: https://github.com/angel-dart/angel/wiki/TypedService
app.use('/api/users', new TypedService<User>(new MapService()));
// Configure hooks for the user service.
// Hooks can be used to add additional functionality, or change the behavior
// of services, and run on any service, regardless of which database you are using.
//
// If you have not already, *definitely* read the service hook documentation:
// https://github.com/angel-dart/angel/wiki/Hooks
var service = app.service('api/users') as HookedService;

View file

@ -1,5 +1,9 @@
import 'package:angel_validate/angel_validate.dart';
// Validators can be used on the server, in the browser, and even in Flutter.
//
// It is highly recommended that you read the documentation:
// https://github.com/angel-dart/validate
final Validator USER = new Validator({
'email': [isString, isNotEmpty, isEmail],
'username': [isString, isNotEmpty],

View file

@ -1,17 +1,21 @@
name: angel
description: An easily-extensible web server framework in Dart.
publish_to: none
publish_to: none # Ensure we don't accidentally publish our private code! ;)
environment:
sdk: ">=1.19.0"
homepage: https://github.com/angel-dart/angel
dependencies:
angel_common: ^1.0.0
angel_configuration: ^1.0.0
angel_hot: ^1.0.0-rc.1
angel_multiserver: ^1.0.0
angel_common: ^1.0.0 # Bundles the most commonly-used Angel packages.
angel_configuration: ^1.0.0 # Included in `angel_common`, but also exposes a transformer
angel_hot: ^1.0.0-rc.1 # Hot-reloading support. :)
angel_multiserver: ^1.0.0 # Helpful for applications running a scale.
dev_dependencies:
grinder: ^0.8.0
http: ^0.11.3
test: ^0.12.13
transformers:
transformers:
# Injects data from application configuration into Dart code.
#
# Documentation:
# https://github.com/angel-dart/configuration
- angel_configuration

View file

@ -1,27 +1,42 @@
import 'dart:io';
import 'package:angel/angel.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_test/angel_test.dart';
import 'package:test/test.dart';
// Angel also includes facilities to make testing easier.
//
// `package:angel_test` ships a client that can test
// both plain HTTP and WebSockets.
//
// Tests do not require your server to actually be mounted on a port,
// so they will run faster than they would in other frameworks, where you
// would have to first bind a socket, and then account for network latency.
//
// See the documentation here:
// https://github.com/angel-dart/test
//
// If you are unfamiliar with Dart's advanced testing library, you can read up
// here:
// https://github.com/dart-lang/test
main() async {
Angel app;
TestClient client;
setUp(() async {
app = await createServer();
var app = await createServer();
client = await connectTo(app);
});
tearDown(() async {
await client.close();
app = null;
});
test('index users', () async {
final response = await client.get('/api/users');
// Request a resource at the given path.
var response = await client.get('/api/users');
// By default, we locked this away from the Internet...
// Expect a 403 response.
expect(response, hasStatus(HttpStatus.FORBIDDEN));
});
}

View file

@ -1,3 +1,9 @@
// Grinder is not part of Angel, but you may consider using it
// to run tasks, a la Gulp.
//
// See its documentation here:
// https://github.com/google/grinder.dart
import 'package:grinder/grinder.dart';
main(args) => grind(args);