예제 #1
0
        private async ValueTask <(StreamCopyResult, Exception)> CopyResponseBodyAsync(HttpContent destinationResponseContent, Stream clientResponseStream,
                                                                                      CancellationToken cancellation)
        {
            // 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, cancellation));
            }

            return(StreamCopyResult.Success, null);
        }
예제 #2
0
        private async Task HandleUpgradedResponse(HttpContext context, HttpResponseMessage destinationResponse,
                                                  CancellationToken longCancellation)
        {
            ProxyTelemetry.Log.ProxyStage(ProxyStage.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>();
            Stream upgradeResult;

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

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

            using var abortTokenSource = CancellationTokenSource.CreateLinkedTokenSource(longCancellation);

            var requestTask  = StreamCopier.CopyAsync(isRequest: true, clientStream, destinationStream, _clock, abortTokenSource.Token).AsTask();
            var responseTask = StreamCopier.CopyAsync(isRequest: false, destinationStream, clientStream, _clock, abortTokenSource.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;

            var(firstResult, firstException) = await firstTask;
            if (firstResult != StreamCopyResult.Success)
            {
                ReportResult(context, requestFinishedFirst, firstResult, firstException);
                // Cancel the other direction
                abortTokenSource.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)
                {
                    ReportResult(context, requestFinishedFirst, secondResult, secondException);
                }
            }

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

                ReportProxyError(context, error, exception);
            }
        }