Commit b37d7c8f authored by Honfika's avatar Honfika

Basic HTTP/2 support. Disabled by default. Warning added to the enable flag:

- only enabled when both client and server supports it (no protocol changing in proxy)
- GetRequest/ResponseBody(AsString) methods are not supported
- cannot modify the request/response (e.g header modifications in BeforeRequest/Response events are ignored)
parent a8a822a4
......@@ -150,6 +150,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
SessionListItem item = null;
await Dispatcher.InvokeAsync(() => { item = addSession(e); });
if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2)
{
// GetRequestBody for HTTP/2 currently not supported
return;
}
if (e.HttpClient.Request.HasBody)
{
e.HttpClient.Request.KeepBody = true;
......@@ -168,6 +174,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
}
});
if (e.HttpClient.ConnectRequest?.TunnelType == TunnelType.Http2)
{
// GetRequestBody for HTTP/2 currently not supported
return;
}
if (item != null)
{
if (e.HttpClient.Response.HasBody)
......@@ -217,6 +229,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
var session = (SessionEventArgsBase)sender;
if (sessionDictionary.TryGetValue(session.HttpClient, out var li))
{
var tunnelType = session.HttpClient.ConnectRequest?.TunnelType ?? TunnelType.Unknown;
if (tunnelType != TunnelType.Unknown)
{
li.Protocol = TunnelTypeToString(tunnelType);
}
li.ReceivedDataCount += args.Count;
}
};
......@@ -226,6 +244,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
var session = (SessionEventArgsBase)sender;
if (sessionDictionary.TryGetValue(session.HttpClient, out var li))
{
var tunnelType = session.HttpClient.ConnectRequest?.TunnelType ?? TunnelType.Unknown;
if (tunnelType != TunnelType.Unknown)
{
li.Protocol = TunnelTypeToString(tunnelType);
}
li.SentDataCount += args.Count;
}
};
......@@ -235,6 +259,21 @@ namespace Titanium.Web.Proxy.Examples.Wpf
return item;
}
private string TunnelTypeToString(TunnelType tunnelType)
{
switch (tunnelType)
{
case TunnelType.Https:
return "https";
case TunnelType.Websocket:
return "websocket";
case TunnelType.Http2:
return "http2";
}
return null;
}
private void ListViewSessions_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete)
......
......@@ -11,7 +11,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
private long? bodySize;
private Exception exception;
private string host;
private string process;
private int processId;
private string protocol;
private long receivedDataCount;
private long sentDataCount;
......@@ -54,10 +54,32 @@ namespace Titanium.Web.Proxy.Examples.Wpf
set => SetField(ref bodySize, value);
}
public int ProcessId
{
get => processId;
set
{
if (SetField(ref processId, value))
{
OnPropertyChanged(nameof(Process));
}
}
}
public string Process
{
get => process;
set => SetField(ref process, value);
get
{
try
{
var process = System.Diagnostics.Process.GetProcessById(processId);
return process.ProcessName + ":" + processId;
}
catch (Exception)
{
return string.Empty;
}
}
}
public long ReceivedDataCount
......@@ -80,13 +102,16 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public event PropertyChangedEventHandler PropertyChanged;
protected void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!Equals(field, value))
{
field = value;
OnPropertyChanged(propertyName);
return true;
}
return false;
}
[NotifyPropertyChangedInvocator]
......@@ -132,20 +157,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
BodySize = responseSize;
}
Process = GetProcessDescription(HttpClient.ProcessId.Value);
}
private string GetProcessDescription(int processId)
{
try
{
var process = System.Diagnostics.Process.GetProcessById(processId);
return process.ProcessName + ":" + processId;
}
catch (Exception)
{
return string.Empty;
}
ProcessId = HttpClient.ProcessId.Value;
}
}
}
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj" />
</ItemGroup>
</Project>
\ No newline at end of file
......@@ -126,6 +126,7 @@ namespace Titanium.Web.Proxy
bool isClientHello = clientHelloInfo != null;
if (isClientHello)
{
connectRequest.TunnelType = TunnelType.Https;
connectRequest.ClientHelloInfo = clientHelloInfo;
}
......@@ -208,9 +209,9 @@ namespace Titanium.Web.Proxy
}
catch (Exception e)
{
var certname = certificate?.GetNameInfo(X509NameType.SimpleName, false);
var certName = certificate?.GetNameInfo(X509NameType.SimpleName, false);
throw new ProxyConnectException(
$"Couldn't authenticate host '{connectHostname}' with certificate '{certname}'.", e, connectArgs);
$"Couldn't authenticate host '{connectHostname}' with certificate '{certName}'.", e, connectArgs);
}
if (await HttpHelper.IsConnectMethod(clientStream) == -1)
......@@ -233,6 +234,11 @@ namespace Titanium.Web.Proxy
// Hostname is excluded or it is not an HTTPS connect
if (!decryptSsl || !isClientHello)
{
if (!isClientHello)
{
connectRequest.TunnelType = TunnelType.Websocket;
}
// create new connection to server.
// If we detected that client tunnel CONNECTs without SSL by checking for empty client hello then
// this connection should not be HTTPS.
......@@ -286,6 +292,8 @@ namespace Titanium.Web.Proxy
string httpCmd = await clientStream.ReadLineAsync(cancellationToken);
if (httpCmd == "PRI * HTTP/2.0")
{
connectArgs.HttpClient.ConnectRequest.TunnelType = TunnelType.Http2;
// HTTP/2 Connection Preface
string line = await clientStream.ReadLineAsync(cancellationToken);
if (line != string.Empty)
......@@ -318,9 +326,16 @@ namespace Titanium.Web.Proxy
await Http2Helper.SendHttp2(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
() => new SessionEventArgs(this, endPoint, cancellationTokenSource)
{
ProxyClient = { Connection = clientConnection },
HttpClient = { ConnectRequest = connectArgs?.HttpClient.ConnectRequest },
UserData = connectArgs?.UserData
},
async args => { await invokeBeforeRequest(args); },
async args => { await invokeBeforeResponse(args); },
connectArgs.CancellationTokenSource, clientConnection.Id, ExceptionFunc);
#endif
}
finally
{
......
......@@ -146,8 +146,8 @@ namespace Titanium.Web.Proxy.Helpers
private static async Task<int> startsWith(ICustomStreamReader clientStreamReader, string expectedStart)
{
bool isExpected = true;
int legthToCheck = 10;
for (int i = 0; i < legthToCheck; i++)
int lengthToCheck = 10;
for (int i = 0; i < lengthToCheck; i++)
{
int b = await clientStreamReader.PeekByteAsync(i);
if (b == -1)
......
......@@ -12,6 +12,8 @@ namespace Titanium.Web.Proxy.Http
Method = "CONNECT";
}
public TunnelType TunnelType { get; internal set; }
public ClientHelloInfo ClientHelloInfo { get; set; }
}
}
......@@ -6,28 +6,28 @@
public static class KnownHeaders
{
// Both
public const string Connection = "connection";
public const string Connection = "Connection";
public const string ConnectionClose = "close";
public const string ConnectionKeepAlive = "keep-alive";
public const string ContentLength = "content-length";
public const string ContentLength = "Content-Length";
public const string ContentType = "content-type";
public const string ContentType = "Content-Type";
public const string ContentTypeCharset = "charset";
public const string ContentTypeBoundary = "boundary";
public const string Upgrade = "upgrade";
public const string Upgrade = "Upgrade";
public const string UpgradeWebsocket = "websocket";
// Request headers
public const string AcceptEncoding = "accept-encoding";
public const string AcceptEncoding = "Accept-Encoding";
public const string Authorization = "Authorization";
public const string Expect = "expect";
public const string Expect = "Expect";
public const string Expect100Continue = "100-continue";
public const string Host = "host";
public const string Host = "Host";
public const string ProxyAuthorization = "Proxy-Authorization";
public const string ProxyAuthorizationBasic = "basic";
......@@ -36,7 +36,7 @@
public const string ProxyConnectionClose = "close";
// Response headers
public const string ContentEncoding = "content-encoding";
public const string ContentEncoding = "Content-Encoding";
public const string ContentEncodingDeflate = "deflate";
public const string ContentEncodingGzip = "gzip";
public const string ContentEncodingBrotli = "br";
......@@ -45,7 +45,7 @@
public const string ProxyAuthenticate = "Proxy-Authenticate";
public const string TransferEncoding = "transfer-encoding";
public const string TransferEncoding = "Transfer-Encoding";
public const string TransferEncodingChunked = "chunked";
}
}
namespace Titanium.Web.Proxy.Http
{
public enum TunnelType
{
Unknown,
Https,
Websocket,
Http2,
}
}
......@@ -59,99 +59,99 @@ namespace Titanium.Web.Proxy.Http2.Hpack
/* 14 */
new HttpHeader(":status", "500"),
/* 15 */
new HttpHeader("accept-charset", string.Empty),
new HttpHeader("Accept-Charset", string.Empty),
/* 16 */
new HttpHeader("accept-encoding", "gzip, deflate"),
new HttpHeader("Accept-Encoding", "gzip, deflate"),
/* 17 */
new HttpHeader("accept-language", string.Empty),
new HttpHeader("Accept-Language", string.Empty),
/* 18 */
new HttpHeader("accept-ranges", string.Empty),
new HttpHeader("Accept-Ranges", string.Empty),
/* 19 */
new HttpHeader("accept", string.Empty),
new HttpHeader("Accept", string.Empty),
/* 20 */
new HttpHeader("access-control-allow-origin", string.Empty),
new HttpHeader("Access-Control-Allow-Origin", string.Empty),
/* 21 */
new HttpHeader("age", string.Empty),
new HttpHeader("Age", string.Empty),
/* 22 */
new HttpHeader("allow", string.Empty),
new HttpHeader("Allow", string.Empty),
/* 23 */
new HttpHeader("authorization", string.Empty),
new HttpHeader("Authorization", string.Empty),
/* 24 */
new HttpHeader("cache-control", string.Empty),
new HttpHeader("Cache-Control", string.Empty),
/* 25 */
new HttpHeader("content-disposition", string.Empty),
new HttpHeader("Content-Disposition", string.Empty),
/* 26 */
new HttpHeader("content-encoding", string.Empty),
new HttpHeader("Content-Encoding", string.Empty),
/* 27 */
new HttpHeader("content-language", string.Empty),
new HttpHeader("Content-Language", string.Empty),
/* 28 */
new HttpHeader("content-length", string.Empty),
new HttpHeader("Content-Length", string.Empty),
/* 29 */
new HttpHeader("content-location", string.Empty),
new HttpHeader("Content-Location", string.Empty),
/* 30 */
new HttpHeader("content-range", string.Empty),
new HttpHeader("Content-Range", string.Empty),
/* 31 */
new HttpHeader("content-type", string.Empty),
new HttpHeader("Content-Type", string.Empty),
/* 32 */
new HttpHeader("cookie", string.Empty),
new HttpHeader("Cookie", string.Empty),
/* 33 */
new HttpHeader("date", string.Empty),
new HttpHeader("Date", string.Empty),
/* 34 */
new HttpHeader("etag", string.Empty),
new HttpHeader("ETag", string.Empty),
/* 35 */
new HttpHeader("expect", string.Empty),
new HttpHeader("Expect", string.Empty),
/* 36 */
new HttpHeader("expires", string.Empty),
new HttpHeader("Expires", string.Empty),
/* 37 */
new HttpHeader("from", string.Empty),
new HttpHeader("From", string.Empty),
/* 38 */
new HttpHeader("host", string.Empty),
new HttpHeader("Host", string.Empty),
/* 39 */
new HttpHeader("if-match", string.Empty),
new HttpHeader("If-Match", string.Empty),
/* 40 */
new HttpHeader("if-modified-since", string.Empty),
new HttpHeader("If-Modified-Since", string.Empty),
/* 41 */
new HttpHeader("if-none-match", string.Empty),
new HttpHeader("If-None-Match", string.Empty),
/* 42 */
new HttpHeader("if-range", string.Empty),
new HttpHeader("If-Range", string.Empty),
/* 43 */
new HttpHeader("if-unmodified-since", string.Empty),
new HttpHeader("If-Unmodified-Since", string.Empty),
/* 44 */
new HttpHeader("last-modified", string.Empty),
new HttpHeader("Last-Modified", string.Empty),
/* 45 */
new HttpHeader("link", string.Empty),
new HttpHeader("Link", string.Empty),
/* 46 */
new HttpHeader("location", string.Empty),
new HttpHeader("Location", string.Empty),
/* 47 */
new HttpHeader("max-forwards", string.Empty),
new HttpHeader("Max-Forwards", string.Empty),
/* 48 */
new HttpHeader("proxy-authenticate", string.Empty),
new HttpHeader("Proxy-Authenticate", string.Empty),
/* 49 */
new HttpHeader("proxy-authorization", string.Empty),
new HttpHeader("Proxy-Authorization", string.Empty),
/* 50 */
new HttpHeader("range", string.Empty),
new HttpHeader("Range", string.Empty),
/* 51 */
new HttpHeader("referer", string.Empty),
new HttpHeader("Referer", string.Empty),
/* 52 */
new HttpHeader("refresh", string.Empty),
new HttpHeader("Refresh", string.Empty),
/* 53 */
new HttpHeader("retry-after", string.Empty),
new HttpHeader("Retry-After", string.Empty),
/* 54 */
new HttpHeader("server", string.Empty),
new HttpHeader("Server", string.Empty),
/* 55 */
new HttpHeader("set-cookie", string.Empty),
new HttpHeader("Set-Cookie", string.Empty),
/* 56 */
new HttpHeader("strict-transport-security", string.Empty),
new HttpHeader("Strict-Transport-Security", string.Empty),
/* 57 */
new HttpHeader("transfer-encoding", string.Empty),
new HttpHeader("Transfer-Encoding", string.Empty),
/* 58 */
new HttpHeader("user-agent", string.Empty),
new HttpHeader("User-Agent", string.Empty),
/* 59 */
new HttpHeader("vary", string.Empty),
new HttpHeader("Vary", string.Empty),
/* 60 */
new HttpHeader("via", string.Empty),
new HttpHeader("Via", string.Empty),
/* 61 */
new HttpHeader("www-authenticate", string.Empty)
new HttpHeader("WWW-Authenticate", string.Empty)
};
private static readonly Dictionary<string, int> staticIndexByName = CreateMap();
......@@ -244,4 +244,4 @@ namespace Titanium.Web.Proxy.Http2.Hpack
return ret;
}
}
}
\ No newline at end of file
}
#if NETCOREAPP2_1
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http2.Hpack;
namespace Titanium.Web.Proxy.Http2
......@@ -25,27 +31,24 @@ namespace Titanium.Web.Proxy.Http2
/// Useful for websocket requests
/// Task-based Asynchronous Pattern
/// </summary>
/// <param name="clientStream"></param>
/// <param name="serverStream"></param>
/// <param name="bufferSize"></param>
/// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param>
/// <param name="cancellationTokenSource"></param>
/// <param name="connectionId"></param>
/// <param name="exceptionFunc"></param>
/// <returns></returns>
internal static async Task SendHttp2(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive,
Func<SessionEventArgs> sessionFactory,
Func<SessionEventArgs, Task> onBeforeRequest, Func<SessionEventArgs, Task> onBeforeResponse,
CancellationTokenSource cancellationTokenSource, Guid connectionId,
ExceptionHandler exceptionFunc)
{
var decoder = new Decoder(8192, 4096 * 16);
var sessions = new ConcurrentDictionary<int, SessionEventArgs>();
// Now async relay all server=>client & client=>server data
var sendRelay =
CopyHttp2FrameAsync(clientStream, serverStream, onDataSend, bufferSize, connectionId,
true, cancellationTokenSource.Token);
copyHttp2FrameAsync(clientStream, serverStream, onDataSend, sessionFactory, decoder, sessions, onBeforeRequest,
bufferSize, connectionId, true, cancellationTokenSource.Token, exceptionFunc);
var receiveRelay =
CopyHttp2FrameAsync(serverStream, clientStream, onDataReceive, bufferSize, connectionId,
false, cancellationTokenSource.Token);
copyHttp2FrameAsync(serverStream, clientStream, onDataReceive, sessionFactory, decoder, sessions, onBeforeResponse,
bufferSize, connectionId, false, cancellationTokenSource.Token, exceptionFunc);
await Task.WhenAny(sendRelay, receiveRelay);
cancellationTokenSource.Cancel();
......@@ -53,16 +56,17 @@ namespace Titanium.Web.Proxy.Http2
await Task.WhenAll(sendRelay, receiveRelay);
}
private static async Task CopyHttp2FrameAsync(Stream input, Stream output, Action<byte[], int, int> onCopy,
int bufferSize, Guid connectionId, bool isClient, CancellationToken cancellationToken)
private static async Task copyHttp2FrameAsync(Stream input, Stream output, Action<byte[], int, int> onCopy,
Func<SessionEventArgs> sessionFactory, Decoder decoder, ConcurrentDictionary<int, SessionEventArgs> sessions,
Func<SessionEventArgs, Task> onBeforeRequestResponse,
int bufferSize, Guid connectionId, bool isClient, CancellationToken cancellationToken,
ExceptionHandler exceptionFunc)
{
var decoder = new Decoder(8192, 4096);
var headerBuffer = new byte[9];
var buffer = new byte[32768];
while (true)
{
int read = await ForceRead(input, headerBuffer, 0, 9, cancellationToken);
int read = await forceRead(input, headerBuffer, 0, 9, cancellationToken);
onCopy(headerBuffer, 0, read);
if (read != 9)
{
......@@ -75,55 +79,112 @@ namespace Titanium.Web.Proxy.Http2
int streamId = ((headerBuffer[5] & 0x7f) << 24) + (headerBuffer[6] << 16) + (headerBuffer[7] << 8) +
headerBuffer[8];
read = await ForceRead(input, buffer, 0, length, cancellationToken);
read = await forceRead(input, buffer, 0, length, cancellationToken);
onCopy(buffer, 0, read);
if (read != length)
{
return;
}
if (isClient)
bool endStream = false;
//System.Diagnostics.Debug.WriteLine("CLIENT: " + isClient + ", STREAM: " + streamId + ", TYPE: " + type);
if (type == 0 /* data */)
{
bool endStreamFlag = (flags & (int)Http2FrameFlag.EndStream) != 0;
if (endStreamFlag)
{
endStream = true;
}
}
else if (type == 1 /*headers*/)
{
if (type == 1 /*headers*/)
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;
if (endStreamFlag)
{
bool endHeaders = (flags & (int)Http2FrameFlag.EndHeaders) != 0;
bool padded = (flags & (int)Http2FrameFlag.Padded) != 0;
bool priority = (flags & (int)Http2FrameFlag.Priority) != 0;
endStream = true;
}
System.Diagnostics.Debug.WriteLine("HEADER: " + streamId + " end: " + endHeaders);
int offset = 0;
if (padded)
{
offset = 1;
}
if (priority)
{
offset += 5;
}
int offset = 0;
if (padded)
{
offset = 1;
}
int dataLength = length - offset;
if (padded)
{
dataLength -= buffer[0];
}
if (priority)
{
offset += 5;
}
if (!sessions.TryGetValue(streamId, out var args))
{
// todo: remove sessions when finished, otherwise it will be a "memory leak"
args = sessionFactory();
sessions.TryAdd(streamId, args);
}
int dataLength = length - offset;
if (padded)
var headerListener = new MyHeaderListener(
(name, value) =>
{
dataLength -= buffer[0];
}
var headerListener = new MyHeaderListener();
try
var headers = isClient ? args.HttpClient.Request.Headers : args.HttpClient.Response.Headers;
headers.AddHeader(name, value);
});
try
{
lock (decoder)
{
decoder.Decode(new BinaryReader(new MemoryStream(buffer, offset, dataLength)),
headerListener);
decoder.EndHeaderBlock();
}
catch (Exception)
if (isClient)
{
args.HttpClient.Request.HttpVersion = HttpVersion.Version20;
args.HttpClient.Request.Method = headerListener.Method;
args.HttpClient.Request.OriginalUrl = headerListener.Status;
args.HttpClient.Request.RequestUri = headerListener.GetUri();
}
else
{
args.HttpClient.Response.HttpVersion = HttpVersion.Version20;
int.TryParse(headerListener.Status, out int statusCode);
args.HttpClient.Response.StatusCode = statusCode;
}
}
catch (Exception ex)
{
exceptionFunc(new ProxyHttpException("Failed to decode HTTP/2 headers", ex, args));
}
if (endHeaders)
{
await onBeforeRequestResponse(args);
}
}
if (!isClient && endStream)
{
sessions.TryRemove(streamId, out _);
}
await output.WriteAsync(headerBuffer, 0, headerBuffer.Length, cancellationToken);
await output.WriteAsync(buffer, 0, length, cancellationToken);
// do not cancel the write operation
await output.WriteAsync(headerBuffer, 0, headerBuffer.Length/*, cancellationToken*/);
await output.WriteAsync(buffer, 0, length/*, cancellationToken*/);
if (cancellationToken.IsCancellationRequested)
{
return;
}
/*using (var fs = new System.IO.FileStream($@"c:\11\{connectionId}.{streamId}.dat", FileMode.Append))
{
......@@ -133,7 +194,7 @@ namespace Titanium.Web.Proxy.Http2
}
}
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)
{
int totalRead = 0;
......@@ -155,9 +216,59 @@ namespace Titanium.Web.Proxy.Http2
class MyHeaderListener : IHeaderListener
{
private readonly Action<string, string> addHeaderFunc;
public string Method { get; private set; }
public string Status { get; private set; }
private string authority;
private string scheme;
public string Path { get; private set; }
public MyHeaderListener(Action<string, string> addHeaderFunc)
{
this.addHeaderFunc = addHeaderFunc;
}
public void AddHeader(string name, string value, bool sensitive)
{
Console.WriteLine(name + ": " + value + " " + sensitive);
if (name[0] == ':')
{
switch (name)
{
case ":method":
Method = value;
return;
case ":authority":
authority = value;
return;
case ":scheme":
scheme = value;
return;
case ":path":
Path = value;
return;
case ":status":
Status = value;
return;
}
}
addHeaderFunc(name, value);
}
public Uri GetUri()
{
if (authority == null)
{
// todo
authority = "abc.abc";
}
return new Uri(scheme + "://" + authority + Path);
}
}
}
......
......@@ -146,10 +146,13 @@ namespace Titanium.Web.Proxy
public bool EnableWinAuth { get; set; }
/// <summary>
/// Enable disable HTTP/2 support. This setting is internal,
/// because the implementation is not finished
/// Enable disable HTTP/2 support.
/// Warning: HTTP/2 support is very limited
/// - only enabled when both client and server supports it (no protocol changing in proxy)
/// - GetRequest/ResponseBody(AsString) methods are not supported
/// - cannot modify the request/response (e.g header modifications in BeforeRequest/Response events are ignored)
/// </summary>
internal bool EnableHttp2 { get; set; } = false;
public bool EnableHttp2 { get; set; } = false;
/// <summary>
/// Should we check for certificate revocation during SSL authentication to servers
......
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