Exemple #1
0
        /// <summary>
        /// Copies bytes from the stream provided in our constructor into the target <paramref name="stream"/>.
        /// </summary>
        /// <remarks>
        /// This is used internally by HttpClient.SendAsync to send the request body.
        /// Here's the sequence of events as of commit 17300169760c61a90cab8d913636c1058a30a8c1 (https://github.com/dotnet/corefx -- tag v3.1.1).
        ///
        /// <code>
        /// HttpClient.SendAsync -->
        /// HttpMessageInvoker.SendAsync -->
        /// HttpClientHandler.SendAsync -->
        /// SocketsHttpHandler.SendAsync -->
        /// HttpConnectionHandler.SendAsync -->
        /// HttpConnectionPoolManager.SendAsync -->
        /// HttpConnectionPool.SendAsync --> ... -->
        /// {
        ///     HTTP/1.1: HttpConnection.SendAsync -->
        ///               HttpConnection.SendAsyncCore -->
        ///               HttpConnection.SendRequestContentAsync -->
        ///               HttpContent.CopyToAsync
        ///
        ///     HTTP/2:   Http2Connection.SendAsync -->
        ///               Http2Stream.SendRequestBodyAsync -->
        ///               HttpContent.CopyToAsync
        ///
        ///     /* Only in .NET 5:
        ///     HTTP/3:   Http3Connection.SendAsync -->
        ///               Http3Connection.SendWithoutWaitingAsync -->
        ///               Http3RequestStream.SendAsync -->
        ///               Http3RequestStream.SendContentAsync -->
        ///               HttpContent.CopyToAsync
        ///     */
        /// }
        ///
        /// HttpContent.CopyToAsync -->
        /// HttpContent.SerializeToStreamAsync (bingo!)
        /// </code>
        ///
        /// Conclusion: by overriding HttpContent.SerializeToStreamAsync,
        /// we have full control over pumping bytes to the target stream for all protocols
        /// (except Web Sockets, which is handled separately).
        /// </remarks>
        protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
        {
            if (Started)
            {
                throw new InvalidOperationException("Stream was already consumed.");
            }

            Started = true;
            try
            {
                if (_autoFlushHttpClientOutgoingStream)
                {
                    // HttpClient's machinery keeps an internal buffer that doesn't get flushed to the socket on every write.
                    // Some protocols (e.g. gRPC) may rely on specific bytes being sent, and HttpClient's buffering would prevent it.
                    // AutoFlushingStream delegates to the provided stream, adding calls to FlushAsync on every WriteAsync.
                    // Note that HttpClient does NOT call Flush on the underlying socket, so the perf impact of this is expected to be small.
                    // This statement is based on current knowledge as of .NET Core 3.1.201.
                    stream = new AutoFlushingStream(stream);
                }

                // Immediately flush request stream to send headers
                // https://github.com/dotnet/corefx/issues/39586#issuecomment-516210081
                await stream.FlushAsync();

                await _streamCopier.CopyAsync(_source, stream, _cancellation);

                _tcs.TrySetResult(true);
            }
            catch (Exception ex)
            {
                _tcs.TrySetException(ex);
                throw;
            }
        }
Exemple #2
0
        /// <summary>
        /// Proxies an upgradable request to the upstream server, treating the upgraded stream as an opaque duplex channel.
        /// </summary>
        /// <remarks>
        /// Upgradable request proxying comprises the following steps:
        ///    (1)  Create outgoing HttpRequestMessage
        ///    (2)  Copy request headers                                              Downstream ---► Proxy ---► Upstream
        ///    (3)  Send the outgoing request using HttpMessageInvoker                Downstream ---► Proxy ---► Upstream
        ///    (4)  Copy response status line                                         Downstream ◄--- Proxy ◄--- Upstream
        ///    (5)  Copy response headers                                             Downstream ◄--- Proxy ◄--- Upstream
        ///       Scenario A: upgrade with upstream worked (got 101 response)
        ///          (A-6)  Upgrade downstream channel (also sends response headers)  Downstream ◄--- Proxy ◄--- Upstream
        ///          (A-7)  Copy duplex streams                                       Downstream ◄--► Proxy ◄--► Upstream
        ///       ---- or ----
        ///       Scenario B: upgrade with upstream failed (got non-101 response)
        ///          (B-6)  Send response headers                                     Downstream ◄--- Proxy ◄--- Upstream
        ///          (B-7)  Copy response body                                        Downstream ◄--- Proxy ◄--- Upstream
        ///
        /// This takes care of WebSockets as well as any other upgradable protocol.
        /// </remarks>
        private async Task UpgradableProxyAsync(
            HttpContext context,
            IHttpUpgradeFeature upgradeFeature,
            HttpRequestMessage upstreamRequest,
            HttpMessageInvoker httpClient,
            CancellationToken shortCancellation,
            CancellationToken longCancellation,
            Action <HttpRequestMessage> requestAction = null)
        {
            _ = context ?? throw new ArgumentNullException(nameof(context));
            _ = upgradeFeature ?? throw new ArgumentNullException(nameof(upgradeFeature));
            _ = upstreamRequest ?? throw new ArgumentNullException(nameof(upstreamRequest));
            _ = httpClient ?? throw new ArgumentNullException(nameof(httpClient));

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 2: Copy request headers Downstream --► Proxy --► Upstream
            CopyHeadersToUpstream(context, upstreamRequest);

            if (requestAction != null)
            {
                requestAction(upstreamRequest);
            }

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 3: Send the outgoing request using HttpMessageInvoker
            var upstreamResponse = await httpClient.SendAsync(upstreamRequest, shortCancellation);

            var upgraded = upstreamResponse.StatusCode == HttpStatusCode.SwitchingProtocols && upstreamResponse.Content != null;

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 4: Copy response status line Downstream ◄-- Proxy ◄-- Upstream
            context.Response.StatusCode = (int)upstreamResponse.StatusCode;
            context.Features.Get <IHttpResponseFeature>().ReasonPhrase = upstreamResponse.ReasonPhrase;

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step 5: Copy response headers Downstream ◄-- Proxy ◄-- Upstream
            CopyHeadersToDownstream(upstreamResponse, context);

            if (!upgraded)
            {
                // :::::::::::::::::::::::::::::::::::::::::::::
                // :: Step B-6: Send response headers Downstream ◄-- Proxy ◄-- Upstream
                // This is important to avoid any extra delays in sending response headers
                // e.g. if the upstream server is slow to provide its response body.
                await context.Response.StartAsync(shortCancellation);

                // :::::::::::::::::::::::::::::::::::::::::::::
                // :: Step B-7: Copy response body Downstream ◄-- Proxy ◄-- Upstream
                await CopyBodyDownstreamAsync(upstreamResponse.Content, context.Response.Body, longCancellation);

                return;
            }

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step A-6: Upgrade the downstream channel. This will send all response headers too.
            using var downstreamStream = await upgradeFeature.UpgradeAsync();

            // :::::::::::::::::::::::::::::::::::::::::::::
            // :: Step A-7: Copy duplex streams
            var upstreamStream = await upstreamResponse.Content.ReadAsStreamAsync();

            var upstreamCopier = new StreamCopier();
            var upstreamTask   = upstreamCopier.CopyAsync(downstreamStream, upstreamStream, longCancellation);

            var downstreamCopier = new StreamCopier();
            var downstreamTask   = downstreamCopier.CopyAsync(upstreamStream, downstreamStream, longCancellation);

            await Task.WhenAll(upstreamTask, downstreamTask);
        }