You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Dart代理隧道连接Binance API:静态代理失效问题排查

Dart HTTP代理隧道连接Binance API静态代理无响应问题

我编写了一段Dart代码,用于通过HTTP代理服务器创建隧道连接Binance API。代码使用动态代理时可正常发起请求(如packetstream.io提供的代理),但使用静态代理时无响应。预期能收到Binance API的响应及响应头,但静态代理场景下无返回,尝试多个服务商的静态代理均出现该问题,请问代码中存在哪些错误?

void main() async {
  final uri = Uri.parse(
      'https://api.binance.com/sapi/v1/asset/get-funding-asset');
  final transport = await connectTunnel('185.142.98.232', 29130, uri.host,
      username: 'Pbt5MoAU89ph', password: 'itsallgood.man');

  final payload = jsonEncode({'key': 'value'});
  final contentLength = utf8.encode(payload).length;

  final headers = [
    Header.ascii(':method', 'POST'),
    Header.ascii(':path', uri.hasQuery ? uri.path + '?' + uri.query : uri.path),
    Header.ascii(':scheme', uri.scheme),
    Header.ascii(':authority', uri.host),
    Header.ascii('content-type', 'application/json'),
    Header.ascii('content-length', contentLength.toString()),
  ];

  final stream = transport.makeRequest(headers, endStream: false);
  stream.outgoingMessages.add(DataStreamMessage(utf8.encode(payload)));
  await stream.outgoingMessages.close();

  final bodyStream = StreamController<List<int>>();
  utf8.decoder.bind(bodyStream.stream).listen(print);

  await for (final message in stream.incomingMessages) {
    if (message is HeadersStreamMessage) {
      for (final header in message.headers) {
        final name = utf8.decode(header.name);
        final value = utf8.decode(header.value);
        print('Header: $name: $value');
      }
    } else if (message is DataStreamMessage) {
      bodyStream.add(message.bytes);
      if (message.endStream) {
        await bodyStream.close();
      }
    } else {
      print('Incoming message: $message');
    }
  }
  if (!bodyStream.isClosed) {
    await bodyStream.close();
  }
  await transport.finish();
}

Future<ClientTransportConnection> connectTunnel(
  String proxyHost,
  int proxyPort,
  String targetHost, {
  required String username,
  required String password,
  int targetPort = 443,
}) async {
  try {
    final proxy = await Socket.connect(
      proxyHost,
      proxyPort,
      timeout: const Duration(seconds: 10),
    );
    const CRLF = '\r\n';
    proxy
      ..write('CONNECT $targetHost:$targetPort HTTP/1.1') // request line
      ..write(CRLF);
    proxy.write('Host: $targetHost:$targetPort'); // header
    proxy.write(CRLF);
    final authString = base64.encode(utf8.encode('$username:$password'));
    proxy.write('Proxy-Authorization: Basic $authString');
    proxy.write(CRLF);
    proxy.write(CRLF);
    final completer = Completer<bool>.sync();
    final sub = proxy.listen(
      (event) {
        final response = ascii.decode(event);
        final lines = response.split(CRLF);
        // status line
        final statusLine = lines.first;
        if (statusLine.startsWith('HTTP/1.1 200')) {
          print(statusLine);
          completer.complete(true);
        } else {
          completer.completeError(statusLine);
        }
      },
      onError: completer.completeError,
    );
    await completer.future; // established
    sub.pause();

    // ignore: close_sinks
    final socket = await SecureSocket.secure(
      proxy,
      host: targetHost,
      supportedProtocols: const ['h2'],
    );
    // ignore: newline-before-return
    return ClientTransportConnection.viaSocket(socket);
  } on SocketException catch (e) {
    print('SocketException: $e');
    rethrow;
  } on HttpException catch (e) {
    print('HttpException: $e');
    rethrow;
  } catch (e) {
    print('Error: $e');
    rethrow;
  }
}

代码中的错误点

1. 代理响应残留数据未清理

静态代理返回的完整响应(包括状态行、响应头和结尾空行)会留在Socket缓冲区中,当前代码仅检查状态行就暂停监听,后续TLS握手时会把这些HTTP数据当成TLS协议数据,导致握手失败、连接卡住。

修复方法:确认隧道建立后,读取并丢弃所有代理响应数据直到空行:

// 在await completer.future;之后添加
const CRLF = '\r\n';
StringBuffer responseBuffer = StringBuffer();
while (true) {
  List<int> data = proxy.read();
  if (data.isEmpty) break;
  responseBuffer.write(ascii.decode(data));
  if (responseBuffer.toString().endsWith('$CRLF$CRLF')) {
    break;
  }
}
sub.cancel(); // 取消原监听

2. 强制指定HTTP/2协议

代码中SecureSocket.secure强制要求使用HTTP/2,但部分静态代理或目标服务可能不支持该协议,导致连接建立后无法正常通信。

修复方法:移除强制HTTP/2的配置,让系统自动协商支持的协议:

final socket = await SecureSocket.secure(
  proxy,
  host: targetHost,
  // 移除supportedProtocols参数
);

3. 代理响应处理不完整

当前监听逻辑仅处理单次接收的数据,若代理响应分多次发送,会导致状态行解析不完整,无法触发隧道建立的完成逻辑。

修复方法:累积数据直到收到完整的HTTP响应头(以空行结尾):

// 替换原代理监听逻辑
final completer = Completer<bool>.sync();
StringBuffer responseBuffer = StringBuffer();
final sub = proxy.listen(
  (event) {
    responseBuffer.write(ascii.decode(event));
    if (responseBuffer.toString().contains('$CRLF$CRLF')) {
      final response = responseBuffer.toString();
      final lines = response.split(CRLF);
      final statusLine = lines.first;
      if (statusLine.startsWith(RegExp(r'HTTP/\d\.\d 200'))) {
        print(statusLine);
        completer.complete(true);
      } else {
        print('Proxy error response: $response');
        completer.completeError(statusLine);
      }
    }
  },
  onError: completer.completeError,
);

4. 状态码检查过严格

静态代理可能返回HTTP/1.0 200等其他成功状态码,当前代码仅匹配HTTP/1.1 200会导致误判。

修复方法:放宽状态码匹配规则,同时输出完整错误响应便于排查:

if (statusLine.startsWith(RegExp(r'HTTP/\d\.\d 200'))) {
  print(statusLine);
  completer.complete(true);
} else {
  print('Proxy error response: ${responseBuffer.toString()}');
  completer.completeError(statusLine);
}

内容的提问来源于stack exchange,提问作者Neon RolePlay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 09:43:16