Skip to content

Commit

Permalink
Optimize connect to http proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
TitleHHHH authored and TitleHHHH committed Sep 28, 2024
1 parent d572a00 commit 5d0eac0
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 64 deletions.
7 changes: 7 additions & 0 deletions HolyClient.sln
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SandBoxMcProtoNet", "src\Sa
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SandBoxStressTest", "src\Sandbox\SandBoxStressTest\SandBoxStressTest.csproj", "{C5D74EC8-B4DC-4E48-92DD-F124E0B05052}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SandboxProxy", "src\Sandbox\SandboxProxy\SandboxProxy.csproj", "{C26D4BA0-EB02-4E4F-AB81-D6C054C2922F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -179,6 +181,10 @@ Global
{C5D74EC8-B4DC-4E48-92DD-F124E0B05052}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5D74EC8-B4DC-4E48-92DD-F124E0B05052}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5D74EC8-B4DC-4E48-92DD-F124E0B05052}.Release|Any CPU.Build.0 = Release|Any CPU
{C26D4BA0-EB02-4E4F-AB81-D6C054C2922F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C26D4BA0-EB02-4E4F-AB81-D6C054C2922F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C26D4BA0-EB02-4E4F-AB81-D6C054C2922F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C26D4BA0-EB02-4E4F-AB81-D6C054C2922F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -208,6 +214,7 @@ Global
{EDA5EB9B-676E-47D4-9714-367C9BD13C69} = {E8DD8BFD-216D-4435-B9F8-E34C03D29FF9}
{A865E72C-3D76-4D1F-8F0A-EEA3204B992F} = {BF4F609C-E739-47C1-B6E9-0FEC82E88ACA}
{C5D74EC8-B4DC-4E48-92DD-F124E0B05052} = {BF4F609C-E739-47C1-B6E9-0FEC82E88ACA}
{C26D4BA0-EB02-4E4F-AB81-D6C054C2922F} = {BF4F609C-E739-47C1-B6E9-0FEC82E88ACA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {41AEE66A-568B-4B2B-A50B-5AC57D067A07}
Expand Down
187 changes: 125 additions & 62 deletions src/QuickProxyNet/Internal/HttpHelper.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Globalization;
using System.Buffers;
using System.Globalization;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text;
using Cysharp.Text;
using DotNext;
using DotNext.Buffers;

namespace QuickProxyNet;
Expand All @@ -22,95 +24,156 @@ private static void Test(string host, int port)
private static async ValueTask WriteConnectionCommand(Stream stream, string host, int port,
NetworkCredential proxyCredentials, CancellationToken cancellationToken)
{
using var builder = ZString.CreateUtf8StringBuilder();
var builder = ZString.CreateUtf8StringBuilder();
try
{
builder.AppendFormat("CONNECT {0}:{1} HTTP/1.1\r\n", host, port);
builder.AppendFormat("Host: {0}:{1}\r\n", host, port);
if (proxyCredentials != null)
{
var token = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}:{1}",
proxyCredentials.UserName, proxyCredentials.Password));
var base64 = Convert.ToBase64String(token);
builder.AppendFormat("Proxy-Authorization: Basic {0}\r\n", base64);
}

builder.Append("\r\n");


builder.AppendFormat("CONNECT {0}:{1} HTTP/1.1\r\n", host, port);
builder.AppendFormat("Host: {0}:{1}\r\n", host, port);
if (proxyCredentials != null)
await stream.WriteAsync(builder.AsMemory(), cancellationToken);
}
finally
{
var token = Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}:{1}",
proxyCredentials.UserName, proxyCredentials.Password));
var base64 = Convert.ToBase64String(token);
builder.AppendFormat("Proxy-Authorization: Basic {0}\r\n", base64);
builder.Dispose();
}
}



builder.Append("\r\n");
internal static async ValueTask<Stream> EstablishHttpTunnelAsync(Stream stream, Uri proxyUri, string host, int port,
NetworkCredential? credentials, CancellationToken cancellationToken)
{
await WriteConnectionCommand(stream, host, port, credentials, cancellationToken);


await stream.WriteAsync(builder.AsMemory(), cancellationToken);
var parser = new HttpResponseParser();
try
{
bool find;
do
{
var memory = parser.GetMemory();
var nread = await stream.ReadAsync(memory, cancellationToken);
if (nread <= 0)
throw new EndOfStreamException();
find = parser.Parse(nread);
} while (find == false);

bool isValid= parser.Validate();
//string response = parser.ToString();

if (!isValid)
{
throw new ProxyProtocolException($"Failed to connect http://{host}:{port}");
}

return stream;
}
finally
{
parser.Dispose();
}
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static bool TryConsumeHeaders(ref Utf16ValueStringBuilder builder)
public struct HttpResponseParser
{
private static int BufferSize = 1024;
private MemoryOwner<byte> _memory;
private static readonly MemoryAllocator<byte> _allocator = ArrayPool<byte>.Shared.ToAllocator();
private static readonly byte[] http200 = "HTTP/1.1 200".Select(x => (byte)x).ToArray();
private int _writtenCount = 0;
private int _indexEnd=-1;
public ReadOnlySpan<byte> Span => _memory.Span.Slice(0, _writtenCount);

public HttpResponseParser()
{
return false;
_memory = _allocator.AllocateExactly(BufferSize);
}

[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static void ValidateHttpResponse(string response, string host, int port)
public Memory<byte> GetMemory()
{
if (response.Length >= 15 && response.StartsWith("HTTP/1.", StringComparison.OrdinalIgnoreCase) &&
(response[7] == '1' || response[7] == '0') && response[8] == ' ' &&
response[9] == '2' && response[10] == '0' && response[11] == '0' &&
response[12] == ' ')
return;

throw new ProxyProtocolException(string.Format(CultureInfo.InvariantCulture,
"Failed to connect to {0}:{1}: {2}", host, port, response));
if (_writtenCount < _memory.Length)
{
return _memory.Memory.Slice(_writtenCount);
}

_memory.Resize(_writtenCount + BufferSize);
return _memory.Memory.Slice(_writtenCount);
}

internal static async ValueTask<Stream> EstablishHttpTunnelAsync(Stream stream, Uri proxyUri, string host, int port,
NetworkCredential? credentials, CancellationToken cancellationToken)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Parse(int count)
{
await WriteConnectionCommand(stream, host, port, credentials, cancellationToken);
_writtenCount += count;
return ParseHttpEnd(count);
}

private static readonly byte[] NewLine = "\r\n\r\n".Select(x => (byte)x).ToArray();

using var builder = ZString.CreateUtf8StringBuilder();
//builder.ToString
do
{
var memory = builder.GetMemory(BufferSize);
var nread = await stream.ReadAsync(memory, cancellationToken);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool ParseHttpEnd(int count)
{
int start = _writtenCount - count;
int length = count;

if (nread <= 0)
break;
int offset = Math.Min(4, start);
start -= offset;
length += offset;

builder.Advance(nread);
ReadOnlySpan<byte> bytes = _memory.Span.Slice(start, length);

int index = bytes.IndexOf(NewLine);
if (index < 0)
{
return false;
}

return null;
} while (true);
_indexEnd = index + start;

return null;
return true;
}
}

public static class StringEscape
{
private static readonly char[] toEscape =
"\0\x1\x2\x3\x4\x5\x6\a\b\t\n\v\f\r\xe\xf\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\"\\"
.ToCharArray();
public bool Validate()
{
if (_indexEnd == -1)
{
throw new InvalidOperationException("No find http response");
}

ReadOnlySpan<byte> span = Span;


if (span.Length > (uint)_indexEnd + 4)
{
return false;
}
if (span.Length >= 15 && span.StartsWith(http200))
{
return true;
}

private static readonly string[] literals =
@"\0,\x0001,\x0002,\x0003,\x0004,\x0005,\x0006,\a,\b,\t,\n,\v,\f,\r,\x000e,\x000f,\x0010,\x0011,\x0012,\x0013,\x0014,\x0015,\x0016,\x0017,\x0018,\x0019,\x001a,\x001b,\x001c,\x001d,\x001e,\x001f"
.Split(new[] { ',' });
return false;
}

public static string Escape(this string input)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override string ToString()
{
var i = input.IndexOfAny(toEscape);
if (i < 0) return input;

var sb = new StringBuilder(input.Length + 5);
var j = 0;
do
{
sb.Append(input, j, i - j);
var c = input[i];
if (c < 0x20) sb.Append(literals[c]);
else sb.Append(@"\").Append(c);
} while ((i = input.IndexOfAny(toEscape, j = ++i)) > 0);
return Encoding.UTF8.GetString(_memory.Span.Slice(0, _indexEnd+4));
}

return sb.Append(input, j, input.Length - j).ToString();
public void Dispose()
{
_memory.Dispose();
}
}
15 changes: 13 additions & 2 deletions src/Sandbox/SandBoxMcProtoNet/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,27 @@
using McProtoNet.Client;
using McProtoNet.MultiVersionProtocol;
using McProtoNet.Serialization;
using QuickProxyNet;

internal class Program
{
public static async Task Main(string[] args)
{

byte[] bytes = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

Console.WriteLine(bytes.AsSpan().IndexOf(new byte[]{3,4,5,6}));
//return;


HttpProxyClient proxyClient = new HttpProxyClient("84.252.75.136", 4444);

await proxyClient.ConnectAsync("google.com", 443);

return;

Console.WriteLine("Start");
try
{

var list = new List<MinecraftClient>();
var listProtocols = new List<MultiProtocol>();
for (int i = 0; i < 200; i++)
Expand Down
1 change: 1 addition & 0 deletions src/Sandbox/SandboxProxy/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Console.WriteLine("Hi");
10 changes: 10 additions & 0 deletions src/Sandbox/SandboxProxy/SandboxProxy.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>

0 comments on commit 5d0eac0

Please sign in to comment.