819 lines
32 KiB
Swift
819 lines
32 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftNIO open source project
|
|
//
|
|
// Copyright (c) 2017-2018 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 XCTest
|
|
import NIOConcurrencyHelpers
|
|
import Dispatch
|
|
@testable import NIO
|
|
|
|
class EchoServerClientTest : XCTestCase {
|
|
func testEcho() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let numBytes = 16 * 1024
|
|
let countingHandler = ByteCountingHandler(numBytes: numBytes, promise: group.next().makePromise())
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
|
|
.childChannelInitializer { channel in
|
|
channel.pipeline.addHandler(countingHandler)
|
|
}.bind(host: "127.0.0.1", port: 0).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.connect(to: serverChannel.localAddress!)
|
|
.wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try clientChannel.syncCloseAcceptingAlreadyClosed())
|
|
}
|
|
|
|
var buffer = clientChannel.allocator.buffer(capacity: numBytes)
|
|
|
|
for i in 0..<numBytes {
|
|
buffer.writeInteger(UInt8(i % 256))
|
|
}
|
|
|
|
try clientChannel.writeAndFlush(NIOAny(buffer)).wait()
|
|
|
|
try countingHandler.assertReceived(buffer: buffer)
|
|
}
|
|
|
|
func testLotsOfUnflushedWrites() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.childChannelInitializer { channel in
|
|
channel.pipeline.addHandler(WriteALotHandler())
|
|
}.bind(host: "127.0.0.1", port: 0).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let promise = group.next().makePromise(of: ByteBuffer.self)
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.channelInitializer { channel in
|
|
channel.pipeline.addHandler(WriteOnConnectHandler(toWrite: "X")).flatMap { v2 in
|
|
channel.pipeline.addHandler(ByteCountingHandler(numBytes: 10000, promise: promise))
|
|
}
|
|
}
|
|
.connect(to: serverChannel.localAddress!).wait())
|
|
defer {
|
|
_ = clientChannel.close()
|
|
}
|
|
|
|
let bytes = try promise.futureResult.wait()
|
|
let expected = String(decoding: Array(repeating: "X".utf8.first!, count: 10000), as: Unicode.UTF8.self)
|
|
XCTAssertEqual(expected, bytes.getString(at: bytes.readerIndex, length: bytes.readableBytes))
|
|
}
|
|
|
|
func testEchoUnixDomainSocket() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
try withTemporaryUnixDomainSocketPathName { udsPath in
|
|
let numBytes = 16 * 1024
|
|
let countingHandler = ByteCountingHandler(numBytes: numBytes, promise: group.next().makePromise())
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
|
|
// Set the handlers that are appled to the accepted Channels
|
|
.childChannelInitializer { channel in
|
|
// Ensure we don't read faster then we can write by adding the BackPressureHandler into the pipeline.
|
|
channel.pipeline.addHandler(countingHandler)
|
|
}.bind(unixDomainSocketPath: udsPath).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.connect(to: serverChannel.localAddress!)
|
|
.wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try clientChannel.syncCloseAcceptingAlreadyClosed())
|
|
}
|
|
|
|
var buffer = clientChannel.allocator.buffer(capacity: numBytes)
|
|
|
|
for i in 0..<numBytes {
|
|
buffer.writeInteger(UInt8(i % 256))
|
|
}
|
|
|
|
XCTAssertNoThrow(try clientChannel.writeAndFlush(NIOAny(buffer)).wait())
|
|
|
|
XCTAssertNoThrow(try countingHandler.assertReceived(buffer: buffer))
|
|
}
|
|
}
|
|
|
|
func testConnectUnixDomainSocket() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
try withTemporaryUnixDomainSocketPathName { udsPath in
|
|
let numBytes = 16 * 1024
|
|
let countingHandler = ByteCountingHandler(numBytes: numBytes, promise: group.next().makePromise())
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
|
|
// Set the handlers that are appled to the accepted Channels
|
|
.childChannelInitializer { channel in
|
|
channel.pipeline.addHandler(countingHandler)
|
|
}.bind(unixDomainSocketPath: udsPath).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.connect(unixDomainSocketPath: udsPath)
|
|
.wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try clientChannel.close().wait())
|
|
}
|
|
|
|
var buffer = clientChannel.allocator.buffer(capacity: numBytes)
|
|
|
|
for i in 0..<numBytes {
|
|
buffer.writeInteger(UInt8(i % 256))
|
|
}
|
|
|
|
try clientChannel.writeAndFlush(NIOAny(buffer)).wait()
|
|
|
|
try countingHandler.assertReceived(buffer: buffer)
|
|
}
|
|
}
|
|
|
|
func testChannelActiveOnConnect() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let handler = ChannelActiveHandler()
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.bind(host: "127.0.0.1", port: 0).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.channelInitializer { $0.pipeline.addHandler(handler) }
|
|
.connect(to: serverChannel.localAddress!).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try clientChannel.syncCloseAcceptingAlreadyClosed())
|
|
}
|
|
|
|
XCTAssertNoThrow(try handler.assertChannelActiveFired())
|
|
}
|
|
|
|
func testWriteThenRead() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.childChannelInitializer { channel in
|
|
channel.pipeline.addHandler(EchoServer())
|
|
}.bind(host: "127.0.0.1", port: 0).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let numBytes = 16 * 1024
|
|
let countingHandler = ByteCountingHandler(numBytes: numBytes, promise: group.next().makePromise())
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.channelInitializer { $0.pipeline.addHandler(countingHandler) }
|
|
.connect(to: serverChannel.localAddress!).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try clientChannel.syncCloseAcceptingAlreadyClosed())
|
|
}
|
|
|
|
var buffer = clientChannel.allocator.buffer(capacity: numBytes)
|
|
for i in 0..<numBytes {
|
|
buffer.writeInteger(UInt8(i % 256))
|
|
}
|
|
XCTAssertNoThrow(try clientChannel.writeAndFlush(NIOAny(buffer)).wait())
|
|
|
|
XCTAssertNoThrow(try countingHandler.assertReceived(buffer: buffer))
|
|
}
|
|
|
|
private final class ChannelActiveHandler: ChannelInboundHandler {
|
|
typealias InboundIn = ByteBuffer
|
|
|
|
private var promise: EventLoopPromise<Void>! = nil
|
|
|
|
func handlerAdded(context: ChannelHandlerContext) {
|
|
promise = context.channel.eventLoop.makePromise()
|
|
}
|
|
|
|
func channelActive(context: ChannelHandlerContext) {
|
|
promise.succeed(())
|
|
context.fireChannelActive()
|
|
}
|
|
|
|
func assertChannelActiveFired() throws {
|
|
try promise.futureResult.wait()
|
|
}
|
|
}
|
|
|
|
private final class EchoServer: ChannelInboundHandler {
|
|
typealias InboundIn = ByteBuffer
|
|
typealias OutboundOut = ByteBuffer
|
|
|
|
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
|
context.write(data, promise: nil)
|
|
}
|
|
|
|
func channelReadComplete(context: ChannelHandlerContext) {
|
|
context.flush()
|
|
}
|
|
}
|
|
|
|
private final class EchoAndEchoAgainAfterSomeTimeServer: ChannelInboundHandler {
|
|
typealias InboundIn = ByteBuffer
|
|
typealias OutboundOut = ByteBuffer
|
|
|
|
private let timeAmount: TimeAmount
|
|
private let group = DispatchGroup()
|
|
private var numberOfReads: Int = 0
|
|
private let calloutQ = DispatchQueue(label: "EchoAndEchoAgainAfterSomeTimeServer callback queue")
|
|
|
|
public init(time: TimeAmount, secondWriteDoneHandler: @escaping () -> Void) {
|
|
self.timeAmount = time
|
|
self.group.enter()
|
|
self.group.notify(queue: self.calloutQ) {
|
|
secondWriteDoneHandler()
|
|
}
|
|
}
|
|
|
|
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
|
self.numberOfReads += 1
|
|
precondition(self.numberOfReads == 1, "\(self) is only ever allowed to read once")
|
|
_ = context.eventLoop.scheduleTask(in: self.timeAmount) {
|
|
context.writeAndFlush(data, promise: nil)
|
|
self.group.leave()
|
|
}.futureResult.recover { e in
|
|
XCTFail("we failed to schedule the task: \(e)")
|
|
self.group.leave()
|
|
}
|
|
context.writeAndFlush(data, promise: nil)
|
|
}
|
|
}
|
|
|
|
private final class WriteALotHandler: ChannelInboundHandler {
|
|
typealias InboundIn = ByteBuffer
|
|
typealias OutboundOut = ByteBuffer
|
|
|
|
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
|
for _ in 0..<10000 {
|
|
context.write(data, promise: nil)
|
|
}
|
|
}
|
|
|
|
func channelReadComplete(context: ChannelHandlerContext) {
|
|
context.flush()
|
|
}
|
|
}
|
|
|
|
private final class CloseInInActiveAndUnregisteredChannelHandler: ChannelInboundHandler {
|
|
typealias InboundIn = Never
|
|
let alreadyClosedInChannelInactive = NIOAtomic<Bool>.makeAtomic(value: false)
|
|
let alreadyClosedInChannelUnregistered = NIOAtomic<Bool>.makeAtomic(value: false)
|
|
let channelUnregisteredPromise: EventLoopPromise<Void>
|
|
let channelInactivePromise: EventLoopPromise<Void>
|
|
|
|
public init(channelUnregisteredPromise: EventLoopPromise<Void>,
|
|
channelInactivePromise: EventLoopPromise<Void>) {
|
|
self.channelUnregisteredPromise = channelUnregisteredPromise
|
|
self.channelInactivePromise = channelInactivePromise
|
|
}
|
|
|
|
public func channelActive(context: ChannelHandlerContext) {
|
|
context.close().whenFailure { error in
|
|
XCTFail("bad, initial close failed (\(error))")
|
|
}
|
|
}
|
|
|
|
public func channelInactive(context: ChannelHandlerContext) {
|
|
if alreadyClosedInChannelInactive.compareAndExchange(expected: false, desired: true) {
|
|
XCTAssertFalse(self.channelUnregisteredPromise.futureResult.isFulfilled,
|
|
"channelInactive should fire before channelUnregistered")
|
|
context.close().map {
|
|
XCTFail("unexpected success")
|
|
}.recover { err in
|
|
switch err {
|
|
case ChannelError.alreadyClosed:
|
|
// OK
|
|
()
|
|
default:
|
|
XCTFail("unexpected error: \(err)")
|
|
}
|
|
}.whenComplete { (_: Result<Void, Error>) in
|
|
self.channelInactivePromise.succeed(())
|
|
}
|
|
}
|
|
}
|
|
|
|
public func channelUnregistered(context: ChannelHandlerContext) {
|
|
if alreadyClosedInChannelUnregistered.compareAndExchange(expected: false, desired: true) {
|
|
XCTAssertTrue(self.channelInactivePromise.futureResult.isFulfilled,
|
|
"when channelUnregister fires, channelInactive should already have fired")
|
|
context.close().map {
|
|
XCTFail("unexpected success")
|
|
}.recover { err in
|
|
switch err {
|
|
case ChannelError.alreadyClosed:
|
|
// OK
|
|
()
|
|
default:
|
|
XCTFail("unexpected error: \(err)")
|
|
}
|
|
}.whenComplete { (_: Result<Void, Error>) in
|
|
self.channelUnregisteredPromise.succeed(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A channel handler that calls write on connect.
|
|
private class WriteOnConnectHandler: ChannelInboundHandler {
|
|
typealias InboundIn = ByteBuffer
|
|
|
|
private let toWrite: String
|
|
|
|
init(toWrite: String) {
|
|
self.toWrite = toWrite
|
|
}
|
|
|
|
func channelActive(context: ChannelHandlerContext) {
|
|
var dataToWrite = context.channel.allocator.buffer(capacity: toWrite.utf8.count)
|
|
dataToWrite.writeString(toWrite)
|
|
context.writeAndFlush(NIOAny(dataToWrite), promise: nil)
|
|
context.fireChannelActive()
|
|
}
|
|
}
|
|
|
|
func testCloseInInactive() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let inactivePromise = group.next().makePromise() as EventLoopPromise<Void>
|
|
let unregistredPromise = group.next().makePromise() as EventLoopPromise<Void>
|
|
let handler = CloseInInActiveAndUnregisteredChannelHandler(channelUnregisteredPromise: unregistredPromise,
|
|
channelInactivePromise: inactivePromise)
|
|
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
|
|
.childChannelInitializer { channel in
|
|
channel.pipeline.addHandler(handler)
|
|
}.bind(host: "127.0.0.1", port: 0).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.connect(to: serverChannel.localAddress!)
|
|
.wait())
|
|
|
|
defer {
|
|
_ = clientChannel.close()
|
|
}
|
|
|
|
_ = try inactivePromise.futureResult.and(unregistredPromise.futureResult).wait()
|
|
|
|
XCTAssertTrue(handler.alreadyClosedInChannelInactive.load())
|
|
XCTAssertTrue(handler.alreadyClosedInChannelUnregistered.load())
|
|
}
|
|
|
|
func testFlushOnEmpty() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let writingBytes = "hello"
|
|
let bytesReceivedPromise = group.next().makePromise(of: ByteBuffer.self)
|
|
let byteCountingHandler = ByteCountingHandler(numBytes: writingBytes.utf8.count, promise: bytesReceivedPromise)
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.childChannelInitializer { channel in
|
|
// When we've received all the bytes we know the connection is up. Remove the handler.
|
|
_ = bytesReceivedPromise.futureResult.flatMap { (_: ByteBuffer) in
|
|
channel.pipeline.removeHandler(byteCountingHandler)
|
|
}
|
|
|
|
return channel.pipeline.addHandler(byteCountingHandler)
|
|
}.bind(host: "127.0.0.1", port: 0).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.connect(to: serverChannel.localAddress!)
|
|
.wait())
|
|
defer {
|
|
_ = clientChannel.close()
|
|
}
|
|
|
|
// First we confirm that the channel really is up by sending in the appropriate number of bytes.
|
|
var bytesToWrite = clientChannel.allocator.buffer(capacity: writingBytes.utf8.count)
|
|
bytesToWrite.writeString(writingBytes)
|
|
let lastWriteFuture = clientChannel.writeAndFlush(NIOAny(bytesToWrite))
|
|
|
|
// When we've received all the bytes we know the connection is up.
|
|
_ = try bytesReceivedPromise.futureResult.wait()
|
|
|
|
// Now, with an empty write pipeline, we want to flush. This should complete immediately and without error.
|
|
clientChannel.flush()
|
|
lastWriteFuture.whenFailure { err in
|
|
XCTFail("\(err)")
|
|
}
|
|
try lastWriteFuture.wait()
|
|
}
|
|
|
|
func testWriteOnConnect() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.childChannelInitializer { channel in
|
|
channel.pipeline.addHandler(EchoServer())
|
|
}.bind(host: "127.0.0.1", port: 0).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let stringToWrite = "hello"
|
|
let promise = group.next().makePromise(of: ByteBuffer.self)
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.channelInitializer { channel in
|
|
channel.pipeline.addHandler(WriteOnConnectHandler(toWrite: stringToWrite)).flatMap {
|
|
channel.pipeline.addHandler(ByteCountingHandler(numBytes: stringToWrite.utf8.count, promise: promise))
|
|
}
|
|
}
|
|
.connect(to: serverChannel.localAddress!).wait())
|
|
defer {
|
|
_ = clientChannel.close()
|
|
}
|
|
|
|
let bytes = try promise.futureResult.wait()
|
|
XCTAssertEqual(bytes.getString(at: bytes.readerIndex, length: bytes.readableBytes), stringToWrite)
|
|
}
|
|
|
|
func testWriteOnAccept() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let stringToWrite = "hello"
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.childChannelInitializer { channel in
|
|
channel.pipeline.addHandler(WriteOnConnectHandler(toWrite: stringToWrite))
|
|
}.bind(host: "127.0.0.1", port: 0).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let promise = group.next().makePromise(of: ByteBuffer.self)
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.channelInitializer { channel in
|
|
channel.pipeline.addHandler(ByteCountingHandler(numBytes: stringToWrite.utf8.count, promise: promise))
|
|
}
|
|
.connect(to: serverChannel.localAddress!).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try clientChannel.close().wait())
|
|
}
|
|
|
|
let bytes = try assertNoThrowWithValue(promise.futureResult.wait())
|
|
XCTAssertEqual(bytes.getString(at: bytes.readerIndex, length: bytes.readableBytes), stringToWrite)
|
|
}
|
|
|
|
func testWriteAfterChannelIsDead() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
let dpGroup = DispatchGroup()
|
|
|
|
dpGroup.enter()
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.childChannelInitializer { channel in
|
|
channel.pipeline.addHandler(EchoAndEchoAgainAfterSomeTimeServer(time: .seconds(1), secondWriteDoneHandler: {
|
|
dpGroup.leave()
|
|
}))
|
|
}.bind(host: "127.0.0.1", port: 0).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let str = "hi there"
|
|
let countingHandler = ByteCountingHandler(numBytes: str.utf8.count, promise: group.next().makePromise())
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.channelInitializer { $0.pipeline.addHandler(countingHandler) }
|
|
.connect(to: serverChannel.localAddress!).wait())
|
|
|
|
var buffer = clientChannel.allocator.buffer(capacity: str.utf8.count)
|
|
buffer.writeString(str)
|
|
try clientChannel.writeAndFlush(NIOAny(buffer)).wait()
|
|
|
|
try countingHandler.assertReceived(buffer: buffer)
|
|
|
|
/* close the client channel so that the second write should fail */
|
|
try clientChannel.close().wait()
|
|
|
|
dpGroup.wait() /* make sure we stick around until the second write has happened */
|
|
}
|
|
|
|
func testPendingReadProcessedAfterWriteError() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let dpGroup = DispatchGroup()
|
|
dpGroup.enter()
|
|
|
|
let str = "hi there"
|
|
|
|
let countingHandler = ByteCountingHandler(numBytes: str.utf8.count * 4, promise: group.next().makePromise())
|
|
|
|
class WriteHandler : ChannelInboundHandler {
|
|
typealias InboundIn = ByteBuffer
|
|
|
|
private var writeFailed = false
|
|
|
|
func channelActive(context: ChannelHandlerContext) {
|
|
var buffer = context.channel.allocator.buffer(capacity: 4)
|
|
buffer.writeString("test")
|
|
writeUntilFailed(context, buffer)
|
|
}
|
|
|
|
private func writeUntilFailed(_ context: ChannelHandlerContext, _ buffer: ByteBuffer) {
|
|
context.writeAndFlush(NIOAny(buffer)).whenSuccess {
|
|
context.eventLoop.execute {
|
|
self.writeUntilFailed(context, buffer)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class WriteWhenActiveHandler: ChannelInboundHandler {
|
|
typealias InboundIn = ByteBuffer
|
|
let str: String
|
|
let dpGroup: DispatchGroup
|
|
|
|
init(_ str: String, _ dpGroup: DispatchGroup) {
|
|
self.str = str
|
|
self.dpGroup = dpGroup
|
|
}
|
|
|
|
func channelActive(context: ChannelHandlerContext) {
|
|
context.fireChannelActive()
|
|
var buffer = context.channel.allocator.buffer(capacity: str.utf8.count)
|
|
buffer.writeString(str)
|
|
|
|
// write it four times and then close the connect.
|
|
context.writeAndFlush(NIOAny(buffer)).flatMap {
|
|
context.writeAndFlush(NIOAny(buffer))
|
|
}.flatMap {
|
|
context.writeAndFlush(NIOAny(buffer))
|
|
}.flatMap {
|
|
context.writeAndFlush(NIOAny(buffer))
|
|
}.flatMap {
|
|
context.close()
|
|
}.whenComplete { (_: Result<Void, Error>) in
|
|
self.dpGroup.leave()
|
|
}
|
|
}
|
|
}
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.childChannelInitializer { channel in
|
|
channel.pipeline.addHandler(WriteWhenActiveHandler(str, dpGroup))
|
|
}.bind(host: "127.0.0.1", port: 0).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.syncCloseAcceptingAlreadyClosed())
|
|
}
|
|
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
// We will only start reading once we wrote all data on the accepted channel.
|
|
//.channelOption(ChannelOptions.autoRead, value: false)
|
|
.channelOption(ChannelOptions.recvAllocator, value: FixedSizeRecvByteBufferAllocator(capacity: 2))
|
|
.channelInitializer { channel in
|
|
channel.pipeline.addHandler(WriteHandler()).flatMap {
|
|
channel.pipeline.addHandler(countingHandler)
|
|
}
|
|
}.connect(to: serverChannel.localAddress!).wait())
|
|
defer {
|
|
XCTAssertNoThrow(try clientChannel.syncCloseAcceptingAlreadyClosed())
|
|
}
|
|
dpGroup.wait()
|
|
|
|
var completeBuffer = clientChannel.allocator.buffer(capacity: str.utf8.count * 4)
|
|
completeBuffer.writeString(str)
|
|
completeBuffer.writeString(str)
|
|
completeBuffer.writeString(str)
|
|
completeBuffer.writeString(str)
|
|
|
|
try countingHandler.assertReceived(buffer: completeBuffer)
|
|
}
|
|
|
|
func testChannelErrorEOFNotFiredThroughPipeline() throws {
|
|
|
|
class ErrorHandler : ChannelInboundHandler {
|
|
typealias InboundIn = ByteBuffer
|
|
|
|
private let promise: EventLoopPromise<Void>
|
|
|
|
init(_ promise: EventLoopPromise<Void>) {
|
|
self.promise = promise
|
|
}
|
|
|
|
public func errorCaught(context: ChannelHandlerContext, error: Error) {
|
|
if let err = error as? ChannelError {
|
|
XCTAssertNotEqual(ChannelError.eof, err)
|
|
}
|
|
}
|
|
|
|
public func channelInactive(context: ChannelHandlerContext) {
|
|
self.promise.succeed(())
|
|
}
|
|
}
|
|
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let promise = group.next().makePromise(of: Void.self)
|
|
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.childChannelInitializer { channel in
|
|
channel.pipeline.addHandler(ErrorHandler(promise))
|
|
}.bind(host: "127.0.0.1", port: 0).wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.close().wait())
|
|
}
|
|
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.connect(to: serverChannel.localAddress!).wait())
|
|
XCTAssertNoThrow(try clientChannel.close().wait())
|
|
|
|
XCTAssertNoThrow(try promise.futureResult.wait())
|
|
}
|
|
|
|
func testPortNumbers() throws {
|
|
var atLeastOneSucceeded = false
|
|
for host in ["127.0.0.1", "::1"] {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
let acceptedRemotePort: NIOAtomic<Int> = .makeAtomic(value: -1)
|
|
let acceptedLocalPort: NIOAtomic<Int> = .makeAtomic(value: -2)
|
|
let sem = DispatchSemaphore(value: 0)
|
|
|
|
let serverChannel: Channel
|
|
do {
|
|
serverChannel = try ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.childChannelInitializer { channel in
|
|
acceptedRemotePort.store(channel.remoteAddress?.port ?? -3)
|
|
acceptedLocalPort.store(channel.localAddress?.port ?? -4)
|
|
sem.signal()
|
|
return channel.eventLoop.makeSucceededFuture(())
|
|
}.bind(host: host, port: 0).wait()
|
|
} catch let e as SocketAddressError {
|
|
if case .unknown(host, port: 0) = e {
|
|
/* this can happen if the system isn't configured for both IPv4 and IPv6 */
|
|
continue
|
|
} else {
|
|
/* nope, that's a different socket error */
|
|
XCTFail("unexpected SocketAddressError: \(e)")
|
|
break
|
|
}
|
|
} catch {
|
|
/* other unknown error */
|
|
XCTFail("unexpected error: \(error)")
|
|
break
|
|
}
|
|
atLeastOneSucceeded = true
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.syncCloseAcceptingAlreadyClosed())
|
|
}
|
|
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group).connect(host: host,
|
|
port: Int(serverChannel.localAddress!.port!)).wait())
|
|
defer {
|
|
XCTAssertNoThrow(try clientChannel.syncCloseAcceptingAlreadyClosed())
|
|
}
|
|
sem.wait()
|
|
XCTAssertEqual(serverChannel.localAddress?.port, clientChannel.remoteAddress?.port)
|
|
XCTAssertEqual(acceptedLocalPort.load(), clientChannel.remoteAddress?.port ?? -5)
|
|
XCTAssertEqual(acceptedRemotePort.load(), clientChannel.localAddress?.port ?? -6)
|
|
}
|
|
XCTAssertTrue(atLeastOneSucceeded)
|
|
}
|
|
|
|
func testConnectingToIPv4And6ButServerOnlyWaitsOnIPv4() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let numBytes = 16 * 1024
|
|
let promise = group.next().makePromise(of: ByteBuffer.self)
|
|
let countingHandler = ByteCountingHandler(numBytes: numBytes, promise: promise)
|
|
|
|
// we're binding to IPv4 only
|
|
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.childChannelInitializer { channel in
|
|
channel.pipeline.addHandler(countingHandler)
|
|
}
|
|
.bind(host: "127.0.0.1", port: 0)
|
|
.wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try serverChannel.syncCloseAcceptingAlreadyClosed())
|
|
}
|
|
|
|
// but we're trying to connect to (depending on the system configuration and resolver) IPv4 and IPv6
|
|
let clientChannel = try assertNoThrowWithValue(ClientBootstrap(group: group)
|
|
.connect(host: "localhost", port: Int(serverChannel.localAddress!.port!))
|
|
.flatMapError {
|
|
promise.fail($0)
|
|
return group.next().makeFailedFuture($0)
|
|
}
|
|
.wait())
|
|
|
|
defer {
|
|
XCTAssertNoThrow(try clientChannel.syncCloseAcceptingAlreadyClosed())
|
|
}
|
|
|
|
var buffer = clientChannel.allocator.buffer(capacity: numBytes)
|
|
|
|
for i in 0..<numBytes {
|
|
buffer.writeInteger(UInt8(i % 256))
|
|
}
|
|
|
|
try clientChannel.writeAndFlush(NIOAny(buffer)).wait()
|
|
|
|
try countingHandler.assertReceived(buffer: buffer)
|
|
}
|
|
}
|