Skip to content
This repository was archived by the owner on Mar 18, 2021. It is now read-only.

Jc/responseencoder #22

Merged
merged 10 commits into from
Nov 30, 2015
Merged
15 changes: 0 additions & 15 deletions .idea/runConfigurations/Application_Tests.xml

This file was deleted.

15 changes: 0 additions & 15 deletions .idea/runConfigurations/Controller_Tests.xml

This file was deleted.

15 changes: 0 additions & 15 deletions .idea/runConfigurations/Isolate_Tests.xml

This file was deleted.

15 changes: 0 additions & 15 deletions .idea/runConfigurations/Pattern_Tests.xml

This file was deleted.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions lib/auth/auth_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ class AuthController<ResourceOwner extends Authenticatable, TokenType extends To

if (grant_type == "password") {
if (username == null || password == null) {
return new Response.badRequest(body: {"error" : "username and password required"});
return new Response.badRequest(body: {"error": "username and password required"});
}

var token = await authenticationServer.authenticate(username, password, rec.username, rec.password);
return AuthController.tokenResponse(token);
} else if (grant_type == "refresh") {
if (refresh_token == null) {
return new Response.badRequest(body: {"error" : "missing refresh_token"});
return new Response.badRequest(body: {"error": "missing refresh_token"});
}

var token = await authenticationServer.refresh(refresh_token, rec.username, rec.password);
Expand Down
106 changes: 69 additions & 37 deletions lib/base/application.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ class ApplicationInstanceConfiguration {
/// run over HTTP instead of HTTPS.
bool isUsingClientCertificate = false;

/// The name of the server-side HTTPS certificate.
/// Information for securing the application over HTTPS.
///
/// Defaults to null. If this is null and [isUsingClientCertificate] is false, the server will
/// run over HTTP instead of HTTPs.
String serverCertificateName = null;
/// Defaults to null. If this is null, this application will run unsecured over HTTP. To
/// run securely over HTTPS, this property must be set with valid security details.
SecurityContext securityContext = null;

/// Options for instances of ApplicationPipeline to use when in this application.
///
Expand All @@ -42,19 +42,18 @@ class ApplicationInstanceConfiguration {
ApplicationInstanceConfiguration();

/// A copy constructor
ApplicationInstanceConfiguration.fromConfiguration(ApplicationInstanceConfiguration config)
: this.address = config.address,
this.port = config.port,
this.isIpv6Only = config.isIpv6Only,
this.isUsingClientCertificate = config.isUsingClientCertificate,
this.serverCertificateName = config.serverCertificateName,
this._shared = config._shared,
this.pipelineOptions = config.pipelineOptions;
ApplicationInstanceConfiguration.fromConfiguration(ApplicationInstanceConfiguration config) {
var reflectedThis = reflect(this);
var reflectedThat = reflect(config);
reflectedThat.type.declarations.values.where((dm) => dm is VariableMirror).forEach((VariableMirror vm) {
reflectedThis.setField(vm.simpleName, reflectedThat.getField(vm.simpleName).reflectee);
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty cool, but it seems kinda dangerous.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How so? I realized it was more dangerous when I was setting each property individually, added a new property, and it wasn't showing up on the other end of the pipeline.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried about the implicit assumption that all of the properties are copied on assignment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok nvm I looked at how its being used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, that brings up a good point: there really isn't a need to copy this information anymore. The purpose was to copy it for each isolate, but that is no longer necessary. removing

}

/// A abstract class that concrete subclasses will implement to provide request handling behavior.
///
/// [Application]s set up HTTP(s) listeners, but do not do anything with them. The behavior of how an application
/// [Application]s set up HTTP(S) listeners, but do not do anything with them. The behavior of how an application
/// responds to requests is defined by its [ApplicationPipeline]. Instances of this class must implement the
/// [handleRequest] method from [RequestHandler] - this is the entry point of all requests into this pipeline.
abstract class ApplicationPipeline extends RequestHandler {
Expand Down Expand Up @@ -107,7 +106,7 @@ abstract class ApplicationPipeline extends RequestHandler {
/// a [PipelineType] and [RequestType].
class Application<PipelineType extends ApplicationPipeline> {
/// A list of items identifying the Isolates running a HTTP(s) listener and response handlers.
List<_ServerRecord> servers = [];
List<_ServerSupervisor> servers = [];

/// The configuration for the HTTP(s) server this application is running.
///
Expand Down Expand Up @@ -144,7 +143,12 @@ class Application<PipelineType extends ApplicationPipeline> {
await Future.wait(futures);
}

Future<_ServerRecord> _spawn(ApplicationInstanceConfiguration config, int identifier) async {
Future stop() async {
await Future.wait(servers.map((s) => s.stop()));
servers = [];
}

Future<_ServerSupervisor> _spawn(ApplicationInstanceConfiguration config, int identifier) async {
var receivePort = new ReceivePort();

var pipelineTypeMirror = reflectType(PipelineType);
Expand All @@ -155,20 +159,22 @@ class Application<PipelineType extends ApplicationPipeline> {
var isolate = await Isolate.spawn(_Server.entry, initialMessage, paused: true);
isolate.addErrorListener(receivePort.sendPort);

return new _ServerRecord(isolate, receivePort, identifier);
return new _ServerSupervisor(isolate, receivePort, identifier);
}
}

class _Server {
static String _FinishedMessage = "finished";

ApplicationInstanceConfiguration configuration;
SendPort supervisingApplicationPort;
ReceivePort supervisingReceivePort;
HttpServer server;
ApplicationPipeline pipeline;
int identifier;

_Server(this.pipeline, this.configuration, this.identifier, this.supervisingApplicationPort);
_Server(this.pipeline, this.configuration, this.identifier, this.supervisingApplicationPort) {
supervisingReceivePort = new ReceivePort();
supervisingReceivePort.listen(listener);
}

ResourceRequest createRequest(HttpRequest req) {
return new ResourceRequest(req);
Expand All @@ -195,24 +201,27 @@ class _Server {

pipeline.didOpen();

supervisingApplicationPort.send(_FinishedMessage);
supervisingApplicationPort.send(supervisingReceivePort.sendPort);
};

if (configuration.serverCertificateName != null) {
if (configuration.securityContext != null) {
HttpServer
.bindSecure(configuration.address, configuration.port,
certificateName: configuration.serverCertificateName, v6Only: configuration.isIpv6Only, shared: configuration._shared)
.then(onBind);
} else if (configuration.isUsingClientCertificate) {
HttpServer
.bindSecure(configuration.address, configuration.port,
requestClientCertificate: true, v6Only: configuration.isIpv6Only, shared: configuration._shared)
.bindSecure(configuration.address, configuration.port, configuration.securityContext,
requestClientCertificate: configuration.isUsingClientCertificate, v6Only: configuration.isIpv6Only, shared: configuration._shared)
.then(onBind);
} else {
HttpServer.bind(configuration.address, configuration.port, v6Only: configuration.isIpv6Only, shared: configuration._shared).then(onBind);
}
}

void listener(dynamic message) {
if (message == _ServerSupervisor._MessageStop) {
server.close().then((s) {
supervisingApplicationPort.send(_ServerSupervisor._MessageStop);
});
}
}

static void entry(_InitialServerMessage params) {
var pipelineSourceLibraryMirror = currentMirrorSystem().libraries[params.pipelineLibraryURI];
var pipelineTypeMirror = pipelineSourceLibraryMirror.declarations[new Symbol(params.pipelineTypeName)] as ClassMirror;
Expand All @@ -224,22 +233,45 @@ class _Server {
}
}

class _ServerRecord {
class _ServerSupervisor {
static String _MessageStop = "_MessageStop";

final Isolate isolate;
final ReceivePort receivePort;
final int identifier;

_ServerRecord(this.isolate, this.receivePort, this.identifier);
SendPort _serverSendPort;

Completer _launchCompleter;
Completer _stopCompleter;

_ServerSupervisor(this.isolate, this.receivePort, this.identifier) {
}

Future resume() {
var completer = new Completer();
receivePort.listen((msg) {
if (msg == _Server._FinishedMessage) {
completer.complete();
}
});
_launchCompleter = new Completer();
receivePort.listen(listener);

isolate.resume(isolate.pauseCapability);
return completer.future.timeout(new Duration(seconds: 30));
return _launchCompleter.future.timeout(new Duration(seconds: 30));
}

Future stop() {
_stopCompleter = new Completer();
_serverSendPort.send(_MessageStop);
return _stopCompleter.future.timeout(new Duration(seconds: 30));
}

void listener(dynamic message) {
if (message is SendPort) {
_launchCompleter.complete();
_launchCompleter = null;

_serverSendPort = message;
} else if (message == _MessageStop) {
_stopCompleter.complete();
_stopCompleter = null;
}
}
}

Expand Down
30 changes: 21 additions & 9 deletions lib/base/http_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,28 @@ abstract class HttpController extends RequestHandler {

/// The content type of responses from this [HttpController].
///
/// Defaults to "application/json". This type will automatically be written to this response's
/// HTTP header.
ContentType responseContentType = ContentType.JSON;

// TODO: Privatize these and make method that requires both
/// This type will automatically be written to this response's
/// HTTP header. Defaults to "application/json". Set with [setResponseEncoder].
ContentType get responseContentType => _responseContentType;
ContentType _responseContentType = ContentType.JSON;

/// Encodes the [Response] body to a suitable format for transmission.
///
/// Encodes the HTTP response body object that is part of the [Response] returned from this request handler methods.
/// Must have the signature (dynamic) => dynamic. Use [setResponseEncoder] to set this value.
///
/// By default, this encoder will convert the body object as JSON.
dynamic responseBodyEncoder = (body) => JSON.encode(body);
/// By default, this encoder will convert the response body as JSON.
Function get responseBodyEncoder => _responseBodyEncoder;
Function _responseBodyEncoder = (body) => JSON.encode(body);

/// Sets the [responseContentType] and [responseBodyEncoder].
///
/// This method is the only way to set [responseContentType] and [responseBodyEncoder] and ensures
/// that both are set simultaneously since they are dependent on one another.
void setResponseEncoder(ContentType contentType, dynamic encoder(dynamic value)) {
_responseContentType = contentType;
_responseBodyEncoder = encoder;
}

/// The HTTP request body object, after being decoded.
///
Expand All @@ -54,6 +66,7 @@ abstract class HttpController extends RequestHandler {
/// respond with a Unsupported Media Type HTTP response.
dynamic requestBody;

// Callbacks
/// Executed prior to handling a request, but after the [resourceRequest] has been set.
///
/// This method is used to do pre-process setup. The [resourceRequest] will be set, but its body will not be decoded
Expand Down Expand Up @@ -163,8 +176,7 @@ abstract class HttpController extends RequestHandler {

if (contentType != null &&
contentType.primaryType == _applicationWWWFormURLEncodedContentType.primaryType &&
contentType.subType == _applicationWWWFormURLEncodedContentType.subType)
{
contentType.subType == _applicationWWWFormURLEncodedContentType.subType) {
queryParams = requestBody;
} else {
queryParams = req.innerRequest.uri.queryParameters;
Expand Down
19 changes: 19 additions & 0 deletions test/application_tests.dart → test/application_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ main() {
var app = new Application<TPipeline>();
app.configuration.port = 8080;

tearDownAll(() async {
app.stop();
});

test("Application starts", () async {
await app.start();
expect(app.servers.length, 1);
Expand All @@ -24,6 +28,21 @@ main() {
expect(tResponse.body, '"t_ok"');
expect(rResponse.body, '"r_ok"');
});

test("Application stops", () async {
await app.stop();

try {
var _ = await http.get("http://localhost:8080/t");
fail("This should fail immeidlatey");
} catch (e) {
expect(e, isNotNull);
}

await app.start();
var resp = await http.get("http://localhost:8080/t");
expect(resp.statusCode, 200);
});
}

class TPipeline extends ApplicationPipeline {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ void main() {

HttpServer server;

tearDownAll(() async {
await server.close();
});

setUp(() async {
adapter = new PostgresModelAdapter(null, () async {
var uri = 'postgres://dart:dart@localhost:5432/dart_test';
return await connect(uri);
});
adapter.loggingEnabled = true;

var authenticationServer = new AuthenticationServer<TestUser, Token>(
new AuthDelegate<TestUser, Token>(adapter));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ void main() {
var uri = 'postgres://dart:dart@localhost:5432/dart_test';
return await connect(uri);
});
adapter.loggingEnabled = true;

delegate = new AuthDelegate<TestUser, Token>(adapter);
await generateTemporarySchemaFromModels(adapter, [TestUser, Token]);
});
Expand Down
Loading