From adc6842d6664d427bb06bc44aa870340bd127ef4 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Tue, 16 Jan 2024 15:51:47 +0800 Subject: [PATCH] =?UTF-8?q?[http2=5Fadapter]=20=F0=9F=90=9B=20Support=20HT?= =?UTF-8?q?TP/1.x=20for=20proxy=20responses=20(#2054)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #2053. According to RFC 2616/9110, protocols with the same major version will be allowed in responses. ### New Pull Request Checklist - [x] I have read the [Documentation](https://pub.dev/documentation/dio/latest/) - [x] I have searched for a similar pull request in the [project](https://github.com/cfug/dio/pulls) and found none - [x] I have updated this branch with the latest `main` branch to avoid conflicts (via merge from master or rebase) - [x] I have added the required tests to prove the fix/feature I'm adding - [x] I have updated the documentation (if necessary) - [x] I have run the tests without failures - [x] I have updated the `CHANGELOG.md` in the corresponding package --------- Signed-off-by: Alex Li --- plugins/http2_adapter/CHANGELOG.md | 1 + .../lib/src/connection_manager.dart | 18 +++++++++-- .../lib/src/connection_manager_imp.dart | 32 +++++++++++++------ plugins/http2_adapter/test/http2_test.dart | 18 +++++++++++ 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/plugins/http2_adapter/CHANGELOG.md b/plugins/http2_adapter/CHANGELOG.md index a65d22591..585f347b1 100644 --- a/plugins/http2_adapter/CHANGELOG.md +++ b/plugins/http2_adapter/CHANGELOG.md @@ -5,6 +5,7 @@ - Fix cancellation for streamed responses and downloads. - Fix progress for streamed responses and downloads. - 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, + ); + }, + ); + }); + }); }