OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library shelf.message; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:convert'; | |
9 | |
10 import 'package:http_parser/http_parser.dart'; | |
11 import 'package:stack_trace/stack_trace.dart'; | |
12 | |
13 import 'shelf_unmodifiable_map.dart'; | |
14 | |
15 /// Represents logic shared between [Request] and [Response]. | |
16 abstract class Message { | |
17 /// The HTTP headers. | |
18 /// | |
19 /// The value is immutable. | |
20 final Map<String, String> headers; | |
21 | |
22 /// Extra context that can be used by for middleware and handlers. | |
23 /// | |
24 /// 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. | |
26 /// | |
27 /// 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] | |
29 /// wanted to take a prefix, its property name would be `"shelf.prefix"`, | |
30 /// since it's in the `shelf` package. | |
31 /// | |
32 /// The value is immutable. | |
33 final Map<String, Object> context; | |
34 | |
35 /// The streaming body of the message. | |
36 /// | |
37 /// This can be read via [read] or [readAsString]. | |
38 final Stream<List<int>> _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 | |
46 /// Creates a new [Message]. | |
47 /// | |
48 /// If [headers] is `null`, it is treated as empty. | |
49 Message(this._body, {Map<String, String> headers, | |
50 Map<String, Object> context}) | |
51 : this.headers = new ShelfUnmodifiableMap<String>(headers, | |
52 ignoreKeyCase: true), | |
53 this.context = new ShelfUnmodifiableMap<Object>(context, | |
54 ignoreKeyCase: false); | |
55 | |
56 /// The contents of the content-length field in [headers]. | |
57 /// | |
58 /// If not set, `null`. | |
59 int get contentLength { | |
60 if (_contentLengthCache != null) return _contentLengthCache; | |
61 if (!headers.containsKey('content-length')) return null; | |
62 _contentLengthCache = int.parse(headers['content-length']); | |
63 return _contentLengthCache; | |
64 } | |
65 int _contentLengthCache; | |
66 | |
67 /// The MIME type of the message. | |
68 /// | |
69 /// This is parsed from the Content-Type header in [headers]. It contains only | |
70 /// the MIME type, without any Content-Type parameters. | |
71 /// | |
72 /// If [headers] doesn't have a Content-Type header, this will be `null`. | |
73 String get mimeType { | |
74 var contentType = _contentType; | |
75 if (contentType == null) return null; | |
76 return contentType.mimeType; | |
77 } | |
78 | |
79 /// The encoding of the message body. | |
80 /// | |
81 /// This is parsed from the "charset" paramater of the Content-Type header in | |
82 /// [headers]. | |
83 /// | |
84 /// If [headers] doesn't have a Content-Type header or it specifies an | |
85 /// encoding that [dart:convert] doesn't support, this will be `null`. | |
86 Encoding get encoding { | |
87 var contentType = _contentType; | |
88 if (contentType == null) return null; | |
89 if (!contentType.parameters.containsKey('charset')) return null; | |
90 return Encoding.getByName(contentType.parameters['charset']); | |
91 } | |
92 | |
93 /// The parsed version of the Content-Type header in [headers]. | |
94 /// | |
95 /// This is cached for efficient access. | |
96 MediaType get _contentType { | |
97 if (_contentTypeCache != null) return _contentTypeCache; | |
98 if (!headers.containsKey('content-type')) return null; | |
99 _contentTypeCache = new MediaType.parse(headers['content-type']); | |
100 return _contentTypeCache; | |
101 } | |
102 MediaType _contentTypeCache; | |
103 | |
104 /// Returns a [Stream] representing the body. | |
105 /// | |
106 /// Can only be called once. | |
107 Stream<List<int>> read() { | |
108 if (_bodyWasRead) { | |
109 throw new StateError("The 'read' method can only be called once on a " | |
110 "shelf.Request/shelf.Response object."); | |
111 } | |
112 _bodyWasRead = true; | |
113 return _body; | |
114 } | |
115 | |
116 /// Returns a [Future] containing the body as a String. | |
117 /// | |
118 /// If [encoding] is passed, that's used to decode the body. | |
119 /// Otherwise the encoding is taken from the Content-Type header. If that | |
120 /// doesn't exist or doesn't have a "charset" parameter, UTF-8 is used. | |
121 /// | |
122 /// This calls [read] internally, which can only be called once. | |
123 Future<String> readAsString([Encoding encoding]) { | |
124 if (encoding == null) encoding = this.encoding; | |
125 if (encoding == null) encoding = UTF8; | |
126 return Chain.track(encoding.decodeStream(read())); | |
127 } | |
128 | |
129 /// Creates a new [Message] by copying existing values and applying specified | |
130 /// changes. | |
131 Message change({Map<String, String> headers, Map<String, Object> context}); | |
132 } | |
OLD | NEW |