| 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 |