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 2a2ba7d4d69..6cb72d9d44f 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 @@ -122,6 +122,9 @@ class HarDataEntry { }; }).toList(); + final isBinary = !isTextMimeType(e.type); + final responseBodyBytes = e.encodedResponse; + return { NetworkEventKeys.startedDateTime.name: e.startTimestamp.toUtc().toIso8601String(), @@ -155,8 +158,14 @@ class HarDataEntry { NetworkEventKeys.content.name: { NetworkEventKeys.size.name: e.responseBody?.length, NetworkEventKeys.mimeType.name: e.type, - NetworkEventKeys.text.name: e.responseBody, + if (responseBodyBytes != null && isBinary) ...{ + NetworkEventKeys.text.name: base64.encode(responseBodyBytes), + 'encoding': 'base64', + } else if (e.responseBody != null) ...{ + NetworkEventKeys.text.name: e.responseBody, + }, }, + NetworkEventKeys.redirectURL.name: '', NetworkEventKeys.headersSize.name: calculateHeadersSize( e.responseHeaders, diff --git a/packages/devtools_app/lib/src/screens/network/network_controller.dart b/packages/devtools_app/lib/src/screens/network/network_controller.dart index 0145ba75b67..6f8157e719a 100644 --- a/packages/devtools_app/lib/src/screens/network/network_controller.dart +++ b/packages/devtools_app/lib/src/screens/network/network_controller.dart @@ -77,9 +77,8 @@ class NetworkController extends DevToolsScreenController debugPrint('No valid request data to export'); return ''; } - + // Build the HAR object try { - // Build the HAR object final har = HarNetworkData(_httpRequests!); return ExportController().downloadFile( json.encode(har.toJson()), @@ -205,8 +204,11 @@ class NetworkController extends DevToolsScreenController shouldLoad: (data) => !data.isEmpty, loadData: (data) => loadOfflineData(data), ); - } - if (serviceConnection.serviceManager.connectedState.value.connected) { + } else if (serviceConnection + .serviceManager + .connectedState + .value + .connected) { await startRecording(); } } 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 b417ce0bb17..de03023d517 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 @@ -102,8 +102,27 @@ class DartIOHttpRequestData extends NetworkRequest { ); _request = updated; final fullRequest = _request as HttpProfileRequest; - _responseBody = utf8.decode(fullRequest.responseBody!); - _requestBody = utf8.decode(fullRequest.requestBody!); + final responseMime = + responseHeaders?['content-type']?.toString().split(';').first; + final requestMime = + requestHeaders?['content-type']?.toString().split(';').first; + + if (fullRequest.responseBody != null) { + if (isTextMimeType(responseMime)) { + _responseBody = utf8.decode(fullRequest.responseBody!); + } else { + _responseBody = base64.encode(fullRequest.responseBody!); + } + } + + if (fullRequest.requestBody != null) { + if (isTextMimeType(requestMime)) { + _requestBody = utf8.decode(fullRequest.requestBody!); + } else { + _requestBody = base64.encode(fullRequest.requestBody!); + } + } + notifyListeners(); } } finally { diff --git a/packages/devtools_app/lib/src/shared/primitives/utils.dart b/packages/devtools_app/lib/src/shared/primitives/utils.dart index 45558fd8a10..3fe1d11a50e 100644 --- a/packages/devtools_app/lib/src/shared/primitives/utils.dart +++ b/packages/devtools_app/lib/src/shared/primitives/utils.dart @@ -1126,3 +1126,18 @@ String devtoolsAssetsBasePath({required String origin, required String path}) { pathParts.removeLast(); return '$trimmedOrigin${pathParts.join(separator)}'; } + +/// Returns `true` if the given [mimeType] is considered textual and can be +/// safely decoded as UTF-8 without base64 encoding. +/// +/// This function is useful for determining whether the content of an HTTP +/// request or response can be directly included in a HAR or JSON file as +/// human-readable text. +bool isTextMimeType(String? mimeType) { + if (mimeType == null) return false; + return mimeType.startsWith('text/') || + mimeType == 'application/json' || + mimeType == 'application/javascript' || + mimeType == 'application/xml' || + mimeType == 'application/x-www-form-urlencoded'; +}