OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 request; | 5 library request; |
6 | 6 |
7 import 'dart:convert'; | 7 import 'dart:convert'; |
8 import 'dart:io'; | |
9 import 'dart:typed_data'; | 8 import 'dart:typed_data'; |
10 | 9 |
| 10 import 'package:http_parser/http_parser.dart'; |
| 11 |
11 import 'base_request.dart'; | 12 import 'base_request.dart'; |
12 import 'byte_stream.dart'; | 13 import 'byte_stream.dart'; |
13 import 'utils.dart'; | 14 import 'utils.dart'; |
14 | 15 |
15 /// An HTTP request where the entire request body is known in advance. | 16 /// An HTTP request where the entire request body is known in advance. |
16 class Request extends BaseRequest { | 17 class Request extends BaseRequest { |
17 /// The size of the request body, in bytes. This is calculated from | 18 /// The size of the request body, in bytes. This is calculated from |
18 /// [bodyBytes]. | 19 /// [bodyBytes]. |
19 /// | 20 /// |
20 /// The content length cannot be set for [Request], since it's automatically | 21 /// The content length cannot be set for [Request], since it's automatically |
(...skipping 17 matching lines...) Expand all Loading... |
38 /// parameter, that parameter's value is used as the encoding. Otherwise, if | 39 /// parameter, that parameter's value is used as the encoding. Otherwise, if |
39 /// [encoding] has been set manually, that encoding is used. If that hasn't | 40 /// [encoding] has been set manually, that encoding is used. If that hasn't |
40 /// been set either, this defaults to [UTF8]. | 41 /// been set either, this defaults to [UTF8]. |
41 /// | 42 /// |
42 /// If the `charset` parameter's value is not a known [Encoding], reading this | 43 /// If the `charset` parameter's value is not a known [Encoding], reading this |
43 /// will throw a [FormatException]. | 44 /// will throw a [FormatException]. |
44 /// | 45 /// |
45 /// If the request has a `Content-Type` header, setting this will set the | 46 /// If the request has a `Content-Type` header, setting this will set the |
46 /// charset parameter on that header. | 47 /// charset parameter on that header. |
47 Encoding get encoding { | 48 Encoding get encoding { |
48 if (_contentType == null || _contentType.charset == null) { | 49 if (_contentType == null || |
| 50 !_contentType.parameters.containsKey('charset')) { |
49 return _defaultEncoding; | 51 return _defaultEncoding; |
50 } | 52 } |
51 return requiredEncodingForCharset(_contentType.charset); | 53 return requiredEncodingForCharset(_contentType.parameters['charset']); |
52 } | 54 } |
53 | 55 |
54 set encoding(Encoding value) { | 56 set encoding(Encoding value) { |
55 _checkFinalized(); | 57 _checkFinalized(); |
56 _defaultEncoding = value; | 58 _defaultEncoding = value; |
57 var contentType = _contentType; | 59 var contentType = _contentType; |
58 if (contentType != null) { | 60 if (contentType == null) return; |
59 contentType = new ContentType(contentType.primaryType, | 61 _contentType = contentType.change(parameters: {'charset': value.name}); |
60 contentType.subType, | |
61 charset: value.name, | |
62 parameters: contentType.parameters); | |
63 _contentType = contentType; | |
64 } | |
65 } | 62 } |
66 | 63 |
67 // TODO(nweiz): make this return a read-only view | 64 // TODO(nweiz): make this return a read-only view |
68 /// The bytes comprising the body of the request. This is converted to and | 65 /// The bytes comprising the body of the request. This is converted to and |
69 /// from [body] using [encoding]. | 66 /// from [body] using [encoding]. |
70 /// | 67 /// |
71 /// This list should only be set, not be modified in place. | 68 /// This list should only be set, not be modified in place. |
72 Uint8List get bodyBytes => _bodyBytes; | 69 Uint8List get bodyBytes => _bodyBytes; |
73 Uint8List _bodyBytes; | 70 Uint8List _bodyBytes; |
74 | 71 |
75 set bodyBytes(List<int> value) { | 72 set bodyBytes(List<int> value) { |
76 _checkFinalized(); | 73 _checkFinalized(); |
77 _bodyBytes = toUint8List(value); | 74 _bodyBytes = toUint8List(value); |
78 } | 75 } |
79 | 76 |
80 /// The body of the request as a string. This is converted to and from | 77 /// The body of the request as a string. This is converted to and from |
81 /// [bodyBytes] using [encoding]. | 78 /// [bodyBytes] using [encoding]. |
82 /// | 79 /// |
83 /// When this is set, if the request does not yet have a `Content-Type` | 80 /// When this is set, if the request does not yet have a `Content-Type` |
84 /// header, one will be added with the type `text/plain`. Then the `charset` | 81 /// header, one will be added with the type `text/plain`. Then the `charset` |
85 /// parameter of the `Content-Type` header (whether new or pre-existing) will | 82 /// parameter of the `Content-Type` header (whether new or pre-existing) will |
86 /// be set to [encoding] if it wasn't already set. | 83 /// be set to [encoding] if it wasn't already set. |
87 String get body => encoding.decode(bodyBytes); | 84 String get body => encoding.decode(bodyBytes); |
88 | 85 |
89 set body(String value) { | 86 set body(String value) { |
90 bodyBytes = encoding.encode(value); | 87 bodyBytes = encoding.encode(value); |
91 var contentType = _contentType; | 88 var contentType = _contentType; |
92 if (contentType == null) { | 89 if (contentType == null) { |
93 contentType = new ContentType("text", "plain", charset: encoding.name); | 90 _contentType = new MediaType("text", "plain", {'charset': encoding.name}); |
94 } else if (contentType.charset == null) { | 91 } else if (!contentType.parameters.containsKey('charset')) { |
95 contentType = new ContentType(contentType.primaryType, | 92 _contentType = contentType.change(parameters: {'charset': encoding.name}); |
96 contentType.subType, | |
97 charset: encoding.name, | |
98 parameters: contentType.parameters); | |
99 } | 93 } |
100 _contentType = contentType; | |
101 } | 94 } |
102 | 95 |
103 /// The form-encoded fields in the body of the request as a map from field | 96 /// The form-encoded fields in the body of the request as a map from field |
104 /// names to values. The form-encoded body is converted to and from | 97 /// names to values. The form-encoded body is converted to and from |
105 /// [bodyBytes] using [encoding] (in the same way as [body]). | 98 /// [bodyBytes] using [encoding] (in the same way as [body]). |
106 /// | 99 /// |
107 /// If the request doesn't have a `Content-Type` header of | 100 /// If the request doesn't have a `Content-Type` header of |
108 /// `application/x-www-form-urlencoded`, reading this will throw a | 101 /// `application/x-www-form-urlencoded`, reading this will throw a |
109 /// [StateError]. | 102 /// [StateError]. |
110 /// | 103 /// |
111 /// If the request has a `Content-Type` header with a type other than | 104 /// If the request has a `Content-Type` header with a type other than |
112 /// `application/x-www-form-urlencoded`, setting this will throw a | 105 /// `application/x-www-form-urlencoded`, setting this will throw a |
113 /// [StateError]. Otherwise, the content type will be set to | 106 /// [StateError]. Otherwise, the content type will be set to |
114 /// `application/x-www-form-urlencoded`. | 107 /// `application/x-www-form-urlencoded`. |
115 /// | 108 /// |
116 /// This map should only be set, not modified in place. | 109 /// This map should only be set, not modified in place. |
117 Map<String, String> get bodyFields { | 110 Map<String, String> get bodyFields { |
118 if (_contentType == null || | 111 var contentType = _contentType; |
119 _contentType.value != "application/x-www-form-urlencoded") { | 112 if (contentType == null || |
| 113 contentType.mimeType != "application/x-www-form-urlencoded") { |
120 throw new StateError('Cannot access the body fields of a Request without ' | 114 throw new StateError('Cannot access the body fields of a Request without ' |
121 'content-type "application/x-www-form-urlencoded".'); | 115 'content-type "application/x-www-form-urlencoded".'); |
122 } | 116 } |
123 | 117 |
124 return queryToMap(body, encoding: encoding); | 118 return queryToMap(body, encoding: encoding); |
125 } | 119 } |
126 | 120 |
127 set bodyFields(Map<String, String> fields) { | 121 set bodyFields(Map<String, String> fields) { |
128 if (_contentType == null) { | 122 var contentType = _contentType; |
129 _contentType = new ContentType("application", "x-www-form-urlencoded"); | 123 if (contentType == null) { |
130 } else if (_contentType.value != "application/x-www-form-urlencoded") { | 124 _contentType = new MediaType("application", "x-www-form-urlencoded"); |
| 125 } else if (contentType.mimeType != "application/x-www-form-urlencoded") { |
131 throw new StateError('Cannot set the body fields of a Request with ' | 126 throw new StateError('Cannot set the body fields of a Request with ' |
132 'content-type "${_contentType.value}".'); | 127 'content-type "${contentType.mimeType}".'); |
133 } | 128 } |
134 | 129 |
135 this.body = mapToQuery(fields, encoding: encoding); | 130 this.body = mapToQuery(fields, encoding: encoding); |
136 } | 131 } |
137 | 132 |
138 /// Creates a new HTTP request. | 133 /// Creates a new HTTP request. |
139 Request(String method, Uri url) | 134 Request(String method, Uri url) |
140 : super(method, url), | 135 : super(method, url), |
141 _defaultEncoding = UTF8, | 136 _defaultEncoding = UTF8, |
142 _bodyBytes = new Uint8List(0); | 137 _bodyBytes = new Uint8List(0); |
143 | 138 |
144 /// Freezes all mutable fields and returns a single-subscription [ByteStream] | 139 /// Freezes all mutable fields and returns a single-subscription [ByteStream] |
145 /// containing the request body. | 140 /// containing the request body. |
146 ByteStream finalize() { | 141 ByteStream finalize() { |
147 super.finalize(); | 142 super.finalize(); |
148 return new ByteStream.fromBytes(bodyBytes); | 143 return new ByteStream.fromBytes(bodyBytes); |
149 } | 144 } |
150 | 145 |
151 /// The `Content-Type` header of the request (if it exists) as a | 146 /// The `Content-Type` header of the request (if it exists) as a |
152 /// [ContentType]. | 147 /// [MediaType]. |
153 ContentType get _contentType { | 148 MediaType get _contentType { |
154 var contentType = headers[HttpHeaders.CONTENT_TYPE]; | 149 var contentType = headers['content-type']; |
155 if (contentType == null) return null; | 150 if (contentType == null) return null; |
156 return ContentType.parse(contentType); | 151 return new MediaType.parse(contentType); |
157 } | 152 } |
158 | 153 |
159 set _contentType(ContentType value) { | 154 set _contentType(MediaType value) { |
160 headers[HttpHeaders.CONTENT_TYPE] = value.toString(); | 155 headers['content-type'] = value.toString(); |
161 } | 156 } |
162 | 157 |
163 /// Throw an error if this request has been finalized. | 158 /// Throw an error if this request has been finalized. |
164 void _checkFinalized() { | 159 void _checkFinalized() { |
165 if (!finalized) return; | 160 if (!finalized) return; |
166 throw new StateError("Can't modify a finalized Request."); | 161 throw new StateError("Can't modify a finalized Request."); |
167 } | 162 } |
168 } | 163 } |
OLD | NEW |