bool Upgrade(IChannelHandlerContext ctx, IFullHttpRequest request)
        {
            // Select the best protocol based on those requested in the UPGRADE header.
            IList <ICharSequence> requestedProtocols = SplitHeader(request.Headers.Get(HttpHeaderNames.Upgrade, null));
            int           numRequestedProtocols      = requestedProtocols.Count;
            IUpgradeCodec upgradeCodec    = null;
            ICharSequence upgradeProtocol = null;

            for (int i = 0; i < numRequestedProtocols; i++)
            {
                ICharSequence p = requestedProtocols[i];
                IUpgradeCodec c = this.upgradeCodecFactory.NewUpgradeCodec(p);
                if (c != null)
                {
                    upgradeProtocol = p;
                    upgradeCodec    = c;
                    break;
                }
            }

            if (upgradeCodec == null)
            {
                // None of the requested protocols are supported, don't upgrade.
                return(false);
            }

            // Make sure the CONNECTION header is present.
            if (!request.Headers.TryGet(HttpHeaderNames.Connection, out ICharSequence connectionHeader))
            {
                return(false);
            }

            // Make sure the CONNECTION header contains UPGRADE as well as all protocol-specific headers.
            ICollection <AsciiString> requiredHeaders = upgradeCodec.RequiredUpgradeHeaders;
            IList <ICharSequence>     values          = SplitHeader(connectionHeader);

            if (!AsciiString.ContainsContentEqualsIgnoreCase(values, HttpHeaderNames.Upgrade) ||
                !AsciiString.ContainsAllContentEqualsIgnoreCase(values, requiredHeaders))
            {
                return(false);
            }

            // Ensure that all required protocol-specific headers are found in the request.
            foreach (AsciiString requiredHeader in requiredHeaders)
            {
                if (!request.Headers.Contains(requiredHeader))
                {
                    return(false);
                }
            }

            // Prepare and send the upgrade response. Wait for this write to complete before upgrading,
            // since we need the old codec in-place to properly encode the response.
            IFullHttpResponse upgradeResponse = CreateUpgradeResponse(upgradeProtocol);

            if (!upgradeCodec.PrepareUpgradeResponse(ctx, request, upgradeResponse.Headers))
            {
                return(false);
            }

            // Create the user event to be fired once the upgrade completes.
            var upgradeEvent = new UpgradeEvent(upgradeProtocol, request);

            IUpgradeCodec finalUpgradeCodec = upgradeCodec;

            ctx.WriteAndFlushAsync(upgradeResponse).ContinueWith(t =>
            {
                try
                {
                    if (t.Status == TaskStatus.RanToCompletion)
                    {
                        // Perform the upgrade to the new protocol.
                        this.sourceCodec.UpgradeFrom(ctx);
                        finalUpgradeCodec.UpgradeTo(ctx, request);

                        // Notify that the upgrade has occurred. Retain the event to offset
                        // the release() in the finally block.
                        ctx.FireUserEventTriggered(upgradeEvent.Retain());

                        // Remove this handler from the pipeline.
                        ctx.Channel.Pipeline.Remove(this);
                    }
                    else
                    {
                        ctx.Channel.CloseAsync();
                    }
                }
                finally
                {
                    // Release the event if the upgrade event wasn't fired.
                    upgradeEvent.Release();
                }
            }, TaskContinuationOptions.ExecuteSynchronously);
            return(true);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Attempts to upgrade to the protocol(s) identified by the <see cref="HttpHeaderNames.Upgrade"/> header (if provided
        /// in the request).
        /// </summary>
        /// <param name="ctx">the context for this handler.</param>
        /// <param name="request">the HTTP request.</param>
        /// <returns><c>true</c> if the upgrade occurred, otherwise <c>false</c>.</returns>
        bool Upgrade(IChannelHandlerContext ctx, IFullHttpRequest request)
        {
            // Select the best protocol based on those requested in the UPGRADE header.
            var           requestedProtocols    = SplitHeader(request.Headers.Get(HttpHeaderNames.Upgrade, null));
            int           numRequestedProtocols = requestedProtocols.Count;
            IUpgradeCodec upgradeCodec          = null;
            ICharSequence upgradeProtocol       = null;

            for (int i = 0; i < numRequestedProtocols; i++)
            {
                ICharSequence p = requestedProtocols[i];
                IUpgradeCodec c = this.upgradeCodecFactory.NewUpgradeCodec(p);
                if (c is object)
                {
                    upgradeProtocol = p;
                    upgradeCodec    = c;
                    break;
                }
            }

            if (upgradeCodec is null)
            {
                // None of the requested protocols are supported, don't upgrade.
                return(false);
            }

            // Make sure the CONNECTION header is present.
            var connectionHeaderValues = request.Headers.GetAll(HttpHeaderNames.Connection);

            if (connectionHeaderValues is null)
            {
                return(false);
            }

            var concatenatedConnectionValue = StringBuilderManager.Allocate(connectionHeaderValues.Count * 10);

            for (var idx = 0; idx < connectionHeaderValues.Count; idx++)
            {
                var connectionHeaderValue = connectionHeaderValues[idx];
                _ = concatenatedConnectionValue
                    .Append(connectionHeaderValue.ToString())
                    .Append(StringUtil.Comma);
            }
            concatenatedConnectionValue.Length -= 1;

            // Make sure the CONNECTION header contains UPGRADE as well as all protocol-specific headers.
            var requiredHeaders = upgradeCodec.RequiredUpgradeHeaders;
            var values          = SplitHeader(StringBuilderManager.ReturnAndFree(concatenatedConnectionValue).AsSpan());

            if (!AsciiString.ContainsContentEqualsIgnoreCase(values, HttpHeaderNames.Upgrade) ||
                !AsciiString.ContainsAllContentEqualsIgnoreCase(values, requiredHeaders))
            {
                return(false);
            }

            // Ensure that all required protocol-specific headers are found in the request.
            for (int idx = 0; idx < requiredHeaders.Count; idx++)
            {
                if (!request.Headers.Contains(requiredHeaders[idx]))
                {
                    return(false);
                }
            }

            // Prepare and send the upgrade response. Wait for this write to complete before upgrading,
            // since we need the old codec in-place to properly encode the response.
            IFullHttpResponse upgradeResponse = CreateUpgradeResponse(upgradeProtocol);

            if (!upgradeCodec.PrepareUpgradeResponse(ctx, request, upgradeResponse.Headers))
            {
                return(false);
            }

            // Create the user event to be fired once the upgrade completes.
            var upgradeEvent = new UpgradeEvent(upgradeProtocol, request);

            // After writing the upgrade response we immediately prepare the
            // pipeline for the next protocol to avoid a race between completion
            // of the write future and receiving data before the pipeline is
            // restructured.
            try
            {
                var writeComplete = ctx.WriteAndFlushAsync(upgradeResponse);

                // Perform the upgrade to the new protocol.
                this.sourceCodec.UpgradeFrom(ctx);
                upgradeCodec.UpgradeTo(ctx, request);

                // Remove this handler from the pipeline.
                _ = ctx.Pipeline.Remove(this);

                // Notify that the upgrade has occurred. Retain the event to offset
                // the release() in the finally block.
                _ = ctx.FireUserEventTriggered(upgradeEvent.Retain());

                // Add the listener last to avoid firing upgrade logic after
                // the channel is already closed since the listener may fire
                // immediately if the write failed eagerly.
                _ = writeComplete.ContinueWith(CloseOnFailureAction, ctx, TaskContinuationOptions.ExecuteSynchronously);
            }
            finally
            {
                // Release the event if the upgrade event wasn't fired.
                _ = upgradeEvent.Release();
            }
            return(true);
        }