Coverage Report

Created: 2025-07-23 06:54

/src/swift-nio/Sources/NIOPosix/ServerSocket.swift
Line
Count
Source (jump to first uncovered line)
1
//===----------------------------------------------------------------------===//
2
//
3
// This source file is part of the SwiftNIO open source project
4
//
5
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
6
// Licensed under Apache License v2.0
7
//
8
// See LICENSE.txt for license information
9
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10
//
11
// SPDX-License-Identifier: Apache-2.0
12
//
13
//===----------------------------------------------------------------------===//
14
15
import NIOCore
16
17
/// A server socket that can accept new connections.
18
class ServerSocket: BaseSocket, ServerSocketProtocol {
19
    typealias SocketType = ServerSocket
20
    private let cleanupOnClose: Bool
21
22
    public final class func bootstrap(
23
        protocolFamily: NIOBSDSocket.ProtocolFamily,
24
        host: String,
25
        port: Int
26
0
    ) throws -> ServerSocket {
27
0
        let socket = try ServerSocket(protocolFamily: protocolFamily)
28
0
        try socket.bind(to: SocketAddress.makeAddressResolvingHost(host, port: port))
29
0
        try socket.listen()
30
0
        return socket
31
0
    }
32
33
    /// Create a new instance.
34
    ///
35
    /// - Parameters:
36
    ///   - protocolFamily: The protocol family to use (usually `AF_INET6` or `AF_INET`).
37
    ///   - protocolSubtype: The subtype of the protocol, corresponding to the `protocol`
38
    ///         argument to the socket syscall. Defaults to 0.
39
    ///   - setNonBlocking: Set non-blocking mode on the socket.
40
    /// - Throws: An `IOError` if creation of the socket failed.
41
    init(
42
        protocolFamily: NIOBSDSocket.ProtocolFamily,
43
        protocolSubtype: NIOBSDSocket.ProtocolSubtype = .default,
44
        setNonBlocking: Bool = false
45
0
    ) throws {
46
0
        let sock = try BaseSocket.makeSocket(
47
0
            protocolFamily: protocolFamily,
48
0
            type: .stream,
49
0
            protocolSubtype: protocolSubtype,
50
0
            setNonBlocking: setNonBlocking
51
0
        )
52
0
        switch protocolFamily {
53
0
        case .unix:
54
0
            cleanupOnClose = true
55
0
        default:
56
0
            cleanupOnClose = false
57
0
        }
58
0
        try super.init(socket: sock)
59
0
    }
60
61
    /// Create a new instance.
62
    ///
63
    /// - Parameters:
64
    ///   - descriptor: The _Unix file descriptor_ representing the bound socket.
65
    ///   - setNonBlocking: Set non-blocking mode on the socket.
66
    /// - Throws: An `IOError` if socket is invalid.
67
    #if !os(Windows)
68
    @available(*, deprecated, renamed: "init(socket:setNonBlocking:)")
69
0
    convenience init(descriptor: CInt, setNonBlocking: Bool = false) throws {
70
0
        try self.init(socket: descriptor, setNonBlocking: setNonBlocking)
71
0
    }
72
    #endif
73
74
    /// Create a new instance.
75
    ///
76
    /// - Parameters:
77
    ///   - descriptor: The _Unix file descriptor_ representing the bound socket.
78
    ///   - setNonBlocking: Set non-blocking mode on the socket.
79
    /// - Throws: An `IOError` if socket is invalid.
80
0
    init(socket: NIOBSDSocket.Handle, setNonBlocking: Bool = false) throws {
81
0
        cleanupOnClose = false  // socket already bound, owner must clean up
82
0
        try super.init(socket: socket)
83
0
        if setNonBlocking {
84
0
            try self.setNonBlocking()
85
0
        }
86
0
    }
87
88
    /// Start to listen for new connections.
89
    ///
90
    /// - Parameters:
91
    ///   - backlog: The backlog to use.
92
    /// - Throws: An `IOError` if creation of the socket failed.
93
0
    func listen(backlog: Int32 = 128) throws {
94
0
        try withUnsafeHandle {
95
0
            _ = try NIOBSDSocket.listen(socket: $0, backlog: backlog)
96
0
        }
97
0
    }
98
99
    /// Accept a new connection
100
    ///
101
    /// - Parameters:
102
    ///   - setNonBlocking: set non-blocking mode on the returned `Socket`. On Linux this will use accept4 with SOCK_NONBLOCK to save a system call.
103
    /// - Returns: A `Socket` once a new connection was established or `nil` if this `ServerSocket` is in non-blocking mode and there is no new connection that can be accepted when this method is called.
104
    /// - Throws: An `IOError` if the operation failed.
105
0
    func accept(setNonBlocking: Bool = false) throws -> Socket? {
106
0
        try withUnsafeHandle { fd in
107
0
            #if os(Linux)
108
0
            let flags: Int32
109
0
            if setNonBlocking {
110
0
                flags = Linux.SOCK_NONBLOCK
111
0
            } else {
112
0
                flags = 0
113
0
            }
114
0
            let result = try Linux.accept4(descriptor: fd, addr: nil, len: nil, flags: flags)
115
0
            #else
116
0
            let result = try NIOBSDSocket.accept(socket: fd, address: nil, address_len: nil)
117
0
            #endif
118
0
119
0
            guard let fd = result else {
120
0
                return nil
121
0
            }
122
0
123
0
            let sock = try Socket(socket: fd)
124
0
125
0
            #if !os(Linux)
126
0
            if setNonBlocking {
127
0
                do {
128
0
                    try sock.setNonBlocking()
129
0
                } catch {
130
0
                    // best effort
131
0
                    try? sock.close()
132
0
                    throw error
133
0
                }
134
0
            }
135
0
            #endif
136
0
            return sock
137
0
        }
138
0
    }
139
140
    /// Close the socket.
141
    ///
142
    /// After the socket was closed all other methods will throw an `IOError` when called.
143
    ///
144
    /// - Throws: An `IOError` if the operation failed.
145
0
    override func close() throws {
146
0
        let maybePathname = self.cleanupOnClose ? (try? self.localAddress().pathname) : nil
147
0
        try super.close()
148
0
        if let socketPath = maybePathname {
149
0
            try BaseSocket.cleanupSocket(unixDomainSocketPath: socketPath)
150
0
        }
151
0
    }
152
}