support proxy http2 (#388)(#51)

This commit is contained in:
wanghongenpin
2025-05-07 14:50:55 +08:00
parent 34b15a0c5c
commit 493fcc3ea0
19 changed files with 737 additions and 529 deletions

View File

@@ -17,6 +17,7 @@
"password": "Password",
"proxySetting": "Proxy Setting",
"systemProxy": "Set as System Proxy",
"enabledHTTP2": "Enable HTTP2",
"serverNotStart": "Proxy server not started",
"download": "Download",

View File

@@ -17,6 +17,7 @@
"password": "密码",
"proxySetting": "代理设置",
"systemProxy": "设置为系统代理",
"enabledHTTP2": "启用HTTP2",
"serverNotStart": "未开启抓包",
"download": "下载",

View File

@@ -55,7 +55,7 @@ class Configuration {
//远程连接 不持久化保存
String? remoteHost;
bool enabledHttp2 = false; //
bool enabledHttp2 = false; // 是否启用http2
//历史记录缓存时间
int historyCacheTime = 0;
@@ -88,6 +88,8 @@ class Configuration {
startup = config['startup'] ?? Platforms.isDesktop();
enableSystemProxy = config['enableSystemProxy'] ?? (config['enableDesktop'] ?? true);
enableSocks5 = config['enableSocks5'] ?? true;
enabledHttp2 = config['enabledHttp2'] ?? false;
proxyPassDomains = config['proxyPassDomains'] ?? SystemProxy.proxyPassDomains;
historyCacheTime = config['historyCacheTime'] ?? 0;
if (config['externalProxy'] != null) {
@@ -147,6 +149,7 @@ class Configuration {
'appWhitelistEnabled': appWhitelistEnabled,
'appBlacklist': appBlacklist,
'historyCacheTime': historyCacheTime,
'enabledHttp2': enabledHttp2,
'whitelist': HostFilter.whitelist.toJson(),
'blacklist': HostFilter.blacklist.toJson(),
};

View File

@@ -115,6 +115,10 @@ class Channel {
await Future.delayed(const Duration(milliseconds: 100));
}
if (isWriting) {
logger.w("[$id] write busy");
}
isWriting = true;
try {
if (!isClosed) {

View File

@@ -180,7 +180,7 @@ class Server extends Network {
var secureSocket = await SecureSocket.secureServer(channel.socket, certificate, bufferedData: data);
channel.serverSecureSocket(secureSocket, channelContext);
} catch (error, trace) {
logger.e('$hostAndPort ssl error', error: error);
logger.e('[${channel.id}] $hostAndPort ssl error', error: error, stackTrace: trace);
try {
channelContext.processInfo ??=
await ProcessInfoUtils.getProcessByPort(channel.remoteSocketAddress, hostAndPort?.domain ?? 'unknown');

View File

@@ -21,7 +21,7 @@ import 'package:proxypin/network/channel/channel_context.dart';
import 'package:proxypin/network/channel/host_port.dart';
import 'package:proxypin/network/http/body_reader.dart';
import 'package:proxypin/network/http/constants.dart';
import 'package:proxypin/network/http/h2/codec.dart';
import 'package:proxypin/network/http/h2/h2_codec.dart';
import 'package:proxypin/network/http/http_parser.dart';
import 'package:proxypin/network/util/byte_buf.dart';
import 'package:proxypin/network/util/compress.dart';
@@ -94,7 +94,8 @@ abstract class HttpCodec<T extends HttpMessage> implements Codec<T, T> {
@override
DecoderResult<T> decode(ChannelContext channelContext, ByteBuf data) {
if (channelContext.serverChannel?.selectedProtocol == HttpConstants.h2) {
var protocol = channelContext.serverChannel?.selectedProtocol;
if (protocol == HttpConstants.h2 || protocol == HttpConstants.h2_14) {
return getH2Codec().decode(channelContext, data);
}

View File

@@ -1,6 +1,7 @@
class HttpConstants {
//h2协议
static const String h2 = 'h2';
static const String h2_14 = 'h2-14';
/// Line feed character /n
static const int lf = 10;

View File

@@ -19,22 +19,25 @@ import 'dart:typed_data';
import 'package:proxypin/network/channel/channel_context.dart';
import 'package:proxypin/network/http/codec.dart';
import 'package:proxypin/network/http/h2/hpack.dart';
import 'package:proxypin/network/http/h2/setting.dart';
import 'package:proxypin/network/http/http.dart';
import 'package:proxypin/network/util/byte_buf.dart';
import 'package:proxypin/network/util/logger.dart';
import 'frame.dart';
import 'hpack/hpack.dart';
/// http编解码
abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
static const maxFrameSize = 16384;
static final List<int> connectionPrefacePRI = "PRI * HTTP/2.0".codeUnits;
HPACKDecoder decoder = HPACKDecoder();
HPACKEncoder encoder = HPACKEncoder();
T createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map<String, String> headers);
HPackDecoder decoder = HPackDecoder();
HPackEncoder encoder = HPackEncoder();
T createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map<String, List<String>> headers);
T? getMessage(ChannelContext channelContext, FrameHeader frameHeader);
@@ -53,6 +56,8 @@ abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
while (byteBuf.isReadable()) {
DecoderResult<T> result = DecoderResult<T>(isDone: false);
FrameHeader? frameHeader = FrameReader._readFrameHeader(byteBuf);
// logger.d("${frameHeader?.streamIdentifier} frame ${frameHeader?.length} ${byteBuf.readableBytes()}");
if (frameHeader == null) {
return result;
}
@@ -63,7 +68,7 @@ abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
return result;
}
result = parseHttp2Packet(channelContext, frameHeader, ByteBuf(framePayload));
result = parseHttp2Packet(channelContext, frameHeader, framePayload);
if (result.isDone) {
return result;
}
@@ -72,27 +77,29 @@ abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
return DecoderResult<T>(isDone: false);
}
DecoderResult<T> parseHttp2Packet(ChannelContext channelContext, FrameHeader frameHeader, ByteBuf framePayload) {
DecoderResult<T> parseHttp2Packet(ChannelContext channelContext, FrameHeader frameHeader, List<int> framePayload) {
var result = DecoderResult<T>();
// logger.d("streamId: ${frameHeader.streamIdentifier} ${frameHeader.type} endHeaders: ${frameHeader.hasEndHeadersFlag} "
// "endStream: ${frameHeader.hasEndStreamFlag}");
// logger.d(
// "${this is Http2RequestDecoder ? 'request' : 'response'} streamId: ${frameHeader.streamIdentifier} ${frameHeader.type} endHeaders: ${frameHeader.hasEndHeadersFlag} "
// "endStream: ${frameHeader.hasEndStreamFlag} ${frameHeader.length}");
//
switch (frameHeader.type) {
case FrameType.headers:
//HEADERS帧
_handleHeadersFrame(channelContext, frameHeader, framePayload);
_handleHeadersFrame(channelContext, frameHeader, ByteBuf(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());
logger.e("CONTINUATION frame but no message found");
result.forward = List.from(frameHeader.encode())..addAll(framePayload);
return result;
}
Map<String, String> headers = _parseHeaders(channelContext, framePayload.readBytes(frameHeader.length));
headers.forEach((key, value) => message.headers.add(key, value));
Map<String, List<String>> headers = _parseHeaders(channelContext, framePayload);
headers.forEach((key, values) => message.headers.addValues(key, values));
if (frameHeader.hasEndHeadersFlag &&
channelContext.getStreamRequest(frameHeader.streamIdentifier)?.method == HttpMethod.head) {
@@ -101,16 +108,16 @@ abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
break;
case FrameType.data:
//DATA帧
_handleDataFrame(channelContext, frameHeader, framePayload);
_handleDataFrame(channelContext, frameHeader, ByteBuf(framePayload));
result.isDone = frameHeader.hasEndStreamFlag;
break;
case FrameType.settings:
SettingHandler.handleSettingsFrame(channelContext, frameHeader, framePayload);
result.forward = List.from(frameHeader.encode())..addAll(framePayload.bytes);
SettingHandler.handleSettingsFrame(channelContext, frameHeader, ByteBuf(framePayload));
result.forward = List.from(frameHeader.encode())..addAll(framePayload);
return result;
default:
//
result.forward = List.from(frameHeader.encode())..addAll(framePayload.bytes);
result.forward = List.from(frameHeader.encode())..addAll(framePayload);
return result;
}
@@ -132,34 +139,38 @@ abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
@override
Uint8List encode(T data) {
var bytesBuilder = BytesBuilder();
// data.headers.contentLength = data.body?.length ?? 0;
var emptyBody = data.body == null || data.body!.isEmpty;
//headers
var headers = encodeHeaders(data);
BytesBuilder headerBlock = BytesBuilder();
// 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;
var headerBlock = encoder.encode(headers);
_writeFrame(bytesBuilder, frameType, flags, data.streamId!, headerBlock.takeBytes());
headerBlock.add(encode);
}
// for (var header in headers) {
// //header分裂
// if (headerBlock.length + encode.length < maxFrameSize) {
// headerBlock.add(encode);
// continue;
// }
//
// FrameType frameType = firstFrame ? FrameType.headers : FrameType.continuation;
// int flags = frameType == FrameType.headers && emptyBody ? 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;
int flags = frameType == FrameType.headers && emptyBody ? FrameHeader.flagsEndStream : 0;
flags |= FrameHeader.flagsEndHeaders;
_writeFrame(bytesBuilder, frameType, flags, data.streamId!, headerBlock.takeBytes());
_writeFrame(bytesBuilder, frameType, flags, data.streamId!, headerBlock);
//body
if (data.body != null) {
if (data.body?.isNotEmpty == true) {
var payload = data.body!;
while (payload.length > maxFrameSize) {
var chunkSize = min(maxFrameSize, payload.length);
@@ -169,6 +180,9 @@ abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
}
_writeFrame(bytesBuilder, FrameType.data, FrameHeader.flagsEndStream, data.streamId!, payload);
} else if (frameType != FrameType.headers && emptyBody) {
//bodyDATA帧
_writeFrame(bytesBuilder, FrameType.data, FrameHeader.flagsEndStream, data.streamId!, []);
}
return bytesBuilder.takeBytes();
@@ -176,8 +190,8 @@ abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
void _writeFrame(BytesBuilder bytesBuilder, FrameType type, int flag, int streamId, List<int> payload) {
FrameHeader frameHeader = FrameHeader(payload.length, type, flag, streamId);
// logger.d("_writeFrame streamId: ${frameHeader.streamIdentifier} ${frameHeader.type} endHeaders: ${frameHeader
// .hasEndHeadersFlag} endStream: ${frameHeader.hasEndStreamFlag}");
// logger.d(
// "${this is Http2RequestDecoder ? 'request' : 'response'} _writeFrame streamId: ${frameHeader.streamIdentifier} ${frameHeader.type} flags:${frameHeader.flags} endHeaders: ${frameHeader.hasEndHeadersFlag} endStream: ${frameHeader.hasEndStreamFlag} ${payload.length}");
bytesBuilder.add(frameHeader.encode());
bytesBuilder.add(payload);
}
@@ -201,8 +215,10 @@ abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
if (frameHeader.hasPaddedFlag) {
padLength = payload.readByte();
}
frameHeader.length;
int dataLength = payload.readableBytes() - padLength;
payload.skipBytes(padLength);
//
int dataLength = payload.readableBytes();
var data = payload.readBytes(dataLength);
var message = getMessage(channelContext, frameHeader)!;
if (message.body == null) {
@@ -234,7 +250,8 @@ abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
weight = payload.readByte(); // weight
}
var headerBlockLength = payload.length - payload.readerIndex - padLength;
payload.skipBytes(padLength);
var headerBlockLength = payload.length - payload.readerIndex;
if (headerBlockLength < 0) {
throw Exception("headerBlockLength < 0");
}
@@ -242,31 +259,34 @@ abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
var blockFragment = payload.readBytes(headerBlockLength);
//
Map<String, String> headers = _parseHeaders(channelContext, blockFragment);
Map<String, List<String>> headers = _parseHeaders(channelContext, blockFragment);
T message = createMessage(channelContext, frameHeader, headers);
headers.forEach((key, value) {
headers.forEach((key, values) {
if (!key.startsWith(":")) {
message.headers.add(key, value);
message.headers.addValues(key, values);
}
});
return HeadersFrame(frameHeader, padLength, exclusiveDependency, streamDependency, weight, blockFragment);
}
Map<String, String> _parseHeaders(ChannelContext channelContext, List<int> payload) {
Map<String, List<String>> _parseHeaders(ChannelContext channelContext, List<int> payload) {
if (channelContext.setting != null) {
decoder.updateTableSize(channelContext.setting!.headTableSize);
decoder.updateMaxReceivingHeaderTableSize(channelContext.setting!.headTableSize);
}
// Decode the headers
List<Header> headers = decoder.decode(payload);
// Convert the headers to a map
Map<String, String> headerMap = {};
Map<String, List<String>> headerMap = {};
for (Header header in headers) {
headerMap[header.name] = header.value;
final name = header.nameString;
final value = header.valueString;
headerMap[name] ??= [];
headerMap[name]!.add(value);
}
return headerMap;
@@ -275,9 +295,10 @@ abstract class Http2Codec<T extends HttpMessage> implements Codec<T, T> {
class Http2RequestDecoder extends Http2Codec<HttpRequest> {
@override
HttpRequest createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map<String, String> headers) {
HttpMethod httpMethod = HttpMethod.valueOf(headers[":method"]!);
var httpRequest = HttpRequest(httpMethod, headers[":path"]!, protocolVersion: headers[":version"] ?? "HTTP/2");
HttpRequest createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map<String, List<String>> headers) {
HttpMethod httpMethod = HttpMethod.valueOf(headers[":method"]!.first);
var httpRequest =
HttpRequest(httpMethod, headers[":path"]!.first, protocolVersion: headers[":version"]?.firstOrNull ?? "HTTP/2");
var old = channelContext.putStreamRequest(frameHeader.streamIdentifier, httpRequest);
assert(old == null, "old request is not null");
return httpRequest;
@@ -292,14 +313,14 @@ class Http2RequestDecoder extends Http2Codec<HttpRequest> {
List<Header> encodeHeaders(HttpRequest message) {
var headers = <Header>[];
var uri = message.requestUri!;
headers.add(Header(":method", message.method.name));
headers.add(Header(":scheme", uri.scheme));
headers.add(Header(":authority", uri.host));
headers.add(Header(":path", message.uri));
headers.add(Header.ascii(":method", message.method.name));
headers.add(Header.ascii(":scheme", uri.scheme));
headers.add(Header.ascii(":authority", uri.host));
headers.add(Header.ascii(":path", message.uri));
message.headers.forEach((key, values) {
for (var value in values) {
headers.add(Header(key, value));
headers.add(Header.ascii(key, value));
}
});
return headers;
@@ -308,9 +329,10 @@ class Http2RequestDecoder extends Http2Codec<HttpRequest> {
class Http2ResponseDecoder extends Http2Codec<HttpResponse> {
@override
HttpResponse createMessage(ChannelContext channelContext, FrameHeader frameHeader, Map<String, String> headers) {
var httpResponse = HttpResponse(HttpStatus.valueOf(int.parse(headers[':status']!)),
protocolVersion: headers[":version"] ?? 'HTTP/2');
HttpResponse createMessage(
ChannelContext channelContext, FrameHeader frameHeader, Map<String, List<String>> headers) {
var httpResponse = HttpResponse(HttpStatus.valueOf(int.parse(headers[':status']!.first)),
protocolVersion: headers[":version"]?.firstOrNull ?? 'HTTP/2');
httpResponse.requestId = channelContext.getStreamRequest(frameHeader.streamIdentifier)!.requestId;
channelContext.putStreamResponse(frameHeader.streamIdentifier, httpResponse);
return httpResponse;
@@ -324,10 +346,10 @@ class Http2ResponseDecoder extends Http2Codec<HttpResponse> {
@override
List<Header> encodeHeaders(HttpResponse message) {
var headers = <Header>[];
headers.add(Header(":status", message.status.code.toString()));
headers.add(Header.ascii(":status", message.status.code.toString()));
message.headers.forEach((key, values) {
for (var value in values) {
headers.add(Header(key, value));
headers.add(Header.ascii(key, value));
}
});
return headers;
@@ -342,7 +364,9 @@ class FrameReader {
return null;
}
return data.readBytes(length);
var readBytes = data.readBytes(length);
data.clearRead();
return readBytes;
}
static FrameHeader? _readFrameHeader(ByteBuf data) {

View File

@@ -1,305 +0,0 @@
/*
* 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:proxypin/network/http/h2/huffman.dart';
import 'package:proxypin/network/util/byte_buf.dart';
class HPACKDecoder {
// static const int _maxHeaderTableSize = 12288;
static HuffmanDecoder huffmanDecoder = HuffmanDecoder();
final IndexTable _indexTable = IndexTable();
updateTableSize(int size) {
_indexTable._maxSize = size;
}
List<Header> decode(List<int> bytes) {
var headers = <Header>[];
var payload = ByteBuf(bytes);
while (payload.isReadable()) {
var header = _decodeHeader(payload);
if (header == null) continue;
headers.add(header);
}
return headers;
}
Header? _decodeHeader(ByteBuf framePayload) {
if (!framePayload.isReadable()) {
return null;
}
int firstByte = framePayload.get(framePayload.readerIndex);
if (firstByte & 0x80 == 0x80) {
// Indexed Header Field
int headerIndex = _decodeInteger(framePayload, 7);
return _indexTable[headerIndex];
} else if (firstByte & 0x40 == 0x40) {
// Literal Header Field with Incremental Indexing
int headerIndex = _decodeInteger(framePayload, 6);
Header header = _readHeaderField(framePayload, headerIndex);
_indexTable.add(header);
return header;
} else if (firstByte & 0x20 == 0x20) {
// Dynamic Table Size Update
int maxSize = _decodeInteger(framePayload, 5);
_indexTable._maxSize = maxSize;
} else if (firstByte & 0x10 == 0x10) {
// Literal Header Field never Indexed
int headerIndex = _decodeInteger(framePayload, 4);
Header header = _readHeaderField(framePayload, headerIndex, neverIndexed: true);
return header;
} else {
// Literal Header Field without Indexing
int headerIndex = _decodeInteger(framePayload, 4);
Header header = _readHeaderField(framePayload, headerIndex);
return header;
}
return null;
}
Header _readHeaderField(ByteBuf data, int index, {bool neverIndexed = false}) {
if (index > 0) {
String name = _indexTable[index].name;
String value = _decodeString(data, neverIndexed: neverIndexed);
return Header(name, value);
}
String name = _decodeString(data);
String value = _decodeString(data, neverIndexed: neverIndexed);
return Header(name, value);
}
String _decodeString(ByteBuf data, {bool neverIndexed = false}) {
int firstByte = data.get(data.readerIndex);
bool huffmanEncoded = (firstByte & 0x80) == 0x80;
int length = _decodeInteger(data, 7);
Uint8List stringBytes = data.readBytes(length);
if (huffmanEncoded) {
// If the string is Huffman encoded, decode it using a Huffman decoder.
return ascii.decode(huffmanDecoder.decode(stringBytes));
} else {
// If the string is not Huffman encoded, simply create a new string from the bytes.
return ascii.decode(stringBytes);
}
}
int _decodeInteger(ByteBuf data, int prefixLength) {
int prefixMask = (1 << prefixLength) - 1;
int value = data.read() & prefixMask;
if (value < prefixMask) {
return value;
}
int shift = 0;
int b;
do {
b = data.read();
value += (b & 127) << shift;
shift += 7;
} while ((b & 128) == 128);
return value;
}
}
class HPACKEncoder {
final IndexTable _indexTable = IndexTable();
static HuffmanEncoder huffmanEncoder = HuffmanEncoder();
List<int> encodeList(List<Header> headers) {
var bytesBuilder = BytesBuilder();
for (var header in headers) {
_encodeHeader(bytesBuilder, header);
}
return bytesBuilder.takeBytes();
}
List<int> encode(Header header) {
var bytesBuilder = BytesBuilder();
_encodeHeader(bytesBuilder, header);
return bytesBuilder.takeBytes();
}
void _encodeHeader(BytesBuilder bytesBuilder, Header header) {
var name = header.name;
var value = header.value;
var index = _getIndex(name, value);
if (index != null) {
_encodeInteger(bytesBuilder, 7, 0x80, index);
return;
}
var nameIndex = _getIndex(name);
if (nameIndex != null) {
_encodeInteger(bytesBuilder, 4, 0, nameIndex);
} else {
bytesBuilder.addByte(0);
_encodeString(bytesBuilder, name);
}
_encodeString(bytesBuilder, value);
}
int? _getIndex(String name, [String? value]) {
for (var i = 1; i < _indexTable.length; i++) {
var header = _indexTable[i];
if (header.name == name && (value == null || header.value == value)) {
return i;
}
}
return null;
}
_encodeString(BytesBuilder bytesBuilder, String value) {
var encoded = ascii.encode(value);
var huffmanEncoded = huffmanEncoder.encode(encoded);
if (huffmanEncoded.length < encoded.length) {
_encodeInteger(bytesBuilder, 7, 0x80, huffmanEncoded.length);
bytesBuilder.add(huffmanEncoded);
} else {
_encodeInteger(bytesBuilder, 7, 0x00, encoded.length);
bytesBuilder.add(encoded);
}
}
void _encodeInteger(BytesBuilder bytesBuilder, int prefixBits, int mask, int value) {
assert(prefixBits <= 8);
if (value < (1 << prefixBits) - 1) {
bytesBuilder.addByte(value | mask);
} else {
bytesBuilder.addByte(((1 << prefixBits) - 1) | mask);
value -= (1 << prefixBits) - 1;
while (value >= 128) {
bytesBuilder.addByte(value % 128 + 128);
value ~/= 128;
}
bytesBuilder.addByte(value);
}
}
}
class Header {
final String name;
String value;
Header(String name, this.value) : name = name.toLowerCase();
@override
String toString() {
return '{name: $name, value: $value}';
}
}
class IndexTable {
static final List<Header> _staticTable = [
Header('', ''),
Header(':authority', ''),
Header(':method', 'GET'),
Header(':method', 'POST'),
Header(':path', '/'),
Header(':path', '/index.html'),
Header(':scheme', 'http'),
Header(':scheme', 'https'),
Header(':status', '200'),
Header(':status', '204'),
Header(':status', '206'),
Header(':status', '304'),
Header(':status', '400'),
Header(':status', '404'),
Header(':status', '500'),
Header('accept-charset', ''),
Header('accept-encoding', 'gzip, deflate, br'),
Header('accept-language', ''),
Header('accept-ranges', ''),
Header('accept', ''),
Header('access-control-allow-origin', ''),
Header('age', ''),
Header('allow', ''),
Header('authorization', ''),
Header('cache-control', ''),
Header('content-disposition', ''),
Header('content-encoding', ''),
Header('content-language', ''),
Header('content-length', ''),
Header('content-location', ''),
Header('content-range', ''),
Header('content-type', ''),
Header('cookie', ''),
Header('date', ''),
Header('etag', ''),
Header('expect', ''),
Header('expires', ''),
Header('from', ''),
Header('host', ''),
Header('if-match', ''),
Header('if-modified-since', ''),
Header('if-none-match', ''),
Header('if-range', ''),
Header('if-unmodified-since', ''),
Header('last-modified', ''),
Header('link', ''),
Header('location', ''),
Header('max-forwards', ''),
Header('proxy-authenticate', ''),
Header('proxy-authorization', ''),
Header('range', ''),
Header('referer', ''),
Header('refresh', ''),
Header('retry-after', ''),
Header('server', ''),
Header('set-cookie', ''),
Header('strict-transport-security', ''),
Header('transfer-encoding', ''),
Header('user-agent', ''),
Header('vary', ''),
Header('via', ''),
Header('www-authenticate', ''),
];
//动态表
final List<Header> _dynamicTable = [];
int _maxSize = 4096;
Header operator [](int index) {
if (index < _staticTable.length) return _staticTable[index];
if (index < _staticTable.length + _dynamicTable.length) {
return _dynamicTable[index - _staticTable.length];
}
throw RangeError('Invalid index: $index');
}
add(Header header) {
_dynamicTable.add(header);
_maxSize -= sizeOf(header);
while (_maxSize < 0) {
_maxSize += sizeOf(_dynamicTable.removeAt(0));
}
}
int get length => _staticTable.length + _dynamicTable.length;
int sizeOf(Header header) => header.name.length + header.value.length + 32;
}

View File

@@ -0,0 +1,348 @@
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// HPACK specification. See here for more information:
/// https://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
import 'dart:convert' show ascii;
import 'dart:typed_data';
import '../../../util/byte_utils.dart';
import 'huffman.dart';
import 'huffman_table.dart';
/// Exception raised due to encoding/decoding errors.
class HPackDecodingException implements Exception {
final String _message;
HPackDecodingException(this._message);
@override
String toString() => 'HPackDecodingException: $_message';
}
/// A HPACK encoding/decoding context.
///
/// This is a statefull class, so encoding/decoding changes internal state.
class HPackContext {
final HPackEncoder encoder = HPackEncoder();
final HPackDecoder decoder = HPackDecoder();
HPackContext({
int maxSendingHeaderTableSize = 4096,
int maxReceivingHeaderTableSize = 4096,
}) {
encoder.updateMaxSendingHeaderTableSize(maxSendingHeaderTableSize);
decoder.updateMaxReceivingHeaderTableSize(maxReceivingHeaderTableSize);
}
}
/// A HTTP/2 header.
class Header {
final List<int> name;
final List<int> value;
final bool neverIndexed;
Header(this.name, this.value, {this.neverIndexed = false});
String get nameString => ascii.decode(name);
String get valueString => ascii.decode(value);
factory Header.ascii(String name, String value) {
// Specs: `However, header field names MUST be converted to lowercase prior
// to their encoding in HTTP/2. A request or response containing uppercase
// header field names MUST be treated as malformed (Section 8.1.2.6).`
return Header(ascii.encode(name.toLowerCase()), ascii.encode(value));
}
}
/// A stateful HPACK decoder.
class HPackDecoder {
int _maxHeaderTableSize = 4096;
final IndexTable _table = IndexTable();
void updateMaxReceivingHeaderTableSize(int newMaximumSize) {
_maxHeaderTableSize = newMaximumSize;
}
List<Header> decode(List<int> data) {
var offset = 0;
int readInteger(int prefixBits) {
assert(prefixBits <= 8 && prefixBits > 0);
var byte = data[offset++] & ((1 << prefixBits) - 1);
int integer;
if (byte == ((1 << prefixBits) - 1)) {
// Length encodeded.
integer = 0;
var shift = 0;
while (true) {
var done = (data[offset] & 0x80) != 0x80;
integer += (data[offset++] & 0x7f) << shift;
shift += 7;
if (done) break;
}
integer += (1 << prefixBits) - 1;
} else {
// In place length.
integer = byte;
}
return integer;
}
List<int> readStringLiteral() {
var isHuffmanEncoding = (data[offset] & 0x80) != 0;
var length = readInteger(7);
var sublist = viewOrSublist(data, offset, length);
offset += length;
if (isHuffmanEncoding) {
return http2HuffmanCodec.decode(sublist);
} else {
return sublist;
}
}
Header readHeaderFieldInternal(int index, {bool neverIndexed = false}) {
List<int> name, value;
if (index > 0) {
name = _table.lookup(index).name;
value = readStringLiteral();
} else {
name = readStringLiteral();
value = readStringLiteral();
}
return Header(name, value, neverIndexed: neverIndexed);
}
try {
var headers = <Header>[];
while (offset < data.length) {
var byte = data[offset];
var isIndexedField = (byte & 0x80) != 0;
var isIncrementalIndexing = (byte & 0xc0) == 0x40;
var isWithoutIndexing = (byte & 0xf0) == 0;
var isNeverIndexing = (byte & 0xf0) == 0x10;
var isDynamicTableSizeUpdate = (byte & 0xe0) == 0x20;
if (isIndexedField) {
var index = readInteger(7);
var field = _table.lookup(index);
headers.add(field);
} else if (isIncrementalIndexing) {
var field = readHeaderFieldInternal(readInteger(6));
_table.addHeaderField(field);
headers.add(field);
} else if (isWithoutIndexing) {
headers.add(readHeaderFieldInternal(readInteger(4)));
} else if (isNeverIndexing) {
headers.add(
readHeaderFieldInternal(readInteger(4), neverIndexed: true),
);
} else if (isDynamicTableSizeUpdate) {
var newMaxSize = readInteger(5);
_table.updateMaxSize(newMaxSize);
} else {
throw HPackDecodingException('Invalid encoding of headers.');
}
}
return headers;
// ignore: avoid_catching_errors
} on RangeError catch (e) {
throw HPackDecodingException('$e');
} on HuffmanDecodingException catch (e) {
throw HPackDecodingException('$e');
}
}
}
/// A stateful HPACK encoder.
// Currently we encode all headers:
// - without huffman encoding
// - without using the dynamic table
class HPackEncoder {
void updateMaxSendingHeaderTableSize(int newMaximumSize) {
// Once we start encoding via dynamic table we need to let the other
// side know the maximum table size we're using.
}
List<int> encode(List<Header> headers) {
var bytesBuilder = BytesBuilder();
var currentByte = 0;
void writeInteger(int prefixBits, int value) {
assert(prefixBits <= 8);
if (value < (1 << prefixBits) - 1) {
currentByte |= value;
bytesBuilder.addByte(currentByte);
} else {
// Length encodeded.
currentByte |= (1 << prefixBits) - 1;
value -= (1 << prefixBits) - 1;
bytesBuilder.addByte(currentByte);
var done = false;
while (!done) {
currentByte = value & 0x7f;
value = value >> 7;
done = value == 0;
if (!done) currentByte |= 0x80;
bytesBuilder.addByte(currentByte);
}
}
currentByte = 0;
}
void writeStringLiteral(List<int> bytes) {
// Support huffman encoding.
currentByte = 0; // 1 would be huffman encoding
writeInteger(7, bytes.length);
bytesBuilder.add(bytes);
}
void writeLiteralHeaderWithoutIndexing(Header header) {
bytesBuilder.addByte(0);
writeStringLiteral(header.name);
writeStringLiteral(header.value);
}
for (var header in headers) {
writeLiteralHeaderWithoutIndexing(header);
}
return bytesBuilder.takeBytes();
}
}
class IndexTable {
static final List<Header?> _staticTable = [
null,
Header(ascii.encode(':authority'), const []),
Header(ascii.encode(':method'), ascii.encode('GET')),
Header(ascii.encode(':method'), ascii.encode('POST')),
Header(ascii.encode(':path'), ascii.encode('/')),
Header(ascii.encode(':path'), ascii.encode('/index.html')),
Header(ascii.encode(':scheme'), ascii.encode('http')),
Header(ascii.encode(':scheme'), ascii.encode('https')),
Header(ascii.encode(':status'), ascii.encode('200')),
Header(ascii.encode(':status'), ascii.encode('204')),
Header(ascii.encode(':status'), ascii.encode('206')),
Header(ascii.encode(':status'), ascii.encode('304')),
Header(ascii.encode(':status'), ascii.encode('400')),
Header(ascii.encode(':status'), ascii.encode('404')),
Header(ascii.encode(':status'), ascii.encode('500')),
Header(ascii.encode('accept-charset'), const []),
Header(ascii.encode('accept-encoding'), ascii.encode('gzip, deflate')),
Header(ascii.encode('accept-language'), const []),
Header(ascii.encode('accept-ranges'), const []),
Header(ascii.encode('accept'), const []),
Header(ascii.encode('access-control-allow-origin'), const []),
Header(ascii.encode('age'), const []),
Header(ascii.encode('allow'), const []),
Header(ascii.encode('authorization'), const []),
Header(ascii.encode('cache-control'), const []),
Header(ascii.encode('content-disposition'), const []),
Header(ascii.encode('content-encoding'), const []),
Header(ascii.encode('content-language'), const []),
Header(ascii.encode('content-length'), const []),
Header(ascii.encode('content-location'), const []),
Header(ascii.encode('content-range'), const []),
Header(ascii.encode('content-type'), const []),
Header(ascii.encode('cookie'), const []),
Header(ascii.encode('date'), const []),
Header(ascii.encode('etag'), const []),
Header(ascii.encode('expect'), const []),
Header(ascii.encode('expires'), const []),
Header(ascii.encode('from'), const []),
Header(ascii.encode('host'), const []),
Header(ascii.encode('if-match'), const []),
Header(ascii.encode('if-modified-since'), const []),
Header(ascii.encode('if-none-match'), const []),
Header(ascii.encode('if-range'), const []),
Header(ascii.encode('if-unmodified-since'), const []),
Header(ascii.encode('last-modified'), const []),
Header(ascii.encode('link'), const []),
Header(ascii.encode('location'), const []),
Header(ascii.encode('max-forwards'), const []),
Header(ascii.encode('proxy-authenticate'), const []),
Header(ascii.encode('proxy-authorization'), const []),
Header(ascii.encode('range'), const []),
Header(ascii.encode('referer'), const []),
Header(ascii.encode('refresh'), const []),
Header(ascii.encode('retry-after'), const []),
Header(ascii.encode('server'), const []),
Header(ascii.encode('set-cookie'), const []),
Header(ascii.encode('strict-transport-security'), const []),
Header(ascii.encode('transfer-encoding'), const []),
Header(ascii.encode('user-agent'), const []),
Header(ascii.encode('vary'), const []),
Header(ascii.encode('via'), const []),
Header(ascii.encode('www-authenticate'), const []),
];
final List<Header> _dynamicTable = [];
/// The maximum size the dynamic table can grow to before entries need to be
/// evicted.
int _maximumSize = 4096;
/// The current size of the dynamic table.
int _currentSize = 0;
IndexTable();
/// Updates the maximum size which the dynamic table can grow to.
void updateMaxSize(int newMaxDynTableSize) {
_maximumSize = newMaxDynTableSize;
_reduce();
}
/// Lookup an item by index.
Header lookup(int index) {
if (index <= 0) {
throw HPackDecodingException(
'Invalid index (was: $index) for table lookup.',
);
}
if (index < _staticTable.length) {
return _staticTable[index]!;
}
index -= _staticTable.length;
if (index < _dynamicTable.length) {
return _dynamicTable[index];
}
throw HPackDecodingException(
'Invalid index (was: $index) for table lookup.',
);
}
/// Adds a new header field to the dynamic table - and evicts entries as
/// necessary.
void addHeaderField(Header header) {
_dynamicTable.insert(0, header);
_currentSize += _sizeOf(header);
_reduce();
}
/// Removes as many entries as required to be within the limit of
/// [_maximumSize].
void _reduce() {
while (_currentSize > _maximumSize) {
var h = _dynamicTable.removeLast();
_currentSize -= _sizeOf(h);
}
}
/// Returns the "size" a [header] has.
///
/// This is specified to be the number of octets of name/value plus 32.
int _sizeOf(Header header) => header.name.length + header.value.length + 32;
}

View File

@@ -0,0 +1,182 @@
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:typed_data';
import 'huffman_table.dart';
class HuffmanDecodingException implements Exception {
final String _message;
HuffmanDecodingException(this._message);
@override
String toString() => 'HuffmanDecodingException: $_message';
}
/// A codec used for encoding/decoding using a huffman codec.
class HuffmanCodec {
final HuffmanEncoder _encoder;
final HuffmanDecoder _decoder;
HuffmanCodec(this._encoder, this._decoder);
List<int> decode(List<int> bytes) => _decoder.decode(bytes);
List<int> encode(List<int> bytes) => _encoder.encode(bytes);
}
/// A huffman decoder based on a [HuffmanTreeNode].
class HuffmanDecoder {
final HuffmanTreeNode _root;
HuffmanDecoder(this._root);
/// Decodes [bytes] using a huffman tree.
List<int> decode(List<int> bytes) {
var buffer = BytesBuilder();
var currentByteOffset = 0;
var node = _root;
var currentDepth = 0;
while (currentByteOffset < bytes.length) {
var byte = bytes[currentByteOffset];
for (var currentBit = 7; currentBit >= 0; currentBit--) {
var right = (byte >> currentBit) & 1 == 1;
if (right) {
node = node.right!;
} else {
node = node.left!;
}
currentDepth++;
if (node.value != null) {
if (node.value == EOS_BYTE) {
throw HuffmanDecodingException(
'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 HuffmanDecodingException(
'Incomplete encoding of a byte or more than 7 bit padding.',
);
}
while (node.right != null) {
node = node.right!;
}
if (node.value != 256) {
throw HuffmanDecodingException('Incomplete encoding of a byte.');
}
}
return buffer.takeBytes();
}
}
/// A huffman encoder based on a list of codewords.
class HuffmanEncoder {
final List<EncodedHuffmanValue> _codewords;
HuffmanEncoder(this._codewords);
/// Encodes [bytes] using a list of codewords.
List<int> encode(List<int> bytes) {
var buffer = BytesBuilder();
var currentByte = 0;
var currentBitOffset = 7;
void writeValue(int value, int numBits) {
var i = numBits - 1;
while (i >= 0) {
if (currentBitOffset == 7 && i >= 7) {
assert(currentByte == 0);
buffer.addByte((value >> (i - 7)) & 0xff);
currentBitOffset = 7;
currentByte = 0;
i -= 8;
} else {
currentByte |= ((value >> i) & 1) << currentBitOffset;
currentBitOffset--;
if (currentBitOffset == -1) {
buffer.addByte(currentByte);
currentBitOffset = 7;
currentByte = 0;
}
i--;
}
}
}
for (var i = 0; i < bytes.length; i++) {
var byte = bytes[i];
var value = _codewords[byte];
writeValue(value.encodedBytes, value.numBits);
}
if (currentBitOffset < 7) {
writeValue(0xff, 1 + currentBitOffset);
}
return buffer.takeBytes();
}
}
/// Specifies the encoding of a specific value using huffman encoding.
class EncodedHuffmanValue {
/// An integer representation of the encoded bit-string.
final int encodedBytes;
/// The number of bits in [encodedBytes].
final int numBits;
const EncodedHuffmanValue(this.encodedBytes, this.numBits);
}
/// A node in the huffman tree.
class HuffmanTreeNode {
HuffmanTreeNode? left;
HuffmanTreeNode? right;
int? value;
}
/// Generates a huffman decoding tree.
HuffmanTreeNode generateHuffmanTree(List<EncodedHuffmanValue> valueEncodings) {
var root = HuffmanTreeNode();
for (var byteOffset = 0; byteOffset < valueEncodings.length; byteOffset++) {
var entry = valueEncodings[byteOffset];
var current = root;
for (var bitNr = 0; bitNr < entry.numBits; bitNr++) {
var right =
((entry.encodedBytes >> (entry.numBits - bitNr - 1)) & 1) == 1;
if (right) {
current.right ??= HuffmanTreeNode();
current = current.right!;
} else {
current.left ??= HuffmanTreeNode();
current = current.left!;
}
}
current.value = byteOffset;
}
return root;
}

View File

@@ -1,155 +1,22 @@
import 'dart:typed_data';
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
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
import 'huffman.dart';
final HuffmanTreeNode _root;
/// The huffman codec for encoding/decoding HTTP/2 header blocks.
final HuffmanCodec http2HuffmanCodec = HuffmanCodec(
HuffmanEncoder(_codeWords),
HuffmanDecoder(generateHuffmanTree(_codeWords)),
);
HuffmanDecoder() : _root = generateHuffmanTree(_huffmanTable);
/// This is the integer representing the End-of-String symbol
/// (it is not representable by a byte).
const int EOS_BYTE = 256;
//http2协议规范 huffman解码
List<int> decode(List<int> bytes) {
var buffer = BytesBuilder();
var currentByteOffset = 0;
var node = _root;
var currentDepth = 0;
while (currentByteOffset < bytes.length) {
var byte = bytes[currentByteOffset];
for (var currentBit = 7; currentBit >= 0; currentBit--) {
var right = (byte >> currentBit) & 1 == 1;
if (right) {
node = node.right!;
} else {
node = node.left!;
}
currentDepth++;
if (node.value != null) {
if (node.value == eosByte) {
throw Exception('More than 7 bit padding is not allowed. Found entire EOS '
'encoding');
}
buffer.addByte(node.value!);
node = _root;
currentDepth = 0;
}
}
currentByteOffset++;
}
if (node != _root) {
if (currentDepth > 7) {
throw Exception('Incomplete encoding of a byte or more than 7 bit padding.');
}
while (node.right != null) {
node = node.right!;
}
if (node.value != 256) {
throw Exception('Incomplete encoding of a byte.');
}
}
return buffer.takeBytes();
}
}
class HuffmanEncoder {
static const int eosByte = 256; //end of string
final List<EncodedHuffmanValue> _codewords;
HuffmanEncoder() : _codewords = _huffmanTable;
//http2协议规范 huffman编码
List<int> encode(List<int> bytes) {
var buffer = BytesBuilder();
var currentByte = 0;
var currentBitOffset = 7;
void writeValue(int value, int numBits) {
var i = numBits - 1;
while (i >= 0) {
if (currentBitOffset == 7 && i >= 7) {
assert(currentByte == 0);
buffer.addByte((value >> (i - 7)) & 0xff);
currentBitOffset = 7;
currentByte = 0;
i -= 8;
} else {
currentByte |= ((value >> i) & 1) << currentBitOffset;
currentBitOffset--;
if (currentBitOffset == -1) {
buffer.addByte(currentByte);
currentBitOffset = 7;
currentByte = 0;
}
i--;
}
}
}
for (var i = 0; i < bytes.length; i++) {
var byte = bytes[i];
var value = _codewords[byte];
writeValue(value.encodedBytes, value.numBits);
}
if (currentBitOffset < 7) {
writeValue(0xff, 1 + currentBitOffset);
}
return buffer.takeBytes();
}
}
///h2 huffman解码tree
HuffmanTreeNode generateHuffmanTree(List<EncodedHuffmanValue> valueEncodings) {
var root = HuffmanTreeNode();
for (var byteOffset = 0; byteOffset < valueEncodings.length; byteOffset++) {
var entry = valueEncodings[byteOffset];
var current = root;
for (var bitNr = 0; bitNr < entry.numBits; bitNr++) {
var right = ((entry.encodedBytes >> (entry.numBits - bitNr - 1)) & 1) == 1;
if (right) {
current.right ??= HuffmanTreeNode();
current = current.right!;
} else {
current.left ??= HuffmanTreeNode();
current = current.left!;
}
}
current.value = byteOffset;
}
return root;
}
class HuffmanTreeNode {
int? value;
HuffmanTreeNode? left;
HuffmanTreeNode? right;
bool isLeaf() {
return left == null && right == null;
}
}
//HPACK规范 huffman编码的字节编码列表
final List<EncodedHuffmanValue> _huffmanTable = [
/// This list of byte encodings via huffman encoding was generated from the
/// HPACK specification.
const List<EncodedHuffmanValue> _codeWords = <EncodedHuffmanValue>[
EncodedHuffmanValue(0x1ff8, 13),
EncodedHuffmanValue(0x7fffd8, 23),
EncodedHuffmanValue(0xfffffe2, 28),
@@ -408,10 +275,3 @@ final List<EncodedHuffmanValue> _huffmanTable = [
EncodedHuffmanValue(0x3ffffee, 26),
EncodedHuffmanValue(0x3fffffff, 30),
];
class EncodedHuffmanValue {
final int encodedBytes;
final int numBits;
EncodedHuffmanValue(this.encodedBytes, this.numBits);
}

View File

@@ -63,7 +63,7 @@ class SettingHandler {
// Handle the setting based on its identifier
switch (identifier) {
case 1: // SETTINGS_HEADER_TABLE_SIZE
setting.maxFrameSize = value;
setting.headTableSize = value;
break;
case 2: // SETTINGS_ENABLE_PUSH
setting.enablePush = value == 1;

View File

@@ -45,10 +45,16 @@ class ByteBuf {
void clear() {
readerIndex = 0;
writerIndex = 0;
_buffer = Uint8List(0);
}
///释放已读的空间
void clearRead() {
if (readerIndex == writerIndex) {
clear();
return;
}
if (readerIndex > 0) {
_buffer = Uint8List.sublistView(_buffer, readerIndex, writerIndex);
writerIndex -= readerIndex;

View File

@@ -0,0 +1,53 @@
import 'dart:typed_data';
List<int> viewOrSublist(List<int> data, int offset, int length) {
if (data is Uint8List) {
return Uint8List.view(data.buffer, data.offsetInBytes + offset, length);
} else {
return data.sublist(offset, offset + length);
}
}
int readInt64(List<int> bytes, int offset) {
var high = readInt32(bytes, offset);
var low = readInt32(bytes, offset + 4);
return high << 32 | low;
}
int readInt32(List<int> bytes, int offset) {
return (bytes[offset] << 24) |
(bytes[offset + 1] << 16) |
(bytes[offset + 2] << 8) |
bytes[offset + 3];
}
int readInt24(List<int> bytes, int offset) {
return (bytes[offset] << 16) | (bytes[offset + 1] << 8) | bytes[offset + 2];
}
int readInt16(List<int> bytes, int offset) {
return (bytes[offset] << 8) | bytes[offset + 1];
}
void setInt64(List<int> bytes, int offset, int value) {
setInt32(bytes, offset, value >> 32);
setInt32(bytes, offset + 4, value & 0xffffffff);
}
void setInt32(List<int> bytes, int offset, int value) {
bytes[offset] = (value >> 24) & 0xff;
bytes[offset + 1] = (value >> 16) & 0xff;
bytes[offset + 2] = (value >> 8) & 0xff;
bytes[offset + 3] = value & 0xff;
}
void setInt24(List<int> bytes, int offset, int value) {
bytes[offset] = (value >> 16) & 0xff;
bytes[offset + 1] = (value >> 8) & 0xff;
bytes[offset + 2] = value & 0xff;
}
void setInt16(List<int> bytes, int offset, int value) {
bytes[offset] = (value >> 8) & 0xff;
bytes[offset + 1] = value & 0xff;
}

View File

@@ -113,10 +113,13 @@ class ProxyHelper {
request.uri = hostAndPort.domain;
}
request.response = HttpResponse(status)
..headers.contentType = 'text/plain'
..headers.contentLength = message.codeUnits.length
..body = message.codeUnits;
if (request.response == null || request.method == HttpMethod.connect) {
request.response = HttpResponse(status)
..headers.contentType = 'text/plain'
..headers.contentLength = message.codeUnits.length
..body = message.codeUnits;
}
request.response?.request = request;
channelContext.host = hostAndPort;

View File

@@ -184,6 +184,7 @@ class _ProxyMenuState extends State<_ProxyMenu> {
const Divider(thickness: 0.3, height: 8),
setSystemProxy(),
const Divider(thickness: 0.3, height: 8),
Row(children: [
Expanded(
child: Padding(
@@ -199,6 +200,22 @@ class _ProxyMenuState extends State<_ProxyMenu> {
SizedBox(width: 10)
]),
const Divider(thickness: 0.3, height: 8),
Row(children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 15),
child: Text(localizations.enabledHTTP2, style: const TextStyle(fontSize: 14)))),
SwitchWidget(
value: configuration.enabledHttp2,
scale: 0.75,
onChanged: (val) {
configuration.enabledHttp2 = val;
changed = true;
}),
SizedBox(width: 10)
]),
const Divider(thickness: 0.3, height: 8),
const SizedBox(height: 3),
Padding(
padding: const EdgeInsets.only(left: 15),

View File

@@ -73,6 +73,15 @@ class _PreferenceState extends State<Preference> {
configuration.enableSocks5 = value;
proxyServer.configuration.flushConfig();
})),
ListTile(
title: Text(localizations.enabledHTTP2),
trailing: SwitchWidget(
value: configuration.enabledHttp2,
scale: 0.8,
onChanged: (value) {
configuration.enabledHttp2 = value;
proxyServer.configuration.flushConfig();
})),
ListTile(
title: Text(localizations.externalProxy),
trailing: const Icon(Icons.keyboard_arrow_right),