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: Tor/SOCKS5 proxy support: add optional proxyInfo property and use it (via the socks5_proxy package) if not null. #1378

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 2 additions & 0 deletions packages/solana/lib/src/rpc/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ abstract class RpcClient {
String url, {
Duration timeout = const Duration(seconds: 30),
Map<String, String> customHeaders = const {},
Map<String, dynamic>? proxyInfo, // {host: String, port: int}
}) =>
_RpcClient(
url,
JsonRpcClient(
url,
timeout: timeout,
customHeaders: customHeaders,
proxyInfo: proxyInfo ?? {},
),
);

Expand Down
90 changes: 69 additions & 21 deletions packages/solana/lib/src/rpc/json_rpc_client.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -12,12 +14,15 @@ class JsonRpcClient {
this._url, {
required Duration timeout,
required Map<String, String> customHeaders,
required Map<String, dynamic> proxyInfo, // {host: String, port: int}
}) : _timeout = timeout,
_headers = {..._defaultHeaders, ...customHeaders};
_headers = {..._defaultHeaders, ...customHeaders},
_proxyInfo = proxyInfo;

final String _url;
final Duration _timeout;
final Map<String, String> _headers;
final Map<String, dynamic> _proxyInfo;
int _lastId = 1;

Future<List<Map<String, dynamic>>> bulkRequest(
Expand Down Expand Up @@ -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();
}
}
}

Expand Down Expand Up @@ -148,3 +178,21 @@ class _JsonRpcArrayResponse implements _JsonRpcResponse {
const _defaultHeaders = <String, String>{
'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<Uint8List> consolidateHttpClientResponseBytes(
HttpClientResponse response) {
final Completer<Uint8List> completer = Completer<Uint8List>();
final List<int> bytes = [];
response.listen(
bytes.addAll,
onDone: () => completer.complete(Uint8List.fromList(bytes)),
onError: completer.completeError,
cancelOnError: true,
);

return completer.future;
}
1 change: 1 addition & 0 deletions packages/solana/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down