コード例 #1
0
        private async ValueTask <(StreamCopyResult, Exception?)> CopyResponseBodyAsync(HttpContent destinationResponseContent, Stream clientResponseStream,
                                                                                       CancellationTokenSource activityCancellationSource, TimeSpan activityTimeout)
        {
            // SocketHttpHandler and similar transports always provide an HttpContent object, even if it's empty.
            // In 3.1 this is only likely to return null in tests.
            // 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 (destinationResponseContent != null)
            {
                using var destinationResponseStream = await destinationResponseContent.ReadAsStreamAsync();

                return(await StreamCopier.CopyAsync(isRequest : false, destinationResponseStream, clientResponseStream, _clock, activityCancellationSource, activityTimeout));
            }

            return(StreamCopyResult.Success, null);
        }
コード例 #2
0
        private async ValueTask <ForwarderError> HandleUpgradedResponse(HttpContext context, HttpResponseMessage destinationResponse,
                                                                        CancellationTokenSource activityCancellationSource, TimeSpan activityTimeout)
        {
            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, _clock, activityCancellationSource, activityTimeout).AsTask();
            var responseTask = StreamCopier.CopyAsync(isRequest: false, destinationStream, clientStream, _clock, activityCancellationSource, activityTimeout).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);
            }
        }