Ejemplo n.º 1
0
    private async ValueTask <ForwarderError> HandleUpgradedResponse(HttpContext context, HttpResponseMessage destinationResponse,
                                                                    ActivityCancellationTokenSource activityCancellationSource)
    {
        ForwarderTelemetry.Log.ForwarderStage(ForwarderStage.ResponseUpgrade);

        // SocketHttpHandler and similar transports always provide an HttpContent object, even if it's empty.
        // Note as of 5.0 HttpResponse.Content never returns null.
        // https://github.com/dotnet/runtime/blame/8fc68f626a11d646109a758cb0fc70a0aa7826f1/src/libraries/System.Net.Http/src/System/Net/Http/HttpResponseMessage.cs#L46
        if (destinationResponse.Content == null)
        {
            throw new InvalidOperationException("A response content is required for upgrades.");
        }

        // :: Step 7-A-1: Upgrade the client channel. This will also send response headers.
        var upgradeFeature = context.Features.Get <IHttpUpgradeFeature>();

        if (upgradeFeature == null)
        {
            var ex = new InvalidOperationException("Invalid 101 response when upgrades aren't supported.");
            destinationResponse.Dispose();
            context.Response.StatusCode = StatusCodes.Status502BadGateway;
            ReportProxyError(context, ForwarderError.UpgradeResponseDestination, ex);
            return(ForwarderError.UpgradeResponseDestination);
        }

        RestoreUpgradeHeaders(context, destinationResponse);

        Stream upgradeResult;

        try
        {
            upgradeResult = await upgradeFeature.UpgradeAsync();
        }
        catch (Exception ex)
        {
            destinationResponse.Dispose();
            ReportProxyError(context, ForwarderError.UpgradeResponseClient, ex);
            return(ForwarderError.UpgradeResponseClient);
        }
        using var clientStream = upgradeResult;

        // :: Step 7-A-2: Copy duplex streams
        using var destinationStream = await destinationResponse.Content.ReadAsStreamAsync();

        var requestTask  = StreamCopier.CopyAsync(isRequest: true, clientStream, destinationStream, StreamCopier.UnknownLength, _clock, activityCancellationSource, activityCancellationSource.Token).AsTask();
        var responseTask = StreamCopier.CopyAsync(isRequest: false, destinationStream, clientStream, StreamCopier.UnknownLength, _clock, activityCancellationSource, activityCancellationSource.Token).AsTask();

        // Make sure we report the first failure.
        var firstTask = await Task.WhenAny(requestTask, responseTask);

        var requestFinishedFirst = firstTask == requestTask;
        var secondTask           = requestFinishedFirst ? responseTask : requestTask;

        ForwarderError error;

        var(firstResult, firstException) = await firstTask;
        if (firstResult != StreamCopyResult.Success)
        {
            error = ReportResult(context, requestFinishedFirst, firstResult, firstException);
            // Cancel the other direction
            activityCancellationSource.Cancel();
            // Wait for this to finish before exiting so the resources get cleaned up properly.
            await secondTask;
        }
        else
        {
            var(secondResult, secondException) = await secondTask;
            if (secondResult != StreamCopyResult.Success)
            {
                error = ReportResult(context, !requestFinishedFirst, secondResult, secondException !);
            }
            else
            {
                error = ForwarderError.None;
            }
        }

        return(error);

        ForwarderError ReportResult(HttpContext context, bool reqeuest, StreamCopyResult result, Exception exception)
        {
            var error = result switch
            {
                StreamCopyResult.InputError => reqeuest ? ForwarderError.UpgradeRequestClient : ForwarderError.UpgradeResponseDestination,
                StreamCopyResult.OutputError => reqeuest ? ForwarderError.UpgradeRequestDestination : ForwarderError.UpgradeResponseClient,
                StreamCopyResult.Canceled => reqeuest ? ForwarderError.UpgradeRequestCanceled : ForwarderError.UpgradeResponseCanceled,
                _ => throw new NotImplementedException(result.ToString()),
            };

            ReportProxyError(context, error, exception);
            return(error);
        }
    }