mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-05-20 16:15:47 +08:00
http proxy init
This commit is contained in:
14
lib/network/bin/server.dart
Normal file
14
lib/network/bin/server.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'dart:async';
|
||||
|
||||
import '../channel.dart';
|
||||
import '../handler.dart';
|
||||
import '../http/codec.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
const port = 8888;
|
||||
Server server = Server(port)
|
||||
..initChannel((channel) {
|
||||
channel.pipeline.handle(HttpRequestCodec(), HttpResponseCodec(), HttpChannelHandler());
|
||||
});
|
||||
await server.bind();
|
||||
}
|
||||
265
lib/network/channel.dart
Normal file
265
lib/network/channel.dart
Normal file
@@ -0,0 +1,265 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:network/network/util/AttributeKeys.dart';
|
||||
import 'package:network/network/util/CertificateManager.dart';
|
||||
|
||||
|
||||
///处理I/O事件或截获I/O操作
|
||||
abstract class ChannelHandler<T> {
|
||||
var log = Logger(
|
||||
printer: PrettyPrinter(
|
||||
methodCount: 0,
|
||||
errorMethodCount: 8,
|
||||
lineLength: 120,
|
||||
colors: true,
|
||||
printEmojis: false,
|
||||
excludeBox: {Level.info: true, Level.debug: true},
|
||||
));
|
||||
|
||||
void channelActive(Channel channel) {}
|
||||
|
||||
void channelRead(Channel channel, T msg) {}
|
||||
|
||||
void channelInactive(Channel channel) {
|
||||
log.i("close $channel");
|
||||
}
|
||||
|
||||
void exceptionCaught(Channel channel, Object cause, {StackTrace? trace}) {
|
||||
var attribute = channel.getAttribute(AttributeKeys.HOST_KEY);
|
||||
log.e("error $attribute $channel", cause, trace);
|
||||
}
|
||||
}
|
||||
|
||||
///与网络套接字或组件的连接,能够进行读、写、连接和绑定等I/O操作。
|
||||
class Channel {
|
||||
final int _id;
|
||||
final ChannelPipeline pipeline = ChannelPipeline();
|
||||
Socket _socket;
|
||||
final Map<String, Object> _attributes = {};
|
||||
bool isOpen = true;
|
||||
|
||||
//此通道连接到的远程地址
|
||||
final InternetAddress remoteAddress;
|
||||
final int remotePort;
|
||||
|
||||
Channel(this._socket)
|
||||
: _id = DateTime.now().millisecondsSinceEpoch + Random().nextInt(9999),
|
||||
remoteAddress = _socket.remoteAddress,
|
||||
remotePort = _socket.remotePort;
|
||||
|
||||
///返回此channel的全局唯一标识符。
|
||||
String get id => _id.toRadixString(16);
|
||||
|
||||
Socket get socket => _socket;
|
||||
|
||||
set secureSocket(secureSocket) => _socket = secureSocket;
|
||||
|
||||
Future<void> write(Object obj) async {
|
||||
var data = pipeline._encoder.encode(obj);
|
||||
_socket.add(data);
|
||||
await _socket.flush();
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
_socket.destroy();
|
||||
isOpen = false;
|
||||
}
|
||||
|
||||
bool get isClosed => !isOpen;
|
||||
|
||||
T? getAttribute<T>(String key) {
|
||||
if (!_attributes.containsKey(key)) {
|
||||
return null;
|
||||
}
|
||||
return _attributes[key] as T;
|
||||
}
|
||||
|
||||
void putAttribute(String key, Object value) {
|
||||
_attributes[key] = value;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Channel($_id ${remoteAddress.host}:$remotePort)';
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelPipeline extends ChannelHandler<Uint8List> {
|
||||
late Decoder _decoder;
|
||||
late Encoder _encoder;
|
||||
late ChannelHandler _handler;
|
||||
|
||||
handle(Decoder decoder, Encoder encoder, ChannelHandler handler) {
|
||||
_encoder = encoder;
|
||||
_decoder = decoder;
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
@override
|
||||
void channelActive(Channel channel) {
|
||||
_handler.channelActive(channel);
|
||||
}
|
||||
|
||||
@override
|
||||
void channelRead(Channel channel, Uint8List msg) {
|
||||
try {
|
||||
var data = _decoder.decode(msg);
|
||||
if (data != null) _handler.channelRead(channel, data!);
|
||||
} catch (error, trace) {
|
||||
exceptionCaught(channel, error, trace: trace);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
exceptionCaught(Channel channel, cause, {StackTrace? trace}) {
|
||||
_handler.exceptionCaught(channel, cause, trace: trace);
|
||||
}
|
||||
|
||||
@override
|
||||
channelInactive(Channel channel) {
|
||||
_handler.channelInactive(channel);
|
||||
}
|
||||
}
|
||||
|
||||
class HostAndPort {
|
||||
static const String httpScheme = "http://";
|
||||
static const String httpsScheme = "https://";
|
||||
final String scheme;
|
||||
final String host;
|
||||
final int port;
|
||||
|
||||
HostAndPort(this.scheme, this.host, this.port);
|
||||
|
||||
bool isSsl() {
|
||||
return httpsScheme.startsWith(scheme);
|
||||
}
|
||||
|
||||
/// 根据url构建
|
||||
static HostAndPort of(String url) {
|
||||
String domain = url;
|
||||
String? scheme;
|
||||
//域名格式 直接解析
|
||||
if (url.startsWith(httpScheme)) {
|
||||
//httpScheme
|
||||
scheme = url.startsWith(httpsScheme) ? httpsScheme : httpScheme;
|
||||
domain = url.substring(scheme.length).split("/")[0];
|
||||
}
|
||||
//ip格式 host:port
|
||||
List<String> hostAndPort = domain.split(":");
|
||||
|
||||
if (hostAndPort.length == 2) {
|
||||
bool isSsl = hostAndPort[1] == "443";
|
||||
scheme = isSsl ? httpsScheme : httpScheme;
|
||||
return HostAndPort(scheme, hostAndPort[0], int.parse(hostAndPort[1]));
|
||||
}
|
||||
scheme ??= httpScheme;
|
||||
return HostAndPort(scheme, hostAndPort[0], 80);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$scheme$host:$port';
|
||||
}
|
||||
}
|
||||
|
||||
/// 解码
|
||||
abstract interface class Decoder<T> {
|
||||
T? decode(Uint8List data);
|
||||
}
|
||||
|
||||
/// 编码
|
||||
abstract interface class Encoder<T> {
|
||||
List<int> encode(T data);
|
||||
}
|
||||
|
||||
/// 编解码器
|
||||
abstract class Codec<T> implements Decoder<T>, Encoder<T> {
|
||||
static const int defaultMaxInitialLineLength = 1024;
|
||||
}
|
||||
|
||||
class RawCodec extends Codec<Object> {
|
||||
@override
|
||||
Object? decode(Uint8List data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
@override
|
||||
List<int> encode(Object data) {
|
||||
return data as List<int>;
|
||||
}
|
||||
}
|
||||
|
||||
abstract interface class ChannelInitializer {
|
||||
void initChannel(Channel channel);
|
||||
}
|
||||
|
||||
class Network {
|
||||
late Function _channelInitializer;
|
||||
|
||||
Network initChannel(void Function(Channel channel) initializer) {
|
||||
_channelInitializer = initializer;
|
||||
return this;
|
||||
}
|
||||
|
||||
Channel listen(Socket socket) {
|
||||
var channel = Channel(socket);
|
||||
_channelInitializer.call(channel);
|
||||
|
||||
channel.pipeline.channelActive(channel);
|
||||
|
||||
socket.listen((data) => _onEvent(data, channel),
|
||||
onError: (error, trace) => channel.pipeline.exceptionCaught(channel, error, trace: trace),
|
||||
onDone: () => channel.pipeline.channelInactive(channel));
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
_onEvent(Uint8List data, Channel channel) async {
|
||||
HostAndPort? hostAndPort = channel.getAttribute(AttributeKeys.HOST_KEY);
|
||||
if (hostAndPort != null && hostAndPort.isSsl()) {
|
||||
try {
|
||||
var certificate = await CertificateManager.getCertificateContext(hostAndPort.host);
|
||||
SecureSocket secureSocket = await SecureSocket.secureServer(channel.socket, certificate, bufferedData: data);
|
||||
channel.secureSocket = secureSocket;
|
||||
secureSocket.listen((event) => channel.pipeline.channelRead(channel, event));
|
||||
} catch (error, trace) {
|
||||
channel.pipeline._handler.exceptionCaught(channel, error, trace: trace);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
channel.pipeline.channelRead(channel, data);
|
||||
}
|
||||
}
|
||||
|
||||
class Server extends Network {
|
||||
final int port;
|
||||
|
||||
Server(this.port);
|
||||
|
||||
Future<void> bind() async {
|
||||
ServerSocket.bind(InternetAddress.loopbackIPv4, port).then((serverSocket) => {
|
||||
serverSocket.listen((socket) {
|
||||
listen(socket);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Client extends Network {
|
||||
var log = Logger();
|
||||
|
||||
Future<Channel> connect(HostAndPort hostAndPort) async {
|
||||
if (hostAndPort.isSsl()) {
|
||||
return SecureSocket.connect(hostAndPort.host, hostAndPort.port, onBadCertificate: (certificate) => true)
|
||||
.then((socket) => listen(socket));
|
||||
}
|
||||
return Socket.connect(hostAndPort.host, hostAndPort.port).then((socket) => listen(socket));
|
||||
}
|
||||
}
|
||||
12
lib/network/compress.dart
Normal file
12
lib/network/compress.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'dart:io';
|
||||
|
||||
///GZIP 解压缩
|
||||
List<int> gzipDecode(List<int> byteBuffer) {
|
||||
GZipCodec gzipCodec = GZipCodec();
|
||||
return gzipCodec.decode(byteBuffer);
|
||||
}
|
||||
|
||||
///GZIP 解压缩
|
||||
List<int> gzipEncode(List<int> input) {
|
||||
return GZipCodec().encode(input);
|
||||
}
|
||||
148
lib/network/handler.dart
Normal file
148
lib/network/handler.dart
Normal file
@@ -0,0 +1,148 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:network/network/http/http.dart';
|
||||
import 'package:network/network/http/http_headers.dart';
|
||||
import 'package:network/network/util/AttributeKeys.dart';
|
||||
import 'package:network/network/util/HostFilter.dart';
|
||||
|
||||
import 'channel.dart';
|
||||
import 'http/codec.dart';
|
||||
|
||||
/// 获取主机和端口
|
||||
HostAndPort getHostAndPort(HttpRequest request) {
|
||||
String requestUri = request.uri;
|
||||
//有些请求直接是路径 /xxx, 从header取host
|
||||
if (request.uri.startsWith("/")) {
|
||||
requestUri = request.headers.get(HttpHeaders.HOST)!;
|
||||
}
|
||||
return HostAndPort.of(requestUri);
|
||||
}
|
||||
|
||||
///
|
||||
class HttpChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
@override
|
||||
void channelActive(Channel channel) {
|
||||
// log.i("accept ${channel.remoteAddress.address}");
|
||||
}
|
||||
|
||||
@override
|
||||
void channelRead(Channel channel, HttpRequest msg) {
|
||||
forward(channel, msg).catchError((error, trace) {
|
||||
log.e("转发请求失败", error, trace);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void channelInactive(Channel channel) {
|
||||
super.channelInactive(channel);
|
||||
Channel? remoteChannel = channel.getAttribute(channel.id);
|
||||
if (remoteChannel != null) {
|
||||
remoteChannel.close();
|
||||
}
|
||||
}
|
||||
|
||||
/// 转发请求
|
||||
Future<void> forward(Channel channel, HttpRequest httpRequest) async {
|
||||
var remoteChannel = await _getRemoteChannel(channel, httpRequest);
|
||||
|
||||
//实现抓包代理转发
|
||||
if (httpRequest.method != HttpMethod.connect) {
|
||||
if (channel.getAttribute(AttributeKeys.HOST_KEY) == null) {
|
||||
remoteChannel.putAttribute(AttributeKeys.URI_KEY, httpRequest.uri);
|
||||
} else {
|
||||
remoteChannel.putAttribute(
|
||||
AttributeKeys.URI_KEY, '${channel.getAttribute(AttributeKeys.HOST_KEY)}${httpRequest.uri}');
|
||||
}
|
||||
log.i("[${channel.id}] ${remoteChannel.getAttribute(AttributeKeys.URI_KEY)}");
|
||||
|
||||
//实现抓包代理转发
|
||||
await remoteChannel.write(httpRequest);
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取远程连接
|
||||
Future<Channel> _getRemoteChannel(Channel clientChannel, HttpRequest httpRequest) async {
|
||||
String clientId = clientChannel.id;
|
||||
//客户端连接 作为缓存
|
||||
Channel? remoteChannel = clientChannel.getAttribute(clientId);
|
||||
if (remoteChannel != null) {
|
||||
return remoteChannel;
|
||||
}
|
||||
|
||||
var hostAndPort = getHostAndPort(httpRequest);
|
||||
clientChannel.putAttribute(AttributeKeys.HOST_KEY, hostAndPort);
|
||||
|
||||
var proxyHandler = HttpResponseProxyHandler(clientChannel);
|
||||
var proxyChannel = await HttpClients.connect(hostAndPort, proxyHandler);
|
||||
|
||||
//https代理新建连接请求
|
||||
if (httpRequest.method == HttpMethod.connect) {
|
||||
await clientChannel.write(HttpResponse(httpRequest.protocolVersion, HttpStatus.ok));
|
||||
}
|
||||
|
||||
//黑名单
|
||||
if (HostFilter.filter(hostAndPort.host)) {
|
||||
relay(clientChannel, proxyChannel);
|
||||
return proxyChannel;
|
||||
}
|
||||
|
||||
clientChannel.putAttribute(clientId, proxyChannel);
|
||||
return proxyChannel;
|
||||
}
|
||||
|
||||
/// 转发请求
|
||||
void relay(Channel clientChannel, Channel remoteChannel) {
|
||||
var rawCodec = RawCodec();
|
||||
clientChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(remoteChannel));
|
||||
remoteChannel.pipeline.handle(rawCodec, rawCodec, RelayHandler(clientChannel));
|
||||
}
|
||||
}
|
||||
|
||||
/// http响应代理
|
||||
class HttpResponseProxyHandler extends ChannelHandler<HttpResponse> {
|
||||
final Channel clientChannel;
|
||||
|
||||
/// 排除的后缀 不打印日志
|
||||
final Set<String> excludeContent = HashSet.from(["javascript", "text/css", "application/font-woff", "image"]);
|
||||
|
||||
HttpResponseProxyHandler(this.clientChannel);
|
||||
|
||||
@override
|
||||
void channelRead(Channel channel, HttpResponse msg) {
|
||||
String contentType = msg.headers.contentType;
|
||||
if (excludeContent.every((element) => !contentType.contains(element))) {
|
||||
log.i("[${clientChannel.id}] Response ${utf8.decode(msg.body ?? [])}");
|
||||
}
|
||||
//发送给客户端
|
||||
clientChannel.write(msg);
|
||||
}
|
||||
|
||||
@override
|
||||
void channelInactive(Channel channel) {
|
||||
super.channelInactive(channel);
|
||||
clientChannel.close();
|
||||
}
|
||||
}
|
||||
|
||||
class RelayHandler extends ChannelHandler<Object> {
|
||||
final Channel remoteChannel;
|
||||
|
||||
RelayHandler(this.remoteChannel);
|
||||
|
||||
@override
|
||||
void channelRead(Channel channel, Object msg) {
|
||||
//发送给客户端
|
||||
remoteChannel.write(msg);
|
||||
}
|
||||
}
|
||||
|
||||
class HttpClients {
|
||||
/// 建立连接
|
||||
static Future<Channel> connect(HostAndPort hostAndPort, ChannelHandler<HttpResponse> handler) async {
|
||||
var client = Client()
|
||||
..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), handler));
|
||||
|
||||
return client.connect(hostAndPort);
|
||||
}
|
||||
}
|
||||
312
lib/network/http/codec.dart
Normal file
312
lib/network/http/codec.dart
Normal file
@@ -0,0 +1,312 @@
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:network/utils/num.dart';
|
||||
|
||||
import '../channel.dart';
|
||||
import '../compress.dart';
|
||||
import 'http.dart';
|
||||
import 'http_headers.dart';
|
||||
|
||||
class HttpConstants {
|
||||
/// Line feed character /n
|
||||
static const int lf = 10;
|
||||
|
||||
/// Carriage return /r
|
||||
static const int cr = 13;
|
||||
|
||||
/// Horizontal space
|
||||
static const int sp = 32;
|
||||
|
||||
/// Colon ':'
|
||||
static const int colon = 58;
|
||||
}
|
||||
|
||||
enum State {
|
||||
readInitial,
|
||||
readHeader,
|
||||
readVariableLengthContent,
|
||||
readFixedLengthContent,
|
||||
readChunkedContent,
|
||||
done,
|
||||
}
|
||||
|
||||
/// http编解码
|
||||
abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
|
||||
final HttpParse _httpParse = HttpParse();
|
||||
State _state = State.readInitial;
|
||||
|
||||
///chunked编码 剩余未读取的chunk大小
|
||||
int _chunkReadableSize = 0;
|
||||
|
||||
late T message;
|
||||
|
||||
final BytesBuilder _bodyBuffer = BytesBuilder();
|
||||
|
||||
T createMessage(List<String> reqLine);
|
||||
|
||||
@override
|
||||
T? decode(Uint8List data) {
|
||||
//请求行
|
||||
if (_state == State.readInitial) {
|
||||
var initialLine = _readInitialLine(data);
|
||||
message = createMessage(initialLine);
|
||||
_state = State.readHeader;
|
||||
}
|
||||
|
||||
//请求头
|
||||
if (_state == State.readHeader) {
|
||||
_readHeader(data, message);
|
||||
_state = message.headers.isChunked ? State.readVariableLengthContent : State.readFixedLengthContent;
|
||||
}
|
||||
|
||||
if (message is HttpRequest) {
|
||||
if ([HttpMethod.get, HttpMethod.connect].contains((message as HttpRequest).method)) {
|
||||
_state = State.done;
|
||||
}
|
||||
}
|
||||
//chunked编码
|
||||
if (_state == State.readChunkedContent) {
|
||||
_bodyBuffer.add(data.sublist(0, min(_chunkReadableSize, data.length)));
|
||||
if (data.length < _chunkReadableSize) {
|
||||
_chunkReadableSize = _chunkReadableSize - data.length;
|
||||
return null;
|
||||
}
|
||||
_httpParse.index = _chunkReadableSize + 2;
|
||||
_state = State.readVariableLengthContent; //读取下一个chunk
|
||||
}
|
||||
//请求体
|
||||
if (_state == State.readFixedLengthContent || _state == State.readVariableLengthContent) {
|
||||
_readBody(data, message);
|
||||
if ((message.contentLength == -1 && !message.headers.isChunked) || _bodyBuffer.length == message.contentLength) {
|
||||
_state = State.done;
|
||||
}
|
||||
}
|
||||
|
||||
if (_state == State.done) {
|
||||
message.body = _convertBody();
|
||||
_state = State.readInitial;
|
||||
return message;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void initialLine(BytesBuilder buffer, T message);
|
||||
|
||||
@override
|
||||
List<int> encode(T message) {
|
||||
BytesBuilder builder = BytesBuilder();
|
||||
//请求行
|
||||
initialLine(builder, message);
|
||||
|
||||
if (message.headers.isGzip) {
|
||||
message.body = gzipEncode(message.body!);
|
||||
}
|
||||
|
||||
//请求头
|
||||
message.headers.remove(HttpHeaders.TRANSFER_ENCODING);
|
||||
int contentLength = _contentLength(message);
|
||||
message.headers.contentLength = contentLength;
|
||||
message.headers.forEach((key, value) {
|
||||
builder
|
||||
..add(key.codeUnits)
|
||||
..addByte(HttpConstants.colon)
|
||||
..addByte(HttpConstants.sp)
|
||||
..add(value.codeUnits)
|
||||
..addByte(HttpConstants.cr)
|
||||
..addByte(HttpConstants.lf);
|
||||
});
|
||||
builder.addByte(HttpConstants.cr);
|
||||
builder.addByte(HttpConstants.lf);
|
||||
|
||||
//请求体
|
||||
builder.add(message.body ?? Uint8List(0));
|
||||
return builder.toBytes();
|
||||
}
|
||||
|
||||
int _contentLength(T message) {
|
||||
return message.body?.length ?? 0;
|
||||
}
|
||||
|
||||
//读取起始行
|
||||
List<String> _readInitialLine(Uint8List data) {
|
||||
_httpParse.reset();
|
||||
int maxSize = min(data.length, Codec.defaultMaxInitialLineLength);
|
||||
return _httpParse.parseInitialLine(data, maxSize);
|
||||
}
|
||||
|
||||
//读取请求头
|
||||
void _readHeader(Uint8List data, T message) {
|
||||
_httpParse.parseHeader(data, message.headers);
|
||||
message.contentLength = message.headers.contentLength;
|
||||
}
|
||||
|
||||
//读取请求体
|
||||
void _readBody(Uint8List data, T message) {
|
||||
if (_state == State.readVariableLengthContent) {
|
||||
while (_httpParse.index < data.length) {
|
||||
var parseLine = _httpParse.parseLine(data);
|
||||
if (parseLine.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
int length = hexToInt(String.fromCharCodes(parseLine));
|
||||
//chunked编码结束
|
||||
if (length == 0) {
|
||||
_state = State.done;
|
||||
return;
|
||||
}
|
||||
_bodyBuffer.add(data.sublist(_httpParse.index, min(_httpParse.index + length, data.length)));
|
||||
if (_httpParse.index + length > data.length) {
|
||||
_state = State.readChunkedContent;
|
||||
_chunkReadableSize = length - (data.length - _httpParse.index);
|
||||
}
|
||||
_httpParse.index += length + 2; //跳过\r\n
|
||||
}
|
||||
|
||||
_httpParse.index = 0;
|
||||
} else if (message.contentLength > 0) {
|
||||
_bodyBuffer.add(data.sublist(_httpParse.index));
|
||||
_httpParse.index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//转换body
|
||||
List<int> _convertBody() {
|
||||
List<int> bytes = _bodyBuffer.toBytes();
|
||||
if (message.headers.isGzip) {
|
||||
bytes = gzipDecode(bytes);
|
||||
}
|
||||
_bodyBuffer.clear();
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
/// http请求编解码
|
||||
class HttpRequestCodec extends HttpCodec<HttpRequest> {
|
||||
@override
|
||||
HttpRequest createMessage(List<String> reqLine) {
|
||||
HttpMethod httpMethod = HttpMethod.valueOf(reqLine[0]);
|
||||
return HttpRequest(httpMethod, reqLine[1], reqLine[2]);
|
||||
}
|
||||
|
||||
@override
|
||||
void initialLine(BytesBuilder buffer, HttpRequest message) {
|
||||
//请求行
|
||||
buffer
|
||||
..add(message.method.name.codeUnits)
|
||||
..addByte(HttpConstants.sp)
|
||||
..add(message.uri.codeUnits)
|
||||
..addByte(HttpConstants.sp)
|
||||
..add(message.protocolVersion.codeUnits)
|
||||
..addByte(HttpConstants.cr)
|
||||
..addByte(HttpConstants.lf);
|
||||
}
|
||||
}
|
||||
|
||||
/// http响应编解码
|
||||
class HttpResponseCodec extends HttpCodec<HttpResponse> {
|
||||
@override
|
||||
HttpResponse createMessage(List<String> reqLine) {
|
||||
var httpStatus = HttpStatus(int.parse(reqLine[1]), reqLine[2]);
|
||||
return HttpResponse(reqLine[0], httpStatus);
|
||||
}
|
||||
|
||||
@override
|
||||
void initialLine(BytesBuilder buffer, HttpResponse message) {
|
||||
//状态行
|
||||
buffer.add(message.protocolVersion.codeUnits);
|
||||
buffer.addByte(HttpConstants.sp);
|
||||
buffer.add(message.status.code.toString().codeUnits);
|
||||
buffer.addByte(HttpConstants.sp);
|
||||
buffer.add(message.status.reasonPhrase.codeUnits);
|
||||
buffer.addByte(HttpConstants.cr);
|
||||
buffer.addByte(HttpConstants.lf);
|
||||
}
|
||||
}
|
||||
|
||||
/// http解析器
|
||||
class HttpParse {
|
||||
int index = 0;
|
||||
|
||||
/// 解析请求行
|
||||
List<String> parseInitialLine(Uint8List data, int size) {
|
||||
List<String> initialLine = [];
|
||||
for (int i = index; i < size; i++) {
|
||||
if (_isLineEnd(data, i)) {
|
||||
//请求行结束
|
||||
Uint8List requestLine = data.sublist(index, i - 1);
|
||||
initialLine = _splitLine(requestLine);
|
||||
index = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (initialLine.length != 3) {
|
||||
throw Exception("parseLine error ${String.fromCharCodes(data)}");
|
||||
}
|
||||
|
||||
return initialLine;
|
||||
}
|
||||
|
||||
/// 解析请求头
|
||||
void parseHeader(Uint8List data, HttpHeaders headers) {
|
||||
while (true) {
|
||||
var line = parseLine(data);
|
||||
if (line.isEmpty) {
|
||||
break;
|
||||
}
|
||||
var header = _splitHeader(line);
|
||||
headers.set(header[0], header[1]);
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List parseLine(Uint8List data) {
|
||||
for (int i = index; i < data.length; i++) {
|
||||
if (_isLineEnd(data, i)) {
|
||||
var line = data.sublist(index, i - 1);
|
||||
index = i + 1;
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return Uint8List(0);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
//是否行结束
|
||||
bool _isLineEnd(List<int> data, int index) {
|
||||
return index >= 1 && data[index] == HttpConstants.lf && data[index - 1] == HttpConstants.cr;
|
||||
}
|
||||
|
||||
//分割行
|
||||
List<String> _splitLine(Uint8List data) {
|
||||
List<String> lines = [];
|
||||
int start = 0;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
if (data[i] == HttpConstants.sp) {
|
||||
lines.add(String.fromCharCodes(data.sublist(start, i)));
|
||||
start = i + 1;
|
||||
if (lines.length == 2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
lines.add(String.fromCharCodes(data.sublist(start)));
|
||||
return lines;
|
||||
}
|
||||
|
||||
//分割头
|
||||
List<String> _splitHeader(List<int> data) {
|
||||
List<String> headers = [];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
if (data[i] == HttpConstants.colon && data[i + 1] == HttpConstants.sp) {
|
||||
headers.add(String.fromCharCodes(data.sublist(0, i)));
|
||||
headers.add(String.fromCharCodes(data.sublist(i + 2)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
139
lib/network/http/http.dart
Normal file
139
lib/network/http/http.dart
Normal file
@@ -0,0 +1,139 @@
|
||||
import 'http_headers.dart';
|
||||
|
||||
///定义HTTP消息的接口,为HttpRequest和HttpResponse提供公共属性。
|
||||
abstract class HttpMessage {
|
||||
final String protocolVersion;
|
||||
|
||||
final HttpHeaders headers = HttpHeaders();
|
||||
int contentLength = -1;
|
||||
|
||||
List<int>? body;
|
||||
|
||||
HttpMessage(this.protocolVersion);
|
||||
|
||||
String get bodyAsString {
|
||||
if (body == null) {
|
||||
return "";
|
||||
}
|
||||
return String.fromCharCodes(body!);
|
||||
}
|
||||
}
|
||||
|
||||
///HTTP请求。
|
||||
class HttpRequest extends HttpMessage {
|
||||
final String uri;
|
||||
late HttpMethod method;
|
||||
|
||||
HttpRequest(this.method, this.uri, String protocolVersion)
|
||||
: super(protocolVersion);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HttpReqeust{version: $protocolVersion, url: $uri, method: ${method.name}, headers: $headers, contentLength: $contentLength, bodyLength: ${body?.length}}';
|
||||
}
|
||||
}
|
||||
|
||||
///HTTP响应。
|
||||
class HttpResponse extends HttpMessage {
|
||||
final HttpStatus status;
|
||||
|
||||
HttpResponse(String protocolVersion, this.status) : super(protocolVersion);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HttpResponse{status: ${status.code}, headers: $headers, contentLength: $contentLength, bodyLength: ${body?.length}}';
|
||||
}
|
||||
}
|
||||
|
||||
///HTTP请求方法。
|
||||
enum HttpMethod {
|
||||
options("OPTIONS"),
|
||||
get("GET"),
|
||||
head("HEAD"),
|
||||
post("POST"),
|
||||
put("PUT"),
|
||||
patch("PATCH"),
|
||||
delete("DELETE"),
|
||||
trace("TRACE"),
|
||||
connect("CONNECT");
|
||||
|
||||
final String name;
|
||||
|
||||
const HttpMethod(this.name);
|
||||
|
||||
static HttpMethod valueOf(String name) {
|
||||
return HttpMethod.values
|
||||
.firstWhere((element) => element.name == name.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
///HTTP响应状态。
|
||||
class HttpStatus {
|
||||
/// 200 OK
|
||||
static final HttpStatus ok = newStatus(200, "OK");
|
||||
|
||||
/// 400 Bad Request
|
||||
static final HttpStatus badRequest = newStatus(400, "Bad Request");
|
||||
|
||||
/// 401 Unauthorized
|
||||
static final HttpStatus unauthorized = newStatus(401, "Unauthorized");
|
||||
|
||||
/// 403 Forbidden
|
||||
static final HttpStatus forbidden = newStatus(403, "Forbidden");
|
||||
|
||||
/// 404 Not Found
|
||||
static final HttpStatus notFound = newStatus(404, "Not Found");
|
||||
|
||||
/// 500 Internal Server Error
|
||||
static final HttpStatus internalServerError =
|
||||
newStatus(500, "Internal Server Error");
|
||||
|
||||
/// 502 Bad Gateway
|
||||
static final HttpStatus badGateway = newStatus(502, "Bad Gateway");
|
||||
|
||||
/// 503 Service Unavailable
|
||||
static final HttpStatus serviceUnavailable =
|
||||
newStatus(503, "Service Unavailable");
|
||||
|
||||
/// 504 Gateway Timeout
|
||||
static final HttpStatus gatewayTimeout =
|
||||
newStatus(504, "Gateway Timeout");
|
||||
|
||||
static HttpStatus newStatus(int statusCode, String reasonPhrase) {
|
||||
return HttpStatus(statusCode, reasonPhrase);
|
||||
}
|
||||
|
||||
static HttpStatus? valueOf(int code) {
|
||||
switch (code) {
|
||||
case 200:
|
||||
return ok;
|
||||
case 400:
|
||||
return badRequest;
|
||||
case 401:
|
||||
return unauthorized;
|
||||
case 403:
|
||||
return forbidden;
|
||||
case 404:
|
||||
return notFound;
|
||||
case 500:
|
||||
return internalServerError;
|
||||
case 502:
|
||||
return badGateway;
|
||||
case 503:
|
||||
return serviceUnavailable;
|
||||
case 504:
|
||||
return gatewayTimeout;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final int code;
|
||||
final String reasonPhrase;
|
||||
|
||||
HttpStatus(this.code, this.reasonPhrase);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HttpResponseStatus{code: $code, reasonPhrase: $reasonPhrase}';
|
||||
}
|
||||
}
|
||||
66
lib/network/http/http_headers.dart
Normal file
66
lib/network/http/http_headers.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'dart:collection';
|
||||
|
||||
class HttpHeaders {
|
||||
static const CONTENT_LENGTH = "Content-Length";
|
||||
static const CONTENT_ENCODING = "Content-Encoding";
|
||||
static const CONTENT_TYPE = "Content-Type";
|
||||
static const String HOST = "Host";
|
||||
static const String TRANSFER_ENCODING = "Transfer-Encoding";
|
||||
|
||||
final LinkedHashMap<String, String> _headers = LinkedHashMap<String, String>();
|
||||
|
||||
// 由小写标头名称键入的原始标头名称。
|
||||
final Map<String, String> _originalHeaderNames = {};
|
||||
|
||||
///设置header。
|
||||
void set(String name, String value) {
|
||||
_headers[name.toLowerCase()] = value;
|
||||
_originalHeaderNames[name] = value;
|
||||
}
|
||||
|
||||
String? get(String name) {
|
||||
return _headers[name.toLowerCase()];
|
||||
}
|
||||
|
||||
void remove(String name) {
|
||||
_headers.remove(name.toLowerCase());
|
||||
_originalHeaderNames.remove(name);
|
||||
_originalHeaderNames.remove(name.toLowerCase());
|
||||
}
|
||||
|
||||
int? getInt(String name) {
|
||||
final value = get(name);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return int.parse(value);
|
||||
}
|
||||
|
||||
bool getBool(String name) {
|
||||
final value = get(name);
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
return value.toLowerCase() == "true";
|
||||
}
|
||||
|
||||
int get contentLength => getInt(CONTENT_LENGTH) ?? -1;
|
||||
|
||||
set contentLength(int contentLength) => set(CONTENT_LENGTH, contentLength.toString());
|
||||
|
||||
bool get isGzip => get(HttpHeaders.CONTENT_ENCODING) == "gzip";
|
||||
|
||||
bool get isChunked => get(HttpHeaders.TRANSFER_ENCODING) == "chunked";
|
||||
|
||||
void forEach(void Function(String name, String value) f) {
|
||||
_originalHeaderNames.forEach(f);
|
||||
}
|
||||
|
||||
set contentType(String contentType) => set(CONTENT_TYPE, contentType);
|
||||
String get contentType => get(CONTENT_TYPE) ?? "";
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'HttpHeaders{$_originalHeaderNames}';
|
||||
}
|
||||
}
|
||||
7
lib/network/util/AttributeKeys.dart
Normal file
7
lib/network/util/AttributeKeys.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
/// @author wanghongen
|
||||
/// 2023/5/23
|
||||
interface class AttributeKeys {
|
||||
static String HOST_KEY = "HOST";
|
||||
static String URI_KEY = "URI";
|
||||
static String REQUEST_KEY = "REQUEST";
|
||||
}
|
||||
82
lib/network/util/CertificateManager.dart
Normal file
82
lib/network/util/CertificateManager.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'dart:core';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:basic_utils/basic_utils.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
var securityContext = await CertificateManager.getCertificateContext('www.baidu.com');
|
||||
print(securityContext);
|
||||
// print(CertificateManager.caCert.tbsCertificateSeqAsString);
|
||||
print(CertificateManager._caCert.tbsCertificate?.subject);
|
||||
print(CertificateManager._caCert.tbsCertificateSeqAsString);
|
||||
print(CertificateManager._caCert);
|
||||
}
|
||||
|
||||
class CertificateManager {
|
||||
/// 证书缓存
|
||||
static final Map<String, SecurityContext> _certificateMap = {};
|
||||
|
||||
/// 服务端密钥
|
||||
static final AsymmetricKeyPair _serverKeyPair = CryptoUtils.generateRSAKeyPair();
|
||||
|
||||
/// ca证书
|
||||
static late X509CertificateData _caCert;
|
||||
|
||||
/// ca私钥
|
||||
static late RSAPrivateKey _caPriKey;
|
||||
|
||||
/// 是否初始化
|
||||
static bool _initialized = false;
|
||||
|
||||
/// 获取域名自签名证书
|
||||
static Future<SecurityContext> getCertificateContext(String host) async {
|
||||
if (_certificateMap.containsKey(host)) {
|
||||
return _certificateMap[host]!;
|
||||
}
|
||||
if (!_initialized) {
|
||||
await _initCAConfig();
|
||||
}
|
||||
|
||||
var cer = generate(_serverKeyPair.publicKey as RSAPublicKey, _caPriKey, host);
|
||||
var rsaPrivateKey = _serverKeyPair.privateKey as RSAPrivateKey;
|
||||
var securityContext = SecurityContext.defaultContext
|
||||
..useCertificateChainBytes(cer.codeUnits)
|
||||
..usePrivateKeyBytes(CryptoUtils.encodeRSAPrivateKeyToPemPkcs1(rsaPrivateKey).codeUnits);
|
||||
_certificateMap[host] = securityContext;
|
||||
return securityContext;
|
||||
}
|
||||
|
||||
/// 生成证书
|
||||
static String generate(PublicKey serverPubKey, RSAPrivateKey caPriKey, String host) {
|
||||
//根据CA证书subject来动态生成目标服务器证书的issuer和subject
|
||||
Map<String, String> x509Subject = {
|
||||
'C': 'CN',
|
||||
'ST': 'BJ',
|
||||
'L': 'BJ',
|
||||
'O': 'network',
|
||||
'OU': 'Proxy',
|
||||
};
|
||||
x509Subject['CN'] = host;
|
||||
var csr = X509Utils.generateRsaCsrPem(x509Subject, caPriKey, serverPubKey as RSAPublicKey, san: [host]);
|
||||
|
||||
Map<String, String> issuer = Map.from(_caCert.tbsCertificate!.subject);
|
||||
var csrPem = X509Utils.generateSelfSignedCertificate(caPriKey, csr, 3650, sans: [host], issuer: issuer);
|
||||
return csrPem;
|
||||
}
|
||||
|
||||
static Future<void> _initCAConfig() async {
|
||||
if (_initialized) {
|
||||
return;
|
||||
}
|
||||
//从项目目录加入ca根证书
|
||||
var caPem = await File('assets/certs/ca.crt').readAsString();
|
||||
_caCert = X509Utils.x509CertificateFromPem(caPem);
|
||||
//根据CA证书subject来动态生成目标服务器证书的issuer和subject
|
||||
|
||||
//从项目目录加入ca私钥
|
||||
var privateBytes = await File('assets/certs/ca_private.der').readAsBytes();
|
||||
_caPriKey = CryptoUtils.rsaPrivateKeyFromDERBytes(privateBytes);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
53
lib/network/util/HostFilter.dart
Normal file
53
lib/network/util/HostFilter.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'dart:collection';
|
||||
|
||||
void main() {
|
||||
print(HostFilter.filter("www.apple.com"));
|
||||
}
|
||||
|
||||
class HostFilter {
|
||||
/// 白名单
|
||||
static final Set<RegExp> _whitelist = buildWhitelist();
|
||||
|
||||
/// 黑名单
|
||||
static final Set<RegExp> _blacklist = buildBlacks();
|
||||
|
||||
/// 构建白名单
|
||||
static Set<RegExp> buildWhitelist() {
|
||||
List<String> whites = [];
|
||||
// whites.add("*.google.com");
|
||||
// whites.add("www.baidu.com");
|
||||
|
||||
Set<RegExp> whitelist = HashSet<RegExp>();
|
||||
for (var white in whites) {
|
||||
whitelist.add(RegExp(white));
|
||||
}
|
||||
|
||||
return whitelist;
|
||||
}
|
||||
|
||||
/// 构建黑名单
|
||||
static Set<RegExp> buildBlacks() {
|
||||
List<String> blacks = [];
|
||||
blacks.add(r"*.google.*");
|
||||
blacks.add(r"*\.github\.com");
|
||||
blacks.add(r"*.apple.*");
|
||||
blacks.add(r"*.qq.com");
|
||||
// blacks.add(r"www.baidu.com");
|
||||
|
||||
Set<RegExp> blacklist = HashSet<RegExp>();
|
||||
for (var black in blacks) {
|
||||
blacklist.add(RegExp(black.replaceAll("*", ".*")));
|
||||
}
|
||||
|
||||
return blacklist;
|
||||
}
|
||||
|
||||
/// 是否过滤
|
||||
static bool filter(String host) {
|
||||
//如果白名单不为空,不在白名单里都是黑名单
|
||||
if (_whitelist.isNotEmpty) {
|
||||
return _whitelist.any((element) => !element.hasMatch(host));
|
||||
}
|
||||
return _blacklist.any((element) => element.hasMatch(host));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user