Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(213)

Side by Side Diff: lib/src/message.dart

Issue 1327453002: Share a body across all versions of a message. (Closed) Base URL: git@github.com:dart-lang/shelf@master
Patch Set: Code review changes Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/src/body.dart ('k') | lib/src/request.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library shelf.message; 5 library shelf.message;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:convert'; 8 import 'dart:convert';
9 9
10 import 'package:http_parser/http_parser.dart'; 10 import 'package:http_parser/http_parser.dart';
11 11
12 import 'body.dart';
12 import 'shelf_unmodifiable_map.dart'; 13 import 'shelf_unmodifiable_map.dart';
13 import 'util.dart'; 14 import 'util.dart';
14 15
16 Body getBody(Message message) => message._body;
17
15 /// Represents logic shared between [Request] and [Response]. 18 /// Represents logic shared between [Request] and [Response].
16 abstract class Message { 19 abstract class Message {
17 /// The HTTP headers. 20 /// The HTTP headers.
18 /// 21 ///
19 /// The value is immutable. 22 /// The value is immutable.
20 final Map<String, String> headers; 23 final Map<String, String> headers;
21 24
22 /// Extra context that can be used by for middleware and handlers. 25 /// Extra context that can be used by for middleware and handlers.
23 /// 26 ///
24 /// For requests, this is used to pass data to inner middleware and handlers; 27 /// For requests, this is used to pass data to inner middleware and handlers;
25 /// for responses, it's used to pass data to outer middleware and handlers. 28 /// for responses, it's used to pass data to outer middleware and handlers.
26 /// 29 ///
27 /// Context properties that are used by a particular package should begin with 30 /// Context properties that are used by a particular package should begin with
28 /// that package's name followed by a period. For example, if [logRequests] 31 /// that package's name followed by a period. For example, if [logRequests]
29 /// wanted to take a prefix, its property name would be `"shelf.prefix"`, 32 /// wanted to take a prefix, its property name would be `"shelf.prefix"`,
30 /// since it's in the `shelf` package. 33 /// since it's in the `shelf` package.
31 /// 34 ///
32 /// The value is immutable. 35 /// The value is immutable.
33 final Map<String, Object> context; 36 final Map<String, Object> context;
34 37
35 /// The streaming body of the message. 38 /// The streaming body of the message.
36 /// 39 ///
37 /// This can be read via [read] or [readAsString]. 40 /// This can be read via [read] or [readAsString].
38 final Stream<List<int>> _body; 41 final Body _body;
39
40 /// This boolean indicates whether [_body] has been read.
41 ///
42 /// After calling [read], or [readAsString] (which internally calls [read]),
43 /// this will be `true`.
44 bool _bodyWasRead = false;
45 42
46 /// Creates a new [Message]. 43 /// Creates a new [Message].
47 /// 44 ///
48 /// [body] is the response body. It may be either a [String], a 45 /// [body] is the response body. It may be either a [String], a
49 /// [Stream<List<int>>], or `null` to indicate no body. If it's a [String], 46 /// [Stream<List<int>>], or `null` to indicate no body. If it's a [String],
50 /// [encoding] is used to encode it to a [Stream<List<int>>]. It defaults to 47 /// [encoding] is used to encode it to a [Stream<List<int>>]. It defaults to
51 /// UTF-8. 48 /// UTF-8.
52 /// 49 ///
53 /// If [headers] is `null`, it is treated as empty. 50 /// If [headers] is `null`, it is treated as empty.
54 /// 51 ///
55 /// If [encoding] is passed, the "encoding" field of the Content-Type header 52 /// If [encoding] is passed, the "encoding" field of the Content-Type header
56 /// in [headers] will be set appropriately. If there is no existing 53 /// in [headers] will be set appropriately. If there is no existing
57 /// Content-Type header, it will be set to "application/octet-stream". 54 /// Content-Type header, it will be set to "application/octet-stream".
58 Message(body, {Encoding encoding, Map<String, String> headers, 55 Message(body, {Encoding encoding, Map<String, String> headers,
59 Map<String, Object> context}) 56 Map<String, Object> context})
60 : this._body = _bodyToStream(body, encoding), 57 : this._body = new Body(body, encoding),
61 this.headers = new ShelfUnmodifiableMap<String>( 58 this.headers = new ShelfUnmodifiableMap<String>(
62 _adjustHeaders(headers, encoding), ignoreKeyCase: true), 59 _adjustHeaders(headers, encoding), ignoreKeyCase: true),
63 this.context = new ShelfUnmodifiableMap<Object>(context, 60 this.context = new ShelfUnmodifiableMap<Object>(context,
64 ignoreKeyCase: false); 61 ignoreKeyCase: false);
65 62
66 /// The contents of the content-length field in [headers]. 63 /// The contents of the content-length field in [headers].
67 /// 64 ///
68 /// If not set, `null`. 65 /// If not set, `null`.
69 int get contentLength { 66 int get contentLength {
70 if (_contentLengthCache != null) return _contentLengthCache; 67 if (_contentLengthCache != null) return _contentLengthCache;
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
107 if (_contentTypeCache != null) return _contentTypeCache; 104 if (_contentTypeCache != null) return _contentTypeCache;
108 if (!headers.containsKey('content-type')) return null; 105 if (!headers.containsKey('content-type')) return null;
109 _contentTypeCache = new MediaType.parse(headers['content-type']); 106 _contentTypeCache = new MediaType.parse(headers['content-type']);
110 return _contentTypeCache; 107 return _contentTypeCache;
111 } 108 }
112 MediaType _contentTypeCache; 109 MediaType _contentTypeCache;
113 110
114 /// Returns a [Stream] representing the body. 111 /// Returns a [Stream] representing the body.
115 /// 112 ///
116 /// Can only be called once. 113 /// Can only be called once.
117 Stream<List<int>> read() { 114 Stream<List<int>> read() => _body.read();
118 if (_bodyWasRead) {
119 throw new StateError("The 'read' method can only be called once on a "
120 "shelf.Request/shelf.Response object.");
121 }
122 _bodyWasRead = true;
123 return _body;
124 }
125 115
126 /// Returns a [Future] containing the body as a String. 116 /// Returns a [Future] containing the body as a String.
127 /// 117 ///
128 /// If [encoding] is passed, that's used to decode the body. 118 /// If [encoding] is passed, that's used to decode the body.
129 /// Otherwise the encoding is taken from the Content-Type header. If that 119 /// Otherwise the encoding is taken from the Content-Type header. If that
130 /// doesn't exist or doesn't have a "charset" parameter, UTF-8 is used. 120 /// doesn't exist or doesn't have a "charset" parameter, UTF-8 is used.
131 /// 121 ///
132 /// This calls [read] internally, which can only be called once. 122 /// This calls [read] internally, which can only be called once.
133 Future<String> readAsString([Encoding encoding]) { 123 Future<String> readAsString([Encoding encoding]) {
134 if (encoding == null) encoding = this.encoding; 124 if (encoding == null) encoding = this.encoding;
135 if (encoding == null) encoding = UTF8; 125 if (encoding == null) encoding = UTF8;
136 return encoding.decodeStream(read()); 126 return encoding.decodeStream(read());
137 } 127 }
138 128
139 /// Creates a new [Message] by copying existing values and applying specified 129 /// Creates a new [Message] by copying existing values and applying specified
140 /// changes. 130 /// changes.
141 Message change({Map<String, String> headers, Map<String, Object> context, 131 Message change({Map<String, String> headers, Map<String, Object> context,
142 body}); 132 body});
143 } 133 }
144 134
145 /// Converts [body] to a byte stream.
146 ///
147 /// [body] may be either a [String], a [Stream<List<int>>], or `null`. If it's a
148 /// [String], [encoding] will be used to convert it to a [Stream<List<int>>].
149 Stream<List<int>> _bodyToStream(body, Encoding encoding) {
150 if (encoding == null) encoding = UTF8;
151 if (body == null) return new Stream.fromIterable([]);
152 if (body is String) return new Stream.fromIterable([encoding.encode(body)]);
153 if (body is Stream) return body;
154
155 throw new ArgumentError('Response body "$body" must be a String or a '
156 'Stream.');
157 }
158
159 /// Adds information about [encoding] to [headers]. 135 /// Adds information about [encoding] to [headers].
160 /// 136 ///
161 /// Returns a new map without modifying [headers]. 137 /// Returns a new map without modifying [headers].
162 Map<String, String> _adjustHeaders( 138 Map<String, String> _adjustHeaders(
163 Map<String, String> headers, Encoding encoding) { 139 Map<String, String> headers, Encoding encoding) {
164 if (headers == null) headers = const {}; 140 if (headers == null) headers = const {};
165 if (encoding == null) return headers; 141 if (encoding == null) return headers;
166 if (headers['content-type'] == null) { 142 if (headers['content-type'] == null) {
167 return addHeader(headers, 'content-type', 143 return addHeader(headers, 'content-type',
168 'application/octet-stream; charset=${encoding.name}'); 144 'application/octet-stream; charset=${encoding.name}');
169 } 145 }
170 146
171 var contentType = new MediaType.parse(headers['content-type']).change( 147 var contentType = new MediaType.parse(headers['content-type']).change(
172 parameters: {'charset': encoding.name}); 148 parameters: {'charset': encoding.name});
173 return addHeader(headers, 'content-type', contentType.toString()); 149 return addHeader(headers, 'content-type', contentType.toString());
174 } 150 }
OLDNEW
« no previous file with comments | « lib/src/body.dart ('k') | lib/src/request.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698