Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split package:http_profile into multiple files #1143

Merged
merged 2 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
541 changes: 1 addition & 540 deletions pkgs/http_profile/lib/http_profile.dart

Large diffs are not rendered by default.

113 changes: 113 additions & 0 deletions pkgs/http_profile/lib/src/http_client_request_profile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// 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';

/// Describes an event related to an HTTP request.
final class HttpProfileRequestEvent {
final int _timestamp;
final String _name;

HttpProfileRequestEvent({required DateTime timestamp, required String name})
: _timestamp = timestamp.microsecondsSinceEpoch,
_name = name;

Map<String, dynamic> _toJson() => <String, dynamic>{
'timestamp': _timestamp,
'event': _name,
};
}

/// A record of debugging information about an HTTP request.
final class HttpClientRequestProfile {
/// Whether HTTP profiling is enabled or not.
///
/// The value can be changed programmatically or through the DevTools Network
/// UX.
static bool get profilingEnabled => HttpClient.enableTimelineLogging;
static set profilingEnabled(bool enabled) =>
HttpClient.enableTimelineLogging = enabled;

final _data = <String, dynamic>{};

/// Records an event related to the request.
///
/// Usage example:
///
/// ```dart
/// profile.addEvent(
/// HttpProfileRequestEvent(
/// timestamp: DateTime.now(),
/// name: "Connection Established",
/// ),
/// );
/// profile.addEvent(
/// HttpProfileRequestEvent(
/// timestamp: DateTime.now(),
/// name: "Remote Disconnected",
/// ),
/// );
/// ```
void addEvent(HttpProfileRequestEvent event) {
(_data['events'] as List<Map<String, dynamic>>).add(event._toJson());
_updated();
}

/// Details about the request.
late final HttpProfileRequestData requestData;

/// Details about the response.
late final HttpProfileResponseData responseData;

void _updated() =>
_data['_lastUpdateTime'] = DateTime.now().microsecondsSinceEpoch;

HttpClientRequestProfile._({
required DateTime requestStartTime,
required String requestMethod,
required String requestUri,
}) {
_data['isolateId'] = Service.getIsolateId(Isolate.current)!;
_data['requestStartTimestamp'] = requestStartTime.microsecondsSinceEpoch;
_data['requestMethod'] = requestMethod;
_data['requestUri'] = requestUri;
_data['events'] = <Map<String, dynamic>>[];
_data['requestData'] = <String, dynamic>{};
requestData = HttpProfileRequestData._(_data, _updated);
_data['responseData'] = <String, dynamic>{};
responseData = HttpProfileResponseData._(
_data['responseData'] as Map<String, dynamic>, _updated);
_data['_requestBodyStream'] = requestData._body.stream;
_data['_responseBodyStream'] = responseData._body.stream;
// This entry is needed to support the updatedSince parameter of
// ext.dart.io.getHttpProfile.
_updated();
}

/// If HTTP profiling is enabled, returns an [HttpClientRequestProfile],
/// otherwise returns `null`.
static HttpClientRequestProfile? profile({
/// The time at which the request was initiated.
required DateTime requestStartTime,

/// The HTTP request method associated with the request.
required String requestMethod,

/// The URI to which the request was sent.
required String requestUri,
}) {
// Always return `null` in product mode so that the profiling code can be
// tree shaken away.
if (const bool.fromEnvironment('dart.vm.product') || !profilingEnabled) {
return null;
}
final requestProfile = HttpClientRequestProfile._(
requestStartTime: requestStartTime,
requestMethod: requestMethod,
requestUri: requestUri,
);
addHttpClientProfilingData(requestProfile._data);
return requestProfile;
}
}
14 changes: 14 additions & 0 deletions pkgs/http_profile/lib/src/http_profile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) 2024, 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:async';
import 'dart:developer' show Service, addHttpClientProfilingData;
import 'dart:io' show HttpClient, HttpClientResponseCompressionState;
import 'dart:isolate' show Isolate;

import 'utils.dart';

part 'http_client_request_profile.dart';
part 'http_profile_request_data.dart';
part 'http_profile_response_data.dart';
183 changes: 183 additions & 0 deletions pkgs/http_profile/lib/src/http_profile_request_data.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// 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;

HttpProfileProxyData({
String? host,
String? username,
bool? isDirect,
int? port,
}) : _host = host,
_username = username,
_isDirect = isDirect,
_port = port;

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>;

/// The body of the request.
StreamSink<List<int>> get bodySink => _body.sink;

/// Information about the networking connection used in the HTTP request.
///
/// This information is meant to be used for debugging.
///
/// It can contain any arbitrary data as long as the values are of type
/// [String] or [int]. For example:
/// { 'localPort': 1285, 'remotePort': 443, 'connectionPoolId': '21x23' }
set connectionInfo(Map<String, dynamic /*String|int*/ > value) {
_checkAndUpdate();
for (final v in value.values) {
if (!(v is String || v is int)) {
throw ArgumentError(
'The values in connectionInfo must be of type String or int.',
);
}
}
_requestData['connectionInfo'] = {...value};
}

/// The content length of the request, in bytes.
set contentLength(int? value) {
_checkAndUpdate();
if (value == null) {
_requestData.remove('contentLength');
} else {
_requestData['contentLength'] = value;
}
}

/// Whether automatic redirect following was enabled for the request.
set followRedirects(bool value) {
_checkAndUpdate();
_requestData['followRedirects'] = value;
}

/// 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);
}

/// The maximum number of redirects allowed during the request.
set maxRedirects(int value) {
_checkAndUpdate();
_requestData['maxRedirects'] = value;
}

/// The requested persistent connection state.
set persistentConnection(bool value) {
_checkAndUpdate();
_requestData['persistentConnection'] = value;
}

/// Proxy authentication details for the request.
set proxyDetails(HttpProfileProxyData value) {
_checkAndUpdate();
_requestData['proxyDetails'] = value._toJson();
}

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.
void close([DateTime? endTime]) {
_checkAndUpdate();
_isClosed = true;
unawaited(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.
void closeWithError(String value, [DateTime? endTime]) {
_checkAndUpdate();
_isClosed = true;
unawaited(bodySink.close());
_requestData['error'] = value;
_data['requestEndTimestamp'] =
(endTime ?? DateTime.now()).microsecondsSinceEpoch;
}
}
Loading