From ba4072691efce94845010ca7ba5d5162e92f87a6 Mon Sep 17 00:00:00 2001 From: wanghongenpin Date: Thu, 28 Dec 2023 16:12:44 +0800 Subject: [PATCH] =?UTF-8?q?http2=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/network/bin/configuration.dart | 2 + lib/network/bin/server.dart | 7 +- lib/network/channel.dart | 200 ++++++--- .../components/request_rewrite_manager.dart | 5 +- lib/network/handler.dart | 87 ++-- lib/network/http/body_reader.dart | 1 + lib/network/http/codec.dart | 119 ++--- lib/network/http/constants.dart | 16 + lib/network/http/h2/codec.dart | 360 +++++++++++++++ lib/network/http/h2/frame.dart | 88 ++++ lib/network/http/h2/hpack.dart | 305 +++++++++++++ lib/network/http/h2/huffman.dart | 417 ++++++++++++++++++ lib/network/http/h2/setting.dart | 87 ++++ lib/network/http/http.dart | 9 +- lib/network/http/http_headers.dart | 2 +- lib/network/http/http_parser.dart | 4 +- lib/network/http_client.dart | 30 +- lib/network/network.dart | 212 +++++---- lib/network/proxy_helper.dart | 18 +- lib/network/util/byte_buf.dart | 82 ++++ lib/ui/content/panel.dart | 2 + lib/ui/desktop/desktop.dart | 8 +- lib/ui/desktop/left/history.dart | 6 +- .../desktop/left/{domain.dart => list.dart} | 147 +++--- .../desktop/left/{path.dart => request.dart} | 16 +- lib/ui/desktop/toolbar/toolbar.dart | 2 +- lib/ui/mobile/mobile.dart | 4 +- lib/ui/mobile/request/history.dart | 2 +- lib/ui/mobile/request/list.dart | 4 +- pubspec.lock | 20 +- 30 files changed, 1850 insertions(+), 412 deletions(-) create mode 100644 lib/network/http/constants.dart create mode 100644 lib/network/http/h2/codec.dart create mode 100644 lib/network/http/h2/frame.dart create mode 100644 lib/network/http/h2/hpack.dart create mode 100644 lib/network/http/h2/huffman.dart create mode 100644 lib/network/http/h2/setting.dart create mode 100644 lib/network/util/byte_buf.dart rename lib/ui/desktop/left/{domain.dart => list.dart} (61%) rename lib/ui/desktop/left/{path.dart => request.dart} (92%) diff --git a/lib/network/bin/configuration.dart b/lib/network/bin/configuration.dart index f6c4227..5f373e9 100644 --- a/lib/network/bin/configuration.dart +++ b/lib/network/bin/configuration.dart @@ -51,6 +51,8 @@ class Configuration { //远程连接 不持久化保存 String? remoteHost; + bool enabledHttp2 = false; // + Configuration._(); /// 单例 diff --git a/lib/network/bin/server.dart b/lib/network/bin/server.dart index 3836744..dd93934 100644 --- a/lib/network/bin/server.dart +++ b/lib/network/bin/server.dart @@ -66,11 +66,10 @@ class ProxyServer { /// 启动代理服务 Future 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); } } diff --git a/lib/network/channel.dart b/lib/network/channel.dart index 33e4c1c..799cf52 100644 --- a/lib/network/channel.dart +++ b/lib/network/channel.dart @@ -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 { 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 _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 write(Object obj) async { + var data = pipeline._encoder.encode(obj); + await writeBytes(data); + } + + Future writeBytes(List 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 _attributes = {}; + + //和本地客户端的连接 + Channel? clientChannel; + + //和远程服务端的连接 + Channel? serverChannel; + + EventListener? listener; + + //http2 stream + final Map>> _streams = {}; + + ChannelContext(); + + //创建服务端连接 + Future 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(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 { late Decoder _decoder; late Encoder _encoder; late ChannelHandler handler; - EventListener? listener; final ByteBuf buffer = ByteBuf(); @@ -177,17 +246,16 @@ class ChannelPipeline extends ChannelHandler { } /// 监听 - 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 { } ///远程转发请求 - 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 { +class RawCodec extends Codec { @override - Object? decode(ByteBuf data, {bool resolveBody = true}) { - return data.readBytes(data.readableBytes()); + DecoderResult decode(ChannelContext channelContext, ByteBuf byteBuf, {bool resolveBody = true}) { + var decoderResult = DecoderResult()..data = byteBuf.readAvailableBytes(); + return decoderResult; } @override - List encode(Object data) { + List encode(dynamic data) { return data as List; } } diff --git a/lib/network/components/request_rewrite_manager.dart b/lib/network/components/request_rewrite_manager.dart index 86dfb78..395039e 100644 --- a/lib/network/components/request_rewrite_manager.dart +++ b/lib/network/components/request_rewrite_manager.dart @@ -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; } } diff --git a/lib/network/handler.dart b/lib/network/handler.dart index 527b0db..d7ef286 100644 --- a/lib/network/handler.dart +++ b/lib/network/handler.dart @@ -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 { 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 { } //代理转发请求 - 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 forward(Channel channel, HttpRequest httpRequest) async { - // log.i("[${channel.id}] ${httpRequest.method.name} ${httpRequest.requestUrl}"); + Future 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 { 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 { } //重定向 - Future redirect(Channel channel, HttpRequest httpRequest, String redirectUrl) async { + Future 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 _getRemoteChannel(Channel clientChannel, HttpRequest httpRequest) async { - String clientId = clientChannel.id; + Future _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 { } /// 连接远程 - Future connectRemote(Channel clientChannel, HostAndPort connectHost) async { + Future 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 { 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 { //重写响应 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 { 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 { 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 { 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}"); } } diff --git a/lib/network/http/body_reader.dart b/lib/network/http/body_reader.dart index f795dee..13d05fd 100644 --- a/lib/network/http/body_reader.dart +++ b/lib/network/http/body_reader.dart @@ -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'; diff --git a/lib/network/http/codec.dart b/lib/network/http/codec.dart index 9cc8e76..766d20e 100644 --- a/lib/network/http/codec.dart +++ b/lib/network/http/codec.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 { + bool isDone = true; + T? data; - int _readerIndex = 0; + //转发消息 + List? forward; - Uint8List get buffer => _buffer.toBytes(); - - int get length => _buffer.length; - - ///添加 - void add(List 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 { /// 解码 如果返回null说明数据不完整 - T? decode(ByteBuf byteBuf, {bool resolveBody = true}); + DecoderResult decode(ChannelContext channelContext, ByteBuf byteBuf); } /// 编码 @@ -130,60 +79,76 @@ abstract class Codec implements Decoder, Encoder { /// http编解码 abstract class HttpCodec implements Codec { final HttpParse _httpParse = HttpParse(); + Http2Codec? _h2Codec; State _state = State.readInitial; - late T message; + late DecoderResult result; BodyReader? bodyReader; T createMessage(List reqLine); + Http2Codec getH2Codec() { + return _h2Codec ??= (this is HttpRequestCodec ? Http2RequestDecoder() : Http2ResponseDecoder()) as Http2Codec; + } + @override - T? decode(ByteBuf data, {bool resolveBody = true}) { + DecoderResult 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 encode(T message) { + if (message.streamId != null) { + return getH2Codec().encode(message); + } + BytesBuilder builder = BytesBuilder(); //请求行 initialLine(builder, message); @@ -195,10 +160,13 @@ abstract class HttpCodec implements Codec { //请求头 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 implements Codec { //读取请求头 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 implements Codec { if (bytes == null) { return null; } - if (message.headers.isGzip) { + if (result.data!.headers.isGzip) { bytes = gzipDecode(bytes); } return bytes; diff --git a/lib/network/http/constants.dart b/lib/network/http/constants.dart new file mode 100644 index 0000000..b85f3a3 --- /dev/null +++ b/lib/network/http/constants.dart @@ -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; +} diff --git a/lib/network/http/h2/codec.dart b/lib/network/http/h2/codec.dart new file mode 100644 index 0000000..b5c8d24 --- /dev/null +++ b/lib/network/http/h2/codec.dart @@ -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 implements Codec { + static const maxFrameSize = 16384; + + static final List connectionPrefacePRI = "PRI * HTTP/2.0".codeUnits; + HPACKDecoder decoder = HPACKDecoder(); + HPACKEncoder encoder = HPACKEncoder(); + + T createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map headers); + + T? getMessage(ChannelContext channelContext, FrameHeader frameHeader); + + @override + DecoderResult 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 result = DecoderResult(); + result.forward = byteBuf.readAvailableBytes(); + return result; + } + + while (byteBuf.isReadable()) { + DecoderResult result = DecoderResult(isDone: false); + FrameHeader? frameHeader = FrameReader._readFrameHeader(byteBuf); + if (frameHeader == null) { + return result; + } + + List? 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(isDone: false); + } + + DecoderResult parseHttp2Packet(ChannelContext channelContext, FrameHeader frameHeader, ByteBuf framePayload) { + var result = DecoderResult(); + // 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 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
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 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 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 _parseHeaders(ChannelContext channelContext, List payload) { + if (channelContext.setting != null) { + decoder.updateTableSize(channelContext.setting!.headTableSize); + } + + // Decode the headers + List
headers = decoder.decode(payload); + + // Convert the headers to a map + Map headerMap = {}; + for (Header header in headers) { + headerMap[header.name] = header.value; + } + + return headerMap; + } +} + +class Http2RequestDecoder extends Http2Codec { + @override + HttpRequest createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map 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
encodeHeaders(HttpRequest message) { + var headers =
[]; + 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 { + @override + HttpResponse createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map 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
encodeHeaders(HttpResponse message) { + var headers =
[]; + 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? _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); + } +} diff --git a/lib/network/http/h2/frame.dart b/lib/network/http/h2/frame.dart new file mode 100644 index 0000000..8372fbf --- /dev/null +++ b/lib/network/http/h2/frame.dart @@ -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 encode() { + var result = []; + 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 _intToBytes(int value, int byteCount) { + var bytes = []; + 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 headerBlockFragment; + + HeadersFrame(super.header, this.padLength, this.exclusiveDependency, this.streamDependency, this.weight, + this.headerBlockFragment); +} + +class DataFrame extends Frame { + final int padLength; + final List data; + + DataFrame(super.header, this.padLength, this.data); +} diff --git a/lib/network/http/h2/hpack.dart b/lib/network/http/h2/hpack.dart new file mode 100644 index 0000000..7a23401 --- /dev/null +++ b/lib/network/http/h2/hpack.dart @@ -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
decode(List bytes) { + var headers =
[]; + 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 encodeList(List
headers) { + var bytesBuilder = BytesBuilder(); + + for (var header in headers) { + _encodeHeader(bytesBuilder, header); + } + + return bytesBuilder.takeBytes(); + } + + List 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
_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
_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; +} diff --git a/lib/network/http/h2/huffman.dart b/lib/network/http/h2/huffman.dart new file mode 100644 index 0000000..e1581fc --- /dev/null +++ b/lib/network/http/h2/huffman.dart @@ -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 decode(List 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 _codewords; + + HuffmanEncoder() : _codewords = _huffmanTable; + + //http2协议规范 huffman编码 + List encode(List 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 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 _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); +} diff --git a/lib/network/http/h2/setting.dart b/lib/network/http/h2/setting.dart new file mode 100644 index 0000000..b46ffc4 --- /dev/null +++ b/lib/network/http/h2/setting.dart @@ -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; + } + } + } +} diff --git a/lib/network/http/http.dart b/lib/network/http/http.dart index 4d17d0e..39d4d7b 100644 --- a/lib/network/http/http.dart +++ b/lib/network/http/http.dart @@ -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? 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; } diff --git a/lib/network/http/http_headers.dart b/lib/network/http/http_headers.dart index fa9abed..ba810f0 100644 --- a/lib/network/http/http_headers.dart +++ b/lib/network/http/http_headers.dart @@ -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()); diff --git a/lib/network/http/http_parser.dart b/lib/network/http/http_parser.dart index 608dd89..17b73df 100644 --- a/lib/network/http/http_parser.dart +++ b/lib/network/http/http_parser.dart @@ -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; diff --git a/lib/network/http_client.dart b/lib/network/http_client.dart index e2a537b..9708b45 100644 --- a/lib/network/http_client.dart +++ b/lib/network/http_client.dart @@ -28,15 +28,17 @@ import 'http/codec.dart'; class HttpClients { /// 建立连接 - static Future startConnect(HostAndPort hostAndPort, ChannelHandler handler) async { + static Future 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 proxyConnect(HostAndPort hostAndPort, ChannelHandler handler, {ProxyInfo? proxyInfo}) async { + static Future 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 connect(Uri uri, ChannelHandler handler) async { + static Future 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 { Completer _completer = Completer(); @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 { } @override - void channelInactive(Channel channel) { + void channelInactive(ChannelContext channelContext, Channel channel) { // log.i("[${channel.id}] channelInactive"); } } diff --git a/lib/network/network.dart b/lib/network/network.dart index 4f5c2e1..9db7931 100644 --- a/lib/network/network.dart +++ b/lib/network/network.dart @@ -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 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 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 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 connect(HostAndPort hostAndPort) async { + Future 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 secureConnect(HostAndPort hostAndPort) async { + Future 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 onEvent(Uint8List data, ChannelContext channelContext, Channel channel) async { + channel.pipeline.channelRead(channelContext, channel, data); } } diff --git a/lib/network/proxy_helper.dart b/lib/network/proxy_helper.dart index 65d7ddc..e93eaa1 100644 --- a/lib/network/proxy_helper.dart +++ b/lib/network/proxy_helper.dart @@ -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!); } -} \ No newline at end of file +} diff --git a/lib/network/util/byte_buf.dart b/lib/network/util/byte_buf.dart new file mode 100644 index 0000000..4730447 --- /dev/null +++ b/lib/network/util/byte_buf.dart @@ -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? bytes]) { + if (bytes != null) _buffer.add(bytes); + } + + ///添加 + void add(List 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]; + } +} diff --git a/lib/ui/content/panel.dart b/lib/ui/content/panel.dart index 4832d16..66f8065 100644 --- a/lib/ui/content/panel.dart +++ b/lib/ui/content/panel.dart @@ -184,6 +184,8 @@ class NetworkTabState extends State 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), diff --git a/lib/ui/desktop/desktop.dart b/lib/ui/desktop/desktop.dart index 3ef2528..ce67e2c 100644 --- a/lib/ui/desktop/desktop.dart +++ b/lib/ui/desktop/desktop.dart @@ -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 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 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( diff --git a/lib/ui/desktop/left/history.dart b/lib/ui/desktop/left/history.dart index 108ee11..013d78b 100644 --- a/lib/ui/desktop/left/history.dart +++ b/lib/ui/desktop/left/history.dart @@ -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; } diff --git a/lib/ui/desktop/left/domain.dart b/lib/ui/desktop/left/list.dart similarity index 61% rename from lib/ui/desktop/left/domain.dart rename to lib/ui/desktop/left/list.dart index 1bfe3bc..8bf8850 100644 --- a/lib/ui/desktop/left/domain.dart +++ b/lib/ui/desktop/left/list.dart @@ -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? 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 createState() { @@ -32,10 +30,10 @@ class DomainWidget extends StatefulWidget { } } -class DomainWidgetState extends State with AutomaticKeepAliveClientMixin { +class DomainWidgetState extends State with AutomaticKeepAliveClientMixin { List container = []; - LinkedHashMap containerMap = LinkedHashMap(); - LinkedHashMap searchView = LinkedHashMap(); + LinkedHashMap containerMap = LinkedHashMap(); + LinkedHashMap searchView = LinkedHashMap(); //搜索的内容 SearchModel? searchModel; @@ -56,15 +54,15 @@ class DomainWidgetState extends State 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 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 with AutomaticKeepAliveClien } ///搜索过滤 - LinkedHashMap searchFilter(SearchModel searchModel) { - LinkedHashMap result = LinkedHashMap(); + LinkedHashMap searchFilter(SearchModel searchModel) { + LinkedHashMap result = LinkedHashMap(); - 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 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 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 with AutomaticKeepAliveClien } ///标题和内容布局 标题是域名 内容是域名下请求 -class HeaderBody extends StatefulWidget { - final stateKey = GlobalKey<_HeaderBodyState>(); - +class DomainRequests extends StatefulWidget { //请求ID和请求的映射 - final Map channelIdPathMap = HashMap(); + final Map requestMap = HashMap(); - final HostAndPort header; + final String domain; final ProxyServer proxyServer; //请求列表 - final Queue body = Queue(); + final Queue 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 search(SearchModel searchModel) { + Iterable search(SearchModel searchModel) { return body .where((element) => searchModel.filter(element.request, element.response.get() ?? element.request.response)); } ///复制 - HeaderBody copy({Iterable? body, bool? selected}) { - var state = key as GlobalKey<_HeaderBodyState>; - var headerBody = HeaderBody(header, + DomainRequests copy({Iterable? 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 createState() { - return _HeaderBodyState(); + return _DomainRequestsState(); } } -class _HeaderBodyState extends State { +class _DomainRequestsState extends State { final GlobalKey transitionState = GlobalKey(); late Configuration configuration; late bool selected; @@ -275,7 +270,7 @@ class _HeaderBodyState extends State { @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 { 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 { 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 { 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 { } _delete() { - widget.channelIdPathMap.clear(); + widget.requestMap.clear(); widget.body.clear(); widget.onRemove?.call(); FlutterToastr.show('删除成功', context); diff --git a/lib/ui/desktop/left/path.dart b/lib/ui/desktop/left/request.dart similarity index 92% rename from lib/ui/desktop/left/path.dart rename to lib/ui/desktop/left/request.dart index 1f8452b..adc5200 100644 --- a/lib/ui/desktop/left/path.dart +++ b/lib/ui/desktop/left/request.dart @@ -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 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 createState() => _PathRowState(); + State 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 { +class _RequestWidgetState extends State { //选择的节点 - static _PathRowState? selectedState; + static _RequestWidgetState? selectedState; bool selected = false; diff --git a/lib/ui/desktop/toolbar/toolbar.dart b/lib/ui/desktop/toolbar/toolbar.dart index 2f7f374..433bce6 100644 --- a/lib/ui/desktop/toolbar/toolbar.dart +++ b/lib/ui/desktop/toolbar/toolbar.dart @@ -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; diff --git a/lib/ui/mobile/mobile.dart b/lib/ui/mobile/mobile.dart index edb4edc..738ec38 100644 --- a/lib/ui/mobile/mobile.dart +++ b/lib/ui/mobile/mobile.dart @@ -76,8 +76,8 @@ class MobileHomeState extends State 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 diff --git a/lib/ui/mobile/request/history.dart b/lib/ui/mobile/request/history.dart index f268804..008f607 100644 --- a/lib/ui/mobile/request/history.dart +++ b/lib/ui/mobile/request/history.dart @@ -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; } diff --git a/lib/ui/mobile/request/list.dart b/lib/ui/mobile/request/list.dart index 31ffce4..2687a30 100644 --- a/lib/ui/mobile/request/list.dart +++ b/lib/ui/mobile/request/list.dart @@ -96,8 +96,7 @@ class RequestListState extends State { } ///添加响应 - 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 with AutomaticKeepAliv ///添加响应 addResponse(HttpResponse response) { - response.request?.response = response; var state = indexes.remove(response.request); state?.currentState?.change(response); diff --git a/pubspec.lock b/pubspec.lock index caa01db..92d38bf 100644 --- a/pubspec.lock +++ b/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: