public HttpClientUpgradeHandler(ISourceCodec sourceCodec, IUpgradeCodec upgradeCodec, int maxContentLength) : base(maxContentLength) { Contract.Requires(sourceCodec != null); Contract.Requires(upgradeCodec != null); this.sourceCodec = sourceCodec; this.upgradeCodec = upgradeCodec; }
/// <summary>Constructs the client upgrade handler.</summary> /// <param name="sourceCodec">the codec that is being used initially.</param> /// <param name="upgradeCodec">the codec that the client would like to upgrade to.</param> /// <param name="maxContentLength">the maximum length of the aggregated content.</param> public HttpClientUpgradeHandler(ISourceCodec sourceCodec, IUpgradeCodec upgradeCodec, int maxContentLength) : base(maxContentLength) { if (sourceCodec is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.sourceCodec); } if (upgradeCodec is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.upgradeCodec); } this.sourceCodec = sourceCodec; this.upgradeCodec = upgradeCodec; }
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); }
/// <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); }