Skip to content
This repository has been archived by the owner on Jul 28, 2020. It is now read-only.

Fix: HttpConnectProxy partial support for Titanium/Apache, and add tests. #105

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Proxy/HttpConnectProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ protected override void ProcessReceive(SocketAsyncEventArgs e)
return;
}

int responseLength = prevMatched > 0 ? (e.Offset - prevMatched) : (e.Offset + result);
int responseLength = (prevMatched > 0 && result == e.Offset) ? (e.Offset - prevMatched) : result;

if (e.Offset + e.BytesTransferred > responseLength + m_LineSeparator.Length)
{
Expand Down
204 changes: 178 additions & 26 deletions Test/HttpConnectProxyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,200 @@ namespace SuperSocket.ClientEngine.Test
public class HttpConnectProxyTest
{
[Fact]
public void TestMatchSecondTime()
public void TestHttp10SimpleOnePacket()
{
var server = CreateSimplyRespond(Encoding.ASCII.GetBytes("OK"));
var proxyServer = CreateSimplyRespond(Encoding.ASCII.GetBytes("OK"));
SimulateHttpConnectProxy(
simulateResponseFromProxyServer: proxyServerPeer =>
{
SendLine(proxyServerPeer, "HTTP/1.0 200 Connection Established\r\n\r\n");
},
verifyCompletedEvent: e =>
{
Assert.Null(e.Exception);
Assert.True(e.Connected);
},
testCopyDataFromLeftToRight: SendAndReceiveHello
);
}

ManualResetEvent wait = new ManualResetEvent(false);
[Fact]
public void TestHttp11SimpleOnePacket()
{
SimulateHttpConnectProxy(
simulateResponseFromProxyServer: proxyServerPeer =>
{
SendLine(proxyServerPeer, "HTTP/1.1 200 Connection Established\r\n\r\n");
},
verifyCompletedEvent: e =>
{
Assert.Null(e.Exception);
Assert.True(e.Connected);
},
testCopyDataFromLeftToRight: SendAndReceiveHello
);
}

[Fact]
public void TestHttp11ComplexOnePacket()
{
SimulateHttpConnectProxy(
simulateResponseFromProxyServer: proxyServerPeer =>
{
SendLine(proxyServerPeer,
"HTTP/1.1 200 Connection Established\r\n" +
"Proxy-agent: Apache/2.2.29 (Win32)\r\n" +
"\r\n"
);
},
verifyCompletedEvent: e =>
{
Assert.Null(e.Exception);
Assert.True(e.Connected);
},
testCopyDataFromLeftToRight: SendAndReceiveHello
);
}

[Fact]
public void TestHttp11ComplexMultiPacket()
{
SimulateHttpConnectProxy(
simulateResponseFromProxyServer: proxyServerPeer =>
{
// Actual response simulation from: Apache/2.2.29 (Win32)
SendLine(proxyServerPeer, "HTTP/1.1 200 Connection Established\r\n");

// This leads to "System.Exception: protocol error: more data has been received"
SendLine(proxyServerPeer, "Proxy-agent: Apache/2.2.29 (Win32)\r\n\r\n");
},
verifyCompletedEvent: e =>
{
Assert.Null(e.Exception);
Assert.True(e.Connected);
},
testCopyDataFromLeftToRight: SendAndReceiveHello
);
}

[Fact]
public void TestHttp11ComplexOnePacketAsForbidden()
{
SimulateHttpConnectProxy(
simulateResponseFromProxyServer: proxyServerPeer =>
{
// Actual 403 response simulation from: Apache/2.2.29 (Win32)

var proxy = new HttpConnectProxy(proxyServer.LocalEndPoint);
ProxyEventArgs eventArgs = null;
proxy.Completed += (a, e) =>
// This leads to "System.Exception: protocol error: more data has been received"
SendLine(proxyServerPeer,
string.Join("\r\n",
"HTTP/1.1 403 Forbidden",
"Date: Tue, 24 Sep 2019 04:35:48 GMT",
"Content-Length: 216",
"Content-Type: text/html; charset=iso-8859-1",
"",
"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">",
"<html><head>",
"<title>403 Forbidden</title>",
"</head><body>",
"<h1>Forbidden</h1>",
"<p>You don't have permission to access 192.168.2.181:7",
"on this server.</p>",
"</body></html>"
)
);
},
verifyCompletedEvent: e =>
{
// This pattern is: NOT SUPPORTED FOR NOW!
Assert.NotNull(e.Exception);
Assert.Equal("protocol error: more data has been received", e.Exception.Message);
Assert.False(e.Connected);
},
testCopyDataFromLeftToRight: SendAndReceiveHello
);
}


void SimulateHttpConnectProxy(
Action<Socket> simulateResponseFromProxyServer,
Action<ProxyEventArgs> verifyCompletedEvent,
Action<Socket, Socket> testCopyDataFromLeftToRight
)
{
var server = NewTcpPeer();
var proxyServer = NewTcpPeer();

var awaitAtProxyServer = proxyServer.AcceptAsync();

var proxy = new HttpConnectProxy(proxyServer.LocalEndPoint, 1024, null);
var eventArgs = (ProxyEventArgs)null;
var eventPulled = new ManualResetEvent(false);
proxy.Completed += (sender, e) =>
{
eventArgs = e;
wait.Set();
eventPulled.Set();
};

proxy.Connect(server.LocalEndPoint);

Assert.True(wait.WaitOne(5000));
Assert.Null(eventArgs.Exception);
Assert.True(eventArgs.Connected);
var proxyServerPeer = awaitAtProxyServer.GetAwaiter().GetResult();
Assert.Equal($"CONNECT 127.0.0.1:{((IPEndPoint)server.LocalEndPoint).Port} HTTP/1.1\r\n", ReadLine(proxyServerPeer));
Assert.Equal($"Host: 127.0.0.1:{((IPEndPoint)server.LocalEndPoint).Port}\r\n", ReadLine(proxyServerPeer));
Assert.Equal($"Proxy-Connection: Keep-Alive\r\n", ReadLine(proxyServerPeer));
Assert.Equal($"\r\n", ReadLine(proxyServerPeer));

simulateResponseFromProxyServer(proxyServerPeer);

Assert.True(eventPulled.WaitOne(5000));

// This verification needs to be ran on xUnit thread.
// Otherwise xUnit cannot identify which test is failure.
verifyCompletedEvent(eventArgs);

if (eventArgs.Connected)
{
testCopyDataFromLeftToRight?.Invoke(proxyServerPeer, eventArgs.Socket);
}
}

void SendAndReceiveHello(Socket left, Socket right)
{
var echoMessage = $"HELLO, it is {DateTime.Now.Ticks} now!\r\n";

SendLine(left, echoMessage);
Assert.Equal(echoMessage, ReadLine(right));
}

void SendLine(Socket socket, string line)
{
Thread.Sleep(100);
socket.Send(Encoding.ASCII.GetBytes(line));
}

Socket CreateSimplyRespond(byte[] data)
string ReadLine(Socket socket)
{
var socket = NewTcpLocalBound();
Task.Run(
() =>
byte[] lineBuff = new byte[1024];
int at = 0;
while (true)
{
int received = socket.Receive(lineBuff, at, 1, SocketFlags.None);
if (received < 0)
{
var stream = socket.Accept();
stream.Send(data);
stream.Shutdown(SocketShutdown.Both);
socket.Dispose();
break;
}
);

return socket;
if (lineBuff[at] == 10)
{
at++;
break;
}
at++;
}
return Encoding.ASCII.GetString(lineBuff, 0, at);
}

Socket NewTcp() => new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

Socket NewTcpLocalBound()
Socket NewTcpPeer()
{
var socket = NewTcp();
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
socket.Listen(1);
return socket;
Expand Down
29 changes: 29 additions & 0 deletions Test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Test

Open `../global.json`, and remove `sdk`, and save like:

```json
{
"projects": [
".", "SuperSocket.ClientEngine", "Test"
]
}
```

Launch test by `dotnet test`

```bat
D:\Proj\SuperSocket.ClientEngine\Test>dotnet test
???????????????????????...
???????????

D:\Proj\SuperSocket.ClientEngine\Test\bin\Debug\netcoreapp1.0\Test.dll(.NETCoreApp,Version=v1.0) ??????
Microsoft (R) Test Execution Command Line Tool Version 15.9.0
Copyright (c) Microsoft Corporation. All rights reserved.

Starting test execution, please wait...

Total tests: 14. Passed: 14. Failed: 0. Skipped: 0.
Test Run Successful.
Test execution time: 4.7565 Seconds
```
27 changes: 27 additions & 0 deletions Test/SearchMarkTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,32 @@ public void TestMatchSecondTimeWithSearchMarkState()
Assert.Equal(0, second.SearchMark(0, second.Length, searchState));
}
}


[Fact]
public void Test()
{
byte[] first = Encoding.ASCII.GetBytes("HTTP/1.1 200 Connection Established\r\n");
byte[] second = Encoding.ASCII.GetBytes("Proxy-agent: Apache/2.2.29 (Win32)\r\n\r\n");

byte[] mark = Encoding.ASCII.GetBytes("\r\n\r\n");

var searchState = new SearchMarkState<byte>(mark);

{
// -1 means: not matched, or partially matched.
Assert.Equal(-1, first.SearchMark(0, first.Length, searchState));

// Check if (1 <= searchState.Matched) in case of partial match.
}
{
var prevMatched = searchState.Matched;
Assert.Equal(prevMatched, 2);

// "\r\n\r\n" is matched on second buffer at second[34] to [37].
// So prevMatched should be ignored this time.
Assert.Equal(34, second.SearchMark(0, second.Length, searchState));
}
}
}
}