blob: 60b1d8e8fed5d32bb22a3ddefe144ab0890bc633 [file] [log] [blame]
// Copyright (c) 2023, 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.
part of 'http_profile.dart';
final class HttpProfileProxyData {
final String? _host;
final String? _username;
final bool? _isDirect;
final int? _port;
String? get host => _host;
String? get username => _username;
bool? get isDirect => _isDirect;
int? get port => _port;
HttpProfileProxyData({
String? host,
String? username,
bool? isDirect,
int? port,
}) : _host = host,
_username = username,
_isDirect = isDirect,
_port = port;
static HttpProfileProxyData _fromJson(Map<String, dynamic> json) =>
HttpProfileProxyData(
host: json['host'] as String?,
username: json['username'] as String?,
isDirect: json['isDirect'] as bool?,
port: json['port'] as int?,
);
Map<String, dynamic> _toJson() => <String, dynamic>{
if (_host != null) 'host': _host,
if (_username != null) 'username': _username,
if (_isDirect != null) 'isDirect': _isDirect,
if (_port != null) 'port': _port,
};
}
/// Describes details about an HTTP request.
final class HttpProfileRequestData {
final Map<String, dynamic> _data;
final StreamController<List<int>> _body = StreamController<List<int>>();
bool _isClosed = false;
final void Function() _updated;
Map<String, dynamic> get _requestData =>
_data['requestData'] as Map<String, dynamic>;
/// A sink that can be used to record the body of the request.
///
/// Errors added to [bodySink] (for example with [StreamSink.addError]) are
/// ignored.
StreamSink<List<int>> get bodySink => _body.sink;
/// The body of the request represented as an unmodifiable list of bytes.
List<int> get bodyBytes =>
UnmodifiableListView(_data['requestBodyBytes'] as List<int>);
/// The content length of the request, in bytes.
set contentLength(int? value) {
_checkAndUpdate();
if (value == null) {
_requestData.remove('contentLength');
} else {
_requestData['contentLength'] = value;
}
}
int? get contentLength => _requestData['contentLength'] as int?;
/// Whether automatic redirect following was enabled for the request.
set followRedirects(bool? value) {
_checkAndUpdate();
if (value == null) {
_requestData.remove('followRedirects');
} else {
_requestData['followRedirects'] = value;
}
}
bool? get followRedirects => _requestData['followRedirects'] as bool?;
/// The request headers where duplicate headers are represented using a list
/// of values.
///
/// For example:
///
/// ```dart
/// // Foo: Bar
/// // Foo: Baz
///
/// profile?.requestData.headersListValues({'Foo': ['Bar', 'Baz']});
/// ```
set headersListValues(Map<String, List<String>>? value) {
_checkAndUpdate();
if (value == null) {
_requestData.remove('headers');
return;
}
_requestData['headers'] = {...value};
}
/// The request headers where duplicate headers are represented using a
/// comma-separated list of values.
///
/// For example:
///
/// ```dart
/// // Foo: Bar
/// // Foo: Baz
///
/// profile?.requestData.headersCommaValues({'Foo': 'Bar, Baz']});
/// ```
set headersCommaValues(Map<String, String>? value) {
_checkAndUpdate();
if (value == null) {
_requestData.remove('headers');
return;
}
_requestData['headers'] = splitHeaderValues(value);
}
/// An unmodifiable map representing the request headers. Duplicate headers
/// are represented using a list of values.
///
/// For example, the map
///
/// ```dart
/// {'Foo': ['Bar', 'Baz']});
/// ```
///
/// represents the headers
///
/// Foo: Bar
/// Foo: Baz
Map<String, List<String>>? get headers => _requestData['headers'] == null
? null
: UnmodifiableMapView(
_requestData['headers'] as Map<String, List<String>>);
/// The maximum number of redirects allowed during the request.
set maxRedirects(int? value) {
_checkAndUpdate();
if (value == null) {
_requestData.remove('maxRedirects');
} else {
_requestData['maxRedirects'] = value;
}
}
int? get maxRedirects => _requestData['maxRedirects'] as int?;
/// The requested persistent connection state.
set persistentConnection(bool? value) {
_checkAndUpdate();
if (value == null) {
_requestData.remove('persistentConnection');
} else {
_requestData['persistentConnection'] = value;
}
}
bool? get persistentConnection =>
_requestData['persistentConnection'] as bool?;
/// Proxy authentication details for the request.
set proxyDetails(HttpProfileProxyData? value) {
_checkAndUpdate();
if (value == null) {
_requestData.remove('proxyDetails');
} else {
_requestData['proxyDetails'] = value._toJson();
}
}
HttpProfileProxyData? get proxyDetails => _requestData['proxyDetails'] == null
? null
: HttpProfileProxyData._fromJson(
_requestData['proxyDetails'] as Map<String, dynamic>,
);
/// The time at which the request was initiated.
DateTime get startTime => DateTime.fromMicrosecondsSinceEpoch(
_data['requestStartTimestamp'] as int,
);
/// The time when the request was fully sent.
DateTime? get endTime => _data['requestEndTimestamp'] == null
? null
: DateTime.fromMicrosecondsSinceEpoch(
_data['requestEndTimestamp'] as int,
);
/// The error that the request failed with.
String? get error =>
_requestData['error'] == null ? null : _requestData['error'] as String;
HttpProfileRequestData._(
this._data,
this._updated,
);
void _checkAndUpdate() {
if (_isClosed) {
throw StateError('HttpProfileResponseData has been closed, no further '
'updates are allowed');
}
_updated();
}
/// Signal that the request, including the entire request body, has been
/// sent.
///
/// [bodySink] will be closed and the fields of [HttpProfileRequestData] will
/// no longer be changeable.
///
/// [endTime] is the time when the request was fully sent. It defaults to the
/// current time.
Future<void> close([DateTime? endTime]) async {
_checkAndUpdate();
_isClosed = true;
await bodySink.close();
_data['requestEndTimestamp'] =
(endTime ?? DateTime.now()).microsecondsSinceEpoch;
}
/// Signal that sending the request has failed with an error.
///
/// [bodySink] will be closed and the fields of [HttpProfileRequestData] will
/// no longer be changeable.
///
/// [value] is a textual description of the error e.g. 'host does not exist'.
///
/// [endTime] is the time when the error occurred. It defaults to the current
/// time.
Future<void> closeWithError(String value, [DateTime? endTime]) async {
_checkAndUpdate();
_isClosed = true;
await bodySink.close();
_requestData['error'] = value;
_data['requestEndTimestamp'] =
(endTime ?? DateTime.now()).microsecondsSinceEpoch;
}
}