Last active
January 12, 2025 11:55
-
-
Save adam-fowler/3456895034489cd0b4396470bbcfbc25 to your computer and use it in GitHub Desktop.
NIOAsyncChannel server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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