コード例 #1
0
        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);
            }
        }