Add ChannelOptions to extract base types. (#203)
Motivation: In some cases users may want to access APIs we haven't exposed in NIOTransportServices. We should have a fallback that allows users to do this. Modifications: - Add ChannelOptions for getting NWConnection and NWListener. Result: Users have an escape hatch
This commit is contained in:
parent
715e3179d3
commit
38ac8221dd
|
|
@ -150,6 +150,16 @@ internal final class NIOTSDatagramChannel: StateManagedNWConnectionChannel {
|
|||
}
|
||||
|
||||
func getChannelSpecificOption0<Option>(option: Option) throws -> Option.Value where Option : ChannelOption {
|
||||
if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) {
|
||||
switch option {
|
||||
case is NIOTSChannelOptions.Types.NIOTSConnectionOption:
|
||||
return self.connection as! Option.Value
|
||||
default:
|
||||
// Check the non-constrained options.
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
fatalError("option \(type(of: option)).\(option) not supported")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,14 @@ public struct NIOTSChannelOptions {
|
|||
|
||||
/// See: ``Types/NIOTSMultipathOption``
|
||||
public static let multipathServiceType = NIOTSChannelOptions.Types.NIOTSMultipathOption()
|
||||
|
||||
/// See: ``Types/NIOTSConnectionOption``.
|
||||
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
|
||||
public static let connection = NIOTSChannelOptions.Types.NIOTSConnectionOption()
|
||||
|
||||
/// See: ``Types/NIOTSListenerOption``.
|
||||
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
|
||||
public static let listener = NIOTSChannelOptions.Types.NIOTSListenerOption()
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -143,6 +151,34 @@ extension NIOTSChannelOptions {
|
|||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
/// ``NIOTSConnectionOption`` accesses the `NWConnection` of the underlying connection.
|
||||
///
|
||||
/// > Warning: Callers must be extremely careful with this option, as it is easy to break an existing
|
||||
/// > connection that uses it. NIOTS doesn't support arbitrary modifications of the `NWConnection`
|
||||
/// > underlying a `Channel`.
|
||||
///
|
||||
/// This option is only valid with a `Channel` backed by an `NWConnection`.
|
||||
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
|
||||
public struct NIOTSConnectionOption: ChannelOption, Equatable {
|
||||
public typealias Value = NWConnection?
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
/// ``NIOTSListenerOption`` accesses the `NWListener` of the underlying connection.
|
||||
///
|
||||
/// > Warning: Callers must be extremely careful with this option, as it is easy to break an existing
|
||||
/// > connection that uses it. NIOTS doesn't support arbitrary modifications of the `NWListener`
|
||||
/// > underlying a `Channel`.
|
||||
///
|
||||
/// This option is only valid with a `Channel` backed by an `NWListener`.
|
||||
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
|
||||
public struct NIOTSListenerOption: ChannelOption, Equatable {
|
||||
public typealias Value = NWListener?
|
||||
|
||||
public init() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -264,6 +264,16 @@ internal final class NIOTSConnectionChannel: StateManagedNWConnectionChannel {
|
|||
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
|
||||
extension NIOTSConnectionChannel: Channel {
|
||||
func getChannelSpecificOption0<Option>(option: Option) throws -> Option.Value where Option : ChannelOption {
|
||||
if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) {
|
||||
switch option {
|
||||
case is NIOTSChannelOptions.Types.NIOTSConnectionOption:
|
||||
return self.connection as! Option.Value
|
||||
default:
|
||||
// Fallthrough to non-restricted options.
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
switch option {
|
||||
case is NIOTSChannelOptions.Types.NIOTSMultipathOption:
|
||||
return self.multipathServiceType as! Option.Value
|
||||
|
|
|
|||
|
|
@ -270,6 +270,16 @@ extension StateManagedListenerChannel {
|
|||
throw ChannelError.ioOnClosedChannel
|
||||
}
|
||||
|
||||
if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) {
|
||||
switch option {
|
||||
case is NIOTSChannelOptions.Types.NIOTSListenerOption:
|
||||
return self.nwListener as! Option.Value
|
||||
default:
|
||||
// Fallthrough to non-restricted options
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
switch option {
|
||||
case is ChannelOptions.Types.AutoReadOption:
|
||||
return autoRead as! Option.Value
|
||||
|
|
|
|||
|
|
@ -918,5 +918,34 @@ class NIOTSConnectionChannelTests: XCTestCase {
|
|||
XCTAssertNoThrow(try connection.close().wait())
|
||||
XCTAssertNoThrow(try testCompletePromise.futureResult.wait())
|
||||
}
|
||||
|
||||
func testCanExtractTheConnection() throws {
|
||||
guard #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) else {
|
||||
throw XCTSkip("Option not available")
|
||||
}
|
||||
|
||||
let listener = try NIOTSListenerBootstrap(group: self.group)
|
||||
.bind(host: "localhost", port: 0).wait()
|
||||
defer {
|
||||
XCTAssertNoThrow(try listener.close().wait())
|
||||
}
|
||||
|
||||
_ = try NIOTSConnectionBootstrap(group: self.group)
|
||||
.channelInitializer { channel in
|
||||
let conn = try! channel.syncOptions!.getOption(NIOTSChannelOptions.connection)
|
||||
XCTAssertNil(conn)
|
||||
return channel.eventLoop.makeSucceededVoidFuture()
|
||||
}.connect(to: listener.localAddress!).flatMap {
|
||||
$0.getOption(NIOTSChannelOptions.connection)
|
||||
}.always { result in
|
||||
switch result {
|
||||
case .success(let connection):
|
||||
// Make sure we unwrap the connection.
|
||||
XCTAssertNotNil(connection)
|
||||
case .failure(let error):
|
||||
XCTFail("Unexpected error: \(error)")
|
||||
}
|
||||
}.wait()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -204,5 +204,58 @@ final class NIOTSDatagramConnectionChannelTests: XCTestCase {
|
|||
_ = try serverHandle.waitForDatagrams(count: 1)
|
||||
XCTAssertNoThrow(try connection.close().wait())
|
||||
}
|
||||
|
||||
func testCanExtractTheConnection() throws {
|
||||
guard #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) else {
|
||||
throw XCTSkip("Option not available")
|
||||
}
|
||||
|
||||
let listener = try NIOTSDatagramListenerBootstrap(group: self.group)
|
||||
.bind(host: "localhost", port: 0).wait()
|
||||
defer {
|
||||
XCTAssertNoThrow(try listener.close().wait())
|
||||
}
|
||||
|
||||
_ = try NIOTSDatagramBootstrap(group: self.group)
|
||||
.channelInitializer { channel in
|
||||
let conn = try! channel.syncOptions!.getOption(NIOTSChannelOptions.connection)
|
||||
XCTAssertNil(conn)
|
||||
return channel.eventLoop.makeSucceededVoidFuture()
|
||||
}.connect(to: listener.localAddress!).flatMap {
|
||||
$0.getOption(NIOTSChannelOptions.connection)
|
||||
}.always { result in
|
||||
switch result {
|
||||
case .success(let connection):
|
||||
// Make sure we unwrap the connection.
|
||||
XCTAssertNotNil(connection)
|
||||
case .failure(let error):
|
||||
XCTFail("Unexpected error: \(error)")
|
||||
}
|
||||
}.wait()
|
||||
}
|
||||
|
||||
|
||||
func testCanExtractTheListener() throws {
|
||||
guard #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) else {
|
||||
throw XCTSkip("Option not available")
|
||||
}
|
||||
|
||||
let listener = try NIOTSDatagramListenerBootstrap(group: self.group)
|
||||
.serverChannelInitializer { channel in
|
||||
let underlyingListener = try! channel.syncOptions!.getOption(NIOTSChannelOptions.listener)
|
||||
XCTAssertNil(underlyingListener)
|
||||
return channel.eventLoop.makeSucceededVoidFuture()
|
||||
}
|
||||
.bind(host: "localhost", port: 0).wait()
|
||||
defer {
|
||||
XCTAssertNoThrow(try listener.close().wait())
|
||||
}
|
||||
|
||||
let listenerFuture: EventLoopFuture<NWListener?> = listener.getOption(NIOTSChannelOptions.listener)
|
||||
|
||||
try listenerFuture.map { listener in
|
||||
XCTAssertNotNil(listener)
|
||||
}.wait()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -332,5 +332,28 @@ class NIOTSListenerChannelTests: XCTestCase {
|
|||
|
||||
XCTAssertNoThrow(try listener.close().wait())
|
||||
}
|
||||
|
||||
func testCanExtractTheListener() throws {
|
||||
guard #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) else {
|
||||
throw XCTSkip("Listener option not available")
|
||||
}
|
||||
|
||||
let listener = try NIOTSListenerBootstrap(group: self.group)
|
||||
.serverChannelInitializer { channel in
|
||||
let underlyingListener = try! channel.syncOptions!.getOption(NIOTSChannelOptions.listener)
|
||||
XCTAssertNil(underlyingListener)
|
||||
return channel.eventLoop.makeSucceededVoidFuture()
|
||||
}
|
||||
.bind(host: "localhost", port: 0).wait()
|
||||
defer {
|
||||
XCTAssertNoThrow(try listener.close().wait())
|
||||
}
|
||||
|
||||
let listenerFuture: EventLoopFuture<NWListener?> = listener.getOption(NIOTSChannelOptions.listener)
|
||||
|
||||
try listenerFuture.map { listener in
|
||||
XCTAssertNotNil(listener)
|
||||
}.wait()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in New Issue