Skip to content

Commit 8da6e0e

Browse files
authored
[package:http_profile] Store request and response bodies in the backing map as lists instead of streams (#1154)
This PR also adds getters that return the request and response bodies as lists of ints
1 parent 8d3c647 commit 8da6e0e

7 files changed

+84
-61
lines changed

pkgs/http_profile/lib/src/http_client_request_profile.dart

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,15 @@ final class HttpClientRequestProfile {
7676
_data['requestData'] = <String, dynamic>{};
7777
requestData = HttpProfileRequestData._(_data, _updated);
7878
_data['responseData'] = <String, dynamic>{};
79-
responseData = HttpProfileResponseData._(
80-
_data['responseData'] as Map<String, dynamic>, _updated);
81-
_data['_requestBodyStream'] = requestData._body.stream;
82-
_data['_responseBodyStream'] = responseData._body.stream;
79+
responseData = HttpProfileResponseData._(_data, _updated);
80+
_data['requestBodyBytes'] = <int>[];
81+
requestData._body.stream.listen(
82+
(final bytes) => (_data['requestBodyBytes'] as List<int>).addAll(bytes),
83+
);
84+
_data['responseBodyBytes'] = <int>[];
85+
responseData._body.stream.listen(
86+
(final bytes) => (_data['responseBodyBytes'] as List<int>).addAll(bytes),
87+
);
8388
// This entry is needed to support the updatedSince parameter of
8489
// ext.dart.io.getHttpProfile.
8590
_updated();

pkgs/http_profile/lib/src/http_profile.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:collection' show UnmodifiableListView;
67
import 'dart:developer' show Service, addHttpClientProfilingData;
78
import 'dart:io' show HttpClient, HttpClientResponseCompressionState;
89
import 'dart:isolate' show Isolate;

pkgs/http_profile/lib/src/http_profile_request_data.dart

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,13 @@ final class HttpProfileRequestData {
3838
Map<String, dynamic> get _requestData =>
3939
_data['requestData'] as Map<String, dynamic>;
4040

41-
/// The body of the request.
41+
/// A sink that can be used to record the body of the request.
4242
StreamSink<List<int>> get bodySink => _body.sink;
4343

44+
/// The body of the request represented as an unmodifiable list of bytes.
45+
List<int> get bodyBytes =>
46+
UnmodifiableListView(_data['requestBodyBytes'] as List<int>);
47+
4448
/// Information about the networking connection used in the HTTP request.
4549
///
4650
/// This information is meant to be used for debugging.
@@ -155,10 +159,10 @@ final class HttpProfileRequestData {
155159
///
156160
/// [endTime] is the time when the request was fully sent. It defaults to the
157161
/// current time.
158-
void close([DateTime? endTime]) {
162+
Future<void> close([DateTime? endTime]) async {
159163
_checkAndUpdate();
160164
_isClosed = true;
161-
unawaited(bodySink.close());
165+
await bodySink.close();
162166
_data['requestEndTimestamp'] =
163167
(endTime ?? DateTime.now()).microsecondsSinceEpoch;
164168
}
@@ -172,10 +176,10 @@ final class HttpProfileRequestData {
172176
///
173177
/// [endTime] is the time when the error occurred. It defaults to the current
174178
/// time.
175-
void closeWithError(String value, [DateTime? endTime]) {
179+
Future<void> closeWithError(String value, [DateTime? endTime]) async {
176180
_checkAndUpdate();
177181
_isClosed = true;
178-
unawaited(bodySink.close());
182+
await bodySink.close();
179183
_requestData['error'] = value;
180184
_data['requestEndTimestamp'] =
181185
(endTime ?? DateTime.now()).microsecondsSinceEpoch;

pkgs/http_profile/lib/src/http_profile_response_data.dart

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,23 @@ final class HttpProfileResponseData {
3232
final void Function() _updated;
3333
final StreamController<List<int>> _body = StreamController<List<int>>();
3434

35+
Map<String, dynamic> get _responseData =>
36+
_data['responseData'] as Map<String, dynamic>;
37+
3538
/// Records a redirect that the connection went through.
3639
void addRedirect(HttpProfileRedirectData redirect) {
3740
_checkAndUpdate();
38-
(_data['redirects'] as List<Map<String, dynamic>>).add(redirect._toJson());
41+
(_responseData['redirects'] as List<Map<String, dynamic>>)
42+
.add(redirect._toJson());
3943
}
4044

41-
/// The body of the response.
45+
/// A sink that can be used to record the body of the response.
4246
StreamSink<List<int>> get bodySink => _body.sink;
4347

48+
/// The body of the response represented as an unmodifiable list of bytes.
49+
List<int> get bodyBytes =>
50+
UnmodifiableListView(_data['responseBodyBytes'] as List<int>);
51+
4452
/// Information about the networking connection used in the HTTP response.
4553
///
4654
/// This information is meant to be used for debugging.
@@ -57,7 +65,7 @@ final class HttpProfileResponseData {
5765
);
5866
}
5967
}
60-
_data['connectionInfo'] = {...value};
68+
_responseData['connectionInfo'] = {...value};
6169
}
6270

6371
/// The reponse headers where duplicate headers are represented using a list
@@ -74,10 +82,10 @@ final class HttpProfileResponseData {
7482
set headersListValues(Map<String, List<String>>? value) {
7583
_checkAndUpdate();
7684
if (value == null) {
77-
_data.remove('headers');
85+
_responseData.remove('headers');
7886
return;
7987
}
80-
_data['headers'] = {...value};
88+
_responseData['headers'] = {...value};
8189
}
8290

8391
/// The response headers where duplicate headers are represented using a
@@ -94,10 +102,10 @@ final class HttpProfileResponseData {
94102
set headersCommaValues(Map<String, String>? value) {
95103
_checkAndUpdate();
96104
if (value == null) {
97-
_data.remove('headers');
105+
_responseData.remove('headers');
98106
return;
99107
}
100-
_data['headers'] = splitHeaderValues(value);
108+
_responseData['headers'] = splitHeaderValues(value);
101109
}
102110

103111
// The compression state of the response.
@@ -107,57 +115,57 @@ final class HttpProfileResponseData {
107115
// uncompressed bytes when they listen to the response body byte stream.
108116
set compressionState(HttpClientResponseCompressionState value) {
109117
_checkAndUpdate();
110-
_data['compressionState'] = value.name;
118+
_responseData['compressionState'] = value.name;
111119
}
112120

113121
// The reason phrase associated with the response e.g. "OK".
114122
set reasonPhrase(String? value) {
115123
_checkAndUpdate();
116124
if (value == null) {
117-
_data.remove('reasonPhrase');
125+
_responseData.remove('reasonPhrase');
118126
} else {
119-
_data['reasonPhrase'] = value;
127+
_responseData['reasonPhrase'] = value;
120128
}
121129
}
122130

123131
/// Whether the status code was one of the normal redirect codes.
124132
set isRedirect(bool value) {
125133
_checkAndUpdate();
126-
_data['isRedirect'] = value;
134+
_responseData['isRedirect'] = value;
127135
}
128136

129137
/// The persistent connection state returned by the server.
130138
set persistentConnection(bool value) {
131139
_checkAndUpdate();
132-
_data['persistentConnection'] = value;
140+
_responseData['persistentConnection'] = value;
133141
}
134142

135143
/// The content length of the response body, in bytes.
136144
set contentLength(int? value) {
137145
_checkAndUpdate();
138146
if (value == null) {
139-
_data.remove('contentLength');
147+
_responseData.remove('contentLength');
140148
} else {
141-
_data['contentLength'] = value;
149+
_responseData['contentLength'] = value;
142150
}
143151
}
144152

145153
set statusCode(int value) {
146154
_checkAndUpdate();
147-
_data['statusCode'] = value;
155+
_responseData['statusCode'] = value;
148156
}
149157

150158
/// The time at which the initial response was received.
151159
set startTime(DateTime value) {
152160
_checkAndUpdate();
153-
_data['startTime'] = value.microsecondsSinceEpoch;
161+
_responseData['startTime'] = value.microsecondsSinceEpoch;
154162
}
155163

156164
HttpProfileResponseData._(
157165
this._data,
158166
this._updated,
159167
) {
160-
_data['redirects'] = <Map<String, dynamic>>[];
168+
_responseData['redirects'] = <Map<String, dynamic>>[];
161169
}
162170

163171
void _checkAndUpdate() {
@@ -176,11 +184,12 @@ final class HttpProfileResponseData {
176184
///
177185
/// [endTime] is the time when the response was fully received. It defaults
178186
/// to the current time.
179-
void close([DateTime? endTime]) {
187+
Future<void> close([DateTime? endTime]) async {
180188
_checkAndUpdate();
181189
_isClosed = true;
182-
unawaited(bodySink.close());
183-
_data['endTime'] = (endTime ?? DateTime.now()).microsecondsSinceEpoch;
190+
await bodySink.close();
191+
_responseData['endTime'] =
192+
(endTime ?? DateTime.now()).microsecondsSinceEpoch;
184193
}
185194

186195
/// Signal that receiving the response has failed with an error.
@@ -192,11 +201,12 @@ final class HttpProfileResponseData {
192201
///
193202
/// [endTime] is the time when the error occurred. It defaults to the current
194203
/// time.
195-
void closeWithError(String value, [DateTime? endTime]) {
204+
Future<void> closeWithError(String value, [DateTime? endTime]) async {
196205
_checkAndUpdate();
197206
_isClosed = true;
198-
unawaited(bodySink.close());
199-
_data['error'] = value;
200-
_data['endTime'] = (endTime ?? DateTime.now()).microsecondsSinceEpoch;
207+
await bodySink.close();
208+
_responseData['error'] = value;
209+
_responseData['endTime'] =
210+
(endTime ?? DateTime.now()).microsecondsSinceEpoch;
201211
}
202212
}

pkgs/http_profile/test/close_test.dart

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ void main() {
2828
group('requestData.close', () {
2929
test('no arguments', () async {
3030
expect(backingMap['requestEndTimestamp'], isNull);
31-
profile.requestData.close();
31+
await profile.requestData.close();
3232

3333
expect(
3434
backingMap['requestEndTimestamp'],
@@ -39,7 +39,7 @@ void main() {
3939

4040
test('with time', () async {
4141
expect(backingMap['requestEndTimestamp'], isNull);
42-
profile.requestData.close(DateTime.parse('2024-03-23'));
42+
await profile.requestData.close(DateTime.parse('2024-03-23'));
4343

4444
expect(
4545
backingMap['requestEndTimestamp'],
@@ -48,7 +48,7 @@ void main() {
4848
});
4949

5050
test('then write body', () async {
51-
profile.requestData.close();
51+
await profile.requestData.close();
5252

5353
expect(
5454
() => profile.requestData.bodySink.add([1, 2, 3]),
@@ -57,7 +57,7 @@ void main() {
5757
});
5858

5959
test('then mutate', () async {
60-
profile.requestData.close();
60+
await profile.requestData.close();
6161

6262
expect(
6363
() => profile.requestData.contentLength = 5,
@@ -75,7 +75,7 @@ void main() {
7575

7676
test('no arguments', () async {
7777
expect(responseData['endTime'], isNull);
78-
profile.responseData.close();
78+
await profile.responseData.close();
7979

8080
expect(
8181
responseData['endTime'],
@@ -86,7 +86,7 @@ void main() {
8686

8787
test('with time', () async {
8888
expect(responseData['endTime'], isNull);
89-
profile.responseData.close(DateTime.parse('2024-03-23'));
89+
await profile.responseData.close(DateTime.parse('2024-03-23'));
9090

9191
expect(
9292
responseData['endTime'],
@@ -95,7 +95,7 @@ void main() {
9595
});
9696

9797
test('then write body', () async {
98-
profile.responseData.close();
98+
await profile.responseData.close();
9999

100100
expect(
101101
() => profile.responseData.bodySink.add([1, 2, 3]),
@@ -104,7 +104,7 @@ void main() {
104104
});
105105

106106
test('then mutate', () async {
107-
profile.responseData.close();
107+
await profile.responseData.close();
108108

109109
expect(
110110
() => profile.responseData.contentLength = 5,

pkgs/http_profile/test/http_profile_request_data_test.dart

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ void main() {
4141

4242
test('populating HttpClientRequestProfile.requestEndTimestamp', () async {
4343
expect(backingMap['requestEndTimestamp'], isNull);
44-
profile.requestData.close(DateTime.parse('2024-03-23'));
44+
await profile.requestData.close(DateTime.parse('2024-03-23'));
4545

4646
expect(
4747
backingMap['requestEndTimestamp'],
@@ -91,7 +91,7 @@ void main() {
9191
final requestData = backingMap['requestData'] as Map<String, dynamic>;
9292
expect(requestData['error'], isNull);
9393

94-
profile.requestData.closeWithError('failed');
94+
await profile.requestData.closeWithError('failed');
9595

9696
expect(requestData['error'], 'failed');
9797
});
@@ -185,4 +185,16 @@ void main() {
185185
expect(proxyDetails['isDirect'], true);
186186
expect(proxyDetails['port'], 4321);
187187
});
188+
189+
test('using HttpClientRequestProfile.requestData.bodySink', () async {
190+
final requestBodyBytes = backingMap['requestBodyBytes'] as List<int>;
191+
expect(requestBodyBytes, isEmpty);
192+
expect(profile.requestData.bodyBytes, isEmpty);
193+
194+
profile.requestData.bodySink.add([1, 2, 3]);
195+
await profile.requestData.close();
196+
197+
expect(requestBodyBytes, [1, 2, 3]);
198+
expect(profile.requestData.bodyBytes, [1, 2, 3]);
199+
});
188200
}

pkgs/http_profile/test/http_profile_response_data_test.dart

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:async';
65
import 'dart:developer' show getHttpClientProfilingData;
76
import 'dart:io';
87

@@ -187,7 +186,7 @@ void main() {
187186
final responseData = backingMap['responseData'] as Map<String, dynamic>;
188187
expect(responseData['endTime'], isNull);
189188

190-
profile.responseData.close(DateTime.parse('2024-03-23'));
189+
await profile.responseData.close(DateTime.parse('2024-03-23'));
191190

192191
expect(
193192
responseData['endTime'],
@@ -199,28 +198,20 @@ void main() {
199198
final responseData = backingMap['responseData'] as Map<String, dynamic>;
200199
expect(responseData['error'], isNull);
201200

202-
profile.responseData.closeWithError('failed');
201+
await profile.responseData.closeWithError('failed');
203202

204203
expect(responseData['error'], 'failed');
205204
});
206205

207-
test('using HttpClientRequestProfile.requestBodySink', () async {
208-
final requestBodyStream =
209-
backingMap['_requestBodyStream'] as Stream<List<int>>;
210-
211-
profile.requestData.bodySink.add([1, 2, 3]);
212-
profile.requestData.close();
213-
214-
expect(await requestBodyStream.expand((i) => i).toList(), [1, 2, 3]);
215-
});
216-
217-
test('using HttpClientRequestProfile.responseBodySink', () async {
218-
final responseBodyStream =
219-
backingMap['_responseBodyStream'] as Stream<List<int>>;
206+
test('using HttpClientRequestProfile.responseData.bodySink', () async {
207+
final responseBodyBytes = backingMap['responseBodyBytes'] as List<int>;
208+
expect(responseBodyBytes, isEmpty);
209+
expect(profile.responseData.bodyBytes, isEmpty);
220210

221211
profile.responseData.bodySink.add([1, 2, 3]);
222-
profile.responseData.close();
212+
await profile.responseData.close();
223213

224-
expect(await responseBodyStream.expand((i) => i).toList(), [1, 2, 3]);
214+
expect(responseBodyBytes, [1, 2, 3]);
215+
expect(profile.responseData.bodyBytes, [1, 2, 3]);
225216
});
226217
}

0 commit comments

Comments
 (0)