mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-04-24 22:19:52 +08:00
http2协议
This commit is contained in:
@@ -51,6 +51,8 @@ class Configuration {
|
||||
//远程连接 不持久化保存
|
||||
String? remoteHost;
|
||||
|
||||
bool enabledHttp2 = false; //
|
||||
|
||||
Configuration._();
|
||||
|
||||
/// 单例
|
||||
|
||||
@@ -66,11 +66,10 @@ class ProxyServer {
|
||||
|
||||
/// 启动代理服务
|
||||
Future<Server> start() async {
|
||||
Server server = Server(configuration);
|
||||
Server server = Server(configuration, listener: CombinedEventListener(listeners));
|
||||
var requestRewrites = await RequestRewrites.instance;
|
||||
|
||||
server.initChannel((channel) {
|
||||
channel.pipeline.listener = CombinedEventListener(listeners);
|
||||
channel.pipeline.handle(HttpRequestCodec(), HttpResponseCodec(),
|
||||
HttpProxyChannelHandler(listener: CombinedEventListener(listeners), requestRewrites: requestRewrites));
|
||||
});
|
||||
@@ -138,9 +137,9 @@ class CombinedEventListener extends EventListener {
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(Channel channel, HttpResponse response) {
|
||||
void onResponse(ChannelContext channelContext, HttpResponse response) {
|
||||
for (var element in listeners) {
|
||||
element.onResponse(channel, response);
|
||||
element.onResponse(channelContext, response);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,13 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:network_proxy/network/host_port.dart';
|
||||
import 'package:network_proxy/network/http/codec.dart';
|
||||
import 'package:network_proxy/network/http/h2/setting.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
import 'package:network_proxy/network/http_client.dart';
|
||||
import 'package:network_proxy/network/util/attribute_keys.dart';
|
||||
import 'package:network_proxy/network/util/byte_buf.dart';
|
||||
import 'package:network_proxy/network/util/logger.dart';
|
||||
import 'package:network_proxy/utils/lang.dart';
|
||||
|
||||
import 'handler.dart';
|
||||
|
||||
@@ -33,19 +36,19 @@ abstract class ChannelHandler<T> {
|
||||
var log = logger;
|
||||
|
||||
///连接建立
|
||||
void channelActive(Channel channel) {}
|
||||
void channelActive(ChannelContext context, Channel channel) {}
|
||||
|
||||
///读取数据事件
|
||||
void channelRead(Channel channel, T msg) {}
|
||||
void channelRead(ChannelContext channelContext, Channel channel, T msg) {}
|
||||
|
||||
///连接断开
|
||||
void channelInactive(Channel channel) {
|
||||
void channelInactive(ChannelContext channelContext, Channel channel) {
|
||||
// log.i("close $channel");
|
||||
}
|
||||
|
||||
void exceptionCaught(Channel channel, dynamic error, {StackTrace? trace}) {
|
||||
HostAndPort? attribute = channel.getAttribute(AttributeKeys.host);
|
||||
log.e("[${channel.id}] error $attribute $channel", error: error, stackTrace: trace);
|
||||
void exceptionCaught(ChannelContext channelContext, Channel channel, dynamic error, {StackTrace? trace}) {
|
||||
HostAndPort? host = channelContext.host;
|
||||
log.e("[${channel.id}] error $host $channel", error: error, stackTrace: trace);
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
@@ -55,7 +58,6 @@ class Channel {
|
||||
final int _id;
|
||||
final ChannelPipeline pipeline = ChannelPipeline();
|
||||
Socket _socket;
|
||||
final Map<String, Object> _attributes = {};
|
||||
|
||||
//是否打开
|
||||
bool isOpen = true;
|
||||
@@ -79,9 +81,10 @@ class Channel {
|
||||
|
||||
Socket get socket => _socket;
|
||||
|
||||
set secureSocket(SecureSocket secureSocket) {
|
||||
secureSocket(SecureSocket secureSocket, ChannelContext channelContext) {
|
||||
_socket = secureSocket;
|
||||
pipeline.listen(this);
|
||||
_socket.done.then((value) => isOpen = false);
|
||||
pipeline.listen(this, channelContext);
|
||||
}
|
||||
|
||||
String? get selectedProtocol => isSsl ? (_socket as SecureSocket).selectedProtocol : null;
|
||||
@@ -90,6 +93,11 @@ class Channel {
|
||||
bool get isSsl => _socket is SecureSocket;
|
||||
|
||||
Future<void> write(Object obj) async {
|
||||
var data = pipeline._encoder.encode(obj);
|
||||
await writeBytes(data);
|
||||
}
|
||||
|
||||
Future<void> writeBytes(List<int> bytes) async {
|
||||
if (isClosed) {
|
||||
logger.w("[$id] channel is closed");
|
||||
return;
|
||||
@@ -103,15 +111,16 @@ class Channel {
|
||||
|
||||
isWriting = true;
|
||||
try {
|
||||
var data = pipeline._encoder.encode(obj);
|
||||
if (!isClosed) {
|
||||
_socket.add(data);
|
||||
_socket.add(bytes);
|
||||
}
|
||||
await _socket.flush();
|
||||
} catch (e, t) {
|
||||
// print(getAttribute(id)._attributes);
|
||||
print(e);
|
||||
print(t);
|
||||
if (e is StateError && e.message == "StreamSink is closed") {
|
||||
isOpen = false;
|
||||
} else {
|
||||
logger.e("[$id] write error", error: e, stackTrace: t);
|
||||
}
|
||||
} finally {
|
||||
isWriting = false;
|
||||
}
|
||||
@@ -141,6 +150,37 @@ class Channel {
|
||||
///返回此channel是否打开
|
||||
bool get isClosed => !isOpen;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Channel($id ${remoteAddress.host}:$remotePort)';
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
class ChannelContext {
|
||||
final Map<String, Object> _attributes = {};
|
||||
|
||||
//和本地客户端的连接
|
||||
Channel? clientChannel;
|
||||
|
||||
//和远程服务端的连接
|
||||
Channel? serverChannel;
|
||||
|
||||
EventListener? listener;
|
||||
|
||||
//http2 stream
|
||||
final Map<int, Pair<HttpRequest, ValueWrap<HttpResponse>>> _streams = {};
|
||||
|
||||
ChannelContext();
|
||||
|
||||
//创建服务端连接
|
||||
Future<Channel> connectServerChannel(HostAndPort hostAndPort, ChannelHandler channelHandler) async {
|
||||
serverChannel = await HttpClients.startConnect(hostAndPort, channelHandler, this);
|
||||
putAttribute(clientChannel!.id, serverChannel);
|
||||
putAttribute(serverChannel!.id, clientChannel);
|
||||
return serverChannel!;
|
||||
}
|
||||
|
||||
T? getAttribute<T>(String key) {
|
||||
if (!_attributes.containsKey(key)) {
|
||||
return null;
|
||||
@@ -156,9 +196,39 @@ class Channel {
|
||||
_attributes[key] = value;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Channel($id ${remoteAddress.host}:$remotePort)';
|
||||
HostAndPort? get host => getAttribute(AttributeKeys.host);
|
||||
|
||||
set host(HostAndPort? host) => putAttribute(AttributeKeys.host, host);
|
||||
|
||||
HttpRequest? get currentRequest => getAttribute(AttributeKeys.request);
|
||||
|
||||
set currentRequest(HttpRequest? request) => putAttribute(AttributeKeys.request, request);
|
||||
|
||||
StreamSetting? setting;
|
||||
|
||||
HttpRequest? putStreamRequest(int streamId, HttpRequest request) {
|
||||
var old = _streams[streamId]?.key;
|
||||
_streams[streamId] = Pair(request, ValueWrap());
|
||||
return old;
|
||||
}
|
||||
|
||||
void putStreamResponse(int streamId, HttpResponse response) {
|
||||
var stream = _streams[streamId]!;
|
||||
stream.key.response = response;
|
||||
response.request = stream.key;
|
||||
stream.value.set(response);
|
||||
}
|
||||
|
||||
HttpRequest? getStreamRequest(int streamId) {
|
||||
return _streams[streamId]?.key;
|
||||
}
|
||||
|
||||
HttpResponse? getStreamResponse(int streamId) {
|
||||
return _streams[streamId]?.value.get();
|
||||
}
|
||||
|
||||
void removeStream(int streamId) {
|
||||
_streams.remove(streamId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +236,6 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
|
||||
late Decoder _decoder;
|
||||
late Encoder _encoder;
|
||||
late ChannelHandler handler;
|
||||
EventListener? listener;
|
||||
|
||||
final ByteBuf buffer = ByteBuf();
|
||||
|
||||
@@ -177,17 +246,16 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
|
||||
}
|
||||
|
||||
/// 监听
|
||||
void listen(Channel channel) {
|
||||
void listen(Channel channel, ChannelContext channelContext) {
|
||||
buffer.clear();
|
||||
|
||||
channel.socket.listen((data) => channel.pipeline.channelRead(channel, data),
|
||||
onError: (error, trace) => channel.pipeline.exceptionCaught(channel, error, trace: trace),
|
||||
onDone: () => channel.pipeline.channelInactive(channel));
|
||||
channel.socket.listen((data) => channel.pipeline.channelRead(channelContext, channel, data),
|
||||
onError: (error, trace) => channel.pipeline.exceptionCaught(channelContext, channel, error, trace: trace),
|
||||
onDone: () => channel.pipeline.channelInactive(channelContext, channel));
|
||||
}
|
||||
|
||||
@override
|
||||
void channelActive(Channel channel) {
|
||||
handler.channelActive(channel);
|
||||
void channelActive(ChannelContext context, Channel channel) {
|
||||
handler.channelActive(context, channel);
|
||||
}
|
||||
|
||||
/// 转发请求
|
||||
@@ -198,101 +266,117 @@ class ChannelPipeline extends ChannelHandler<Uint8List> {
|
||||
}
|
||||
|
||||
///远程转发请求
|
||||
remoteForward(Channel clientChannel, HostAndPort remote, Uint8List msg) async {
|
||||
Channel? remoteChannel = clientChannel.getAttribute(clientChannel.id);
|
||||
remoteChannel = remoteChannel ?? await HttpClients.startConnect(remote, RelayHandler(clientChannel));
|
||||
remoteForward(ChannelContext channelContext, HostAndPort remote, Uint8List msg) async {
|
||||
var clientChannel = channelContext.clientChannel!;
|
||||
Channel? remoteChannel =
|
||||
channelContext.serverChannel ?? await channelContext.connectServerChannel(remote, RelayHandler(clientChannel));
|
||||
if (clientChannel.isSsl && !remoteChannel.isSsl) {
|
||||
remoteChannel.secureSocket = await SecureSocket.secure(remoteChannel.socket,
|
||||
host: clientChannel.getAttribute(AttributeKeys.domain), onBadCertificate: (certificate) => true);
|
||||
SecureSocket secureSocket = await SecureSocket.secure(remoteChannel.socket,
|
||||
host: channelContext.getAttribute(AttributeKeys.domain), onBadCertificate: (certificate) => true);
|
||||
remoteChannel.secureSocket(secureSocket, channelContext);
|
||||
}
|
||||
|
||||
relay(clientChannel, remoteChannel);
|
||||
handler.channelRead(clientChannel, msg);
|
||||
handler.channelRead(channelContext, clientChannel, msg);
|
||||
}
|
||||
|
||||
@override
|
||||
void channelRead(Channel channel, Uint8List msg) async {
|
||||
void channelRead(ChannelContext channelContext, Channel channel, Uint8List msg) async {
|
||||
try {
|
||||
//手机扫码连接转发远程
|
||||
HostAndPort? remote = channel.getAttribute(AttributeKeys.remote);
|
||||
Channel? remoteChannel = channel.getAttribute(channel.id);
|
||||
HostAndPort? remote = channelContext.getAttribute(AttributeKeys.remote);
|
||||
if (remote != null) {
|
||||
remoteForward(channel, remote, msg);
|
||||
remoteForward(channelContext, remote, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.add(msg);
|
||||
|
||||
Channel? remoteChannel = channelContext.getAttribute(channel.id);
|
||||
|
||||
//大body 不解析直接转发
|
||||
if (buffer.length > Codec.maxBodyLength) {
|
||||
relay(channel, remoteChannel!);
|
||||
handler.channelRead(channel, buffer.buffer);
|
||||
handler.channelRead(channelContext, channel, buffer.bytes);
|
||||
buffer.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
HttpRequest? request = remoteChannel?.getAttribute(AttributeKeys.request);
|
||||
var data = _decoder.decode(buffer, resolveBody: request?.method != HttpMethod.head);
|
||||
if (data == null) {
|
||||
var decodeResult = _decoder.decode(channelContext, buffer);
|
||||
if (!decodeResult.isDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (decodeResult.forward != null) {
|
||||
if (remoteChannel != null) {
|
||||
await remoteChannel.writeBytes(decodeResult.forward!);
|
||||
} else {
|
||||
logger.w("[$channel] forward remoteChannel is null");
|
||||
}
|
||||
buffer.clearRead();
|
||||
return;
|
||||
}
|
||||
|
||||
var length = buffer.length;
|
||||
buffer.clear();
|
||||
buffer.clearRead();
|
||||
|
||||
var data = decodeResult.data;
|
||||
if (data is HttpRequest) {
|
||||
data.packageSize = length;
|
||||
data.hostAndPort = channel.getAttribute(AttributeKeys.host) ?? getHostAndPort(data, ssl: channel.isSsl);
|
||||
channelContext.currentRequest = data;
|
||||
data.hostAndPort = channelContext.host ?? getHostAndPort(data, ssl: channel.isSsl);
|
||||
if (data.headers.host != null && data.headers.host?.contains(":") == false) {
|
||||
data.hostAndPort?.host = data.headers.host!;
|
||||
}
|
||||
}
|
||||
|
||||
if (data is HttpResponse) {
|
||||
data.requestId = channelContext.currentRequest?.requestId ?? data.requestId;
|
||||
data.packageSize = length;
|
||||
data.remoteAddress = '${channel.remoteAddress.host}:${channel.remotePort}';
|
||||
data.request = request;
|
||||
request?.response = data;
|
||||
data.request ??= channelContext.currentRequest;
|
||||
channelContext.currentRequest?.response = data;
|
||||
}
|
||||
|
||||
//websocket协议
|
||||
if (data is HttpResponse && data.isWebSocket && remoteChannel != null) {
|
||||
request?.hostAndPort?.scheme = channel.isSsl ? HostAndPort.wssScheme : HostAndPort.wsScheme;
|
||||
channelContext.currentRequest?.hostAndPort?.scheme =
|
||||
channel.isSsl ? HostAndPort.wssScheme : HostAndPort.wsScheme;
|
||||
logger.d("webSocket ${data.request?.hostAndPort}");
|
||||
remoteChannel.write(data);
|
||||
|
||||
var rawCodec = RawCodec();
|
||||
channel.pipeline.handle(rawCodec, rawCodec, WebSocketChannelHandler(remoteChannel, data, listener: listener));
|
||||
remoteChannel.pipeline
|
||||
.handle(rawCodec, rawCodec, WebSocketChannelHandler(channel, data.request!, listener: listener));
|
||||
channel.pipeline.handle(rawCodec, rawCodec, WebSocketChannelHandler(remoteChannel, data));
|
||||
remoteChannel.pipeline.handle(rawCodec, rawCodec, WebSocketChannelHandler(channel, data.request!));
|
||||
return;
|
||||
}
|
||||
|
||||
handler.channelRead(channel, data!);
|
||||
handler.channelRead(channelContext, channel, data!);
|
||||
} catch (error, trace) {
|
||||
buffer.clear();
|
||||
exceptionCaught(channel, error, trace: trace);
|
||||
exceptionCaught(channelContext, channel, error, trace: trace);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
exceptionCaught(Channel channel, dynamic error, {StackTrace? trace}) {
|
||||
handler.exceptionCaught(channel, error, trace: trace);
|
||||
exceptionCaught(ChannelContext channelContext, Channel channel, dynamic error, {StackTrace? trace}) {
|
||||
handler.exceptionCaught(channelContext, channel, error, trace: trace);
|
||||
}
|
||||
|
||||
@override
|
||||
channelInactive(Channel channel) {
|
||||
handler.channelInactive(channel);
|
||||
channelInactive(ChannelContext channelContext, Channel channel) {
|
||||
handler.channelInactive(channelContext, channel);
|
||||
}
|
||||
}
|
||||
|
||||
class RawCodec extends Codec<Object> {
|
||||
class RawCodec extends Codec<dynamic> {
|
||||
@override
|
||||
Object? decode(ByteBuf data, {bool resolveBody = true}) {
|
||||
return data.readBytes(data.readableBytes());
|
||||
DecoderResult<dynamic> decode(ChannelContext channelContext, ByteBuf byteBuf, {bool resolveBody = true}) {
|
||||
var decoderResult = DecoderResult()..data = byteBuf.readAvailableBytes();
|
||||
return decoderResult;
|
||||
}
|
||||
|
||||
@override
|
||||
List<int> encode(Object data) {
|
||||
List<int> encode(dynamic data) {
|
||||
return data as List<int>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +340,7 @@ class RequestRewrites {
|
||||
if (rewriteRule.type == RuleType.responseReplace) {
|
||||
var rewriteItems = await getRewriteItems(rewriteRule);
|
||||
rewriteItems.where((item) => item.enabled).forEach((item) => _replaceResponse(response, item));
|
||||
logger.d('rewrite response $response');
|
||||
// logger.d('rewrite response $response');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -359,6 +359,8 @@ class RequestRewrites {
|
||||
}
|
||||
return item.value ?? '';
|
||||
}));
|
||||
|
||||
message.headers.contentLength = message.body!.length;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -408,6 +410,7 @@ class RequestRewrites {
|
||||
if (item.body != null &&
|
||||
(item.type == RewriteType.replaceResponseBody || item.type == RewriteType.replaceRequestBody)) {
|
||||
message.body = utf8.encode(item.body!);
|
||||
message.headers.contentLength = message.body!.length;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import 'http_client.dart';
|
||||
abstract class EventListener {
|
||||
void onRequest(Channel channel, HttpRequest request);
|
||||
|
||||
void onResponse(Channel channel, HttpResponse response);
|
||||
void onResponse(ChannelContext channelContext, HttpResponse response);
|
||||
|
||||
void onMessage(Channel channel, HttpMessage message, WebSocketFrame frame) {}
|
||||
}
|
||||
@@ -50,8 +50,7 @@ class HttpProxyChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
HttpProxyChannelHandler({this.listener, this.requestRewrites});
|
||||
|
||||
@override
|
||||
void channelRead(Channel channel, HttpRequest msg) async {
|
||||
channel.putAttribute(AttributeKeys.request, msg);
|
||||
void channelRead(ChannelContext channelContext, Channel channel, HttpRequest msg) async {
|
||||
//下载证书
|
||||
if (msg.uri == 'http://proxy.pin/ssl' || msg.requestUrl == 'http://127.0.0.1:${channel.socket.port}/ssl') {
|
||||
ProxyHelper.crtDownload(channel, msg);
|
||||
@@ -64,37 +63,36 @@ class HttpProxyChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
}
|
||||
|
||||
//代理转发请求
|
||||
forward(channel, msg).catchError((error, trace) {
|
||||
exceptionCaught(channel, error, trace: trace);
|
||||
forward(channelContext, channel, msg).catchError((error, trace) {
|
||||
exceptionCaught(channelContext, channel, error, trace: trace);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void exceptionCaught(Channel channel, error, {StackTrace? trace}) {
|
||||
super.exceptionCaught(channel, error, trace: trace);
|
||||
ProxyHelper.exceptionHandler(channel, listener, channel.getAttribute(AttributeKeys.request), error);
|
||||
void exceptionCaught(ChannelContext channelContext, Channel channel, error, {StackTrace? trace}) {
|
||||
super.exceptionCaught(channelContext, channel, error, trace: trace);
|
||||
ProxyHelper.exceptionHandler(channelContext, channel, listener, channelContext.currentRequest, error);
|
||||
}
|
||||
|
||||
@override
|
||||
void channelInactive(Channel channel) {
|
||||
Channel? remoteChannel = channel.getAttribute(channel.id);
|
||||
void channelInactive(ChannelContext channelContext, Channel channel) {
|
||||
Channel? remoteChannel = channelContext.serverChannel;
|
||||
remoteChannel?.close();
|
||||
// log.i("[${channel.id}] close ${channel.error}");
|
||||
// log.d("[${channel.id}] close ${channel.error}");
|
||||
}
|
||||
|
||||
/// 转发请求
|
||||
Future<void> forward(Channel channel, HttpRequest httpRequest) async {
|
||||
// log.i("[${channel.id}] ${httpRequest.method.name} ${httpRequest.requestUrl}");
|
||||
Future<void> forward(ChannelContext channelContext, Channel channel, HttpRequest httpRequest) async {
|
||||
// log.d("[${channel.id}] ${httpRequest.method.name} ${httpRequest.requestUrl}");
|
||||
if (channel.error != null) {
|
||||
ProxyHelper.exceptionHandler(channel, listener, httpRequest, channel.error);
|
||||
ProxyHelper.exceptionHandler(channelContext, channel, listener, httpRequest, channel.error);
|
||||
return;
|
||||
}
|
||||
|
||||
//获取远程连接
|
||||
Channel remoteChannel;
|
||||
try {
|
||||
remoteChannel = await _getRemoteChannel(channel, httpRequest);
|
||||
remoteChannel.putAttribute(remoteChannel.id, channel);
|
||||
remoteChannel = await _getRemoteChannel(channelContext, channel, httpRequest);
|
||||
} catch (error) {
|
||||
channel.error = error; //记录异常
|
||||
//https代理新建连接请求
|
||||
@@ -131,7 +129,7 @@ class HttpProxyChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
var uri = '${httpRequest.remoteDomain()}${httpRequest.path()}';
|
||||
String? redirectUrl = await requestRewrites?.getRedirectRule(uri);
|
||||
if (redirectUrl?.isNotEmpty == true) {
|
||||
await redirect(channel, httpRequest, redirectUrl!);
|
||||
await redirect(channelContext, channel, httpRequest, redirectUrl!);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -140,43 +138,45 @@ class HttpProxyChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
}
|
||||
|
||||
//重定向
|
||||
Future<void> redirect(Channel channel, HttpRequest httpRequest, String redirectUrl) async {
|
||||
Future<void> redirect(
|
||||
ChannelContext channelContext, Channel channel, HttpRequest httpRequest, String redirectUrl) async {
|
||||
var proxyHandler = HttpResponseProxyHandler(channel, listener: listener, requestRewrites: requestRewrites);
|
||||
|
||||
var redirectUri = UriBuild.build(redirectUrl, params: httpRequest.queries);
|
||||
httpRequest.uri = redirectUri.toString();
|
||||
httpRequest.headers.host = redirectUri.host;
|
||||
var redirectChannel = await HttpClients.connect(Uri.parse(redirectUrl), proxyHandler);
|
||||
var redirectChannel = await HttpClients.connect(Uri.parse(redirectUrl), proxyHandler, channelContext);
|
||||
channelContext.serverChannel = redirectChannel;
|
||||
await redirectChannel.write(httpRequest);
|
||||
}
|
||||
|
||||
/// 获取远程连接
|
||||
Future<Channel> _getRemoteChannel(Channel clientChannel, HttpRequest httpRequest) async {
|
||||
String clientId = clientChannel.id;
|
||||
Future<Channel> _getRemoteChannel(
|
||||
ChannelContext channelContext, Channel clientChannel, HttpRequest httpRequest) async {
|
||||
//客户端连接 作为缓存
|
||||
Channel? remoteChannel = clientChannel.getAttribute(clientId);
|
||||
Channel? remoteChannel = channelContext.serverChannel;
|
||||
if (remoteChannel != null) {
|
||||
return remoteChannel;
|
||||
}
|
||||
|
||||
var hostAndPort = httpRequest.hostAndPort ?? getHostAndPort(httpRequest);
|
||||
clientChannel.putAttribute(AttributeKeys.host, hostAndPort);
|
||||
channelContext.host = hostAndPort;
|
||||
|
||||
//远程转发
|
||||
HostAndPort? remote = clientChannel.getAttribute(AttributeKeys.remote);
|
||||
HostAndPort? remote = channelContext.getAttribute(AttributeKeys.remote);
|
||||
//外部代理
|
||||
ProxyInfo? proxyInfo = clientChannel.getAttribute(AttributeKeys.proxyInfo);
|
||||
ProxyInfo? proxyInfo = channelContext.getAttribute(AttributeKeys.proxyInfo);
|
||||
|
||||
if (remote != null || proxyInfo != null) {
|
||||
HostAndPort connectHost = remote ?? HostAndPort.host(proxyInfo!.host, proxyInfo.port!);
|
||||
var proxyChannel = await connectRemote(clientChannel, connectHost);
|
||||
var proxyChannel = await connectRemote(channelContext, clientChannel, connectHost);
|
||||
if (httpRequest.method == HttpMethod.connect) {
|
||||
proxyChannel.write(httpRequest);
|
||||
}
|
||||
return proxyChannel;
|
||||
}
|
||||
|
||||
var proxyChannel = await connectRemote(clientChannel, hostAndPort);
|
||||
var proxyChannel = await connectRemote(channelContext, clientChannel, hostAndPort);
|
||||
//https代理新建连接请求
|
||||
if (httpRequest.method == HttpMethod.connect) {
|
||||
await clientChannel.write(
|
||||
@@ -186,16 +186,14 @@ class HttpProxyChannelHandler extends ChannelHandler<HttpRequest> {
|
||||
}
|
||||
|
||||
/// 连接远程
|
||||
Future<Channel> connectRemote(Channel clientChannel, HostAndPort connectHost) async {
|
||||
Future<Channel> connectRemote(ChannelContext channelContext, Channel clientChannel, HostAndPort connectHost) async {
|
||||
var proxyHandler = HttpResponseProxyHandler(clientChannel, listener: listener, requestRewrites: requestRewrites);
|
||||
var proxyChannel = await HttpClients.startConnect(connectHost, proxyHandler);
|
||||
proxyChannel.pipeline.listener = listener;
|
||||
String clientId = clientChannel.id;
|
||||
clientChannel.putAttribute(clientId, proxyChannel);
|
||||
var proxyChannel = await channelContext.connectServerChannel(connectHost, proxyHandler);
|
||||
|
||||
if (clientChannel.isSsl) {
|
||||
proxyChannel.secureSocket = await SecureSocket.secure(proxyChannel.socket,
|
||||
SecureSocket secureSocket = await SecureSocket.secure(proxyChannel.socket,
|
||||
host: connectHost.host, onBadCertificate: (certificate) => true);
|
||||
proxyChannel.secureSocket(secureSocket, channelContext);
|
||||
}
|
||||
return proxyChannel;
|
||||
}
|
||||
@@ -212,9 +210,11 @@ class HttpResponseProxyHandler extends ChannelHandler<HttpResponse> {
|
||||
HttpResponseProxyHandler(this.clientChannel, {this.listener, this.requestRewrites});
|
||||
|
||||
@override
|
||||
void channelRead(Channel channel, HttpResponse msg) async {
|
||||
void channelRead(ChannelContext channelContext, Channel channel, HttpResponse msg) async {
|
||||
var request = channelContext.currentRequest;
|
||||
|
||||
//域名是否过滤
|
||||
if (HostFilter.filter(msg.request?.hostAndPort?.host) || msg.request?.method == HttpMethod.connect) {
|
||||
if (HostFilter.filter(request?.hostAndPort?.host) || request?.method == HttpMethod.connect) {
|
||||
await clientChannel.write(msg);
|
||||
return;
|
||||
}
|
||||
@@ -237,13 +237,13 @@ class HttpResponseProxyHandler extends ChannelHandler<HttpResponse> {
|
||||
//重写响应
|
||||
await requestRewrites?.responseRewrite(msg.request?.requestUrl, msg);
|
||||
|
||||
listener?.onResponse(clientChannel, msg);
|
||||
listener?.onResponse(channelContext, msg);
|
||||
//发送给客户端
|
||||
await clientChannel.write(msg);
|
||||
}
|
||||
|
||||
@override
|
||||
void channelInactive(Channel channel) {
|
||||
void channelInactive(ChannelContext channelContext, Channel channel) {
|
||||
clientChannel.close();
|
||||
}
|
||||
}
|
||||
@@ -254,29 +254,28 @@ class RelayHandler extends ChannelHandler<Object> {
|
||||
RelayHandler(this.remoteChannel);
|
||||
|
||||
@override
|
||||
void channelRead(Channel channel, Object msg) async {
|
||||
void channelRead(ChannelContext channelContext, Channel channel, Object msg) async {
|
||||
//发送给客户端
|
||||
remoteChannel.write(msg);
|
||||
}
|
||||
|
||||
@override
|
||||
void channelInactive(Channel channel) {
|
||||
void channelInactive(ChannelContext channelContext, Channel channel) {
|
||||
remoteChannel.close();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
/// websocket处理器
|
||||
class WebSocketChannelHandler extends ChannelHandler<Uint8List> {
|
||||
final WebSocketDecoder decoder = WebSocketDecoder();
|
||||
|
||||
final Channel proxyChannel;
|
||||
final HttpMessage message;
|
||||
EventListener? listener;
|
||||
|
||||
WebSocketChannelHandler(this.proxyChannel, this.message, {this.listener});
|
||||
WebSocketChannelHandler(this.proxyChannel, this.message);
|
||||
|
||||
@override
|
||||
void channelRead(Channel channel, Uint8List msg) {
|
||||
void channelRead(ChannelContext channelContext, Channel channel, Uint8List msg) {
|
||||
proxyChannel.write(msg);
|
||||
|
||||
var frame = decoder.decode(msg);
|
||||
@@ -286,7 +285,7 @@ class WebSocketChannelHandler extends ChannelHandler<Uint8List> {
|
||||
frame.isFromClient = message is HttpRequest;
|
||||
|
||||
message.messages.add(frame);
|
||||
listener?.onMessage(channel, message, frame);
|
||||
channelContext.listener?.onMessage(channel, message, frame);
|
||||
logger.d("socket channelRead ${frame.payloadLength} ${frame.fin} ${frame.payloadDataAsString}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:network_proxy/network/http/constants.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
|
||||
import '../../utils/num.dart';
|
||||
|
||||
@@ -14,29 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:network_proxy/network/channel.dart';
|
||||
import 'package:network_proxy/network/http/body_reader.dart';
|
||||
import 'package:network_proxy/network/http/constants.dart';
|
||||
import 'package:network_proxy/network/http/h2/codec.dart';
|
||||
import 'package:network_proxy/network/http/http_parser.dart';
|
||||
import 'package:network_proxy/network/util/byte_buf.dart';
|
||||
|
||||
import '../../utils/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;
|
||||
}
|
||||
|
||||
class ParserException implements Exception {
|
||||
final String message;
|
||||
@@ -57,63 +49,20 @@ enum State {
|
||||
done,
|
||||
}
|
||||
|
||||
///类似于netty ByteBuf
|
||||
class ByteBuf {
|
||||
final BytesBuilder _buffer = BytesBuilder();
|
||||
class DecoderResult<T> {
|
||||
bool isDone = true;
|
||||
T? data;
|
||||
|
||||
int _readerIndex = 0;
|
||||
//转发消息
|
||||
List<int>? forward;
|
||||
|
||||
Uint8List get buffer => _buffer.toBytes();
|
||||
|
||||
int get length => _buffer.length;
|
||||
|
||||
///添加
|
||||
void add(List<int> bytes) {
|
||||
_buffer.add(bytes);
|
||||
}
|
||||
|
||||
///清空
|
||||
clear() {
|
||||
_buffer.clear();
|
||||
_readerIndex = 0;
|
||||
}
|
||||
|
||||
///读取索引
|
||||
int get readerIndex => _readerIndex;
|
||||
|
||||
bool isReadable() => _readerIndex < _buffer.length;
|
||||
|
||||
///可读字节数
|
||||
int readableBytes() {
|
||||
return _buffer.length - _readerIndex;
|
||||
}
|
||||
|
||||
///读取字节
|
||||
Uint8List readBytes(int length) {
|
||||
Uint8List bytes = buffer.sublist(_readerIndex, _readerIndex + length);
|
||||
_readerIndex += length;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
///跳过
|
||||
skipBytes(int length) {
|
||||
_readerIndex += length;
|
||||
}
|
||||
|
||||
///读取字节
|
||||
int read() {
|
||||
return buffer[_readerIndex++];
|
||||
}
|
||||
|
||||
int get(int index) {
|
||||
return buffer[index];
|
||||
}
|
||||
DecoderResult({this.isDone = true});
|
||||
}
|
||||
|
||||
/// 解码
|
||||
abstract interface class Decoder<T> {
|
||||
/// 解码 如果返回null说明数据不完整
|
||||
T? decode(ByteBuf byteBuf, {bool resolveBody = true});
|
||||
DecoderResult<T> decode(ChannelContext channelContext, ByteBuf byteBuf);
|
||||
}
|
||||
|
||||
/// 编码
|
||||
@@ -130,60 +79,76 @@ abstract class Codec<T> implements Decoder<T>, Encoder<T> {
|
||||
/// http编解码
|
||||
abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
|
||||
final HttpParse _httpParse = HttpParse();
|
||||
Http2Codec<T>? _h2Codec;
|
||||
State _state = State.readInitial;
|
||||
|
||||
late T message;
|
||||
late DecoderResult<T> result;
|
||||
|
||||
BodyReader? bodyReader;
|
||||
|
||||
T createMessage(List<String> reqLine);
|
||||
|
||||
Http2Codec<T> getH2Codec() {
|
||||
return _h2Codec ??= (this is HttpRequestCodec ? Http2RequestDecoder() : Http2ResponseDecoder()) as Http2Codec<T>;
|
||||
}
|
||||
|
||||
@override
|
||||
T? decode(ByteBuf data, {bool resolveBody = true}) {
|
||||
DecoderResult<T> decode(ChannelContext channelContext, ByteBuf data) {
|
||||
if (channelContext.serverChannel?.selectedProtocol == HttpConstants.h2) {
|
||||
return getH2Codec().decode(channelContext, data);
|
||||
}
|
||||
|
||||
//请求行
|
||||
if (_state == State.readInitial) {
|
||||
init();
|
||||
var initialLine = _readInitialLine(data);
|
||||
message = createMessage(initialLine);
|
||||
result.data = createMessage(initialLine);
|
||||
_state = State.readHeader;
|
||||
}
|
||||
|
||||
//请求头
|
||||
try {
|
||||
if (_state == State.readHeader) {
|
||||
_readHeader(data, message);
|
||||
_readHeader(data, result.data!);
|
||||
}
|
||||
|
||||
//请求体
|
||||
if (_state == State.body) {
|
||||
var result = resolveBody ? bodyReader!.readBody(data.readBytes(data.readableBytes())) : null;
|
||||
if (!resolveBody || result?.isDone == true) {
|
||||
bool resolveBody = channelContext.currentRequest?.method != HttpMethod.head;
|
||||
var bodyResult = resolveBody ? bodyReader!.readBody(data.readAvailableBytes()) : null;
|
||||
if (!resolveBody || bodyResult?.isDone == true) {
|
||||
_state = State.done;
|
||||
message.body = result?.body;
|
||||
result.data!.body = bodyResult?.body;
|
||||
}
|
||||
}
|
||||
|
||||
if (_state == State.done) {
|
||||
message.body = _convertBody(message.body);
|
||||
result.data!.body = _convertBody(result.data!.body);
|
||||
_state = State.readInitial;
|
||||
return message;
|
||||
result.isDone = true;
|
||||
return result;
|
||||
}
|
||||
} catch (e) {
|
||||
_state = State.readInitial;
|
||||
rethrow;
|
||||
}
|
||||
|
||||
return null;
|
||||
return result;
|
||||
}
|
||||
|
||||
void init() {
|
||||
bodyReader = null;
|
||||
result = DecoderResult(isDone: false);
|
||||
}
|
||||
|
||||
void initialLine(BytesBuilder buffer, T message);
|
||||
|
||||
@override
|
||||
List<int> encode(T message) {
|
||||
if (message.streamId != null) {
|
||||
return getH2Codec().encode(message);
|
||||
}
|
||||
|
||||
BytesBuilder builder = BytesBuilder();
|
||||
//请求行
|
||||
initialLine(builder, message);
|
||||
@@ -195,10 +160,13 @@ abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
|
||||
|
||||
//请求头
|
||||
message.headers.remove(HttpHeaders.TRANSFER_ENCODING);
|
||||
message.headers.remove(HttpHeaders.CONTENT_LENGTH);
|
||||
|
||||
if (body != null && body.isNotEmpty) {
|
||||
message.headers.contentLength = body.length;
|
||||
} else if (message.contentLength != 0){
|
||||
message.headers.remove(HttpHeaders.CONTENT_LENGTH);
|
||||
}
|
||||
|
||||
message.headers.forEach((key, values) {
|
||||
for (var v in values) {
|
||||
builder
|
||||
@@ -227,7 +195,6 @@ abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
|
||||
//读取请求头
|
||||
void _readHeader(ByteBuf data, T message) {
|
||||
if (_httpParse.parseHeaders(data, message.headers)) {
|
||||
message.contentLength = message.headers.contentLength;
|
||||
_state = State.body;
|
||||
bodyReader = BodyReader(message);
|
||||
}
|
||||
@@ -238,7 +205,7 @@ abstract class HttpCodec<T extends HttpMessage> implements Codec<T> {
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
if (message.headers.isGzip) {
|
||||
if (result.data!.headers.isGzip) {
|
||||
bytes = gzipDecode(bytes);
|
||||
}
|
||||
return bytes;
|
||||
|
||||
16
lib/network/http/constants.dart
Normal file
16
lib/network/http/constants.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
class HttpConstants {
|
||||
//h2协议
|
||||
static const String h2 = 'h2';
|
||||
|
||||
/// 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;
|
||||
}
|
||||
360
lib/network/http/h2/codec.dart
Normal file
360
lib/network/http/h2/codec.dart
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:network_proxy/network/channel.dart';
|
||||
import 'package:network_proxy/network/http/codec.dart';
|
||||
import 'package:network_proxy/network/http/h2/hpack.dart';
|
||||
import 'package:network_proxy/network/http/h2/setting.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
import 'package:network_proxy/network/util/byte_buf.dart';
|
||||
|
||||
import 'frame.dart';
|
||||
|
||||
/// http编解码
|
||||
abstract class Http2Codec<T extends HttpMessage> implements Codec<T> {
|
||||
static const maxFrameSize = 16384;
|
||||
|
||||
static final List<int> connectionPrefacePRI = "PRI * HTTP/2.0".codeUnits;
|
||||
HPACKDecoder decoder = HPACKDecoder();
|
||||
HPACKEncoder encoder = HPACKEncoder();
|
||||
|
||||
T createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map<String, String> headers);
|
||||
|
||||
T? getMessage(ChannelContext channelContext, FrameHeader frameHeader);
|
||||
|
||||
@override
|
||||
DecoderResult<T> decode(ChannelContext channelContext, ByteBuf byteBuf, {bool resolveBody = true}) {
|
||||
//Connection Preface PRI * HTTP/2.0
|
||||
if (byteBuf.get(byteBuf.readerIndex) == 0x50 &&
|
||||
byteBuf.get(byteBuf.readerIndex + 1) == 0x52 &&
|
||||
byteBuf.get(byteBuf.readerIndex + 2) == 0x49 &&
|
||||
isConnectionPrefacePRI(byteBuf)) {
|
||||
DecoderResult<T> result = DecoderResult<T>();
|
||||
result.forward = byteBuf.readAvailableBytes();
|
||||
return result;
|
||||
}
|
||||
|
||||
while (byteBuf.isReadable()) {
|
||||
DecoderResult<T> result = DecoderResult<T>(isDone: false);
|
||||
FrameHeader? frameHeader = FrameReader._readFrameHeader(byteBuf);
|
||||
if (frameHeader == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
List<int>? framePayload = FrameReader._readFramePayload(byteBuf, frameHeader.length);
|
||||
if (framePayload == null) {
|
||||
byteBuf.readerIndex -= FrameReader.headerLength;
|
||||
return result;
|
||||
}
|
||||
|
||||
result = parseHttp2Packet(channelContext, frameHeader, ByteBuf(framePayload));
|
||||
if (result.isDone) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return DecoderResult<T>(isDone: false);
|
||||
}
|
||||
|
||||
DecoderResult<T> parseHttp2Packet(ChannelContext channelContext, FrameHeader frameHeader, ByteBuf framePayload) {
|
||||
var result = DecoderResult<T>();
|
||||
// logger.d("streamId: ${frameHeader.streamIdentifier} ${frameHeader.type} endHeaders: ${frameHeader.hasEndHeadersFlag} "
|
||||
// "endStream: ${frameHeader.hasEndStreamFlag}");
|
||||
//根据帧类型进行处理
|
||||
switch (frameHeader.type) {
|
||||
case FrameType.headers:
|
||||
//处理HEADERS帧
|
||||
_handleHeadersFrame(channelContext, frameHeader, framePayload);
|
||||
result.isDone = frameHeader.hasEndStreamFlag && frameHeader.hasEndHeadersFlag;
|
||||
break;
|
||||
case FrameType.continuation:
|
||||
//处理CONTINUATION帧
|
||||
var message = getMessage(channelContext, frameHeader);
|
||||
if (message == null) {
|
||||
result.forward = List.from(frameHeader.encode())..addAll(framePayload.readAvailableBytes());
|
||||
return result;
|
||||
}
|
||||
|
||||
Map<String, String> headers = _parseHeaders(channelContext, framePayload.readBytes(frameHeader.length));
|
||||
headers.forEach((key, value) => message.headers.add(key, value));
|
||||
|
||||
if (frameHeader.hasEndHeadersFlag &&
|
||||
channelContext.getStreamRequest(frameHeader.streamIdentifier)?.method == HttpMethod.head) {
|
||||
result.isDone = true;
|
||||
}
|
||||
break;
|
||||
case FrameType.data:
|
||||
//处理DATA帧
|
||||
_handleDataFrame(channelContext, frameHeader, framePayload);
|
||||
result.isDone = frameHeader.hasEndStreamFlag;
|
||||
break;
|
||||
case FrameType.settings:
|
||||
SettingHandler.handleSettingsFrame(channelContext, frameHeader, framePayload);
|
||||
result.forward = List.from(frameHeader.encode())..addAll(framePayload.bytes);
|
||||
return result;
|
||||
default:
|
||||
//其他帧类型 原文转发
|
||||
result.forward = List.from(frameHeader.encode())..addAll(framePayload.bytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (result.isDone && frameHeader.streamIdentifier > 0) {
|
||||
result.data = getMessage(channelContext, frameHeader);
|
||||
result.data?.streamId = frameHeader.streamIdentifier;
|
||||
channelContext.currentRequest = channelContext.getStreamRequest(frameHeader.streamIdentifier);
|
||||
|
||||
if (result.data is HttpResponse) {
|
||||
channelContext.removeStream(frameHeader.streamIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
List<Header> encodeHeaders(T message);
|
||||
|
||||
@override
|
||||
Uint8List encode(T data) {
|
||||
var bytesBuilder = BytesBuilder();
|
||||
|
||||
//headers
|
||||
var headers = encodeHeaders(data);
|
||||
BytesBuilder headerBlock = BytesBuilder();
|
||||
bool firstFrame = true;
|
||||
for (var header in headers) {
|
||||
var encode = encoder.encode(header);
|
||||
//防止出现桢分片导致header分裂
|
||||
if (headerBlock.length + encode.length < maxFrameSize) {
|
||||
headerBlock.add(encode);
|
||||
continue;
|
||||
}
|
||||
FrameType frameType = firstFrame ? FrameType.headers : FrameType.continuation;
|
||||
int flags = frameType == FrameType.headers && data.body == null ? FrameHeader.flagsEndStream : 0;
|
||||
firstFrame = false;
|
||||
|
||||
_writeFrame(bytesBuilder, frameType, flags, data.streamId!, headerBlock.takeBytes());
|
||||
headerBlock.add(encode);
|
||||
}
|
||||
|
||||
FrameType frameType = firstFrame ? FrameType.headers : FrameType.continuation;
|
||||
int flags = frameType == FrameType.headers && data.body == null ? FrameHeader.flagsEndStream : 0;
|
||||
flags |= FrameHeader.flagsEndHeaders;
|
||||
|
||||
_writeFrame(bytesBuilder, frameType, flags, data.streamId!, headerBlock.takeBytes());
|
||||
|
||||
//body
|
||||
if (data.body != null) {
|
||||
var payload = data.body!;
|
||||
while (payload.length > maxFrameSize) {
|
||||
var chunkSize = min(maxFrameSize, payload.length);
|
||||
var chunk = payload.sublist(0, chunkSize);
|
||||
payload = payload.sublist(chunkSize);
|
||||
_writeFrame(bytesBuilder, FrameType.data, 0, data.streamId!, chunk);
|
||||
}
|
||||
|
||||
_writeFrame(bytesBuilder, FrameType.data, FrameHeader.flagsEndStream, data.streamId!, payload);
|
||||
}
|
||||
|
||||
return bytesBuilder.takeBytes();
|
||||
}
|
||||
|
||||
void _writeFrame(BytesBuilder bytesBuilder, FrameType type, int flag, int streamId, List<int> payload) {
|
||||
FrameHeader frameHeader = FrameHeader(payload.length, type, flag, streamId);
|
||||
// logger.d("_writeFrame streamId: ${frameHeader.streamIdentifier} ${frameHeader.type} endHeaders: ${frameHeader
|
||||
// .hasEndHeadersFlag} endStream: ${frameHeader.hasEndStreamFlag}");
|
||||
bytesBuilder.add(frameHeader.encode());
|
||||
bytesBuilder.add(payload);
|
||||
}
|
||||
|
||||
bool isConnectionPrefacePRI(ByteBuf data) {
|
||||
if (data.readableBytes() < 9) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < connectionPrefacePRI.length; i++) {
|
||||
if (data.get(data.readerIndex + i) != connectionPrefacePRI[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
DataFrame _handleDataFrame(ChannelContext channelContext, FrameHeader frameHeader, ByteBuf payload) {
|
||||
// DATA 帧格式
|
||||
int padLength = 0;
|
||||
//如果帧头部有PADDED标志位,则需要读取PADDED长度
|
||||
if (frameHeader.hasPaddedFlag) {
|
||||
padLength = payload.readByte();
|
||||
}
|
||||
frameHeader.length;
|
||||
int dataLength = payload.readableBytes() - padLength;
|
||||
var data = payload.readBytes(dataLength);
|
||||
var message = getMessage(channelContext, frameHeader)!;
|
||||
if (message.body == null) {
|
||||
message.body = data;
|
||||
} else {
|
||||
message.body = List.from(message.body!)..addAll(data);
|
||||
}
|
||||
// print("DataFrame ${message.bodyAsString}");
|
||||
return DataFrame(frameHeader, padLength, data);
|
||||
}
|
||||
|
||||
HeadersFrame _handleHeadersFrame(ChannelContext channelContext, FrameHeader frameHeader, ByteBuf payload) {
|
||||
// HEADERS 帧格式
|
||||
int padLength = 0;
|
||||
//如果帧头部有PADDED标志位,则需要读取PADDED长度
|
||||
if (frameHeader.hasPaddedFlag) {
|
||||
padLength = payload.readByte();
|
||||
}
|
||||
|
||||
int? streamDependency;
|
||||
bool exclusiveDependency = false;
|
||||
int? weight;
|
||||
//如果帧头部有PRIORITY标志位,则需要读取优先级信息
|
||||
if (frameHeader.hasPriorityFlag) {
|
||||
//读取优先级信息
|
||||
int dependency = payload.readInt();
|
||||
exclusiveDependency = (dependency & 0x80000000) == 0x80000000;
|
||||
streamDependency = dependency & 0x7fffffff;
|
||||
weight = payload.readByte(); // weight
|
||||
}
|
||||
|
||||
var headerBlockLength = payload.length - payload.readerIndex - padLength;
|
||||
if (headerBlockLength < 0) {
|
||||
throw Exception("headerBlockLength < 0");
|
||||
}
|
||||
|
||||
var blockFragment = payload.readBytes(headerBlockLength);
|
||||
|
||||
//读取头部信息
|
||||
Map<String, String> headers = _parseHeaders(channelContext, blockFragment);
|
||||
|
||||
T message = createMessage(channelContext, frameHeader, headers);
|
||||
|
||||
headers.forEach((key, value) {
|
||||
if (!key.startsWith(":")) {
|
||||
message.headers.add(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
return HeadersFrame(frameHeader, padLength, exclusiveDependency, streamDependency, weight, blockFragment);
|
||||
}
|
||||
|
||||
Map<String, String> _parseHeaders(ChannelContext channelContext, List<int> payload) {
|
||||
if (channelContext.setting != null) {
|
||||
decoder.updateTableSize(channelContext.setting!.headTableSize);
|
||||
}
|
||||
|
||||
// Decode the headers
|
||||
List<Header> headers = decoder.decode(payload);
|
||||
|
||||
// Convert the headers to a map
|
||||
Map<String, String> headerMap = {};
|
||||
for (Header header in headers) {
|
||||
headerMap[header.name] = header.value;
|
||||
}
|
||||
|
||||
return headerMap;
|
||||
}
|
||||
}
|
||||
|
||||
class Http2RequestDecoder extends Http2Codec<HttpRequest> {
|
||||
@override
|
||||
HttpRequest createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map<String, String> headers) {
|
||||
HttpMethod httpMethod = HttpMethod.valueOf(headers[":method"]!);
|
||||
var httpRequest = HttpRequest(httpMethod, headers[":path"]!, protocolVersion: headers[":version"] ?? "HTTP/2");
|
||||
var old = channelContext.putStreamRequest(frameHeader.streamIdentifier, httpRequest);
|
||||
assert(old == null, "old request is not null");
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
@override
|
||||
HttpRequest? getMessage(ChannelContext channelContext, FrameHeader frameHeader) {
|
||||
return channelContext.getStreamRequest(frameHeader.streamIdentifier);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Header> encodeHeaders(HttpRequest message) {
|
||||
var headers = <Header>[];
|
||||
var uri = message.requestUri!;
|
||||
headers.add(Header(":method", message.method.name));
|
||||
headers.add(Header(":scheme", uri.scheme));
|
||||
headers.add(Header(":authority", uri.host));
|
||||
headers.add(Header(":path", message.uri));
|
||||
|
||||
message.headers.forEach((key, values) {
|
||||
for (var value in values) {
|
||||
headers.add(Header(key, value));
|
||||
}
|
||||
});
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
|
||||
class Http2ResponseDecoder extends Http2Codec<HttpResponse> {
|
||||
@override
|
||||
HttpResponse createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map<String, String> headers) {
|
||||
var httpResponse = HttpResponse(HttpStatus.valueOf(int.parse(headers[':status']!)),
|
||||
protocolVersion: headers[":version"] ?? 'HTTP/2');
|
||||
httpResponse.requestId = channelContext.getStreamRequest(frameHeader.streamIdentifier)!.requestId;
|
||||
channelContext.putStreamResponse(frameHeader.streamIdentifier, httpResponse);
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
@override
|
||||
HttpResponse? getMessage(ChannelContext channelContext, FrameHeader frameHeader) {
|
||||
return channelContext.getStreamResponse(frameHeader.streamIdentifier);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Header> encodeHeaders(HttpResponse message) {
|
||||
var headers = <Header>[];
|
||||
headers.add(Header(":status", message.status.code.toString()));
|
||||
message.headers.forEach((key, values) {
|
||||
for (var value in values) {
|
||||
headers.add(Header(key, value));
|
||||
}
|
||||
});
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
|
||||
class FrameReader {
|
||||
static int headerLength = 9;
|
||||
|
||||
static List<int>? _readFramePayload(ByteBuf data, int length) {
|
||||
if (data.readableBytes() < length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.readBytes(length);
|
||||
}
|
||||
|
||||
static FrameHeader? _readFrameHeader(ByteBuf data) {
|
||||
if (data.readableBytes() < headerLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int length = data.read() << 16 | data.read() << 8 | data.read();
|
||||
FrameType type = FrameType.values[data.read()];
|
||||
int flags = data.read();
|
||||
int streamIdentifier = data.readInt();
|
||||
|
||||
return FrameHeader(length, type, flags, streamIdentifier);
|
||||
}
|
||||
}
|
||||
88
lib/network/http/h2/frame.dart
Normal file
88
lib/network/http/h2/frame.dart
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
enum FrameType { data, headers, priority, rstStream, settings, pushPromise, ping, goaway, windowUpdate, continuation }
|
||||
|
||||
class FrameHeader {
|
||||
static const flagsEndStream = 0x01;
|
||||
static const flagsEndHeaders = 0x04;
|
||||
|
||||
final int length;
|
||||
final FrameType type;
|
||||
int flags; // 8 bits
|
||||
final int streamIdentifier;
|
||||
|
||||
FrameHeader(this.length, this.type, this.flags, this.streamIdentifier);
|
||||
|
||||
bool get hasPaddedFlag => (flags & 0x08) == 0x08;
|
||||
|
||||
bool get hasPriorityFlag => (flags & 0x20) == 0x20;
|
||||
|
||||
bool get hasEndHeadersFlag => (flags & flagsEndHeaders) == flagsEndHeaders;
|
||||
|
||||
bool get hasEndStreamFlag => (flags & flagsEndStream) == flagsEndStream;
|
||||
|
||||
bool get hasAckFlag => (flags & 0x01) == 0x01;
|
||||
|
||||
List<int> encode() {
|
||||
var result = <int>[];
|
||||
result.addAll(_intToBytes(length, 3)); // length is 24 bits
|
||||
result.add(type.index); // type is 8 bits
|
||||
result.add(flags); // flags is 8 bits
|
||||
result.addAll(_intToBytes(streamIdentifier, 4)); // streamIdentifier is 32 bits
|
||||
return result;
|
||||
}
|
||||
|
||||
List<int> _intToBytes(int value, int byteCount) {
|
||||
var bytes = <int>[];
|
||||
for (var i = 0; i < byteCount; i++) {
|
||||
bytes.insert(0, value & 0xff);
|
||||
value >>= 8;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
class Frame {
|
||||
final FrameHeader header;
|
||||
|
||||
Frame(this.header);
|
||||
|
||||
Map toJson() => {
|
||||
'length': header.length,
|
||||
'type': header.type.toString().split('.')[1],
|
||||
'flags': header.flags,
|
||||
'streamIdentifier': header.streamIdentifier
|
||||
};
|
||||
}
|
||||
|
||||
class HeadersFrame extends Frame {
|
||||
final int padLength;
|
||||
final bool exclusiveDependency;
|
||||
final int? streamDependency;
|
||||
final int? weight;
|
||||
final List<int> headerBlockFragment;
|
||||
|
||||
HeadersFrame(super.header, this.padLength, this.exclusiveDependency, this.streamDependency, this.weight,
|
||||
this.headerBlockFragment);
|
||||
}
|
||||
|
||||
class DataFrame extends Frame {
|
||||
final int padLength;
|
||||
final List<int> data;
|
||||
|
||||
DataFrame(super.header, this.padLength, this.data);
|
||||
}
|
||||
305
lib/network/http/h2/hpack.dart
Normal file
305
lib/network/http/h2/hpack.dart
Normal file
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:network_proxy/network/http/h2/huffman.dart';
|
||||
import 'package:network_proxy/network/util/byte_buf.dart';
|
||||
|
||||
class HPACKDecoder {
|
||||
// static const int _maxHeaderTableSize = 12288;
|
||||
static HuffmanDecoder huffmanDecoder = HuffmanDecoder();
|
||||
final IndexTable _indexTable = IndexTable();
|
||||
|
||||
updateTableSize(int size) {
|
||||
_indexTable._maxSize = size;
|
||||
}
|
||||
|
||||
List<Header> decode(List<int> bytes) {
|
||||
var headers = <Header>[];
|
||||
var payload = ByteBuf(bytes);
|
||||
while (payload.isReadable()) {
|
||||
var header = _decodeHeader(payload);
|
||||
if (header == null) continue;
|
||||
headers.add(header);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
Header? _decodeHeader(ByteBuf framePayload) {
|
||||
if (!framePayload.isReadable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int firstByte = framePayload.get(framePayload.readerIndex);
|
||||
if (firstByte & 0x80 == 0x80) {
|
||||
// Indexed Header Field
|
||||
int headerIndex = _decodeInteger(framePayload, 7);
|
||||
return _indexTable[headerIndex];
|
||||
} else if (firstByte & 0x40 == 0x40) {
|
||||
// Literal Header Field with Incremental Indexing
|
||||
int headerIndex = _decodeInteger(framePayload, 6);
|
||||
Header header = _readHeaderField(framePayload, headerIndex);
|
||||
_indexTable.add(header);
|
||||
return header;
|
||||
} else if (firstByte & 0x20 == 0x20) {
|
||||
// Dynamic Table Size Update
|
||||
int maxSize = _decodeInteger(framePayload, 5);
|
||||
_indexTable._maxSize = maxSize;
|
||||
} else if (firstByte & 0x10 == 0x10) {
|
||||
// Literal Header Field never Indexed
|
||||
int headerIndex = _decodeInteger(framePayload, 4);
|
||||
Header header = _readHeaderField(framePayload, headerIndex, neverIndexed: true);
|
||||
return header;
|
||||
} else {
|
||||
// Literal Header Field without Indexing
|
||||
int headerIndex = _decodeInteger(framePayload, 4);
|
||||
Header header = _readHeaderField(framePayload, headerIndex);
|
||||
return header;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Header _readHeaderField(ByteBuf data, int index, {bool neverIndexed = false}) {
|
||||
if (index > 0) {
|
||||
String name = _indexTable[index].name;
|
||||
String value = _decodeString(data, neverIndexed: neverIndexed);
|
||||
return Header(name, value);
|
||||
}
|
||||
|
||||
String name = _decodeString(data);
|
||||
String value = _decodeString(data, neverIndexed: neverIndexed);
|
||||
return Header(name, value);
|
||||
}
|
||||
|
||||
String _decodeString(ByteBuf data, {bool neverIndexed = false}) {
|
||||
int firstByte = data.get(data.readerIndex);
|
||||
bool huffmanEncoded = (firstByte & 0x80) == 0x80;
|
||||
int length = _decodeInteger(data, 7);
|
||||
|
||||
Uint8List stringBytes = data.readBytes(length);
|
||||
if (huffmanEncoded) {
|
||||
// If the string is Huffman encoded, decode it using a Huffman decoder.
|
||||
return ascii.decode(huffmanDecoder.decode(stringBytes));
|
||||
} else {
|
||||
// If the string is not Huffman encoded, simply create a new string from the bytes.
|
||||
return ascii.decode(stringBytes);
|
||||
}
|
||||
}
|
||||
|
||||
int _decodeInteger(ByteBuf data, int prefixLength) {
|
||||
int prefixMask = (1 << prefixLength) - 1;
|
||||
int value = data.read() & prefixMask;
|
||||
|
||||
if (value < prefixMask) {
|
||||
return value;
|
||||
}
|
||||
|
||||
int shift = 0;
|
||||
int b;
|
||||
do {
|
||||
b = data.read();
|
||||
value += (b & 127) << shift;
|
||||
shift += 7;
|
||||
} while ((b & 128) == 128);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
class HPACKEncoder {
|
||||
final IndexTable _indexTable = IndexTable();
|
||||
static HuffmanEncoder huffmanEncoder = HuffmanEncoder();
|
||||
|
||||
List<int> encodeList(List<Header> headers) {
|
||||
var bytesBuilder = BytesBuilder();
|
||||
|
||||
for (var header in headers) {
|
||||
_encodeHeader(bytesBuilder, header);
|
||||
}
|
||||
|
||||
return bytesBuilder.takeBytes();
|
||||
}
|
||||
|
||||
List<int> encode(Header header) {
|
||||
var bytesBuilder = BytesBuilder();
|
||||
_encodeHeader(bytesBuilder, header);
|
||||
return bytesBuilder.takeBytes();
|
||||
}
|
||||
|
||||
void _encodeHeader(BytesBuilder bytesBuilder, Header header) {
|
||||
var name = header.name;
|
||||
var value = header.value;
|
||||
var index = _getIndex(name, value);
|
||||
if (index != null) {
|
||||
_encodeInteger(bytesBuilder, 7, 0x80, index);
|
||||
return;
|
||||
}
|
||||
|
||||
var nameIndex = _getIndex(name);
|
||||
if (nameIndex != null) {
|
||||
_encodeInteger(bytesBuilder, 4, 0, nameIndex);
|
||||
} else {
|
||||
bytesBuilder.addByte(0);
|
||||
_encodeString(bytesBuilder, name);
|
||||
}
|
||||
_encodeString(bytesBuilder, value);
|
||||
}
|
||||
|
||||
int? _getIndex(String name, [String? value]) {
|
||||
for (var i = 1; i < _indexTable.length; i++) {
|
||||
var header = _indexTable[i];
|
||||
if (header.name == name && (value == null || header.value == value)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_encodeString(BytesBuilder bytesBuilder, String value) {
|
||||
var encoded = ascii.encode(value);
|
||||
|
||||
var huffmanEncoded = huffmanEncoder.encode(encoded);
|
||||
if (huffmanEncoded.length < encoded.length) {
|
||||
_encodeInteger(bytesBuilder, 7, 0x80, huffmanEncoded.length);
|
||||
bytesBuilder.add(huffmanEncoded);
|
||||
} else {
|
||||
_encodeInteger(bytesBuilder, 7, 0x00, encoded.length);
|
||||
bytesBuilder.add(encoded);
|
||||
}
|
||||
}
|
||||
|
||||
void _encodeInteger(BytesBuilder bytesBuilder, int prefixBits, int mask, int value) {
|
||||
assert(prefixBits <= 8);
|
||||
|
||||
if (value < (1 << prefixBits) - 1) {
|
||||
bytesBuilder.addByte(value | mask);
|
||||
} else {
|
||||
bytesBuilder.addByte(((1 << prefixBits) - 1) | mask);
|
||||
value -= (1 << prefixBits) - 1;
|
||||
while (value >= 128) {
|
||||
bytesBuilder.addByte(value % 128 + 128);
|
||||
value ~/= 128;
|
||||
}
|
||||
bytesBuilder.addByte(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Header {
|
||||
final String name;
|
||||
String value;
|
||||
|
||||
Header(String name, this.value) : name = name.toLowerCase();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '{name: $name, value: $value}';
|
||||
}
|
||||
}
|
||||
|
||||
class IndexTable {
|
||||
static final List<Header> _staticTable = [
|
||||
Header('', ''),
|
||||
Header(':authority', ''),
|
||||
Header(':method', 'GET'),
|
||||
Header(':method', 'POST'),
|
||||
Header(':path', '/'),
|
||||
Header(':path', '/index.html'),
|
||||
Header(':scheme', 'http'),
|
||||
Header(':scheme', 'https'),
|
||||
Header(':status', '200'),
|
||||
Header(':status', '204'),
|
||||
Header(':status', '206'),
|
||||
Header(':status', '304'),
|
||||
Header(':status', '400'),
|
||||
Header(':status', '404'),
|
||||
Header(':status', '500'),
|
||||
Header('accept-charset', ''),
|
||||
Header('accept-encoding', 'gzip, deflate, br'),
|
||||
Header('accept-language', ''),
|
||||
Header('accept-ranges', ''),
|
||||
Header('accept', ''),
|
||||
Header('access-control-allow-origin', ''),
|
||||
Header('age', ''),
|
||||
Header('allow', ''),
|
||||
Header('authorization', ''),
|
||||
Header('cache-control', ''),
|
||||
Header('content-disposition', ''),
|
||||
Header('content-encoding', ''),
|
||||
Header('content-language', ''),
|
||||
Header('content-length', ''),
|
||||
Header('content-location', ''),
|
||||
Header('content-range', ''),
|
||||
Header('content-type', ''),
|
||||
Header('cookie', ''),
|
||||
Header('date', ''),
|
||||
Header('etag', ''),
|
||||
Header('expect', ''),
|
||||
Header('expires', ''),
|
||||
Header('from', ''),
|
||||
Header('host', ''),
|
||||
Header('if-match', ''),
|
||||
Header('if-modified-since', ''),
|
||||
Header('if-none-match', ''),
|
||||
Header('if-range', ''),
|
||||
Header('if-unmodified-since', ''),
|
||||
Header('last-modified', ''),
|
||||
Header('link', ''),
|
||||
Header('location', ''),
|
||||
Header('max-forwards', ''),
|
||||
Header('proxy-authenticate', ''),
|
||||
Header('proxy-authorization', ''),
|
||||
Header('range', ''),
|
||||
Header('referer', ''),
|
||||
Header('refresh', ''),
|
||||
Header('retry-after', ''),
|
||||
Header('server', ''),
|
||||
Header('set-cookie', ''),
|
||||
Header('strict-transport-security', ''),
|
||||
Header('transfer-encoding', ''),
|
||||
Header('user-agent', ''),
|
||||
Header('vary', ''),
|
||||
Header('via', ''),
|
||||
Header('www-authenticate', ''),
|
||||
];
|
||||
|
||||
//动态表
|
||||
final List<Header> _dynamicTable = [];
|
||||
|
||||
int _maxSize = 4096;
|
||||
|
||||
Header operator [](int index) {
|
||||
if (index < _staticTable.length) return _staticTable[index];
|
||||
if (index < _staticTable.length + _dynamicTable.length) {
|
||||
return _dynamicTable[index - _staticTable.length];
|
||||
}
|
||||
throw RangeError('Invalid index: $index');
|
||||
}
|
||||
|
||||
add(Header header) {
|
||||
_dynamicTable.add(header);
|
||||
_maxSize -= sizeOf(header);
|
||||
while (_maxSize < 0) {
|
||||
_maxSize += sizeOf(_dynamicTable.removeAt(0));
|
||||
}
|
||||
}
|
||||
|
||||
int get length => _staticTable.length + _dynamicTable.length;
|
||||
|
||||
int sizeOf(Header header) => header.name.length + header.value.length + 32;
|
||||
}
|
||||
417
lib/network/http/h2/huffman.dart
Normal file
417
lib/network/http/h2/huffman.dart
Normal file
@@ -0,0 +1,417 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
main() {
|
||||
var list = [241, 227, 194, 254, 231, 52, 246, 174, 67, 211];
|
||||
print(HuffmanDecoder().decode(list));
|
||||
}
|
||||
class HuffmanDecoder {
|
||||
static const int eosByte = 256; //end of string
|
||||
|
||||
final HuffmanTreeNode _root;
|
||||
|
||||
HuffmanDecoder() : _root = generateHuffmanTree(_huffmanTable);
|
||||
|
||||
//http2协议规范 huffman解码
|
||||
List<int> decode(List<int> bytes) {
|
||||
var buffer = BytesBuilder();
|
||||
|
||||
var currentByteOffset = 0;
|
||||
var node = _root;
|
||||
var currentDepth = 0;
|
||||
while (currentByteOffset < bytes.length) {
|
||||
var byte = bytes[currentByteOffset];
|
||||
for (var currentBit = 7; currentBit >= 0; currentBit--) {
|
||||
var right = (byte >> currentBit) & 1 == 1;
|
||||
if (right) {
|
||||
node = node.right!;
|
||||
} else {
|
||||
node = node.left!;
|
||||
}
|
||||
currentDepth++;
|
||||
if (node.value != null) {
|
||||
if (node.value == eosByte) {
|
||||
throw Exception('More than 7 bit padding is not allowed. Found entire EOS '
|
||||
'encoding');
|
||||
}
|
||||
buffer.addByte(node.value!);
|
||||
node = _root;
|
||||
currentDepth = 0;
|
||||
}
|
||||
}
|
||||
currentByteOffset++;
|
||||
}
|
||||
|
||||
if (node != _root) {
|
||||
if (currentDepth > 7) {
|
||||
throw Exception('Incomplete encoding of a byte or more than 7 bit padding.');
|
||||
}
|
||||
|
||||
while (node.right != null) {
|
||||
node = node.right!;
|
||||
}
|
||||
|
||||
if (node.value != 256) {
|
||||
throw Exception('Incomplete encoding of a byte.');
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.takeBytes();
|
||||
}
|
||||
}
|
||||
|
||||
class HuffmanEncoder {
|
||||
static const int eosByte = 256; //end of string
|
||||
|
||||
final List<EncodedHuffmanValue> _codewords;
|
||||
|
||||
HuffmanEncoder() : _codewords = _huffmanTable;
|
||||
|
||||
//http2协议规范 huffman编码
|
||||
List<int> encode(List<int> bytes) {
|
||||
var buffer = BytesBuilder();
|
||||
|
||||
var currentByte = 0;
|
||||
var currentBitOffset = 7;
|
||||
|
||||
void writeValue(int value, int numBits) {
|
||||
var i = numBits - 1;
|
||||
while (i >= 0) {
|
||||
if (currentBitOffset == 7 && i >= 7) {
|
||||
assert(currentByte == 0);
|
||||
|
||||
buffer.addByte((value >> (i - 7)) & 0xff);
|
||||
currentBitOffset = 7;
|
||||
currentByte = 0;
|
||||
i -= 8;
|
||||
} else {
|
||||
currentByte |= ((value >> i) & 1) << currentBitOffset;
|
||||
|
||||
currentBitOffset--;
|
||||
if (currentBitOffset == -1) {
|
||||
buffer.addByte(currentByte);
|
||||
currentBitOffset = 7;
|
||||
currentByte = 0;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
var byte = bytes[i];
|
||||
var value = _codewords[byte];
|
||||
writeValue(value.encodedBytes, value.numBits);
|
||||
}
|
||||
|
||||
if (currentBitOffset < 7) {
|
||||
writeValue(0xff, 1 + currentBitOffset);
|
||||
}
|
||||
|
||||
return buffer.takeBytes();
|
||||
}
|
||||
|
||||
}
|
||||
///生成h2 huffman解码tree
|
||||
HuffmanTreeNode generateHuffmanTree(List<EncodedHuffmanValue> valueEncodings) {
|
||||
var root = HuffmanTreeNode();
|
||||
|
||||
for (var byteOffset = 0; byteOffset < valueEncodings.length; byteOffset++) {
|
||||
var entry = valueEncodings[byteOffset];
|
||||
|
||||
var current = root;
|
||||
for (var bitNr = 0; bitNr < entry.numBits; bitNr++) {
|
||||
var right = ((entry.encodedBytes >> (entry.numBits - bitNr - 1)) & 1) == 1;
|
||||
|
||||
if (right) {
|
||||
current.right ??= HuffmanTreeNode();
|
||||
current = current.right!;
|
||||
} else {
|
||||
current.left ??= HuffmanTreeNode();
|
||||
current = current.left!;
|
||||
}
|
||||
}
|
||||
|
||||
current.value = byteOffset;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
class HuffmanTreeNode {
|
||||
int? value;
|
||||
|
||||
HuffmanTreeNode? left;
|
||||
HuffmanTreeNode? right;
|
||||
|
||||
bool isLeaf() {
|
||||
return left == null && right == null;
|
||||
}
|
||||
}
|
||||
|
||||
//HPACK规范 huffman编码的字节编码列表。
|
||||
final List<EncodedHuffmanValue> _huffmanTable = [
|
||||
EncodedHuffmanValue(0x1ff8, 13),
|
||||
EncodedHuffmanValue(0x7fffd8, 23),
|
||||
EncodedHuffmanValue(0xfffffe2, 28),
|
||||
EncodedHuffmanValue(0xfffffe3, 28),
|
||||
EncodedHuffmanValue(0xfffffe4, 28),
|
||||
EncodedHuffmanValue(0xfffffe5, 28),
|
||||
EncodedHuffmanValue(0xfffffe6, 28),
|
||||
EncodedHuffmanValue(0xfffffe7, 28),
|
||||
EncodedHuffmanValue(0xfffffe8, 28),
|
||||
EncodedHuffmanValue(0xffffea, 24),
|
||||
EncodedHuffmanValue(0x3ffffffc, 30),
|
||||
EncodedHuffmanValue(0xfffffe9, 28),
|
||||
EncodedHuffmanValue(0xfffffea, 28),
|
||||
EncodedHuffmanValue(0x3ffffffd, 30),
|
||||
EncodedHuffmanValue(0xfffffeb, 28),
|
||||
EncodedHuffmanValue(0xfffffec, 28),
|
||||
EncodedHuffmanValue(0xfffffed, 28),
|
||||
EncodedHuffmanValue(0xfffffee, 28),
|
||||
EncodedHuffmanValue(0xfffffef, 28),
|
||||
EncodedHuffmanValue(0xffffff0, 28),
|
||||
EncodedHuffmanValue(0xffffff1, 28),
|
||||
EncodedHuffmanValue(0xffffff2, 28),
|
||||
EncodedHuffmanValue(0x3ffffffe, 30),
|
||||
EncodedHuffmanValue(0xffffff3, 28),
|
||||
EncodedHuffmanValue(0xffffff4, 28),
|
||||
EncodedHuffmanValue(0xffffff5, 28),
|
||||
EncodedHuffmanValue(0xffffff6, 28),
|
||||
EncodedHuffmanValue(0xffffff7, 28),
|
||||
EncodedHuffmanValue(0xffffff8, 28),
|
||||
EncodedHuffmanValue(0xffffff9, 28),
|
||||
EncodedHuffmanValue(0xffffffa, 28),
|
||||
EncodedHuffmanValue(0xffffffb, 28),
|
||||
EncodedHuffmanValue(0x14, 6),
|
||||
EncodedHuffmanValue(0x3f8, 10),
|
||||
EncodedHuffmanValue(0x3f9, 10),
|
||||
EncodedHuffmanValue(0xffa, 12),
|
||||
EncodedHuffmanValue(0x1ff9, 13),
|
||||
EncodedHuffmanValue(0x15, 6),
|
||||
EncodedHuffmanValue(0xf8, 8),
|
||||
EncodedHuffmanValue(0x7fa, 11),
|
||||
EncodedHuffmanValue(0x3fa, 10),
|
||||
EncodedHuffmanValue(0x3fb, 10),
|
||||
EncodedHuffmanValue(0xf9, 8),
|
||||
EncodedHuffmanValue(0x7fb, 11),
|
||||
EncodedHuffmanValue(0xfa, 8),
|
||||
EncodedHuffmanValue(0x16, 6),
|
||||
EncodedHuffmanValue(0x17, 6),
|
||||
EncodedHuffmanValue(0x18, 6),
|
||||
EncodedHuffmanValue(0x0, 5),
|
||||
EncodedHuffmanValue(0x1, 5),
|
||||
EncodedHuffmanValue(0x2, 5),
|
||||
EncodedHuffmanValue(0x19, 6),
|
||||
EncodedHuffmanValue(0x1a, 6),
|
||||
EncodedHuffmanValue(0x1b, 6),
|
||||
EncodedHuffmanValue(0x1c, 6),
|
||||
EncodedHuffmanValue(0x1d, 6),
|
||||
EncodedHuffmanValue(0x1e, 6),
|
||||
EncodedHuffmanValue(0x1f, 6),
|
||||
EncodedHuffmanValue(0x5c, 7),
|
||||
EncodedHuffmanValue(0xfb, 8),
|
||||
EncodedHuffmanValue(0x7ffc, 15),
|
||||
EncodedHuffmanValue(0x20, 6),
|
||||
EncodedHuffmanValue(0xffb, 12),
|
||||
EncodedHuffmanValue(0x3fc, 10),
|
||||
EncodedHuffmanValue(0x1ffa, 13),
|
||||
EncodedHuffmanValue(0x21, 6),
|
||||
EncodedHuffmanValue(0x5d, 7),
|
||||
EncodedHuffmanValue(0x5e, 7),
|
||||
EncodedHuffmanValue(0x5f, 7),
|
||||
EncodedHuffmanValue(0x60, 7),
|
||||
EncodedHuffmanValue(0x61, 7),
|
||||
EncodedHuffmanValue(0x62, 7),
|
||||
EncodedHuffmanValue(0x63, 7),
|
||||
EncodedHuffmanValue(0x64, 7),
|
||||
EncodedHuffmanValue(0x65, 7),
|
||||
EncodedHuffmanValue(0x66, 7),
|
||||
EncodedHuffmanValue(0x67, 7),
|
||||
EncodedHuffmanValue(0x68, 7),
|
||||
EncodedHuffmanValue(0x69, 7),
|
||||
EncodedHuffmanValue(0x6a, 7),
|
||||
EncodedHuffmanValue(0x6b, 7),
|
||||
EncodedHuffmanValue(0x6c, 7),
|
||||
EncodedHuffmanValue(0x6d, 7),
|
||||
EncodedHuffmanValue(0x6e, 7),
|
||||
EncodedHuffmanValue(0x6f, 7),
|
||||
EncodedHuffmanValue(0x70, 7),
|
||||
EncodedHuffmanValue(0x71, 7),
|
||||
EncodedHuffmanValue(0x72, 7),
|
||||
EncodedHuffmanValue(0xfc, 8),
|
||||
EncodedHuffmanValue(0x73, 7),
|
||||
EncodedHuffmanValue(0xfd, 8),
|
||||
EncodedHuffmanValue(0x1ffb, 13),
|
||||
EncodedHuffmanValue(0x7fff0, 19),
|
||||
EncodedHuffmanValue(0x1ffc, 13),
|
||||
EncodedHuffmanValue(0x3ffc, 14),
|
||||
EncodedHuffmanValue(0x22, 6),
|
||||
EncodedHuffmanValue(0x7ffd, 15),
|
||||
EncodedHuffmanValue(0x3, 5),
|
||||
EncodedHuffmanValue(0x23, 6),
|
||||
EncodedHuffmanValue(0x4, 5),
|
||||
EncodedHuffmanValue(0x24, 6),
|
||||
EncodedHuffmanValue(0x5, 5),
|
||||
EncodedHuffmanValue(0x25, 6),
|
||||
EncodedHuffmanValue(0x26, 6),
|
||||
EncodedHuffmanValue(0x27, 6),
|
||||
EncodedHuffmanValue(0x6, 5),
|
||||
EncodedHuffmanValue(0x74, 7),
|
||||
EncodedHuffmanValue(0x75, 7),
|
||||
EncodedHuffmanValue(0x28, 6),
|
||||
EncodedHuffmanValue(0x29, 6),
|
||||
EncodedHuffmanValue(0x2a, 6),
|
||||
EncodedHuffmanValue(0x7, 5),
|
||||
EncodedHuffmanValue(0x2b, 6),
|
||||
EncodedHuffmanValue(0x76, 7),
|
||||
EncodedHuffmanValue(0x2c, 6),
|
||||
EncodedHuffmanValue(0x8, 5),
|
||||
EncodedHuffmanValue(0x9, 5),
|
||||
EncodedHuffmanValue(0x2d, 6),
|
||||
EncodedHuffmanValue(0x77, 7),
|
||||
EncodedHuffmanValue(0x78, 7),
|
||||
EncodedHuffmanValue(0x79, 7),
|
||||
EncodedHuffmanValue(0x7a, 7),
|
||||
EncodedHuffmanValue(0x7b, 7),
|
||||
EncodedHuffmanValue(0x7ffe, 15),
|
||||
EncodedHuffmanValue(0x7fc, 11),
|
||||
EncodedHuffmanValue(0x3ffd, 14),
|
||||
EncodedHuffmanValue(0x1ffd, 13),
|
||||
EncodedHuffmanValue(0xffffffc, 28),
|
||||
EncodedHuffmanValue(0xfffe6, 20),
|
||||
EncodedHuffmanValue(0x3fffd2, 22),
|
||||
EncodedHuffmanValue(0xfffe7, 20),
|
||||
EncodedHuffmanValue(0xfffe8, 20),
|
||||
EncodedHuffmanValue(0x3fffd3, 22),
|
||||
EncodedHuffmanValue(0x3fffd4, 22),
|
||||
EncodedHuffmanValue(0x3fffd5, 22),
|
||||
EncodedHuffmanValue(0x7fffd9, 23),
|
||||
EncodedHuffmanValue(0x3fffd6, 22),
|
||||
EncodedHuffmanValue(0x7fffda, 23),
|
||||
EncodedHuffmanValue(0x7fffdb, 23),
|
||||
EncodedHuffmanValue(0x7fffdc, 23),
|
||||
EncodedHuffmanValue(0x7fffdd, 23),
|
||||
EncodedHuffmanValue(0x7fffde, 23),
|
||||
EncodedHuffmanValue(0xffffeb, 24),
|
||||
EncodedHuffmanValue(0x7fffdf, 23),
|
||||
EncodedHuffmanValue(0xffffec, 24),
|
||||
EncodedHuffmanValue(0xffffed, 24),
|
||||
EncodedHuffmanValue(0x3fffd7, 22),
|
||||
EncodedHuffmanValue(0x7fffe0, 23),
|
||||
EncodedHuffmanValue(0xffffee, 24),
|
||||
EncodedHuffmanValue(0x7fffe1, 23),
|
||||
EncodedHuffmanValue(0x7fffe2, 23),
|
||||
EncodedHuffmanValue(0x7fffe3, 23),
|
||||
EncodedHuffmanValue(0x7fffe4, 23),
|
||||
EncodedHuffmanValue(0x1fffdc, 21),
|
||||
EncodedHuffmanValue(0x3fffd8, 22),
|
||||
EncodedHuffmanValue(0x7fffe5, 23),
|
||||
EncodedHuffmanValue(0x3fffd9, 22),
|
||||
EncodedHuffmanValue(0x7fffe6, 23),
|
||||
EncodedHuffmanValue(0x7fffe7, 23),
|
||||
EncodedHuffmanValue(0xffffef, 24),
|
||||
EncodedHuffmanValue(0x3fffda, 22),
|
||||
EncodedHuffmanValue(0x1fffdd, 21),
|
||||
EncodedHuffmanValue(0xfffe9, 20),
|
||||
EncodedHuffmanValue(0x3fffdb, 22),
|
||||
EncodedHuffmanValue(0x3fffdc, 22),
|
||||
EncodedHuffmanValue(0x7fffe8, 23),
|
||||
EncodedHuffmanValue(0x7fffe9, 23),
|
||||
EncodedHuffmanValue(0x1fffde, 21),
|
||||
EncodedHuffmanValue(0x7fffea, 23),
|
||||
EncodedHuffmanValue(0x3fffdd, 22),
|
||||
EncodedHuffmanValue(0x3fffde, 22),
|
||||
EncodedHuffmanValue(0xfffff0, 24),
|
||||
EncodedHuffmanValue(0x1fffdf, 21),
|
||||
EncodedHuffmanValue(0x3fffdf, 22),
|
||||
EncodedHuffmanValue(0x7fffeb, 23),
|
||||
EncodedHuffmanValue(0x7fffec, 23),
|
||||
EncodedHuffmanValue(0x1fffe0, 21),
|
||||
EncodedHuffmanValue(0x1fffe1, 21),
|
||||
EncodedHuffmanValue(0x3fffe0, 22),
|
||||
EncodedHuffmanValue(0x1fffe2, 21),
|
||||
EncodedHuffmanValue(0x7fffed, 23),
|
||||
EncodedHuffmanValue(0x3fffe1, 22),
|
||||
EncodedHuffmanValue(0x7fffee, 23),
|
||||
EncodedHuffmanValue(0x7fffef, 23),
|
||||
EncodedHuffmanValue(0xfffea, 20),
|
||||
EncodedHuffmanValue(0x3fffe2, 22),
|
||||
EncodedHuffmanValue(0x3fffe3, 22),
|
||||
EncodedHuffmanValue(0x3fffe4, 22),
|
||||
EncodedHuffmanValue(0x7ffff0, 23),
|
||||
EncodedHuffmanValue(0x3fffe5, 22),
|
||||
EncodedHuffmanValue(0x3fffe6, 22),
|
||||
EncodedHuffmanValue(0x7ffff1, 23),
|
||||
EncodedHuffmanValue(0x3ffffe0, 26),
|
||||
EncodedHuffmanValue(0x3ffffe1, 26),
|
||||
EncodedHuffmanValue(0xfffeb, 20),
|
||||
EncodedHuffmanValue(0x7fff1, 19),
|
||||
EncodedHuffmanValue(0x3fffe7, 22),
|
||||
EncodedHuffmanValue(0x7ffff2, 23),
|
||||
EncodedHuffmanValue(0x3fffe8, 22),
|
||||
EncodedHuffmanValue(0x1ffffec, 25),
|
||||
EncodedHuffmanValue(0x3ffffe2, 26),
|
||||
EncodedHuffmanValue(0x3ffffe3, 26),
|
||||
EncodedHuffmanValue(0x3ffffe4, 26),
|
||||
EncodedHuffmanValue(0x7ffffde, 27),
|
||||
EncodedHuffmanValue(0x7ffffdf, 27),
|
||||
EncodedHuffmanValue(0x3ffffe5, 26),
|
||||
EncodedHuffmanValue(0xfffff1, 24),
|
||||
EncodedHuffmanValue(0x1ffffed, 25),
|
||||
EncodedHuffmanValue(0x7fff2, 19),
|
||||
EncodedHuffmanValue(0x1fffe3, 21),
|
||||
EncodedHuffmanValue(0x3ffffe6, 26),
|
||||
EncodedHuffmanValue(0x7ffffe0, 27),
|
||||
EncodedHuffmanValue(0x7ffffe1, 27),
|
||||
EncodedHuffmanValue(0x3ffffe7, 26),
|
||||
EncodedHuffmanValue(0x7ffffe2, 27),
|
||||
EncodedHuffmanValue(0xfffff2, 24),
|
||||
EncodedHuffmanValue(0x1fffe4, 21),
|
||||
EncodedHuffmanValue(0x1fffe5, 21),
|
||||
EncodedHuffmanValue(0x3ffffe8, 26),
|
||||
EncodedHuffmanValue(0x3ffffe9, 26),
|
||||
EncodedHuffmanValue(0xffffffd, 28),
|
||||
EncodedHuffmanValue(0x7ffffe3, 27),
|
||||
EncodedHuffmanValue(0x7ffffe4, 27),
|
||||
EncodedHuffmanValue(0x7ffffe5, 27),
|
||||
EncodedHuffmanValue(0xfffec, 20),
|
||||
EncodedHuffmanValue(0xfffff3, 24),
|
||||
EncodedHuffmanValue(0xfffed, 20),
|
||||
EncodedHuffmanValue(0x1fffe6, 21),
|
||||
EncodedHuffmanValue(0x3fffe9, 22),
|
||||
EncodedHuffmanValue(0x1fffe7, 21),
|
||||
EncodedHuffmanValue(0x1fffe8, 21),
|
||||
EncodedHuffmanValue(0x7ffff3, 23),
|
||||
EncodedHuffmanValue(0x3fffea, 22),
|
||||
EncodedHuffmanValue(0x3fffeb, 22),
|
||||
EncodedHuffmanValue(0x1ffffee, 25),
|
||||
EncodedHuffmanValue(0x1ffffef, 25),
|
||||
EncodedHuffmanValue(0xfffff4, 24),
|
||||
EncodedHuffmanValue(0xfffff5, 24),
|
||||
EncodedHuffmanValue(0x3ffffea, 26),
|
||||
EncodedHuffmanValue(0x7ffff4, 23),
|
||||
EncodedHuffmanValue(0x3ffffeb, 26),
|
||||
EncodedHuffmanValue(0x7ffffe6, 27),
|
||||
EncodedHuffmanValue(0x3ffffec, 26),
|
||||
EncodedHuffmanValue(0x3ffffed, 26),
|
||||
EncodedHuffmanValue(0x7ffffe7, 27),
|
||||
EncodedHuffmanValue(0x7ffffe8, 27),
|
||||
EncodedHuffmanValue(0x7ffffe9, 27),
|
||||
EncodedHuffmanValue(0x7ffffea, 27),
|
||||
EncodedHuffmanValue(0x7ffffeb, 27),
|
||||
EncodedHuffmanValue(0xffffffe, 28),
|
||||
EncodedHuffmanValue(0x7ffffec, 27),
|
||||
EncodedHuffmanValue(0x7ffffed, 27),
|
||||
EncodedHuffmanValue(0x7ffffee, 27),
|
||||
EncodedHuffmanValue(0x7ffffef, 27),
|
||||
EncodedHuffmanValue(0x7fffff0, 27),
|
||||
EncodedHuffmanValue(0x3ffffee, 26),
|
||||
EncodedHuffmanValue(0x3fffffff, 30),
|
||||
];
|
||||
|
||||
class EncodedHuffmanValue {
|
||||
final int encodedBytes;
|
||||
final int numBits;
|
||||
|
||||
EncodedHuffmanValue(this.encodedBytes, this.numBits);
|
||||
}
|
||||
87
lib/network/http/h2/setting.dart
Normal file
87
lib/network/http/h2/setting.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import 'package:network_proxy/network/channel.dart';
|
||||
import 'package:network_proxy/network/http/h2/frame.dart';
|
||||
import 'package:network_proxy/network/util/byte_buf.dart';
|
||||
|
||||
class StreamSetting {
|
||||
/// 允许发送方通知远程端点用于解码头块的头压缩表的最大大小(以八位字节为单位)。
|
||||
/// 初始值为4096个八位字节。
|
||||
int headTableSize = 4096;
|
||||
|
||||
///如果一个端点接收到的这个参数设置为0,它就不能发送PUSH_PROMISE帧。
|
||||
///初始值为1,表示允许服务器推送。
|
||||
bool enablePush = true;
|
||||
|
||||
///指示发送方允许的最大并发流数。这个限制是定向的:它适用于发送方允许接收方创建的流的数量。最初,对该值没有限制。建议此值不小于100,以免不必要地限制并行性。
|
||||
int? maxConcurrentStreams;
|
||||
|
||||
/// 指示发送方用于流级流控制的初始窗口大小(以八位字节为单位)。初始值为216-1(65,535)个八位字节。
|
||||
int initialWindowSize = 65535;
|
||||
|
||||
///表示发送方愿意接收的最大帧有效负载的大小(以八位字节为单位)。
|
||||
int maxFrameSize = 16384;
|
||||
|
||||
///建议设置通知对等方发送方准备接受的头列表的最大大小(以八位字节为单位)。
|
||||
///该值基于头字段的未压缩大小,包括名称和值的长度(以八位字节为单位)加上每个头字段32个八位字节的开销。
|
||||
int? maxHeaderListSize;
|
||||
}
|
||||
|
||||
class SettingHandler {
|
||||
static void handleSettingsFrame(ChannelContext channelContext, FrameHeader frameHeader, ByteBuf payload) {
|
||||
// SETTINGS frames must have a length that is a multiple of 6 bytes
|
||||
if (frameHeader.length % 6 != 0) {
|
||||
throw Exception("Invalid SETTINGS frame length");
|
||||
}
|
||||
|
||||
// If the SETTINGS frame has the ACK flag set, then it is an acknowledgement
|
||||
if (frameHeader.hasAckFlag) {
|
||||
// Handle SETTINGS ACK
|
||||
return;
|
||||
}
|
||||
var setting = channelContext.setting ??= StreamSetting();
|
||||
// Otherwise, it is a SETTINGS frame that carries settings
|
||||
while (payload.isReadable()) {
|
||||
int identifier = payload.readShort();
|
||||
int value = payload.readInt();
|
||||
// logger.d("SettingHandler.handleSettingsFrame identifier=$identifier value=$value");
|
||||
|
||||
// Handle the setting based on its identifier
|
||||
switch (identifier) {
|
||||
case 1: // SETTINGS_HEADER_TABLE_SIZE
|
||||
setting.maxFrameSize = value;
|
||||
break;
|
||||
case 2: // SETTINGS_ENABLE_PUSH
|
||||
setting.enablePush = value == 1;
|
||||
break;
|
||||
case 3: // SETTINGS_MAX_CONCURRENT_STREAMS
|
||||
setting.maxConcurrentStreams = value;
|
||||
break;
|
||||
case 4: // SETTINGS_INITIAL_WINDOW_SIZE
|
||||
setting.initialWindowSize = value;
|
||||
break;
|
||||
case 5: // SETTINGS_MAX_FRAME_SIZE
|
||||
setting.maxFrameSize = value;
|
||||
break;
|
||||
case 6: // SETTINGS_MAX_HEADER_LIST_SIZE
|
||||
setting.maxHeaderListSize = value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:network_proxy/network/host_port.dart';
|
||||
import 'package:network_proxy/network/http/websocket.dart';
|
||||
@@ -38,10 +39,11 @@ abstract class HttpMessage {
|
||||
"application/json": ContentType.json
|
||||
};
|
||||
|
||||
final String protocolVersion;
|
||||
String protocolVersion;
|
||||
|
||||
final HttpHeaders headers = HttpHeaders();
|
||||
int contentLength = -1;
|
||||
|
||||
int get contentLength => headers.contentLength;
|
||||
|
||||
//报文大小
|
||||
int? packageSize;
|
||||
@@ -49,6 +51,8 @@ abstract class HttpMessage {
|
||||
List<int>? body;
|
||||
String? remoteAddress;
|
||||
|
||||
String requestId = (DateTime.now().millisecondsSinceEpoch + Random().nextInt(99999)).toRadixString(16);
|
||||
int? streamId; // http2 streamId
|
||||
HttpMessage(this.protocolVersion);
|
||||
|
||||
//json序列化
|
||||
@@ -145,7 +149,6 @@ class HttpRequest extends HttpMessage {
|
||||
HttpRequest copy({String? uri}) {
|
||||
var request = HttpRequest(method, uri ?? this.uri, protocolVersion: protocolVersion);
|
||||
request.headers.addAll(headers);
|
||||
request.contentLength = contentLength;
|
||||
request.body = body;
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class HttpHeaders {
|
||||
return value.toLowerCase() == "true";
|
||||
}
|
||||
|
||||
int get contentLength => getInt(CONTENT_LENGTH) ?? -1;
|
||||
int get contentLength => getInt(CONTENT_LENGTH) ?? 0;
|
||||
|
||||
set contentLength(int contentLength) => set(CONTENT_LENGTH, contentLength.toString());
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:network_proxy/network/http/codec.dart';
|
||||
import 'package:network_proxy/network/http/constants.dart';
|
||||
import 'package:network_proxy/network/http/http_headers.dart';
|
||||
import 'package:network_proxy/network/util/byte_buf.dart';
|
||||
|
||||
/// http解析器
|
||||
class HttpParse {
|
||||
@@ -22,7 +24,7 @@ class HttpParse {
|
||||
}
|
||||
|
||||
if (initialLine.length != 3) {
|
||||
throw ParserException("parseLine error", String.fromCharCodes(data.buffer));
|
||||
throw ParserException("parseLine error", String.fromCharCodes(data.bytes));
|
||||
}
|
||||
|
||||
return initialLine;
|
||||
|
||||
@@ -28,15 +28,17 @@ import 'http/codec.dart';
|
||||
|
||||
class HttpClients {
|
||||
/// 建立连接
|
||||
static Future<Channel> startConnect(HostAndPort hostAndPort, ChannelHandler handler) async {
|
||||
static Future<Channel> startConnect(
|
||||
HostAndPort hostAndPort, ChannelHandler handler, ChannelContext channelContext) async {
|
||||
var client = Client()
|
||||
..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), handler));
|
||||
|
||||
return client.connect(hostAndPort);
|
||||
return client.connect(hostAndPort, channelContext);
|
||||
}
|
||||
|
||||
///代理建立连接
|
||||
static Future<Channel> proxyConnect(HostAndPort hostAndPort, ChannelHandler handler, {ProxyInfo? proxyInfo}) async {
|
||||
static Future<Channel> proxyConnect(HostAndPort hostAndPort, ChannelHandler handler, ChannelContext channelContext,
|
||||
{ProxyInfo? proxyInfo}) async {
|
||||
var client = Client()
|
||||
..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), handler));
|
||||
|
||||
@@ -46,7 +48,7 @@ class HttpClients {
|
||||
}
|
||||
|
||||
HostAndPort connectHost = proxyInfo == null ? hostAndPort : HostAndPort.host(proxyInfo.host, proxyInfo.port!);
|
||||
var channel = await client.connect(connectHost);
|
||||
var channel = await client.connect(connectHost, channelContext);
|
||||
|
||||
if (proxyInfo == null || !hostAndPort.isSsl()) {
|
||||
return channel;
|
||||
@@ -74,14 +76,14 @@ class HttpClients {
|
||||
}
|
||||
|
||||
/// 建立连接
|
||||
static Future<Channel> connect(Uri uri, ChannelHandler handler) async {
|
||||
static Future<Channel> connect(Uri uri, ChannelHandler handler, ChannelContext channelContext) async {
|
||||
Client client = Client()
|
||||
..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), handler));
|
||||
if (uri.scheme == "https" || uri.scheme == "wss") {
|
||||
return client.secureConnect(HostAndPort.of(uri.toString()));
|
||||
return client.secureConnect(HostAndPort.of(uri.toString()), channelContext);
|
||||
}
|
||||
|
||||
return client.connect(HostAndPort.of(uri.toString()));
|
||||
return client.connect(HostAndPort.of(uri.toString()), channelContext);
|
||||
}
|
||||
|
||||
/// 发送get请求
|
||||
@@ -98,7 +100,8 @@ class HttpClients {
|
||||
var client = Client()
|
||||
..initChannel((channel) => channel.pipeline.handle(HttpResponseCodec(), HttpRequestCodec(), httpResponseHandler));
|
||||
|
||||
Channel channel = await client.connect(hostAndPort);
|
||||
ChannelContext channelContext = ChannelContext();
|
||||
Channel channel = await client.connect(hostAndPort, channelContext);
|
||||
await channel.write(request);
|
||||
|
||||
return httpResponseHandler.getResponse(duration).whenComplete(() => channel.close());
|
||||
@@ -112,13 +115,16 @@ class HttpClients {
|
||||
request.headers.host = '${Uri.parse(request.uri).host}:${Uri.parse(request.uri).port}';
|
||||
} catch (_) {}
|
||||
}
|
||||
request.protocolVersion = 'HTTP/1.1';
|
||||
|
||||
ChannelContext channelContext = ChannelContext();
|
||||
var httpResponseHandler = HttpResponseHandler();
|
||||
HostAndPort hostPort = HostAndPort.of(request.uri);
|
||||
Channel channel = await proxyConnect(proxyInfo: proxyInfo, hostPort, httpResponseHandler);
|
||||
Channel channel = await proxyConnect(proxyInfo: proxyInfo, hostPort, httpResponseHandler, channelContext);
|
||||
|
||||
if (hostPort.isSsl()) {
|
||||
channel.secureSocket = await SecureSocket.secure(channel.socket, onBadCertificate: (certificate) => true);
|
||||
var secureSocket = await SecureSocket.secure(channel.socket, onBadCertificate: (certificate) => true);
|
||||
channel.secureSocket(secureSocket, channelContext);
|
||||
}
|
||||
|
||||
await channel.write(request);
|
||||
@@ -130,7 +136,7 @@ class HttpResponseHandler extends ChannelHandler<HttpResponse> {
|
||||
Completer<HttpResponse> _completer = Completer<HttpResponse>();
|
||||
|
||||
@override
|
||||
void channelRead(Channel channel, HttpResponse msg) {
|
||||
void channelRead(ChannelContext channelContext, Channel channel, HttpResponse msg) {
|
||||
// log.i("[${channel.id}] Response $msg");
|
||||
_completer.complete(msg);
|
||||
}
|
||||
@@ -144,7 +150,7 @@ class HttpResponseHandler extends ChannelHandler<HttpResponse> {
|
||||
}
|
||||
|
||||
@override
|
||||
void channelInactive(Channel channel) {
|
||||
void channelInactive(ChannelContext channelContext, Channel channel) {
|
||||
// log.i("[${channel.id}] channelInactive");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,112 +20,35 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:network_proxy/network/bin/configuration.dart';
|
||||
import 'package:network_proxy/network/channel.dart';
|
||||
import 'package:network_proxy/network/components/host_filter.dart';
|
||||
import 'package:network_proxy/network/handler.dart';
|
||||
import 'package:network_proxy/network/http_client.dart';
|
||||
import 'package:network_proxy/network/util/attribute_keys.dart';
|
||||
import 'package:network_proxy/network/util/crts.dart';
|
||||
import 'package:network_proxy/network/components/host_filter.dart';
|
||||
import 'package:network_proxy/network/util/tls.dart';
|
||||
import 'package:network_proxy/utils/platform.dart';
|
||||
|
||||
import 'host_port.dart';
|
||||
|
||||
class Network {
|
||||
abstract class Network {
|
||||
late Function _channelInitializer;
|
||||
Configuration? configuration;
|
||||
StreamSubscription? subscription;
|
||||
|
||||
Network initChannel(void Function(Channel channel) initializer) {
|
||||
_channelInitializer = initializer;
|
||||
return this;
|
||||
}
|
||||
|
||||
Channel listen(Socket socket) {
|
||||
var channel = Channel(socket);
|
||||
Channel listen(Channel channel, ChannelContext channelContext) {
|
||||
_channelInitializer.call(channel);
|
||||
channel.pipeline.channelActive(channel);
|
||||
subscription = socket.listen((data) => _onEvent(data, channel),
|
||||
onError: (error, StackTrace trace) => channel.pipeline.exceptionCaught(channel, error, trace: trace),
|
||||
onDone: () => channel.pipeline.channelInactive(channel));
|
||||
channel.pipeline.channelActive(channelContext, channel);
|
||||
|
||||
channel.socket.listen((data) => onEvent(data, channelContext, channel),
|
||||
onError: (error, StackTrace trace) =>
|
||||
channel.pipeline.exceptionCaught(channelContext, channel, error, trace: trace),
|
||||
onDone: () => channel.pipeline.channelInactive(channelContext, channel));
|
||||
return channel;
|
||||
}
|
||||
|
||||
_onEvent(Uint8List data, Channel channel) async {
|
||||
//手机扫码转发远程地址
|
||||
if (configuration?.remoteHost != null) {
|
||||
channel.putAttribute(AttributeKeys.remote, HostAndPort.of(configuration!.remoteHost!));
|
||||
}
|
||||
|
||||
//外部代理信息
|
||||
if (configuration?.externalProxy?.enabled == true) {
|
||||
ProxyInfo externalProxy = configuration!.externalProxy!;
|
||||
if (externalProxy.capturePacket == true) {
|
||||
channel.putAttribute(AttributeKeys.proxyInfo, externalProxy);
|
||||
} else {
|
||||
//不抓包直接转发
|
||||
channel.putAttribute(AttributeKeys.remote, HostAndPort.host(externalProxy.host, externalProxy.port!));
|
||||
}
|
||||
}
|
||||
|
||||
HostAndPort? hostAndPort = channel.getAttribute(AttributeKeys.host);
|
||||
|
||||
//黑名单 或 没开启https 直接转发
|
||||
if ((Platforms.isMobile() && HostFilter.filter(hostAndPort?.host)) ||
|
||||
(hostAndPort?.isSsl() == true && configuration?.enableSsl == false)) {
|
||||
relay(channel, channel.getAttribute(channel.id));
|
||||
channel.pipeline.channelRead(channel, data);
|
||||
return;
|
||||
}
|
||||
|
||||
//ssl握手
|
||||
if (hostAndPort?.isSsl() == true || TLS.isTLSClientHello(data)) {
|
||||
if (hostAndPort?.scheme == HostAndPort.httpScheme) {
|
||||
hostAndPort?.scheme = HostAndPort.httpsScheme;
|
||||
}
|
||||
ssl(channel, hostAndPort, data);
|
||||
return;
|
||||
}
|
||||
|
||||
channel.pipeline.channelRead(channel, data);
|
||||
}
|
||||
|
||||
/// ssl握手
|
||||
void ssl(Channel channel, HostAndPort? hostAndPort, Uint8List data) async {
|
||||
try {
|
||||
if (hostAndPort == null && TLS.getDomain(data) != null) {
|
||||
hostAndPort = HostAndPort.host(TLS.getDomain(data)!, 443);
|
||||
}
|
||||
channel.putAttribute(AttributeKeys.domain, hostAndPort?.host);
|
||||
|
||||
Channel? remoteChannel = channel.getAttribute(channel.id);
|
||||
|
||||
if (HostFilter.filter(hostAndPort?.host)) {
|
||||
remoteChannel = remoteChannel ?? await HttpClients.startConnect(hostAndPort!, RelayHandler(channel));
|
||||
relay(channel, remoteChannel);
|
||||
channel.pipeline.channelRead(channel, data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (remoteChannel != null && !remoteChannel.isSsl) {
|
||||
// var supportProtocols = TLS.supportProtocols(data);
|
||||
remoteChannel.secureSocket = await SecureSocket.secure(remoteChannel.socket,
|
||||
host: hostAndPort?.host, onBadCertificate: (certificate) => true);
|
||||
}
|
||||
|
||||
//ssl自签证书
|
||||
var certificate = await CertificateManager.getCertificateContext(hostAndPort!.host);
|
||||
// var selectedProtocol = remoteChannel?.selectedProtocol;
|
||||
// if (selectedProtocol != null) certificate.setAlpnProtocols([selectedProtocol], true);
|
||||
|
||||
//服务端等待客户端ssl握手
|
||||
channel.secureSocket = await SecureSocket.secureServer(channel.socket, certificate, bufferedData: data);
|
||||
} catch (error, trace) {
|
||||
if (error is HandshakeException) {
|
||||
channel.putAttribute(AttributeKeys.host, hostAndPort);
|
||||
}
|
||||
channel.pipeline.exceptionCaught(channel, error, trace: trace);
|
||||
}
|
||||
}
|
||||
Future<void> onEvent(Uint8List data, ChannelContext channelContext, Channel channel);
|
||||
|
||||
/// 转发请求
|
||||
void relay(Channel clientChannel, Channel remoteChannel) {
|
||||
@@ -136,17 +59,22 @@ class Network {
|
||||
}
|
||||
|
||||
class Server extends Network {
|
||||
Configuration configuration;
|
||||
|
||||
late ServerSocket serverSocket;
|
||||
bool isRunning = false;
|
||||
EventListener? listener;
|
||||
|
||||
Server(Configuration configuration) {
|
||||
super.configuration = configuration;
|
||||
}
|
||||
Server(this.configuration, {this.listener});
|
||||
|
||||
Future<ServerSocket> bind(int port) async {
|
||||
serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, port);
|
||||
serverSocket.listen((socket) {
|
||||
listen(socket);
|
||||
var channel = Channel(socket);
|
||||
ChannelContext channelContext = ChannelContext();
|
||||
channelContext.clientChannel = channel;
|
||||
channelContext.listener = listener;
|
||||
listen(channel, channelContext);
|
||||
});
|
||||
isRunning = true;
|
||||
return serverSocket;
|
||||
@@ -158,10 +86,93 @@ class Server extends Network {
|
||||
await serverSocket.close();
|
||||
return serverSocket;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onEvent(Uint8List data, ChannelContext channelContext, Channel channel) async {
|
||||
//手机扫码转发远程地址
|
||||
if (configuration.remoteHost != null) {
|
||||
channelContext.putAttribute(AttributeKeys.remote, HostAndPort.of(configuration.remoteHost!));
|
||||
}
|
||||
|
||||
//外部代理信息
|
||||
if (configuration.externalProxy?.enabled == true) {
|
||||
ProxyInfo externalProxy = configuration.externalProxy!;
|
||||
if (externalProxy.capturePacket == true) {
|
||||
channelContext.putAttribute(AttributeKeys.proxyInfo, externalProxy);
|
||||
} else {
|
||||
//不抓包直接转发
|
||||
channelContext.putAttribute(AttributeKeys.remote, HostAndPort.host(externalProxy.host, externalProxy.port!));
|
||||
}
|
||||
}
|
||||
|
||||
HostAndPort? hostAndPort = channelContext.host;
|
||||
|
||||
//黑名单 或 没开启https 直接转发
|
||||
if ((Platforms.isMobile() && HostFilter.filter(hostAndPort?.host)) ||
|
||||
(hostAndPort?.isSsl() == true && configuration.enableSsl == false)) {
|
||||
var remoteChannel = channelContext.serverChannel ??
|
||||
await channelContext.connectServerChannel(hostAndPort!, RelayHandler(channel));
|
||||
relay(channel, remoteChannel);
|
||||
channel.pipeline.channelRead(channelContext, channel, data);
|
||||
return;
|
||||
}
|
||||
|
||||
//ssl握手
|
||||
if (hostAndPort?.isSsl() == true || TLS.isTLSClientHello(data)) {
|
||||
if (hostAndPort?.scheme == HostAndPort.httpScheme) {
|
||||
hostAndPort?.scheme = HostAndPort.httpsScheme;
|
||||
}
|
||||
ssl(channelContext, channel, data);
|
||||
return;
|
||||
}
|
||||
|
||||
channel.pipeline.channelRead(channelContext, channel, data);
|
||||
}
|
||||
|
||||
/// ssl握手
|
||||
void ssl(ChannelContext channelContext, Channel channel, Uint8List data) async {
|
||||
var hostAndPort = channelContext.host;
|
||||
try {
|
||||
if (hostAndPort == null && TLS.getDomain(data) != null) {
|
||||
hostAndPort = HostAndPort.host(TLS.getDomain(data)!, 443);
|
||||
}
|
||||
channelContext.putAttribute(AttributeKeys.domain, hostAndPort?.host);
|
||||
|
||||
Channel? remoteChannel = channelContext.serverChannel;
|
||||
|
||||
if (HostFilter.filter(hostAndPort?.host)) {
|
||||
remoteChannel = remoteChannel ?? await channelContext.connectServerChannel(hostAndPort!, RelayHandler(channel));
|
||||
relay(channel, remoteChannel);
|
||||
channel.pipeline.channelRead(channelContext, channel, data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (remoteChannel != null && !remoteChannel.isSsl) {
|
||||
var supportProtocols = configuration.enabledHttp2 ? TLS.supportProtocols(data) : null;
|
||||
var secureSocket = await SecureSocket.secure(remoteChannel.socket,
|
||||
supportedProtocols: supportProtocols, host: hostAndPort?.host, onBadCertificate: (certificate) => true);
|
||||
remoteChannel.secureSocket(secureSocket, channelContext);
|
||||
}
|
||||
|
||||
//ssl自签证书
|
||||
var certificate = await CertificateManager.getCertificateContext(hostAndPort!.host);
|
||||
var selectedProtocol = remoteChannel?.selectedProtocol;
|
||||
if (selectedProtocol != null) certificate.setAlpnProtocols([selectedProtocol], true);
|
||||
|
||||
//服务端等待客户端ssl握手
|
||||
var secureSocket = await SecureSocket.secureServer(channel.socket, certificate, bufferedData: data);
|
||||
channel.secureSocket(secureSocket, channelContext);
|
||||
} catch (error, trace) {
|
||||
if (error is HandshakeException) {
|
||||
channelContext.host = hostAndPort;
|
||||
}
|
||||
channel.pipeline.exceptionCaught(channelContext, channel, error, trace: trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Client extends Network {
|
||||
Future<Channel> connect(HostAndPort hostAndPort) async {
|
||||
Future<Channel> connect(HostAndPort hostAndPort, ChannelContext channelContext) async {
|
||||
String host = hostAndPort.host;
|
||||
//说明支持ipv6
|
||||
if (host.startsWith("[") && host.endsWith(']')) {
|
||||
@@ -172,13 +183,24 @@ class Client extends Network {
|
||||
if (socket.address.type != InternetAddressType.unix) {
|
||||
socket.setOption(SocketOption.tcpNoDelay, true);
|
||||
}
|
||||
return listen(socket);
|
||||
var channel = Channel(socket);
|
||||
channelContext.serverChannel = channel;
|
||||
return listen(channel, channelContext);
|
||||
});
|
||||
}
|
||||
|
||||
/// ssl连接
|
||||
Future<Channel> secureConnect(HostAndPort hostAndPort) async {
|
||||
Future<Channel> secureConnect(HostAndPort hostAndPort, ChannelContext channelContext) async {
|
||||
return SecureSocket.connect(hostAndPort.host, hostAndPort.port,
|
||||
timeout: const Duration(seconds: 3), onBadCertificate: (certificate) => true).then((socket) => listen(socket));
|
||||
timeout: const Duration(seconds: 3), onBadCertificate: (certificate) => true).then((socket) {
|
||||
var channel = Channel(socket);
|
||||
channelContext.serverChannel = channel;
|
||||
return listen(channel, channelContext);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onEvent(Uint8List data, ChannelContext channelContext, Channel channel) async {
|
||||
channel.pipeline.channelRead(channelContext, channel, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,13 @@ import 'package:network_proxy/network/host_port.dart';
|
||||
import 'package:network_proxy/network/http/codec.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
import 'package:network_proxy/network/http/http_headers.dart';
|
||||
import 'package:network_proxy/network/util/attribute_keys.dart';
|
||||
import 'package:network_proxy/network/util/file_read.dart';
|
||||
|
||||
import 'components/host_filter.dart';
|
||||
|
||||
class ProxyHelper {
|
||||
|
||||
//请求本服务
|
||||
static localRequest(HttpRequest msg, Channel channel) async {
|
||||
static localRequest(HttpRequest msg, Channel channel) async {
|
||||
//获取配置
|
||||
if (msg.path() == '/config') {
|
||||
final requestRewrites = await RequestRewrites.instance;
|
||||
@@ -46,7 +44,7 @@ class ProxyHelper {
|
||||
}
|
||||
|
||||
/// 下载证书
|
||||
static void crtDownload(Channel channel, HttpRequest request) async {
|
||||
static void crtDownload(Channel channel, HttpRequest request) async {
|
||||
const String fileMimeType = 'application/x-x509-ca-cert';
|
||||
var response = HttpResponse(HttpStatus.ok);
|
||||
response.headers.set(HttpHeaders.CONTENT_TYPE, fileMimeType);
|
||||
@@ -65,8 +63,9 @@ class ProxyHelper {
|
||||
}
|
||||
|
||||
///异常处理
|
||||
static exceptionHandler(Channel channel, EventListener? listener, HttpRequest? request, error) {
|
||||
HostAndPort? hostAndPort = channel.getAttribute(AttributeKeys.host);
|
||||
static exceptionHandler(
|
||||
ChannelContext channelContext, Channel channel, EventListener? listener, HttpRequest? request, error) {
|
||||
HostAndPort? hostAndPort = channelContext.host;
|
||||
hostAndPort ??= HostAndPort.host(scheme: HostAndPort.httpScheme, channel.remoteAddress.host, channel.remotePort);
|
||||
String message = error.toString();
|
||||
HttpStatus status = HttpStatus(-1, message);
|
||||
@@ -89,10 +88,11 @@ class ProxyHelper {
|
||||
..headers.contentType = 'text/plain'
|
||||
..headers.contentLength = message.codeUnits.length
|
||||
..body = message.codeUnits;
|
||||
request.response?.request = request;
|
||||
|
||||
channel.putAttribute(AttributeKeys.host, hostAndPort);
|
||||
channelContext.host = hostAndPort;
|
||||
|
||||
listener?.onRequest(channel, request);
|
||||
listener?.onResponse(channel, request.response!);
|
||||
listener?.onResponse(channelContext, request.response!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
82
lib/network/util/byte_buf.dart
Normal file
82
lib/network/util/byte_buf.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
///类似于netty ByteBuf
|
||||
class ByteBuf {
|
||||
BytesBuilder _buffer = BytesBuilder();
|
||||
int readerIndex = 0;
|
||||
|
||||
Uint8List get bytes => _buffer.toBytes();
|
||||
|
||||
int get length => _buffer.length;
|
||||
|
||||
ByteBuf([List<int>? bytes]) {
|
||||
if (bytes != null) _buffer.add(bytes);
|
||||
}
|
||||
|
||||
///添加
|
||||
void add(List<int> bytes) {
|
||||
_buffer.add(bytes);
|
||||
}
|
||||
|
||||
///清空
|
||||
clear() {
|
||||
_buffer.clear();
|
||||
readerIndex = 0;
|
||||
}
|
||||
|
||||
///释放
|
||||
clearRead() {
|
||||
var takeBytes = _buffer.takeBytes();
|
||||
_buffer.add(Uint8List.sublistView(takeBytes, readerIndex, takeBytes.length));
|
||||
readerIndex = 0;
|
||||
}
|
||||
|
||||
bool isReadable() => readerIndex < _buffer.length;
|
||||
|
||||
///可读字节数
|
||||
int readableBytes() {
|
||||
return _buffer.length - readerIndex;
|
||||
}
|
||||
|
||||
///读取所有可用字节
|
||||
Uint8List readAvailableBytes() {
|
||||
return readBytes(readableBytes());
|
||||
}
|
||||
|
||||
///读取字节
|
||||
Uint8List readBytes(int length) {
|
||||
Uint8List list = bytes.sublist(readerIndex, readerIndex + length);
|
||||
readerIndex += length;
|
||||
return list;
|
||||
}
|
||||
|
||||
///跳过
|
||||
skipBytes(int length) {
|
||||
readerIndex += length;
|
||||
}
|
||||
|
||||
///读取字节
|
||||
int read() {
|
||||
return bytes[readerIndex++];
|
||||
}
|
||||
|
||||
///读取字节
|
||||
int readByte() {
|
||||
return bytes[readerIndex++];
|
||||
}
|
||||
|
||||
int readShort() {
|
||||
int value = bytes[readerIndex++] << 8 | bytes[readerIndex++];
|
||||
return value;
|
||||
}
|
||||
|
||||
int readInt() {
|
||||
int value =
|
||||
bytes[readerIndex++] << 24 | bytes[readerIndex++] << 16 | bytes[readerIndex++] << 8 | bytes[readerIndex++];
|
||||
return value;
|
||||
}
|
||||
|
||||
int get(int index) {
|
||||
return bytes[index];
|
||||
}
|
||||
}
|
||||
@@ -184,6 +184,8 @@ class NetworkTabState extends State<NetworkTabController> with SingleTickerProvi
|
||||
const SizedBox(height: 20),
|
||||
rowWidget("Request Method", request.method.name),
|
||||
const SizedBox(height: 20),
|
||||
rowWidget("Protocol", request.protocolVersion),
|
||||
const SizedBox(height: 20),
|
||||
rowWidget("Status Code", response?.status.toString()),
|
||||
const SizedBox(height: 20),
|
||||
rowWidget("Remote Address", response?.remoteAddress),
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:network_proxy/network/http/websocket.dart';
|
||||
import 'package:network_proxy/ui/component/state_component.dart';
|
||||
import 'package:network_proxy/ui/component/toolbox.dart';
|
||||
import 'package:network_proxy/ui/content/panel.dart';
|
||||
import 'package:network_proxy/ui/desktop/left/domain.dart';
|
||||
import 'package:network_proxy/ui/desktop/left/list.dart';
|
||||
import 'package:network_proxy/ui/desktop/left/favorite.dart';
|
||||
import 'package:network_proxy/ui/desktop/left/history.dart';
|
||||
import 'package:network_proxy/ui/desktop/toolbar/toolbar.dart';
|
||||
@@ -45,8 +45,8 @@ class _DesktopHomePagePageState extends State<DesktopHomePage> implements EventL
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(Channel channel, HttpResponse response) {
|
||||
domainStateKey.currentState!.addResponse(channel, response);
|
||||
void onResponse(ChannelContext channelContext, HttpResponse response) {
|
||||
domainStateKey.currentState!.addResponse(channelContext, response);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -71,7 +71,7 @@ class _DesktopHomePagePageState extends State<DesktopHomePage> implements EventL
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final domainWidget = DomainWidget(key: domainStateKey, proxyServer: proxyServer, panel: panel);
|
||||
final domainWidget = DomainList(key: domainStateKey, proxyServer: proxyServer, panel: panel);
|
||||
return Scaffold(
|
||||
appBar: Tab(child: Toolbar(proxyServer, domainStateKey, sideNotifier: _selectIndex)),
|
||||
body: Row(
|
||||
|
||||
@@ -18,7 +18,7 @@ import 'package:network_proxy/ui/component/widgets.dart';
|
||||
import 'package:network_proxy/utils/har.dart';
|
||||
|
||||
import '../../content/panel.dart';
|
||||
import 'domain.dart';
|
||||
import 'list.dart';
|
||||
|
||||
///历史记录
|
||||
class HistoryPageWidget extends StatelessWidget {
|
||||
@@ -59,7 +59,7 @@ class HistoryPageWidget extends StatelessWidget {
|
||||
)),
|
||||
body: futureWidget(HistoryStorage.instance.then((value) => value.getRequests(item)), (data) {
|
||||
print("START ${DateTime.now()}");
|
||||
return DomainWidget(panel: panel, proxyServer: proxyServer, list: data, shrinkWrap: false);
|
||||
return DomainList(panel: panel, proxyServer: proxyServer, list: data, shrinkWrap: false);
|
||||
}, loading: true));
|
||||
}
|
||||
}
|
||||
@@ -287,7 +287,7 @@ class WriteTask extends EventListener {
|
||||
void onRequest(Channel channel, HttpRequest request) {}
|
||||
|
||||
@override
|
||||
void onResponse(Channel channel, HttpResponse response) {
|
||||
void onResponse(ChannelContext channelContext, HttpResponse response) {
|
||||
if (response.request == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,26 +5,24 @@ import 'package:flutter_toastr/flutter_toastr.dart';
|
||||
import 'package:network_proxy/network/bin/configuration.dart';
|
||||
import 'package:network_proxy/network/bin/server.dart';
|
||||
import 'package:network_proxy/network/channel.dart';
|
||||
import 'package:network_proxy/network/host_port.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
import 'package:network_proxy/network/util/attribute_keys.dart';
|
||||
import 'package:network_proxy/network/components/host_filter.dart';
|
||||
import 'package:network_proxy/network/http/http.dart';
|
||||
import 'package:network_proxy/ui/component/transition.dart';
|
||||
import 'package:network_proxy/ui/component/utils.dart';
|
||||
import 'package:network_proxy/ui/component/widgets.dart';
|
||||
import 'package:network_proxy/ui/content/panel.dart';
|
||||
import 'package:network_proxy/ui/desktop/left/model/search_model.dart';
|
||||
import 'package:network_proxy/ui/desktop/left/path.dart';
|
||||
import 'package:network_proxy/ui/desktop/left/request.dart';
|
||||
import 'package:network_proxy/ui/desktop/left/search.dart';
|
||||
|
||||
///左侧域名
|
||||
class DomainWidget extends StatefulWidget {
|
||||
class DomainList extends StatefulWidget {
|
||||
final NetworkTabController panel;
|
||||
final ProxyServer proxyServer;
|
||||
final List<HttpRequest>? list;
|
||||
final bool shrinkWrap;
|
||||
|
||||
const DomainWidget({super.key, required this.panel, required this.proxyServer, this.list, this.shrinkWrap = true});
|
||||
const DomainList({super.key, required this.panel, required this.proxyServer, this.list, this.shrinkWrap = true});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
@@ -32,10 +30,10 @@ class DomainWidget extends StatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class DomainWidgetState extends State<DomainWidget> with AutomaticKeepAliveClientMixin {
|
||||
class DomainWidgetState extends State<DomainList> with AutomaticKeepAliveClientMixin {
|
||||
List<HttpRequest> container = [];
|
||||
LinkedHashMap<HostAndPort, HeaderBody> containerMap = LinkedHashMap<HostAndPort, HeaderBody>();
|
||||
LinkedHashMap<HostAndPort, HeaderBody> searchView = LinkedHashMap<HostAndPort, HeaderBody>();
|
||||
LinkedHashMap<String, DomainRequests> containerMap = LinkedHashMap<String, DomainRequests>();
|
||||
LinkedHashMap<String, DomainRequests> searchView = LinkedHashMap<String, DomainRequests>();
|
||||
|
||||
//搜索的内容
|
||||
SearchModel? searchModel;
|
||||
@@ -56,15 +54,15 @@ class DomainWidgetState extends State<DomainWidget> with AutomaticKeepAliveClien
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.list?.forEach((request) {
|
||||
var host = HostAndPort.of(request.requestUrl);
|
||||
HeaderBody? headerBody = containerMap[host];
|
||||
if (headerBody == null) {
|
||||
headerBody = HeaderBody(host, proxyServer: widget.panel.proxyServer, onRemove: () => remove(host));
|
||||
containerMap[host] = headerBody;
|
||||
var host = request.remoteDomain()!;
|
||||
DomainRequests? domainRequests = containerMap[host];
|
||||
if (domainRequests == null) {
|
||||
domainRequests = DomainRequests(host, proxyServer: widget.panel.proxyServer, onRemove: () => remove(host));
|
||||
containerMap[host] = domainRequests;
|
||||
}
|
||||
var listURI =
|
||||
PathRow(request, widget.panel, proxyServer: widget.panel.proxyServer, remove: (it) => headerBody!.remove(it));
|
||||
headerBody.addBody(null, listURI);
|
||||
var listURI = RequestWidget(request, widget.panel,
|
||||
proxyServer: widget.panel.proxyServer, remove: (it) => domainRequests!.remove(it));
|
||||
domainRequests.addBody(null, listURI);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,8 +84,7 @@ class DomainWidgetState extends State<DomainWidget> with AutomaticKeepAliveClien
|
||||
|
||||
Widget body = widget.shrinkWrap
|
||||
? SingleChildScrollView(child: Column(children: list.toList()))
|
||||
: ListView.builder(
|
||||
itemCount: list.length, cacheExtent: 1000, itemBuilder: (_, index) => list.elementAt(index));
|
||||
: ListView.builder(itemCount: list.length, cacheExtent: 1000, itemBuilder: (_, index) => list.elementAt(index));
|
||||
|
||||
return Scaffold(
|
||||
body: body,
|
||||
@@ -99,13 +96,13 @@ class DomainWidgetState extends State<DomainWidget> with AutomaticKeepAliveClien
|
||||
}
|
||||
|
||||
///搜索过滤
|
||||
LinkedHashMap<HostAndPort, HeaderBody> searchFilter(SearchModel searchModel) {
|
||||
LinkedHashMap<HostAndPort, HeaderBody> result = LinkedHashMap<HostAndPort, HeaderBody>();
|
||||
LinkedHashMap<String, DomainRequests> searchFilter(SearchModel searchModel) {
|
||||
LinkedHashMap<String, DomainRequests> result = LinkedHashMap<String, DomainRequests>();
|
||||
|
||||
containerMap.forEach((key, headerBody) {
|
||||
var body = headerBody.search(searchModel);
|
||||
containerMap.forEach((key, domainRequests) {
|
||||
var body = domainRequests.search(searchModel);
|
||||
if (body.isNotEmpty) {
|
||||
result[key] = headerBody.copy(body: body, selected: searchView[key]?.currentSelected);
|
||||
result[key] = domainRequests.copy(body: body, selected: searchView[key]?.currentSelected);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -115,46 +112,46 @@ class DomainWidgetState extends State<DomainWidget> with AutomaticKeepAliveClien
|
||||
///添加请求
|
||||
add(Channel channel, HttpRequest request) {
|
||||
container.add(request);
|
||||
HostAndPort? hostAndPort = request.hostAndPort;
|
||||
if (hostAndPort == null) {
|
||||
String? host = request.remoteDomain();
|
||||
if (host == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//按照域名分类
|
||||
HeaderBody? headerBody = containerMap[hostAndPort];
|
||||
if (headerBody != null) {
|
||||
var listURI =
|
||||
PathRow(request, widget.panel, proxyServer: widget.proxyServer, remove: (it) => headerBody!.remove(it));
|
||||
headerBody.addBody(channel.id, listURI);
|
||||
DomainRequests? domainRequests = containerMap[host];
|
||||
if (domainRequests != null) {
|
||||
var requestWidget = RequestWidget(request, widget.panel,
|
||||
proxyServer: widget.proxyServer, remove: (it) => domainRequests!.remove(it));
|
||||
domainRequests.addBody(request.requestId, requestWidget);
|
||||
|
||||
//搜索视图
|
||||
if (searchModel?.isNotEmpty == true && searchModel?.filter(request, null) == true) {
|
||||
searchView[hostAndPort]?.addBody(channel.id, listURI);
|
||||
searchView[host]?.addBody(request.requestId, requestWidget);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
headerBody = HeaderBody(hostAndPort, proxyServer: widget.proxyServer, onRemove: () => remove(hostAndPort));
|
||||
var listURI =
|
||||
PathRow(request, widget.panel, proxyServer: widget.proxyServer, remove: (it) => headerBody!.remove(it));
|
||||
headerBody.addBody(channel.id, listURI);
|
||||
domainRequests = DomainRequests(host, proxyServer: widget.proxyServer, onRemove: () => remove(host));
|
||||
var requestWidget = RequestWidget(request, widget.panel,
|
||||
proxyServer: widget.proxyServer, remove: (it) => domainRequests!.remove(it));
|
||||
domainRequests.addBody(request.requestId, requestWidget);
|
||||
setState(() {
|
||||
containerMap[hostAndPort] = headerBody!;
|
||||
containerMap[host] = domainRequests!;
|
||||
});
|
||||
}
|
||||
|
||||
remove(HostAndPort hostAndPort) {
|
||||
remove(String host) {
|
||||
setState(() {
|
||||
containerMap.remove(hostAndPort);
|
||||
container.removeWhere((element) => element.hostAndPort == hostAndPort);
|
||||
containerMap.remove(host);
|
||||
container.removeWhere((element) => element.remoteDomain() == host);
|
||||
});
|
||||
}
|
||||
|
||||
///添加响应
|
||||
addResponse(Channel channel, HttpResponse response) {
|
||||
HostAndPort hostAndPort = channel.getAttribute(AttributeKeys.host);
|
||||
HeaderBody? headerBody = containerMap[hostAndPort];
|
||||
var pathRow = headerBody?.getBody(channel.id);
|
||||
addResponse(ChannelContext channelContext, HttpResponse response) {
|
||||
String domain = channelContext.host!.domain;
|
||||
DomainRequests? domainRequests = containerMap[domain];
|
||||
var pathRow = domainRequests?.getRequest(response);
|
||||
pathRow?.add(response);
|
||||
if (pathRow == null) {
|
||||
return;
|
||||
@@ -162,11 +159,11 @@ class DomainWidgetState extends State<DomainWidget> with AutomaticKeepAliveClien
|
||||
|
||||
//搜索视图
|
||||
if (searchModel?.isNotEmpty == true && searchModel?.filter(pathRow.request, response) == true) {
|
||||
var header = searchView[hostAndPort];
|
||||
if (header?.getBody(channel.id) == null) {
|
||||
header?.addBody(channel.id, pathRow);
|
||||
var requests = searchView[domain];
|
||||
if (requests?.getRequest(response) == null) {
|
||||
requests?.addBody(response.requestId, pathRow);
|
||||
}
|
||||
headerBody?.getBody(channel.id)?.add(response);
|
||||
requests?.getRequest(response)?.add(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,17 +178,15 @@ class DomainWidgetState extends State<DomainWidget> with AutomaticKeepAliveClien
|
||||
}
|
||||
|
||||
///标题和内容布局 标题是域名 内容是域名下请求
|
||||
class HeaderBody extends StatefulWidget {
|
||||
final stateKey = GlobalKey<_HeaderBodyState>();
|
||||
|
||||
class DomainRequests extends StatefulWidget {
|
||||
//请求ID和请求的映射
|
||||
final Map<String, PathRow> channelIdPathMap = HashMap<String, PathRow>();
|
||||
final Map<String, RequestWidget> requestMap = HashMap<String, RequestWidget>();
|
||||
|
||||
final HostAndPort header;
|
||||
final String domain;
|
||||
final ProxyServer proxyServer;
|
||||
|
||||
//请求列表
|
||||
final Queue<PathRow> body = Queue();
|
||||
final Queue<RequestWidget> body = Queue();
|
||||
|
||||
//是否选中
|
||||
final bool selected;
|
||||
@@ -199,39 +194,39 @@ class HeaderBody extends StatefulWidget {
|
||||
//移除回调
|
||||
final Function()? onRemove;
|
||||
|
||||
HeaderBody(this.header, {this.selected = false, this.onRemove, required this.proxyServer})
|
||||
: super(key: GlobalKey<_HeaderBodyState>());
|
||||
DomainRequests(this.domain, {this.selected = false, this.onRemove, required this.proxyServer})
|
||||
: super(key: GlobalKey<_DomainRequestsState>());
|
||||
|
||||
///添加请求
|
||||
void addBody(String? key, PathRow widget) {
|
||||
void addBody(String? requestId, RequestWidget widget) {
|
||||
body.addFirst(widget);
|
||||
if (key == null) {
|
||||
if (requestId == null) {
|
||||
return;
|
||||
}
|
||||
channelIdPathMap[key] = widget;
|
||||
requestMap[requestId] = widget;
|
||||
changeState();
|
||||
}
|
||||
|
||||
PathRow? getBody(String key) {
|
||||
return channelIdPathMap[key];
|
||||
RequestWidget? getRequest(HttpResponse response) {
|
||||
return requestMap[response.request?.requestId ?? response.requestId];
|
||||
}
|
||||
|
||||
remove(PathRow pathRow) {
|
||||
if (body.remove(pathRow)) {
|
||||
remove(RequestWidget requestWidget) {
|
||||
if (body.remove(requestWidget)) {
|
||||
changeState();
|
||||
}
|
||||
}
|
||||
|
||||
///根据文本过滤
|
||||
Iterable<PathRow> search(SearchModel searchModel) {
|
||||
Iterable<RequestWidget> search(SearchModel searchModel) {
|
||||
return body
|
||||
.where((element) => searchModel.filter(element.request, element.response.get() ?? element.request.response));
|
||||
}
|
||||
|
||||
///复制
|
||||
HeaderBody copy({Iterable<PathRow>? body, bool? selected}) {
|
||||
var state = key as GlobalKey<_HeaderBodyState>;
|
||||
var headerBody = HeaderBody(header,
|
||||
DomainRequests copy({Iterable<RequestWidget>? body, bool? selected}) {
|
||||
var state = key as GlobalKey<_DomainRequestsState>;
|
||||
var headerBody = DomainRequests(domain,
|
||||
selected: selected ?? state.currentState?.selected == true, onRemove: onRemove, proxyServer: proxyServer);
|
||||
if (body != null) {
|
||||
headerBody.body.addAll(body);
|
||||
@@ -240,22 +235,22 @@ class HeaderBody extends StatefulWidget {
|
||||
}
|
||||
|
||||
bool get currentSelected {
|
||||
var state = key as GlobalKey<_HeaderBodyState>;
|
||||
var state = key as GlobalKey<_DomainRequestsState>;
|
||||
return state.currentState?.selected == true;
|
||||
}
|
||||
|
||||
changeState() {
|
||||
var state = key as GlobalKey<_HeaderBodyState>;
|
||||
var state = key as GlobalKey<_DomainRequestsState>;
|
||||
state.currentState?.changeState();
|
||||
}
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _HeaderBodyState();
|
||||
return _DomainRequestsState();
|
||||
}
|
||||
}
|
||||
|
||||
class _HeaderBodyState extends State<HeaderBody> {
|
||||
class _DomainRequestsState extends State<DomainRequests> {
|
||||
final GlobalKey<ColorTransitionState> transitionState = GlobalKey<ColorTransitionState>();
|
||||
late Configuration configuration;
|
||||
late bool selected;
|
||||
@@ -275,7 +270,7 @@ class _HeaderBodyState extends State<HeaderBody> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: [
|
||||
_hostWidget(widget.header.domain),
|
||||
_hostWidget(widget.domain),
|
||||
Offstage(offstage: !selected, child: Column(children: widget.body.toList()))
|
||||
]);
|
||||
}
|
||||
@@ -318,7 +313,7 @@ class _HeaderBodyState extends State<HeaderBody> {
|
||||
height: 35,
|
||||
child: const Text("添加黑名单", style: TextStyle(fontSize: 13)),
|
||||
onTap: () {
|
||||
HostFilter.blacklist.add(widget.header.host);
|
||||
HostFilter.blacklist.add(Uri.parse(widget.domain).host);
|
||||
configuration.flushConfig();
|
||||
FlutterToastr.show('添加成功', context);
|
||||
}),
|
||||
@@ -326,7 +321,7 @@ class _HeaderBodyState extends State<HeaderBody> {
|
||||
height: 35,
|
||||
child: const Text("添加白名单", style: TextStyle(fontSize: 13)),
|
||||
onTap: () {
|
||||
HostFilter.whitelist.add(widget.header.host);
|
||||
HostFilter.whitelist.add(Uri.parse(widget.domain).host);
|
||||
configuration.flushConfig();
|
||||
FlutterToastr.show('添加成功', context);
|
||||
}),
|
||||
@@ -334,7 +329,7 @@ class _HeaderBodyState extends State<HeaderBody> {
|
||||
height: 35,
|
||||
child: const Text("删除白名单", style: TextStyle(fontSize: 13)),
|
||||
onTap: () {
|
||||
HostFilter.whitelist.remove(widget.header.host);
|
||||
HostFilter.whitelist.remove(Uri.parse(widget.domain).host);
|
||||
configuration.flushConfig();
|
||||
FlutterToastr.show('删除成功', context);
|
||||
}),
|
||||
@@ -346,7 +341,7 @@ class _HeaderBodyState extends State<HeaderBody> {
|
||||
}
|
||||
|
||||
_delete() {
|
||||
widget.channelIdPathMap.clear();
|
||||
widget.requestMap.clear();
|
||||
widget.body.clear();
|
||||
widget.onRemove?.call();
|
||||
FlutterToastr.show('删除成功', context);
|
||||
@@ -19,31 +19,31 @@ import 'package:network_proxy/utils/lang.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
///请求 URI
|
||||
class PathRow extends StatefulWidget {
|
||||
class RequestWidget extends StatefulWidget {
|
||||
final Color? color;
|
||||
final HttpRequest request;
|
||||
final ValueWrap<HttpResponse> response = ValueWrap();
|
||||
|
||||
final NetworkTabController panel;
|
||||
final ProxyServer proxyServer;
|
||||
final Function(PathRow)? remove;
|
||||
final Function(RequestWidget)? remove;
|
||||
|
||||
PathRow(this.request, this.panel, {Key? key, this.color = Colors.green, required this.proxyServer, this.remove})
|
||||
: super(key: GlobalKey<_PathRowState>());
|
||||
RequestWidget(this.request, this.panel, {Key? key, this.color = Colors.green, required this.proxyServer, this.remove})
|
||||
: super(key: GlobalKey<_RequestWidgetState>());
|
||||
|
||||
@override
|
||||
State<PathRow> createState() => _PathRowState();
|
||||
State<RequestWidget> createState() => _RequestWidgetState();
|
||||
|
||||
void add(HttpResponse response) {
|
||||
this.response.set(response);
|
||||
var state = key as GlobalKey<_PathRowState>;
|
||||
var state = key as GlobalKey<_RequestWidgetState>;
|
||||
state.currentState?.changeState();
|
||||
}
|
||||
}
|
||||
|
||||
class _PathRowState extends State<PathRow> {
|
||||
class _RequestWidgetState extends State<RequestWidget> {
|
||||
//选择的节点
|
||||
static _PathRowState? selectedState;
|
||||
static _RequestWidgetState? selectedState;
|
||||
|
||||
bool selected = false;
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:network_proxy/ui/launch/launch.dart';
|
||||
import 'package:network_proxy/utils/ip.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import '../left/domain.dart';
|
||||
import '../left/list.dart';
|
||||
|
||||
class Toolbar extends StatefulWidget {
|
||||
final ProxyServer proxyServer;
|
||||
|
||||
@@ -76,8 +76,8 @@ class MobileHomeState extends State<MobileHomePage> implements EventListener, Li
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(Channel channel, HttpResponse response) {
|
||||
requestStateKey.currentState!.addResponse(channel, response);
|
||||
void onResponse(ChannelContext channelContext, HttpResponse response) {
|
||||
requestStateKey.currentState!.addResponse(channelContext, response);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -251,7 +251,7 @@ class WriteTask extends EventListener {
|
||||
void onRequest(Channel channel, HttpRequest request) {}
|
||||
|
||||
@override
|
||||
void onResponse(Channel channel, HttpResponse response) {
|
||||
void onResponse(ChannelContext channelContext, HttpResponse response) {
|
||||
if (response.request == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,8 +96,7 @@ class RequestListState extends State<RequestListWidget> {
|
||||
}
|
||||
|
||||
///添加响应
|
||||
addResponse(Channel channel, HttpResponse response) {
|
||||
response.request?.response = response;
|
||||
addResponse(ChannelContext channelContext, HttpResponse response) {
|
||||
requestSequenceKey.currentState?.addResponse(response);
|
||||
domainListKey.currentState?.addResponse(response);
|
||||
}
|
||||
@@ -173,7 +172,6 @@ class RequestSequenceState extends State<RequestSequence> with AutomaticKeepAliv
|
||||
|
||||
///添加响应
|
||||
addResponse(HttpResponse response) {
|
||||
response.request?.response = response;
|
||||
var state = indexes.remove(response.request);
|
||||
state?.currentState?.change(response);
|
||||
|
||||
|
||||
20
pubspec.lock
20
pubspec.lock
@@ -437,10 +437,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mocktail
|
||||
sha256: bac151b31e4ed78bd59ab89aa4c0928f297b1180186d5daf03734519e5f596c1
|
||||
sha256: f603ebd85a576e5914870b02e5839fc5d0243b867bf710651cf239a28ebb365e
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
version: "1.0.2"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -698,10 +698,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd"
|
||||
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "3.1.1"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -722,26 +722,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "138bd45b3a456dcfafc46d1a146787424f8d2edfbf2809c9324361e58f851cf7"
|
||||
sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
version: "2.2.2"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc"
|
||||
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "3.1.1"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921
|
||||
sha256: bb55f38968b9427ce5dcdb8aaaa41049282195e0cfa4cf48593572fa3d1f36bc
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.2.1"
|
||||
version: "4.3.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user