public ListenerTask MainLoopListenerTask(TestContext ctx, CancellationToken cancellationToken) { var me = $"{Listener.ME}({Connection.ME}) TASK"; HttpOperation clientOperation = null; lock (Listener) { if (currentListenerTask != null) { throw new InvalidOperationException(); } if (Listener.UsingInstrumentation) { if (assignedOperation == null) { throw new InvalidOperationException(); } clientOperation = assignedOperation.Operation; } } ctx.LogDebug(5, $"{me} {State}"); currentListenerTask = StartListenerTask(); currentListenerTask.Start(); return(currentListenerTask); ListenerTask StartListenerTask() { switch (State) { case ConnectionState.Listening: return(ListenerTask.Create(this, State, Start, Accepted)); case ConnectionState.InitializeConnection: return(ListenerTask.Create(this, State, InitializeConnection, Initialized)); case ConnectionState.WaitingForRequest: return(ListenerTask.Create(this, State, ReadRequestHeader, GotRequest)); case ConnectionState.HasRequest: return(ListenerTask.Create(this, State, HandleRequest, RequestComplete)); case ConnectionState.RequestComplete: return(ListenerTask.Create(this, State, WriteResponse, ResponseWritten)); case ConnectionState.InitializeProxyConnection: return(ListenerTask.Create(this, State, InitProxyConnection, InitProxyConnectionDone)); case ConnectionState.ConnectToTarget: return(ListenerTask.Create(this, State, ConnectToTarget, ProxyConnectionEstablished)); case ConnectionState.HandleProxyConnection: return(ListenerTask.Create(this, State, HandleProxyConnection, ProxyConnectionFinished)); case ConnectionState.RunTunnel: return(ListenerTask.Create(this, State, ProxyConnect, ProxyConnectDone)); case ConnectionState.WaitForClose: return(ListenerTask.Create(this, State, WaitForClose, WaitForCloseDone)); default: throw ctx.AssertFail(State); } } Task Start() { return(Accept(ctx, cancellationToken)); } ConnectionState Accepted() { return(ConnectionState.InitializeConnection); } Task <(bool complete, bool success)> InitializeConnection() { return(Initialize(ctx, clientOperation, cancellationToken)); } ConnectionState Initialized(bool completed, bool success) { ctx.LogDebug(5, $"{me} INITIALIZED: {completed} {success}"); if (!completed) { return(ConnectionState.CannotReuseConnection); } if (!success) { return(ConnectionState.Closed); } return(ConnectionState.WaitingForRequest); } Task <HttpRequest> ReadRequestHeader() { ctx.LogDebug(5, $"{me} READ REQUEST HEADER"); return(Connection.ReadRequestHeader(ctx, cancellationToken)); } ConnectionState GotRequest(HttpRequest request) { currentRequest = request; if (request.Method == "CONNECT") { Server.BumpRequestCount(); return(ConnectionState.RunTunnel); } var operation = Listener.GetOperation(this, request); if (operation == null) { ctx.LogDebug(5, $"{me} INVALID REQUEST: {request.Path}"); return(ConnectionState.Closed); } currentOperation = operation; ctx.LogDebug(5, $"{me} GOT REQUEST"); if (Listener.TargetListener == null) { return(ConnectionState.HasRequest); } return(ConnectionState.InitializeProxyConnection); } Task <HttpResponse> HandleRequest() { return(currentOperation.HandleRequest(ctx, this, Connection, currentRequest, cancellationToken)); } async Task InitProxyConnection() { await currentRequest.ReadHeaders(ctx, cancellationToken).ConfigureAwait(false); await currentRequest.Read(ctx, cancellationToken); } ConnectionState InitProxyConnectionDone() { var request = currentRequest; var operation = currentOperation; var remoteAddress = connection.RemoteEndPoint.Address; request.AddHeader("X-Forwarded-For", remoteAddress); var authManager = ((BuiltinProxyServer)Listener.Server).AuthenticationManager; if (authManager != null) { AuthenticationState state; var response = authManager.HandleAuthentication(ctx, connection, request, out state); if (response != null) { Listener.TargetListener.UnregisterOperation(operation); response.Redirect = Listener.RegisterOperation(ctx, operation.Operation, operation.Handler, request.Path); operation.RegisterProxyAuth(response.Redirect); return(RequestComplete(response)); } // HACK: Mono rewrites chunked requests into non-chunked. request.AddHeader("X-Mono-Redirected", "true"); } return(ConnectionState.ConnectToTarget); } async Task ConnectToTarget() { ctx.LogDebug(5, $"{me} CONNECT TO TARGET"); targetConnection = new SocketConnection(Listener.TargetListener.Server); var targetEndPoint = new DnsEndPoint(currentOperation.Uri.Host, currentOperation.Uri.Port); ctx.LogDebug(5, $"{me} CONNECT TO TARGET #1: {targetEndPoint}"); cancellationToken.ThrowIfCancellationRequested(); await targetConnection.ConnectAsync(ctx, targetEndPoint, cancellationToken); ctx.LogDebug(5, $"{me} CONNECT TO TARGET #2"); cancellationToken.ThrowIfCancellationRequested(); await targetConnection.Initialize(ctx, currentOperation.Operation, cancellationToken); ctx.LogDebug(5, $"{me} CONNECT TO TARGET #3"); } ConnectionState ProxyConnectionEstablished() { return(ConnectionState.HandleProxyConnection); } async Task <HttpResponse> HandleProxyConnection() { var copyResponseTask = CopyResponse(); cancellationToken.ThrowIfCancellationRequested(); await targetConnection.WriteRequest(ctx, currentRequest, cancellationToken); ctx.LogDebug(5, $"{me} HANDLE PROXY CONNECTION"); cancellationToken.ThrowIfCancellationRequested(); var response = await copyResponseTask.ConfigureAwait(false); ctx.LogDebug(5, $"{me} HANDLE PROXY CONNECTION #1"); return(response); } ConnectionState ProxyConnectionFinished(HttpResponse response) { currentResponse = response; targetConnection.Dispose(); targetConnection = null; return(ConnectionState.RequestComplete); } async Task <HttpResponse> CopyResponse() { cancellationToken.ThrowIfCancellationRequested(); var response = await targetConnection.ReadResponse(ctx, cancellationToken).ConfigureAwait(false); response.SetHeader("Connection", "close"); response.SetHeader("Proxy-Connection", "close"); return(response); } async Task ProxyConnect() { await CreateTunnel(ctx, ((ProxyConnection)connection).Stream, currentRequest, cancellationToken); } ConnectionState ProxyConnectDone() { return(ConnectionState.Closed); } ConnectionState RequestComplete(HttpResponse response) { ctx.LogDebug(5, $"{me}: {response} {response.Redirect?.ME}"); currentResponse = response; var keepAlive = (response.KeepAlive ?? false) && !response.CloseConnection; if (response.Redirect != null && Listener.UsingInstrumentation && (!keepAlive || Operation.Operation.HasAnyFlags(HttpOperationFlags.RedirectOnNewConnection))) { return(ConnectionState.NeedContextForRedirect); } return(ConnectionState.RequestComplete); } async Task <bool> WriteResponse() { var response = Interlocked.Exchange(ref currentResponse, null); var redirect = Interlocked.Exchange(ref redirectContext, null); redirectRequested = response.Redirect; var keepAlive = (response.KeepAlive ?? false) && !response.CloseConnection; if (redirect != null) { ctx.LogDebug(5, $"{me} REDIRECT ON NEW CONTEXT: {redirect.ME}!"); await redirect.ServerStartTask.ConfigureAwait(false); ctx.LogDebug(5, $"{me} REDIRECT ON NEW CONTEXT #1: {redirect.ME}!"); keepAlive = false; } if (!Operation.Operation.HasAnyFlags(HttpOperationFlags.DontWriteResponse)) { await connection.WriteResponse(ctx, response, cancellationToken).ConfigureAwait(false); } return(keepAlive); } ConnectionState ResponseWritten(bool keepAlive) { var request = Interlocked.Exchange(ref currentRequest, null); var operation = Interlocked.Exchange(ref currentOperation, null); var redirect = Interlocked.Exchange(ref redirectRequested, null); if (redirect != null && (operation?.Operation.HasAnyFlags(HttpOperationFlags.RedirectOnNewConnection) ?? false)) { return(ConnectionState.WaitForClose); } if (!keepAlive) { return(ConnectionState.Closed); } if (redirect == null) { return(ConnectionState.ReuseConnection); } if (operation?.Operation.HasAnyFlags(HttpOperationFlags.ServerAbortsRedirection) ?? false) { connection.Dispose(); } return(ConnectionState.WaitingForRequest); } async Task <bool> WaitForClose() { var result = await Connection.WaitForRequest(2500, cancellationToken).ConfigureAwait(false); ctx.LogDebug(5, $"{me} WAIT FOR CLOSE #1: {result}"); return(ctx.Expect(result, Is.False, "expected client to close the connection")); } ConnectionState WaitForCloseDone(bool result) { return(result ? ConnectionState.Closed : ConnectionState.CannotReuseConnection); } }