Commit 42a5ee99 authored by Honfika's avatar Honfika

allow to change http/2 body

parent 7330b5c3
...@@ -159,6 +159,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -159,6 +159,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf
//{ //{
//} //}
//if (!e.HttpClient.Request.RequestUri.ToString().Contains("/mail/u/"))
// return;
if (e.HttpClient.Request.HasBody) if (e.HttpClient.Request.HasBody)
{ {
e.HttpClient.Request.KeepBody = true; e.HttpClient.Request.KeepBody = true;
...@@ -177,7 +180,10 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -177,7 +180,10 @@ namespace Titanium.Web.Proxy.Examples.Wpf
} }
}); });
e.HttpClient.Response.Headers.AddHeader("X-Titanium-Header", "HTTP/2 works"); //e.SetResponseBody(Encoding.ASCII.GetBytes("TITANIUMMMM!!!!"));
//if (!e.HttpClient.Request.RequestUri.ToString().Contains("/mail/u/"))
// return;
//e.HttpClient.Response.Headers.AddHeader("X-Titanium-Header", "HTTP/2 works");
//if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2) //if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2)
//{ //{
......
...@@ -91,6 +91,9 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -91,6 +91,9 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
if (request.HttpVersion == HttpHeader.Version20) if (request.HttpVersion == HttpHeader.Version20)
{ {
// do not send to the remote endpoint
request.Http2IgnoreBodyFrames = true;
request.Http2BodyData = new MemoryStream(); request.Http2BodyData = new MemoryStream();
var tcs = new TaskCompletionSource<bool>(); var tcs = new TaskCompletionSource<bool>();
...@@ -100,6 +103,10 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -100,6 +103,10 @@ namespace Titanium.Web.Proxy.EventArguments
request.ReadHttp2BeforeHandlerTaskCompletionSource.SetResult(true); request.ReadHttp2BeforeHandlerTaskCompletionSource.SetResult(true);
await tcs.Task; await tcs.Task;
// Now set the flag to true
// So that next time we can deliver body from cache
request.IsBodyRead = true;
} }
else else
{ {
...@@ -157,6 +164,9 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -157,6 +164,9 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
if (response.HttpVersion == HttpHeader.Version20) if (response.HttpVersion == HttpHeader.Version20)
{ {
// do not send to the remote endpoint
response.Http2IgnoreBodyFrames = true;
response.Http2BodyData = new MemoryStream(); response.Http2BodyData = new MemoryStream();
var tcs = new TaskCompletionSource<bool>(); var tcs = new TaskCompletionSource<bool>();
...@@ -166,6 +176,10 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -166,6 +176,10 @@ namespace Titanium.Web.Proxy.EventArguments
response.ReadHttp2BeforeHandlerTaskCompletionSource.SetResult(true); response.ReadHttp2BeforeHandlerTaskCompletionSource.SetResult(true);
await tcs.Task; await tcs.Task;
// Now set the flag to true
// So that next time we can deliver body from cache
response.IsBodyRead = true;
} }
else else
{ {
......
...@@ -56,6 +56,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -56,6 +56,15 @@ namespace Titanium.Web.Proxy.Http
internal MemoryStream Http2BodyData; internal MemoryStream Http2BodyData;
internal bool Http2IgnoreBodyFrames;
internal Task Http2BeforeHandlerTask;
/// <summary>
/// Priority used only in HTTP/2
/// </summary>
internal long? Priority;
/// <summary> /// <summary>
/// Keeps the body data after the session is finished. /// Keeps the body data after the session is finished.
/// </summary> /// </summary>
...@@ -201,6 +210,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -201,6 +210,8 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
internal bool Locked { get; set; } internal bool Locked { get; set; }
internal bool BodyAvailable => BodyInternal != null;
internal abstract void EnsureBodyAvailable(bool throwWhenNotReadYet = true); internal abstract void EnsureBodyAvailable(bool throwWhenNotReadYet = true);
/// <summary> /// <summary>
......
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
...@@ -128,22 +130,56 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -128,22 +130,56 @@ namespace Titanium.Web.Proxy.Http2
//System.Diagnostics.Debug.WriteLine("CONN: " + connectionId + ", CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type); //System.Diagnostics.Debug.WriteLine("CONN: " + connectionId + ", CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type);
if (type == 0 /* data */) if (type == 0 /* data */)
{ {
bool padded = (flags & (int)Http2FrameFlag.Padded) != 0;
bool endStreamFlag = (flags & (int)Http2FrameFlag.EndStream) != 0; bool endStreamFlag = (flags & (int)Http2FrameFlag.EndStream) != 0;
if (endStreamFlag) if (endStreamFlag)
{ {
endStream = true; endStream = true;
} }
if (rr.Http2IgnoreBodyFrames)
{
sendPacket = false;
}
if (rr.ReadHttp2BodyTaskCompletionSource != null) if (rr.ReadHttp2BodyTaskCompletionSource != null)
{ {
// Get body method was called in the "before" event handler // Get body method was called in the "before" event handler
var data = rr.Http2BodyData; var data = rr.Http2BodyData;
data.Write(buffer, 0, length); int offset = 0;
if (padded)
{
offset++;
length--;
length -= buffer[0];
}
data.Write(buffer, offset, length);
if (endStream) if (endStream)
{ {
rr.Body = data.ToArray(); var body = data.ToArray();
if (rr.ContentEncoding != null)
{
using (var ms = new MemoryStream())
{
using (var zip =
DecompressionFactory.Create(rr.ContentEncoding, new MemoryStream(body)))
{
zip.CopyTo(ms);
}
body = ms.ToArray();
}
}
if (!rr.BodyAvailable)
{
rr.Body = body;
}
rr.IsBodyRead = true; rr.IsBodyRead = true;
var tcs = rr.ReadHttp2BodyTaskCompletionSource; var tcs = rr.ReadHttp2BodyTaskCompletionSource;
...@@ -155,6 +191,13 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -155,6 +191,13 @@ namespace Titanium.Web.Proxy.Http2
} }
rr.Http2BodyData = null; rr.Http2BodyData = null;
if (rr.Http2BeforeHandlerTask != null)
{
await rr.Http2BeforeHandlerTask;
}
await sendBody(remoteSettings, rr, headerBuffer, buffer, output);
} }
} }
} }
...@@ -173,11 +216,14 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -173,11 +216,14 @@ namespace Titanium.Web.Proxy.Http2
if (padded) if (padded)
{ {
offset = 1; offset = 1;
breakpoint();
} }
if (priority) if (priority)
{ {
offset += 5; var priorityData = ((long)buffer[offset++] << 32) + ((long)buffer[offset++] << 24) +
(buffer[offset++] << 16) + (buffer[offset++] << 8) + buffer[offset++];
rr.Priority = priorityData;
} }
int dataLength = length - offset; int dataLength = length - offset;
...@@ -212,6 +258,7 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -212,6 +258,7 @@ namespace Titanium.Web.Proxy.Http2
request.HttpVersion = HttpVersion.Version20; request.HttpVersion = HttpVersion.Version20;
request.Method = headerListener.Method; request.Method = headerListener.Method;
request.OriginalUrl = headerListener.Path; request.OriginalUrl = headerListener.Path;
request.RequestUri = headerListener.GetUri(); request.RequestUri = headerListener.GetUri();
} }
else else
...@@ -227,91 +274,43 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -227,91 +274,43 @@ namespace Titanium.Web.Proxy.Http2
exceptionFunc(new ProxyHttpException("Failed to decode HTTP/2 headers", ex, args)); exceptionFunc(new ProxyHttpException("Failed to decode HTTP/2 headers", ex, args));
} }
if (!endHeaders)
{
breakpoint();
}
if (endHeaders) if (endHeaders)
{ {
var tcs = new TaskCompletionSource<bool>(); var tcs = new TaskCompletionSource<bool>();
rr.ReadHttp2BeforeHandlerTaskCompletionSource = tcs; rr.ReadHttp2BeforeHandlerTaskCompletionSource = tcs;
var handler = onBeforeRequestResponse(args); var handler = onBeforeRequestResponse(args);
rr.Http2BeforeHandlerTask = handler;
if (handler == await Task.WhenAny(tcs.Task, handler)) if (handler == await Task.WhenAny(tcs.Task, handler))
{ {
rr.ReadHttp2BeforeHandlerTaskCompletionSource = null; rr.ReadHttp2BeforeHandlerTaskCompletionSource = null;
tcs.SetResult(true); tcs.SetResult(true);
} await sendHeader(remoteSettings, headerBuffer, rr, endStream, output);
rr.Locked = true;
var encoder = new Encoder(remoteSettings.HeaderTableSize);
var ms = new MemoryStream();
var writer = new BinaryWriter(ms);
if (priority)
{
writer.Write(buffer, padded ? 1 : 0, 5);
}
if (isClient)
{
var request = (Request)rr;
encoder.EncodeHeader(writer, ":method", request.Method);
encoder.EncodeHeader(writer, ":authority", request.RequestUri.Host);
encoder.EncodeHeader(writer, ":scheme", request.RequestUri.Scheme);
encoder.EncodeHeader(writer, ":path", request.RequestUriString, false,
HpackUtil.IndexType.None, false);
} }
else else
{ {
var response = (Response)rr; rr.Http2IgnoreBodyFrames = true;
encoder.EncodeHeader(writer, ":status", response.StatusCode.ToString());
} }
foreach (var header in rr.Headers) rr.Locked = true;
{
encoder.EncodeHeader(writer, header.Name.ToLower(), header.Value);
} }
var data = ms.ToArray(); sendPacket = false;
int newLength = data.Length;
//if (newLength == length)
//{
// var x = data;
// for (int i = 0; i < x.Length; i++)
// {
// if (i >= buffer.Length || buffer[i] != x[i])
// {
// ;
// }
// }
//}
headerBuffer[0] = (byte)((newLength >> 16) & 0xff);
headerBuffer[1] = (byte)((newLength >> 8) & 0xff);
headerBuffer[2] = (byte)(newLength & 0xff);
if (padded)
{
// clear the padding flag
headerBuffer[4] = (byte)(flags & ~((int)Http2FrameFlag.Padded));
} }
else if (type == 5 /* push_promise */)
//var headerListener2 = new MyHeaderListener2( {
// (name, value) => breakpoint();
// {
// System.Diagnostics.Debug.WriteLine("LL: " + name + ": " + value);
// });
//var decoder2 = new Decoder(8192, headerTableSize);
//decoder2.Decode(new BinaryReader(new MemoryStream(buffer, offset, dataLength)),
// headerListener2);
//decoder2.EndHeaderBlock();
await output.WriteAsync(headerBuffer, 0, headerBuffer.Length /*, cancellationToken*/);
await output.WriteAsync(data, 0, data.Length /*, cancellationToken*/);
} }
else if (type == 9 /* continuation */)
sendPacket = false; {
// todo: implementing this type is mandatory for multi-part headers
breakpoint();
} }
else if (type == 4 /* settings */) else if (type == 4 /* settings */)
{ {
...@@ -387,6 +386,105 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -387,6 +386,105 @@ namespace Titanium.Web.Proxy.Http2
} }
} }
[Conditional("DEBUG")]
private static void breakpoint()
{
// when this method is called something received which is not yet implemented
;
}
private static async Task sendHeader(Http2Settings settings, byte[] headerBuffer, RequestResponseBase rr, bool endStream, Stream output)
{
var encoder = new Encoder(settings.HeaderTableSize);
var ms = new MemoryStream();
var writer = new BinaryWriter(ms);
if (rr.Priority.HasValue)
{
long p = rr.Priority.Value;
writer.Write((byte)((p >> 32) & 0xff));
writer.Write((byte)((p >> 24) & 0xff));
writer.Write((byte)((p >> 16) & 0xff));
writer.Write((byte)((p >> 8) & 0xff));
writer.Write((byte)(p & 0xff));
}
if (rr is Request request)
{
encoder.EncodeHeader(writer, ":method", request.Method);
encoder.EncodeHeader(writer, ":authority", request.RequestUri.Host);
encoder.EncodeHeader(writer, ":scheme", request.RequestUri.Scheme);
encoder.EncodeHeader(writer, ":path", request.RequestUriString, false,
HpackUtil.IndexType.None, false);
}
else
{
var response = (Response)rr;
encoder.EncodeHeader(writer, ":status", response.StatusCode.ToString());
}
foreach (var header in rr.Headers)
{
encoder.EncodeHeader(writer, header.Name.ToLower(), header.Value);
}
var data = ms.ToArray();
int newLength = data.Length;
headerBuffer[0] = (byte)((newLength >> 16) & 0xff);
headerBuffer[1] = (byte)((newLength >> 8) & 0xff);
headerBuffer[2] = (byte)(newLength & 0xff);
headerBuffer[3] = 1; // type: header
int flags = (int)Http2FrameFlag.EndHeaders;
if (endStream)
{
flags |= (int)Http2FrameFlag.EndStream;
}
if (rr.Priority.HasValue)
{
flags |= (int)Http2FrameFlag.Priority;
}
headerBuffer[4] = (byte)flags;
// clear the padding flag
//headerBuffer[4] = (byte)(flags & ~((int)Http2FrameFlag.Padded));
// send the header
await output.WriteAsync(headerBuffer, 0, headerBuffer.Length /*, cancellationToken*/);
await output.WriteAsync(data, 0, data.Length /*, cancellationToken*/);
}
private static async Task sendBody(Http2Settings settings, RequestResponseBase rr, byte[] headerBuffer, byte[] buffer, Stream output)
{
var body = rr.CompressBodyAndUpdateContentLength();
await sendHeader(settings, headerBuffer, rr, !(rr.HasBody && rr.IsBodyRead), output);
if (rr.HasBody && rr.IsBodyRead)
{
int pos = 0;
while (pos < body.Length)
{
int bodyFrameLength = Math.Min(buffer.Length, body.Length - pos);
Buffer.BlockCopy(body, pos, buffer, 0, bodyFrameLength);
pos += bodyFrameLength;
headerBuffer[0] = (byte)((bodyFrameLength >> 16) & 0xff);
headerBuffer[1] = (byte)((bodyFrameLength >> 8) & 0xff);
headerBuffer[2] = (byte)(bodyFrameLength & 0xff);
headerBuffer[3] = 0; // type: data
headerBuffer[4] = pos < body.Length ? (byte)0 : (byte)(int)Http2FrameFlag.EndStream;
await output.WriteAsync(headerBuffer, 0, headerBuffer.Length /*, cancellationToken*/);
await output.WriteAsync(buffer, 0, bodyFrameLength /*, cancellationToken*/);
}
}
else
{
;
}
}
private static async Task<int> forceRead(Stream input, byte[] buffer, int offset, int bytesToRead, private static async Task<int> forceRead(Stream input, byte[] buffer, int offset, int bytesToRead,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
......
...@@ -125,7 +125,6 @@ namespace Titanium.Web.Proxy ...@@ -125,7 +125,6 @@ namespace Titanium.Web.Proxy
} }
args.TimeLine["Response Sent"] = DateTime.Now; args.TimeLine["Response Sent"] = DateTime.Now;
} }
/// <summary> /// <summary>
......
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