diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 2d8e79605..b76b60c9f 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -19,6 +19,7 @@ body: description: Which package has a problem? options: - dio + - compatibility_layer - cookie_manager - http2_adapter - native_dio_adapter diff --git a/.idea/modules.xml b/.idea/modules.xml index a4f1661d8..67aa758b3 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,6 +3,7 @@ + diff --git a/README-ZH.md b/README-ZH.md index b8ad3c394..1a237f1c6 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -22,6 +22,8 @@ Language: [English](README.md) | 简体中文 - cookie_manager: [链接](plugins/cookie_manager) [![Pub](https://img.shields.io/pub/v/dio_cookie_manager.svg?label=dev&include_prereleases)](https://pub.flutter-io.cn/packages/dio_cookie_manager) +- compatibility_layer: [链接](plugins/compatibility_layer) + [![Pub](https://img.shields.io/pub/v/dio_compatibility_layer.svg?label=dev&include_prereleases)](https://pub.flutter-io.cn/packages/dio_compatibility_layer) - http2_adapter: [链接](plugins/http2_adapter) [![Pub](https://img.shields.io/pub/v/dio_http2_adapter.svg?label=dev&include_prereleases)](https://pub.flutter-io.cn/packages/dio_http2_adapter) - native_dio_adapter: [链接](plugins/native_dio_adapter) diff --git a/README.md b/README.md index 9e00f0025..fe977be73 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ See the [Migration Guide][] for the complete breaking changes list.** - cookie_manager: [link](plugins/cookie_manager) [![Pub](https://img.shields.io/pub/v/dio_cookie_manager.svg?label=dev&include_prereleases)](https://pub.dev/packages/dio_cookie_manager) +- compatibility_layer: [link](plugins/compatibility_layer) + [![Pub](https://img.shields.io/pub/v/dio_compatibility_layer.svg?label=dev&include_prereleases)](https://pub.dev/packages/dio_compatibility_layer) - http2_adapter: [link](plugins/http2_adapter) [![Pub](https://img.shields.io/pub/v/dio_http2_adapter.svg?label=dev&include_prereleases)](https://pub.dev/packages/dio_http2_adapter) - native_dio_adapter: [link](plugins/native_dio_adapter) diff --git a/dio/CHANGELOG.md b/dio/CHANGELOG.md index b4913ed83..3167d7386 100644 --- a/dio/CHANGELOG.md +++ b/dio/CHANGELOG.md @@ -9,6 +9,7 @@ See the [Migration Guide][] for the complete breaking changes list.** - Fix `receiveTimeout` for streamed responses. - Fix cancellation for streamed responses and downloads when using `IOHttpClientAdapter`. - Fix receive progress for streamed responses and downloads when using `IOHttpClientAdapter`. +- Support relative `baseUrl` on the Web platform. ## 5.4.0 diff --git a/dio/lib/src/options.dart b/dio/lib/src/options.dart index 8a816ae98..aa8d2f96d 100644 --- a/dio/lib/src/options.dart +++ b/dio/lib/src/options.dart @@ -92,8 +92,16 @@ typedef RequestEncoder = FutureOr> Function( /// The mixin class for options that provides common attributes. mixin OptionsMixin { - /// Request base url, it can contain sub paths like: https://pub.dev/api/. - late String baseUrl; + /// Request base url, it can be multiple forms: + /// - Contains sub paths, e.g. `https://pub.dev/api/`. + /// - Relative path on Web, e.g. `api/`. + String get baseUrl => _baseUrl; + late String _baseUrl; + + set baseUrl(String value) { + assert(value.isEmpty || kIsWeb || Uri.parse(value).host.isNotEmpty); + _baseUrl = value; + } /// Common query parameters. /// @@ -141,9 +149,7 @@ class BaseOptions extends _RequestConfig with OptionsMixin { RequestEncoder? requestEncoder, ResponseDecoder? responseDecoder, ListFormat? listFormat, - }) : assert(connectTimeout == null || !connectTimeout.isNegative), - assert(baseUrl.isEmpty || Uri.parse(baseUrl).host.isNotEmpty), - super( + }) : super( method: method, receiveTimeout: receiveTimeout, sendTimeout: sendTimeout, @@ -161,8 +167,8 @@ class BaseOptions extends _RequestConfig with OptionsMixin { responseDecoder: responseDecoder, listFormat: listFormat, ) { - this.queryParameters = queryParameters ?? {}; this.baseUrl = baseUrl; + this.queryParameters = queryParameters ?? {}; this.connectTimeout = connectTimeout; } diff --git a/dio/lib/src/utils.dart b/dio/lib/src/utils.dart index 0073bd7b0..04f7e44c1 100644 --- a/dio/lib/src/utils.dart +++ b/dio/lib/src/utils.dart @@ -6,6 +6,11 @@ import 'dart:developer' as dev; import 'options.dart'; import 'parameter.dart'; +// See https://github.com/flutter/flutter/pull/112122. +const kIsWeb = bool.hasEnvironment('dart.library.js_util') + ? bool.fromEnvironment('dart.library.js_util') + : identical(0, 0.0); + // For the web platform, an inline `bool.fromEnvironment` translates to // `core.bool.fromEnvironment` instead of correctly being replaced by the // constant value found in the environment at build time. diff --git a/dio/test/options_test.dart b/dio/test/options_test.dart index f7d7a8464..159e597ed 100644 --- a/dio/test/options_test.dart +++ b/dio/test/options_test.dart @@ -470,18 +470,23 @@ void main() { }); test('option invalid base url', () { - final opt1 = 'blob:http://localhost/xyz123'; - final opt2 = 'https://pub.dev'; - final opt3 = 'https://'; - final opt4 = 'https://loremipsum/'; - final opt5 = ''; - final opt6 = 'pub.dev'; - expect(Uri.parse(opt1).host.isNotEmpty, false); - expect(Uri.parse(opt2).host.isNotEmpty, true); - expect(Uri.parse(opt3).host.isNotEmpty, false); - expect(Uri.parse(opt4).host.isNotEmpty, true); - expect(Uri.parse(opt5).host.isNotEmpty, false); - expect(Uri.parse(opt6).host.isNotEmpty, false); + final invalidUrls = [ + 'blob:http://localhost/xyz123', + 'https://', + 'pub.dev', + ]; + final validUrls = [ + '', + 'https://pub.dev', + 'https://loremipsum/', + if (kIsWeb) 'api/', + ]; + for (final url in invalidUrls) { + expect(() => BaseOptions(baseUrl: url), throwsA(isA())); + } + for (final url in validUrls) { + expect(BaseOptions(baseUrl: url), isA()); + } }); test('Throws when using invalid methods', () async { diff --git a/dio_workspace.iml b/dio_workspace.iml index 47f8e0a3e..135d32488 100644 --- a/dio_workspace.iml +++ b/dio_workspace.iml @@ -4,9 +4,6 @@ - - - diff --git a/plugins/compatibility_layer/.gitignore b/plugins/compatibility_layer/.gitignore new file mode 100644 index 000000000..3cceda557 --- /dev/null +++ b/plugins/compatibility_layer/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/plugins/compatibility_layer/CHANGELOG.md b/plugins/compatibility_layer/CHANGELOG.md new file mode 100644 index 000000000..2e7754e54 --- /dev/null +++ b/plugins/compatibility_layer/CHANGELOG.md @@ -0,0 +1,9 @@ +# CHANGELOG + +## Unreleased + +*None.* + +## 0.1.0 + +- Initial version. diff --git a/plugins/compatibility_layer/LICENSE b/plugins/compatibility_layer/LICENSE new file mode 100644 index 000000000..6fe7bd1ba --- /dev/null +++ b/plugins/compatibility_layer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 The CFUG Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/plugins/compatibility_layer/README.md b/plugins/compatibility_layer/README.md new file mode 100644 index 000000000..9336e563c --- /dev/null +++ b/plugins/compatibility_layer/README.md @@ -0,0 +1,47 @@ +# dio_compatibility_layer + +[![pub package](https://img.shields.io/pub/v/dio_compatibility_layer.svg)](https://pub.dev/packages/dio_compatibility_layer) +[![likes](https://img.shields.io/pub/likes/dio_compatibility_layer)](https://pub.dev/packages/dio_compatibility_layer/score) +[![popularity](https://img.shields.io/pub/popularity/dio_compatibility_layer)](https://pub.dev/packages/dio_compatibility_layer/score) +[![pub points](https://img.shields.io/pub/points/dio_compatibility_layer)](https://pub.dev/packages/dio_compatibility_layer/score) + +If you encounter bugs, consider fixing it by opening a PR or at least contribute a failing test case. + +This package contains adapters for [Dio](https://pub.dev/packages/dio) +which enables you to make use of other HTTP clients as the underlying implementation. + +Currently, it supports compatibility with +- [`http`](https://pub.dev/packages/http) + +## Get started + +### Install + +Add the `dio_compatibility_layer` package to your +[pubspec dependencies](https://pub.dev/packages/dio_compatibility_layer/install). + +### Example + +To use the `http` compatibility: + +```dart +import 'package:dio/dio.dart'; +import 'package:dio_compatibility_layer/dio_compatibility_layer.dart'; +import 'package:http/http.dart'; + +void main() async { + // Start in the `http` world. You can use `http`, `cronet_http`, + // `cupertino_http` and other `http` compatible packages. + final httpClient = Client(); + + // Make the `httpClient` compatible via the `ConversionLayerAdapter` class. + final dioAdapter = ConversionLayerAdapter(httpClient); + + // Make dio use the `httpClient` via the conversion layer. + final dio = Dio()..httpClientAdapter = dioAdapter; + + // Make a request + final response = await dio.get('https://dart.dev'); + print(response); +} +``` diff --git a/plugins/compatibility_layer/analysis_options.yaml b/plugins/compatibility_layer/analysis_options.yaml new file mode 100644 index 000000000..d11f6ec33 --- /dev/null +++ b/plugins/compatibility_layer/analysis_options.yaml @@ -0,0 +1,7 @@ +include: ../../analysis_options.yaml + +analyzer: + language: + strict-raw-types: true + strict-casts: true + strict-inference: true diff --git a/plugins/compatibility_layer/dart_test.yaml b/plugins/compatibility_layer/dart_test.yaml new file mode 100644 index 000000000..dbc48f42c --- /dev/null +++ b/plugins/compatibility_layer/dart_test.yaml @@ -0,0 +1,12 @@ +presets: + # empty placeholder required in CI scripts + all: + +override_platforms: + chrome: + settings: + headless: true + firefox: + settings: + # headless argument has to be set explicitly for non-chrome browsers + arguments: --headless diff --git a/plugins/compatibility_layer/dio_compatibility_layer.iml b/plugins/compatibility_layer/dio_compatibility_layer.iml new file mode 100644 index 000000000..04b3b5ece --- /dev/null +++ b/plugins/compatibility_layer/dio_compatibility_layer.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/plugins/compatibility_layer/example/conversion_layer_example.dart b/plugins/compatibility_layer/example/conversion_layer_example.dart new file mode 100644 index 000000000..a246b2f0b --- /dev/null +++ b/plugins/compatibility_layer/example/conversion_layer_example.dart @@ -0,0 +1,19 @@ +import 'package:dio/dio.dart'; +import 'package:dio_compatibility_layer/dio_compatibility_layer.dart'; +import 'package:http/http.dart'; + +void main() async { + // Start in the `http` world. You can use `http`, `cronet_http`, + // `cupertino_http` and other `http` compatible packages. + final httpClient = Client(); + + // Make the `httpClient` compatible via the `ConversionLayerAdapter` class. + final dioAdapter = ConversionLayerAdapter(httpClient); + + // Make dio use the `httpClient` via the conversion layer. + final dio = Dio()..httpClientAdapter = dioAdapter; + + // Make a request. + final response = await dio.get('https://dart.dev'); + print(response); +} diff --git a/plugins/compatibility_layer/lib/dio_compatibility_layer.dart b/plugins/compatibility_layer/lib/dio_compatibility_layer.dart new file mode 100644 index 000000000..41e7e91eb --- /dev/null +++ b/plugins/compatibility_layer/lib/dio_compatibility_layer.dart @@ -0,0 +1,3 @@ +library dio_compatibility_layer; + +export 'src/conversion_layer_adapter.dart'; diff --git a/plugins/compatibility_layer/lib/src/conversion_layer_adapter.dart b/plugins/compatibility_layer/lib/src/conversion_layer_adapter.dart new file mode 100644 index 000000000..87ec81e1a --- /dev/null +++ b/plugins/compatibility_layer/lib/src/conversion_layer_adapter.dart @@ -0,0 +1,97 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:dio/dio.dart'; +import 'package:http/http.dart' as http; + +const _kIsWeb = bool.hasEnvironment('dart.library.js_util') + ? bool.fromEnvironment('dart.library.js_util') + : identical(0, 0.0); + +/// A conversion layer which translates [Dio] requests to +/// [`http`](https://pub.dev/packages/http) compatible requests. +/// This enables you to use +/// [`cronet_http`](https://pub.dev/packages/cronet_http), +/// [`cupertino_http`](https://pub.dev/packages/cupertino_http), +/// and other `http` compatible packages with [Dio]. +class ConversionLayerAdapter implements HttpClientAdapter { + ConversionLayerAdapter(this.client); + + /// The client instance from the `http` package. + final http.Client client; + + @override + Future fetch( + RequestOptions options, + Stream? requestStream, + Future? cancelFuture, + ) async { + final request = await _fromOptionsAndStream(options, requestStream); + final response = await client.send(request); + return ResponseBody( + response.stream.cast(), + response.statusCode, + statusMessage: response.reasonPhrase, + isRedirect: response.isRedirect, + headers: Map.fromEntries( + response.headers.entries.map((e) => MapEntry(e.key, [e.value])), + ), + ); + } + + @override + void close({bool force = false}) => client.close(); + + Future _fromOptionsAndStream( + RequestOptions options, + Stream? requestStream, + ) async { + final http.BaseRequest request; + if (_kIsWeb && requestStream != null) { + final normalRequest = request = http.Request( + options.method, + options.uri, + ); + final completer = Completer(); + final sink = ByteConversionSink.withCallback( + (bytes) => completer.complete( + bytes is Uint8List ? bytes : Uint8List.fromList(bytes), + ), + ); + requestStream.listen( + sink.add, + onError: completer.completeError, + onDone: sink.close, + cancelOnError: true, + ); + final bytes = await completer.future; + normalRequest.bodyBytes = bytes; + } else if (requestStream != null) { + final streamedRequest = request = http.StreamedRequest( + options.method, + options.uri, + ); + requestStream.listen( + streamedRequest.sink.add, + onError: streamedRequest.sink.addError, + onDone: streamedRequest.sink.close, + cancelOnError: true, + ); + } else { + request = http.Request(options.method, options.uri); + } + request.headers.addAll( + Map.fromEntries( + options.headers.entries.map( + (e) => MapEntry(e.key, e.value.toString().trim()), + ), + ), + ); + request + ..followRedirects = options.followRedirects + ..maxRedirects = options.maxRedirects + ..persistentConnection = options.persistentConnection; + return request; + } +} diff --git a/plugins/compatibility_layer/pubspec.yaml b/plugins/compatibility_layer/pubspec.yaml new file mode 100644 index 000000000..5916f8c36 --- /dev/null +++ b/plugins/compatibility_layer/pubspec.yaml @@ -0,0 +1,24 @@ +name: dio_compatibility_layer +version: 0.1.0 + +description: Enables dio to make use of http packages. +topics: + - dio + - http + - network + - native + - cronet +homepage: https://github.com/cfug/dio +repository: https://github.com/cfug/dio/blob/main/plugins/compatibility_layer +issue_tracker: https://github.com/cfug/dio/issues + +environment: + sdk: ^3.0.0 + +dependencies: + dio: ^5.2.0 + http: ^1.0.0 + +dev_dependencies: + lints: any + test: any diff --git a/plugins/compatibility_layer/test/client_mock.dart b/plugins/compatibility_layer/test/client_mock.dart new file mode 100644 index 000000000..184ba168b --- /dev/null +++ b/plugins/compatibility_layer/test/client_mock.dart @@ -0,0 +1,27 @@ +import 'package:http/http.dart'; + +class CloseClientMock implements Client { + bool closeWasCalled = false; + + @override + void close() { + closeWasCalled = true; + } + + @override + dynamic noSuchMethod(Invocation i) => super.noSuchMethod(i); +} + +class ClientMock implements Client { + StreamedResponse? response; + BaseRequest? request; + + @override + Future send(BaseRequest request) async { + this.request = request; + return response!; + } + + @override + dynamic noSuchMethod(Invocation i) => super.noSuchMethod(i); +} diff --git a/plugins/compatibility_layer/test/conversion_layer_adapter_test.dart b/plugins/compatibility_layer/test/conversion_layer_adapter_test.dart new file mode 100644 index 000000000..e2d956f39 --- /dev/null +++ b/plugins/compatibility_layer/test/conversion_layer_adapter_test.dart @@ -0,0 +1,64 @@ +import 'dart:typed_data'; + +import 'package:dio/dio.dart'; +import 'package:dio_compatibility_layer/dio_compatibility_layer.dart'; +import 'package:http/http.dart'; +import 'package:test/test.dart'; + +import 'client_mock.dart'; + +void main() { + test('close', () { + final mock = CloseClientMock(); + final cla = ConversionLayerAdapter(mock); + + cla.close(); + + expect(mock.closeWasCalled, true); + }); + + test('close with force', () { + final mock = CloseClientMock(); + final cla = ConversionLayerAdapter(mock); + + cla.close(force: true); + + expect(mock.closeWasCalled, true); + }); + + test('headers', () async { + final mock = ClientMock()..response = StreamedResponse(Stream.empty(), 200); + final cla = ConversionLayerAdapter(mock); + + await cla.fetch( + RequestOptions(path: '', headers: {'foo': 'bar'}), + Stream.empty(), + null, + ); + + expect(mock.request?.headers, {'foo': 'bar'}); + }); + + test('download stream', () async { + final mock = ClientMock() + ..response = StreamedResponse( + Stream.fromIterable([ + Uint8List.fromList([10, 1]), + Uint8List.fromList([1, 4]), + Uint8List.fromList([5, 1]), + Uint8List.fromList([1, 1]), + Uint8List.fromList([2, 4]), + ]), + 200, + ); + final cla = ConversionLayerAdapter(mock); + + final resp = await cla.fetch( + RequestOptions(path: ''), + null, + null, + ); + + expect(await resp.stream.length, 5); + }); +} diff --git a/plugins/http2_adapter/CHANGELOG.md b/plugins/http2_adapter/CHANGELOG.md index 4a5fcb8c2..72527f93f 100644 --- a/plugins/http2_adapter/CHANGELOG.md +++ b/plugins/http2_adapter/CHANGELOG.md @@ -5,7 +5,8 @@ - Bump minimum Dart SDK to 3.0.0 as required by the `http2` package. - Fix cancellation for streamed responses and downloads. - Fix progress for streamed responses and downloads. -- Fix redirect with multiple path segments not working. +- Bump minimum Dart SDK to 3.0.0 as required by the `http2` package. +- Allows `HTTP/1.0` when connecting to proxies. ## 2.4.0 diff --git a/plugins/http2_adapter/lib/src/connection_manager.dart b/plugins/http2_adapter/lib/src/connection_manager.dart index 8dc8ae5f4..b97bfe823 100644 --- a/plugins/http2_adapter/lib/src/connection_manager.dart +++ b/plugins/http2_adapter/lib/src/connection_manager.dart @@ -1,16 +1,18 @@ part of 'http2_adapter.dart'; -/// ConnectionManager is used to manager the connections that should be reusable. -/// The main responsibility of ConnectionManager is to implement a connection reuse -/// strategy for http2. +/// Manages the connections that should be reusable. +/// It implements a connection reuse strategy for HTTP/2. abstract class ConnectionManager { factory ConnectionManager({ Duration idleTimeout = const Duration(seconds: 15), void Function(Uri uri, ClientSetting)? onClientCreate, + ProxyConnectedPredicate proxyConnectedPredicate = + defaultProxyConnectedPredicate, }) => _ConnectionManager( idleTimeout: idleTimeout, onClientCreate: onClientCreate, + proxyConnectedPredicate: proxyConnectedPredicate, ); /// Get the connection(may reuse) for each request. @@ -20,3 +22,13 @@ abstract class ConnectionManager { void close({bool force = false}); } + +/// {@template dio_http2_adapter.ProxyConnectedPredicate} +/// Checks whether the proxy has been connected through the given [status]. +/// {@endtemplate} +typedef ProxyConnectedPredicate = bool Function(String protocol, String status); + +/// Accepts HTTP/1.x connections for proxies. +bool defaultProxyConnectedPredicate(String protocol, String status) { + return status.startsWith(RegExp(r'HTTP/1+\.\d 200')); +} diff --git a/plugins/http2_adapter/lib/src/connection_manager_imp.dart b/plugins/http2_adapter/lib/src/connection_manager_imp.dart index 036039dee..28430b8fb 100644 --- a/plugins/http2_adapter/lib/src/connection_manager_imp.dart +++ b/plugins/http2_adapter/lib/src/connection_manager_imp.dart @@ -5,6 +5,7 @@ class _ConnectionManager implements ConnectionManager { _ConnectionManager({ Duration? idleTimeout, this.onClientCreate, + this.proxyConnectedPredicate = defaultProxyConnectedPredicate, }) : _idleTimeout = idleTimeout ?? const Duration(seconds: 1); /// Callback when socket created. @@ -13,6 +14,9 @@ class _ConnectionManager implements ConnectionManager { /// for unverifiable certificates. final void Function(Uri uri, ClientSetting)? onClientCreate; + /// {@macro dio_http2_adapter.ProxyConnectedPredicate} + final ProxyConnectedPredicate proxyConnectedPredicate; + /// Sets the idle timeout(milliseconds) of non-active persistent /// connections. For the sake of socket reuse feature with http/2, /// the value should not be less than 1 second. @@ -173,7 +177,9 @@ class _ConnectionManager implements ConnectionManager { // Use CRLF as the end of the line https://www.ietf.org/rfc/rfc2616.txt const crlf = '\r\n'; - proxySocket.write('CONNECT ${target.host}:${target.port} HTTP/1.1'); + // TODO(EVERYONE): Figure out why we can only use an HTTP/1.x proxy here. + const proxyProtocol = 'HTTP/1.1'; + proxySocket.write('CONNECT ${target.host}:${target.port} $proxyProtocol'); proxySocket.write(crlf); proxySocket.write('Host: ${target.host}:${target.port}'); @@ -203,16 +209,24 @@ class _ConnectionManager implements ConnectionManager { final response = ascii.decode(event); final lines = response.split(crlf); final statusLine = lines.first; - - if (statusLine.startsWith('HTTP/1.1 200')) { - completerProxyInitialization.complete(); - } else { - completerProxyInitialization.completeError( - SocketException('Proxy cannot be initialized'), - ); + if (!completerProxyInitialization.isCompleted) { + if (proxyConnectedPredicate(proxyProtocol, statusLine)) { + completerProxyInitialization.complete(); + } else { + completerProxyInitialization.completeError( + SocketException( + 'Proxy cannot be initialized with status = [$statusLine], ' + 'host = ${target.host}, port = ${target.port}', + ), + ); + } + } + }, + onError: (e, s) { + if (!completerProxyInitialization.isCompleted) { + completerProxyInitialization.completeError(e, s); } }, - onError: completerProxyInitialization.completeError, ); await completerProxyInitialization.future; diff --git a/plugins/http2_adapter/test/http2_test.dart b/plugins/http2_adapter/test/http2_test.dart index 41bee3e56..319b40cd4 100644 --- a/plugins/http2_adapter/test/http2_test.dart +++ b/plugins/http2_adapter/test/http2_test.dart @@ -161,4 +161,22 @@ void main() { expect(content, contains('Numkey: 2')); expect(content, contains('Booleankey: false')); }); + + group(ProxyConnectedPredicate, () { + group('defaultProxyConnectedPredicate', () { + test( + 'accepts HTTP/1.x for HTTP/1.1 proxy', + () { + expect( + defaultProxyConnectedPredicate('HTTP/1.1', 'HTTP/1.1 200'), + true, + ); + expect( + defaultProxyConnectedPredicate('HTTP/1.1', 'HTTP/1.0 200'), + true, + ); + }, + ); + }); + }); } diff --git a/plugins/native_dio_adapter/CHANGELOG.md b/plugins/native_dio_adapter/CHANGELOG.md index edfcbc728..868ac5310 100644 --- a/plugins/native_dio_adapter/CHANGELOG.md +++ b/plugins/native_dio_adapter/CHANGELOG.md @@ -2,7 +2,12 @@ ## Unreleased +*None.* + +## 1.3.0 + - Provide fix suggestions for `dart fix`. +- Bump cronet_http version to `>=0.4.0 <=2.0.0`. ## 1.2.0 diff --git a/plugins/native_dio_adapter/README.md b/plugins/native_dio_adapter/README.md index aab6c498b..e3aa236e3 100644 --- a/plugins/native_dio_adapter/README.md +++ b/plugins/native_dio_adapter/README.md @@ -7,7 +7,6 @@ > Note: This uses the native http implementation on macOS, iOS and Android. > Other platforms still use the Dart http stack. -> Please keep in mind that Android is still considered experimental. If you encounter bugs, consider fixing it by opening a PR or at least contribute a failing test case. diff --git a/plugins/native_dio_adapter/pubspec.yaml b/plugins/native_dio_adapter/pubspec.yaml index ebff7cd17..01924f9c9 100644 --- a/plugins/native_dio_adapter/pubspec.yaml +++ b/plugins/native_dio_adapter/pubspec.yaml @@ -1,5 +1,5 @@ name: native_dio_adapter -version: 1.2.0 +version: 1.3.0 description: A client for dio which makes use of cupertino_http and cronet_http to delegate HTTP requests to the native platform. topics: @@ -22,7 +22,7 @@ dependencies: dio: ^5.4.0 cupertino_http: ^1.0.0 - cronet_http: ^0.4.0 + cronet_http: '>=0.4.0 <=2.0.0' http: ^1.0.0 dev_dependencies: