Skip to content

Instantly share code, notes, and snippets.

@adam-fowler
Last active January 12, 2025 11:55
Show Gist options
  • Save adam-fowler/3456895034489cd0b4396470bbcfbc25 to your computer and use it in GitHub Desktop.
Save adam-fowler/3456895034489cd0b4396470bbcfbc25 to your computer and use it in GitHub Desktop.
NIOAsyncChannel server
import NIOCore
import NIOHTTP1
import NIOPosix
/// Sendable server response that doesn't use ``IOData``
public typealias SendableHTTPServerResponsePart = HTTPPart<HTTPResponseHead, ByteBuffer>
/// Channel to convert HTTPServerResponsePart to the Sendable type HBHTTPServerResponsePart
final class HTTPSendableResponseChannelHandler: ChannelOutboundHandler, RemovableChannelHandler {
typealias OutboundIn = SendableHTTPServerResponsePart
typealias OutboundOut = HTTPServerResponsePart
func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
let part = unwrapOutboundIn(data)
switch part {
case .head(let head):
context.write(self.wrapOutboundOut(.head(head)), promise: promise)
case .body(let buffer):
context.write(self.wrapOutboundOut(.body(.byteBuffer(buffer))), promise: promise)
case .end:
context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: promise)
}
}
}
@available(macOS 14, *)
func server() async throws {
let asyncChannel = try await ServerBootstrap(group: MultiThreadedEventLoopGroup.singleton)
// Specify backlog and enable SO_REUSEADDR for the server itself
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true)
.bind(host: "127.0.0.1", port: 8888, serverBackPressureStrategy: nil) { channel in
return channel.eventLoop.makeCompletedFuture {
try channel.pipeline.syncOperations.configureHTTPServerPipeline()
try channel.pipeline.syncOperations.addHandler(HTTPSendableResponseChannelHandler())
return try NIOAsyncChannel<HTTPServerRequestPart, SendableHTTPServerResponsePart>(
wrappingChannelSynchronously: channel,
configuration: .init()
)
}
}
print("Listening on 127.0.0.1:8888")
await withDiscardingTaskGroup { group in
do {
try await asyncChannel.executeThenClose { inbound in
for try await childChannel in inbound {
group.addTask {
try? await childChannel.executeThenClose { inbound, outbound in
for try await part in inbound {
if case .end = part {
let buffer = ByteBuffer(string: "Hello world!\n")
try await outbound.write(
.head(
.init(
version: .http1_1,
status: .ok,
headers: ["Content-Length": "\(buffer.readableBytes)"]
)
)
)
try await outbound.write(.body(buffer))
try await outbound.write(.end(nil))
break
}
}
}
}
}
}
} catch {
print("ERROR: Waiting on child channel: \(error)")
}
}
}
if #available(macOS 14, *) {
try await server()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment