diff --git a/connection-limiter/.gitignore b/connection-limiter/.gitignore new file mode 100644 index 0000000..bb460e7 --- /dev/null +++ b/connection-limiter/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/connection-limiter/Package.swift b/connection-limiter/Package.swift new file mode 100644 index 0000000..54ec2b3 --- /dev/null +++ b/connection-limiter/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version:5.2 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "connection-limiter", + dependencies: [ + .package(name: "swift-nio", url: "https://github.com/apple/swift-nio", from: "2.16.0") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages which this package depends on. + .target( + name: "connection-limiter", + dependencies: [ + .product(name: "NIO", package: "swift-nio") + ]), + .testTarget( + name: "connection-limiterTests", + dependencies: ["connection-limiter"]), + ] +) diff --git a/connection-limiter/README.md b/connection-limiter/README.md new file mode 100644 index 0000000..5064d28 --- /dev/null +++ b/connection-limiter/README.md @@ -0,0 +1,3 @@ +# connection-limiter + +A description of this package. diff --git a/connection-limiter/Sources/connection-limiter/main.swift b/connection-limiter/Sources/connection-limiter/main.swift new file mode 100644 index 0000000..bda58e7 --- /dev/null +++ b/connection-limiter/Sources/connection-limiter/main.swift @@ -0,0 +1,64 @@ + +import NIO + +class LimitHandler: ChannelDuplexHandler { + + typealias OutboundIn = Channel + typealias InboundIn = Channel + + let connectionLimit: Int + var currentConnections = 0 + + init(connectionLimit: Int) { + self.connectionLimit = connectionLimit + } + + func channelRead(context: ChannelHandlerContext, data: NIOAny) { + let channel = self.unwrapInboundIn(data) + context.fireChannelRead(data) + self.currentConnections += 1 + channel.closeFuture.whenSuccess { + context.read() + } + } + + func read(context: ChannelHandlerContext) { + guard self.currentConnections < self.connectionLimit else { + return + } + context.read() + } + +} + +class EchoChannelHandler: ChannelInboundHandler { + + typealias InboundIn = ByteBuffer + typealias InboundOut = ByteBuffer + + func channelRead(context: ChannelHandlerContext, data: NIOAny) { + var input = self.unwrapInboundIn(data) + var buffer = context.channel.allocator.buffer(capacity: input.readableBytes + 6) + buffer.writeString("Echo: ") + buffer.writeBuffer(&input) + let output = self.wrapInboundOut(buffer) + context.writeAndFlush(output, promise: nil) + } + +} + +let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) +try ServerBootstrap(group: group) +.serverChannelInitializer({ (channel) -> EventLoopFuture in + channel.pipeline.addHandler(LimitHandler(connectionLimit: 1)) +}) +.serverChannelOption(ChannelOptions.maxMessagesPerRead, value: 1) +.serverChannelOption(ChannelOptions.backlog, value: 1) +.childChannelInitializer { (channel) -> EventLoopFuture in + channel.pipeline.addHandler(EchoChannelHandler()) +} +.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) +.bind(host: "127.0.0.1", port: 4321) +.wait() +.closeFuture +.wait() diff --git a/connection-limiter/Tests/connection-limiterTests/connection_limiterTests.swift b/connection-limiter/Tests/connection-limiterTests/connection_limiterTests.swift new file mode 100644 index 0000000..676e5ed --- /dev/null +++ b/connection-limiter/Tests/connection-limiterTests/connection_limiterTests.swift @@ -0,0 +1,6 @@ +import XCTest +import class Foundation.Bundle + +final class connection_limiterTests: XCTestCase { + +}