diff --git a/packages/devtools_app/lib/src/screens/network/constants.dart b/packages/devtools_app/lib/src/screens/network/constants.dart index b12334fac8b..e8fa703e4be 100644 --- a/packages/devtools_app/lib/src/screens/network/constants.dart +++ b/packages/devtools_app/lib/src/screens/network/constants.dart @@ -56,6 +56,7 @@ enum NetworkEventKeys { httpOnly, secure, reasonPhrase, + encoding, } class NetworkEventDefaults { diff --git a/packages/devtools_app/lib/src/screens/network/har_data_entry.dart b/packages/devtools_app/lib/src/screens/network/har_data_entry.dart index fe50b818403..bd9316d0ec3 100644 --- a/packages/devtools_app/lib/src/screens/network/har_data_entry.dart +++ b/packages/devtools_app/lib/src/screens/network/har_data_entry.dart @@ -3,10 +3,10 @@ // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. import 'dart:convert'; +import 'dart:typed_data'; import '../../screens/network/utils/http_utils.dart'; import '../../shared/http/http_request_data.dart'; -import '../../shared/primitives/utils.dart'; import 'constants.dart'; /// A class representing a single entry in the HTTP Archive (HAR) format. @@ -119,6 +119,11 @@ class HarDataEntry { }; }).toList(); + final encodedRequest = e.encodedRequest; + final encodedResponse = e.encodedResponse; + final decodedRequest = _decodeBytes(encodedRequest); + final decodedResponse = _decodeBytes(encodedResponse); + return { NetworkEventKeys.startedDateTime.name: e.startTimestamp .toUtc() @@ -134,12 +139,12 @@ class HarDataEntry { NetworkEventKeys.queryString.name: queryString, NetworkEventKeys.postData.name: { NetworkEventKeys.mimeType.name: e.contentType, - NetworkEventKeys.text.name: e.requestBody, + NetworkEventKeys.text.name: decodedRequest, }, NetworkEventKeys.headersSize.name: calculateHeadersSize( e.requestHeaders, ), - NetworkEventKeys.bodySize.name: _calculateBodySize(e.requestBody), + NetworkEventKeys.bodySize.name: _calculateBodySize(encodedRequest), }, // Response NetworkEventKeys.response.name: { @@ -151,15 +156,17 @@ class HarDataEntry { NetworkEventKeys.cookies.name: responseCookies, NetworkEventKeys.headers.name: responseHeaders, NetworkEventKeys.content.name: { - NetworkEventKeys.size.name: e.responseBody?.length, + NetworkEventKeys.size.name: encodedResponse?.length ?? 0, NetworkEventKeys.mimeType.name: e.type, - NetworkEventKeys.text.name: e.responseBody, + NetworkEventKeys.text.name: decodedResponse, + if (_isBinary(encodedResponse)) + NetworkEventKeys.encoding.name: 'base64', }, NetworkEventKeys.redirectURL.name: '', NetworkEventKeys.headersSize.name: calculateHeadersSize( e.responseHeaders, ), - NetworkEventKeys.bodySize.name: _calculateBodySize(e.responseBody), + NetworkEventKeys.bodySize.name: _calculateBodySize(encodedResponse), }, // Cache NetworkEventKeys.cache.name: {}, @@ -194,6 +201,25 @@ class HarDataEntry { return request; } + static String? _decodeBytes(Uint8List? bytes) { + if (bytes == null) return null; + try { + return utf8.decode(bytes); + } catch (_) { + return base64Encode(bytes); + } + } + + static bool _isBinary(Uint8List? bytes) { + if (bytes == null) return false; + try { + utf8.decode(bytes); + return false; + } catch (_) { + return true; + } + } + static Map _convertHeadersListToMap( List serializedHeaders, ) { @@ -271,9 +297,6 @@ class HarDataEntry { } } -int _calculateBodySize(String? requestBody) { - if (requestBody.isNullOrEmpty) { - return 0; - } - return utf8.encode(requestBody!).length; +int _calculateBodySize(Uint8List? body) { + return body?.length ?? 0; } diff --git a/packages/devtools_app/lib/src/shared/http/http_request_data.dart b/packages/devtools_app/lib/src/shared/http/http_request_data.dart index aeb478e44bb..6347ffdbf8d 100644 --- a/packages/devtools_app/lib/src/shared/http/http_request_data.dart +++ b/packages/devtools_app/lib/src/shared/http/http_request_data.dart @@ -101,8 +101,23 @@ class DartIOHttpRequestData extends NetworkRequest { ); _request = updated; final fullRequest = _request as HttpProfileRequest; - _responseBody = utf8.decode(fullRequest.responseBody!); - _requestBody = utf8.decode(fullRequest.requestBody!); + if (fullRequest.responseBody != null) { + try { + _responseBody = utf8.decode(fullRequest.responseBody!); + } catch (_) { + _responseBody = + '[Binary data (${fullRequest.responseBody!.length} bytes)]'; + } + } + + if (fullRequest.requestBody != null) { + try { + _requestBody = utf8.decode(fullRequest.requestBody!); + } catch (_) { + _requestBody = + '[Binary data (${fullRequest.requestBody!.length} bytes)]'; + } + } notifyListeners(); } } finally { @@ -303,7 +318,9 @@ class DartIOHttpRequestData extends NetworkRequest { _responseBody = utf8.decode(fullRequest.responseBody!); return _responseBody; } on FormatException { - return ''; + _responseBody = + '[Binary data (${fullRequest.responseBody!.length} bytes)]'; + return _responseBody; } } @@ -313,6 +330,12 @@ class DartIOHttpRequestData extends NetworkRequest { return fullRequest.responseBody; } + Uint8List? get encodedRequest { + if (_request is! HttpProfileRequest) return null; + final fullRequest = _request as HttpProfileRequest; + return fullRequest.requestBody; + } + String? _responseBody; String? get requestBody { @@ -329,7 +352,8 @@ class DartIOHttpRequestData extends NetworkRequest { _requestBody = utf8.decode(fullRequest.requestBody!); return _requestBody; } on FormatException { - return ''; + _requestBody = '[Binary data (${fullRequest.requestBody!.length} bytes)]'; + return _requestBody; } } diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index fc8c7f4bed4..b98ca752ea0 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -39,7 +39,7 @@ TODO: Remove this section if there are not any updates. ## Network profiler updates -TODO: Remove this section if there are not any updates. +- Fix crash in the Network tab when viewing binary multipart request or response bodies (#9978) ## Logging updates