| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:convert'; | |
| import 'dart:typed_data'; | |
| import 'package:http_parser/http_parser.dart'; | |
| import 'base_request.dart'; | |
| import 'byte_stream.dart'; | |
| import 'utils.dart'; | |
| /// An HTTP request where the entire request body is known in advance. | |
| class Request extends BaseRequest { | |
| /// The size of the request body, in bytes. This is calculated from | |
| /// [bodyBytes]. | |
| /// | |
| /// The content length cannot be set for [Request], since it's automatically | |
| /// calculated from [bodyBytes]. | |
| @override | |
| int get contentLength => bodyBytes.length; | |
| @override | |
| set contentLength(int? value) { | |
| throw UnsupportedError('Cannot set the contentLength property of ' | |
| 'non-streaming Request objects.'); | |
| } | |
| /// The default encoding to use when converting between [bodyBytes] and | |
| /// [body]. | |
| /// | |
| /// This is only used if [encoding] hasn't been manually set and if the | |
| /// content-type header has no encoding information. | |
| Encoding _defaultEncoding; | |
| /// The encoding used for the request. | |
| /// | |
| /// This encoding is used when converting between [bodyBytes] and [body]. | |
| /// | |
| /// If the request has a `Content-Type` header and that header has a `charset` | |
| /// parameter, that parameter's value is used as the encoding. Otherwise, if | |
| /// [encoding] has been set manually, that encoding is used. If that hasn't | |
| /// been set either, this defaults to [utf8]. | |
| /// | |
| /// If the `charset` parameter's value is not a known [Encoding], reading this | |
| /// will throw a [FormatException]. | |
| /// | |
| /// If the request has a `Content-Type` header, setting this will set the | |
| /// charset parameter on that header. | |
| Encoding get encoding { | |
| if (_contentType == null || | |
| !_contentType!.parameters.containsKey('charset')) { | |
| return _defaultEncoding; | |
| } | |
| return requiredEncodingForCharset(_contentType!.parameters['charset']!); | |
| } | |
| set encoding(Encoding value) { | |
| _checkFinalized(); | |
| _defaultEncoding = value; | |
| var contentType = _contentType; | |
| if (contentType == null) return; | |
| _contentType = contentType.change(parameters: {'charset': value.name}); | |
| } | |
| // TODO(nweiz): make this return a read-only view | |
| /// The bytes comprising the body of the request. | |
| /// | |
| /// This is converted to and from [body] using [encoding]. | |
| /// | |
| /// This list should only be set, not modified in place. | |
| /// | |
| /// Unlike [body], setting [bodyBytes] does not implicitly set a | |
| /// `Content-Type` header. | |
| /// | |
| /// ```dart | |
| /// final request = Request('GET', Uri.https('example.com', 'whatsit/create')) | |
| /// ..bodyBytes = utf8.encode(jsonEncode({})) | |
| /// ..headers['content-type'] = 'application/json'; | |
| /// ``` | |
| Uint8List get bodyBytes => _bodyBytes; | |
| Uint8List _bodyBytes; | |
| set bodyBytes(List<int> value) { | |
| _checkFinalized(); | |
| _bodyBytes = toUint8List(value); | |
| } | |
| /// The body of the request as a string. | |
| /// | |
| /// This is converted to and from [bodyBytes] using [encoding]. | |
| /// | |
| /// When this is set, if the request does not yet have a `Content-Type` | |
| /// header, one will be added with the type `text/plain`. Then the `charset` | |
| /// parameter of the `Content-Type` header (whether new or pre-existing) will | |
| /// be set to [encoding] if it wasn't already set. | |
| /// | |
| /// To set the body of the request, without setting the `Content-Type` header, | |
| /// use [bodyBytes]. | |
| String get body => encoding.decode(bodyBytes); | |
| set body(String value) { | |
| bodyBytes = encoding.encode(value); | |
| var contentType = _contentType; | |
| if (contentType == null) { | |
| _contentType = MediaType('text', 'plain', {'charset': encoding.name}); | |
| } else if (!contentType.parameters.containsKey('charset')) { | |
| _contentType = contentType.change(parameters: {'charset': encoding.name}); | |
| } | |
| } | |
| /// The form-encoded fields in the body of the request as a map from field | |
| /// names to values. | |
| /// | |
| /// The form-encoded body is converted to and from [bodyBytes] using | |
| /// [encoding] (in the same way as [body]). | |
| /// | |
| /// If the request doesn't have a `Content-Type` header of | |
| /// `application/x-www-form-urlencoded`, reading this will throw a | |
| /// [StateError]. | |
| /// | |
| /// If the request has a `Content-Type` header with a type other than | |
| /// `application/x-www-form-urlencoded`, setting this will throw a | |
| /// [StateError]. Otherwise, the content type will be set to | |
| /// `application/x-www-form-urlencoded`. | |
| /// | |
| /// This map should only be set, not modified in place. | |
| Map<String, String> get bodyFields { | |
| var contentType = _contentType; | |
| if (contentType == null || | |
| contentType.mimeType != 'application/x-www-form-urlencoded') { | |
| throw StateError('Cannot access the body fields of a Request without ' | |
| 'content-type "application/x-www-form-urlencoded".'); | |
| } | |
| return Uri.splitQueryString(body, encoding: encoding); | |
| } | |
| set bodyFields(Map<String, String> fields) { | |
| var contentType = _contentType; | |
| if (contentType == null) { | |
| _contentType = MediaType('application', 'x-www-form-urlencoded'); | |
| } else if (contentType.mimeType != 'application/x-www-form-urlencoded') { | |
| throw StateError('Cannot set the body fields of a Request with ' | |
| 'content-type "${contentType.mimeType}".'); | |
| } | |
| body = mapToQuery(fields, encoding: encoding); | |
| } | |
| Request(super.method, super.url) | |
| : _defaultEncoding = utf8, | |
| _bodyBytes = Uint8List(0); | |
| /// Freezes all mutable fields and returns a single-subscription [ByteStream] | |
| /// containing the request body. | |
| @override | |
| ByteStream finalize() { | |
| super.finalize(); | |
| return ByteStream.fromBytes(bodyBytes); | |
| } | |
| /// The `Content-Type` header of the request (if it exists) as a [MediaType]. | |
| MediaType? get _contentType { | |
| var contentType = headers['content-type']; | |
| if (contentType == null) return null; | |
| return MediaType.parse(contentType); | |
| } | |
| set _contentType(MediaType? value) { | |
| if (value == null) { | |
| headers.remove('content-type'); | |
| } else { | |
| headers['content-type'] = value.toString(); | |
| } | |
| } | |
| /// Throw an error if this request has been finalized. | |
| void _checkFinalized() { | |
| if (!finalized) return; | |
| throw StateError("Can't modify a finalized Request."); | |
| } | |
| } |