// This source file is part of the Swift.org Server APIs open source project // // Copyright (c) 2017 Swift Server API project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // import Dispatch import Foundation import ServerSecurity import TLSService // MARK: Server /// An HTTP server that listens for connections on a TCP socket and spawns Listeners to handle them. ///:nodoc: public class PoCSocketSimpleServer: CurrentConnectionCounting { /// PoCSocket to listen on for connections private let serverSocket: PoCSocket = PoCSocket() /// Collection of listeners of sockets. Used to kill connections on timeout or shutdown private var connectionListenerList = ConnectionListenerCollection() /// Timer that cleans up idle sockets on expire private let pruneSocketTimer: DispatchSourceTimer = DispatchSource.makeTimerSource(queue: DispatchQueue(label: "pruneSocketTimer")) /// The port we're listening on. Used primarily to query a randomly assigned port during XCTests public var port: Int { return Int(serverSocket.listeningPort) } /// Tuning parameter to set the number of queues private var queueMax: Int = 4 //sensible default /// Tuning parameter to set the number of sockets we can accept at one time private var acceptMax: Int = 8 //sensible default /// Used to stop `accept(2)`ing while shutdown in progress to avoid spurious logs private let _isShuttingDownLock = DispatchSemaphore(value: 1) private var _isShuttingDown: Bool = false var isShuttingDown: Bool { get { _isShuttingDownLock.wait() defer { _isShuttingDownLock.signal() } return _isShuttingDown } set { _isShuttingDownLock.wait() defer { _isShuttingDownLock.signal() } _isShuttingDown = newValue } } /// Starts the server listening on a given port /// /// - Parameters: /// - port: TCP port. See listen(2) /// - handler: Function that creates the HTTP Response from the HTTP Request /// - Throws: Error (usually a socket error) generated public func start(port: Int = 0, queueCount: Int = 0, acceptCount: Int = 0, maxReadLength: Int = 1048576, keepAliveTimeout: Double = 5.0, tls: TLSConfiguration? = nil, handler: @escaping HTTPRequestHandler) throws { // Don't let a signal generated by a broken socket kill the server signal(SIGPIPE, SIG_IGN) if queueCount > 0 { queueMax = queueCount } if acceptCount > 0 { acceptMax = acceptCount } // Set up TLS if let tlsConfig = tls { let tlsService = try TLSService(usingConfiguration: tlsConfig) self.serverSocket.TLSdelegate = tlsService } try self.serverSocket.bindAndListen(on: port) pruneSocketTimer.setEventHandler { [weak self] in self?.connectionListenerList.prune() } #if swift(>=4.0) pruneSocketTimer.schedule(deadline: .now() + keepAliveTimeout, repeating: .seconds(Int(keepAliveTimeout))) #else pruneSocketTimer.scheduleRepeating(deadline: .now() + keepAliveTimeout, interval: .seconds(Int(keepAliveTimeout))) #endif pruneSocketTimer.resume() var readQueues = [DispatchQueue]() var writeQueues = [DispatchQueue]() let acceptQueue = DispatchQueue(label: "Accept Queue", qos: .default, attributes: .concurrent) let acceptSemaphore = DispatchSemaphore.init(value: acceptMax) for idx in 0.. { weak var value: T? init (_ value: T) { self.value = value } } /// Lock around access to storage private let lock = DispatchSemaphore(value: 1) /// Storage for weak connection listeners private var storage = [WeakConnectionListener]() /// Add a new connection to the collection /// /// - Parameter listener: socket manager object func add(_ listener: PoCSocketConnectionListener) { lock.wait() storage.append(WeakConnectionListener(listener)) lock.signal() } /// Used when shutting down the server to close all connections func closeAll() { lock.wait() storage.filter { $0.value != nil }.forEach { $0.value?.close() } lock.signal() } /// Close any idle sockets and remove any weak pointers to closed (and freed) sockets from the collection func prune() { lock.wait() storage.filter { nil != $0.value }.forEach { $0.value?.closeIfIdleSocket() } storage = storage.filter { $0.value != nil }.filter { $0.value?.isOpen ?? false } lock.signal() } /// Count of collections var count: Int { lock.wait() let count = storage.filter { $0.value != nil }.count lock.signal() return count } }