THRIFT-3859: Add support for Unix Domain Sockets to TSocketServer and TSocketTransport.
Client: cocoa
TSocketServer and TSocketTransport have been refactored to support sockets created using either a port or a path.
Existing behavior for port-based socket transport is unchanged by this commit.
This closes #1031
diff --git a/lib/cocoa/src/server/TSocketServer.h b/lib/cocoa/src/server/TSocketServer.h
index fe657ea..95b0d3c 100644
--- a/lib/cocoa/src/server/TSocketServer.h
+++ b/lib/cocoa/src/server/TSocketServer.h
@@ -41,6 +41,10 @@
protocolFactory:(id <TProtocolFactory>)protocolFactory
processorFactory:(id <TProcessorFactory>)processorFactory;
+- (instancetype) initWithPath: (NSString *) path
+ protocolFactory: (id <TProtocolFactory>) protocolFactory
+ processorFactory: (id <TProcessorFactory>) processorFactory;
+
@end
diff --git a/lib/cocoa/src/server/TSocketServer.m b/lib/cocoa/src/server/TSocketServer.m
index ccbbba1..09b603c 100644
--- a/lib/cocoa/src/server/TSocketServer.m
+++ b/lib/cocoa/src/server/TSocketServer.m
@@ -25,7 +25,7 @@
#import <sys/socket.h>
#include <netinet/in.h>
-
+#include <sys/un.h>
NSString *const TSocketServerClientConnectionFinished = @"TSocketServerClientConnectionFinished";
@@ -40,13 +40,14 @@
@property(strong, nonatomic) id<TProcessorFactory> processorFactory;
@property(strong, nonatomic) NSFileHandle *socketFileHandle;
@property(strong, nonatomic) dispatch_queue_t processingQueue;
+@property(strong, nonatomic) NSString *domainSocketPath;
@end
@implementation TSocketServer
--(instancetype) initWithPort:(int)port
+-(instancetype) initWithSocket:(CFSocketRef)socket
protocolFactory:(id <TProtocolFactory>)protocolFactory
processorFactory:(id <TProcessorFactory>)processorFactory;
{
@@ -62,32 +63,7 @@
_processingQueue = dispatch_queue_create("TSocketServer.processing", processingQueueAttr);
// create a socket.
- int fd = -1;
- CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL);
- if (socket) {
- CFSocketSetSocketFlags(socket, CFSocketGetSocketFlags(socket) & ~kCFSocketCloseOnInvalidate);
- fd = CFSocketGetNative(socket);
- int yes = 1;
- setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
-
- struct sockaddr_in addr;
- memset(&addr, 0, sizeof(addr));
- addr.sin_len = sizeof(addr);
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
- NSData *address = [NSData dataWithBytes:&addr length:sizeof(addr)];
- if (CFSocketSetAddress(socket, (__bridge CFDataRef)address) != kCFSocketSuccess) {
- CFSocketInvalidate(socket);
- CFRelease(socket);
- NSLog(@"TSocketServer: Could not bind to address");
- return nil;
- }
- }
- else {
- NSLog(@"TSocketServer: No server socket");
- return nil;
- }
+ int fd = CFSocketGetNative(socket);
// wrap it in a file handle so we can get messages from it
_socketFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd
@@ -106,15 +82,114 @@
// tell socket to listen
[_socketFileHandle acceptConnectionInBackgroundAndNotify];
- NSLog(@"TSocketServer: Listening on TCP port %d", port);
+ return self;
+}
+- (id) initWithPort: (int) port
+ protocolFactory: (id <TProtocolFactory>) protocolFactory
+ processorFactory: (id <TProcessorFactory>) processorFactory
+{
+ CFSocketRef socket = [[self class] createSocketWithPort:port];
+ if (socket == NULL) {
+ return nil;
+ }
+
+ if (self = [self initWithSocket:socket protocolFactory:protocolFactory processorFactory:processorFactory]) {
+ NSLog(@"TSocketServer: Listening on TCP port %d", port);
+ }
return self;
}
++(CFSocketRef) createSocketWithPort:(int)port
+{
+ CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL);
+ if (socket) {
+ CFSocketSetSocketFlags(socket, CFSocketGetSocketFlags(socket) & ~kCFSocketCloseOnInvalidate);
+ int fd = CFSocketGetNative(socket);
+ int yes = 1;
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
+
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_len = sizeof(addr);
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ NSData *address = [NSData dataWithBytes:&addr length:sizeof(addr)];
+ if (CFSocketSetAddress(socket, (__bridge CFDataRef)address) != kCFSocketSuccess) {
+ CFSocketInvalidate(socket);
+ CFRelease(socket);
+ NSLog(@"TSocketServer: Could not bind to address");
+ return NULL;
+ }
+
+ return socket;
+ }
+ else {
+ NSLog(@"TSocketServer: No server socket");
+ return NULL;
+ }
+}
+
+- (id) initWithPath: (NSString *) path
+ protocolFactory: (id <TProtocolFactory>) protocolFactory
+ processorFactory: (id <TProcessorFactory>) processorFactory
+{
+ _domainSocketPath = path;
+ CFSocketRef socket = [[self class] createSocketWithPath:path];
+ if (socket == NULL) {
+ return nil;
+ }
+
+ if (self = [self initWithSocket:socket protocolFactory:protocolFactory processorFactory:processorFactory]) {
+ NSLog(@"TSocketServer: Listening on path %@", path);
+ }
+ return self;
+}
+
++ (CFSocketRef) createSocketWithPath: (NSString *) path
+{
+ CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_LOCAL, SOCK_STREAM, IPPROTO_IP, 0, NULL, NULL);
+ if (socket) {
+ CFSocketSetSocketFlags(socket, CFSocketGetSocketFlags(socket) & ~kCFSocketCloseOnInvalidate);
+ int fd = CFSocketGetNative(socket);
+ int yes = 1;
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
+
+ size_t nullTerminatedPathLength = path.length + 1;
+ struct sockaddr_un addr;
+ if (nullTerminatedPathLength> sizeof(addr.sun_path)) {
+ NSLog(@"TSocketServer: Unable to create socket at path %@. Path is too long.", path);
+ return NULL;
+ }
+
+ addr.sun_family = AF_LOCAL;
+ memcpy(addr.sun_path, path.UTF8String, nullTerminatedPathLength);
+ addr.sun_len = SUN_LEN(&addr);
+
+ NSData *address = [NSData dataWithBytes:&addr length:sizeof(addr)];
+ if (CFSocketSetAddress(socket, (__bridge CFDataRef)address) != kCFSocketSuccess) {
+ CFSocketInvalidate(socket);
+ CFRelease(socket);
+ NSLog(@"TSocketServer: Could not bind to address");
+ return NULL;
+ }
+
+ return socket;
+ } else {
+ NSLog(@"TSocketServer: No server socket");
+ return NULL;
+ }
+}
+
-(void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ if (_domainSocketPath != nil) {
+ unlink(_domainSocketPath.UTF8String);
+ }
}
diff --git a/lib/cocoa/src/transport/TSocketTransport.h b/lib/cocoa/src/transport/TSocketTransport.h
index 4ea03cc..a7b91a2 100644
--- a/lib/cocoa/src/transport/TSocketTransport.h
+++ b/lib/cocoa/src/transport/TSocketTransport.h
@@ -28,6 +28,8 @@
-(id) initWithHostname:(NSString *)hostname
port:(int)port;
+-(id) initWithPath:(NSString *)path;
+
@end
diff --git a/lib/cocoa/src/transport/TSocketTransport.m b/lib/cocoa/src/transport/TSocketTransport.m
index 272baf6..9c58abb 100644
--- a/lib/cocoa/src/transport/TSocketTransport.m
+++ b/lib/cocoa/src/transport/TSocketTransport.m
@@ -24,21 +24,21 @@
#import <CFNetwork/CFNetwork.h>
#endif
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+
@interface TSocketTransport () <NSStreamDelegate>
@end
@implementation TSocketTransport
--(id) initWithHostname:(NSString *)hostname
- port:(int)port
+- (id) initWithReadStream: (CFReadStreamRef) readStream writeStream: (CFWriteStreamRef) writeStream
{
NSInputStream *inputStream = nil;
NSOutputStream *outputStream = nil;
- CFReadStreamRef readStream = NULL;
- CFWriteStreamRef writeStream = NULL;
- CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)hostname, port, &readStream, &writeStream);
if (readStream && writeStream) {
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
@@ -70,4 +70,62 @@
return [super initWithInputStream:inputStream outputStream:outputStream];
}
+- (id) initWithHostname: (NSString *) hostname
+ port: (int) port
+{
+ CFReadStreamRef readStream = NULL;
+ CFWriteStreamRef writeStream = NULL;
+ CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)hostname, port, &readStream, &writeStream);
+ return [self initWithReadStream:readStream writeStream:writeStream];
+}
+
+- (id) initWithPath: (NSString *) path
+{
+ CFSocketNativeHandle sockfd = socket(AF_LOCAL, SOCK_STREAM, IPPROTO_IP);
+ int yes = 1;
+ if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
+ {
+ NSLog(@"TSocketTransport: Unable to set REUSEADDR property of socket.");
+ return nil;
+ }
+
+ NSData *serverAddress = [[self class] createAddressWithPath:path];
+
+ CFReadStreamRef readStream = NULL;
+ CFWriteStreamRef writeStream = NULL;
+ CFStreamCreatePairWithSocket(kCFAllocatorDefault, sockfd, &readStream, &writeStream);
+ if (!readStream || !writeStream)
+ {
+ NSLog(@"TSocketTransport: Unable to create read/write stream pair for socket.");
+ return nil;
+ }
+
+ if (connect(sockfd, (struct sockaddr *)serverAddress.bytes, (socklen_t) serverAddress.length) < 0)
+ {
+ NSLog(@"TSocketTransport: Connect error: %s\n", strerror(errno));
+ return nil;
+ }
+
+ return [self initWithReadStream:readStream writeStream:writeStream];
+}
+
++ (NSData *) createAddressWithPath: (NSString *)path
+{
+ struct sockaddr_un servaddr;
+
+ size_t nullTerminatedPathLength = path.length + 1;
+ if (nullTerminatedPathLength> sizeof(servaddr.sun_path)) {
+ NSLog(@"TSocketTransport: Unable to create socket at path %@. Path is too long.", path);
+ return nil;
+ }
+
+ bzero(&servaddr,sizeof(servaddr));
+ servaddr.sun_family = AF_LOCAL;
+ memcpy(servaddr.sun_path, path.UTF8String, nullTerminatedPathLength);
+ servaddr.sun_len = SUN_LEN(&servaddr);
+
+ return [NSData dataWithBytes:&servaddr length:sizeof(servaddr)];
+}
+
+
@end