From da7252dc93b7e8c6a9173149ad5d1d39205cccdd Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Sun, 23 Jun 2024 13:27:24 +0530 Subject: [PATCH 01/35] implemented har feature --- .../screens/network/network_controller.dart | 183 ++++++++++++++++-- .../src/screens/network/network_screen.dart | 8 + .../lib/src/shared/analytics/constants.dart | 1 + .../constants/_network_constants.dart | 5 + .../import_export/import_export.dart | 3 +- 5 files changed, 184 insertions(+), 16 deletions(-) create mode 100644 packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart 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 c623f74cd19..364419d0028 100644 --- a/packages/devtools_app/lib/src/screens/network/network_controller.dart +++ b/packages/devtools_app/lib/src/screens/network/network_controller.dart @@ -3,23 +3,21 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'package:devtools_app_shared/utils.dart'; import 'package:flutter/foundation.dart'; import 'package:vm_service/vm_service.dart'; +import '../../../devtools_app.dart'; +import '../../shared/config_specific/import_export/import_export.dart'; import '../../shared/config_specific/logger/allowed_error.dart'; -import '../../shared/globals.dart'; -import '../../shared/http/http_request_data.dart'; import '../../shared/http/http_service.dart' as http_service; -import '../../shared/primitives/utils.dart'; -import '../../shared/ui/filter.dart'; -import '../../shared/ui/search.dart'; -import '../../shared/utils.dart'; -import 'network_model.dart'; -import 'network_screen.dart'; import 'network_service.dart'; +final _exportController = ExportController(); +List? httpRequests; + /// Different types of Network Response which can be used to visualise response /// on Response tab enum NetworkResponseViewType { @@ -46,6 +44,7 @@ class NetworkController extends DisposableController with SearchControllerMixin, FilterControllerMixin, + OfflineScreenControllerMixin, AutoDisposeControllerMixin { NetworkController() { _networkService = NetworkService(this); @@ -57,6 +56,154 @@ class NetworkController extends DisposableController subscribeToFilterChanges(); } + String? exportAsHarFile() { + httpRequests = + filteredData.value.whereType().toList(); + + if (httpRequests!.isEmpty) { + debugPrint('No valid request data to export'); + return ''; + } + + try { + if (httpRequests!.isNotEmpty) { + final har = { + 'log': { + 'version': '1.2', + 'creator': { + 'name': 'flutter_tool', + 'version': '0.0.2', + }, + 'pages': [ + { + 'startedDateTime': httpRequests?.first.startTimestamp + .toUtc() + .toIso8601String(), + 'id': 'page_0', + 'title': 'FlutterCapture', + 'pageTimings': { + 'onContentLoad': -1, + 'onLoad': -1, + }, + }, + ], + 'entries': httpRequests + ?.map( + (e) => { + 'pageref': 'page_0', + 'startedDateTime': + e.startTimestamp.toUtc().toIso8601String(), + 'time': e.duration?.inMilliseconds, + 'request': { + 'method': e.method.toUpperCase(), + 'url': e.uri.toString(), + 'httpVersion': 'HTTP/1.1', + 'cookies': e.requestCookies + .map( + (e) => { + 'name': e.name, + 'value': e.value, + 'path': e.path, + 'domain': e.domain, + 'expires': e.expires?.toUtc().toIso8601String(), + 'httpOnly': e.httpOnly, + 'secure': e.secure, + }, + ) + .toList(), + 'headers': e.requestHeaders?.entries.map((h) { + var value = h.value; + if (value is List) { + value = value.first; + } + return { + 'name': h.key, + 'value': value, + }; + }).toList(), + 'queryString': Uri.parse(e.uri) + .queryParameters + .entries + .map( + (q) => { + 'name': q.key, + 'value': q.value, + }, + ) + .toList(), + 'postData': { + 'mimeType': e.contentType, + 'text': e.requestBody, + }, + 'headersSize': -1, + 'bodySize': -1, + }, + 'response': { + 'status': e.status, + 'statusText': '', + 'httpVersion': 'http/2.0', + 'cookies': e.responseCookies + .map( + (e) => { + 'name': e.name, + 'value': e.value, + 'path': e.path, + 'domain': e.domain, + 'expires': e.expires?.toUtc().toIso8601String(), + 'httpOnly': e.httpOnly, + 'secure': e.secure, + }, + ) + .toList(), + 'headers': e.responseHeaders?.entries.map((h) { + var value = h.value; + if (value is List) { + value = value.first; + } + return { + 'name': h.key, + 'value': value, + }; + }).toList(), + 'content': { + 'size': e.responseBody?.length, + 'mimeType': e.type, + 'text': e.responseBody, + }, + 'redirectURL': '', + 'headersSize': -1, + 'bodySize': -1, + }, + 'cache': {}, + 'timings': { + 'blocked': -1, + 'dns': -1, + 'connect': -1, + 'send': 1, + 'wait': e.duration!.inMilliseconds - 2, + 'receive': 1, + 'ssl': -1, + }, + 'serverIPAddress': '10.0.0.1', + 'connection': e.hashCode.toString(), + 'comment': '', + }, + ) + .toList(), + }, + }; + debugPrint('data is ${json.encode(har)}'); + return _exportController.downloadFile( + json.encode(har), + type: ExportFileType.har, + ); + } + } catch (ex) { + debugPrint('Exception in export $ex'); + } + return null; + } + static const methodFilterId = 'network-method-filter'; static const statusFilterId = 'network-status-filter'; @@ -266,15 +413,11 @@ class NetworkController extends DisposableController final service = serviceConnection.serviceManager.service!; await service.forEachIsolate( (isolate) async { - final future = switch (type) { - _NetworkTrafficType.http => - service.httpEnableTimelineLoggingWrapper(isolate.id!), - _NetworkTrafficType.socket => - service.socketProfilingEnabledWrapper(isolate.id!), - }; + final httpFuture = + service.httpEnableTimelineLoggingWrapper(isolate.id!); // The above call won't complete immediately if the isolate is paused, // so give up waiting after 500ms. - final state = await timeout(future, 500); + final state = await timeout(httpFuture, 500); if (state?.enabled != true) { enabled = false; } @@ -360,6 +503,16 @@ class NetworkController extends DisposableController serviceConnection.errorBadgeManager.incrementBadgeCount(NetworkScreen.id); } } + + @override + OfflineScreenData prepareOfflineScreenData() { + debugPrint('offline data - httpRequests are $httpRequests'); + return OfflineScreenData( + screenId: NetworkScreen.id, + //TODO deserialize har data and pass here + data: {}, + ); + } } /// Class for managing the set of all current websocket requests, and diff --git a/packages/devtools_app/lib/src/screens/network/network_screen.dart b/packages/devtools_app/lib/src/screens/network/network_screen.dart index 92bab290b03..edb0bea51a2 100644 --- a/packages/devtools_app/lib/src/screens/network/network_screen.dart +++ b/packages/devtools_app/lib/src/screens/network/network_screen.dart @@ -234,6 +234,14 @@ class _NetworkProfilerControlsState extends State<_NetworkProfilerControls> onPressed: widget.controller.clear, ), const SizedBox(width: defaultSpacing), + DownloadButton( + minScreenWidthForTextBeforeScaling: + _NetworkProfilerControls._includeTextWidth, + onPressed: widget.controller.exportAsHarFile, + gaScreen: gac.network, + gaSelection: gac.NetworkEvent.networkDownloadHar, + ), + const SizedBox(width: defaultSpacing), const Expanded(child: SizedBox()), // TODO(kenz): fix focus issue when state is refreshed SearchField( diff --git a/packages/devtools_app/lib/src/shared/analytics/constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants.dart index 784b6a5c26c..b1c1ddd9547 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants.dart @@ -12,6 +12,7 @@ part 'constants/_debugger_constants.dart'; part 'constants/_deep_links_constants.dart'; part 'constants/_extension_constants.dart'; part 'constants/_memory_constants.dart'; +part 'constants/_network_constants.dart'; part 'constants/_performance_constants.dart'; part 'constants/_vs_code_sidebar_constants.dart'; part 'constants/_inspector_constants.dart'; diff --git a/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart new file mode 100644 index 00000000000..f99248415d8 --- /dev/null +++ b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart @@ -0,0 +1,5 @@ +part of '../constants.dart'; + +class NetworkEvent { + static const networkDownloadHar = 'networkDownloadHar'; +} diff --git a/packages/devtools_app/lib/src/shared/config_specific/import_export/import_export.dart b/packages/devtools_app/lib/src/shared/config_specific/import_export/import_export.dart index 903c12d03cb..356db3f61da 100644 --- a/packages/devtools_app/lib/src/shared/config_specific/import_export/import_export.dart +++ b/packages/devtools_app/lib/src/shared/config_specific/import_export/import_export.dart @@ -113,7 +113,8 @@ enum ExportFileType { json, csv, yaml, - data; + data, + har; @override String toString() => name; From f6f9491e0816f698a285b45b5d9a4fd8ba034447 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Sun, 23 Jun 2024 13:30:45 +0530 Subject: [PATCH 02/35] reverting some code --- .../lib/src/screens/network/network_controller.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 364419d0028..a38be27302e 100644 --- a/packages/devtools_app/lib/src/screens/network/network_controller.dart +++ b/packages/devtools_app/lib/src/screens/network/network_controller.dart @@ -413,11 +413,15 @@ class NetworkController extends DisposableController final service = serviceConnection.serviceManager.service!; await service.forEachIsolate( (isolate) async { - final httpFuture = - service.httpEnableTimelineLoggingWrapper(isolate.id!); + final future = switch (type) { + _NetworkTrafficType.http => + service.httpEnableTimelineLoggingWrapper(isolate.id!), + _NetworkTrafficType.socket => + service.socketProfilingEnabledWrapper(isolate.id!), + }; // The above call won't complete immediately if the isolate is paused, // so give up waiting after 500ms. - final state = await timeout(httpFuture, 500); + final state = await timeout(future, 500); if (state?.enabled != true) { enabled = false; } From a1ba9bdbf607ebce46c63b8cf1d4a390511cf476 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Mon, 24 Jun 2024 22:17:39 +0530 Subject: [PATCH 03/35] comments resolved, code refactoring --- .../lib/src/screens/network/har_builder.dart | 139 +++++++++++++++++ .../screens/network/network_controller.dart | 140 ++---------------- .../constants/_network_constants.dart | 74 +++++++++ 3 files changed, 226 insertions(+), 127 deletions(-) create mode 100644 packages/devtools_app/lib/src/screens/network/har_builder.dart diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart new file mode 100644 index 00000000000..6c21bd731dd --- /dev/null +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -0,0 +1,139 @@ +import '../../../devtools_app.dart'; +import '../../shared/analytics/constants.dart'; + +Map buildHar(List httpRequests) { +// Build the creator + final creator = { + NetworkEventKeys.name: NetworkEventDefaults.creatorName, + NetworkEventKeys.creatorVersion: NetworkEventDefaults.creatorVersion, + }; + +// Build the pages + final pages = [ + { + NetworkEventKeys.startedDateTime: + httpRequests.first.startTimestamp.toUtc().toIso8601String(), + NetworkEventKeys.id: NetworkEventDefaults.id, + NetworkEventKeys.title: NetworkEventDefaults.title, + NetworkEventKeys.pageTimings: { + NetworkEventKeys.onContentLoad: NetworkEventDefaults.onContentLoad, + NetworkEventKeys.onLoad: NetworkEventDefaults.onLoad, + }, + }, + ]; + +// Build the entries + final entries = httpRequests.map((e) { + final requestCookies = e.requestCookies.map((cookie) { + return { + NetworkEventKeys.name: cookie.name, + NetworkEventKeys.value: cookie.value, + 'path': cookie.path, + 'domain': cookie.domain, + 'expires': cookie.expires?.toUtc().toIso8601String(), + 'httpOnly': cookie.httpOnly, + 'secure': cookie.secure, + }; + }).toList(); + + final requestHeaders = e.requestHeaders?.entries.map((header) { + var value = header.value; + if (value is List) { + value = value.first; + } + return { + NetworkEventKeys.name: header.key, + NetworkEventKeys.value: value, + }; + }).toList(); + + final queryString = Uri.parse(e.uri).queryParameters.entries.map((param) { + return { + NetworkEventKeys.name: param.key, + NetworkEventKeys.value: param.value, + }; + }).toList(); + + final responseCookies = e.responseCookies.map((cookie) { + return { + NetworkEventKeys.name: cookie.name, + NetworkEventKeys.value: cookie.value, + 'path': cookie.path, + 'domain': cookie.domain, + 'expires': cookie.expires?.toUtc().toIso8601String(), + 'httpOnly': cookie.httpOnly, + 'secure': cookie.secure, + }; + }).toList(); + + final responseHeaders = e.responseHeaders?.entries.map((header) { + var value = header.value; + if (value is List) { + value = value.first; + } + return { + NetworkEventKeys.name: header.key, + NetworkEventKeys.value: value, + }; + }).toList(); + + return { + NetworkEventKeys.pageref: NetworkEventDefaults.id, + NetworkEventKeys.startedDateTime: + e.startTimestamp.toUtc().toIso8601String(), + NetworkEventKeys.time: e.duration?.inMilliseconds, + NetworkEventKeys.request: { + NetworkEventKeys.method: e.method.toUpperCase(), + NetworkEventKeys.url: e.uri.toString(), + NetworkEventKeys.httpVersion: NetworkEventDefaults.httpVersion, + NetworkEventKeys.cookies: requestCookies, + NetworkEventKeys.headers: requestHeaders, + NetworkEventKeys.queryString: queryString, + NetworkEventKeys.postData: { + NetworkEventKeys.mimeType: e.contentType, + NetworkEventKeys.text: e.requestBody, + }, + NetworkEventKeys.headersSize: NetworkEventDefaults.headersSize, + NetworkEventKeys.bodySize: NetworkEventDefaults.bodySize, + }, + NetworkEventKeys.response: { + NetworkEventKeys.status: e.status, + NetworkEventKeys.statusText: '', + NetworkEventKeys.httpVersion: NetworkEventDefaults.responseHttpVersion, + NetworkEventKeys.cookies: responseCookies, + NetworkEventKeys.headers: responseHeaders, + NetworkEventKeys.content: { + NetworkEventKeys.size: e.responseBody?.length, + NetworkEventKeys.mimeType: e.type, + NetworkEventKeys.text: e.responseBody, + }, + NetworkEventKeys.redirectURL: '', + NetworkEventKeys.headersSize: NetworkEventDefaults.headersSize, + NetworkEventKeys.bodySize: NetworkEventDefaults.bodySize, + }, + NetworkEventKeys.cache: {}, + NetworkEventKeys.timings: { + NetworkEventKeys.blocked: NetworkEventDefaults.blocked, + NetworkEventKeys.dns: NetworkEventDefaults.dns, + NetworkEventKeys.connect: NetworkEventDefaults.connect, + NetworkEventKeys.send: NetworkEventDefaults.send, + NetworkEventKeys.wait: e.duration!.inMilliseconds - 2, + NetworkEventKeys.receive: NetworkEventDefaults.receive, + NetworkEventKeys.ssl: NetworkEventDefaults.ssl, + }, + NetworkEventKeys.serverIPAddress: NetworkEventDefaults.serverIPAddress, + NetworkEventKeys.connection: e.hashCode.toString(), + NetworkEventKeys.comment: '', + }; + }).toList(); + +// Assemble the final HAR object + return { + NetworkEventKeys.log: { + NetworkEventKeys.version: NetworkEventDefaults.logVersion, + NetworkEventKeys.creator: creator, + NetworkEventKeys.pages: pages, + NetworkEventKeys.entries: entries, + }, + }; +} 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 a38be27302e..e6637a0e765 100644 --- a/packages/devtools_app/lib/src/screens/network/network_controller.dart +++ b/packages/devtools_app/lib/src/screens/network/network_controller.dart @@ -9,10 +9,19 @@ import 'package:devtools_app_shared/utils.dart'; import 'package:flutter/foundation.dart'; import 'package:vm_service/vm_service.dart'; -import '../../../devtools_app.dart'; import '../../shared/config_specific/import_export/import_export.dart'; import '../../shared/config_specific/logger/allowed_error.dart'; +import '../../shared/globals.dart'; +import '../../shared/http/http_request_data.dart'; import '../../shared/http/http_service.dart' as http_service; +import '../../shared/offline_data.dart'; +import '../../shared/primitives/utils.dart'; +import '../../shared/ui/filter.dart'; +import '../../shared/ui/search.dart'; +import '../../shared/utils.dart'; +import 'har_builder.dart'; +import 'network_model.dart'; +import 'network_screen.dart'; import 'network_service.dart'; final _exportController = ExportController(); @@ -66,132 +75,9 @@ class NetworkController extends DisposableController } try { - if (httpRequests!.isNotEmpty) { - final har = { - 'log': { - 'version': '1.2', - 'creator': { - 'name': 'flutter_tool', - 'version': '0.0.2', - }, - 'pages': [ - { - 'startedDateTime': httpRequests?.first.startTimestamp - .toUtc() - .toIso8601String(), - 'id': 'page_0', - 'title': 'FlutterCapture', - 'pageTimings': { - 'onContentLoad': -1, - 'onLoad': -1, - }, - }, - ], - 'entries': httpRequests - ?.map( - (e) => { - 'pageref': 'page_0', - 'startedDateTime': - e.startTimestamp.toUtc().toIso8601String(), - 'time': e.duration?.inMilliseconds, - 'request': { - 'method': e.method.toUpperCase(), - 'url': e.uri.toString(), - 'httpVersion': 'HTTP/1.1', - 'cookies': e.requestCookies - .map( - (e) => { - 'name': e.name, - 'value': e.value, - 'path': e.path, - 'domain': e.domain, - 'expires': e.expires?.toUtc().toIso8601String(), - 'httpOnly': e.httpOnly, - 'secure': e.secure, - }, - ) - .toList(), - 'headers': e.requestHeaders?.entries.map((h) { - var value = h.value; - if (value is List) { - value = value.first; - } - return { - 'name': h.key, - 'value': value, - }; - }).toList(), - 'queryString': Uri.parse(e.uri) - .queryParameters - .entries - .map( - (q) => { - 'name': q.key, - 'value': q.value, - }, - ) - .toList(), - 'postData': { - 'mimeType': e.contentType, - 'text': e.requestBody, - }, - 'headersSize': -1, - 'bodySize': -1, - }, - 'response': { - 'status': e.status, - 'statusText': '', - 'httpVersion': 'http/2.0', - 'cookies': e.responseCookies - .map( - (e) => { - 'name': e.name, - 'value': e.value, - 'path': e.path, - 'domain': e.domain, - 'expires': e.expires?.toUtc().toIso8601String(), - 'httpOnly': e.httpOnly, - 'secure': e.secure, - }, - ) - .toList(), - 'headers': e.responseHeaders?.entries.map((h) { - var value = h.value; - if (value is List) { - value = value.first; - } - return { - 'name': h.key, - 'value': value, - }; - }).toList(), - 'content': { - 'size': e.responseBody?.length, - 'mimeType': e.type, - 'text': e.responseBody, - }, - 'redirectURL': '', - 'headersSize': -1, - 'bodySize': -1, - }, - 'cache': {}, - 'timings': { - 'blocked': -1, - 'dns': -1, - 'connect': -1, - 'send': 1, - 'wait': e.duration!.inMilliseconds - 2, - 'receive': 1, - 'ssl': -1, - }, - 'serverIPAddress': '10.0.0.1', - 'connection': e.hashCode.toString(), - 'comment': '', - }, - ) - .toList(), - }, - }; + if (httpRequests != null && httpRequests!.isNotEmpty) { + // Build the HAR object + final har = buildHar(httpRequests!); debugPrint('data is ${json.encode(har)}'); return _exportController.downloadFile( json.encode(har), diff --git a/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart index f99248415d8..c193b372528 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart @@ -1,5 +1,79 @@ +// Copyright 2024 The Chromium Authors. 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 '../constants.dart'; class NetworkEvent { static const networkDownloadHar = 'networkDownloadHar'; } + +class NetworkEventKeys { + static const log = 'log'; + static const version = 'version'; + static const creator = 'creator'; + static const name = 'name'; + static const creatorVersion = 'version'; + static const pages = 'pages'; + static const startedDateTime = 'startedDateTime'; + static const id = 'id'; + static const title = 'title'; + static const pageTimings = 'pageTimings'; + static const onContentLoad = 'onContentLoad'; + static const onLoad = 'onLoad'; + static const entries = 'entries'; + static const pageref = 'pageref'; + static const time = 'time'; + static const request = 'request'; + static const method = 'method'; + static const url = 'url'; + static const httpVersion = 'httpVersion'; + static const cookies = 'cookies'; + static const headers = 'headers'; + static const queryString = 'queryString'; + static const postData = 'postData'; + static const mimeType = 'mimeType'; + static const text = 'text'; + static const headersSize = 'headersSize'; + static const bodySize = 'bodySize'; + static const response = 'response'; + static const status = 'status'; + static const statusText = 'statusText'; + static const content = 'content'; + static const size = 'size'; + static const redirectURL = 'redirectURL'; + static const cache = 'cache'; + static const timings = 'timings'; + static const blocked = 'blocked'; + static const dns = 'dns'; + static const connect = 'connect'; + static const send = 'send'; + static const wait = 'wait'; + static const receive = 'receive'; + static const ssl = 'ssl'; + static const serverIPAddress = 'serverIPAddress'; + static const connection = 'connection'; + static const comment = 'comment'; + static const value = 'value'; +} + +class NetworkEventDefaults { + static const logVersion = '1.2'; + static const creatorName = 'devtools'; + static const creatorVersion = '0.0.2'; + static const id = 'page_0'; + static const title = 'FlutterCapture'; + static const onContentLoad = -1; + static const onLoad = -1; + static const httpVersion = 'HTTP/1.1'; + static const responseHttpVersion = 'http/2.0'; + static const headersSize = -1; + static const bodySize = -1; + static const blocked = -1; + static const dns = -1; + static const connect = -1; + static const send = 1; + static const receive = 1; + static const ssl = -1; + static const serverIPAddress = '10.0.0.1'; +} From ef3a637e0aefb5ed3ec7d3ed8c848814838a3a6e Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Mon, 24 Jun 2024 22:40:15 +0530 Subject: [PATCH 04/35] added doc comments for buildHar --- .../lib/src/screens/network/har_builder.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index 6c21bd731dd..012ced74d11 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -1,6 +1,20 @@ import '../../../devtools_app.dart'; import '../../shared/analytics/constants.dart'; +/// Builds a HAR (HTTP Archive) object from a list of HTTP requests. +/// +/// The HAR format is a JSON-based format used for logging a web browser's +/// interaction with a site. It is useful for web performance analysis and +/// debugging. This function constructs the HAR object based on the 1.2 +/// specification. +/// +/// For more details on the HAR format, see the [HAR 1.2 Specification](https://github.com/ahmadnassri/har-spec/blob/master/versions/1.2.md). +/// +/// Parameters: +/// - [httpRequests]: A list of DartIOHttpRequestData data. +/// +/// Returns: +/// - A Map representing the HAR object. Map buildHar(List httpRequests) { // Build the creator final creator = { From 058e0535485aa144c333aa4300215df2101621cd Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Mon, 24 Jun 2024 22:48:12 +0530 Subject: [PATCH 05/35] exportController moved to method scope --- .../lib/src/screens/network/network_controller.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 e6637a0e765..89ebe25d2ba 100644 --- a/packages/devtools_app/lib/src/screens/network/network_controller.dart +++ b/packages/devtools_app/lib/src/screens/network/network_controller.dart @@ -24,7 +24,6 @@ import 'network_model.dart'; import 'network_screen.dart'; import 'network_service.dart'; -final _exportController = ExportController(); List? httpRequests; /// Different types of Network Response which can be used to visualise response @@ -66,6 +65,8 @@ class NetworkController extends DisposableController } String? exportAsHarFile() { + final exportController = ExportController(); + httpRequests = filteredData.value.whereType().toList(); @@ -79,7 +80,7 @@ class NetworkController extends DisposableController // Build the HAR object final har = buildHar(httpRequests!); debugPrint('data is ${json.encode(har)}'); - return _exportController.downloadFile( + return exportController.downloadFile( json.encode(har), type: ExportFileType.har, ); From ae292ad1bdd4bec9cb2a6ada3d15b1439bbbb8b5 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Mon, 24 Jun 2024 23:01:53 +0530 Subject: [PATCH 06/35] httpRequests moved to NetworkController class --- .../lib/src/screens/network/network_controller.dart | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) 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 89ebe25d2ba..99f3f1b8bb3 100644 --- a/packages/devtools_app/lib/src/screens/network/network_controller.dart +++ b/packages/devtools_app/lib/src/screens/network/network_controller.dart @@ -24,8 +24,6 @@ import 'network_model.dart'; import 'network_screen.dart'; import 'network_service.dart'; -List? httpRequests; - /// Different types of Network Response which can be used to visualise response /// on Response tab enum NetworkResponseViewType { @@ -63,22 +61,23 @@ class NetworkController extends DisposableController ); subscribeToFilterChanges(); } + List? _httpRequests; String? exportAsHarFile() { final exportController = ExportController(); - httpRequests = + _httpRequests = filteredData.value.whereType().toList(); - if (httpRequests!.isEmpty) { + if (_httpRequests!.isEmpty) { debugPrint('No valid request data to export'); return ''; } try { - if (httpRequests != null && httpRequests!.isNotEmpty) { + if (_httpRequests != null && _httpRequests!.isNotEmpty) { // Build the HAR object - final har = buildHar(httpRequests!); + final har = buildHar(_httpRequests!); debugPrint('data is ${json.encode(har)}'); return exportController.downloadFile( json.encode(har), @@ -397,7 +396,7 @@ class NetworkController extends DisposableController @override OfflineScreenData prepareOfflineScreenData() { - debugPrint('offline data - httpRequests are $httpRequests'); + debugPrint('offline data - httpRequests are $_httpRequests'); return OfflineScreenData( screenId: NetworkScreen.id, //TODO deserialize har data and pass here From 1b9e483b40b7e8c5e6a751a2ff062cd8c627ca77 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Wed, 26 Jun 2024 21:49:27 +0530 Subject: [PATCH 07/35] code comments resolved --- .../lib/src/screens/network/har_builder.dart | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index 012ced74d11..39d8c2d07c2 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -1,10 +1,15 @@ -import '../../../devtools_app.dart'; +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + import '../../shared/analytics/constants.dart'; +import '../../shared/http/http_request_data.dart'; + /// Builds a HAR (HTTP Archive) object from a list of HTTP requests. /// /// The HAR format is a JSON-based format used for logging a web browser's -/// interaction with a site. It is useful for web performance analysis and +/// interaction with a site. It is useful for performance analysis and /// debugging. This function constructs the HAR object based on the 1.2 /// specification. /// @@ -15,31 +20,31 @@ import '../../shared/analytics/constants.dart'; /// /// Returns: /// - A Map representing the HAR object. -Map buildHar(List httpRequests) { -// Build the creator - final creator = { +Map buildHar(List httpRequests) { + // Build the creator + final creator = { NetworkEventKeys.name: NetworkEventDefaults.creatorName, NetworkEventKeys.creatorVersion: NetworkEventDefaults.creatorVersion, }; -// Build the pages - final pages = [ + // Build the pages + final pages = >[ { NetworkEventKeys.startedDateTime: httpRequests.first.startTimestamp.toUtc().toIso8601String(), NetworkEventKeys.id: NetworkEventDefaults.id, NetworkEventKeys.title: NetworkEventDefaults.title, - NetworkEventKeys.pageTimings: { + NetworkEventKeys.pageTimings: { NetworkEventKeys.onContentLoad: NetworkEventDefaults.onContentLoad, NetworkEventKeys.onLoad: NetworkEventDefaults.onLoad, }, }, ]; -// Build the entries + // Build the entries final entries = httpRequests.map((e) { final requestCookies = e.requestCookies.map((cookie) { - return { + return { NetworkEventKeys.name: cookie.name, NetworkEventKeys.value: cookie.value, 'path': cookie.path, @@ -55,21 +60,21 @@ Map buildHar(List httpRequests) { if (value is List) { value = value.first; } - return { + return { NetworkEventKeys.name: header.key, NetworkEventKeys.value: value, }; }).toList(); final queryString = Uri.parse(e.uri).queryParameters.entries.map((param) { - return { + return { NetworkEventKeys.name: param.key, NetworkEventKeys.value: param.value, }; }).toList(); final responseCookies = e.responseCookies.map((cookie) { - return { + return { NetworkEventKeys.name: cookie.name, NetworkEventKeys.value: cookie.value, 'path': cookie.path, @@ -85,38 +90,38 @@ Map buildHar(List httpRequests) { if (value is List) { value = value.first; } - return { + return { NetworkEventKeys.name: header.key, NetworkEventKeys.value: value, }; }).toList(); - return { + return { NetworkEventKeys.pageref: NetworkEventDefaults.id, NetworkEventKeys.startedDateTime: e.startTimestamp.toUtc().toIso8601String(), NetworkEventKeys.time: e.duration?.inMilliseconds, - NetworkEventKeys.request: { + NetworkEventKeys.request: { NetworkEventKeys.method: e.method.toUpperCase(), NetworkEventKeys.url: e.uri.toString(), NetworkEventKeys.httpVersion: NetworkEventDefaults.httpVersion, NetworkEventKeys.cookies: requestCookies, NetworkEventKeys.headers: requestHeaders, NetworkEventKeys.queryString: queryString, - NetworkEventKeys.postData: { + NetworkEventKeys.postData: { NetworkEventKeys.mimeType: e.contentType, NetworkEventKeys.text: e.requestBody, }, NetworkEventKeys.headersSize: NetworkEventDefaults.headersSize, NetworkEventKeys.bodySize: NetworkEventDefaults.bodySize, }, - NetworkEventKeys.response: { + NetworkEventKeys.response: { NetworkEventKeys.status: e.status, NetworkEventKeys.statusText: '', NetworkEventKeys.httpVersion: NetworkEventDefaults.responseHttpVersion, NetworkEventKeys.cookies: responseCookies, NetworkEventKeys.headers: responseHeaders, - NetworkEventKeys.content: { + NetworkEventKeys.content: { NetworkEventKeys.size: e.responseBody?.length, NetworkEventKeys.mimeType: e.type, NetworkEventKeys.text: e.responseBody, @@ -125,8 +130,8 @@ Map buildHar(List httpRequests) { NetworkEventKeys.headersSize: NetworkEventDefaults.headersSize, NetworkEventKeys.bodySize: NetworkEventDefaults.bodySize, }, - NetworkEventKeys.cache: {}, - NetworkEventKeys.timings: { + NetworkEventKeys.cache: {}, + NetworkEventKeys.timings: { NetworkEventKeys.blocked: NetworkEventDefaults.blocked, NetworkEventKeys.dns: NetworkEventDefaults.dns, NetworkEventKeys.connect: NetworkEventDefaults.connect, @@ -141,9 +146,9 @@ Map buildHar(List httpRequests) { }; }).toList(); -// Assemble the final HAR object - return { - NetworkEventKeys.log: { + // Assemble the final HAR object + return { + NetworkEventKeys.log: { NetworkEventKeys.version: NetworkEventDefaults.logVersion, NetworkEventKeys.creator: creator, NetworkEventKeys.pages: pages, From 5c306c1c8f6b599a1931dcbed08d8febd86fb2cb Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Thu, 27 Jun 2024 12:04:56 +0530 Subject: [PATCH 08/35] removed pages, pageref and serverIPAddress --- .../lib/src/screens/network/har_builder.dart | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index 39d8c2d07c2..bd7886f4119 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -27,20 +27,6 @@ Map buildHar(List httpRequests) { NetworkEventKeys.creatorVersion: NetworkEventDefaults.creatorVersion, }; - // Build the pages - final pages = >[ - { - NetworkEventKeys.startedDateTime: - httpRequests.first.startTimestamp.toUtc().toIso8601String(), - NetworkEventKeys.id: NetworkEventDefaults.id, - NetworkEventKeys.title: NetworkEventDefaults.title, - NetworkEventKeys.pageTimings: { - NetworkEventKeys.onContentLoad: NetworkEventDefaults.onContentLoad, - NetworkEventKeys.onLoad: NetworkEventDefaults.onLoad, - }, - }, - ]; - // Build the entries final entries = httpRequests.map((e) { final requestCookies = e.requestCookies.map((cookie) { @@ -97,7 +83,6 @@ Map buildHar(List httpRequests) { }).toList(); return { - NetworkEventKeys.pageref: NetworkEventDefaults.id, NetworkEventKeys.startedDateTime: e.startTimestamp.toUtc().toIso8601String(), NetworkEventKeys.time: e.duration?.inMilliseconds, @@ -140,7 +125,6 @@ Map buildHar(List httpRequests) { NetworkEventKeys.receive: NetworkEventDefaults.receive, NetworkEventKeys.ssl: NetworkEventDefaults.ssl, }, - NetworkEventKeys.serverIPAddress: NetworkEventDefaults.serverIPAddress, NetworkEventKeys.connection: e.hashCode.toString(), NetworkEventKeys.comment: '', }; @@ -151,7 +135,6 @@ Map buildHar(List httpRequests) { NetworkEventKeys.log: { NetworkEventKeys.version: NetworkEventDefaults.logVersion, NetworkEventKeys.creator: creator, - NetworkEventKeys.pages: pages, NetworkEventKeys.entries: entries, }, }; From 5d8fc7552206753399f04a0ec3a7326cdcf07993 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Thu, 27 Jun 2024 13:34:13 +0530 Subject: [PATCH 09/35] added header size and body size, creatorVersion set --- .../lib/src/screens/network/har_builder.dart | 43 ++++++++++++++++--- .../constants/_network_constants.dart | 6 --- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index bd7886f4119..d6ac638b846 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert'; +import '../../shared/utils.dart'; import '../../shared/analytics/constants.dart'; import '../../shared/http/http_request_data.dart'; @@ -24,7 +26,7 @@ Map buildHar(List httpRequests) { // Build the creator final creator = { NetworkEventKeys.name: NetworkEventDefaults.creatorName, - NetworkEventKeys.creatorVersion: NetworkEventDefaults.creatorVersion, + NetworkEventKeys.creatorVersion: devToolsVersion, }; // Build the entries @@ -86,6 +88,7 @@ Map buildHar(List httpRequests) { NetworkEventKeys.startedDateTime: e.startTimestamp.toUtc().toIso8601String(), NetworkEventKeys.time: e.duration?.inMilliseconds, + // Request NetworkEventKeys.request: { NetworkEventKeys.method: e.method.toUpperCase(), NetworkEventKeys.url: e.uri.toString(), @@ -97,9 +100,10 @@ Map buildHar(List httpRequests) { NetworkEventKeys.mimeType: e.contentType, NetworkEventKeys.text: e.requestBody, }, - NetworkEventKeys.headersSize: NetworkEventDefaults.headersSize, - NetworkEventKeys.bodySize: NetworkEventDefaults.bodySize, + NetworkEventKeys.headersSize: _calculateHeadersSize(e.requestHeaders), + NetworkEventKeys.bodySize: _calculateBodySize(e.requestBody), }, + // Response NetworkEventKeys.response: { NetworkEventKeys.status: e.status, NetworkEventKeys.statusText: '', @@ -112,9 +116,10 @@ Map buildHar(List httpRequests) { NetworkEventKeys.text: e.responseBody, }, NetworkEventKeys.redirectURL: '', - NetworkEventKeys.headersSize: NetworkEventDefaults.headersSize, - NetworkEventKeys.bodySize: NetworkEventDefaults.bodySize, + NetworkEventKeys.headersSize: _calculateHeadersSize(e.responseHeaders), + NetworkEventKeys.bodySize: _calculateBodySize(e.responseBody), }, + // Cache NetworkEventKeys.cache: {}, NetworkEventKeys.timings: { NetworkEventKeys.blocked: NetworkEventDefaults.blocked, @@ -139,3 +144,31 @@ Map buildHar(List httpRequests) { }, }; } + +int _calculateHeadersSize(Map? headers) { + if (headers == null) return -1; + + // Combine headers into a single string with CRLF endings + String headersString = headers.entries.map((entry) { + final key = entry.key; + var value = entry.value; + // If the value is a List, join it with a comma + if (value is List) { + value = value.join(', '); + } + return '$key: $value\r\n'; + }).join(); + + // Add final CRLF to indicate end of headers + headersString += '\r\n'; + + // Calculate the byte length of the headers string + return utf8.encode(headersString).length; +} + +int _calculateBodySize(String? requestBody) { + if (requestBody == null || requestBody.isEmpty) { + return 0; + } + return utf8.encode(requestBody).length; +} diff --git a/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart index c193b372528..5c66632d6f2 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart @@ -60,20 +60,14 @@ class NetworkEventKeys { class NetworkEventDefaults { static const logVersion = '1.2'; static const creatorName = 'devtools'; - static const creatorVersion = '0.0.2'; - static const id = 'page_0'; - static const title = 'FlutterCapture'; static const onContentLoad = -1; static const onLoad = -1; static const httpVersion = 'HTTP/1.1'; static const responseHttpVersion = 'http/2.0'; - static const headersSize = -1; - static const bodySize = -1; static const blocked = -1; static const dns = -1; static const connect = -1; static const send = 1; static const receive = 1; static const ssl = -1; - static const serverIPAddress = '10.0.0.1'; } From 16faad7ee139b4f7eca7f076f9d16ed595cdf1da Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Thu, 27 Jun 2024 13:42:02 +0530 Subject: [PATCH 10/35] organized imports --- .../devtools_app/lib/src/screens/network/har_builder.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index d6ac638b846..b728b590b6b 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -3,10 +3,10 @@ // found in the LICENSE file. import 'dart:convert'; -import '../../shared/utils.dart'; -import '../../shared/analytics/constants.dart'; +import '../../shared/analytics/constants.dart'; import '../../shared/http/http_request_data.dart'; +import '../../shared/utils.dart'; /// Builds a HAR (HTTP Archive) object from a list of HTTP requests. /// From 1c4442c84a612ec76cc7d67d62c059208d13f426 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Mon, 8 Jul 2024 23:29:20 +0530 Subject: [PATCH 11/35] removed mixin and override, changed year to 2024, renamed variable --- .../lib/src/screens/network/har_builder.dart | 2 +- .../lib/src/screens/network/network_controller.dart | 12 ------------ .../lib/src/screens/network/network_screen.dart | 2 +- .../analytics/constants/_network_constants.dart | 2 +- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index b728b590b6b..d589b761b41 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -1,4 +1,4 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2024 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. 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 99f3f1b8bb3..c63f25cb229 100644 --- a/packages/devtools_app/lib/src/screens/network/network_controller.dart +++ b/packages/devtools_app/lib/src/screens/network/network_controller.dart @@ -14,7 +14,6 @@ import '../../shared/config_specific/logger/allowed_error.dart'; import '../../shared/globals.dart'; import '../../shared/http/http_request_data.dart'; import '../../shared/http/http_service.dart' as http_service; -import '../../shared/offline_data.dart'; import '../../shared/primitives/utils.dart'; import '../../shared/ui/filter.dart'; import '../../shared/ui/search.dart'; @@ -50,7 +49,6 @@ class NetworkController extends DisposableController with SearchControllerMixin, FilterControllerMixin, - OfflineScreenControllerMixin, AutoDisposeControllerMixin { NetworkController() { _networkService = NetworkService(this); @@ -393,16 +391,6 @@ class NetworkController extends DisposableController serviceConnection.errorBadgeManager.incrementBadgeCount(NetworkScreen.id); } } - - @override - OfflineScreenData prepareOfflineScreenData() { - debugPrint('offline data - httpRequests are $_httpRequests'); - return OfflineScreenData( - screenId: NetworkScreen.id, - //TODO deserialize har data and pass here - data: {}, - ); - } } /// Class for managing the set of all current websocket requests, and diff --git a/packages/devtools_app/lib/src/screens/network/network_screen.dart b/packages/devtools_app/lib/src/screens/network/network_screen.dart index edb0bea51a2..56592da40b7 100644 --- a/packages/devtools_app/lib/src/screens/network/network_screen.dart +++ b/packages/devtools_app/lib/src/screens/network/network_screen.dart @@ -239,7 +239,7 @@ class _NetworkProfilerControlsState extends State<_NetworkProfilerControls> _NetworkProfilerControls._includeTextWidth, onPressed: widget.controller.exportAsHarFile, gaScreen: gac.network, - gaSelection: gac.NetworkEvent.networkDownloadHar, + gaSelection: gac.NetworkEvent.downloadAsHar, ), const SizedBox(width: defaultSpacing), const Expanded(child: SizedBox()), diff --git a/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart index 5c66632d6f2..0e04ea2eb91 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart @@ -5,7 +5,7 @@ part of '../constants.dart'; class NetworkEvent { - static const networkDownloadHar = 'networkDownloadHar'; + static const downloadAsHar = 'downloadAsHar'; } class NetworkEventKeys { From 8c08441c12a4b82eb5ab6dc411ee4e9d1104321a Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Mon, 8 Jul 2024 23:39:45 +0530 Subject: [PATCH 12/35] moved constants, converted to enum --- .../lib/src/screens/network/constants.dart | 71 +++++++++++ .../lib/src/screens/network/har_builder.dart | 113 +++++++++--------- .../src/screens/network/network_screen.dart | 3 +- .../constants/_network_constants.dart | 73 ----------- 4 files changed, 131 insertions(+), 129 deletions(-) create mode 100644 packages/devtools_app/lib/src/screens/network/constants.dart delete mode 100644 packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart diff --git a/packages/devtools_app/lib/src/screens/network/constants.dart b/packages/devtools_app/lib/src/screens/network/constants.dart new file mode 100644 index 00000000000..4d2f238c4db --- /dev/null +++ b/packages/devtools_app/lib/src/screens/network/constants.dart @@ -0,0 +1,71 @@ +// Copyright 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +enum NetworkEvent { + downloadAsHar, +} + +enum NetworkEventKeys { + log, + version, + creator, + name, + creatorVersion, + pages, + startedDateTime, + id, + title, + pageTimings, + onContentLoad, + onLoad, + entries, + pageref, + time, + request, + method, + url, + httpVersion, + cookies, + headers, + queryString, + postData, + mimeType, + text, + headersSize, + bodySize, + response, + status, + statusText, + content, + size, + redirectURL, + cache, + timings, + blocked, + dns, + connect, + send, + wait, + receive, + ssl, + serverIPAddress, + connection, + comment, + value, +} + +class NetworkEventDefaults { + static const logVersion = '1.2'; + static const creatorName = 'devtools'; + static const onContentLoad = -1; + static const onLoad = -1; + static const httpVersion = 'HTTP/1.1'; + static const responseHttpVersion = 'http/2.0'; + static const blocked = -1; + static const dns = -1; + static const connect = -1; + static const send = 1; + static const receive = 1; + static const ssl = -1; +} diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index d589b761b41..ae3f3be9959 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -4,9 +4,9 @@ import 'dart:convert'; -import '../../shared/analytics/constants.dart'; import '../../shared/http/http_request_data.dart'; import '../../shared/utils.dart'; +import 'constants.dart'; /// Builds a HAR (HTTP Archive) object from a list of HTTP requests. /// @@ -25,16 +25,16 @@ import '../../shared/utils.dart'; Map buildHar(List httpRequests) { // Build the creator final creator = { - NetworkEventKeys.name: NetworkEventDefaults.creatorName, - NetworkEventKeys.creatorVersion: devToolsVersion, + NetworkEventKeys.name.name: NetworkEventDefaults.creatorName, + NetworkEventKeys.creatorVersion.name: devToolsVersion, }; // Build the entries final entries = httpRequests.map((e) { final requestCookies = e.requestCookies.map((cookie) { return { - NetworkEventKeys.name: cookie.name, - NetworkEventKeys.value: cookie.value, + NetworkEventKeys.name.name: cookie.name, + NetworkEventKeys.value.name: cookie.value, 'path': cookie.path, 'domain': cookie.domain, 'expires': cookie.expires?.toUtc().toIso8601String(), @@ -49,22 +49,22 @@ Map buildHar(List httpRequests) { value = value.first; } return { - NetworkEventKeys.name: header.key, - NetworkEventKeys.value: value, + NetworkEventKeys.name.name: header.key, + NetworkEventKeys.value.name: value, }; }).toList(); final queryString = Uri.parse(e.uri).queryParameters.entries.map((param) { return { - NetworkEventKeys.name: param.key, - NetworkEventKeys.value: param.value, + NetworkEventKeys.name.name: param.key, + NetworkEventKeys.value.name: param.value, }; }).toList(); final responseCookies = e.responseCookies.map((cookie) { return { - NetworkEventKeys.name: cookie.name, - NetworkEventKeys.value: cookie.value, + NetworkEventKeys.name.name: cookie.name, + NetworkEventKeys.value.name: cookie.value, 'path': cookie.path, 'domain': cookie.domain, 'expires': cookie.expires?.toUtc().toIso8601String(), @@ -79,68 +79,71 @@ Map buildHar(List httpRequests) { value = value.first; } return { - NetworkEventKeys.name: header.key, - NetworkEventKeys.value: value, + NetworkEventKeys.name.name: header.key, + NetworkEventKeys.value.name: value, }; }).toList(); return { - NetworkEventKeys.startedDateTime: + NetworkEventKeys.startedDateTime.name: e.startTimestamp.toUtc().toIso8601String(), - NetworkEventKeys.time: e.duration?.inMilliseconds, + NetworkEventKeys.time.name: e.duration?.inMilliseconds, // Request - NetworkEventKeys.request: { - NetworkEventKeys.method: e.method.toUpperCase(), - NetworkEventKeys.url: e.uri.toString(), - NetworkEventKeys.httpVersion: NetworkEventDefaults.httpVersion, - NetworkEventKeys.cookies: requestCookies, - NetworkEventKeys.headers: requestHeaders, - NetworkEventKeys.queryString: queryString, - NetworkEventKeys.postData: { - NetworkEventKeys.mimeType: e.contentType, - NetworkEventKeys.text: e.requestBody, + NetworkEventKeys.request.name: { + NetworkEventKeys.method.name: e.method.toUpperCase(), + NetworkEventKeys.url.name: e.uri.toString(), + NetworkEventKeys.httpVersion.name: NetworkEventDefaults.httpVersion, + NetworkEventKeys.cookies.name: requestCookies, + NetworkEventKeys.headers.name: requestHeaders, + NetworkEventKeys.queryString.name: queryString, + NetworkEventKeys.postData.name: { + NetworkEventKeys.mimeType.name: e.contentType, + NetworkEventKeys.text.name: e.requestBody, }, - NetworkEventKeys.headersSize: _calculateHeadersSize(e.requestHeaders), - NetworkEventKeys.bodySize: _calculateBodySize(e.requestBody), + NetworkEventKeys.headersSize.name: + _calculateHeadersSize(e.requestHeaders), + NetworkEventKeys.bodySize.name: _calculateBodySize(e.requestBody), }, // Response - NetworkEventKeys.response: { - NetworkEventKeys.status: e.status, - NetworkEventKeys.statusText: '', - NetworkEventKeys.httpVersion: NetworkEventDefaults.responseHttpVersion, - NetworkEventKeys.cookies: responseCookies, - NetworkEventKeys.headers: responseHeaders, - NetworkEventKeys.content: { - NetworkEventKeys.size: e.responseBody?.length, - NetworkEventKeys.mimeType: e.type, - NetworkEventKeys.text: e.responseBody, + NetworkEventKeys.response.name: { + NetworkEventKeys.status.name: e.status, + NetworkEventKeys.statusText.name: '', + NetworkEventKeys.httpVersion.name: + NetworkEventDefaults.responseHttpVersion, + NetworkEventKeys.cookies.name: responseCookies, + NetworkEventKeys.headers.name: responseHeaders, + NetworkEventKeys.content.name: { + NetworkEventKeys.size.name: e.responseBody?.length, + NetworkEventKeys.mimeType.name: e.type, + NetworkEventKeys.text.name: e.responseBody, }, - NetworkEventKeys.redirectURL: '', - NetworkEventKeys.headersSize: _calculateHeadersSize(e.responseHeaders), - NetworkEventKeys.bodySize: _calculateBodySize(e.responseBody), + NetworkEventKeys.redirectURL.name: '', + NetworkEventKeys.headersSize.name: + _calculateHeadersSize(e.responseHeaders), + NetworkEventKeys.bodySize.name: _calculateBodySize(e.responseBody), }, // Cache - NetworkEventKeys.cache: {}, - NetworkEventKeys.timings: { - NetworkEventKeys.blocked: NetworkEventDefaults.blocked, - NetworkEventKeys.dns: NetworkEventDefaults.dns, - NetworkEventKeys.connect: NetworkEventDefaults.connect, - NetworkEventKeys.send: NetworkEventDefaults.send, - NetworkEventKeys.wait: e.duration!.inMilliseconds - 2, - NetworkEventKeys.receive: NetworkEventDefaults.receive, - NetworkEventKeys.ssl: NetworkEventDefaults.ssl, + NetworkEventKeys.cache.name: {}, + NetworkEventKeys.timings.name: { + NetworkEventKeys.blocked.name: NetworkEventDefaults.blocked, + NetworkEventKeys.dns.name: NetworkEventDefaults.dns, + NetworkEventKeys.connect.name: NetworkEventDefaults.connect, + NetworkEventKeys.send.name: NetworkEventDefaults.send, + NetworkEventKeys.wait.name: e.duration!.inMilliseconds - 2, + NetworkEventKeys.receive.name: NetworkEventDefaults.receive, + NetworkEventKeys.ssl.name: NetworkEventDefaults.ssl, }, - NetworkEventKeys.connection: e.hashCode.toString(), - NetworkEventKeys.comment: '', + NetworkEventKeys.connection.name: e.hashCode.toString(), + NetworkEventKeys.comment.name: '', }; }).toList(); // Assemble the final HAR object return { - NetworkEventKeys.log: { - NetworkEventKeys.version: NetworkEventDefaults.logVersion, - NetworkEventKeys.creator: creator, - NetworkEventKeys.entries: entries, + NetworkEventKeys.log.name: { + NetworkEventKeys.version.name: NetworkEventDefaults.logVersion, + NetworkEventKeys.creator.name: creator, + NetworkEventKeys.entries.name: entries, }, }; } diff --git a/packages/devtools_app/lib/src/screens/network/network_screen.dart b/packages/devtools_app/lib/src/screens/network/network_screen.dart index 56592da40b7..1cda82cbc35 100644 --- a/packages/devtools_app/lib/src/screens/network/network_screen.dart +++ b/packages/devtools_app/lib/src/screens/network/network_screen.dart @@ -25,6 +25,7 @@ import '../../shared/ui/filter.dart'; import '../../shared/ui/search.dart'; import '../../shared/ui/utils.dart'; import '../../shared/utils.dart'; +import 'constants.dart'; import 'network_controller.dart'; import 'network_model.dart'; import 'network_request_inspector.dart'; @@ -239,7 +240,7 @@ class _NetworkProfilerControlsState extends State<_NetworkProfilerControls> _NetworkProfilerControls._includeTextWidth, onPressed: widget.controller.exportAsHarFile, gaScreen: gac.network, - gaSelection: gac.NetworkEvent.downloadAsHar, + gaSelection: NetworkEvent.downloadAsHar.name, ), const SizedBox(width: defaultSpacing), const Expanded(child: SizedBox()), diff --git a/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart deleted file mode 100644 index 0e04ea2eb91..00000000000 --- a/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2024 The Chromium Authors. 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 '../constants.dart'; - -class NetworkEvent { - static const downloadAsHar = 'downloadAsHar'; -} - -class NetworkEventKeys { - static const log = 'log'; - static const version = 'version'; - static const creator = 'creator'; - static const name = 'name'; - static const creatorVersion = 'version'; - static const pages = 'pages'; - static const startedDateTime = 'startedDateTime'; - static const id = 'id'; - static const title = 'title'; - static const pageTimings = 'pageTimings'; - static const onContentLoad = 'onContentLoad'; - static const onLoad = 'onLoad'; - static const entries = 'entries'; - static const pageref = 'pageref'; - static const time = 'time'; - static const request = 'request'; - static const method = 'method'; - static const url = 'url'; - static const httpVersion = 'httpVersion'; - static const cookies = 'cookies'; - static const headers = 'headers'; - static const queryString = 'queryString'; - static const postData = 'postData'; - static const mimeType = 'mimeType'; - static const text = 'text'; - static const headersSize = 'headersSize'; - static const bodySize = 'bodySize'; - static const response = 'response'; - static const status = 'status'; - static const statusText = 'statusText'; - static const content = 'content'; - static const size = 'size'; - static const redirectURL = 'redirectURL'; - static const cache = 'cache'; - static const timings = 'timings'; - static const blocked = 'blocked'; - static const dns = 'dns'; - static const connect = 'connect'; - static const send = 'send'; - static const wait = 'wait'; - static const receive = 'receive'; - static const ssl = 'ssl'; - static const serverIPAddress = 'serverIPAddress'; - static const connection = 'connection'; - static const comment = 'comment'; - static const value = 'value'; -} - -class NetworkEventDefaults { - static const logVersion = '1.2'; - static const creatorName = 'devtools'; - static const onContentLoad = -1; - static const onLoad = -1; - static const httpVersion = 'HTTP/1.1'; - static const responseHttpVersion = 'http/2.0'; - static const blocked = -1; - static const dns = -1; - static const connect = -1; - static const send = 1; - static const receive = 1; - static const ssl = -1; -} From 625b452c59e73100aab825f43033218075d08bd5 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Thu, 11 Jul 2024 09:01:11 +0530 Subject: [PATCH 13/35] downloadAsHar moved back to analytics constants --- .../devtools_app/lib/src/screens/network/network_screen.dart | 3 +-- .../src/shared/analytics/constants/_network_constants.dart | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart diff --git a/packages/devtools_app/lib/src/screens/network/network_screen.dart b/packages/devtools_app/lib/src/screens/network/network_screen.dart index 1cda82cbc35..1600fb6a9ba 100644 --- a/packages/devtools_app/lib/src/screens/network/network_screen.dart +++ b/packages/devtools_app/lib/src/screens/network/network_screen.dart @@ -25,7 +25,6 @@ import '../../shared/ui/filter.dart'; import '../../shared/ui/search.dart'; import '../../shared/ui/utils.dart'; import '../../shared/utils.dart'; -import 'constants.dart'; import 'network_controller.dart'; import 'network_model.dart'; import 'network_request_inspector.dart'; @@ -240,7 +239,7 @@ class _NetworkProfilerControlsState extends State<_NetworkProfilerControls> _NetworkProfilerControls._includeTextWidth, onPressed: widget.controller.exportAsHarFile, gaScreen: gac.network, - gaSelection: NetworkEvent.downloadAsHar.name, + gaSelection: gac.NetworkEvent.downloadAsHar.name, ), const SizedBox(width: defaultSpacing), const Expanded(child: SizedBox()), diff --git a/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart new file mode 100644 index 00000000000..a183c67db7c --- /dev/null +++ b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart @@ -0,0 +1,5 @@ +part of '../constants.dart'; + +enum NetworkEvent { + downloadAsHar, +} From 3dd0274ca157dc39f409b45f95a860c7f5eb4d3e Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Thu, 11 Jul 2024 10:11:16 +0530 Subject: [PATCH 14/35] added copyright header --- .../src/shared/analytics/constants/_network_constants.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart index a183c67db7c..5ac4bbfa2cd 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants/_network_constants.dart @@ -1,3 +1,7 @@ +// Copyright 2024 The Chromium Authors. 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 '../constants.dart'; enum NetworkEvent { From 0261ab71156c375e34b2a92d2febf4a7771027e9 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Fri, 12 Jul 2024 11:19:25 +0530 Subject: [PATCH 15/35] HarNetworkData model class added, serialisation and de-serialisation code added --- .../lib/src/screens/network/constants.dart | 18 +- .../lib/src/screens/network/har_builder.dart | 10 +- .../src/screens/network/har_network_data.dart | 29 +++ .../src/shared/http/http_request_data.dart | 95 ++++++++ .../test/network/har_network_test.dart | 51 ++++ .../test/network/sample_requests.json | 228 ++++++++++++++++++ 6 files changed, 426 insertions(+), 5 deletions(-) create mode 100644 packages/devtools_app/lib/src/screens/network/har_network_data.dart create mode 100644 packages/devtools_app/test/network/har_network_test.dart create mode 100644 packages/devtools_app/test/network/sample_requests.json diff --git a/packages/devtools_app/lib/src/screens/network/constants.dart b/packages/devtools_app/lib/src/screens/network/constants.dart index 4d2f238c4db..3594d6c9f0c 100644 --- a/packages/devtools_app/lib/src/screens/network/constants.dart +++ b/packages/devtools_app/lib/src/screens/network/constants.dart @@ -2,10 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -enum NetworkEvent { - downloadAsHar, -} - enum NetworkEventKeys { log, version, @@ -69,3 +65,17 @@ class NetworkEventDefaults { static const receive = 1; static const ssl = -1; } + +class NetworkEventCustomFieldKeys { + static const isolateId = '_isolateId'; + static const id = '_id'; + static const startTime = '_startTime'; + static const events = '_events'; +} + +class NetworkEventCustomFieldRemappedKeys { + static const isolateId = 'isolateId'; + static const id = 'id'; + static const startTime = 'startTime'; + static const events = 'events'; +} diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index ae3f3be9959..080c33b9611 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -129,12 +129,20 @@ Map buildHar(List httpRequests) { NetworkEventKeys.dns.name: NetworkEventDefaults.dns, NetworkEventKeys.connect.name: NetworkEventDefaults.connect, NetworkEventKeys.send.name: NetworkEventDefaults.send, - NetworkEventKeys.wait.name: e.duration!.inMilliseconds - 2, + NetworkEventKeys.wait.name: e.duration?.inMilliseconds ?? 0 - 2, NetworkEventKeys.receive.name: NetworkEventDefaults.receive, NetworkEventKeys.ssl.name: NetworkEventDefaults.ssl, }, NetworkEventKeys.connection.name: e.hashCode.toString(), NetworkEventKeys.comment.name: '', + + // Custom fields + // har spec requires underscore to be added for custom fields, hence removing them + NetworkEventCustomFieldKeys.isolateId: '', + NetworkEventCustomFieldKeys.id: e.id, + NetworkEventCustomFieldKeys.startTime: + e.startTimestamp.microsecondsSinceEpoch, + NetworkEventCustomFieldKeys.events: [], }; }).toList(); diff --git a/packages/devtools_app/lib/src/screens/network/har_network_data.dart b/packages/devtools_app/lib/src/screens/network/har_network_data.dart new file mode 100644 index 00000000000..dccda0b9686 --- /dev/null +++ b/packages/devtools_app/lib/src/screens/network/har_network_data.dart @@ -0,0 +1,29 @@ +import 'package:flutter/foundation.dart'; + +import '../../../devtools_app.dart'; +import 'har_builder.dart'; +// ignore_for_file: avoid_dynamic_calls + +class HarNetworkData { + HarNetworkData(this.requests); + + factory HarNetworkData.fromJson(Map json) { + count++; + debugPrint('count is $count'); + final entries = (json['log']?['entries'] as List? ?? []) + .map( + (entryJson) => + DartIOHttpRequestData.fromJson(entryJson as Map), + ) + .toList(); + + return HarNetworkData(entries); + } + static int count = 0; + + final List requests; + + Map toJson() { + return buildHar(requests); + } +} 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 1196793b4d7..b09c45ca798 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 @@ -10,11 +10,13 @@ import 'package:logging/logging.dart'; import 'package:mime/mime.dart'; import 'package:vm_service/vm_service.dart'; +import '../../screens/network/constants.dart'; import '../../screens/network/network_model.dart'; import '../globals.dart'; import '../primitives/utils.dart'; import 'http.dart'; +// ignore_for_file: avoid_dynamic_calls final _log = Logger('http_request_data'); /// Used to represent an instant event emitted during an HTTP request. @@ -46,6 +48,99 @@ class DartIOHttpRequestData extends NetworkRequest { } } + factory DartIOHttpRequestData.fromJson(Map requestData) { + _convertHeaders(requestData); + final modifiedRequestData = + _remapCustomFieldKeys(requestData) as Map; + + // Retrieving url, method from requestData + modifiedRequestData['uri'] = modifiedRequestData['request']['url']; + modifiedRequestData['method'] = modifiedRequestData['request']['method']; + + // Adding missing keys which are mandatory for parsing + modifiedRequestData['response']['redirects'] = []; + + return DartIOHttpRequestData( + HttpProfileRequestRef.parse(modifiedRequestData)!, + requestFullDataFromVmService: false, + ) + .._responseBody = modifiedRequestData['response'] != null && + modifiedRequestData['response']['content'] != null + ? modifiedRequestData['response']['content']['text'] + : null + .._requestBody = modifiedRequestData['request'] != null && + modifiedRequestData['request']['postData'] != null + ? modifiedRequestData['request']['postData']['text'] + : null; + } + + static Map _convertHeadersListToMap( + List serializedHeaders, + ) { + final transformedHeaders = {}; + + for (final header in serializedHeaders) { + final key = header[NetworkEventKeys.name.name] as String?; + final value = header[NetworkEventKeys.value.name]; + + if (transformedHeaders.containsKey(key)) { + if (transformedHeaders[key] is List) { + (transformedHeaders[key] as List).add(value); + } else { + transformedHeaders[key ?? ''] = [transformedHeaders[key], value]; + } + } else { + transformedHeaders[key ?? ''] = value; + } + } + + return transformedHeaders; + } + + // Convert list of headers to map + static void _convertHeaders(Map requestData) { + // Request Headers + if (requestData['request'] != null && + requestData['request']['headers'] != null) { + if (requestData['request']['headers'] is List) { + requestData['request']['headers'] = + _convertHeadersListToMap(requestData['request']['headers']); + } + } + // Response Headers + if (requestData['response'] != null && + requestData['response']['headers'] != null) { + if (requestData['response']['headers'] is List) { + requestData['response']['headers'] = + _convertHeadersListToMap(requestData['response']['headers']); + } + } + } + + // Removing underscores from custom fields + static Map _remapCustomFieldKeys( + Map originalMap, + ) { + final replacementMap = { + NetworkEventCustomFieldKeys.isolateId: 'isolateId', + NetworkEventCustomFieldKeys.id: 'id', + NetworkEventCustomFieldKeys.startTime: 'startTime', + NetworkEventCustomFieldKeys.events: 'events', + }; + + final convertedMap = {}; + + originalMap.forEach((key, value) { + if (replacementMap.containsKey(key)) { + convertedMap[replacementMap[key]!] = value; + } else { + convertedMap[key] = value; + } + }); + + return convertedMap; + } + static const _connectionInfoKey = 'connectionInfo'; static const _contentTypeKey = 'content-type'; static const _localPortKey = 'localPort'; diff --git a/packages/devtools_app/test/network/har_network_test.dart b/packages/devtools_app/test/network/har_network_test.dart new file mode 100644 index 00000000000..ecfbb024d13 --- /dev/null +++ b/packages/devtools_app/test/network/har_network_test.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:devtools_app/src/screens/network/har_network_data.dart'; + +import 'package:flutter_test/flutter_test.dart'; + +// ignore_for_file: avoid_dynamic_calls + +void main() { + final file = File('test/network/sample_requests.json'); + final fileContent = file.readAsStringSync(); + final jsonData = jsonDecode(fileContent); + group('HarNetworkData', () { + test('toJson serializes correctly', () { + // Parse the HAR data + final harData = HarNetworkData.fromJson(jsonData); + + // Serialize the HAR data back to JSON + final json = harData.toJson(); + + // Verify the serialization + expect(json['log'], isNotNull); + final log = json['log'] as Map?; + expect(log?['version'], '1.2'); + expect(log?['creator']?['name'], 'devtools'); + + final entries = log?['entries'] as List?; + expect(entries?.length, 2); + + final entry = entries?[0] as Map?; + expect(entry?['startedDateTime'], '2024-07-11T13:19:35.156Z'); + expect(entry?['request']?['method'], 'GET'); + expect( + entry?['request']?['url'], + 'https://jsonplaceholder.typicode.com/albums/1', + ); + expect(entry?['request']?['httpVersion'], 'HTTP/1.1'); + expect(entry?['request']?['cookies'], isEmpty); + + expect(entry?['cache'], isEmpty); + expect(entry?['timings']?['blocked'], -1); + expect(entry?['timings']?['dns'], -1); + expect(entry?['timings']?['connect'], -1); + expect(entry?['timings']?['send'], 1); + expect(entry?['timings']?['receive'], 1); + expect(entry?['timings']?['ssl'], -1); + expect(entry?['comment'], ''); + }); + }); +} diff --git a/packages/devtools_app/test/network/sample_requests.json b/packages/devtools_app/test/network/sample_requests.json new file mode 100644 index 00000000000..fc828bccf1c --- /dev/null +++ b/packages/devtools_app/test/network/sample_requests.json @@ -0,0 +1,228 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "devtools", + "creatorVersion": "2.38.0-dev.0" + }, + "entries": [ + { + "startedDateTime": "2024-07-11T13:19:35.156Z", + "time": 313, + "request": { + "method": "GET", + "url": "https://jsonplaceholder.typicode.com/albums/1", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": { "name": "x-ratelimit-reset", + "value": "1709091028"}, + "queryString": [], + "postData": { + "mimeType": "[application/json; charset=utf-8]", + "text": null + }, + "headersSize": 120, + "bodySize": 0 + }, + "response": { + "status": "200", + "statusText": "", + "httpVersion": "http/2.0", + "cookies": [], + "headers": [{"": ""}], + "content": { + "size": 0, + "mimeType": "json", + "text": "" + }, + "redirectURL": "", + "headersSize": 1164, + "bodySize": 0 + }, + "cache": {}, + "timings": { + "blocked": -1, + "dns": -1, + "connect": -1, + "send": 1, + "wait": 313, + "receive": 1, + "ssl": -1 + }, + "connection": "525659655", + "comment": "", + "_isolateId": "", + "_id": "-2285651906968511108", + "_startTime": 1720703975156000, + "_events": [] + }, + { + "startedDateTime": "2024-07-11T13:19:37.155Z", + "time": 182, + "request": { + "method": "GET", + "url": "https://jsonplaceholder.typicode.com/albums/1", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "user-agent", + "value": "Dart/3.4 (dart:io)" + }, + { + "name": "accept-encoding", + "value": "gzip" + }, + { + "name": "content-length", + "value": "0" + }, + { + "name": "host", + "value": "jsonplaceholder.typicode.com" + } + ], + "queryString": [], + "postData": { + "mimeType": "[application/json; charset=utf-8]", + "text": null + }, + "headersSize": 120, + "bodySize": 0 + }, + "response": { + "status": "200", + "statusText": "", + "httpVersion": "http/2.0", + "cookies": [], + "headers": [ + { + "name": "x-ratelimit-reset", + "value": "1709091028" + }, + { + "name": "x-ratelimit-limit", + "value": "1000" + }, + { + "name": "date", + "value": "Thu, 11 Jul 2024 13:19:37 GMT" + }, + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "vary", + "value": "Origin, Accept-Encoding" + }, + { + "name": "content-encoding", + "value": "gzip" + }, + { + "name": "x-ratelimit-remaining", + "value": "999" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "server", + "value": "cloudflare" + }, + { + "name": "reporting-endpoints", + "value": "heroku-nel=https://nel.heroku.com/reports?ts=1709090976&sid=e11707d5-02a7-43ef-b45e-2cf4d2036f7d&s=YpsV0iwpAFY4lRLb4CpTkx%2B929Gjbe5%2BZ7X1RzUnIzs%3D" + }, + { + "name": "cf-ray", + "value": "8a1916135be93c5e-BOM" + }, + { + "name": "etag", + "value": "W/\"40-74G1+b66MteeTYAz6G+NybtDGFA\"" + }, + { + "name": "connection", + "value": "keep-alive" + }, + { + "name": "cache-control", + "value": "max-age=43200" + }, + { + "name": "age", + "value": "3993" + }, + { + "name": "report-to", + "value": "{\"group\":\"heroku-nel\",\"max_age\":3600,\"endpoints\":[{\"url\":\"https://nel.heroku.com/reports?ts=1709090976&sid=e11707d5-02a7-43ef-b45e-2cf4d2036f7d&s=YpsV0iwpAFY4lRLb4CpTkx%2B929Gjbe5%2BZ7X1RzUnIzs%3D\"}]}" + }, + { + "name": "cf-cache-status", + "value": "HIT" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "x-powered-by", + "value": "Express" + }, + { + "name": "alt-svc", + "value": "h3=\":443\"; ma=86400" + }, + { + "name": "nel", + "value": "{\"report_to\":\"heroku-nel\",\"max_age\":3600,\"success_fraction\":0.005,\"failure_fraction\":0.05,\"response_headers\":[\"Via\"]}" + }, + { + "name": "via", + "value": "1.1 vegur" + }, + { + "name": "x-content-type-options", + "value": "nosniff" + }, + { + "name": "expires", + "value": "-1" + } + ], + "content": { + "size": 0, + "mimeType": "json", + "text": "" + }, + "redirectURL": "", + "headersSize": 1164, + "bodySize": 0 + }, + "cache": {}, + "timings": { + "blocked": -1, + "dns": -1, + "connect": -1, + "send": 1, + "wait": 182, + "receive": 1, + "ssl": -1 + }, + "connection": "249624739", + "comment": "", + "_isolateId": "", + "_id": "-2285651906968511106", + "_startTime": 1720703977155000, + "_events": [] + } + ] + } +} \ No newline at end of file From b77c4549ac773cdc0613e57ba6edf01aede6d53f Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Fri, 12 Jul 2024 11:24:02 +0530 Subject: [PATCH 16/35] removed testing code --- .../devtools_app/lib/src/screens/network/har_network_data.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_network_data.dart b/packages/devtools_app/lib/src/screens/network/har_network_data.dart index dccda0b9686..4299ae72200 100644 --- a/packages/devtools_app/lib/src/screens/network/har_network_data.dart +++ b/packages/devtools_app/lib/src/screens/network/har_network_data.dart @@ -8,8 +8,6 @@ class HarNetworkData { HarNetworkData(this.requests); factory HarNetworkData.fromJson(Map json) { - count++; - debugPrint('count is $count'); final entries = (json['log']?['entries'] as List? ?? []) .map( (entryJson) => @@ -19,7 +17,6 @@ class HarNetworkData { return HarNetworkData(entries); } - static int count = 0; final List requests; From 484e9f25951b9741bbf3b95f3e8c38e33452b9d2 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Tue, 16 Jul 2024 15:09:46 +0530 Subject: [PATCH 17/35] added copyright, created enum, condition checking improvements, Used object directly --- .../lib/src/screens/network/constants.dart | 10 +++++----- .../lib/src/screens/network/har_builder.dart | 8 ++++---- .../screens/network/network_controller.dart | 20 ++++++++----------- .../src/shared/http/http_request_data.dart | 8 ++++---- .../test/network/har_network_test.dart | 4 ++++ 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/constants.dart b/packages/devtools_app/lib/src/screens/network/constants.dart index 3594d6c9f0c..3004ce1002c 100644 --- a/packages/devtools_app/lib/src/screens/network/constants.dart +++ b/packages/devtools_app/lib/src/screens/network/constants.dart @@ -73,9 +73,9 @@ class NetworkEventCustomFieldKeys { static const events = '_events'; } -class NetworkEventCustomFieldRemappedKeys { - static const isolateId = 'isolateId'; - static const id = 'id'; - static const startTime = 'startTime'; - static const events = 'events'; +enum NetworkEventCustomFieldRemappedKeys { + isolateId, + id, + startTime, + events, } diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index 080c33b9611..e64cc4e4ddd 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -138,11 +138,11 @@ Map buildHar(List httpRequests) { // Custom fields // har spec requires underscore to be added for custom fields, hence removing them - NetworkEventCustomFieldKeys.isolateId: '', - NetworkEventCustomFieldKeys.id: e.id, - NetworkEventCustomFieldKeys.startTime: + NetworkEventCustomFieldKeys.isolateId.name: '', + NetworkEventCustomFieldKeys.id.name: e.id, + NetworkEventCustomFieldKeys.startTime.name: e.startTimestamp.microsecondsSinceEpoch, - NetworkEventCustomFieldKeys.events: [], + NetworkEventCustomFieldKeys.events.name: [], }; }).toList(); 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 c63f25cb229..d6ae566414f 100644 --- a/packages/devtools_app/lib/src/screens/network/network_controller.dart +++ b/packages/devtools_app/lib/src/screens/network/network_controller.dart @@ -62,26 +62,22 @@ class NetworkController extends DisposableController List? _httpRequests; String? exportAsHarFile() { - final exportController = ExportController(); - _httpRequests = filteredData.value.whereType().toList(); - if (_httpRequests!.isEmpty) { + if (_httpRequests.isNullOrEmpty) { debugPrint('No valid request data to export'); return ''; } try { - if (_httpRequests != null && _httpRequests!.isNotEmpty) { - // Build the HAR object - final har = buildHar(_httpRequests!); - debugPrint('data is ${json.encode(har)}'); - return exportController.downloadFile( - json.encode(har), - type: ExportFileType.har, - ); - } + // Build the HAR object + final har = buildHar(_httpRequests!); + debugPrint('data is ${json.encode(har)}'); + return ExportController().downloadFile( + json.encode(har), + type: ExportFileType.har, + ); } catch (ex) { debugPrint('Exception in export $ex'); } 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 b09c45ca798..eb88ce65423 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 @@ -122,10 +122,10 @@ class DartIOHttpRequestData extends NetworkRequest { Map originalMap, ) { final replacementMap = { - NetworkEventCustomFieldKeys.isolateId: 'isolateId', - NetworkEventCustomFieldKeys.id: 'id', - NetworkEventCustomFieldKeys.startTime: 'startTime', - NetworkEventCustomFieldKeys.events: 'events', + NetworkEventCustomFieldKeys.isolateId.name: 'isolateId', + NetworkEventCustomFieldKeys.id.name: 'id', + NetworkEventCustomFieldKeys.startTime.name: 'startTime', + NetworkEventCustomFieldKeys.events.name: 'events', }; final convertedMap = {}; diff --git a/packages/devtools_app/test/network/har_network_test.dart b/packages/devtools_app/test/network/har_network_test.dart index ecfbb024d13..dea2702e3ec 100644 --- a/packages/devtools_app/test/network/har_network_test.dart +++ b/packages/devtools_app/test/network/har_network_test.dart @@ -1,3 +1,7 @@ +// Copyright 2024 The Chromium Authors. 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:convert'; import 'dart:io'; From f504b1875a3b647d7f3087a342f1af71c1a75341 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Tue, 16 Jul 2024 15:17:19 +0530 Subject: [PATCH 18/35] import optimised, used Serializable mixin --- .../lib/src/screens/network/har_network_data.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_network_data.dart b/packages/devtools_app/lib/src/screens/network/har_network_data.dart index 4299ae72200..ff6c1309504 100644 --- a/packages/devtools_app/lib/src/screens/network/har_network_data.dart +++ b/packages/devtools_app/lib/src/screens/network/har_network_data.dart @@ -1,10 +1,10 @@ -import 'package:flutter/foundation.dart'; +import 'package:devtools_shared/devtools_shared.dart'; +import '../../shared/http/http_request_data.dart'; -import '../../../devtools_app.dart'; import 'har_builder.dart'; // ignore_for_file: avoid_dynamic_calls -class HarNetworkData { +class HarNetworkData with Serializable { HarNetworkData(this.requests); factory HarNetworkData.fromJson(Map json) { @@ -20,6 +20,7 @@ class HarNetworkData { final List requests; + @override Map toJson() { return buildHar(requests); } From f741882973fc5692537b3f29b7f80d2e7b39eeea Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Tue, 16 Jul 2024 17:05:34 +0530 Subject: [PATCH 19/35] minor fix --- .../lib/src/shared/http/http_request_data.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 eb88ce65423..b09c45ca798 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 @@ -122,10 +122,10 @@ class DartIOHttpRequestData extends NetworkRequest { Map originalMap, ) { final replacementMap = { - NetworkEventCustomFieldKeys.isolateId.name: 'isolateId', - NetworkEventCustomFieldKeys.id.name: 'id', - NetworkEventCustomFieldKeys.startTime.name: 'startTime', - NetworkEventCustomFieldKeys.events.name: 'events', + NetworkEventCustomFieldKeys.isolateId: 'isolateId', + NetworkEventCustomFieldKeys.id: 'id', + NetworkEventCustomFieldKeys.startTime: 'startTime', + NetworkEventCustomFieldKeys.events: 'events', }; final convertedMap = {}; From eb7f84857eb168bfc3b7dd8d432406b53840e3a1 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Tue, 16 Jul 2024 17:07:37 +0530 Subject: [PATCH 20/35] replaced dynamic with Object? in har network data, minor fix --- .../devtools_app/lib/src/screens/network/har_builder.dart | 8 ++++---- .../lib/src/screens/network/har_network_data.dart | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index e64cc4e4ddd..080c33b9611 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -138,11 +138,11 @@ Map buildHar(List httpRequests) { // Custom fields // har spec requires underscore to be added for custom fields, hence removing them - NetworkEventCustomFieldKeys.isolateId.name: '', - NetworkEventCustomFieldKeys.id.name: e.id, - NetworkEventCustomFieldKeys.startTime.name: + NetworkEventCustomFieldKeys.isolateId: '', + NetworkEventCustomFieldKeys.id: e.id, + NetworkEventCustomFieldKeys.startTime: e.startTimestamp.microsecondsSinceEpoch, - NetworkEventCustomFieldKeys.events.name: [], + NetworkEventCustomFieldKeys.events: [], }; }).toList(); diff --git a/packages/devtools_app/lib/src/screens/network/har_network_data.dart b/packages/devtools_app/lib/src/screens/network/har_network_data.dart index ff6c1309504..562d5a251ba 100644 --- a/packages/devtools_app/lib/src/screens/network/har_network_data.dart +++ b/packages/devtools_app/lib/src/screens/network/har_network_data.dart @@ -2,13 +2,13 @@ import 'package:devtools_shared/devtools_shared.dart'; import '../../shared/http/http_request_data.dart'; import 'har_builder.dart'; -// ignore_for_file: avoid_dynamic_calls class HarNetworkData with Serializable { HarNetworkData(this.requests); - factory HarNetworkData.fromJson(Map json) { - final entries = (json['log']?['entries'] as List? ?? []) + factory HarNetworkData.fromJson(Map json) { + final entries = ((json['log'] as Map)['entries'] + as List) .map( (entryJson) => DartIOHttpRequestData.fromJson(entryJson as Map), From a608451adab5df8242b719e4d4695d66fcba44d2 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Tue, 16 Jul 2024 17:09:57 +0530 Subject: [PATCH 21/35] used enum --- .../lib/src/shared/http/http_request_data.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 b09c45ca798..c86c84807f8 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 @@ -122,10 +122,14 @@ class DartIOHttpRequestData extends NetworkRequest { Map originalMap, ) { final replacementMap = { - NetworkEventCustomFieldKeys.isolateId: 'isolateId', - NetworkEventCustomFieldKeys.id: 'id', - NetworkEventCustomFieldKeys.startTime: 'startTime', - NetworkEventCustomFieldKeys.events: 'events', + NetworkEventCustomFieldKeys.isolateId: + NetworkEventCustomFieldRemappedKeys.isolateId.name, + NetworkEventCustomFieldKeys.id: + NetworkEventCustomFieldRemappedKeys.id.name, + NetworkEventCustomFieldKeys.startTime: + NetworkEventCustomFieldRemappedKeys.startTime.name, + NetworkEventCustomFieldKeys.events: + NetworkEventCustomFieldRemappedKeys.events.name, }; final convertedMap = {}; From b734dc3e111e683f12bc45c9ce69f74de23d7fda Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Tue, 16 Jul 2024 17:14:08 +0530 Subject: [PATCH 22/35] header and dart docs added --- .../src/screens/network/har_network_data.dart | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/devtools_app/lib/src/screens/network/har_network_data.dart b/packages/devtools_app/lib/src/screens/network/har_network_data.dart index 562d5a251ba..8d9fae4ce7d 100644 --- a/packages/devtools_app/lib/src/screens/network/har_network_data.dart +++ b/packages/devtools_app/lib/src/screens/network/har_network_data.dart @@ -1,11 +1,32 @@ +// Copyright 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + import 'package:devtools_shared/devtools_shared.dart'; import '../../shared/http/http_request_data.dart'; import 'har_builder.dart'; +/// A class that represents network data in the HTTP Archive (HAR) format. +/// +/// This class implements the [Serializable] interface, allowing instances to +/// be serialized to and from JSON. class HarNetworkData with Serializable { + /// Creates an instance of [HarNetworkData] with a list of DartIOHttpRequestData requests. + /// + /// The [requests] parameter should contain the list of DartIOHttpRequestData request data. HarNetworkData(this.requests); + /// Creates an instance of [HarNetworkData] from a JSON object. + /// + /// This factory constructor expects the [json] parameter to be a map + /// representing the HAR data, with a structure containing a 'log' key, + /// which in turn contains an 'entries' key. Each entry in the 'entries' + /// list should be a map representing an HTTP request. + /// + /// ```dart + /// final harData = HarNetworkData.fromJson(json); + /// ``` factory HarNetworkData.fromJson(Map json) { final entries = ((json['log'] as Map)['entries'] as List) @@ -18,8 +39,17 @@ class HarNetworkData with Serializable { return HarNetworkData(entries); } + /// The list of DartIOHttpRequestData request data. final List requests; + /// Converts the instance to a JSON object. + /// + /// This method returns a map representing the HAR data, suitable for + /// serialization. + /// + /// ```dart + /// final json = harData.toJson(); + /// ``` @override Map toJson() { return buildHar(requests); From 449f9e654265bdb5ff7fb8489d0e5421de04e7dd Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Tue, 16 Jul 2024 17:24:41 +0530 Subject: [PATCH 23/35] used HarNetworkData --- .../lib/src/screens/network/network_controller.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 d6ae566414f..01785e084c8 100644 --- a/packages/devtools_app/lib/src/screens/network/network_controller.dart +++ b/packages/devtools_app/lib/src/screens/network/network_controller.dart @@ -18,7 +18,7 @@ import '../../shared/primitives/utils.dart'; import '../../shared/ui/filter.dart'; import '../../shared/ui/search.dart'; import '../../shared/utils.dart'; -import 'har_builder.dart'; +import 'har_network_data.dart'; import 'network_model.dart'; import 'network_screen.dart'; import 'network_service.dart'; @@ -72,10 +72,10 @@ class NetworkController extends DisposableController try { // Build the HAR object - final har = buildHar(_httpRequests!); + final har = HarNetworkData(_httpRequests!); debugPrint('data is ${json.encode(har)}'); return ExportController().downloadFile( - json.encode(har), + json.encode(har.toJson()), type: ExportFileType.har, ); } catch (ex) { From 0175330c84e2f2ab23a0a468e65bc9a47d0cb4e3 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Wed, 17 Jul 2024 12:23:51 +0530 Subject: [PATCH 24/35] print removed, scope added to class name in dart docs --- .../lib/src/screens/network/har_network_data.dart | 6 +++--- .../lib/src/screens/network/network_controller.dart | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_network_data.dart b/packages/devtools_app/lib/src/screens/network/har_network_data.dart index 8d9fae4ce7d..1eaebf6bc63 100644 --- a/packages/devtools_app/lib/src/screens/network/har_network_data.dart +++ b/packages/devtools_app/lib/src/screens/network/har_network_data.dart @@ -12,9 +12,9 @@ import 'har_builder.dart'; /// This class implements the [Serializable] interface, allowing instances to /// be serialized to and from JSON. class HarNetworkData with Serializable { - /// Creates an instance of [HarNetworkData] with a list of DartIOHttpRequestData requests. + /// Creates an instance of [HarNetworkData] with a list of [DartIOHttpRequestData] requests. /// - /// The [requests] parameter should contain the list of DartIOHttpRequestData request data. + /// The [requests] parameter should contain the list of [DartIOHttpRequestData] request data. HarNetworkData(this.requests); /// Creates an instance of [HarNetworkData] from a JSON object. @@ -39,7 +39,7 @@ class HarNetworkData with Serializable { return HarNetworkData(entries); } - /// The list of DartIOHttpRequestData request data. + /// The list of [DartIOHttpRequestData] request data. final List requests; /// Converts the instance to a JSON object. 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 01785e084c8..887a91995a9 100644 --- a/packages/devtools_app/lib/src/screens/network/network_controller.dart +++ b/packages/devtools_app/lib/src/screens/network/network_controller.dart @@ -73,7 +73,6 @@ class NetworkController extends DisposableController try { // Build the HAR object final har = HarNetworkData(_httpRequests!); - debugPrint('data is ${json.encode(har)}'); return ExportController().downloadFile( json.encode(har.toJson()), type: ExportFileType.har, From 6c13f54c88af02c1a5707d3d7d2c56bd78901c97 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Wed, 17 Jul 2024 12:30:27 +0530 Subject: [PATCH 25/35] dart docs improvements --- .../devtools_app/lib/src/screens/network/har_builder.dart | 2 +- .../lib/src/screens/network/har_network_data.dart | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index 080c33b9611..1e42be7cece 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -18,7 +18,7 @@ import 'constants.dart'; /// For more details on the HAR format, see the [HAR 1.2 Specification](https://github.com/ahmadnassri/har-spec/blob/master/versions/1.2.md). /// /// Parameters: -/// - [httpRequests]: A list of DartIOHttpRequestData data. +/// - [httpRequests]: A list of [DartIOHttpRequestData] data. /// /// Returns: /// - A Map representing the HAR object. diff --git a/packages/devtools_app/lib/src/screens/network/har_network_data.dart b/packages/devtools_app/lib/src/screens/network/har_network_data.dart index 1eaebf6bc63..19905bf589a 100644 --- a/packages/devtools_app/lib/src/screens/network/har_network_data.dart +++ b/packages/devtools_app/lib/src/screens/network/har_network_data.dart @@ -19,10 +19,10 @@ class HarNetworkData with Serializable { /// Creates an instance of [HarNetworkData] from a JSON object. /// - /// This factory constructor expects the [json] parameter to be a map + /// This factory constructor expects the [json] parameter to be a Map /// representing the HAR data, with a structure containing a 'log' key, /// which in turn contains an 'entries' key. Each entry in the 'entries' - /// list should be a map representing an HTTP request. + /// list should be a Map representing an HTTP request. /// /// ```dart /// final harData = HarNetworkData.fromJson(json); @@ -44,7 +44,7 @@ class HarNetworkData with Serializable { /// Converts the instance to a JSON object. /// - /// This method returns a map representing the HAR data, suitable for + /// This method returns a Map representing the HAR data, suitable for /// serialization. /// /// ```dart From f332b400251abe434f4399c6d983169a2f466be1 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Wed, 17 Jul 2024 15:15:31 +0530 Subject: [PATCH 26/35] removed -2ms --- packages/devtools_app/lib/src/screens/network/har_builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index 1e42be7cece..8172c60c491 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -129,7 +129,7 @@ Map buildHar(List httpRequests) { NetworkEventKeys.dns.name: NetworkEventDefaults.dns, NetworkEventKeys.connect.name: NetworkEventDefaults.connect, NetworkEventKeys.send.name: NetworkEventDefaults.send, - NetworkEventKeys.wait.name: e.duration?.inMilliseconds ?? 0 - 2, + NetworkEventKeys.wait.name: e.duration?.inMilliseconds ?? 0, NetworkEventKeys.receive.name: NetworkEventDefaults.receive, NetworkEventKeys.ssl.name: NetworkEventDefaults.ssl, }, From 70d9468741cabb85bb1f05d240e6da8ca306556e Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Wed, 17 Jul 2024 15:25:03 +0530 Subject: [PATCH 27/35] renamed exception variable --- .../lib/src/screens/network/network_controller.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 887a91995a9..a4ee0296bec 100644 --- a/packages/devtools_app/lib/src/screens/network/network_controller.dart +++ b/packages/devtools_app/lib/src/screens/network/network_controller.dart @@ -77,8 +77,8 @@ class NetworkController extends DisposableController json.encode(har.toJson()), type: ExportFileType.har, ); - } catch (ex) { - debugPrint('Exception in export $ex'); + } catch (e) { + debugPrint('Exception in export $e'); } return null; } From 6f03ce277f37a24c07cbb3c3d14e55fbf052b364 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Wed, 17 Jul 2024 15:27:46 +0530 Subject: [PATCH 28/35] changed dynamic to Object? --- .../devtools_app/lib/src/screens/network/har_network_data.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_network_data.dart b/packages/devtools_app/lib/src/screens/network/har_network_data.dart index 19905bf589a..97dc06b0c23 100644 --- a/packages/devtools_app/lib/src/screens/network/har_network_data.dart +++ b/packages/devtools_app/lib/src/screens/network/har_network_data.dart @@ -32,7 +32,7 @@ class HarNetworkData with Serializable { as List) .map( (entryJson) => - DartIOHttpRequestData.fromJson(entryJson as Map), + DartIOHttpRequestData.fromJson(entryJson as Map), ) .toList(); From 6c73e2897a3d186b19cf1a62270bf5db3ddd2f4b Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Wed, 17 Jul 2024 15:34:31 +0530 Subject: [PATCH 29/35] removed ignore, added cast everywhere, changed dynamic to Object? --- .../test/network/har_network_test.dart | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/devtools_app/test/network/har_network_test.dart b/packages/devtools_app/test/network/har_network_test.dart index dea2702e3ec..35e2b47da33 100644 --- a/packages/devtools_app/test/network/har_network_test.dart +++ b/packages/devtools_app/test/network/har_network_test.dart @@ -9,12 +9,11 @@ import 'package:devtools_app/src/screens/network/har_network_data.dart'; import 'package:flutter_test/flutter_test.dart'; -// ignore_for_file: avoid_dynamic_calls - void main() { final file = File('test/network/sample_requests.json'); final fileContent = file.readAsStringSync(); final jsonData = jsonDecode(fileContent); + group('HarNetworkData', () { test('toJson serializes correctly', () { // Parse the HAR data @@ -25,30 +24,31 @@ void main() { // Verify the serialization expect(json['log'], isNotNull); - final log = json['log'] as Map?; + final log = json['log'] as Map?; expect(log?['version'], '1.2'); - expect(log?['creator']?['name'], 'devtools'); + expect((log?['creator'] as Map)['name'], 'devtools'); - final entries = log?['entries'] as List?; + final entries = log?['entries'] as List?; expect(entries?.length, 2); - final entry = entries?[0] as Map?; + final entry = entries?[0] as Map?; expect(entry?['startedDateTime'], '2024-07-11T13:19:35.156Z'); - expect(entry?['request']?['method'], 'GET'); + expect((entry?['request'] as Map)['method'], 'GET'); expect( - entry?['request']?['url'], + (entry?['request'] as Map)['url'], 'https://jsonplaceholder.typicode.com/albums/1', ); - expect(entry?['request']?['httpVersion'], 'HTTP/1.1'); - expect(entry?['request']?['cookies'], isEmpty); + expect((entry?['request'] as Map)['httpVersion'], + 'HTTP/1.1'); + expect((entry?['request'] as Map)['cookies'], isEmpty); expect(entry?['cache'], isEmpty); - expect(entry?['timings']?['blocked'], -1); - expect(entry?['timings']?['dns'], -1); - expect(entry?['timings']?['connect'], -1); - expect(entry?['timings']?['send'], 1); - expect(entry?['timings']?['receive'], 1); - expect(entry?['timings']?['ssl'], -1); + expect((entry?['timings'] as Map)['blocked'], -1); + expect((entry?['timings'] as Map)['dns'], -1); + expect((entry?['timings'] as Map)['connect'], -1); + expect((entry?['timings'] as Map)['send'], 1); + expect((entry?['timings'] as Map)['receive'], 1); + expect((entry?['timings'] as Map)['ssl'], -1); expect(entry?['comment'], ''); }); }); From fb4fd58547a796c961984cd5e5c9c6c849cf9910 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Wed, 17 Jul 2024 16:32:25 +0530 Subject: [PATCH 30/35] removed ignore, added cast, added trailing comma --- .../src/shared/http/http_request_data.dart | 85 ++++++++++++------- .../test/network/har_network_test.dart | 6 +- 2 files changed, 59 insertions(+), 32 deletions(-) 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 c86c84807f8..cab14b9ba11 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 @@ -16,7 +16,6 @@ import '../globals.dart'; import '../primitives/utils.dart'; import 'http.dart'; -// ignore_for_file: avoid_dynamic_calls final _log = Logger('http_request_data'); /// Used to represent an instant event emitted during an HTTP request. @@ -50,28 +49,43 @@ class DartIOHttpRequestData extends NetworkRequest { factory DartIOHttpRequestData.fromJson(Map requestData) { _convertHeaders(requestData); + final modifiedRequestData = _remapCustomFieldKeys(requestData) as Map; + dynamic requestPostData; + dynamic responseContent; // Retrieving url, method from requestData - modifiedRequestData['uri'] = modifiedRequestData['request']['url']; - modifiedRequestData['method'] = modifiedRequestData['request']['method']; + modifiedRequestData['uri'] = + (modifiedRequestData['request'] as Map)['url']; + modifiedRequestData['method'] = + (modifiedRequestData['request'] as Map)['method']; // Adding missing keys which are mandatory for parsing - modifiedRequestData['response']['redirects'] = []; + (modifiedRequestData['response'] as Map)['redirects'] = []; + + if (modifiedRequestData['response'] != null && + (modifiedRequestData['response'] as Map)['content'] != + null) { + responseContent = + (modifiedRequestData['response'] as Map)['content']; + } + + if (modifiedRequestData['request'] != null && + (modifiedRequestData['request'] as Map)['postData'] != + null) { + requestPostData = + (modifiedRequestData['response'] as Map)['content']; + } return DartIOHttpRequestData( HttpProfileRequestRef.parse(modifiedRequestData)!, requestFullDataFromVmService: false, ) - .._responseBody = modifiedRequestData['response'] != null && - modifiedRequestData['response']['content'] != null - ? modifiedRequestData['response']['content']['text'] - : null - .._requestBody = modifiedRequestData['request'] != null && - modifiedRequestData['request']['postData'] != null - ? modifiedRequestData['request']['postData']['text'] - : null; + .._responseBody = + (responseContent as Map)['text'].toString() + .._requestBody = + (requestPostData as Map)['text'].toString(); } static Map _convertHeadersListToMap( @@ -80,17 +94,21 @@ class DartIOHttpRequestData extends NetworkRequest { final transformedHeaders = {}; for (final header in serializedHeaders) { - final key = header[NetworkEventKeys.name.name] as String?; - final value = header[NetworkEventKeys.value.name]; - - if (transformedHeaders.containsKey(key)) { - if (transformedHeaders[key] is List) { - (transformedHeaders[key] as List).add(value); - } else { - transformedHeaders[key ?? ''] = [transformedHeaders[key], value]; + if (header is Map) { + final key = header[NetworkEventKeys.name.name] as String?; + final value = header[NetworkEventKeys.value.name]; + + if (key != null) { + if (transformedHeaders.containsKey(key)) { + if (transformedHeaders[key] is List) { + (transformedHeaders[key] as List).add(value); + } else { + transformedHeaders[key] = [transformedHeaders[key], value]; + } + } else { + transformedHeaders[key] = value; + } } - } else { - transformedHeaders[key ?? ''] = value; } } @@ -101,18 +119,25 @@ class DartIOHttpRequestData extends NetworkRequest { static void _convertHeaders(Map requestData) { // Request Headers if (requestData['request'] != null && - requestData['request']['headers'] != null) { - if (requestData['request']['headers'] is List) { - requestData['request']['headers'] = - _convertHeadersListToMap(requestData['request']['headers']); + (requestData['request'] as Map)['headers'] != null) { + if ((requestData['request'] as Map)['headers'] is List) { + (requestData['request'] as Map)['headers'] = + _convertHeadersListToMap( + ((requestData['request'] as Map)['headers']) + as List, + ); } } // Response Headers if (requestData['response'] != null && - requestData['response']['headers'] != null) { - if (requestData['response']['headers'] is List) { - requestData['response']['headers'] = - _convertHeadersListToMap(requestData['response']['headers']); + (requestData['response'] as Map)['headers'] != null) { + if ((requestData['response'] as Map)['headers'] + is List) { + (requestData['response'] as Map)['headers'] = + _convertHeadersListToMap( + ((requestData['response'] as Map)['headers']) + as List, + ); } } } diff --git a/packages/devtools_app/test/network/har_network_test.dart b/packages/devtools_app/test/network/har_network_test.dart index 35e2b47da33..e616784db1c 100644 --- a/packages/devtools_app/test/network/har_network_test.dart +++ b/packages/devtools_app/test/network/har_network_test.dart @@ -38,8 +38,10 @@ void main() { (entry?['request'] as Map)['url'], 'https://jsonplaceholder.typicode.com/albums/1', ); - expect((entry?['request'] as Map)['httpVersion'], - 'HTTP/1.1'); + expect( + (entry?['request'] as Map)['httpVersion'], + 'HTTP/1.1', + ); expect((entry?['request'] as Map)['cookies'], isEmpty); expect(entry?['cache'], isEmpty); From 0a0b4be264b7fccea6868c9c77377303e70344c5 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Wed, 17 Jul 2024 22:17:34 +0530 Subject: [PATCH 31/35] used isNullOrEmpty --- .../devtools_app/lib/src/screens/network/har_builder.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index 8172c60c491..02ad94b3a49 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -1,9 +1,9 @@ // Copyright 2024 The Chromium Authors. 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:convert'; +import '../../../devtools_app.dart'; import '../../shared/http/http_request_data.dart'; import '../../shared/utils.dart'; import 'constants.dart'; @@ -178,8 +178,8 @@ int _calculateHeadersSize(Map? headers) { } int _calculateBodySize(String? requestBody) { - if (requestBody == null || requestBody.isEmpty) { + if (requestBody.isNullOrEmpty) { return 0; } - return utf8.encode(requestBody).length; + return utf8.encode(requestBody!).length; } From 21f6a5f47e75755426503acd708a2375996a4df2 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Thu, 18 Jul 2024 10:13:29 +0530 Subject: [PATCH 32/35] import fixed --- packages/devtools_app/lib/src/screens/network/har_builder.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index 02ad94b3a49..d2192fe2d2d 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -3,8 +3,8 @@ // found in the LICENSE file. import 'dart:convert'; -import '../../../devtools_app.dart'; import '../../shared/http/http_request_data.dart'; +import '../../shared/primitives/utils.dart'; import '../../shared/utils.dart'; import 'constants.dart'; From 2d12a0dba3b28824509878baf520b01447fb7861 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Thu, 18 Jul 2024 10:30:02 +0530 Subject: [PATCH 33/35] created new class HarDataEntry for code separation --- .../src/screens/network/har_network_data.dart | 163 +++++++++++++++++- .../src/shared/http/http_request_data.dart | 124 +------------ 2 files changed, 161 insertions(+), 126 deletions(-) diff --git a/packages/devtools_app/lib/src/screens/network/har_network_data.dart b/packages/devtools_app/lib/src/screens/network/har_network_data.dart index 97dc06b0c23..8dec7855512 100644 --- a/packages/devtools_app/lib/src/screens/network/har_network_data.dart +++ b/packages/devtools_app/lib/src/screens/network/har_network_data.dart @@ -4,7 +4,7 @@ import 'package:devtools_shared/devtools_shared.dart'; import '../../shared/http/http_request_data.dart'; - +import 'constants.dart'; import 'har_builder.dart'; /// A class that represents network data in the HTTP Archive (HAR) format. @@ -28,13 +28,14 @@ class HarNetworkData with Serializable { /// final harData = HarNetworkData.fromJson(json); /// ``` factory HarNetworkData.fromJson(Map json) { - final entries = ((json['log'] as Map)['entries'] - as List) - .map( - (entryJson) => - DartIOHttpRequestData.fromJson(entryJson as Map), - ) - .toList(); + final entries = + ((json['log'] as Map)['entries'] as List) + .map( + (entryJson) => + HarDataEntry.fromJson(entryJson as Map) + .toDartIOHttpRequest(), + ) + .toList(); return HarNetworkData(entries); } @@ -55,3 +56,149 @@ class HarNetworkData with Serializable { return buildHar(requests); } } + +class HarDataEntry { + HarDataEntry(this.request); + + /// Creates an instance of [HarDataEntry] from a JSON object. + /// + /// This factory constructor expects the [json] parameter to be a Map + /// representing a single HAR entry. + factory HarDataEntry.fromJson(Map json) { + _convertHeaders(json); + + final modifiedRequestData = + _remapCustomFieldKeys(json) as Map; + + // Retrieving url, method from requestData + modifiedRequestData['uri'] = + (modifiedRequestData['request'] as Map)['url']; + modifiedRequestData['method'] = + (modifiedRequestData['request'] as Map)['method']; + + // Adding missing keys which are mandatory for parsing + (modifiedRequestData['response'] as Map)['redirects'] = []; + Object? requestPostData; + Object? responseContent; + if (modifiedRequestData['response'] != null && + (modifiedRequestData['response'] as Map)['content'] != + null) { + responseContent = + (modifiedRequestData['response'] as Map)['content']; + } + + if (modifiedRequestData['request'] != null && + (modifiedRequestData['request'] as Map)['postData'] != + null) { + requestPostData = + (modifiedRequestData['response'] as Map)['content']; + } + + return HarDataEntry( + DartIOHttpRequestData.fromJson( + modifiedRequestData, + requestPostData as Map, + responseContent as Map, + ), + ); + } + + final DartIOHttpRequestData request; + + /// Converts the instance to a JSON object. + /// + /// This method returns a Map representing a single HAR entry, suitable for + /// serialization. + Map toJson() { + // Implement the logic to convert DartIOHttpRequestData to HAR entry format + return {}; + } + + /// Converts the HAR data entry back to [DartIOHttpRequestData]. + DartIOHttpRequestData toDartIOHttpRequest() { + return request; + } + + static Map _convertHeadersListToMap( + List serializedHeaders, + ) { + final transformedHeaders = {}; + + for (final header in serializedHeaders) { + if (header is Map) { + final key = header[NetworkEventKeys.name.name] as String?; + final value = header[NetworkEventKeys.value.name]; + + if (key != null) { + if (transformedHeaders.containsKey(key)) { + if (transformedHeaders[key] is List) { + (transformedHeaders[key] as List).add(value); + } else { + transformedHeaders[key] = [transformedHeaders[key], value]; + } + } else { + transformedHeaders[key] = value; + } + } + } + } + + return transformedHeaders; + } + + // Convert list of headers to map + static void _convertHeaders(Map requestData) { + // Request Headers + if (requestData['request'] != null && + (requestData['request'] as Map)['headers'] != null) { + if ((requestData['request'] as Map)['headers'] is List) { + (requestData['request'] as Map)['headers'] = + _convertHeadersListToMap( + ((requestData['request'] as Map)['headers']) + as List, + ); + } + } + + // Response Headers + if (requestData['response'] != null && + (requestData['response'] as Map)['headers'] != null) { + if ((requestData['response'] as Map)['headers'] + is List) { + (requestData['response'] as Map)['headers'] = + _convertHeadersListToMap( + ((requestData['response'] as Map)['headers']) + as List, + ); + } + } + } + + // Removing underscores from custom fields + static Map _remapCustomFieldKeys( + Map originalMap, + ) { + final replacementMap = { + NetworkEventCustomFieldKeys.isolateId: + NetworkEventCustomFieldRemappedKeys.isolateId.name, + NetworkEventCustomFieldKeys.id: + NetworkEventCustomFieldRemappedKeys.id.name, + NetworkEventCustomFieldKeys.startTime: + NetworkEventCustomFieldRemappedKeys.startTime.name, + NetworkEventCustomFieldKeys.events: + NetworkEventCustomFieldRemappedKeys.events.name, + }; + + final convertedMap = {}; + + originalMap.forEach((key, value) { + if (replacementMap.containsKey(key)) { + convertedMap[replacementMap[key]!] = value; + } else { + convertedMap[key] = value; + } + }); + + return convertedMap; + } +} 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 cab14b9ba11..65d8adb7aeb 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 @@ -10,7 +10,6 @@ import 'package:logging/logging.dart'; import 'package:mime/mime.dart'; import 'package:vm_service/vm_service.dart'; -import '../../screens/network/constants.dart'; import '../../screens/network/network_model.dart'; import '../globals.dart'; import '../primitives/utils.dart'; @@ -47,127 +46,16 @@ class DartIOHttpRequestData extends NetworkRequest { } } - factory DartIOHttpRequestData.fromJson(Map requestData) { - _convertHeaders(requestData); - - final modifiedRequestData = - _remapCustomFieldKeys(requestData) as Map; - dynamic requestPostData; - dynamic responseContent; - - // Retrieving url, method from requestData - modifiedRequestData['uri'] = - (modifiedRequestData['request'] as Map)['url']; - modifiedRequestData['method'] = - (modifiedRequestData['request'] as Map)['method']; - - // Adding missing keys which are mandatory for parsing - (modifiedRequestData['response'] as Map)['redirects'] = []; - - if (modifiedRequestData['response'] != null && - (modifiedRequestData['response'] as Map)['content'] != - null) { - responseContent = - (modifiedRequestData['response'] as Map)['content']; - } - - if (modifiedRequestData['request'] != null && - (modifiedRequestData['request'] as Map)['postData'] != - null) { - requestPostData = - (modifiedRequestData['response'] as Map)['content']; - } - + factory DartIOHttpRequestData.fromJson( + Map modifiedRequestData, + Map requestPostData, + Map responseContent) { return DartIOHttpRequestData( HttpProfileRequestRef.parse(modifiedRequestData)!, requestFullDataFromVmService: false, ) - .._responseBody = - (responseContent as Map)['text'].toString() - .._requestBody = - (requestPostData as Map)['text'].toString(); - } - - static Map _convertHeadersListToMap( - List serializedHeaders, - ) { - final transformedHeaders = {}; - - for (final header in serializedHeaders) { - if (header is Map) { - final key = header[NetworkEventKeys.name.name] as String?; - final value = header[NetworkEventKeys.value.name]; - - if (key != null) { - if (transformedHeaders.containsKey(key)) { - if (transformedHeaders[key] is List) { - (transformedHeaders[key] as List).add(value); - } else { - transformedHeaders[key] = [transformedHeaders[key], value]; - } - } else { - transformedHeaders[key] = value; - } - } - } - } - - return transformedHeaders; - } - - // Convert list of headers to map - static void _convertHeaders(Map requestData) { - // Request Headers - if (requestData['request'] != null && - (requestData['request'] as Map)['headers'] != null) { - if ((requestData['request'] as Map)['headers'] is List) { - (requestData['request'] as Map)['headers'] = - _convertHeadersListToMap( - ((requestData['request'] as Map)['headers']) - as List, - ); - } - } - // Response Headers - if (requestData['response'] != null && - (requestData['response'] as Map)['headers'] != null) { - if ((requestData['response'] as Map)['headers'] - is List) { - (requestData['response'] as Map)['headers'] = - _convertHeadersListToMap( - ((requestData['response'] as Map)['headers']) - as List, - ); - } - } - } - - // Removing underscores from custom fields - static Map _remapCustomFieldKeys( - Map originalMap, - ) { - final replacementMap = { - NetworkEventCustomFieldKeys.isolateId: - NetworkEventCustomFieldRemappedKeys.isolateId.name, - NetworkEventCustomFieldKeys.id: - NetworkEventCustomFieldRemappedKeys.id.name, - NetworkEventCustomFieldKeys.startTime: - NetworkEventCustomFieldRemappedKeys.startTime.name, - NetworkEventCustomFieldKeys.events: - NetworkEventCustomFieldRemappedKeys.events.name, - }; - - final convertedMap = {}; - - originalMap.forEach((key, value) { - if (replacementMap.containsKey(key)) { - convertedMap[replacementMap[key]!] = value; - } else { - convertedMap[key] = value; - } - }); - - return convertedMap; + .._responseBody = responseContent['text'].toString() + .._requestBody = requestPostData['text'].toString(); } static const _connectionInfoKey = 'connectionInfo'; From 2c3253926ab01921d1ee29eec6413a4a08ce6be6 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Thu, 18 Jul 2024 16:06:36 +0530 Subject: [PATCH 34/35] used HarDataEntry for har generation --- .../lib/src/screens/network/har_builder.dart | 147 +-------- .../src/screens/network/har_data_entry.dart | 295 ++++++++++++++++++ .../src/screens/network/har_network_data.dart | 149 +-------- .../src/shared/http/http_request_data.dart | 7 +- 4 files changed, 303 insertions(+), 295 deletions(-) create mode 100644 packages/devtools_app/lib/src/screens/network/har_data_entry.dart diff --git a/packages/devtools_app/lib/src/screens/network/har_builder.dart b/packages/devtools_app/lib/src/screens/network/har_builder.dart index d2192fe2d2d..2b06386f0ec 100644 --- a/packages/devtools_app/lib/src/screens/network/har_builder.dart +++ b/packages/devtools_app/lib/src/screens/network/har_builder.dart @@ -1,12 +1,11 @@ // Copyright 2024 The Chromium Authors. 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:convert'; import '../../shared/http/http_request_data.dart'; -import '../../shared/primitives/utils.dart'; import '../../shared/utils.dart'; import 'constants.dart'; +import 'har_data_entry.dart'; /// Builds a HAR (HTTP Archive) object from a list of HTTP requests. /// @@ -30,121 +29,7 @@ Map buildHar(List httpRequests) { }; // Build the entries - final entries = httpRequests.map((e) { - final requestCookies = e.requestCookies.map((cookie) { - return { - NetworkEventKeys.name.name: cookie.name, - NetworkEventKeys.value.name: cookie.value, - 'path': cookie.path, - 'domain': cookie.domain, - 'expires': cookie.expires?.toUtc().toIso8601String(), - 'httpOnly': cookie.httpOnly, - 'secure': cookie.secure, - }; - }).toList(); - - final requestHeaders = e.requestHeaders?.entries.map((header) { - var value = header.value; - if (value is List) { - value = value.first; - } - return { - NetworkEventKeys.name.name: header.key, - NetworkEventKeys.value.name: value, - }; - }).toList(); - - final queryString = Uri.parse(e.uri).queryParameters.entries.map((param) { - return { - NetworkEventKeys.name.name: param.key, - NetworkEventKeys.value.name: param.value, - }; - }).toList(); - - final responseCookies = e.responseCookies.map((cookie) { - return { - NetworkEventKeys.name.name: cookie.name, - NetworkEventKeys.value.name: cookie.value, - 'path': cookie.path, - 'domain': cookie.domain, - 'expires': cookie.expires?.toUtc().toIso8601String(), - 'httpOnly': cookie.httpOnly, - 'secure': cookie.secure, - }; - }).toList(); - - final responseHeaders = e.responseHeaders?.entries.map((header) { - var value = header.value; - if (value is List) { - value = value.first; - } - return { - NetworkEventKeys.name.name: header.key, - NetworkEventKeys.value.name: value, - }; - }).toList(); - - return { - NetworkEventKeys.startedDateTime.name: - e.startTimestamp.toUtc().toIso8601String(), - NetworkEventKeys.time.name: e.duration?.inMilliseconds, - // Request - NetworkEventKeys.request.name: { - NetworkEventKeys.method.name: e.method.toUpperCase(), - NetworkEventKeys.url.name: e.uri.toString(), - NetworkEventKeys.httpVersion.name: NetworkEventDefaults.httpVersion, - NetworkEventKeys.cookies.name: requestCookies, - NetworkEventKeys.headers.name: requestHeaders, - NetworkEventKeys.queryString.name: queryString, - NetworkEventKeys.postData.name: { - NetworkEventKeys.mimeType.name: e.contentType, - NetworkEventKeys.text.name: e.requestBody, - }, - NetworkEventKeys.headersSize.name: - _calculateHeadersSize(e.requestHeaders), - NetworkEventKeys.bodySize.name: _calculateBodySize(e.requestBody), - }, - // Response - NetworkEventKeys.response.name: { - NetworkEventKeys.status.name: e.status, - NetworkEventKeys.statusText.name: '', - NetworkEventKeys.httpVersion.name: - NetworkEventDefaults.responseHttpVersion, - NetworkEventKeys.cookies.name: responseCookies, - NetworkEventKeys.headers.name: responseHeaders, - NetworkEventKeys.content.name: { - NetworkEventKeys.size.name: e.responseBody?.length, - NetworkEventKeys.mimeType.name: e.type, - NetworkEventKeys.text.name: e.responseBody, - }, - NetworkEventKeys.redirectURL.name: '', - NetworkEventKeys.headersSize.name: - _calculateHeadersSize(e.responseHeaders), - NetworkEventKeys.bodySize.name: _calculateBodySize(e.responseBody), - }, - // Cache - NetworkEventKeys.cache.name: {}, - NetworkEventKeys.timings.name: { - NetworkEventKeys.blocked.name: NetworkEventDefaults.blocked, - NetworkEventKeys.dns.name: NetworkEventDefaults.dns, - NetworkEventKeys.connect.name: NetworkEventDefaults.connect, - NetworkEventKeys.send.name: NetworkEventDefaults.send, - NetworkEventKeys.wait.name: e.duration?.inMilliseconds ?? 0, - NetworkEventKeys.receive.name: NetworkEventDefaults.receive, - NetworkEventKeys.ssl.name: NetworkEventDefaults.ssl, - }, - NetworkEventKeys.connection.name: e.hashCode.toString(), - NetworkEventKeys.comment.name: '', - - // Custom fields - // har spec requires underscore to be added for custom fields, hence removing them - NetworkEventCustomFieldKeys.isolateId: '', - NetworkEventCustomFieldKeys.id: e.id, - NetworkEventCustomFieldKeys.startTime: - e.startTimestamp.microsecondsSinceEpoch, - NetworkEventCustomFieldKeys.events: [], - }; - }).toList(); + final entries = httpRequests.map((e) => HarDataEntry.toJson(e)).toList(); // Assemble the final HAR object return { @@ -155,31 +40,3 @@ Map buildHar(List httpRequests) { }, }; } - -int _calculateHeadersSize(Map? headers) { - if (headers == null) return -1; - - // Combine headers into a single string with CRLF endings - String headersString = headers.entries.map((entry) { - final key = entry.key; - var value = entry.value; - // If the value is a List, join it with a comma - if (value is List) { - value = value.join(', '); - } - return '$key: $value\r\n'; - }).join(); - - // Add final CRLF to indicate end of headers - headersString += '\r\n'; - - // Calculate the byte length of the headers string - return utf8.encode(headersString).length; -} - -int _calculateBodySize(String? requestBody) { - if (requestBody.isNullOrEmpty) { - return 0; - } - return utf8.encode(requestBody!).length; -} 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 new file mode 100644 index 00000000000..351443dcc2a --- /dev/null +++ b/packages/devtools_app/lib/src/screens/network/har_data_entry.dart @@ -0,0 +1,295 @@ +// Copyright 2024 The Chromium Authors. 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:convert'; + +import '../../shared/http/http_request_data.dart'; +import '../../shared/primitives/utils.dart'; +import 'constants.dart'; + +class HarDataEntry { + HarDataEntry(this.request); + + /// Creates an instance of [HarDataEntry] from a JSON object. + /// + /// This factory constructor expects the [json] parameter to be a Map + /// representing a single HAR entry. + factory HarDataEntry.fromJson(Map json) { + _convertHeaders(json); + + final modifiedRequestData = + _remapCustomFieldKeys(json) as Map; + + // Retrieving url, method from requestData + modifiedRequestData['uri'] = + (modifiedRequestData['request'] as Map)['url']; + modifiedRequestData['method'] = + (modifiedRequestData['request'] as Map)['method']; + + // Adding missing keys which are mandatory for parsing + (modifiedRequestData['response'] as Map)['redirects'] = []; + Object? requestPostData; + Object? responseContent; + if (modifiedRequestData['response'] != null && + (modifiedRequestData['response'] as Map)['content'] != + null) { + responseContent = + (modifiedRequestData['response'] as Map)['content']; + } + + if (modifiedRequestData['request'] != null && + (modifiedRequestData['request'] as Map)['postData'] != + null) { + requestPostData = + (modifiedRequestData['response'] as Map)['content']; + } + + return HarDataEntry( + DartIOHttpRequestData.fromJson( + modifiedRequestData, + requestPostData as Map, + responseContent as Map, + ), + ); + } + + final DartIOHttpRequestData request; + + /// Converts the instance to a JSON object. + /// + /// This method returns a Map representing a single HAR entry, suitable for + /// serialization. + static Map toJson(DartIOHttpRequestData e) { + // Implement the logic to convert DartIOHttpRequestData to HAR entry format + final requestCookies = e.requestCookies.map((cookie) { + return { + NetworkEventKeys.name.name: cookie.name, + NetworkEventKeys.value.name: cookie.value, + 'path': cookie.path, + 'domain': cookie.domain, + 'expires': cookie.expires?.toUtc().toIso8601String(), + 'httpOnly': cookie.httpOnly, + 'secure': cookie.secure, + }; + }).toList(); + + final requestHeaders = e.requestHeaders?.entries.map((header) { + var value = header.value; + if (value is List) { + value = value.first; + } + return { + NetworkEventKeys.name.name: header.key, + NetworkEventKeys.value.name: value, + }; + }).toList(); + + final queryString = Uri.parse(e.uri).queryParameters.entries.map((param) { + return { + NetworkEventKeys.name.name: param.key, + NetworkEventKeys.value.name: param.value, + }; + }).toList(); + + final responseCookies = e.responseCookies.map((cookie) { + return { + NetworkEventKeys.name.name: cookie.name, + NetworkEventKeys.value.name: cookie.value, + 'path': cookie.path, + 'domain': cookie.domain, + 'expires': cookie.expires?.toUtc().toIso8601String(), + 'httpOnly': cookie.httpOnly, + 'secure': cookie.secure, + }; + }).toList(); + + final responseHeaders = e.responseHeaders?.entries.map((header) { + var value = header.value; + if (value is List) { + value = value.first; + } + return { + NetworkEventKeys.name.name: header.key, + NetworkEventKeys.value.name: value, + }; + }).toList(); + + return { + NetworkEventKeys.startedDateTime.name: + e.startTimestamp.toUtc().toIso8601String(), + NetworkEventKeys.time.name: e.duration?.inMilliseconds, + // Request + NetworkEventKeys.request.name: { + NetworkEventKeys.method.name: e.method.toUpperCase(), + NetworkEventKeys.url.name: e.uri.toString(), + NetworkEventKeys.httpVersion.name: NetworkEventDefaults.httpVersion, + NetworkEventKeys.cookies.name: requestCookies, + NetworkEventKeys.headers.name: requestHeaders, + NetworkEventKeys.queryString.name: queryString, + NetworkEventKeys.postData.name: { + NetworkEventKeys.mimeType.name: e.contentType, + NetworkEventKeys.text.name: e.requestBody, + }, + NetworkEventKeys.headersSize.name: + _calculateHeadersSize(e.requestHeaders), + NetworkEventKeys.bodySize.name: _calculateBodySize(e.requestBody), + }, + // Response + NetworkEventKeys.response.name: { + NetworkEventKeys.status.name: e.status, + NetworkEventKeys.statusText.name: '', + NetworkEventKeys.httpVersion.name: + NetworkEventDefaults.responseHttpVersion, + NetworkEventKeys.cookies.name: responseCookies, + NetworkEventKeys.headers.name: responseHeaders, + NetworkEventKeys.content.name: { + NetworkEventKeys.size.name: e.responseBody?.length, + NetworkEventKeys.mimeType.name: e.type, + NetworkEventKeys.text.name: e.responseBody, + }, + NetworkEventKeys.redirectURL.name: '', + NetworkEventKeys.headersSize.name: + _calculateHeadersSize(e.responseHeaders), + NetworkEventKeys.bodySize.name: _calculateBodySize(e.responseBody), + }, + // Cache + NetworkEventKeys.cache.name: {}, + NetworkEventKeys.timings.name: { + NetworkEventKeys.blocked.name: NetworkEventDefaults.blocked, + NetworkEventKeys.dns.name: NetworkEventDefaults.dns, + NetworkEventKeys.connect.name: NetworkEventDefaults.connect, + NetworkEventKeys.send.name: NetworkEventDefaults.send, + NetworkEventKeys.wait.name: e.duration?.inMilliseconds ?? 0, + NetworkEventKeys.receive.name: NetworkEventDefaults.receive, + NetworkEventKeys.ssl.name: NetworkEventDefaults.ssl, + }, + NetworkEventKeys.connection.name: e.hashCode.toString(), + NetworkEventKeys.comment.name: '', + + // Custom fields + // har spec requires underscore to be added for custom fields, hence removing them + NetworkEventCustomFieldKeys.isolateId: '', + NetworkEventCustomFieldKeys.id: e.id, + NetworkEventCustomFieldKeys.startTime: + e.startTimestamp.microsecondsSinceEpoch, + NetworkEventCustomFieldKeys.events: [], + }; + } + + /// Converts the HAR data entry back to [DartIOHttpRequestData]. + DartIOHttpRequestData toDartIOHttpRequest() { + return request; + } + + static Map _convertHeadersListToMap( + List serializedHeaders, + ) { + final transformedHeaders = {}; + + for (final header in serializedHeaders) { + if (header is Map) { + final key = header[NetworkEventKeys.name.name] as String?; + final value = header[NetworkEventKeys.value.name]; + + if (key != null) { + if (transformedHeaders.containsKey(key)) { + if (transformedHeaders[key] is List) { + (transformedHeaders[key] as List).add(value); + } else { + transformedHeaders[key] = [transformedHeaders[key], value]; + } + } else { + transformedHeaders[key] = value; + } + } + } + } + + return transformedHeaders; + } + + // Convert list of headers to map + static void _convertHeaders(Map requestData) { + // Request Headers + if (requestData['request'] != null && + (requestData['request'] as Map)['headers'] != null) { + if ((requestData['request'] as Map)['headers'] is List) { + (requestData['request'] as Map)['headers'] = + _convertHeadersListToMap( + ((requestData['request'] as Map)['headers']) + as List, + ); + } + } + + // Response Headers + if (requestData['response'] != null && + (requestData['response'] as Map)['headers'] != null) { + if ((requestData['response'] as Map)['headers'] + is List) { + (requestData['response'] as Map)['headers'] = + _convertHeadersListToMap( + ((requestData['response'] as Map)['headers']) + as List, + ); + } + } + } + + // Removing underscores from custom fields + static Map _remapCustomFieldKeys( + Map originalMap, + ) { + final replacementMap = { + NetworkEventCustomFieldKeys.isolateId: + NetworkEventCustomFieldRemappedKeys.isolateId.name, + NetworkEventCustomFieldKeys.id: + NetworkEventCustomFieldRemappedKeys.id.name, + NetworkEventCustomFieldKeys.startTime: + NetworkEventCustomFieldRemappedKeys.startTime.name, + NetworkEventCustomFieldKeys.events: + NetworkEventCustomFieldRemappedKeys.events.name, + }; + + final convertedMap = {}; + + originalMap.forEach((key, value) { + if (replacementMap.containsKey(key)) { + convertedMap[replacementMap[key]!] = value; + } else { + convertedMap[key] = value; + } + }); + + return convertedMap; + } +} + +int _calculateHeadersSize(Map? headers) { + if (headers == null) return -1; + + // Combine headers into a single string with CRLF endings + String headersString = headers.entries.map((entry) { + final key = entry.key; + var value = entry.value; + // If the value is a List, join it with a comma + if (value is List) { + value = value.join(', '); + } + return '$key: $value\r\n'; + }).join(); + + // Add final CRLF to indicate end of headers + headersString += '\r\n'; + + // Calculate the byte length of the headers string + return utf8.encode(headersString).length; +} + +int _calculateBodySize(String? requestBody) { + if (requestBody.isNullOrEmpty) { + return 0; + } + return utf8.encode(requestBody!).length; +} diff --git a/packages/devtools_app/lib/src/screens/network/har_network_data.dart b/packages/devtools_app/lib/src/screens/network/har_network_data.dart index 8dec7855512..579cf8de939 100644 --- a/packages/devtools_app/lib/src/screens/network/har_network_data.dart +++ b/packages/devtools_app/lib/src/screens/network/har_network_data.dart @@ -3,9 +3,10 @@ // found in the LICENSE file. import 'package:devtools_shared/devtools_shared.dart'; + import '../../shared/http/http_request_data.dart'; -import 'constants.dart'; import 'har_builder.dart'; +import 'har_data_entry.dart'; /// A class that represents network data in the HTTP Archive (HAR) format. /// @@ -56,149 +57,3 @@ class HarNetworkData with Serializable { return buildHar(requests); } } - -class HarDataEntry { - HarDataEntry(this.request); - - /// Creates an instance of [HarDataEntry] from a JSON object. - /// - /// This factory constructor expects the [json] parameter to be a Map - /// representing a single HAR entry. - factory HarDataEntry.fromJson(Map json) { - _convertHeaders(json); - - final modifiedRequestData = - _remapCustomFieldKeys(json) as Map; - - // Retrieving url, method from requestData - modifiedRequestData['uri'] = - (modifiedRequestData['request'] as Map)['url']; - modifiedRequestData['method'] = - (modifiedRequestData['request'] as Map)['method']; - - // Adding missing keys which are mandatory for parsing - (modifiedRequestData['response'] as Map)['redirects'] = []; - Object? requestPostData; - Object? responseContent; - if (modifiedRequestData['response'] != null && - (modifiedRequestData['response'] as Map)['content'] != - null) { - responseContent = - (modifiedRequestData['response'] as Map)['content']; - } - - if (modifiedRequestData['request'] != null && - (modifiedRequestData['request'] as Map)['postData'] != - null) { - requestPostData = - (modifiedRequestData['response'] as Map)['content']; - } - - return HarDataEntry( - DartIOHttpRequestData.fromJson( - modifiedRequestData, - requestPostData as Map, - responseContent as Map, - ), - ); - } - - final DartIOHttpRequestData request; - - /// Converts the instance to a JSON object. - /// - /// This method returns a Map representing a single HAR entry, suitable for - /// serialization. - Map toJson() { - // Implement the logic to convert DartIOHttpRequestData to HAR entry format - return {}; - } - - /// Converts the HAR data entry back to [DartIOHttpRequestData]. - DartIOHttpRequestData toDartIOHttpRequest() { - return request; - } - - static Map _convertHeadersListToMap( - List serializedHeaders, - ) { - final transformedHeaders = {}; - - for (final header in serializedHeaders) { - if (header is Map) { - final key = header[NetworkEventKeys.name.name] as String?; - final value = header[NetworkEventKeys.value.name]; - - if (key != null) { - if (transformedHeaders.containsKey(key)) { - if (transformedHeaders[key] is List) { - (transformedHeaders[key] as List).add(value); - } else { - transformedHeaders[key] = [transformedHeaders[key], value]; - } - } else { - transformedHeaders[key] = value; - } - } - } - } - - return transformedHeaders; - } - - // Convert list of headers to map - static void _convertHeaders(Map requestData) { - // Request Headers - if (requestData['request'] != null && - (requestData['request'] as Map)['headers'] != null) { - if ((requestData['request'] as Map)['headers'] is List) { - (requestData['request'] as Map)['headers'] = - _convertHeadersListToMap( - ((requestData['request'] as Map)['headers']) - as List, - ); - } - } - - // Response Headers - if (requestData['response'] != null && - (requestData['response'] as Map)['headers'] != null) { - if ((requestData['response'] as Map)['headers'] - is List) { - (requestData['response'] as Map)['headers'] = - _convertHeadersListToMap( - ((requestData['response'] as Map)['headers']) - as List, - ); - } - } - } - - // Removing underscores from custom fields - static Map _remapCustomFieldKeys( - Map originalMap, - ) { - final replacementMap = { - NetworkEventCustomFieldKeys.isolateId: - NetworkEventCustomFieldRemappedKeys.isolateId.name, - NetworkEventCustomFieldKeys.id: - NetworkEventCustomFieldRemappedKeys.id.name, - NetworkEventCustomFieldKeys.startTime: - NetworkEventCustomFieldRemappedKeys.startTime.name, - NetworkEventCustomFieldKeys.events: - NetworkEventCustomFieldRemappedKeys.events.name, - }; - - final convertedMap = {}; - - originalMap.forEach((key, value) { - if (replacementMap.containsKey(key)) { - convertedMap[replacementMap[key]!] = value; - } else { - convertedMap[key] = value; - } - }); - - return convertedMap; - } -} 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 65d8adb7aeb..38afd862874 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 @@ -47,9 +47,10 @@ class DartIOHttpRequestData extends NetworkRequest { } factory DartIOHttpRequestData.fromJson( - Map modifiedRequestData, - Map requestPostData, - Map responseContent) { + Map modifiedRequestData, + Map requestPostData, + Map responseContent, + ) { return DartIOHttpRequestData( HttpProfileRequestRef.parse(modifiedRequestData)!, requestFullDataFromVmService: false, From b60b074465f2c2d8d1a637d8c42eb6c3e4350e22 Mon Sep 17 00:00:00 2001 From: hrajwade96 Date: Fri, 19 Jul 2024 15:47:54 +0530 Subject: [PATCH 35/35] changed dynamic to Object? --- .../src/screens/network/har_data_entry.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 351443dcc2a..5b587972fd8 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 @@ -19,7 +19,7 @@ class HarDataEntry { _convertHeaders(json); final modifiedRequestData = - _remapCustomFieldKeys(json) as Map; + _remapCustomFieldKeys(json); // Retrieving url, method from requestData modifiedRequestData['uri'] = @@ -182,13 +182,13 @@ class HarDataEntry { return request; } - static Map _convertHeadersListToMap( - List serializedHeaders, + static Map _convertHeadersListToMap( + List serializedHeaders, ) { - final transformedHeaders = {}; + final transformedHeaders = {}; for (final header in serializedHeaders) { - if (header is Map) { + if (header is Map) { final key = header[NetworkEventKeys.name.name] as String?; final value = header[NetworkEventKeys.value.name]; @@ -210,7 +210,7 @@ class HarDataEntry { } // Convert list of headers to map - static void _convertHeaders(Map requestData) { + static void _convertHeaders(Map requestData) { // Request Headers if (requestData['request'] != null && (requestData['request'] as Map)['headers'] != null) { @@ -218,7 +218,7 @@ class HarDataEntry { (requestData['request'] as Map)['headers'] = _convertHeadersListToMap( ((requestData['request'] as Map)['headers']) - as List, + as List, ); } } @@ -231,7 +231,7 @@ class HarDataEntry { (requestData['response'] as Map)['headers'] = _convertHeadersListToMap( ((requestData['response'] as Map)['headers']) - as List, + as List, ); } } @@ -252,7 +252,7 @@ class HarDataEntry { NetworkEventCustomFieldRemappedKeys.events.name, }; - final convertedMap = {}; + final convertedMap = {}; originalMap.forEach((key, value) { if (replacementMap.containsKey(key)) { @@ -266,7 +266,7 @@ class HarDataEntry { } } -int _calculateHeadersSize(Map? headers) { +int _calculateHeadersSize(Map? headers) { if (headers == null) return -1; // Combine headers into a single string with CRLF endings