From 7330b5c36919e1399b04143f0b4e5ee7ab9dd960 Mon Sep 17 00:00:00 2001 From: Honfika Date: Sat, 27 Apr 2019 10:09:16 +0200 Subject: [PATCH 1/4] allow to modify HTTP/2 ehader --- .../MainWindow.xaml.cs | 2 + src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs | 13 +-- .../Http2/Hpack/StaticTable.cs | 12 +-- src/Titanium.Web.Proxy/Http2/Http2Helper.cs | 82 ++++++++++++++++++- src/Titanium.Web.Proxy/Models/HttpHeader.cs | 9 +- 5 files changed, 101 insertions(+), 17 deletions(-) diff --git a/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs b/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs index 0e2cb80e9..62ef259d0 100644 --- a/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs +++ b/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs @@ -177,6 +177,8 @@ await Dispatcher.InvokeAsync(() => } }); + e.HttpClient.Response.Headers.AddHeader("X-Titanium-Header", "HTTP/2 works"); + //if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2) //{ //} diff --git a/src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs b/src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs index 5a90913cf..cf503a4d8 100644 --- a/src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs +++ b/src/Titanium.Web.Proxy/Http2/Hpack/Encoder.cs @@ -61,7 +61,9 @@ public Encoder(int maxHeaderTableSize) /// Name. /// Value. /// If set to true sensitive. - public void EncodeHeader(BinaryWriter output, string name, string value, bool sensitive = false) + /// Index type. + /// Use static name. + 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 (sensitive) @@ -116,10 +118,9 @@ public void EncodeHeader(BinaryWriter output, string name, string value, bool se } else { - int nameIndex = getNameIndex(name); + int nameIndex = useStaticName ? getNameIndex(name) : -1; ensureCapacity(headerSize); - var indexType = HpackUtil.IndexType.Incremental; encodeLiteral(output, name, value, indexType, nameIndex); add(name, value); } @@ -309,7 +310,7 @@ private HeaderEntry getEntry(string name, string value) int i = index(h); 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; } @@ -336,7 +337,7 @@ private int getIndex(string name) int index = -1; 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; break; @@ -513,7 +514,7 @@ private class HeaderEntry : HttpHeader /// Value. /// Index. /// Next. - 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; Hash = hash; diff --git a/src/Titanium.Web.Proxy/Http2/Hpack/StaticTable.cs b/src/Titanium.Web.Proxy/Http2/Hpack/StaticTable.cs index 771574f59..3198a3c34 100644 --- a/src/Titanium.Web.Proxy/Http2/Hpack/StaticTable.cs +++ b/src/Titanium.Web.Proxy/Http2/Hpack/StaticTable.cs @@ -154,7 +154,7 @@ public static class StaticTable new HttpHeader("WWW-Authenticate", string.Empty) }; - private static readonly Dictionary staticIndexByName = CreateMap(); + private static readonly Dictionary staticIndexByName = createMap(); /// /// The number of header fields in the static table. @@ -180,12 +180,12 @@ public static HttpHeader Get(int index) /// Name. public static int GetIndex(string name) { - if (!staticIndexByName.ContainsKey(name)) + if (!staticIndexByName.TryGetValue(name, out int index)) { return -1; } - return staticIndexByName[name]; + return index; } /// @@ -207,7 +207,7 @@ public static int GetIndex(string name, string value) while (index <= Length) { var entry = Get(index); - if (!HpackUtil.Equals(name, entry.Name)) + if (!name.Equals(entry.Name, StringComparison.OrdinalIgnoreCase)) { break; } @@ -227,7 +227,7 @@ public static int GetIndex(string name, string value) /// create a map of header name to index value to allow quick lookup /// /// The map. - private static Dictionary CreateMap() + private static Dictionary createMap() { int length = staticTable.Count; var ret = new Dictionary(length); @@ -237,7 +237,7 @@ private static Dictionary CreateMap() for (int index = length; index > 0; index--) { var entry = Get(index); - string name = entry.Name; + string name = entry.Name.ToLower(); ret[name] = index; } diff --git a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs index df31c6e08..2d5c02904 100644 --- a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs +++ b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs @@ -99,6 +99,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio return; } + bool sendPacket = true; bool endStream = false; SessionEventArgs args = null; @@ -210,7 +211,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio var request = args.HttpClient.Request; request.HttpVersion = HttpVersion.Version20; request.Method = headerListener.Method; - request.OriginalUrl = headerListener.Status; + request.OriginalUrl = headerListener.Path; request.RequestUri = headerListener.GetUri(); } else @@ -240,7 +241,77 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio } 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 + { + 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; + //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)); + } + + //var headerListener2 = new MyHeaderListener2( + // (name, value) => + // { + // 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*/); } + + sendPacket = false; } else if (type == 4 /* settings */) { @@ -296,9 +367,12 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio //System.Diagnostics.Debug.WriteLine("REMOVED CONN: " + connectionId + ", CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type); } - // do not cancel the write operation - await output.WriteAsync(headerBuffer, 0, headerBuffer.Length/*, cancellationToken*/); - await output.WriteAsync(buffer, 0, length/*, cancellationToken*/); + if (sendPacket) + { + // do not cancel the write operation + await output.WriteAsync(headerBuffer, 0, headerBuffer.Length /*, cancellationToken*/); + await output.WriteAsync(buffer, 0, length /*, cancellationToken*/); + } if (cancellationToken.IsCancellationRequested) { diff --git a/src/Titanium.Web.Proxy/Models/HttpHeader.cs b/src/Titanium.Web.Proxy/Models/HttpHeader.cs index 030008c60..02383fab7 100644 --- a/src/Titanium.Web.Proxy/Models/HttpHeader.cs +++ b/src/Titanium.Web.Proxy/Models/HttpHeader.cs @@ -35,13 +35,20 @@ public HttpHeader(string name, string value) { if (string.IsNullOrEmpty(name)) { - throw new Exception("Name cannot be null"); + throw new Exception("Name cannot be null or empty"); } Name = name.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(); + } + /// /// Header Name. /// From 42a5ee99ed2e142666978164f51f5e4a962a3d14 Mon Sep 17 00:00:00 2001 From: Honfika Date: Sat, 27 Apr 2019 23:33:20 +0200 Subject: [PATCH 2/4] allow to change http/2 body --- .../MainWindow.xaml.cs | 8 +- .../EventArguments/SessionEventArgs.cs | 14 ++ .../Http/RequestResponseBase.cs | 11 + src/Titanium.Web.Proxy/Http2/Http2Helper.cs | 238 ++++++++++++------ src/Titanium.Web.Proxy/ResponseHandler.cs | 1 - 5 files changed, 200 insertions(+), 72 deletions(-) diff --git a/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs b/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs index 62ef259d0..e6dec971a 100644 --- a/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs +++ b/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs @@ -159,6 +159,9 @@ private async Task ProxyServer_BeforeRequest(object sender, SessionEventArgs e) //{ //} + //if (!e.HttpClient.Request.RequestUri.ToString().Contains("/mail/u/")) + // return; + if (e.HttpClient.Request.HasBody) { e.HttpClient.Request.KeepBody = true; @@ -177,7 +180,10 @@ await Dispatcher.InvokeAsync(() => } }); - 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) //{ diff --git a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs index 52ee23464..1ae513631 100644 --- a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs +++ b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs @@ -91,6 +91,9 @@ private async Task readRequestBodyAsync(CancellationToken cancellationToken) { if (request.HttpVersion == HttpHeader.Version20) { + // do not send to the remote endpoint + request.Http2IgnoreBodyFrames = true; + request.Http2BodyData = new MemoryStream(); var tcs = new TaskCompletionSource(); @@ -100,6 +103,10 @@ private async Task readRequestBodyAsync(CancellationToken cancellationToken) request.ReadHttp2BeforeHandlerTaskCompletionSource.SetResult(true); await tcs.Task; + + // Now set the flag to true + // So that next time we can deliver body from cache + request.IsBodyRead = true; } else { @@ -157,6 +164,9 @@ private async Task readResponseBodyAsync(CancellationToken cancellationToken) { if (response.HttpVersion == HttpHeader.Version20) { + // do not send to the remote endpoint + response.Http2IgnoreBodyFrames = true; + response.Http2BodyData = new MemoryStream(); var tcs = new TaskCompletionSource(); @@ -166,6 +176,10 @@ private async Task readResponseBodyAsync(CancellationToken cancellationToken) response.ReadHttp2BeforeHandlerTaskCompletionSource.SetResult(true); await tcs.Task; + + // Now set the flag to true + // So that next time we can deliver body from cache + response.IsBodyRead = true; } else { diff --git a/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs b/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs index 9e3e988c0..e8134f735 100644 --- a/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs +++ b/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs @@ -56,6 +56,15 @@ public abstract class RequestResponseBase internal MemoryStream Http2BodyData; + internal bool Http2IgnoreBodyFrames; + + internal Task Http2BeforeHandlerTask; + + /// + /// Priority used only in HTTP/2 + /// + internal long? Priority; + /// /// Keeps the body data after the session is finished. /// @@ -201,6 +210,8 @@ internal set /// internal bool Locked { get; set; } + internal bool BodyAvailable => BodyInternal != null; + internal abstract void EnsureBodyAvailable(bool throwWhenNotReadYet = true); /// diff --git a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs index 2d5c02904..afc29b65c 100644 --- a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs +++ b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs @@ -2,10 +2,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; +using Titanium.Web.Proxy.Compression; using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Http; @@ -128,22 +130,56 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio //System.Diagnostics.Debug.WriteLine("CONN: " + connectionId + ", CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type); if (type == 0 /* data */) { + bool padded = (flags & (int)Http2FrameFlag.Padded) != 0; bool endStreamFlag = (flags & (int)Http2FrameFlag.EndStream) != 0; if (endStreamFlag) { endStream = true; } + if (rr.Http2IgnoreBodyFrames) + { + sendPacket = false; + } + if (rr.ReadHttp2BodyTaskCompletionSource != null) { // Get body method was called in the "before" event handler 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) { - 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; var tcs = rr.ReadHttp2BodyTaskCompletionSource; @@ -155,8 +191,15 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio } rr.Http2BodyData = null; + + if (rr.Http2BeforeHandlerTask != null) + { + await rr.Http2BeforeHandlerTask; + } + + await sendBody(remoteSettings, rr, headerBuffer, buffer, output); } - } + } } else if (type == 1 /* headers */) { @@ -173,11 +216,14 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio if (padded) { offset = 1; + breakpoint(); } 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; @@ -212,6 +258,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio request.HttpVersion = HttpVersion.Version20; request.Method = headerListener.Method; request.OriginalUrl = headerListener.Path; + request.RequestUri = headerListener.GetUri(); } else @@ -227,92 +274,44 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio exceptionFunc(new ProxyHttpException("Failed to decode HTTP/2 headers", ex, args)); } + if (!endHeaders) + { + breakpoint(); + } + if (endHeaders) { var tcs = new TaskCompletionSource(); rr.ReadHttp2BeforeHandlerTaskCompletionSource = tcs; var handler = onBeforeRequestResponse(args); + rr.Http2BeforeHandlerTask = handler; if (handler == await Task.WhenAny(tcs.Task, handler)) { rr.ReadHttp2BeforeHandlerTaskCompletionSource = null; tcs.SetResult(true); - } - - 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); + await sendHeader(remoteSettings, headerBuffer, rr, endStream, output); } else { - var response = (Response)rr; - encoder.EncodeHeader(writer, ":status", response.StatusCode.ToString()); + rr.Http2IgnoreBodyFrames = true; } - foreach (var header in rr.Headers) - { - encoder.EncodeHeader(writer, header.Name.ToLower(), header.Value); - } - - var data = ms.ToArray(); - 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)); - } - - //var headerListener2 = new MyHeaderListener2( - // (name, value) => - // { - // 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*/); + rr.Locked = true; } sendPacket = false; } + else if (type == 5 /* push_promise */) + { + breakpoint(); + } + else if (type == 9 /* continuation */) + { + // todo: implementing this type is mandatory for multi-part headers + breakpoint(); + } else if (type == 4 /* settings */) { if (length % 6 != 0) @@ -387,6 +386,105 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio } } + [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 forceRead(Stream input, byte[] buffer, int offset, int bytesToRead, CancellationToken cancellationToken) { diff --git a/src/Titanium.Web.Proxy/ResponseHandler.cs b/src/Titanium.Web.Proxy/ResponseHandler.cs index f727f01a4..39156f637 100644 --- a/src/Titanium.Web.Proxy/ResponseHandler.cs +++ b/src/Titanium.Web.Proxy/ResponseHandler.cs @@ -125,7 +125,6 @@ await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None, } args.TimeLine["Response Sent"] = DateTime.Now; - } /// From 8bbfa3085035c839ee66da961d3f551d84436db6 Mon Sep 17 00:00:00 2001 From: Honfika Date: Sun, 28 Apr 2019 15:49:29 +0200 Subject: [PATCH 3/4] HTTP/2 small refactoring --- .../EventArguments/SessionEventArgs.cs | 5 + .../Http2/Http2FrameFlag.cs | 14 ++ .../Http2/Http2FrameHeader.cs | 27 +++ .../Http2/Http2FrameType.cs | 16 ++ src/Titanium.Web.Proxy/Http2/Http2Helper.cs | 173 +++++++++++------- 5 files changed, 164 insertions(+), 71 deletions(-) create mode 100644 src/Titanium.Web.Proxy/Http2/Http2FrameFlag.cs create mode 100644 src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs create mode 100644 src/Titanium.Web.Proxy/Http2/Http2FrameType.cs diff --git a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs index 1ae513631..e93a417c6 100644 --- a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs +++ b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs @@ -28,6 +28,11 @@ public class SessionEventArgs : SessionEventArgsBase /// private bool reRequest; + /// + /// Is this session a HTTP/2 promise? + /// + public bool IsPromise { get; internal set; } + /// /// Constructor to initialize the proxy /// diff --git a/src/Titanium.Web.Proxy/Http2/Http2FrameFlag.cs b/src/Titanium.Web.Proxy/Http2/Http2FrameFlag.cs new file mode 100644 index 000000000..ba7eea53b --- /dev/null +++ b/src/Titanium.Web.Proxy/Http2/Http2FrameFlag.cs @@ -0,0 +1,14 @@ +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 diff --git a/src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs b/src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs new file mode 100644 index 000000000..d4e87bfc1 --- /dev/null +++ b/src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs @@ -0,0 +1,27 @@ +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 = 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; + return buf; + } + } +} diff --git a/src/Titanium.Web.Proxy/Http2/Http2FrameType.cs b/src/Titanium.Web.Proxy/Http2/Http2FrameType.cs new file mode 100644 index 000000000..a6dcbadf4 --- /dev/null +++ b/src/Titanium.Web.Proxy/Http2/Http2FrameType.cs @@ -0,0 +1,16 @@ +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 diff --git a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs index afc29b65c..5638074c3 100644 --- a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs +++ b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs @@ -15,16 +15,6 @@ namespace Titanium.Web.Proxy.Http2 { - [Flags] - internal enum Http2FrameFlag - { - Ack = 0x01, - EndStream = 0x01, - EndHeaders = 0x04, - Padded = 0x08, - Priority = 0x20, - } - internal class Http2Helper { /// @@ -72,22 +62,29 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio int headerTableSize = 0; Decoder decoder = null; - var headerBuffer = new byte[9]; + Http2FrameHeader frameHeader = new Http2FrameHeader(); + frameHeader.Buffer = new byte[9]; byte[] buffer = null; while (true) { - int read = await forceRead(input, headerBuffer, 0, 9, cancellationToken); - onCopy(headerBuffer, 0, read); + var frameHeaderBuffer = frameHeader.Buffer; + int read = await forceRead(input, frameHeaderBuffer, 0, 9, cancellationToken); + onCopy(frameHeaderBuffer, 0, read); if (read != 9) { return; } - int length = (headerBuffer[0] << 16) + (headerBuffer[1] << 8) + headerBuffer[2]; - byte type = headerBuffer[3]; - byte flags = headerBuffer[4]; - int streamId = ((headerBuffer[5] & 0x7f) << 24) + (headerBuffer[6] << 16) + (headerBuffer[7] << 8) + - headerBuffer[8]; + int length = (frameHeaderBuffer[0] << 16) + (frameHeaderBuffer[1] << 8) + frameHeaderBuffer[2]; + var type = (Http2FrameType)frameHeaderBuffer[3]; + var flags = (Http2FrameFlag)frameHeaderBuffer[4]; + int streamId = ((frameHeaderBuffer[5] & 0x7f) << 24) + (frameHeaderBuffer[6] << 16) + + (frameHeaderBuffer[7] << 8) + frameHeaderBuffer[8]; + + frameHeader.Length = length; + frameHeader.Type = type; + frameHeader.Flags = flags; + frameHeader.StreamId = streamId; if (buffer == null || buffer.Length < localSettings.MaxFrameSize) { @@ -105,33 +102,35 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio bool endStream = false; SessionEventArgs args = null; - RequestResponseBase rr = null; - if (type == 0 || type == 1) + RequestResponseBase rr; + if (type == Http2FrameType.Data || type == Http2FrameType.Headers || type == Http2FrameType.PushPromise) { 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); } - if (!isClient) + if (type == Http2FrameType.Headers && !isClient) { throw new ProxyHttpException("HTTP Response received before any Request header frame.", null, args); } - args = sessionFactory(); - sessions.TryAdd(streamId, args); + if (type == Http2FrameType.PushPromise && isClient) + { + throw new ProxyHttpException("HTTP Push promise received from the client.", null, 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 == Http2FrameType.Data) { - bool padded = (flags & (int)Http2FrameFlag.Padded) != 0; - 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) { endStream = true; @@ -197,16 +196,21 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio await rr.Http2BeforeHandlerTask; } - await sendBody(remoteSettings, rr, headerBuffer, buffer, output); + if (args.IsPromise) + { + breakpoint(); + } + + await sendBody(remoteSettings, rr, frameHeader, buffer, output); } } } - else if (type == 1 /* headers */) + else if (type == Http2FrameType.Headers/* || type == Http2FrameType.PushPromise*/) { - bool endHeaders = (flags & (int)Http2FrameFlag.EndHeaders) != 0; - bool padded = (flags & (int)Http2FrameFlag.Padded) != 0; - bool priority = (flags & (int)Http2FrameFlag.Priority) != 0; - bool endStreamFlag = (flags & (int)Http2FrameFlag.EndStream) != 0; + bool endHeaders = (flags & Http2FrameFlag.EndHeaders) != 0; + bool padded = (flags & Http2FrameFlag.Padded) != 0; + bool priority = (flags & Http2FrameFlag.Priority) != 0; + bool endStreamFlag = (flags & Http2FrameFlag.EndStream) != 0; if (endStreamFlag) { endStream = true; @@ -218,13 +222,45 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio offset = 1; breakpoint(); } - - if (priority) + + if (type == Http2FrameType.PushPromise) { - var priorityData = ((long)buffer[offset++] << 32) + ((long)buffer[offset++] << 24) + - (buffer[offset++] << 16) + (buffer[offset++] << 8) + buffer[offset++]; - rr.Priority = priorityData; + 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; if (padded) @@ -252,9 +288,8 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio headerListener); decoder.EndHeaderBlock(); - if (isClient) + if (rr is Request request) { - var request = args.HttpClient.Request; request.HttpVersion = HttpVersion.Version20; request.Method = headerListener.Method; request.OriginalUrl = headerListener.Path; @@ -263,7 +298,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio } else { - var response = args.HttpClient.Response; + var response = (Response)rr; response.HttpVersion = HttpVersion.Version20; int.TryParse(headerListener.Status, out int statusCode); response.StatusCode = statusCode; @@ -291,7 +326,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio { rr.ReadHttp2BeforeHandlerTaskCompletionSource = null; tcs.SetResult(true); - await sendHeader(remoteSettings, headerBuffer, rr, endStream, output); + await sendHeader(remoteSettings, frameHeader, rr, endStream, output, args.IsPromise); } else { @@ -303,16 +338,12 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio sendPacket = false; } - else if (type == 5 /* push_promise */) - { - breakpoint(); - } - else if (type == 9 /* continuation */) + else if (type == Http2FrameType.Continuation) { // todo: implementing this type is mandatory for multi-part headers breakpoint(); } - else if (type == 4 /* settings */) + else if (type == Http2FrameType.Settings) { if (length % 6 != 0) { @@ -339,7 +370,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio } } - if (type == 3 /* rst_stream */) + if (type == Http2FrameType.RstStream) { int errorCode = (buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3]; if (streamId == 0) @@ -363,13 +394,14 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio if (!isClient && endStream) { 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); } if (sendPacket) { // do not cancel the write operation - await output.WriteAsync(headerBuffer, 0, headerBuffer.Length /*, cancellationToken*/); + var buf = frameHeader.CopyToBuffer(); + await output.WriteAsync(buf, 0, buf.Length/*, cancellationToken*/); await output.WriteAsync(buffer, 0, length /*, cancellationToken*/); } @@ -393,7 +425,7 @@ private static void breakpoint() ; } - private static async Task sendHeader(Http2Settings settings, byte[] headerBuffer, RequestResponseBase rr, bool endStream, Stream output) + 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(); @@ -430,36 +462,35 @@ private static async Task sendHeader(Http2Settings settings, byte[] headerBuffer 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 + frameHeader.Length = newLength; + frameHeader.Type = pushPromise ? Http2FrameType.PushPromise : Http2FrameType.Headers; - int flags = (int)Http2FrameFlag.EndHeaders; + var flags = Http2FrameFlag.EndHeaders; if (endStream) { - flags |= (int)Http2FrameFlag.EndStream; + flags |= Http2FrameFlag.EndStream; } if (rr.Priority.HasValue) { - flags |= (int)Http2FrameFlag.Priority; + flags |= Http2FrameFlag.Priority; } - headerBuffer[4] = (byte)flags; + frameHeader.Flags = flags; // clear the padding flag //headerBuffer[4] = (byte)(flags & ~((int)Http2FrameFlag.Padded)); // send the header - await output.WriteAsync(headerBuffer, 0, headerBuffer.Length /*, cancellationToken*/); + 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, byte[] headerBuffer, byte[] buffer, Stream output) + private static async Task sendBody(Http2Settings settings, RequestResponseBase rr, Http2FrameHeader frameHeader, byte[] buffer, Stream output) { var body = rr.CompressBodyAndUpdateContentLength(); - await sendHeader(settings, headerBuffer, rr, !(rr.HasBody && rr.IsBodyRead), output); + await sendHeader(settings, frameHeader, rr, !(rr.HasBody && rr.IsBodyRead), output, false); if (rr.HasBody && rr.IsBodyRead) { @@ -470,12 +501,12 @@ private static async Task sendBody(Http2Settings settings, RequestResponseBase r 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*/); + 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*/); } } From 0c88f3f18b332c006029afc03b7e487c04a490f3 Mon Sep 17 00:00:00 2001 From: Honfika Date: Sun, 28 Apr 2019 23:21:12 +0200 Subject: [PATCH 4/4] HTTP/2 fix, pushpromise commented out (not wokring yet) --- .../MainWindow.xaml.cs | 17 ++- .../Http2/Http2FrameHeader.cs | 7 +- src/Titanium.Web.Proxy/Http2/Http2Helper.cs | 121 +++++++++--------- 3 files changed, 78 insertions(+), 67 deletions(-) diff --git a/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs b/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs index e6dec971a..9e3ba6f57 100644 --- a/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs +++ b/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs @@ -152,6 +152,11 @@ await Dispatcher.InvokeAsync(() => private async Task ProxyServer_BeforeRequest(object sender, SessionEventArgs e) { + if (e.HttpClient.ConnectRequest?.TunnelType != TunnelType.Http2) + { + return; + } + SessionListItem item = null; await Dispatcher.InvokeAsync(() => { item = addSession(e); }); @@ -171,6 +176,11 @@ private async Task ProxyServer_BeforeRequest(object sender, SessionEventArgs e) private async Task ProxyServer_BeforeResponse(object sender, SessionEventArgs e) { + if (e.HttpClient.ConnectRequest?.TunnelType != TunnelType.Http2) + { + return; + } + SessionListItem item = null; await Dispatcher.InvokeAsync(() => { @@ -180,14 +190,9 @@ await Dispatcher.InvokeAsync(() => } }); - //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) - //{ - //} + //e.SetResponseBody(Encoding.ASCII.GetBytes("TITANIUMMMM!!!!")); if (item != null) { diff --git a/src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs b/src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs index d4e87bfc1..f211511d5 100644 --- a/src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs +++ b/src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs @@ -15,12 +15,17 @@ internal class Http2FrameHeader public byte[] CopyToBuffer() { int length = Length; - var buf = Buffer; + 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; } } diff --git a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs index 5638074c3..2fe85d194 100644 --- a/src/Titanium.Web.Proxy/Http2/Http2Helper.cs +++ b/src/Titanium.Web.Proxy/Http2/Http2Helper.cs @@ -62,7 +62,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio int headerTableSize = 0; Decoder decoder = null; - Http2FrameHeader frameHeader = new Http2FrameHeader(); + var frameHeader = new Http2FrameHeader(); frameHeader.Buffer = new byte[9]; byte[] buffer = null; while (true) @@ -102,20 +102,20 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio bool endStream = false; SessionEventArgs args = null; - RequestResponseBase rr; - if (type == Http2FrameType.Data || type == Http2FrameType.Headers || type == Http2FrameType.PushPromise) + RequestResponseBase rr = null; + if (type == Http2FrameType.Data || type == Http2FrameType.Headers/* || type == Http2FrameType.PushPromise*/) { if (!sessions.TryGetValue(streamId, out args)) { - if (type == Http2FrameType.Data) - { - throw new ProxyHttpException("HTTP Body data received before any header frame.", null, args); - } + //if (type == Http2FrameType.Data) + //{ + // throw new ProxyHttpException("HTTP Body data received before any header frame.", null, args); + //} - if (type == Http2FrameType.Headers && !isClient) - { - throw new ProxyHttpException("HTTP Response received before any Request header frame.", null, args); - } + //if (type == Http2FrameType.Headers && !isClient) + //{ + // throw new ProxyHttpException("HTTP Response received before any Request header frame.", null, args); + //} if (type == Http2FrameType.PushPromise && isClient) { @@ -125,7 +125,7 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio } //System.Diagnostics.Debug.WriteLine("CONN: " + connectionId + ", CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type); - if (type == Http2FrameType.Data) + if (type == Http2FrameType.Data && args != null) { rr = isClient ? (RequestResponseBase)args.HttpClient.Request : args.HttpClient.Response; @@ -155,54 +155,6 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio } data.Write(buffer, offset, length); - - if (endStream) - { - 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; - - 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); - } } } else if (type == Http2FrameType.Headers/* || type == Http2FrameType.PushPromise*/) @@ -391,6 +343,55 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio } } + 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) { sessions.TryRemove(streamId, out _);