THRIFT-4838: Add unix socket support for Swift
Client: Swift
Patch: Kino Roy
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 5ff30cf..188bdf8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -624,6 +624,9 @@
test/rs/bin/* \
test/go/bin/*
+ - name: Create tmp domain socket folder
+ run: mkdir /tmp/v0.16
+
- name: Run cross test
env:
THRIFT_CROSSTEST_CONCURRENCY: 4
diff --git a/LANGUAGES.md b/LANGUAGES.md
index f7108a7..27877ba 100644
--- a/LANGUAGES.md
+++ b/LANGUAGES.md
@@ -350,7 +350,7 @@
<!-- Build Systems ---------><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td>
<!-- Language Levels -------><td colspan=2>5.7</td>
<!-- Field types -----------><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
-<!-- Low-Level Transports --><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
+<!-- Low-Level Transports --><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
<!-- Transport Wrappers ----><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td>
<!-- Protocols -------------><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
<!-- Servers ---------------><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cred.png" alt=""/></td><td><img src="/doc/images/cgrn.png" alt="Yes"/></td>
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)))
+ }
+}
diff --git a/test/swift/CrossTests/Sources/TestClient/main.swift b/test/swift/CrossTests/Sources/TestClient/main.swift
index 6817981..d627408 100644
--- a/test/swift/CrossTests/Sources/TestClient/main.swift
+++ b/test/swift/CrossTests/Sources/TestClient/main.swift
@@ -40,7 +40,12 @@
}
static func getTransport(parameters: TestClientParameters) throws -> TTransport {
- let socketTransport = try TSocketTransport(hostname: parameters.host!, port: parameters.port!)
+ let socketTransport: TTransport = try { () throws -> TTransport in
+ if let domainSocket = parameters.domainSocket {
+ return try TSocketTransport(path: domainSocket)
+ }
+ return try TSocketTransport(hostname: parameters.host!, port: parameters.port!)
+ }()
if parameters.transport == .framed {
return TFramedTransport(transport: socketTransport)
}
diff --git a/test/swift/CrossTests/Sources/TestServer/main.swift b/test/swift/CrossTests/Sources/TestServer/main.swift
index 15564d3..433bde7 100644
--- a/test/swift/CrossTests/Sources/TestServer/main.swift
+++ b/test/swift/CrossTests/Sources/TestServer/main.swift
@@ -34,19 +34,31 @@
let processor = ThriftTestProcessor(service: service)
- switch (parameters.proto, parameters.transport) {
- case (.binary, .buffered):
+ switch (parameters.proto, parameters.transport, parameters.domainSocket) {
+ case (.binary, .buffered, .none):
let proto = TBinaryProtocol.self
server = try TSocketServer(port: parameters.port!, inProtocol: proto, outProtocol: proto, processor: processor)
- case (.binary, .framed):
+ case (.binary, .framed, .none):
let proto = TBinaryProtocol.self
server = try TFramedSocketServer(port: parameters.port!, inProtocol: proto, outProtocol: proto, processor: processor)
- case (.compact, .buffered):
+ case (.compact, .buffered, .none):
let proto = TCompactProtocol.self
server = try TSocketServer(port: parameters.port!, inProtocol: proto, outProtocol: proto, processor: processor)
- case (.compact, .framed):
+ case (.compact, .framed, .none):
let proto = TCompactProtocol.self
server = try TFramedSocketServer(port: parameters.port!, inProtocol: proto, outProtocol: proto, processor: processor)
+ case (.binary, .buffered, .some(let domainSocket)):
+ let proto = TBinaryProtocol.self
+ server = try TSocketServer(path: domainSocket, inProtocol: proto, outProtocol: proto, processor: processor)
+ case (.binary, .framed, .some(let domainSocket)):
+ let proto = TBinaryProtocol.self
+ server = try TFramedSocketServer(path: domainSocket, inProtocol: proto, outProtocol: proto, processor: processor)
+ case (.compact, .buffered, .some(let domainSocket)):
+ let proto = TCompactProtocol.self
+ server = try TSocketServer(path: domainSocket, inProtocol: proto, outProtocol: proto, processor: processor)
+ case (.compact, .framed, .some(let domainSocket)):
+ let proto = TCompactProtocol.self
+ server = try TFramedSocketServer(path: domainSocket, inProtocol: proto, outProtocol: proto, processor: processor)
default:
throw ParserError.unsupportedOption
}
diff --git a/test/tests.json b/test/tests.json
index 015a559..c9bd068 100644
--- a/test/tests.json
+++ b/test/tests.json
@@ -797,14 +797,14 @@
"workdir": "swift/CrossTests/.build/x86_64-unknown-linux-gnu/debug",
"protocols": ["binary", "compact"],
"transports": ["buffered", "framed"],
- "sockets": ["ip"]
+ "sockets": ["ip", "domain"]
},
"client": {
"command": ["TestClient"],
"workdir": "swift/CrossTests/.build/x86_64-unknown-linux-gnu/debug",
"protocols": ["binary", "compact"],
"transports": ["buffered", "framed"],
- "sockets": ["ip"]
+ "sockets": ["ip", "domain"]
}
}
]