如何在程序内实现内置的host.mark
如何用dart实现HTTPS代理.mark
起源
第三个问题来了,有时发送HTTP请求的时候,因为DNS污染的原因,请求并不能成功。
而在手机上如果没有root权限,像PC端那样直接修改hosts不太可能。
这个时候怎么做才好呢?
过程
- 看了一下
dart:io
,想要实现这个功能只需要在源码里面改那么一丁点东西,也就是在Socket连接建立的时候将host替换掉,可惜。 - 目前也没啥好一点的现成的工具,如果是从TCP层面入手的话,如果没有像java netty那样的第三方工具,自己实现整个SSL层很麻烦啊。
- 工作量少一点的方法是,考虑自己建立一个本地HTTPS代理,想要访问的HTTP请求设置一下代理即可。 思路如下:
- 接收从Client段发来的CONNECT消息,从头部读取到host和port
- 在自己维护的表内查询是否有host,并据此建立与服务器的连接
host = map[host] == null? host : map[host]; socketWithServer = socket.connect(host, port);
- 与服务器的连接建立后,向客户端回个确认消息
socketWithClient.add('HTTP/1.1 200 Connection Established\r\n\r\n');
- 充当客户端与服务器之间的水管工,来啥转发啥。
- 很难想象不到两百行代码就可以搞定,而且里面还有那么多try catch…
代码
功能实现
import 'dart:io';
import 'dart:convert';
class CustomHttpsProxy {
final Map hosts;
final int port;
ServerSocket serverSocket;
CustomHttpsProxy({this.hosts, this.port});
Future init() async {
await ServerSocket.bind(InternetAddress.anyIPv4, port).then((serverSocket) {
this.serverSocket = serverSocket;
serverSocket.listen((client) {
try {
ClientConnectionHandler(client, hosts).handle();
} catch (e) {
print('ClientConnectionHandler exception $e');
}
});
}).catchError((e) {
print('serverSocket 处理异常$e');
});
return serverSocket;
}
void close() {
if (serverSocket != null) {
serverSocket.close();
}
}
}
class ClientConnectionHandler {
final RegExp regx = RegExp(r'CONNECT ([^ :]+)(?::([0-9]+))? HTTP/1.1\r\n');
final Map hosts;
Socket server;
Socket client;
String content = '';
String host;
int port;
ClientConnectionHandler(this.client, this.hosts);
void closeSockets() {
// print('socket is going to destroy');
if (server != null) {
server.destroy();
}
client.destroy();
}
void dataHandler(data) {
if (server == null) {
content += utf8.decode(data);
final m = regx.firstMatch(content);
if (m != null) {
host = m.group(1);
port = m.group(2) == null ? 443 : int.parse(m.group(2));
final realHost = hosts != null && hosts.containsKey(host) ? hosts[host] : host;
try {
ServerConnectionHandler(realHost, port, this)
.handle()
.catchError((e) {
print('Server error $e');
closeSockets();
});
} catch (e) {
print('Server exception $e');
closeSockets();
}
}
} else {
try {
server.add(data);
} catch (e) {
print('sever has been shut down');
closeSockets();
}
}
}
void errorHandler(error, StackTrace trace) {
print('client socket error: $error');
}
void doneHandler() {
closeSockets();
}
void handle() {
client.listen(dataHandler,
onError: errorHandler, onDone: doneHandler, cancelOnError: true);
}
}
class ServerConnectionHandler {
final String RESPONSE = 'HTTP/1.1 200 Connection Established\r\n\r\n';
final String host;
final int port;
final ClientConnectionHandler handler;
Socket server;
Socket client;
String content = '';
ServerConnectionHandler(this.host, this.port, this.handler) {
client = handler.client;
}
//接收报文
void dataHandler(data) {
try {
client.add(data);
} on Exception catch (e) {
print('client has been shut down $e');
handler.closeSockets();
}
}
void errorHandler(error, StackTrace trace) {
print('server socket error: $error');
}
void doneHandler() {
handler.closeSockets();
}
Future handle() async {
// print('尝试建立连接: $host:$port');
server = await Socket.connect(host, port, timeout: Duration(seconds: 60));
server.listen(dataHandler,
onError: errorHandler, onDone: doneHandler, cancelOnError: true);
handler.server = server;
client.write(RESPONSE);
}
}
一个样例
import 'dart:io';
import 'dart:convert';
import 'package:httpProxy/httpProxy.dart';
void main(List<String> arguments) {
runPoxyAndDownloadFile();
}
void runPoxyAndDownloadFile() async {
final hostMap = {
'www.abc.com': '123.123.123.123',
};
var proxy = CustomHttpsProxy(hosts: hostMap, port: 4041);
final result = await proxy.init();
print('proxy established: ${result != null}');
await doHttpGet();
proxy.close();
}
Future doHttpGet() async {
var httpClient = HttpClient();
httpClient.findProxy = (uri) => 'PROXY localhost:4041';
var request = await httpClient.getUrl(Uri.parse('https://www.abc.com/favicon.ico'));
var response = await request.close();
// var responseBody = await response.transform(Utf8Decoder()).join();
var file = File('1.txt');
var raf = file.openSync(mode: FileMode.write);
response.listen((data) {
raf.writeFromSync(data);
}, onDone: () => raf.closeSync());
}