Unverified Commit 66c41133 authored by honfika's avatar honfika Committed by GitHub

Merge pull request #584 from honfika/master

HTTP/2: allow to read the body
parents 03cd1e18 e6a201e1
...@@ -36,6 +36,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -36,6 +36,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public MainWindow() public MainWindow()
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
proxyServer.EnableHttp2 = true;
//proxyServer.CertificateManager.CertificateEngine = CertificateEngine.DefaultWindows; //proxyServer.CertificateManager.CertificateEngine = CertificateEngine.DefaultWindows;
////Set a password for the .pfx file ////Set a password for the .pfx file
...@@ -150,11 +153,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -150,11 +153,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf
SessionListItem item = null; SessionListItem item = null;
await Dispatcher.InvokeAsync(() => { item = addSession(e); }); await Dispatcher.InvokeAsync(() => { item = addSession(e); });
if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2) //if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2)
{ //{
// GetRequestBody for HTTP/2 currently not supported //}
return;
}
if (e.HttpClient.Request.HasBody) if (e.HttpClient.Request.HasBody)
{ {
...@@ -174,11 +175,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -174,11 +175,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf
} }
}); });
if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2) //if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2)
{ //{
// GetRequestBody for HTTP/2 currently not supported //}
return;
}
if (item != null) if (item != null)
{ {
......
...@@ -89,13 +89,28 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -89,13 +89,28 @@ namespace Titanium.Web.Proxy.EventArguments
// If not already read (not cached yet) // If not already read (not cached yet)
if (!request.IsBodyRead) if (!request.IsBodyRead)
{ {
var body = await readBodyAsync(true, cancellationToken); if (request.HttpVersion == HttpHeader.Version20)
request.Body = body; {
request.Http2BodyData = new MemoryStream();
var tcs = new TaskCompletionSource<bool>();
request.ReadHttp2BodyTaskCompletionSource = tcs;
// signal to HTTP/2 copy frame method to continue
request.ReadHttp2BeforeHandlerTaskCompletionSource.SetResult(true);
await tcs.Task;
}
else
{
var body = await readBodyAsync(true, cancellationToken);
request.Body = body;
// Now set the flag to true // Now set the flag to true
// So that next time we can deliver body from cache // So that next time we can deliver body from cache
request.IsBodyRead = true; request.IsBodyRead = true;
OnDataSent(body, 0, body.Length); OnDataSent(body, 0, body.Length);
}
} }
} }
...@@ -140,13 +155,28 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -140,13 +155,28 @@ namespace Titanium.Web.Proxy.EventArguments
// If not already read (not cached yet) // If not already read (not cached yet)
if (!response.IsBodyRead) if (!response.IsBodyRead)
{ {
var body = await readBodyAsync(false, cancellationToken); if (response.HttpVersion == HttpHeader.Version20)
response.Body = body; {
response.Http2BodyData = new MemoryStream();
var tcs = new TaskCompletionSource<bool>();
response.ReadHttp2BodyTaskCompletionSource = tcs;
// signal to HTTP/2 copy frame method to continue
response.ReadHttp2BeforeHandlerTaskCompletionSource.SetResult(true);
await tcs.Task;
}
else
{
var body = await readBodyAsync(false, cancellationToken);
response.Body = body;
// Now set the flag to true // Now set the flag to true
// So that next time we can deliver body from cache // So that next time we can deliver body from cache
response.IsBodyRead = true; response.IsBodyRead = true;
OnDataReceived(body, 0, body.Length); OnDataReceived(body, 0, body.Length);
}
} }
} }
......
...@@ -55,25 +55,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -55,25 +55,7 @@ namespace Titanium.Web.Proxy.EventArguments
HttpClient = new HttpWebClient(request); HttpClient = new HttpWebClient(request);
LocalEndPoint = endPoint; LocalEndPoint = endPoint;
HttpClient.ProcessId = new Lazy<int>(() => HttpClient.ProcessId = new Lazy<int>(() => ProxyClient.Connection.GetProcessId(endPoint));
{
if (RunTime.IsWindows)
{
var remoteEndPoint = ClientEndPoint;
// If client is localhost get the process id
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
{
var ipVersion = endPoint.IpV6Enabled ? IpVersion.Ipv6 : IpVersion.Ipv4;
return TcpHelper.GetProcessIdByLocalPort(ipVersion, remoteEndPoint.Port);
}
// can't access process Id of remote request from remote machine
return -1;
}
throw new PlatformNotSupportedException();
});
} }
/// <summary> /// <summary>
......
...@@ -3,6 +3,7 @@ using System.ComponentModel; ...@@ -3,6 +3,7 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Compression; using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
...@@ -49,6 +50,12 @@ namespace Titanium.Web.Proxy.Http ...@@ -49,6 +50,12 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
internal string OriginalContentEncoding { get; set; } internal string OriginalContentEncoding { get; set; }
internal TaskCompletionSource<bool> ReadHttp2BeforeHandlerTaskCompletionSource;
internal TaskCompletionSource<bool> ReadHttp2BodyTaskCompletionSource;
internal MemoryStream Http2BodyData;
/// <summary> /// <summary>
/// Keeps the body data after the session is finished. /// Keeps the body data after the session is finished.
/// </summary> /// </summary>
......
...@@ -39,15 +39,14 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -39,15 +39,14 @@ namespace Titanium.Web.Proxy.Http2
CancellationTokenSource cancellationTokenSource, Guid connectionId, CancellationTokenSource cancellationTokenSource, Guid connectionId,
ExceptionHandler exceptionFunc) ExceptionHandler exceptionFunc)
{ {
var decoder = new Decoder(8192, 4096 * 16);
var sessions = new ConcurrentDictionary<int, SessionEventArgs>(); var sessions = new ConcurrentDictionary<int, SessionEventArgs>();
// Now async relay all server=>client & client=>server data // Now async relay all server=>client & client=>server data
var sendRelay = var sendRelay =
copyHttp2FrameAsync(clientStream, serverStream, onDataSend, sessionFactory, decoder, sessions, onBeforeRequest, copyHttp2FrameAsync(clientStream, serverStream, onDataSend, sessionFactory, sessions, onBeforeRequest,
bufferSize, connectionId, true, cancellationTokenSource.Token, exceptionFunc); bufferSize, connectionId, true, cancellationTokenSource.Token, exceptionFunc);
var receiveRelay = var receiveRelay =
copyHttp2FrameAsync(serverStream, clientStream, onDataReceive, sessionFactory, decoder, sessions, onBeforeResponse, copyHttp2FrameAsync(serverStream, clientStream, onDataReceive, sessionFactory, sessions, onBeforeResponse,
bufferSize, connectionId, false, cancellationTokenSource.Token, exceptionFunc); bufferSize, connectionId, false, cancellationTokenSource.Token, exceptionFunc);
await Task.WhenAny(sendRelay, receiveRelay); await Task.WhenAny(sendRelay, receiveRelay);
...@@ -57,11 +56,13 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -57,11 +56,13 @@ namespace Titanium.Web.Proxy.Http2
} }
private static async Task copyHttp2FrameAsync(Stream input, Stream output, Action<byte[], int, int> onCopy, private static async Task copyHttp2FrameAsync(Stream input, Stream output, Action<byte[], int, int> onCopy,
Func<SessionEventArgs> sessionFactory, Decoder decoder, ConcurrentDictionary<int, SessionEventArgs> sessions, Func<SessionEventArgs> sessionFactory, ConcurrentDictionary<int, SessionEventArgs> sessions,
Func<SessionEventArgs, Task> onBeforeRequestResponse, Func<SessionEventArgs, Task> onBeforeRequestResponse,
int bufferSize, Guid connectionId, bool isClient, CancellationToken cancellationToken, int bufferSize, Guid connectionId, bool isClient, CancellationToken cancellationToken,
ExceptionHandler exceptionFunc) ExceptionHandler exceptionFunc)
{ {
var decoder = new Decoder(8192, 4096 * 16);
var headerBuffer = new byte[9]; var headerBuffer = new byte[9];
var buffer = new byte[32768]; var buffer = new byte[32768];
while (true) while (true)
...@@ -88,7 +89,30 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -88,7 +89,30 @@ namespace Titanium.Web.Proxy.Http2
bool endStream = false; bool endStream = false;
//System.Diagnostics.Debug.WriteLine("CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type); SessionEventArgs args = null;
RequestResponseBase rr = null;
if (type == 0 || type == 1)
{
if (!sessions.TryGetValue(streamId, out args))
{
if (type == 0)
{
throw new ProxyHttpException("HTTP Body data received before any header frame.", null, args);
}
if (!isClient)
{
throw new ProxyHttpException("HTTP Response received before any Request header frame.", null, args);
}
args = sessionFactory();
sessions.TryAdd(streamId, args);
}
rr = isClient ? (RequestResponseBase)args.HttpClient.Request : args.HttpClient.Response;
}
//System.Diagnostics.Debug.WriteLine("CONN: " + connectionId + ", CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type);
if (type == 0 /* data */) if (type == 0 /* data */)
{ {
bool endStreamFlag = (flags & (int)Http2FrameFlag.EndStream) != 0; bool endStreamFlag = (flags & (int)Http2FrameFlag.EndStream) != 0;
...@@ -96,6 +120,30 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -96,6 +120,30 @@ namespace Titanium.Web.Proxy.Http2
{ {
endStream = true; endStream = true;
} }
if (rr.ReadHttp2BodyTaskCompletionSource != null)
{
// Get body method was called in the "before" event handler
var data = rr.Http2BodyData;
data.Write(buffer, 0, length);
if (endStream)
{
rr.Body = data.ToArray();
rr.IsBodyRead = true;
var tcs = rr.ReadHttp2BodyTaskCompletionSource;
rr.ReadHttp2BodyTaskCompletionSource = null;
if (!tcs.Task.IsCompleted)
{
tcs.SetResult(true);
}
rr.Http2BodyData = null;
}
}
} }
else if (type == 1 /*headers*/) else if (type == 1 /*headers*/)
{ {
...@@ -125,13 +173,6 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -125,13 +173,6 @@ namespace Titanium.Web.Proxy.Http2
dataLength -= buffer[0]; dataLength -= buffer[0];
} }
if (!sessions.TryGetValue(streamId, out var args))
{
// todo: remove sessions when finished, otherwise it will be a "memory leak"
args = sessionFactory();
sessions.TryAdd(streamId, args);
}
var headerListener = new MyHeaderListener( var headerListener = new MyHeaderListener(
(name, value) => (name, value) =>
{ {
...@@ -149,16 +190,18 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -149,16 +190,18 @@ namespace Titanium.Web.Proxy.Http2
if (isClient) if (isClient)
{ {
args.HttpClient.Request.HttpVersion = HttpVersion.Version20; var request = args.HttpClient.Request;
args.HttpClient.Request.Method = headerListener.Method; request.HttpVersion = HttpVersion.Version20;
args.HttpClient.Request.OriginalUrl = headerListener.Status; request.Method = headerListener.Method;
args.HttpClient.Request.RequestUri = headerListener.GetUri(); request.OriginalUrl = headerListener.Status;
request.RequestUri = headerListener.GetUri();
} }
else else
{ {
args.HttpClient.Response.HttpVersion = HttpVersion.Version20; var response = args.HttpClient.Response;
response.HttpVersion = HttpVersion.Version20;
int.TryParse(headerListener.Status, out int statusCode); int.TryParse(headerListener.Status, out int statusCode);
args.HttpClient.Response.StatusCode = statusCode; response.StatusCode = statusCode;
} }
} }
catch (Exception ex) catch (Exception ex)
...@@ -168,13 +211,25 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -168,13 +211,25 @@ namespace Titanium.Web.Proxy.Http2
if (endHeaders) if (endHeaders)
{ {
await onBeforeRequestResponse(args); var tcs = new TaskCompletionSource<bool>();
rr.ReadHttp2BeforeHandlerTaskCompletionSource = tcs;
var handler = onBeforeRequestResponse(args);
if (handler == await Task.WhenAny(tcs.Task, handler))
{
rr.ReadHttp2BeforeHandlerTaskCompletionSource = null;
tcs.SetResult(true);
}
rr.Locked = true;
} }
} }
if (!isClient && endStream) if (!isClient && endStream)
{ {
sessions.TryRemove(streamId, out _); sessions.TryRemove(streamId, out _);
//System.Diagnostics.Debug.WriteLine("REMOVED CONN: " + connectionId + ", CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type);
} }
// do not cancel the write operation // do not cancel the write operation
...@@ -186,7 +241,7 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -186,7 +241,7 @@ namespace Titanium.Web.Proxy.Http2
return; return;
} }
/*using (var fs = new System.IO.FileStream($@"c:\11\{connectionId}.{streamId}.dat", FileMode.Append)) /*using (var fs = new System.IO.FileStream($@"c:\temp\{connectionId}.{streamId}.dat", FileMode.Append))
{ {
fs.Write(headerBuffer, 0, headerBuffer.Length); fs.Write(headerBuffer, 0, headerBuffer.Length);
fs.Write(buffer, 0, length); fs.Write(buffer, 0, length);
......
...@@ -7,6 +7,8 @@ using System.Net.Security; ...@@ -7,6 +7,8 @@ using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
...@@ -34,11 +36,42 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -34,11 +36,42 @@ namespace Titanium.Web.Proxy.Network.Tcp
private readonly TcpClient tcpClient; private readonly TcpClient tcpClient;
private int? processId;
public Stream GetStream() public Stream GetStream()
{ {
return tcpClient.GetStream(); return tcpClient.GetStream();
} }
public int GetProcessId(ProxyEndPoint endPoint)
{
if (processId.HasValue)
{
return processId.Value;
}
if (RunTime.IsWindows)
{
var remoteEndPoint = (IPEndPoint)RemoteEndPoint;
// If client is localhost get the process id
if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
{
var ipVersion = endPoint.IpV6Enabled ? IpVersion.Ipv6 : IpVersion.Ipv4;
processId = TcpHelper.GetProcessIdByLocalPort(ipVersion, remoteEndPoint.Port);
}
else
{
// can't access process Id of remote request from remote machine
processId = -1;
}
return processId.Value;
}
throw new PlatformNotSupportedException();
}
/// <summary> /// <summary>
/// Dispose. /// Dispose.
/// </summary> /// </summary>
......
...@@ -149,7 +149,6 @@ namespace Titanium.Web.Proxy ...@@ -149,7 +149,6 @@ namespace Titanium.Web.Proxy
/// Enable disable HTTP/2 support. /// Enable disable HTTP/2 support.
/// Warning: HTTP/2 support is very limited /// Warning: HTTP/2 support is very limited
/// - only enabled when both client and server supports it (no protocol changing in proxy) /// - only enabled when both client and server supports it (no protocol changing in proxy)
/// - GetRequest/ResponseBody(AsString) methods are not supported
/// - cannot modify the request/response (e.g header modifications in BeforeRequest/Response events are ignored) /// - cannot modify the request/response (e.g header modifications in BeforeRequest/Response events are ignored)
/// </summary> /// </summary>
public bool EnableHttp2 { get; set; } = false; public bool EnableHttp2 { get; set; } = false;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment