blob: 09b603cf4ad857bcf653c6c439d2140e745f0e03 [file] [log] [blame]
/*
* 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.
*/
#import <Foundation/Foundation.h>
#import "TSocketServer.h"
#import "TNSFileHandleTransport.h"
#import "TProtocol.h"
#import "TTransportError.h"
#import <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
NSString *const TSocketServerClientConnectionFinished = @"TSocketServerClientConnectionFinished";
NSString *const TSocketServerProcessorKey = @"TSocketServerProcessor";
NSString *const TSockerServerTransportKey = @"TSockerServerTransport";
@interface TSocketServer ()
@property(strong, nonatomic) id<TProtocolFactory> inputProtocolFactory;
@property(strong, nonatomic) id<TProtocolFactory> outputProtocolFactory;
@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) initWithSocket:(CFSocketRef)socket
protocolFactory:(id <TProtocolFactory>)protocolFactory
processorFactory:(id <TProcessorFactory>)processorFactory;
{
self = [super init];
_inputProtocolFactory = protocolFactory;
_outputProtocolFactory = protocolFactory;
_processorFactory = processorFactory;
dispatch_queue_attr_t processingQueueAttr =
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_BACKGROUND, 0);
_processingQueue = dispatch_queue_create("TSocketServer.processing", processingQueueAttr);
// create a socket.
int fd = CFSocketGetNative(socket);
// wrap it in a file handle so we can get messages from it
_socketFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd
closeOnDealloc:YES];
// throw away our socket
CFSocketInvalidate(socket);
CFRelease(socket);
// register for notifications of accepted incoming connections
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(connectionAccepted:)
name:NSFileHandleConnectionAcceptedNotification
object:_socketFileHandle];
// tell socket to listen
[_socketFileHandle acceptConnectionInBackgroundAndNotify];
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);
}
}
-(void) connectionAccepted:(NSNotification *)notification
{
NSFileHandle *socket = [notification.userInfo objectForKey:NSFileHandleNotificationFileHandleItem];
// Now that we have a client connected, handle request on queue
dispatch_async(_processingQueue, ^{
[self handleClientConnection:socket];
});
// Continue accepting connections
[_socketFileHandle acceptConnectionInBackgroundAndNotify];
}
-(void) handleClientConnection:(NSFileHandle *)clientSocket
{
@autoreleasepool {
TNSFileHandleTransport *transport = [[TNSFileHandleTransport alloc] initWithFileHandle:clientSocket];
id<TProcessor> processor = [_processorFactory processorForTransport:transport];
id <TProtocol> inProtocol = [_inputProtocolFactory newProtocolOnTransport:transport];
id <TProtocol> outProtocol = [_outputProtocolFactory newProtocolOnTransport:transport];
NSError *error;
if (![processor processOnInputProtocol:inProtocol outputProtocol:outProtocol error:&error]) {
// Handle error
NSLog(@"Error processing request: %@", error);
}
dispatch_async(dispatch_get_main_queue(), ^{
[NSNotificationCenter.defaultCenter postNotificationName:TSocketServerClientConnectionFinished
object:self
userInfo:@{TSocketServerProcessorKey: processor,
TSockerServerTransportKey: transport}];
});
}
}
@end