Files
swift-nio/Sources/NIOChatClient/main.swift
Rafael Cepeda b60d141920 Document when channel.remoteAddress field can be nil (#3456)
Added extra documentation to `channel.remoteAddress` field to capture a
scenario where this field might be `nil`

### Motivation:

It is somewhat common for users to have code like
`channel.remoteAddress!` in their implementation, as it is a reasonable
assumption to think a socket connection will have an associated remote
address. However, in at least one known situation this might not be the
case. When that happens, user's code might crash due to the force unwrap
of the optional field.

### Modifications:

Introduced more documentation to make it clear that users should be
prepared to handle the `nil` scenario.

### Result:

Less frequent mishandling of `channel.remoteAddress`.
2025-12-01 11:09:56 +00:00

98 lines
3.1 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2021 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 NIOCore
import NIOPosix
private final class ChatHandler: ChannelInboundHandler {
public typealias InboundIn = ByteBuffer
public typealias OutboundOut = ByteBuffer
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let buffer = Self.unwrapInboundIn(data)
print(String(buffer: buffer))
}
public func errorCaught(context: ChannelHandlerContext, error: Error) {
print("error: ", error)
// As we are not really interested getting notified on success or failure we just pass nil as promise to
// reduce allocations.
context.close(promise: nil)
}
}
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let bootstrap = ClientBootstrap(group: group)
// Enable SO_REUSEADDR.
.channelOption(.socketOption(.so_reuseaddr), value: 1)
.channelInitializer { channel in
channel.eventLoop.makeCompletedFuture {
try channel.pipeline.syncOperations.addHandler(ChatHandler())
}
}
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
enum ConnectTo {
case ip(host: String, port: Int)
case unixDomainSocket(path: String)
}
let connectTarget: ConnectTo
switch (arg1, arg1.flatMap(Int.init), arg2.flatMap(Int.init)) {
case (.some(let h), _, .some(let p)):
// we got two arguments, let's interpret that as host and port
connectTarget = .ip(host: h, port: p)
case (.some(let portString), .none, _):
// couldn't parse as number, expecting unix domain socket path
connectTarget = .unixDomainSocket(path: portString)
case (_, .some(let p), _):
// only one argument --> port
connectTarget = .ip(host: defaultHost, port: p)
default:
connectTarget = .ip(host: defaultHost, port: defaultPort)
}
let channel = try { () -> Channel in
switch connectTarget {
case .ip(let host, let port):
return try bootstrap.connect(host: host, port: port).wait()
case .unixDomainSocket(let path):
return try bootstrap.connect(unixDomainSocketPath: path).wait()
}
}()
// In production code, you should check if `channel.remoteAddress` is actually
// present, as in rare situations it can be `nil`.
print("ChatClient connected to ChatServer: \(channel.remoteAddress!), happy chatting\n. Press ^D to exit.")
while let line = readLine(strippingNewline: false) {
let buffer = channel.allocator.buffer(string: line)
try! channel.writeAndFlush(buffer).wait()
}
// EOF, close connect
try! channel.close().wait()
print("ChatClient closed")