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 |
| 12 import 'body.dart'; |
| 13 import 'shelf_unmodifiable_map.dart'; |
| 14 import 'util.dart'; |
| 15 |
| 16 Body getBody(Message message) => message._body; |
| 17 |
| 18 /// Represents logic shared between [Request] and [Response]. |
| 19 abstract class Message { |
| 20 /// The HTTP headers. |
| 21 /// |
| 22 /// The value is immutable. |
| 23 final Map<String, String> headers; |
| 24 |
| 25 /// Extra context that can be used by for middleware and handlers. |
| 26 /// |
| 27 /// For requests, this is used to pass data to inner middleware and handlers; |
| 28 /// for responses, it's used to pass data to outer middleware and handlers. |
| 29 /// |
| 30 /// Context properties that are used by a particular package should begin with |
| 31 /// that package's name followed by a period. For example, if [logRequests] |
| 32 /// wanted to take a prefix, its property name would be `"shelf.prefix"`, |
| 33 /// since it's in the `shelf` package. |
| 34 /// |
| 35 /// The value is immutable. |
| 36 final Map<String, Object> context; |
| 37 |
| 38 /// The streaming body of the message. |
| 39 /// |
| 40 /// This can be read via [read] or [readAsString]. |
| 41 final Body _body; |
| 42 |
| 43 /// Creates a new [Message]. |
| 44 /// |
| 45 /// [body] is the response body. It may be either a [String], a |
| 46 /// [Stream<List<int>>], or `null` to indicate no body. If it's a [String], |
| 47 /// [encoding] is used to encode it to a [Stream<List<int>>]. It defaults to |
| 48 /// UTF-8. |
| 49 /// |
| 50 /// If [headers] is `null`, it is treated as empty. |
| 51 /// |
| 52 /// If [encoding] is passed, the "encoding" field of the Content-Type header |
| 53 /// in [headers] will be set appropriately. If there is no existing |
| 54 /// Content-Type header, it will be set to "application/octet-stream". |
| 55 Message(body, {Encoding encoding, Map<String, String> headers, |
| 56 Map<String, Object> context}) |
| 57 : this._body = new Body(body, encoding), |
| 58 this.headers = new ShelfUnmodifiableMap<String>( |
| 59 _adjustHeaders(headers, encoding), ignoreKeyCase: true), |
| 60 this.context = new ShelfUnmodifiableMap<Object>(context, |
| 61 ignoreKeyCase: false); |
| 62 |
| 63 /// The contents of the content-length field in [headers]. |
| 64 /// |
| 65 /// If not set, `null`. |
| 66 int get contentLength { |
| 67 if (_contentLengthCache != null) return _contentLengthCache; |
| 68 if (!headers.containsKey('content-length')) return null; |
| 69 _contentLengthCache = int.parse(headers['content-length']); |
| 70 return _contentLengthCache; |
| 71 } |
| 72 int _contentLengthCache; |
| 73 |
| 74 /// The MIME type of the message. |
| 75 /// |
| 76 /// This is parsed from the Content-Type header in [headers]. It contains only |
| 77 /// the MIME type, without any Content-Type parameters. |
| 78 /// |
| 79 /// If [headers] doesn't have a Content-Type header, this will be `null`. |
| 80 String get mimeType { |
| 81 var contentType = _contentType; |
| 82 if (contentType == null) return null; |
| 83 return contentType.mimeType; |
| 84 } |
| 85 |
| 86 /// The encoding of the message body. |
| 87 /// |
| 88 /// This is parsed from the "charset" parameter of the Content-Type header in |
| 89 /// [headers]. |
| 90 /// |
| 91 /// If [headers] doesn't have a Content-Type header or it specifies an |
| 92 /// encoding that [dart:convert] doesn't support, this will be `null`. |
| 93 Encoding get encoding { |
| 94 var contentType = _contentType; |
| 95 if (contentType == null) return null; |
| 96 if (!contentType.parameters.containsKey('charset')) return null; |
| 97 return Encoding.getByName(contentType.parameters['charset']); |
| 98 } |
| 99 |
| 100 /// The parsed version of the Content-Type header in [headers]. |
| 101 /// |
| 102 /// This is cached for efficient access. |
| 103 MediaType get _contentType { |
| 104 if (_contentTypeCache != null) return _contentTypeCache; |
| 105 if (!headers.containsKey('content-type')) return null; |
| 106 _contentTypeCache = new MediaType.parse(headers['content-type']); |
| 107 return _contentTypeCache; |
| 108 } |
| 109 MediaType _contentTypeCache; |
| 110 |
| 111 /// Returns a [Stream] representing the body. |
| 112 /// |
| 113 /// Can only be called once. |
| 114 Stream<List<int>> read() => _body.read(); |
| 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 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 body}); |
| 133 } |
| 134 |
| 135 /// Adds information about [encoding] to [headers]. |
| 136 /// |
| 137 /// Returns a new map without modifying [headers]. |
| 138 Map<String, String> _adjustHeaders( |
| 139 Map<String, String> headers, Encoding encoding) { |
| 140 if (headers == null) headers = const {}; |
| 141 if (encoding == null) return headers; |
| 142 if (headers['content-type'] == null) { |
| 143 return addHeader(headers, 'content-type', |
| 144 'application/octet-stream; charset=${encoding.name}'); |
| 145 } |
| 146 |
| 147 var contentType = new MediaType.parse(headers['content-type']).change( |
| 148 parameters: {'charset': encoding.name}); |
| 149 return addHeader(headers, 'content-type', contentType.toString()); |
| 150 } |
OLD | NEW |