THRIFT-4838: Add unix socket support for Swift
Client: Swift
Patch: Kino Roy
diff --git a/lib/swift/Sources/LinuxHelper.swift b/lib/swift/Sources/LinuxHelper.swift
index 83603f8..7ab0b3e 100644
--- a/lib/swift/Sources/LinuxHelper.swift
+++ b/lib/swift/Sources/LinuxHelper.swift
@@ -27,7 +27,7 @@
extension UInt {
public static func &(lhs: UInt, rhs: Int) -> UInt {
- let cast = unsafeBitCast(rhs, to: UInt.self)
+ let cast = UInt(bitPattern: rhs)
return lhs & cast
}
}
diff --git a/lib/swift/Sources/TSocketServer.swift b/lib/swift/Sources/TSocketServer.swift
index 6cd9adf..27b4b55 100644
--- a/lib/swift/Sources/TSocketServer.swift
+++ b/lib/swift/Sources/TSocketServer.swift
@@ -102,6 +102,49 @@
// tell socket to listen
acceptConnectionInBackgroundAndNotify(handle: socketFileHandle)
}
+
+ public init(path: String,
+ inProtocol: InProtocol.Type,
+ outProtocol: OutProtocol.Type,
+ processor: Processor) throws {
+ self.processor = processor
+ // create a socket
+ let socket = UnixSocket(path: path)
+ let fd = socket.fd
+
+ if fd == -1 {
+ print("TSocketServer: No server socket")
+ throw TTransportError(error: .notOpen, message: "Could not create socket")
+ }
+
+ // wrap it in a file handle so we can get messages from it
+ socketFileHandle = FileHandle(fileDescriptor: fd, closeOnDealloc: true)
+
+ // register for notifications of accepted incoming connections
+ _ = NotificationCenter.default.addObserver(forName: .NSFileHandleConnectionAccepted,
+ object: nil, queue: nil) {
+ [weak self] notification in
+ guard let strongSelf = self else { return }
+ guard let clientSocket = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { return }
+ strongSelf.connectionAccepted(clientSocket)
+ }
+
+ let bindRes = socket.bind()
+ guard bindRes == 0 else {
+ print("TServerSocket: bind failed")
+ throw TTransportError(error: .notOpen, message: "Could not create socket")
+ }
+ let listenRes = listen(fd, 1024)
+ guard listenRes == 0 else {
+ print("TServerSocket: listen failed")
+ throw TTransportError(error: .notOpen, message: "Could not create socket")
+ }
+
+ // tell socket to listen
+ acceptConnectionInBackgroundAndNotify(handle: socketFileHandle)
+
+ print("TSocketServer: Listening on unix path \(path)")
+ }
private func acceptConnectionInBackgroundAndNotify(handle: FileHandle) {
DispatchQueue(label: "TSocketServer.connectionAccept").async {
@@ -111,6 +154,7 @@
}
}
}
+
func connectionAccepted(_ clientSocket: FileHandle) {
// Now that we have a client connected, handle the request on queue
processingQueue.async {
diff --git a/lib/swift/Sources/TSocketTransport.swift b/lib/swift/Sources/TSocketTransport.swift
index 640612b..fb99bc5 100644
--- a/lib/swift/Sources/TSocketTransport.swift
+++ b/lib/swift/Sources/TSocketTransport.swift
@@ -173,6 +173,15 @@
self.init(socketDescriptor: sock)
}
+
+ public convenience init(path: String) throws {
+ let socket = UnixSocket(path: path)
+ let errno = socket.connect()
+ guard errno == 0 else {
+ throw TTransportError(error: .notOpen, message: "Error binding to socket at path: \(path). Errno: \(errno)")
+ }
+ self.init(socketDescriptor: socket.fd)
+ }
deinit {
close()
diff --git a/lib/swift/Sources/UnixSocket.swift b/lib/swift/Sources/UnixSocket.swift
new file mode 100644
index 0000000..0b7a637
--- /dev/null
+++ b/lib/swift/Sources/UnixSocket.swift
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#if os(OSX) || os(iOS) || os(watchOS) || os(tvOS)
+import Darwin
+#elseif os(Linux) || os(FreeBSD) || os(PS4) || os(Android)
+import Glibc
+import Dispatch
+#endif
+import Foundation
+
+private struct Sys {
+#if os(Linux)
+ static let read = Glibc.read
+ static let write = Glibc.write
+ static let close = Glibc.close
+ static let socket = Glibc.socket
+ static let connect = Glibc.connect
+ static let bind = Glibc.bind
+ static let recv = Glibc.recv
+#else
+ static let read = Darwin.read
+ static let write = Darwin.write
+ static let close = Darwin.close
+ static let socket = Darwin.socket
+ static let connect = Darwin.connect
+ static let bind = Darwin.bind
+ static let recv = Darwin.recv
+#endif
+}
+
+
+public class UnixSocket {
+ public var fd: Int32
+ private var socketAddress: sockaddr_un
+
+ public init(path: String) {
+ socketAddress = sockaddr_un()
+ socketAddress.sun_family = sa_family_t(AF_UNIX)
+
+ let lengthOfPath = path.withCString { Int(strlen($0)) }
+
+ guard lengthOfPath < MemoryLayout.size(ofValue: socketAddress.sun_path) else {
+ fatalError()
+ }
+
+ _ = withUnsafeMutablePointer(to: &socketAddress.sun_path.0) { ptr in
+ path.withCString {
+ strncpy(ptr, $0, lengthOfPath)
+ }
+ }
+
+#if os(Linux)
+ fd = Sys.socket(AF_UNIX, 1 /*SOCK_STREAM*/, 0);
+#else
+ fd = Sys.socket(AF_UNIX, SOCK_STREAM, 0);
+#endif
+
+ }
+ public func connect() -> Int32 {
+ let socketAddressCasted = withUnsafePointer(to: &socketAddress) {
+ $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
+ return $0
+ }
+ }
+ return Sys.connect(fd, socketAddressCasted, socklen_t(MemoryLayout<sockaddr_un>.size(ofValue: socketAddress)))
+ }
+ public func bind() -> Int32 {
+ let socketAddressCasted = withUnsafePointer(to: &socketAddress) {
+ $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
+ return $0
+ }
+ }
+ return Sys.bind(fd, socketAddressCasted, socklen_t(MemoryLayout<sockaddr_un>.size(ofValue: socketAddress)))
+ }
+}