-
Notifications
You must be signed in to change notification settings - Fork 354
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split package:http_profile into multiple files
- Loading branch information
1 parent
69332d3
commit 4a0e236
Showing
11 changed files
with
899 additions
and
756 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
113 changes: 113 additions & 0 deletions
113
pkgs/http_profile/lib/src/http_client_request_profile.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 addHttpClientProfilingData, Service; | ||
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
183
pkgs/http_profile/lib/src/http_profile_request_data.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.