From f11614bb4514cd0c56e158b7db7a761881a6fd3b Mon Sep 17 00:00:00 2001 From: "nweiz@google.com" Date: Wed, 16 Apr 2014 01:23:01 +0000 Subject: [PATCH] Add a context field to Message in shelf. Originally https://codereview.chromium.org/226263007/. BUG=https://code.google.com/p/dart/issues/detail?id=17992 R=kevmoo@google.com Review URL: https://codereview.chromium.org//239183005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@35102 260f80e4-7a28-3924-810f-c04153c831b5 --- AUTHORS | 3 +- pkg/shelf/CHANGELOG.md | 5 +++ pkg/shelf/lib/src/message.dart | 20 ++++++++++-- pkg/shelf/lib/src/request.dart | 4 +-- pkg/shelf/lib/src/response.dart | 54 ++++++++++++++++++++------------ pkg/shelf/pubspec.yaml | 2 +- pkg/shelf/test/message_test.dart | 30 +++++++++++++++--- 7 files changed, 88 insertions(+), 30 deletions(-) diff --git a/AUTHORS b/AUTHORS index dc0e928b778..ab4eb42685d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,4 +28,5 @@ Nikolaus Graf Alexandre Ardhuin Victor Berchet Roel Spilker -Martin Charles \ No newline at end of file +Martin Charles +Anders Holmgren diff --git a/pkg/shelf/CHANGELOG.md b/pkg/shelf/CHANGELOG.md index 834ea70aaa1..a8ec6164c0d 100644 --- a/pkg/shelf/CHANGELOG.md +++ b/pkg/shelf/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.5.1 + +* Add a `context` map to `Request` and `Response` for passing data among + handlers and middleware. + ## 0.5.0+1 * Allow `scheduled_test` development dependency up to v0.12.0 diff --git a/pkg/shelf/lib/src/message.dart b/pkg/shelf/lib/src/message.dart index ad64d10ea31..1b2e966c466 100644 --- a/pkg/shelf/lib/src/message.dart +++ b/pkg/shelf/lib/src/message.dart @@ -20,6 +20,19 @@ abstract class Message { /// The value is immutable. final Map headers; + /// Extra context that can be used by for middleware and handlers. + /// + /// For requests, this is used to pass data to inner middleware and handlers; + /// for responses, it's used to pass data to outer middleware and handlers. + /// + /// Context properties that are used by a particular package should begin with + /// that package's name followed by a period. For example, if [logRequests] + /// wanted to take a prefix, its property name would be `"shelf.prefix"`, + /// since it's in the `shelf` package. + /// + /// The value is immutable. + final Map context; + /// The streaming body of the message. /// /// This can be read via [read] or [readAsString]. @@ -28,8 +41,11 @@ abstract class Message { /// Creates a new [Message]. /// /// If [headers] is `null`, it is treated as empty. - Message(this._body, {Map headers}) - : this.headers = _getIgnoreCaseMapView(headers); + Message(this._body, {Map headers, + Map context}) + : this.headers = _getIgnoreCaseMapView(headers), + this.context = new pc.UnmodifiableMapView( + context == null ? const {} : new Map.from(context)); /// The contents of the content-length field in [headers]. /// diff --git a/pkg/shelf/lib/src/request.dart b/pkg/shelf/lib/src/request.dart index 71c4e98200a..f7e011dc426 100644 --- a/pkg/shelf/lib/src/request.dart +++ b/pkg/shelf/lib/src/request.dart @@ -71,14 +71,14 @@ class Request extends Message { // TODO(kevmoo) finish documenting the rest of the arguments. Request(this.method, Uri requestedUri, {String protocolVersion, Map headers, Uri url, String scriptName, - Stream> body}) + Stream> body, Map context}) : this.requestedUri = requestedUri, this.protocolVersion = protocolVersion == null ? '1.1' : protocolVersion, this.url = _computeUrl(requestedUri, url, scriptName), this.scriptName = _computeScriptName(requestedUri, url, scriptName), super(body == null ? new Stream.fromIterable([]) : body, - headers: headers) { + headers: headers, context: context) { if (method.isEmpty) throw new ArgumentError('method cannot be empty.'); // TODO(kevmoo) use isAbsolute property on Uri once Issue 18053 is fixed diff --git a/pkg/shelf/lib/src/response.dart b/pkg/shelf/lib/src/response.dart index bbc268bed4f..e59b0c55ef8 100644 --- a/pkg/shelf/lib/src/response.dart +++ b/pkg/shelf/lib/src/response.dart @@ -53,8 +53,10 @@ class Response extends Message { /// If [encoding] is passed, the "encoding" field of the Content-Type header /// in [headers] will be set appropriately. If there is no existing /// Content-Type header, it will be set to "application/octet-stream". - Response.ok(body, {Map headers, Encoding encoding}) - : this(200, body: body, headers: headers, encoding: encoding); + Response.ok(body, {Map headers, Encoding encoding, + Map context}) + : this(200, body: body, headers: headers, encoding: encoding, + context: context); /// Constructs a 301 Moved Permanently response. /// @@ -71,8 +73,9 @@ class Response extends Message { /// in [headers] will be set appropriately. If there is no existing /// Content-Type header, it will be set to "application/octet-stream". Response.movedPermanently(location, {body, Map headers, - Encoding encoding}) - : this._redirect(301, location, body, headers, encoding); + Encoding encoding, Map context}) + : this._redirect(301, location, body, headers, encoding, + context: context); /// Constructs a 302 Found response. /// @@ -89,8 +92,9 @@ class Response extends Message { /// in [headers] will be set appropriately. If there is no existing /// Content-Type header, it will be set to "application/octet-stream". Response.found(location, {body, Map headers, - Encoding encoding}) - : this._redirect(302, location, body, headers, encoding); + Encoding encoding, Map context}) + : this._redirect(302, location, body, headers, encoding, + context: context); /// Constructs a 303 See Other response. /// @@ -108,17 +112,20 @@ class Response extends Message { /// in [headers] will be set appropriately. If there is no existing /// Content-Type header, it will be set to "application/octet-stream". Response.seeOther(location, {body, Map headers, - Encoding encoding}) - : this._redirect(303, location, body, headers, encoding); + Encoding encoding, Map context}) + : this._redirect(303, location, body, headers, encoding, + context: context); /// Constructs a helper constructor for redirect responses. Response._redirect(int statusCode, location, body, - Map headers, Encoding encoding) + Map headers, Encoding encoding, + { Map context }) : this(statusCode, body: body, encoding: encoding, headers: _addHeader( - headers, 'location', _locationToString(location))); + headers, 'location', _locationToString(location)), + context: context); /// Constructs a 304 Not Modified response. /// @@ -126,9 +133,11 @@ class Response extends Message { /// information used to determine whether the requested resource has changed /// since the last request. It indicates that the resource has not changed and /// the old value should be used. - Response.notModified({Map headers}) + Response.notModified({Map headers, + Map context}) : this(304, headers: _addHeader( - headers, 'date', formatHttpDate(new DateTime.now()))); + headers, 'date', formatHttpDate(new DateTime.now())), + context: context); /// Constructs a 403 Forbidden response. /// @@ -143,8 +152,9 @@ class Response extends Message { /// in [headers] will be set appropriately. If there is no existing /// Content-Type header, it will be set to "application/octet-stream". Response.forbidden(body, {Map headers, - Encoding encoding}) - : this(403, body: body, headers: headers); + Encoding encoding, Map context}) + : this(403, body: body, headers: headers, + context: context); /// Constructs a 404 Not Found response. /// @@ -159,8 +169,10 @@ class Response extends Message { /// If [encoding] is passed, the "encoding" field of the Content-Type header /// in [headers] will be set appropriately. If there is no existing /// Content-Type header, it will be set to "application/octet-stream". - Response.notFound(body, {Map headers, Encoding encoding}) - : this(404, body: body, headers: headers); + Response.notFound(body, {Map headers, Encoding encoding, + Map context}) + : this(404, body: body, headers: headers, + context: context); /// Constructs a 500 Internal Server Error response. /// @@ -176,10 +188,11 @@ class Response extends Message { /// in [headers] will be set appropriately. If there is no existing /// Content-Type header, it will be set to "application/octet-stream". Response.internalServerError({body, Map headers, - Encoding encoding}) + Encoding encoding, Map context}) : this(500, headers: body == null ? _adjust500Headers(headers) : headers, - body: body == null ? 'Internal Server Error' : body); + body: body == null ? 'Internal Server Error' : body, + context: context); /// Constructs an HTTP response with the given [statusCode]. /// @@ -194,9 +207,10 @@ class Response extends Message { /// in [headers] will be set appropriately. If there is no existing /// Content-Type header, it will be set to "application/octet-stream". Response(this.statusCode, {body, Map headers, - Encoding encoding}) + Encoding encoding, Map context}) : super(_bodyToStream(body, encoding), - headers: _adjustHeaders(headers, encoding)) { + headers: _adjustHeaders(headers, encoding), + context: context) { if (statusCode < 100) { throw new ArgumentError("Invalid status code: $statusCode."); } diff --git a/pkg/shelf/pubspec.yaml b/pkg/shelf/pubspec.yaml index fb0dbb56b00..848f20cccad 100644 --- a/pkg/shelf/pubspec.yaml +++ b/pkg/shelf/pubspec.yaml @@ -1,5 +1,5 @@ name: shelf -version: 0.5.0+1 +version: 0.5.1-dev author: Dart Team description: Web Server Middleware for Dart homepage: http://www.dartlang.org diff --git a/pkg/shelf/test/message_test.dart b/pkg/shelf/test/message_test.dart index 57f32c4afe0..1db286ba449 100644 --- a/pkg/shelf/test/message_test.dart +++ b/pkg/shelf/test/message_test.dart @@ -11,13 +11,15 @@ import 'package:shelf/src/message.dart'; import 'package:unittest/unittest.dart'; class _TestMessage extends Message { - _TestMessage(Map headers, Stream> body) - : super(body, headers: headers); + _TestMessage(Map headers, Map context, + Stream> body) + : super(body, headers: headers, context: context); } -Message _createMessage({Map headers, Stream> body}) { +Message _createMessage({Map headers, + Map context, Stream> body}) { if (body == null) body = new Stream.fromIterable([]); - return new _TestMessage(headers, body); + return new _TestMessage(headers, context, body); } void main() { @@ -43,6 +45,26 @@ void main() { expect(() => message.headers['h2'] = 'value2', throwsUnsupportedError); }); }); + + group('context', () { + test('is accessible', () { + var message = _createMessage(context: {'foo': 'bar'}); + expect(message.context, containsPair('foo', 'bar')); + }); + + test('null context value becomes empty and immutable', () { + var message = _createMessage(); + expect(message.context, isEmpty); + expect(() => message.context['key'] = 'value', throwsUnsupportedError); + }); + + test('is immutable', () { + var message = _createMessage(context: {'key': 'value'}); + expect(() => message.context['key'] = 'value', throwsUnsupportedError); + expect(() => message.context['key2'] = 'value', throwsUnsupportedError); + }); + }); + group("readAsString", () { test("supports a null body", () { var request = _createMessage();