blob: 20da3c1292cb5c9e45c00b571414a59cbbef7267 [file] [log] [blame]
// 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.
library request;
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:uri';
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].
int get contentLength => bodyBytes.length;
set contentLength(int value) {
throw new 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 [Encoding.UTF_8].
///
/// 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.charset == null) {
return _defaultEncoding;
}
return requiredEncodingForCharset(_contentType.charset);
}
set encoding(Encoding value) {
_checkFinalized();
_defaultEncoding = value;
var contentType = _contentType;
if (contentType != null) {
contentType = new ContentType(contentType.primaryType,
contentType.subType,
charset: value.name,
parameters: contentType.parameters);
_contentType = contentType;
}
}
// 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 be modified in place.
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.
String get body => decodeString(bodyBytes, encoding);
set body(String value) {
bodyBytes = encodeString(value, encoding);
var contentType = _contentType;
if (contentType == null) {
contentType = new ContentType("text", "plain", charset: encoding.name);
} else if (contentType.charset == null) {
contentType = new ContentType(contentType.primaryType,
contentType.subType,
charset: encoding.name,
parameters: contentType.parameters);
}
_contentType = contentType;
}
/// 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 {
if (_contentType == null ||
_contentType.value != "application/x-www-form-urlencoded") {
throw new StateError('Cannot access the body fields of a Request without '
'content-type "application/x-www-form-urlencoded".');
}
return queryToMap(body);
}
set bodyFields(Map<String, String> fields) {
if (_contentType == null) {
_contentType = new ContentType("application", "x-www-form-urlencoded");
} else if (_contentType.value != "application/x-www-form-urlencoded") {
throw new StateError('Cannot set the body fields of a Request with '
'content-type "${_contentType.value}".');
}
this.body = mapToQuery(fields);
}
/// Creates a new HTTP request.
Request(String method, Uri url)
: super(method, url),
_defaultEncoding = Encoding.UTF_8,
_bodyBytes = new Uint8List(0);
/// Freezes all mutable fields and returns a single-subscription [ByteStream]
/// containing the request body.
ByteStream finalize() {
super.finalize();
return new ByteStream.fromBytes(bodyBytes);
}
/// The `Content-Type` header of the request (if it exists) as a
/// [ContentType].
ContentType get _contentType {
var contentType = headers[HttpHeaders.CONTENT_TYPE];
if (contentType == null) return null;
return new ContentType.fromString(contentType);
}
set _contentType(ContentType value) {
headers[HttpHeaders.CONTENT_TYPE] = value.toString();
}
/// Throw an error if this request has been finalized.
void _checkFinalized() {
if (!finalized) return;
throw new StateError("Can't modify a finalized Request.");
}
}