Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for adding w3c header #21

Merged
merged 19 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ jobs:
- checkout
- run: flutter doctor
- run: flutter pub get
- run: flutter pub run build_runner build --delete-conflicting-outputs
- run: flutter test
- run: dart analyze --fatal-warnings lib
- run: flutter pub publish --dry-run
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [2.5.0] - 18/11/2024

### Added

- Add support for tracing network requests from Instabug to services like Datadog and New Relic ([#21](https://github.com/Instabug/Instabug-Dart-http-Adapter/pull/21)).

## [2.4.0] - 7/05/2024

### Added
Expand Down
4 changes: 3 additions & 1 deletion example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
<application
android:label="example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"
Expand Down
13 changes: 13 additions & 0 deletions example/android/app/src/main/res/xml/network_security_config.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
<domain includeSubdomains="true">api.instabug.com</domain>
</domain-config>
<base-config>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
74 changes: 54 additions & 20 deletions lib/instabug_http_client.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
// ignore_for_file: invalid_use_of_internal_member

library instabug_http_client;

import 'dart:convert';

// to maintain supported versions prior to Flutter 3.3
// ignore: unnecessary_import
import 'dart:typed_data';

import 'package:http/http.dart' as http;
import 'package:instabug_flutter/instabug_flutter.dart';
import 'package:instabug_http_client/instabug_http_logger.dart';
import 'package:meta/meta.dart';

class InstabugHttpClient extends InstabugHttpLogger implements http.Client {
InstabugHttpClient() : client = http.Client() {
logger = this;
}

final NetworkLogger _networklogger = NetworkLogger();
@visibleForTesting
http.Client client;

Expand All @@ -24,66 +30,92 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client {

@override
Future<http.Response> delete(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) {
{Map<String, String>? headers, Object? body, Encoding? encoding}) async {
final DateTime startTime = DateTime.now();
final Map<String, String> requestHeader = headers ?? <String, String>{};
final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime);
return client
.delete(url, body: body, headers: headers, encoding: encoding)
.delete(url, body: body, headers: requestHeader, encoding: encoding)
.then((http.Response response) {
logger.onLogger(response, startTime: startTime);
logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader);
return response;
});
}

Future<W3CHeader?> getW3cHeader(Map<String, String> requestHeader, DateTime startTime) async {
final W3CHeader? w3cHeader = await _networklogger.getW3CHeader(
requestHeader, startTime.millisecondsSinceEpoch);
if (w3cHeader?.isW3cHeaderFound == false &&
w3cHeader?.w3CGeneratedHeader != null) {
requestHeader['traceparent'] = w3cHeader!.w3CGeneratedHeader!;
}
return w3cHeader;
}

@override
Future<http.Response> get(Uri url, {Map<String, String>? headers}) {
Future<http.Response> get(Uri url, {Map<String, String>? headers}) async {
final DateTime startTime = DateTime.now();
return client.get(url, headers: headers).then((http.Response response) {
logger.onLogger(response, startTime: startTime);
final Map<String, String> requestHeader = headers ?? <String, String>{};
final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime);
return client
.get(url, headers: requestHeader)
.then((http.Response response) {
logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader);
return response;
});
}

@override
Future<http.Response> head(Uri url, {Map<String, String>? headers}) {
Future<http.Response> head(Uri url, {Map<String, String>? headers}) async {
final DateTime startTime = DateTime.now();
return client.head(url, headers: headers).then((http.Response response) {
logger.onLogger(response, startTime: startTime);
final Map<String, String> requestHeader = headers ?? <String, String>{};
final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime);
return client
.head(url, headers: requestHeader)
.then((http.Response response) {
logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader);
return response;
});
}

@override
Future<http.Response> patch(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) {
{Map<String, String>? headers, Object? body, Encoding? encoding}) async {
final DateTime startTime = DateTime.now();
final Map<String, String> requestHeader = headers ?? <String, String>{};
final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime);
return client
.patch(url, headers: headers, body: body, encoding: encoding)
.patch(url, headers: requestHeader, body: body, encoding: encoding)
.then((http.Response response) {
logger.onLogger(response, startTime: startTime);
logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader);
return response;
});
}

@override
Future<http.Response> post(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) {
{Map<String, String>? headers, Object? body, Encoding? encoding}) async {
final DateTime startTime = DateTime.now();
final Map<String, String> requestHeader = headers ?? <String, String>{};
final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime);
return client
.post(url, headers: headers, body: body, encoding: encoding)
.post(url, headers: requestHeader, body: body, encoding: encoding)
.then((http.Response response) {
logger.onLogger(response, startTime: startTime);
logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader);
return response;
});
}

@override
Future<http.Response> put(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding}) {
{Map<String, String>? headers, Object? body, Encoding? encoding}) async {
final DateTime startTime = DateTime.now();
final Map<String, String> requestHeader = headers ?? <String, String>{};
final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime);
return client
.put(url, headers: headers, body: body, encoding: encoding)
.put(url, headers: requestHeader, body: body, encoding: encoding)
.then((http.Response response) {
logger.onLogger(response, startTime: startTime);
logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader);
return response;
});
}
Expand All @@ -97,12 +129,14 @@ class InstabugHttpClient extends InstabugHttpLogger implements http.Client {
client.readBytes(url, headers: headers);

@override
Future<http.StreamedResponse> send(http.BaseRequest request) {
Future<http.StreamedResponse> send(http.BaseRequest request) async {
final DateTime startTime = DateTime.now();
final Map<String, String> requestHeader = request.headers;
final W3CHeader? w3cHeader = await getW3cHeader(requestHeader, startTime);
return client.send(request).then((http.StreamedResponse streamedResponse) =>
http.Response.fromStream(streamedResponse)
.then((http.Response response) {
logger.onLogger(response, startTime: startTime);
logger.onLogger(response, startTime: startTime, w3CHeader: w3cHeader);
// Need to return new StreamedResponse, as body only can be listened once
return http.StreamedResponse(
Stream<List<int>>.value(response.bodyBytes),
Expand Down
3 changes: 2 additions & 1 deletion lib/instabug_http_logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'package:http/http.dart' as http;
import 'package:instabug_flutter/instabug_flutter.dart';

class InstabugHttpLogger {
void onLogger(http.Response response, {DateTime? startTime}) {
void onLogger(http.Response response, {DateTime? startTime,W3CHeader? w3CHeader}) {
final NetworkLogger networkLogger = NetworkLogger();

final Map<String, dynamic> requestHeaders = <String, dynamic>{};
Expand All @@ -29,6 +29,7 @@ class InstabugHttpLogger {
url: request.url.toString(),
requestHeaders: requestHeaders,
requestBody: requestBody,
w3cHeader: w3CHeader
);

final DateTime endTime = DateTime.now();
Expand Down
6 changes: 3 additions & 3 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: instabug_http_client
description:
This package is an add on to instabug_flutter. It intercepts any requests performed
with http Package and sends them to the report that will be sent to the dashboard.
version: 2.4.0
version: 2.5.0
homepage: https://github.com/Instabug/Instabug-Flutter#readme

environment:
Expand All @@ -12,14 +12,14 @@ dependencies:
flutter:
sdk: flutter
http: ^0.13.3
instabug_flutter: '>=11.0.0 <14.0.0'
instabug_flutter: '>=14.0.0 <15.0.0'
meta: ^1.3.0

dev_dependencies:
build_runner: ^2.1.1
flutter_test:
sdk: flutter
mockito: ^5.0.10
mockito: '>=5.2.0 <5.5.0'
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec

Expand Down
32 changes: 24 additions & 8 deletions test/instabug_http_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:instabug_flutter/instabug_flutter.dart';
import 'package:instabug_flutter/src/generated/instabug.api.g.dart';
import 'package:instabug_http_client/instabug_http_client.dart';
import 'package:instabug_http_client/instabug_http_logger.dart';
import 'package:mockito/annotations.dart';
Expand All @@ -17,8 +19,22 @@ import 'instabug_http_client_test.mocks.dart';
@GenerateMocks(<Type>[
InstabugHttpLogger,
InstabugHttpClient,
InstabugHostApi,
])
Future<void> main() async {
TestWidgetsFlutterBinding.ensureInitialized();
final MockInstabugHostApi mHost = MockInstabugHostApi();

setUpAll(() {
Instabug.$setHostApi(mHost);
NetworkLogger.$setHostApi(mHost);
when(mHost.isW3CFeatureFlagsEnabled()).thenAnswer((_)=> Future<Map<String,bool>>.value(<String, bool>{
'isW3cCaughtHeaderEnabled': true,
'isW3cExternalGeneratedHeaderEnabled': false,
'isW3cExternalTraceIDEnabled': true,
}));
});

const Map<String, String> fakeResponse = <String, String>{
'id': '123',
'activationCode': '111111',
Expand All @@ -37,7 +53,7 @@ Future<void> main() async {
});

test('expect instabug http client GET to return response', () async {
when<dynamic>(instabugHttpClient.client.get(url))
when<dynamic>(instabugHttpClient.client.get(url,headers: anyNamed('headers')))
.thenAnswer((_) async => mockedResponse);
final http.Response result = await instabugHttpClient.get(url);
expect(result, isInstanceOf<http.Response>());
Expand All @@ -48,7 +64,7 @@ Future<void> main() async {
});

test('expect instabug http client HEAD to return response', () async {
when<dynamic>(instabugHttpClient.client.head(url))
when<dynamic>(instabugHttpClient.client.head(url,headers: anyNamed('headers')))
.thenAnswer((_) async => mockedResponse);
final http.Response result = await instabugHttpClient.head(url);
expect(result, isInstanceOf<http.Response>());
Expand All @@ -59,7 +75,7 @@ Future<void> main() async {
});

test('expect instabug http client DELETE to return response', () async {
when<dynamic>(instabugHttpClient.client.delete(url))
when<dynamic>(instabugHttpClient.client.delete(url,headers: anyNamed('headers')))
.thenAnswer((_) async => mockedResponse);
final http.Response result = await instabugHttpClient.delete(url);
expect(result, isInstanceOf<http.Response>());
Expand All @@ -70,7 +86,7 @@ Future<void> main() async {
});

test('expect instabug http client PATCH to return response', () async {
when<dynamic>(instabugHttpClient.client.patch(url))
when<dynamic>(instabugHttpClient.client.patch(url,headers: anyNamed('headers')))
.thenAnswer((_) async => mockedResponse);
final http.Response result = await instabugHttpClient.patch(url);
expect(result, isInstanceOf<http.Response>());
Expand All @@ -81,7 +97,7 @@ Future<void> main() async {
});

test('expect instabug http client POST to return response', () async {
when<dynamic>(instabugHttpClient.client.post(url))
when<dynamic>(instabugHttpClient.client.post(url,headers: anyNamed('headers')))
.thenAnswer((_) async => mockedResponse);
final http.Response result = await instabugHttpClient.post(url);
expect(result, isInstanceOf<http.Response>());
Expand All @@ -92,7 +108,7 @@ Future<void> main() async {
});

test('expect instabug http client PUT to return response', () async {
when<dynamic>(instabugHttpClient.client.put(url))
when<dynamic>(instabugHttpClient.client.put(url,headers: anyNamed('headers')))
.thenAnswer((_) async => mockedResponse);
final http.Response result = await instabugHttpClient.put(url);
expect(result, isInstanceOf<http.Response>());
Expand All @@ -104,7 +120,7 @@ Future<void> main() async {

test('expect instabug http client READ to return response', () async {
const String response = 'Some response string';
when<dynamic>(instabugHttpClient.client.read(url))
when<dynamic>(instabugHttpClient.client.read(url,headers: anyNamed('headers')))
.thenAnswer((_) async => response);

final String result = await instabugHttpClient.read(url);
Expand Down Expand Up @@ -161,7 +177,7 @@ Future<void> main() async {
});

test('stress test for GET method', () async {
when<dynamic>(instabugHttpClient.client.get(url))
when<dynamic>(instabugHttpClient.client.get(url,headers: anyNamed('headers')))
.thenAnswer((_) async => mockedResponse);
for (int i = 0; i < 10000; i++) {
await instabugHttpClient.get(url);
Expand Down
Loading