From ab76a4d01c6a275ea6623726e9339ba664af3f31 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Tue, 19 Nov 2024 23:03:46 -0500 Subject: [PATCH] feat: Initial echo server --- .editorconfig | 7 ++ .gitignore | 8 ++ .swiftformat | 11 +++ .swiftlint.yml | 9 +++ .../xcschemes/swift-nio-echo.xcscheme | 78 +++++++++++++++++++ Package.resolved | 42 ++++++++++ Package.swift | 25 ++++++ Sources/NioEcho/main.swift | 72 +++++++++++++++++ 8 files changed, 252 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .swiftformat create mode 100644 .swiftlint.yml create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/swift-nio-echo.xcscheme create mode 100644 Package.resolved create mode 100644 Package.swift create mode 100644 Sources/NioEcho/main.swift diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7cfbe01 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*.swift] +indent_style = space +indent_size = 2 +tab_width = 2 +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..08f338e --- /dev/null +++ b/.swiftformat @@ -0,0 +1,11 @@ +--self init-only +--indent 2 +--ifdef indent +--trimwhitespace always +--wraparguments before-first +--wrapparameters before-first +--wrapcollections preserve +--wrapconditions after-first +--typeblanklines preserve +--commas inline +--stripunusedargs closure-only diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..8df1f05 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,9 @@ +disabled_rules: + - closing_brace + - fuction_body_length + +included: + - Sources + - Tests + +ignore_multiline_statement_conditions: true diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/swift-nio-echo.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/swift-nio-echo.xcscheme new file mode 100644 index 0000000..8ff4223 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/swift-nio-echo.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..4d48fbd --- /dev/null +++ b/Package.resolved @@ -0,0 +1,42 @@ +{ + "originHash" : "20f1c21ae87f9bea31b5f7dfef662b3911d92dc048f13b9fb09d350230df3c34", + "pins" : [ + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", + "version" : "1.1.4" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "914081701062b11e3bb9e21accc379822621995e", + "version" : "2.76.1" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "c8a44d836fe7913603e246acab7c528c2e780168", + "version" : "1.4.0" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..c79d776 --- /dev/null +++ b/Package.swift @@ -0,0 +1,25 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "swift-nio-echo", + platforms: [ + .macOS(.v15) + ], + products: [ + .executable(name: "nio-echo", targets: ["NIOEcho"]) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-nio.git", from: "2.76.1") + ], + targets: [ + .executableTarget( + name: "NIOEcho", + dependencies: [ + .product(name: "NIO", package: "swift-nio") + ] + ) + ] +) diff --git a/Sources/NioEcho/main.swift b/Sources/NioEcho/main.swift new file mode 100644 index 0000000..e0d162d --- /dev/null +++ b/Sources/NioEcho/main.swift @@ -0,0 +1,72 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book +import NIOCore +import NIOPosix + +private final class EchoHandler: ChannelInboundHandler { + typealias InboundIn = ByteBuffer + typealias OutboundOut = BackPressureHandler + + func channelRead(context: ChannelHandlerContext, data: NIOAny) { + context.write(data, promise: nil) + } + + func channelReadComplete(context: ChannelHandlerContext) { + context.flush() + } + + func errorCaught(context: ChannelHandlerContext, error: any Error) { + print("error: \(error)") + context.close(promise: nil) + } +} + +let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) +let bootstrap = ServerBootstrap(group: group) + .serverChannelOption(.backlog, value: 256) + .serverChannelOption(.socketOption(.so_reuseaddr), value: 1) + // set the handlers that are applied. + .childChannelInitializer { channel in + channel.eventLoop.makeCompletedFuture { + try channel.pipeline.syncOperations.addHandler(BackPressureHandler()) + try channel.pipeline.syncOperations.addHandler(EchoHandler()) + } + } + .childChannelOption(.socketOption(.so_reuseaddr), value: 1) + .childChannelOption(.maxMessagesPerRead, value: 16) + .childChannelOption(.recvAllocator, value: AdaptiveRecvByteBufferAllocator()) + +defer { try? group.syncShutdownGracefully() } + +// First argument is the program path +let arguments = CommandLine.arguments +let arg1 = arguments.dropFirst().first +let arg2 = arguments.dropFirst(2).first + +let defaultHost = "::1" +let defaultPort = 9999 + +struct BindTo { + let host: String + let port: Int +} + +let bindTarget: BindTo +switch (arg1, arg1.flatMap(Int.init), arg2.flatMap(Int.init)) { +case let (_, .some(port), _): + bindTarget = .init(host: defaultHost, port: port) +case let (.some(host), _, .some(port)): + bindTarget = .init(host: host, port: port) +case let (.some(host), .none, .none): + bindTarget = .init(host: host, port: defaultPort) +default: + bindTarget = .init(host: defaultHost, port: defaultPort) +} + +let channel = try bootstrap.bind(host: bindTarget.host, port: bindTarget.port).wait() + +print("Server started and listening on: \(channel.localAddress!)") + +try channel.closeFuture.wait() + +print("Server closed!")