Unverified Commit 6a186156 authored by honfika's avatar honfika Committed by GitHub

Merge pull request #587 from honfika/master

allow to modify HTTP/2 header
parents 29ffc5b9 0c88f3f1
...@@ -152,6 +152,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -152,6 +152,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf
private async Task ProxyServer_BeforeRequest(object sender, SessionEventArgs e) private async Task ProxyServer_BeforeRequest(object sender, SessionEventArgs e)
{ {
if (e.HttpClient.ConnectRequest?.TunnelType != TunnelType.Http2)
{
return;
}
SessionListItem item = null; SessionListItem item = null;
await Dispatcher.InvokeAsync(() => { item = addSession(e); }); await Dispatcher.InvokeAsync(() => { item = addSession(e); });
...@@ -159,6 +164,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -159,6 +164,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;
...@@ -168,6 +176,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -168,6 +176,11 @@ namespace Titanium.Web.Proxy.Examples.Wpf
private async Task ProxyServer_BeforeResponse(object sender, SessionEventArgs e) private async Task ProxyServer_BeforeResponse(object sender, SessionEventArgs e)
{ {
if (e.HttpClient.ConnectRequest?.TunnelType != TunnelType.Http2)
{
return;
}
SessionListItem item = null; SessionListItem item = null;
await Dispatcher.InvokeAsync(() => await Dispatcher.InvokeAsync(() =>
{ {
...@@ -177,9 +190,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -177,9 +190,9 @@ namespace Titanium.Web.Proxy.Examples.Wpf
} }
}); });
//if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2) //e.HttpClient.Response.Headers.AddHeader("X-Titanium-Header", "HTTP/2 works");
//{
//} //e.SetResponseBody(Encoding.ASCII.GetBytes("TITANIUMMMM!!!!"));
if (item != null) if (item != null)
{ {
......
...@@ -28,6 +28,11 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -28,6 +28,11 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
private bool reRequest; private bool reRequest;
/// <summary>
/// Is this session a HTTP/2 promise?
/// </summary>
public bool IsPromise { get; internal set; }
/// <summary> /// <summary>
/// Constructor to initialize the proxy /// Constructor to initialize the proxy
/// </summary> /// </summary>
...@@ -91,6 +96,9 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -91,6 +96,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 +108,10 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -100,6 +108,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 +169,9 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -157,6 +169,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 +181,10 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -166,6 +181,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>
......
...@@ -61,7 +61,9 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -61,7 +61,9 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
/// <param name="value">Value.</param> /// <param name="value">Value.</param>
/// <param name="sensitive">If set to <c>true</c> sensitive.</param> /// <param name="sensitive">If set to <c>true</c> sensitive.</param>
public void EncodeHeader(BinaryWriter output, string name, string value, bool sensitive = false) /// <param name="indexType">Index type.</param>
/// <param name="useStaticName">Use static name.</param>
public void EncodeHeader(BinaryWriter output, string name, string value, bool sensitive = false, HpackUtil.IndexType indexType = HpackUtil.IndexType.Incremental, bool useStaticName = true)
{ {
// If the header value is sensitive then it must never be indexed // If the header value is sensitive then it must never be indexed
if (sensitive) if (sensitive)
...@@ -116,10 +118,9 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -116,10 +118,9 @@ namespace Titanium.Web.Proxy.Http2.Hpack
} }
else else
{ {
int nameIndex = getNameIndex(name); int nameIndex = useStaticName ? getNameIndex(name) : -1;
ensureCapacity(headerSize); ensureCapacity(headerSize);
var indexType = HpackUtil.IndexType.Incremental;
encodeLiteral(output, name, value, indexType, nameIndex); encodeLiteral(output, name, value, indexType, nameIndex);
add(name, value); add(name, value);
} }
...@@ -309,7 +310,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -309,7 +310,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
int i = index(h); int i = index(h);
for (var e = headerFields[i]; e != null; e = e.Next) for (var e = headerFields[i]; e != null; e = e.Next)
{ {
if (e.Hash == h && Equals(name, e.Name) && Equals(value, e.Value)) if (e.Hash == h && name.Equals(e.Name, StringComparison.OrdinalIgnoreCase) && Equals(value, e.Value))
{ {
return e; return e;
} }
...@@ -336,7 +337,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -336,7 +337,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
int index = -1; int index = -1;
for (var e = headerFields[i]; e != null; e = e.Next) for (var e = headerFields[i]; e != null; e = e.Next)
{ {
if (e.Hash == h && HpackUtil.Equals(name, e.Name)) if (e.Hash == h && name.Equals(e.Name, StringComparison.OrdinalIgnoreCase))
{ {
index = e.Index; index = e.Index;
break; break;
...@@ -513,7 +514,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -513,7 +514,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// <param name="value">Value.</param> /// <param name="value">Value.</param>
/// <param name="index">Index.</param> /// <param name="index">Index.</param>
/// <param name="next">Next.</param> /// <param name="next">Next.</param>
public HeaderEntry(int hash, string name, string value, int index, HeaderEntry next) : base(name, value) public HeaderEntry(int hash, string name, string value, int index, HeaderEntry next) : base(name, value, true)
{ {
Index = index; Index = index;
Hash = hash; Hash = hash;
......
...@@ -154,7 +154,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -154,7 +154,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
new HttpHeader("WWW-Authenticate", string.Empty) new HttpHeader("WWW-Authenticate", string.Empty)
}; };
private static readonly Dictionary<string, int> staticIndexByName = CreateMap(); private static readonly Dictionary<string, int> staticIndexByName = createMap();
/// <summary> /// <summary>
/// The number of header fields in the static table. /// The number of header fields in the static table.
...@@ -180,12 +180,12 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -180,12 +180,12 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
public static int GetIndex(string name) public static int GetIndex(string name)
{ {
if (!staticIndexByName.ContainsKey(name)) if (!staticIndexByName.TryGetValue(name, out int index))
{ {
return -1; return -1;
} }
return staticIndexByName[name]; return index;
} }
/// <summary> /// <summary>
...@@ -207,7 +207,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -207,7 +207,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
while (index <= Length) while (index <= Length)
{ {
var entry = Get(index); var entry = Get(index);
if (!HpackUtil.Equals(name, entry.Name)) if (!name.Equals(entry.Name, StringComparison.OrdinalIgnoreCase))
{ {
break; break;
} }
...@@ -227,7 +227,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -227,7 +227,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/// create a map of header name to index value to allow quick lookup /// create a map of header name to index value to allow quick lookup
/// </summary> /// </summary>
/// <returns>The map.</returns> /// <returns>The map.</returns>
private static Dictionary<string, int> CreateMap() private static Dictionary<string, int> createMap()
{ {
int length = staticTable.Count; int length = staticTable.Count;
var ret = new Dictionary<string, int>(length); var ret = new Dictionary<string, int>(length);
...@@ -237,7 +237,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack ...@@ -237,7 +237,7 @@ namespace Titanium.Web.Proxy.Http2.Hpack
for (int index = length; index > 0; index--) for (int index = length; index > 0; index--)
{ {
var entry = Get(index); var entry = Get(index);
string name = entry.Name; string name = entry.Name.ToLower();
ret[name] = index; ret[name] = index;
} }
......
using System;
namespace Titanium.Web.Proxy.Http2
{
[Flags]
internal enum Http2FrameFlag : byte
{
Ack = 0x01,
EndStream = 0x01,
EndHeaders = 0x04,
Padded = 0x08,
Priority = 0x20,
}
}
\ No newline at end of file
namespace Titanium.Web.Proxy.Http2
{
internal class Http2FrameHeader
{
public int Length;
public Http2FrameType Type;
public Http2FrameFlag Flags;
public int StreamId;
public byte[] Buffer;
public byte[] CopyToBuffer()
{
int length = Length;
var buf = /*new byte[9];*/Buffer;
buf[0] = (byte)((length >> 16) & 0xff);
buf[1] = (byte)((length >> 8) & 0xff);
buf[2] = (byte)(length & 0xff);
buf[3] = (byte)Type;
buf[4] = (byte)Flags;
int streamId = StreamId;
//buf[5] = (byte)((streamId >> 24) & 0xff);
//buf[6] = (byte)((streamId >> 16) & 0xff);
//buf[7] = (byte)((streamId >> 8) & 0xff);
//buf[8] = (byte)(streamId & 0xff);
return buf;
}
}
}
namespace Titanium.Web.Proxy.Http2
{
internal enum Http2FrameType : byte
{
Data = 0x00,
Headers = 0x01,
Priority = 0x02,
RstStream = 0x03,
Settings = 0x04,
PushPromise = 0x05,
Ping = 0x06,
GoAway = 0x07,
WindowUpdate = 0x08,
Continuation = 0x09,
}
}
\ No newline at end of file
...@@ -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;
...@@ -13,16 +15,6 @@ using Titanium.Web.Proxy.Http2.Hpack; ...@@ -13,16 +15,6 @@ using Titanium.Web.Proxy.Http2.Hpack;
namespace Titanium.Web.Proxy.Http2 namespace Titanium.Web.Proxy.Http2
{ {
[Flags]
internal enum Http2FrameFlag
{
Ack = 0x01,
EndStream = 0x01,
EndHeaders = 0x04,
Padded = 0x08,
Priority = 0x20,
}
internal class Http2Helper internal class Http2Helper
{ {
/// <summary> /// <summary>
...@@ -70,22 +62,29 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -70,22 +62,29 @@ namespace Titanium.Web.Proxy.Http2
int headerTableSize = 0; int headerTableSize = 0;
Decoder decoder = null; Decoder decoder = null;
var headerBuffer = new byte[9]; var frameHeader = new Http2FrameHeader();
frameHeader.Buffer = new byte[9];
byte[] buffer = null; byte[] buffer = null;
while (true) while (true)
{ {
int read = await forceRead(input, headerBuffer, 0, 9, cancellationToken); var frameHeaderBuffer = frameHeader.Buffer;
onCopy(headerBuffer, 0, read); int read = await forceRead(input, frameHeaderBuffer, 0, 9, cancellationToken);
onCopy(frameHeaderBuffer, 0, read);
if (read != 9) if (read != 9)
{ {
return; return;
} }
int length = (headerBuffer[0] << 16) + (headerBuffer[1] << 8) + headerBuffer[2]; int length = (frameHeaderBuffer[0] << 16) + (frameHeaderBuffer[1] << 8) + frameHeaderBuffer[2];
byte type = headerBuffer[3]; var type = (Http2FrameType)frameHeaderBuffer[3];
byte flags = headerBuffer[4]; var flags = (Http2FrameFlag)frameHeaderBuffer[4];
int streamId = ((headerBuffer[5] & 0x7f) << 24) + (headerBuffer[6] << 16) + (headerBuffer[7] << 8) + int streamId = ((frameHeaderBuffer[5] & 0x7f) << 24) + (frameHeaderBuffer[6] << 16) +
headerBuffer[8]; (frameHeaderBuffer[7] << 8) + frameHeaderBuffer[8];
frameHeader.Length = length;
frameHeader.Type = type;
frameHeader.Flags = flags;
frameHeader.StreamId = streamId;
if (buffer == null || buffer.Length < localSettings.MaxFrameSize) if (buffer == null || buffer.Length < localSettings.MaxFrameSize)
{ {
...@@ -99,70 +98,71 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -99,70 +98,71 @@ namespace Titanium.Web.Proxy.Http2
return; return;
} }
bool sendPacket = true;
bool endStream = false; bool endStream = false;
SessionEventArgs args = null; SessionEventArgs args = null;
RequestResponseBase rr = null; RequestResponseBase rr = null;
if (type == 0 || type == 1) if (type == Http2FrameType.Data || type == Http2FrameType.Headers/* || type == Http2FrameType.PushPromise*/)
{ {
if (!sessions.TryGetValue(streamId, out args)) if (!sessions.TryGetValue(streamId, out args))
{ {
if (type == 0) //if (type == Http2FrameType.Data)
{ //{
throw new ProxyHttpException("HTTP Body data received before any header frame.", null, args); // throw new ProxyHttpException("HTTP Body data received before any header frame.", null, args);
} //}
if (!isClient) //if (type == Http2FrameType.Headers && !isClient)
//{
// throw new ProxyHttpException("HTTP Response received before any Request header frame.", null, args);
//}
if (type == Http2FrameType.PushPromise && isClient)
{ {
throw new ProxyHttpException("HTTP Response received before any Request header frame.", null, args); throw new ProxyHttpException("HTTP Push promise received from the client.", 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); //System.Diagnostics.Debug.WriteLine("CONN: " + connectionId + ", CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type);
if (type == 0 /* data */) if (type == Http2FrameType.Data && args != null)
{ {
bool endStreamFlag = (flags & (int)Http2FrameFlag.EndStream) != 0; rr = isClient ? (RequestResponseBase)args.HttpClient.Request : args.HttpClient.Response;
bool padded = (flags & Http2FrameFlag.Padded) != 0;
bool endStreamFlag = (flags & 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)
if (endStream)
{ {
rr.Body = data.ToArray(); offset++;
rr.IsBodyRead = true; length--;
length -= buffer[0];
var tcs = rr.ReadHttp2BodyTaskCompletionSource;
rr.ReadHttp2BodyTaskCompletionSource = null;
if (!tcs.Task.IsCompleted)
{
tcs.SetResult(true);
}
rr.Http2BodyData = null;
} }
}
data.Write(buffer, offset, length);
}
} }
else if (type == 1 /* headers */) else if (type == Http2FrameType.Headers/* || type == Http2FrameType.PushPromise*/)
{ {
bool endHeaders = (flags & (int)Http2FrameFlag.EndHeaders) != 0; bool endHeaders = (flags & Http2FrameFlag.EndHeaders) != 0;
bool padded = (flags & (int)Http2FrameFlag.Padded) != 0; bool padded = (flags & Http2FrameFlag.Padded) != 0;
bool priority = (flags & (int)Http2FrameFlag.Priority) != 0; bool priority = (flags & Http2FrameFlag.Priority) != 0;
bool endStreamFlag = (flags & (int)Http2FrameFlag.EndStream) != 0; bool endStreamFlag = (flags & Http2FrameFlag.EndStream) != 0;
if (endStreamFlag) if (endStreamFlag)
{ {
endStream = true; endStream = true;
...@@ -172,12 +172,47 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -172,12 +172,47 @@ namespace Titanium.Web.Proxy.Http2
if (padded) if (padded)
{ {
offset = 1; offset = 1;
breakpoint();
} }
if (priority) if (type == Http2FrameType.PushPromise)
{ {
offset += 5; int promisedStreamId = (buffer[offset++] << 24) + (buffer[offset++] << 16) + (buffer[offset++] << 8) + buffer[offset++];
if (!sessions.TryGetValue(streamId, out args))
{
args = sessionFactory();
args.IsPromise = true;
sessions.TryAdd(streamId, args);
sessions.TryAdd(promisedStreamId, args);
}
System.Diagnostics.Debug.WriteLine("PROMISE STREAM: " + streamId + ", " + promisedStreamId +
", CONN: " + connectionId);
rr = args.HttpClient.Request;
if (isClient)
{
// push_promise from client???
breakpoint();
}
} }
else
{
if (!sessions.TryGetValue(streamId, out args))
{
args = sessionFactory();
sessions.TryAdd(streamId, args);
}
rr = isClient ? (RequestResponseBase)args.HttpClient.Request : args.HttpClient.Response;
if (priority)
{
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;
if (padded) if (padded)
...@@ -205,17 +240,17 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -205,17 +240,17 @@ namespace Titanium.Web.Proxy.Http2
headerListener); headerListener);
decoder.EndHeaderBlock(); decoder.EndHeaderBlock();
if (isClient) if (rr is Request request)
{ {
var request = args.HttpClient.Request;
request.HttpVersion = HttpVersion.Version20; request.HttpVersion = HttpVersion.Version20;
request.Method = headerListener.Method; request.Method = headerListener.Method;
request.OriginalUrl = headerListener.Status; request.OriginalUrl = headerListener.Path;
request.RequestUri = headerListener.GetUri(); request.RequestUri = headerListener.GetUri();
} }
else else
{ {
var response = args.HttpClient.Response; var response = (Response)rr;
response.HttpVersion = HttpVersion.Version20; response.HttpVersion = HttpVersion.Version20;
int.TryParse(headerListener.Status, out int statusCode); int.TryParse(headerListener.Status, out int statusCode);
response.StatusCode = statusCode; response.StatusCode = statusCode;
...@@ -226,23 +261,41 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -226,23 +261,41 @@ 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, frameHeader, rr, endStream, output, args.IsPromise);
}
else
{
rr.Http2IgnoreBodyFrames = true;
} }
rr.Locked = true; rr.Locked = true;
} }
sendPacket = false;
} }
else if (type == 4 /* settings */) else if (type == Http2FrameType.Continuation)
{
// todo: implementing this type is mandatory for multi-part headers
breakpoint();
}
else if (type == Http2FrameType.Settings)
{ {
if (length % 6 != 0) if (length % 6 != 0)
{ {
...@@ -269,7 +322,7 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -269,7 +322,7 @@ namespace Titanium.Web.Proxy.Http2
} }
} }
if (type == 3 /* rst_stream */) if (type == Http2FrameType.RstStream)
{ {
int errorCode = (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3]; int errorCode = (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3];
if (streamId == 0) if (streamId == 0)
...@@ -290,15 +343,68 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -290,15 +343,68 @@ namespace Titanium.Web.Proxy.Http2
} }
} }
if (endStream && rr.ReadHttp2BodyTaskCompletionSource != null)
{
if (!rr.BodyAvailable)
{
var data = rr.Http2BodyData;
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();
}
}
rr.Body = body;
}
rr.IsBodyRead = true;
var tcs = rr.ReadHttp2BodyTaskCompletionSource;
rr.ReadHttp2BodyTaskCompletionSource = null;
if (!tcs.Task.IsCompleted)
{
tcs.SetResult(true);
}
rr.Http2BodyData = null;
if (rr.Http2BeforeHandlerTask != null)
{
await rr.Http2BeforeHandlerTask;
}
if (args.IsPromise)
{
breakpoint();
}
await sendBody(remoteSettings, rr, frameHeader, buffer, output);
}
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); System.Diagnostics.Debug.WriteLine("REMOVED CONN: " + connectionId + ", CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type);
} }
// do not cancel the write operation if (sendPacket)
await output.WriteAsync(headerBuffer, 0, headerBuffer.Length/*, cancellationToken*/); {
await output.WriteAsync(buffer, 0, length/*, cancellationToken*/); // do not cancel the write operation
var buf = frameHeader.CopyToBuffer();
await output.WriteAsync(buf, 0, buf.Length/*, cancellationToken*/);
await output.WriteAsync(buffer, 0, length /*, cancellationToken*/);
}
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
...@@ -313,6 +419,104 @@ namespace Titanium.Web.Proxy.Http2 ...@@ -313,6 +419,104 @@ 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, Http2FrameHeader frameHeader, RequestResponseBase rr, bool endStream, Stream output, bool pushPromise)
{
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;
frameHeader.Length = newLength;
frameHeader.Type = pushPromise ? Http2FrameType.PushPromise : Http2FrameType.Headers;
var flags = Http2FrameFlag.EndHeaders;
if (endStream)
{
flags |= Http2FrameFlag.EndStream;
}
if (rr.Priority.HasValue)
{
flags |= Http2FrameFlag.Priority;
}
frameHeader.Flags = flags;
// clear the padding flag
//headerBuffer[4] = (byte)(flags & ~((int)Http2FrameFlag.Padded));
// send the header
var buf = frameHeader.CopyToBuffer();
await output.WriteAsync(buf, 0, buf.Length/*, cancellationToken*/);
await output.WriteAsync(data, 0, data.Length /*, cancellationToken*/);
}
private static async Task sendBody(Http2Settings settings, RequestResponseBase rr, Http2FrameHeader frameHeader, byte[] buffer, Stream output)
{
var body = rr.CompressBodyAndUpdateContentLength();
await sendHeader(settings, frameHeader, rr, !(rr.HasBody && rr.IsBodyRead), output, false);
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;
frameHeader.Length = bodyFrameLength;
frameHeader.Type = Http2FrameType.Data;
frameHeader.Flags = pos < body.Length ? (Http2FrameFlag)0 : Http2FrameFlag.EndStream;
var buf = frameHeader.CopyToBuffer();
await output.WriteAsync(buf, 0, buf.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)
{ {
......
...@@ -35,13 +35,20 @@ namespace Titanium.Web.Proxy.Models ...@@ -35,13 +35,20 @@ namespace Titanium.Web.Proxy.Models
{ {
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
{ {
throw new Exception("Name cannot be null"); throw new Exception("Name cannot be null or empty");
} }
Name = name.Trim(); Name = name.Trim();
Value = value.Trim(); Value = value.Trim();
} }
protected HttpHeader(string name, string value, bool headerEntry)
{
// special header entry created in inherited class with empty name
Name = name.Trim();
Value = value.Trim();
}
/// <summary> /// <summary>
/// Header Name. /// Header Name.
/// </summary> /// </summary>
......
...@@ -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