Unverified Commit b584af90 authored by honfika's avatar honfika Committed by GitHub

Merge pull request #679 from justcoding121/master

beta
parents 8fa9ebd3 fdfaf871
......@@ -42,6 +42,10 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer.ForwardToUpstreamGateway = true;
proxyServer.CertificateManager.SaveFakeCertificates = true;
// this is just to show the functionality, provided implementations use junk value
//proxyServer.GetCustomUpStreamProxyFunc = onGetCustomUpStreamProxyFunc;
//proxyServer.CustomUpStreamProxyFailureFunc = onCustomUpStreamProxyFailureFunc;
// optionally set the Certificate Engine
// Under Mono or Non-Windows runtimes only BouncyCastle will be supported
//proxyServer.CertificateManager.CertificateEngine = Network.CertificateEngine.BouncyCastle;
......@@ -116,6 +120,18 @@ namespace Titanium.Web.Proxy.Examples.Basic
//proxyServer.CertificateManager.RemoveTrustedRootCertificates();
}
private async Task<IExternalProxy> onGetCustomUpStreamProxyFunc(SessionEventArgsBase arg)
{
// this is just to show the functionality, provided values are junk
return new ExternalProxy() { BypassLocalhost = false, HostName = "127.0.0.9", Port = 9090, Password = "fake", UserName = "fake", UseDefaultCredentials = false };
}
private async Task<IExternalProxy> onCustomUpStreamProxyFailureFunc(SessionEventArgsBase arg)
{
// this is just to show the functionality, provided values are junk
return new ExternalProxy() { BypassLocalhost = false, HostName = "127.0.0.10", Port = 9191, Password = "fake2", UserName = "fake2", UseDefaultCredentials = false };
}
private async Task onBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
string hostname = e.HttpClient.Request.RequestUri.Host;
......
......@@ -32,6 +32,8 @@
<GridViewColumn Header="SentBytes" DisplayMemberBinding="{Binding SentDataCount}" />
<GridViewColumn Header="ReceivedBytes" DisplayMemberBinding="{Binding ReceivedDataCount}" />
<GridViewColumn Header="ClientEndPoint" DisplayMemberBinding="{Binding ClientEndPoint}" />
<GridViewColumn Header="ClientConnectionId" DisplayMemberBinding="{Binding ClientConnectionId}" />
<GridViewColumn Header="ServerConnectionId" DisplayMemberBinding="{Binding ServerConnectionId}" />
</GridView>
</ListView.View>
</ListView>
......
......@@ -146,13 +146,15 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{
if (sessionDictionary.TryGetValue(e.HttpClient, out var item))
{
item.Update();
item.Update(e);
}
});
}
private async Task ProxyServer_BeforeRequest(object sender, SessionEventArgs e)
{
//if (e.HttpClient.Request.HttpVersion.Major != 2) return;
SessionListItem item = null;
await Dispatcher.InvokeAsync(() => { item = addSession(e); });
......@@ -175,7 +177,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{
if (sessionDictionary.TryGetValue(e.HttpClient, out item))
{
item.Update();
item.Update(e);
}
});
......@@ -190,7 +192,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
e.HttpClient.Response.KeepBody = true;
await e.GetResponseBody();
await Dispatcher.InvokeAsync(() => { item.Update(); });
await Dispatcher.InvokeAsync(() => { item.Update(e); });
if (item == SelectedSession)
{
await Dispatcher.InvokeAsync(selectedSessionChanged);
......@@ -225,6 +227,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf
var item = new SessionListItem
{
Number = lastSessionNumber,
ClientConnectionId = e.ClientConnectionId,
ServerConnectionId = e.ServerConnectionId,
HttpClient = e.HttpClient,
ClientEndPoint = e.ClientEndPoint,
IsTunnelConnect = isTunnelConnect
......@@ -236,7 +240,8 @@ 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;
var connectRequest = session.HttpClient.ConnectRequest;
var tunnelType = connectRequest?.TunnelType ?? TunnelType.Unknown;
if (tunnelType != TunnelType.Unknown)
{
li.Protocol = TunnelTypeToString(tunnelType);
......@@ -244,6 +249,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
li.ReceivedDataCount += args.Count;
//if (tunnelType == TunnelType.Http2)
AppendTransferLog(session.GetHashCode() + (isTunnelConnect ? "_tunnel" : "") + "_received",
args.Buffer, args.Offset, args.Count);
}
......@@ -254,7 +260,8 @@ 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;
var connectRequest = session.HttpClient.ConnectRequest;
var tunnelType = connectRequest?.TunnelType ?? TunnelType.Unknown;
if (tunnelType != TunnelType.Unknown)
{
li.Protocol = TunnelTypeToString(tunnelType);
......@@ -262,7 +269,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf
li.SentDataCount += args.Count;
AppendTransferLog(session.GetHashCode() + (isTunnelConnect ? "_tunnel" : "") + "_sent",
//if (tunnelType == TunnelType.Http2)
AppendTransferLog( session.GetHashCode() + (isTunnelConnect ? "_tunnel" : "") + "_sent",
args.Buffer, args.Offset, args.Count);
}
};
......@@ -272,6 +280,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf
te.DecryptedDataReceived += (sender, args) =>
{
var session = (SessionEventArgsBase)sender;
//var tunnelType = session.HttpClient.ConnectRequest?.TunnelType ?? TunnelType.Unknown;
//if (tunnelType == TunnelType.Http2)
AppendTransferLog(session.GetHashCode() + "_decrypted_received", args.Buffer, args.Offset,
args.Count);
};
......@@ -279,11 +289,13 @@ namespace Titanium.Web.Proxy.Examples.Wpf
te.DecryptedDataSent += (sender, args) =>
{
var session = (SessionEventArgsBase)sender;
//var tunnelType = session.HttpClient.ConnectRequest?.TunnelType ?? TunnelType.Unknown;
//if (tunnelType == TunnelType.Http2)
AppendTransferLog(session.GetHashCode() + "_decrypted_sent", args.Buffer, args.Offset, args.Count);
};
}
item.Update();
item.Update(e);
return item;
}
......@@ -362,9 +374,15 @@ namespace Titanium.Web.Proxy.Examples.Wpf
//string hexStr = string.Join(" ", data.Select(x => x.ToString("X2")));
var sb = new StringBuilder();
sb.AppendLine("URI: " + request.RequestUri);
sb.Append(request.HeaderText);
sb.Append(request.Encoding.GetString(data));
sb.Append(truncated ? Environment.NewLine + $"Data is truncated after {truncateLimit} bytes" : null);
if (truncated)
{
sb.AppendLine();
sb.Append($"Data is truncated after {truncateLimit} bytes");
}
sb.Append((request as ConnectRequest)?.ClientHelloInfo);
TextBoxRequest.Text = sb.ToString();
......@@ -381,7 +399,12 @@ namespace Titanium.Web.Proxy.Examples.Wpf
sb = new StringBuilder();
sb.Append(response.HeaderText);
sb.Append(response.Encoding.GetString(data));
sb.Append(truncated ? Environment.NewLine + $"Data is truncated after {truncateLimit} bytes" : null);
if (truncated)
{
sb.AppendLine();
sb.Append($"Data is truncated after {truncateLimit} bytes");
}
sb.Append((response as ConnectResponse)?.ServerHelloInfo);
if (SelectedSession.Exception != null)
{
......
......@@ -2,6 +2,7 @@
using System.ComponentModel;
using System.Net;
using System.Runtime.CompilerServices;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Examples.Wpf.Annotations;
using Titanium.Web.Proxy.Http;
......@@ -18,9 +19,23 @@ namespace Titanium.Web.Proxy.Examples.Wpf
private long sentDataCount;
private string statusCode;
private string url;
private Guid clientConnectionId;
private Guid serverConnectionId;
public int Number { get; set; }
public Guid ClientConnectionId
{
get => clientConnectionId;
set => SetField(ref clientConnectionId, value);
}
public Guid ServerConnectionId
{
get => serverConnectionId;
set => SetField(ref serverConnectionId, value);
}
public HttpWebClient HttpClient { get; set; }
public IPEndPoint ClientEndPoint { get; set; }
......@@ -123,13 +138,15 @@ namespace Titanium.Web.Proxy.Examples.Wpf
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void Update()
public void Update(SessionEventArgsBase args)
{
var request = HttpClient.Request;
var response = HttpClient.Response;
int statusCode = response?.StatusCode ?? 0;
StatusCode = statusCode == 0 ? "-" : statusCode.ToString();
Protocol = request.RequestUri.Scheme;
ClientConnectionId = args.ClientConnectionId;
ServerConnectionId = args.ServerConnectionId;
if (IsTunnelConnect)
{
......
......@@ -65,11 +65,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public event EventHandler<MultipartRequestPartSentEventArgs>? MultipartRequestPartSent;
private HttpStream getStream(bool isRequest)
{
return isRequest ? (HttpStream)ClientStream : HttpClient.Connection.Stream;
}
/// <summary>
/// Read request body content as bytes[] for current session
/// </summary>
......@@ -194,15 +189,15 @@ namespace Titanium.Web.Proxy.EventArguments
private async Task<byte[]> readBodyAsync(bool isRequest, CancellationToken cancellationToken)
{
using var bodyStream = new MemoryStream();
using var http = new HttpStream(bodyStream, BufferPool);
using var writer = new HttpStream(bodyStream, BufferPool);
if (isRequest)
{
await CopyRequestBodyAsync(http, TransformationMode.Uncompress, cancellationToken);
await CopyRequestBodyAsync(writer, TransformationMode.Uncompress, cancellationToken);
}
else
{
await CopyResponseBodyAsync(http, TransformationMode.Uncompress, cancellationToken);
await copyResponseBodyAsync(writer, TransformationMode.Uncompress, cancellationToken);
}
return bodyStream.ToArray();
......@@ -223,9 +218,9 @@ namespace Titanium.Web.Proxy.EventArguments
return;
}
using var bodyStream = new MemoryStream();
using var http = new HttpStream(bodyStream, BufferPool);
await copyBodyAsync(isRequest, true, http, TransformationMode.None, null, cancellationToken);
var reader = isRequest ? (HttpStream)ClientStream : HttpClient.Connection.Stream;
await reader.CopyBodyAsync(requestResponse, true, new NullWriter(), TransformationMode.None, null, cancellationToken);
}
/// <summary>
......@@ -235,13 +230,13 @@ namespace Titanium.Web.Proxy.EventArguments
internal async Task CopyRequestBodyAsync(IHttpStreamWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
{
var request = HttpClient.Request;
var reader = ClientStream;
long contentLength = request.ContentLength;
// send the request body bytes to server
if (contentLength > 0 && hasMulipartEventSubscribers && request.IsMultipartFormData)
{
var reader = getStream(true);
var boundary = HttpHelper.GetBoundaryFromContentType(request.ContentType);
using (var copyStream = new CopyStream(reader, writer, BufferPool))
......@@ -267,54 +262,13 @@ namespace Titanium.Web.Proxy.EventArguments
}
else
{
await copyBodyAsync(true, false, writer, transformation, OnDataSent, cancellationToken);
await reader.CopyBodyAsync(request, false, writer, transformation, OnDataSent, cancellationToken);
}
}
internal async Task CopyResponseBodyAsync(IHttpStreamWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
{
await copyBodyAsync(false, false, writer, transformation, OnDataReceived, cancellationToken);
}
private async Task copyBodyAsync(bool isRequest, bool useOriginalHeaderValues, IHttpStreamWriter writer, TransformationMode transformation, Action<byte[], int, int>? onCopy, CancellationToken cancellationToken)
private async Task copyResponseBodyAsync(IHttpStreamWriter writer, TransformationMode transformation, CancellationToken cancellationToken)
{
var stream = getStream(isRequest);
var requestResponse = isRequest ? (RequestResponseBase)HttpClient.Request : HttpClient.Response;
bool isChunked = useOriginalHeaderValues? requestResponse.OriginalIsChunked : requestResponse.IsChunked;
long contentLength = useOriginalHeaderValues ? requestResponse.OriginalContentLength : requestResponse.ContentLength;
if (transformation == TransformationMode.None)
{
await writer.CopyBodyAsync(stream, isChunked, contentLength, onCopy, cancellationToken);
return;
}
LimitedStream limitedStream;
Stream? decompressStream = null;
string? contentEncoding = useOriginalHeaderValues ? requestResponse.OriginalContentEncoding : requestResponse.ContentEncoding;
Stream s = limitedStream = new LimitedStream(stream, BufferPool, isChunked, contentLength);
if (transformation == TransformationMode.Uncompress && contentEncoding != null)
{
s = decompressStream = DecompressionFactory.Create(CompressionUtil.CompressionNameToEnum(contentEncoding), s);
}
try
{
var http = new HttpStream(s, BufferPool, true);
await writer.CopyBodyAsync(http, false, -1, onCopy, cancellationToken);
}
finally
{
decompressStream?.Dispose();
await limitedStream.Finish();
limitedStream.Dispose();
}
await HttpClient.Connection.Stream.CopyBodyAsync(HttpClient.Response, false, writer, transformation, OnDataReceived, cancellationToken);
}
/// <summary>
......@@ -638,7 +592,6 @@ namespace Titanium.Web.Proxy.EventArguments
HttpClient.Response = response;
HttpClient.Response.Locked = true;
}
}
/// <summary>
......
......@@ -33,6 +33,10 @@ namespace Titanium.Web.Proxy.EventArguments
internal HttpClientStream ClientStream { get; }
public Guid ClientConnectionId => ClientConnection.Id;
public Guid ServerConnectionId => HttpClient.HasConnection ? ServerConnection.Id : Guid.Empty;
protected readonly IBufferPool BufferPool;
protected readonly ExceptionHandler ExceptionFunc;
private bool enableWinAuth;
......
......@@ -58,11 +58,11 @@ namespace Titanium.Web.Proxy
return;
}
Request.ParseRequestLine(httpCmd!, out string _, out string httpUrl, out var version);
Request.ParseRequestLine(httpCmd!, out string _, out var httpUrl, out var version);
var connectRequest = new ConnectRequest(httpUrl)
var connectRequest = new ConnectRequest(httpUrl.GetString())
{
OriginalUrlData = HttpHeader.Encoding.GetBytes(httpUrl),
RequestUriString8 = httpUrl,
HttpVersion = version
};
......@@ -91,8 +91,7 @@ namespace Titanium.Web.Proxy
}
// send the response
await clientStream.WriteResponseAsync(connectArgs.HttpClient.Response,
cancellationToken: cancellationToken);
await clientStream.WriteResponseAsync(connectArgs.HttpClient.Response, cancellationToken);
return;
}
......@@ -101,8 +100,7 @@ namespace Titanium.Web.Proxy
await endPoint.InvokeBeforeTunnelConnectResponse(this, connectArgs, ExceptionFunc);
// send the response
await clientStream.WriteResponseAsync(connectArgs.HttpClient.Response,
cancellationToken: cancellationToken);
await clientStream.WriteResponseAsync(connectArgs.HttpClient.Response, cancellationToken);
return;
}
......@@ -114,7 +112,7 @@ namespace Titanium.Web.Proxy
response.Headers.FixProxyHeaders();
connectArgs.HttpClient.Response = response;
await clientStream.WriteResponseAsync(response, cancellationToken: cancellationToken);
await clientStream.WriteResponseAsync(response, cancellationToken);
var clientHelloInfo = await SslTools.PeekClientHello(clientStream, BufferPool, cancellationToken);
......@@ -177,7 +175,7 @@ namespace Titanium.Web.Proxy
}
}
string connectHostname = httpUrl;
string connectHostname = httpUrl.GetString();
int idx = connectHostname.IndexOf(":");
if (idx >= 0)
{
......
......@@ -20,10 +20,13 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="response">The response object.</param>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns>The Task.</returns>
internal async Task WriteResponseAsync(Response response, CancellationToken cancellationToken = default)
internal async ValueTask WriteResponseAsync(Response response, CancellationToken cancellationToken = default)
{
var headerBuilder = new HeaderBuilder();
// Write back response status to client
headerBuilder.WriteResponseLine(response.HttpVersion, response.StatusCode, response.StatusDescription);
await WriteAsync(response, headerBuilder, cancellationToken);
}
}
......
......@@ -170,7 +170,7 @@ namespace Titanium.Web.Proxy.Helpers
/// Determines whether is connect method.
/// </summary>
/// <returns>1: when CONNECT, 0: when valid HTTP method, -1: otherwise</returns>
internal static Task<int> IsConnectMethod(IPeekStream httpReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
internal static ValueTask<int> IsConnectMethod(IPeekStream httpReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
return startsWith(httpReader, bufferPool, "CONNECT", cancellationToken);
}
......@@ -179,7 +179,7 @@ namespace Titanium.Web.Proxy.Helpers
/// Determines whether is pri method (HTTP/2).
/// </summary>
/// <returns>1: when PRI, 0: when valid HTTP method, -1: otherwise</returns>
internal static Task<int> IsPriMethod(IPeekStream httpReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
internal static ValueTask<int> IsPriMethod(IPeekStream httpReader, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{
return startsWith(httpReader, bufferPool, "PRI", cancellationToken);
}
......@@ -190,7 +190,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns>
/// 1: when starts with the given string, 0: when valid HTTP method, -1: otherwise
/// </returns>
private static async Task<int> startsWith(IPeekStream httpReader, IBufferPool bufferPool, string expectedStart, CancellationToken cancellationToken = default)
private static async ValueTask<int> startsWith(IPeekStream httpReader, IBufferPool bufferPool, string expectedStart, CancellationToken cancellationToken = default)
{
const int lengthToCheck = 10;
if (bufferPool.BufferSize < lengthToCheck)
......
using System.IO;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.StreamExtended.BufferPool;
......@@ -8,7 +10,7 @@ namespace Titanium.Web.Proxy.Helpers
{
internal sealed class HttpServerStream : HttpStream
{
internal HttpServerStream(Stream stream, IBufferPool bufferPool)
internal HttpServerStream(Stream stream, IBufferPool bufferPool)
: base(stream, bufferPool)
{
}
......@@ -19,11 +21,34 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="request">The request object.</param>
/// <param name="cancellationToken">Optional cancellation token for this async task.</param>
/// <returns></returns>
internal async Task WriteRequestAsync(Request request, CancellationToken cancellationToken = default)
internal async ValueTask WriteRequestAsync(Request request, CancellationToken cancellationToken = default)
{
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteRequestLine(request.Method, request.Url, request.HttpVersion);
headerBuilder.WriteRequestLine(request.Method, request.RequestUriString, request.HttpVersion);
await WriteAsync(request, headerBuilder, cancellationToken);
}
internal async ValueTask<ResponseStatusInfo> ReadResponseStatus(CancellationToken cancellationToken = default)
{
try
{
string httpStatus = await ReadLineAsync(cancellationToken) ??
throw new ServerConnectionException("Server connection was closed.");
if (httpStatus == string.Empty)
{
// is this really possible?
httpStatus = await ReadLineAsync(cancellationToken) ??
throw new ServerConnectionException("Server connection was closed.");
}
Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string description);
return new ResponseStatusInfo { Version = version, StatusCode = statusCode, Description = description };
}
catch (Exception e) when (!(e is ServerConnectionException))
{
throw new ServerConnectionException("Server connection was closed.");
}
}
}
}
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.StreamExtended.Network;
internal class NullWriter : IHttpStreamWriter
{
public static NullWriter Instance { get; } = new NullWriter();
public void Write(byte[] buffer, int offset, int count)
{
}
#if NET45
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
}
#else
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
#endif
public ValueTask WriteLineAsync(CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}
public ValueTask WriteLineAsync(string value, CancellationToken cancellationToken = default)
{
throw new System.NotImplementedException();
}
}
using System;
namespace Titanium.Web.Proxy.Helpers
{
struct ResponseStatusInfo
{
public Version Version { get; set; }
public int StatusCode { get; set; }
public string Description { get; set; }
}
}
......@@ -41,11 +41,21 @@ namespace Titanium.Web.Proxy.Http
WriteLine();
}
public void WriteHeaders(HeaderCollection headers)
public void WriteHeaders(HeaderCollection headers, bool sendProxyAuthorization = true,
string? upstreamProxyUserName = null, string? upstreamProxyPassword = null)
{
if (upstreamProxyUserName != null && upstreamProxyPassword != null)
{
WriteHeader(HttpHeader.ProxyConnectionKeepAlive);
WriteHeader(HttpHeader.GetProxyAuthorizationHeader(upstreamProxyUserName, upstreamProxyPassword));
}
foreach (var header in headers)
{
WriteHeader(header);
if (sendProxyAuthorization || !KnownHeaders.ProxyAuthorization.Equals(header.Name))
{
WriteHeader(header);
}
}
WriteLine();
......
......@@ -7,7 +7,7 @@ namespace Titanium.Web.Proxy.Http
{
internal static class HeaderParser
{
internal static async Task ReadHeaders(ILineStream reader, HeaderCollection headerCollection,
internal static async ValueTask ReadHeaders(ILineStream reader, HeaderCollection headerCollection,
CancellationToken cancellationToken)
{
string? tmpLine;
......
......@@ -103,9 +103,8 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Prepare and send the http(s) request
/// </summary>
/// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTransparent,
CancellationToken cancellationToken)
/// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour, bool isTransparent, CancellationToken cancellationToken)
{
var upstreamProxy = Connection.UpStreamProxy;
......@@ -114,23 +113,17 @@ namespace Titanium.Web.Proxy.Http
var serverStream = Connection.Stream;
string url;
if (useUpstreamProxy || isTransparent)
if (!useUpstreamProxy || isTransparent)
{
url = Request.Url;
url = Request.RequestUriString;
}
else
{
url = Request.RequestUri.GetOriginalPathAndQuery();
if (url == string.Empty)
{
url = "/";
}
url = Request.RequestUri.ToString();
}
var headerBuilder = new HeaderBuilder();
// prepare the request & headers
headerBuilder.WriteRequestLine(Request.Method, url, Request.HttpVersion);
string? upstreamProxyUserName = null;
string? upstreamProxyPassword = null;
// Send Authentication to Upstream proxy if needed
if (!isTransparent && upstreamProxy != null
......@@ -138,21 +131,16 @@ namespace Titanium.Web.Proxy.Http
&& !string.IsNullOrEmpty(upstreamProxy.UserName)
&& upstreamProxy.Password != null)
{
headerBuilder.WriteHeader(HttpHeader.ProxyConnectionKeepAlive);
headerBuilder.WriteHeader(HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password));
}
// write request headers
foreach (var header in Request.Headers)
{
if (isTransparent || header.Name != KnownHeaders.ProxyAuthorization.String)
{
headerBuilder.WriteHeader(header);
}
upstreamProxyUserName = upstreamProxy.UserName;
upstreamProxyPassword = upstreamProxy.Password;
}
headerBuilder.WriteLine();
// prepare the request & headers
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteRequestLine(Request.Method, url, Request.HttpVersion);
headerBuilder.WriteHeaders(Request.Headers, !isTransparent, upstreamProxyUserName, upstreamProxyPassword);
// write request headers
await serverStream.WriteHeadersAsync(headerBuilder, cancellationToken);
if (enable100ContinueBehaviour && Request.ExpectContinue)
......@@ -183,28 +171,10 @@ namespace Titanium.Web.Proxy.Http
return;
}
string httpStatus;
try
{
httpStatus = await Connection.Stream.ReadLineAsync(cancellationToken) ??
throw new ServerConnectionException("Server connection was closed.");
}
catch (Exception e) when (!(e is ServerConnectionException))
{
throw new ServerConnectionException("Server connection was closed.");
}
if (httpStatus == string.Empty)
{
httpStatus = await Connection.Stream.ReadLineAsync(cancellationToken) ??
throw new ServerConnectionException("Server connection was closed.");
}
Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string statusDescription);
Response.HttpVersion = version;
Response.StatusCode = statusCode;
Response.StatusDescription = statusDescription;
var httpStatus = await Connection.Stream.ReadResponseStatus(cancellationToken);
Response.HttpVersion = httpStatus.Version;
Response.StatusCode = httpStatus.StatusCode;
Response.StatusDescription = httpStatus.Description;
// Read the response headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(Connection.Stream, Response.Headers, cancellationToken);
......
......@@ -24,26 +24,15 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public bool IsHttps { get; internal set; }
private ByteString originalUrlData;
private ByteString urlData;
private ByteString requestUriString8;
internal ByteString OriginalUrlData
internal ByteString RequestUriString8
{
get => originalUrlData;
get => requestUriString8;
set
{
originalUrlData = value;
UrlData = value;
}
}
private protected ByteString UrlData
{
get => urlData;
set
{
urlData = value;
var scheme = getUriScheme(UrlData);
requestUriString8 = value;
var scheme = getUriScheme(value);
if (scheme.Length > 0)
{
IsHttps = scheme.Equals(ProxyServer.UriSchemeHttps8);
......@@ -54,19 +43,37 @@ namespace Titanium.Web.Proxy.Http
internal string? Authority { get; set; }
/// <summary>
/// The original request Url.
/// Request HTTP Uri.
/// </summary>
public string OriginalUrl => originalUrlData.GetString();
public Uri RequestUri
{
get
{
string url = Url;
try
{
return new Uri(url);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{url}'", ex);
}
}
set
{
Url = value.ToString();
}
}
/// <summary>
/// Request HTTP Uri.
/// The request url as it is in the HTTP header
/// </summary>
public Uri RequestUri
public string Url
{
get
{
string url = UrlData.GetString();
if (getUriScheme(UrlData).Length == 0)
string url = RequestUriString8.GetString();
if (getUriScheme(RequestUriString8).Length == 0)
{
string? hostAndPath = Host ?? Authority;
......@@ -82,28 +89,26 @@ namespace Titanium.Web.Proxy.Http
url = string.Concat(IsHttps ? "https://" : "http://", hostAndPath);
}
try
{
return new Uri(url);
}
catch (Exception ex)
{
throw new Exception($"Invalid URI: '{url}'", ex);
}
return url;
}
set
{
RequestUriString = value;
}
}
/// <summary>
/// The request url as it is in the HTTP header
/// The request uri as it is in the HTTP header
/// </summary>
public string Url
public string RequestUriString
{
get => UrlData.GetString();
get => RequestUriString8.GetString();
set
{
UrlData = value.GetByteString();
RequestUriString8 = (ByteString)value;
if (Host != null)
var scheme = getUriScheme(RequestUriString8);
if (scheme.Length > 0 && Host != null)
{
var uri = new Uri(value);
Host = uri.Authority;
......@@ -111,13 +116,6 @@ namespace Titanium.Web.Proxy.Http
}
}
[Obsolete("This property is obsolete. Use Url property instead")]
public string RequestUriString
{
get => Url;
set => Url = value;
}
/// <summary>
/// Has request body?
/// </summary>
......@@ -219,7 +217,7 @@ namespace Titanium.Web.Proxy.Http
get
{
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteRequestLine(Method, Url, HttpVersion);
headerBuilder.WriteRequestLine(Method, RequestUriString, HttpVersion);
headerBuilder.WriteHeaders(Headers);
return headerBuilder.GetString(HttpHeader.Encoding);
}
......@@ -256,7 +254,7 @@ namespace Titanium.Web.Proxy.Http
}
}
internal static void ParseRequestLine(string httpCmd, out string httpMethod, out string httpUrl,
internal static void ParseRequestLine(string httpCmd, out string httpMethod, out ByteString httpUrl,
out Version version)
{
int firstSpace = httpCmd.IndexOf(' ');
......@@ -281,11 +279,11 @@ namespace Titanium.Web.Proxy.Http
if (firstSpace == lastSpace)
{
httpUrl = httpCmd.AsSpan(firstSpace + 1).ToString();
httpUrl = (ByteString)httpCmd.AsSpan(firstSpace + 1).ToString();
}
else
{
httpUrl = httpCmd.AsSpan(firstSpace + 1, lastSpace - firstSpace - 1).ToString();
httpUrl = (ByteString)httpCmd.AsSpan(firstSpace + 1, lastSpace - firstSpace - 1).ToString();
// parse the HTTP version
var httpVersion = httpCmd.AsSpan(lastSpace + 1);
......
......@@ -210,6 +210,8 @@ namespace Titanium.Web.Proxy.Http
internal bool Locked { get; set; }
internal bool BodyAvailable => BodyInternal != null;
internal bool IsBodySent { get; set; }
internal abstract void EnsureBodyAvailable(bool throwWhenNotReadYet = true);
......
......@@ -265,7 +265,7 @@ namespace Titanium.Web.Proxy.Http2
request.Method = method.GetString();
request.IsHttps = headerListener.Scheme == ProxyServer.UriSchemeHttps;
request.Authority = headerListener.Authority.GetString();
request.OriginalUrlData = path;
request.RequestUriString8 = path;
//request.RequestUri = headerListener.GetUri();
}
......
......@@ -38,8 +38,8 @@ namespace Titanium.Web.Proxy.Network
try
{
// setup connection
currentConnection = currentConnection as TcpServerConnection ??
await generator();
currentConnection ??= await generator();
// try
@continue = await action(currentConnection);
......
......@@ -281,21 +281,25 @@ namespace Titanium.Web.Proxy.Network.Tcp
}
}
bool useUpstreamProxy = false;
bool useUpstreamProxy1 = false;
// check if external proxy is set for HTTP/HTTPS
if (externalProxy != null &&
!(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort))
if (externalProxy != null && !(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort))
{
useUpstreamProxy = true;
useUpstreamProxy1 = true;
// check if we need to ByPass
if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName))
{
useUpstreamProxy = false;
useUpstreamProxy1 = false;
}
}
if (!useUpstreamProxy1)
{
externalProxy = null;
}
TcpClient? tcpClient = null;
HttpServerStream? stream = null;
......@@ -307,8 +311,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
retry:
try
{
string hostname = useUpstreamProxy ? externalProxy!.HostName : remoteHostName;
int port = useUpstreamProxy ? externalProxy!.Port : remotePort;
string hostname = externalProxy != null ? externalProxy.HostName : remoteHostName;
int port = externalProxy?.Port ?? remotePort;
var ipAddresses = await Dns.GetHostAddressesAsync(hostname);
if (ipAddresses == null || ipAddresses.Length == 0)
......@@ -358,7 +362,6 @@ retry:
try
{
connectTask.Dispose();
}
catch
{
......@@ -397,6 +400,17 @@ retry:
if (tcpClient == null)
{
if (session != null && proxyServer.CustomUpStreamProxyFailureFunc != null)
{
var newUpstreamProxy = await proxyServer.CustomUpStreamProxyFailureFunc(session);
if (newUpstreamProxy != null)
{
session.CustomUpStreamProxyUsed = newUpstreamProxy;
session.TimeLine["Retrying Upstream Proxy Connection"] = DateTime.Now;
return await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps, sslProtocol, applicationProtocols, isConnect, proxyServer, session, upStreamEndPoint, externalProxy, cacheKey, cancellationToken);
}
}
throw new Exception($"Could not establish connection to {hostname}", lastException);
}
......@@ -409,34 +423,30 @@ retry:
stream = new HttpServerStream(tcpClient.GetStream(), proxyServer.BufferPool);
if (useUpstreamProxy && (isConnect || isHttps))
if (externalProxy != null && (isConnect || isHttps))
{
string authority = $"{remoteHostName}:{remotePort}";
var connectRequest = new ConnectRequest(authority)
{
IsHttps = isHttps,
OriginalUrlData = HttpHeader.Encoding.GetBytes(authority),
RequestUriString8 = HttpHeader.Encoding.GetBytes(authority),
HttpVersion = httpVersion
};
connectRequest.Headers.AddHeader(KnownHeaders.Connection, KnownHeaders.ConnectionKeepAlive);
if (!string.IsNullOrEmpty(externalProxy!.UserName) && externalProxy.Password != null)
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
connectRequest.Headers.AddHeader(HttpHeader.ProxyConnectionKeepAlive);
connectRequest.Headers.AddHeader(
HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password));
connectRequest.Headers.AddHeader(HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password));
}
await stream.WriteRequestAsync(connectRequest, cancellationToken: cancellationToken);
string httpStatus = await stream.ReadLineAsync(cancellationToken)
?? throw new ServerConnectionException("Server connection was closed.");
var httpStatus = await stream.ReadResponseStatus(cancellationToken);
Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription);
if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK")
&& !statusDescription.EqualsIgnoreCase("Connection Established"))
if (httpStatus.StatusCode != 200 && !httpStatus.Description.EqualsIgnoreCase("OK")
&& !httpStatus.Description.EqualsIgnoreCase("Connection Established"))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
......@@ -486,7 +496,7 @@ retry:
}
return new TcpServerConnection(proxyServer, tcpClient, stream, remoteHostName, remotePort, isHttps,
negotiatedApplicationProtocol, httpVersion, useUpstreamProxy, externalProxy, upStreamEndPoint, cacheKey);
negotiatedApplicationProtocol, httpVersion, externalProxy, upStreamEndPoint, cacheKey);
}
......@@ -659,4 +669,3 @@ retry:
}
}
}
......@@ -15,9 +15,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
internal class TcpServerConnection : IDisposable
{
public Guid Id { get; } = Guid.NewGuid();
internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient, HttpServerStream stream,
string hostName, int port, bool isHttps, SslApplicationProtocol negotiatedApplicationProtocol,
Version version, bool useUpstreamProxy, IExternalProxy? upStreamProxy, IPEndPoint? upStreamEndPoint, string cacheKey)
Version version, IExternalProxy? upStreamProxy, IPEndPoint? upStreamEndPoint, string cacheKey)
{
TcpClient = tcpClient;
LastAccess = DateTime.Now;
......@@ -29,7 +31,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
IsHttps = isHttps;
NegotiatedApplicationProtocol = negotiatedApplicationProtocol;
Version = version;
UseUpstreamProxy = useUpstreamProxy;
UpStreamProxy = upStreamProxy;
UpStreamEndPoint = upStreamEndPoint;
......@@ -50,8 +51,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal SslApplicationProtocol NegotiatedApplicationProtocol { get; set; }
internal bool UseUpstreamProxy { get; set; }
/// <summary>
/// Local NIC via connection is made
/// </summary>
......
......@@ -283,6 +283,12 @@ namespace Titanium.Web.Proxy
/// </summary>
public Func<SessionEventArgsBase, Task<IExternalProxy?>>? GetCustomUpStreamProxyFunc { get; set; }
/// <summary>
/// A callback to provide a chance for an upstream proxy failure to be handled by a new upstream proxy.
/// User should return the ExternalProxy object with valid credentials or null.
/// </summary>
public Func<SessionEventArgsBase, Task<IExternalProxy?>>? CustomUpStreamProxyFailureFunc { get; set; }
/// <summary>
/// Callback for error events in this proxy instance.
/// </summary>
......
......@@ -73,7 +73,7 @@ namespace Titanium.Web.Proxy
{
try
{
Request.ParseRequestLine(httpCmd!, out string httpMethod, out string httpUrl, out var version);
Request.ParseRequestLine(httpCmd!, out string httpMethod, out ByteString httpUrl, out var version);
// Read the request headers in to unique and non-unique header collections
await HeaderParser.ReadHeaders(clientStream, args.HttpClient.Request.Headers,
......@@ -86,7 +86,7 @@ namespace Titanium.Web.Proxy
request.Authority = connectRequest.Authority;
}
request.OriginalUrlData = HttpHeader.Encoding.GetBytes(httpUrl);
request.RequestUriString8 = httpUrl;
request.Method = httpMethod;
request.HttpVersion = version;
......@@ -99,8 +99,7 @@ namespace Titanium.Web.Proxy
await onBeforeResponse(args);
// send the response
await clientStream.WriteResponseAsync(args.HttpClient.Response,
cancellationToken: cancellationToken);
await clientStream.WriteResponseAsync(args.HttpClient.Response, cancellationToken);
return;
}
......@@ -173,7 +172,7 @@ namespace Titanium.Web.Proxy
connection = null;
}
var result = await handleHttpSessionRequest(httpMethod, httpUrl, version, args, connection,
var result = await handleHttpSessionRequest(args, connection,
clientConnection.NegotiatedApplicationProtocol,
cancellationToken, cancellationTokenSource);
......@@ -253,17 +252,20 @@ namespace Titanium.Web.Proxy
}
}
private async Task<RetryResult> handleHttpSessionRequest(string requestHttpMethod, string requestHttpUrl, Version requestVersion, SessionEventArgs args,
private async Task<RetryResult> handleHttpSessionRequest(SessionEventArgs args,
TcpServerConnection? serverConnection, SslApplicationProtocol sslApplicationProtocol,
CancellationToken cancellationToken, CancellationTokenSource cancellationTokenSource)
{
args.HttpClient.Request.Locked = true;
// do not cache server connections for WebSockets
bool noCache = args.HttpClient.Request.UpgradeToWebSocket;
// a connection generator task with captured parameters via closure.
Func<Task<TcpServerConnection>> generator = () =>
tcpConnectionFactory.GetServerConnection(this, args, isConnect: false,
applicationProtocol: sslApplicationProtocol,
noCache: false, cancellationToken: cancellationToken);
noCache: noCache, cancellationToken: cancellationToken);
// for connection pool, retry fails until cache is exhausted.
return await retryPolicy<ServerConnectionException>().ExecuteAsync(async (connection) =>
......@@ -278,9 +280,7 @@ namespace Titanium.Web.Proxy
args.HttpClient.ConnectRequest!.TunnelType = TunnelType.Websocket;
// if upgrading to websocket then relay the request without reading the contents
await handleWebSocketUpgrade(requestHttpMethod, requestHttpUrl, requestVersion, args, args.HttpClient.Request,
args.HttpClient.Response, args.ClientStream,
connection, cancellationTokenSource, cancellationToken);
await handleWebSocketUpgrade(args, args.ClientStream, connection, cancellationTokenSource, cancellationToken);
return false;
}
......
......@@ -70,7 +70,7 @@ namespace Titanium.Web.Proxy
if (response.Locked)
{
// write custom user response with body and return.
await clientStream.WriteResponseAsync(response, cancellationToken: cancellationToken);
await clientStream.WriteResponseAsync(response, cancellationToken);
if (args.HttpClient.HasConnection && !args.HttpClient.CloseServerConnection)
{
......@@ -93,8 +93,7 @@ namespace Titanium.Web.Proxy
// clear current response
await args.ClearResponse(cancellationToken);
await handleHttpSessionRequest(args.HttpClient.Request.Method, args.HttpClient.Request.Url, args.HttpClient.Request.HttpVersion,
args, null, args.ClientConnection.NegotiatedApplicationProtocol,
await handleHttpSessionRequest(args, null, args.ClientConnection.NegotiatedApplicationProtocol,
cancellationToken, args.CancellationTokenSource);
return;
}
......@@ -106,26 +105,25 @@ namespace Titanium.Web.Proxy
response.Headers.FixProxyHeaders();
}
if (response.IsBodyRead)
{
await clientStream.WriteResponseAsync(response, cancellationToken: cancellationToken);
}
else
await clientStream.WriteResponseAsync(response, cancellationToken);
if (response.OriginalHasBody)
{
// Write back response status to client
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteResponseLine(response.HttpVersion, response.StatusCode, response.StatusDescription);
headerBuilder.WriteHeaders(response.Headers);
await clientStream.WriteHeadersAsync(headerBuilder, cancellationToken);
// Write body if exists
if (response.HasBody)
if (response.IsBodySent)
{
// syphon out body
await args.SyphonOutBodyAsync(false, cancellationToken);
}
else
{
await args.CopyResponseBodyAsync(clientStream, TransformationMode.None,
cancellationToken);
// Copy body if exists
var serverStream = args.HttpClient.Connection.Stream;
await serverStream.CopyBodyAsync(response, false, clientStream, TransformationMode.None,
args.OnDataReceived, cancellationToken);
}
}
args.TimeLine["Response Sent"] = DateTime.Now;
}
......
using System.Threading;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.StreamExtended.Network
......@@ -8,5 +9,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
int Read(byte[] buffer, int offset, int count);
Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
Task CopyBodyAsync(IHttpStreamWriter writer, bool isChunked, long contentLength,
Action<byte[], int, int>? onCopy, CancellationToken cancellationToken);
}
}
......@@ -7,13 +7,14 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
/// <summary>
/// A concrete implementation of this interface is required when calling CopyStream.
/// </summary>
internal interface IHttpStreamWriter
public interface IHttpStreamWriter
{
void Write(byte[] buffer, int i, int bufferLength);
void Write(byte[] buffer, int offset, int count);
Task WriteAsync(byte[] buffer, int i, int bufferLength, CancellationToken cancellationToken);
Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
Task CopyBodyAsync(IHttpStreamReader streamReader, bool isChunked, long contentLength,
Action<byte[], int, int>? onCopy, CancellationToken cancellationToken);
ValueTask WriteLineAsync(CancellationToken cancellationToken = default);
ValueTask WriteLineAsync(string value, CancellationToken cancellationToken = default);
}
}
......@@ -16,42 +16,25 @@ namespace Titanium.Web.Proxy
/// <summary>
/// Handle upgrade to websocket
/// </summary>
private async Task handleWebSocketUpgrade(string requestHttpMethod, string requestHttpUrl, Version requestVersion,
SessionEventArgs args, Request request, Response response,
private async Task handleWebSocketUpgrade(SessionEventArgs args,
HttpClientStream clientStream, TcpServerConnection serverConnection,
CancellationTokenSource cancellationTokenSource, CancellationToken cancellationToken)
{
// prepare the prefix content
var headerBuilder = new HeaderBuilder();
headerBuilder.WriteRequestLine(requestHttpMethod, requestHttpUrl, requestVersion);
headerBuilder.WriteHeaders(request.Headers);
await serverConnection.Stream.WriteHeadersAsync(headerBuilder, cancellationToken);
await serverConnection.Stream.WriteRequestAsync(args.HttpClient.Request, cancellationToken);
string httpStatus;
try
{
httpStatus = await serverConnection.Stream.ReadLineAsync(cancellationToken)
?? throw new ServerConnectionException("Server connection was closed.");
}
catch (Exception e) when (!(e is ServerConnectionException))
{
throw new ServerConnectionException("Server connection was closed.", e);
}
Response.ParseResponseLine(httpStatus, out var responseVersion,
out int responseStatusCode,
out string responseStatusDescription);
response.HttpVersion = responseVersion;
response.StatusCode = responseStatusCode;
response.StatusDescription = responseStatusDescription;
var httpStatus = await serverConnection.Stream.ReadResponseStatus(cancellationToken);
var response = args.HttpClient.Response;
response.HttpVersion = httpStatus.Version;
response.StatusCode = httpStatus.StatusCode;
response.StatusDescription = httpStatus.Description;
await HeaderParser.ReadHeaders(serverConnection.Stream, response.Headers,
cancellationToken);
if (!args.IsTransparent)
{
await clientStream.WriteResponseAsync(response,
cancellationToken: cancellationToken);
await clientStream.WriteResponseAsync(response, cancellationToken);
}
// If user requested call back then do it
......
......@@ -22,7 +22,7 @@ namespace Titanium.Web.Proxy.IntegrationTests.Helpers
var request = new Request
{
Method = "POST",
Url = "/",
RequestUriString = "/",
HttpVersion = new Version(1, 1)
};
request.Headers.AddHeader(KnownHeaders.Host, server);
......
......@@ -28,7 +28,7 @@ namespace Titanium.Web.Proxy.IntegrationTests.Helpers
Request.ParseRequestLine(line, out var method, out var url, out var version);
var request = new Request
{
Method = method, Url = url, HttpVersion = version
Method = method, RequestUriString8 = url, HttpVersion = version
};
while (!string.IsNullOrEmpty(line = reader.ReadLine()))
{
......
......@@ -15,9 +15,12 @@ namespace Titanium.Web.Proxy.IntegrationTests
{
var testSuite = new TestSuite();
bool serverCalled = false;
var server = testSuite.GetServer();
server.HandleRequest((context) =>
{
serverCalled = true;
return context.Response.WriteAsync("I am server. I received your greetings.");
});
......@@ -37,11 +40,11 @@ namespace Titanium.Web.Proxy.IntegrationTests
var response = await client.GetAsync(new Uri(server.ListeningHttpUrl));
Assert.IsFalse(serverCalled, "Server should not be called.");
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.IsTrue(body.Contains("TitaniumWebProxy-Stopped!!"));
}
[TestMethod]
......@@ -76,7 +79,6 @@ namespace Titanium.Web.Proxy.IntegrationTests
var body = await response.Content.ReadAsStringAsync();
Assert.IsTrue(body.Contains("TitaniumWebProxy-Stopped!!"));
}
[TestMethod]
......@@ -111,7 +113,6 @@ namespace Titanium.Web.Proxy.IntegrationTests
var body = await response.Content.ReadAsStringAsync();
Assert.IsTrue(body.Contains("TitaniumWebProxy-Stopped!!"));
}
......@@ -147,7 +148,6 @@ namespace Titanium.Web.Proxy.IntegrationTests
var body = await response.Content.ReadAsStringAsync();
Assert.IsTrue(body.Contains("TitaniumWebProxy-Stopped!!"));
}
[TestMethod]
......@@ -181,7 +181,6 @@ namespace Titanium.Web.Proxy.IntegrationTests
var body = await response.Content.ReadAsStringAsync();
Assert.IsTrue(body.Contains("TitaniumWebProxy-Stopped!!"));
}
}
}
\ No newline at end of file
}
......@@ -16,7 +16,7 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
<PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
......
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