diff --git a/packages/solana/lib/src/rpc/client.dart b/packages/solana/lib/src/rpc/client.dart index dda868e8f7..fdc83dbef2 100644 --- a/packages/solana/lib/src/rpc/client.dart +++ b/packages/solana/lib/src/rpc/client.dart @@ -18,6 +18,7 @@ abstract class RpcClient { String url, { Duration timeout = const Duration(seconds: 30), Map customHeaders = const {}, + Map? proxyInfo, // {host: String, port: int} }) => _RpcClient( url, @@ -25,6 +26,7 @@ abstract class RpcClient { url, timeout: timeout, customHeaders: customHeaders, + proxyInfo: proxyInfo ?? {}, ), ); diff --git a/packages/solana/lib/src/rpc/json_rpc_client.dart b/packages/solana/lib/src/rpc/json_rpc_client.dart index d12abc6087..7f4d393cdc 100644 --- a/packages/solana/lib/src/rpc/json_rpc_client.dart +++ b/packages/solana/lib/src/rpc/json_rpc_client.dart @@ -1,7 +1,9 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; -import 'package:http/http.dart'; +import 'package:socks5_proxy/socks.dart'; import 'package:solana/src/exceptions/http_exception.dart'; import 'package:solana/src/exceptions/json_rpc_exception.dart'; import 'package:solana/src/exceptions/rpc_timeout_exception.dart'; @@ -12,12 +14,15 @@ class JsonRpcClient { this._url, { required Duration timeout, required Map customHeaders, + required Map proxyInfo, // {host: String, port: int} }) : _timeout = timeout, - _headers = {..._defaultHeaders, ...customHeaders}; + _headers = {..._defaultHeaders, ...customHeaders}, + _proxyInfo = proxyInfo; final String _url; final Duration _timeout; final Map _headers; + final Map _proxyInfo; int _lastId = 1; Future>> bulkRequest( @@ -68,26 +73,51 @@ class JsonRpcClient { Future<_JsonRpcResponse> _postRequest( JsonRpcRequest request, ) async { - final body = request.toJson(); - // Perform the POST request - final Response response = await post( - Uri.parse(_url), - headers: _headers, - body: json.encode(body), - ).timeout( - _timeout, - onTimeout: () => throw RpcTimeoutException( - method: request.method, - body: body, - timeout: _timeout, - ), - ); - // Handle the response - if (response.statusCode == 200) { - return _JsonRpcResponse._parse(json.decode(response.body)); - } + final Uri uri = Uri.parse(_url); + final HttpClient httpClient = HttpClient(); + + try { + // If proxyInfo is provided, configure the proxy. + if (_proxyInfo.isNotEmpty) { + SocksTCPClient.assignToHttpClient(httpClient, [ + ProxySettings( + InternetAddress(_proxyInfo['host'] as String), + _proxyInfo['port'] as int, + ), + ]); + } + + final HttpClientRequest httpClientRequest = await httpClient.postUrl(uri); + _headers + .forEach((key, value) => httpClientRequest.headers.set(key, value)); + httpClientRequest.write(json.encode(request.toJson())); + + final HttpClientResponse response = + await httpClientRequest.close().timeout( + _timeout, + onTimeout: () { + throw RpcTimeoutException( + method: request.method, + body: json.encode(request.toJson()), + timeout: _timeout, + ); + }, + ); + + // Consolidate the bytes and parse the response. + final Uint8List bodyBytes = + await consolidateHttpClientResponseBytes(response); + final String responseBody = utf8.decode(bodyBytes); + final int statusCode = response.statusCode; - throw HttpException(response.statusCode, response.body); + if (statusCode == 200) { + return _JsonRpcResponse._parse(json.decode(responseBody)); + } + + throw HttpException(statusCode, responseBody); + } finally { + httpClient.close(); + } } } @@ -148,3 +178,21 @@ class _JsonRpcArrayResponse implements _JsonRpcResponse { const _defaultHeaders = { 'Content-Type': 'application/json', }; + +/// Helper function to consolidate HttpClientResponse bytes. +/// +/// Helps ensure HttpClientResponse is fully read before closing the connection. +/// Helper for proxied connections. +Future consolidateHttpClientResponseBytes( + HttpClientResponse response) { + final Completer completer = Completer(); + final List bytes = []; + response.listen( + bytes.addAll, + onDone: () => completer.complete(Uint8List.fromList(bytes)), + onError: completer.completeError, + cancelOnError: true, + ); + + return completer.future; +} diff --git a/packages/solana/pubspec.yaml b/packages/solana/pubspec.yaml index 2ba95ebe40..08d155aa72 100644 --- a/packages/solana/pubspec.yaml +++ b/packages/solana/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: freezed_annotation: ^2.0.0 http: ^1.1.0 json_annotation: ^4.4.0 + socks5_proxy: ^1.0.4 typed_data: ^1.3.0 web_socket_channel: ^2.1.0