diff --git a/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs b/examples/Titanium.Web.Proxy.Examples.Wpf/MainWindow.xaml.cs
index 0e2cb80e9..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); });
@@ -159,6 +164,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;
@@ -168,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(() =>
{
@@ -177,9 +190,9 @@ await Dispatcher.InvokeAsync(() =>
}
});
- //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)
{
diff --git a/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs b/src/Titanium.Web.Proxy/EventArguments/SessionEventArgs.cs
index 52ee23464..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
///
@@ -91,6 +96,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 +108,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 +169,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 +181,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/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/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..f211511d5
--- /dev/null
+++ b/src/Titanium.Web.Proxy/Http2/Http2FrameHeader.cs
@@ -0,0 +1,32 @@
+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;
+ }
+ }
+}
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 df31c6e08..2fe85d194 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;
@@ -13,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
{
///
@@ -70,22 +62,29 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio
int headerTableSize = 0;
Decoder decoder = null;
- var headerBuffer = new byte[9];
+ var 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)
{
@@ -99,70 +98,71 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio
return;
}
+ bool sendPacket = true;
bool endStream = false;
SessionEventArgs args = 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 (type == 0)
- {
- 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 (!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);
- 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)
{
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);
-
- if (endStream)
+ int offset = 0;
+ if (padded)
{
- rr.Body = data.ToArray();
- rr.IsBodyRead = true;
-
- var tcs = rr.ReadHttp2BodyTaskCompletionSource;
- rr.ReadHttp2BodyTaskCompletionSource = null;
-
- if (!tcs.Task.IsCompleted)
- {
- tcs.SetResult(true);
- }
-
- rr.Http2BodyData = null;
+ offset++;
+ length--;
+ length -= buffer[0];
}
- }
+
+ 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 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;
@@ -172,12 +172,47 @@ private static async Task copyHttp2FrameAsync(Stream input, Stream output, Actio
if (padded)
{
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;
if (padded)
@@ -205,17 +240,17 @@ 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.Status;
+ request.OriginalUrl = headerListener.Path;
+
request.RequestUri = headerListener.GetUri();
}
else
{
- var response = args.HttpClient.Response;
+ var response = (Response)rr;
response.HttpVersion = HttpVersion.Version20;
int.TryParse(headerListener.Status, out int statusCode);
response.StatusCode = statusCode;
@@ -226,23 +261,41 @@ 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);
+ await sendHeader(remoteSettings, frameHeader, rr, endStream, output, args.IsPromise);
+ }
+ else
+ {
+ rr.Http2IgnoreBodyFrames = 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)
{
@@ -269,7 +322,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)
@@ -290,15 +343,68 @@ 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 _);
- //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
- await output.WriteAsync(headerBuffer, 0, headerBuffer.Length/*, cancellationToken*/);
- await output.WriteAsync(buffer, 0, length/*, cancellationToken*/);
+ if (sendPacket)
+ {
+ // 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)
{
@@ -313,6 +419,104 @@ 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, 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 forceRead(Stream input, byte[] buffer, int offset, int bytesToRead,
CancellationToken cancellationToken)
{
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.
///
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;
-
}
///