1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import NIOCore
/// A helper `ChannelInboundHandler` that makes it easy to swap channel pipelines
/// based on the result of an ALPN negotiation.
///
/// The standard pattern used by applications that want to use ALPN is to select
/// an application protocol based on the result, optionally falling back to some
/// default protocol. To do this in SwiftNIO requires that the channel pipeline be
/// reconfigured based on the result of the ALPN negotiation. This channel handler
/// encapsulates that logic in a generic form that doesn't depend on the specific
/// TLS implementation in use by using ``TLSUserEvent``
///
/// The user of this channel handler provides a single closure that is called with
/// an ``ALPNResult`` when the ALPN negotiation is complete. Based on that result
/// the user is free to reconfigure the `ChannelPipeline` as required, and should
/// return an `EventLoopFuture` that will complete when the pipeline is reconfigured.
///
/// Until the `EventLoopFuture` completes, this channel handler will buffer inbound
/// data. When the `EventLoopFuture` completes, the buffered data will be replayed
/// down the channel. Then, finally, this channel handler will automatically remove
/// itself from the channel pipeline, leaving the pipeline in its final
/// configuration.
///
/// Importantly, this is a typed variant of the ``ApplicationProtocolNegotiationHandler`` and allows the user to
/// specify a type that must be returned from the supplied closure. The result will then be used to succeed the ``NIOTypedApplicationProtocolNegotiationHandler/protocolNegotiationResult``
/// promise. This allows us to construct pipelines that include protocol negotiation handlers and be able to bridge them into `NIOAsyncChannel`
/// based bootstraps.
public final class NIOTypedApplicationProtocolNegotiationHandler<NegotiationResult>: ChannelInboundHandler, RemovableChannelHandler {
public typealias InboundIn = Any
public typealias InboundOut = Any
public var protocolNegotiationResult: EventLoopFuture<NegotiationResult> {
return self.negotiatedPromise.futureResult
}
private var negotiatedPromise: EventLoopPromise<NegotiationResult> {
precondition(self._negotiatedPromise != nil, "Tried to access the protocol negotiation result before the handler was added to a pipeline")
return self._negotiatedPromise!
}
private var _negotiatedPromise: EventLoopPromise<NegotiationResult>?
private let completionHandler: (ALPNResult, Channel) -> EventLoopFuture<NegotiationResult>
private var stateMachine = ProtocolNegotiationHandlerStateMachine<NegotiationResult>()
/// Create an `ApplicationProtocolNegotiationHandler` with the given completion
/// callback.
///
/// - Parameter alpnCompleteHandler: The closure that will fire when ALPN
/// negotiation has completed.
public init(alpnCompleteHandler: @escaping (ALPNResult, Channel) -> EventLoopFuture<NegotiationResult>) {
self.completionHandler = alpnCompleteHandler
}
/// Create an `ApplicationProtocolNegotiationHandler` with the given completion
/// callback.
///
/// - Parameter alpnCompleteHandler: The closure that will fire when ALPN
/// negotiation has completed.
public convenience init(alpnCompleteHandler: @escaping (ALPNResult) -> EventLoopFuture<NegotiationResult>) {
self.init { result, _ in
alpnCompleteHandler(result)
}
}
public func handlerAdded(context: ChannelHandlerContext) {
self._negotiatedPromise = context.eventLoop.makePromise()
}
public func handlerRemoved(context: ChannelHandlerContext) {
switch self.stateMachine.handlerRemoved() {
case .failPromise:
self.negotiatedPromise.fail(ChannelError.inappropriateOperationForState)
case .none:
break
}
}
public func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
switch self.stateMachine.userInboundEventTriggered(event: event) {
case .fireUserInboundEventTriggered:
context.fireUserInboundEventTriggered(event)
case .invokeUserClosure(let result):
self.invokeUserClosure(context: context, result: result)
}
}
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
switch self.stateMachine.channelRead(data: data) {
case .fireChannelRead:
context.fireChannelRead(data)
case .none:
break
}
}
public func channelInactive(context: ChannelHandlerContext) {
self.stateMachine.channelInactive()
self.negotiatedPromise.fail(ChannelError.outputClosed)
context.fireChannelInactive()
}
private func invokeUserClosure(context: ChannelHandlerContext, result: ALPNResult) {
let switchFuture = self.completionHandler(result, context.channel)
switchFuture
.hop(to: context.eventLoop)
.whenComplete { result in
self.userFutureCompleted(context: context, result: result)
}
}
private func userFutureCompleted(context: ChannelHandlerContext, result: Result<NegotiationResult, Error>) {
switch self.stateMachine.userFutureCompleted(with: result) {
case .fireErrorCaughtAndRemoveHandler(let error):
self.negotiatedPromise.fail(error)
context.fireErrorCaught(error)
context.pipeline.removeHandler(self, promise: nil)
case .fireErrorCaughtAndStartUnbuffering(let error):
self.negotiatedPromise.fail(error)
context.fireErrorCaught(error)
self.unbuffer(context: context)
case .startUnbuffering(let value):
self.negotiatedPromise.succeed(value)
self.unbuffer(context: context)
case .removeHandler(let value):
self.negotiatedPromise.succeed(value)
context.pipeline.removeHandler(self, promise: nil)
case .none:
break
}
}
private func unbuffer(context: ChannelHandlerContext) {
while true {
switch self.stateMachine.unbuffer() {
case .fireChannelRead(let data):
context.fireChannelRead(data)
case .fireChannelReadCompleteAndRemoveHandler:
context.fireChannelReadComplete()
context.pipeline.removeHandler(self, promise: nil)
return
}
}
}
}
@available(*, unavailable)
extension NIOTypedApplicationProtocolNegotiationHandler: Sendable {}
|