├── .gitignore ├── LICENSE ├── MAAsyncHTTPServer.h ├── MAAsyncHTTPServer.m ├── MAAsyncHost.h ├── MAAsyncHost.m ├── MAAsyncIO.xcodeproj └── project.pbxproj ├── MAAsyncReader.h ├── MAAsyncReader.m ├── MAAsyncSocketListener.h ├── MAAsyncSocketListener.m ├── MAAsyncSocketUtils.h ├── MAAsyncSocketUtils.m ├── MAAsyncWriter.h ├── MAAsyncWriter.m ├── MAFDRefcount.h ├── MAFDRefcount.m ├── MAFDSource.h ├── MAFDSource.m ├── MAHTTPRequest.h ├── MAHTTPRequest.m ├── README.markdown └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | # xcode noise 2 | *.mode1v3 3 | *.pbxuser 4 | *.pyc 5 | *~.nib/ 6 | build/* 7 | xcuserdata 8 | project.xcworkspace 9 | 10 | # osx noise 11 | .DS_Store 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MAAsyncIO and all code associated with it is distributed under a BSD license, as listed below. 2 | 3 | 4 | Copyright (c) 2010, Michael Ash 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 8 | 9 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 10 | 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 12 | 13 | Neither the name of Michael Ash nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MAAsyncHTTPServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // MAAsyncHTTPServer.h 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/9/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MAHTTPRequest.h" 11 | #import "MAAsyncWriter.h" 12 | 13 | @class MAAsyncSocketListener; 14 | 15 | extern NSString *const defaultRequestRoute; 16 | extern char *const defaultHTTPHeaderBodySeparator; 17 | typedef void (^MAAsyncHTTPRequestHandler)(MAHTTPRequest *request, MAAsyncWriter *writer); 18 | 19 | @interface MAAsyncHTTPServer : NSObject 20 | { 21 | MAAsyncSocketListener *_listener; 22 | NSMutableArray *_routes; 23 | dispatch_queue_t _routesQueue; 24 | } 25 | 26 | - (id)initWithPort: (int)port error: (NSError **)error; 27 | 28 | - (void)registerDefaultRouteHandler: (MAAsyncHTTPRequestHandler)block; 29 | - (void)registerRoute: (NSString *)route method: (MAHTTPMethod)method handler: (MAAsyncHTTPRequestHandler)block; 30 | - (void)unregisterRoute: (NSString *)route method: (MAHTTPMethod)method; 31 | - (MAAsyncHTTPRequestHandler)registeredRoute:(NSString *)route method: (MAHTTPMethod)method; 32 | 33 | - (int)port; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /MAAsyncHTTPServer.m: -------------------------------------------------------------------------------- 1 | // 2 | // MAAsyncHTTPServer.m 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/9/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import "MAAsyncHTTPServer.h" 10 | 11 | #import "MAAsyncReader.h" 12 | #import "MAAsyncSocketListener.h" 13 | 14 | NSString *const defaultRequestRoute = @"/"; 15 | char *const defaultHTTPHeaderBodySeparator = "\r\n\r\n"; 16 | 17 | @interface MAAsyncHTTPServer () 18 | 19 | - (void)_gotConnection: (MAAsyncReader *)reader writer: (MAAsyncWriter *)writer; 20 | 21 | @end 22 | 23 | @implementation MAAsyncHTTPServer 24 | 25 | - (id)initWithPort: (int)port error: (NSError **)error 26 | { 27 | if((self = [self init])) 28 | { 29 | NSRange r; 30 | if(port > 0) 31 | r = NSMakeRange(port, 1); 32 | else 33 | r = NSMakeRange(0, 0); 34 | 35 | _listener = [[MAAsyncSocketListener listenerWith4and6WithPortRange: r tryRandom: port <= 0 error: error] retain]; 36 | 37 | // _routes = [[NSMutableDictionary alloc] initWithCapacity:1]; 38 | _routes = [[NSMutableArray alloc] initWithCapacity:10]; 39 | for(NSUInteger i = 0; i<10;i++) 40 | [_routes addObject:[NSMutableDictionary dictionaryWithCapacity:1]]; 41 | 42 | _routesQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.mikesah.MAAsyncHTTPServer.routesQueue.%i",port] UTF8String], NULL); 43 | 44 | if(!_listener) 45 | { 46 | [self release]; 47 | return nil; 48 | } 49 | } 50 | return self; 51 | } 52 | 53 | - (void)dealloc 54 | { 55 | dispatch_release(_routesQueue); 56 | [_listener invalidate]; 57 | [_listener release]; 58 | [_routes release]; 59 | 60 | [super dealloc]; 61 | } 62 | 63 | 64 | - (void)registerDefaultRouteHandler: (MAAsyncHTTPRequestHandler)block 65 | { 66 | [self registerRoute: defaultRequestRoute method: kMAHTTPNotDefined handler: block]; 67 | 68 | __block MAAsyncHTTPServer *weakSelf = self; 69 | [_listener setAcceptCallback: ^(MAAsyncReader *reader, MAAsyncWriter *writer, NSData *peerAddress) { 70 | [weakSelf _gotConnection: reader writer: writer]; 71 | }]; 72 | } 73 | 74 | - (void)registerRoute: (NSString *)route method: (MAHTTPMethod)method handler: (MAAsyncHTTPRequestHandler)block 75 | { 76 | id localBlock = [block copy]; 77 | 78 | dispatch_async(_routesQueue, ^{ 79 | NSMutableDictionary *handlerByRoutes = [[_routes objectAtIndex:method] mutableCopy]; 80 | [handlerByRoutes setObject:localBlock forKey:route]; 81 | [_routes replaceObjectAtIndex:method withObject:handlerByRoutes]; 82 | [handlerByRoutes release]; 83 | }); 84 | 85 | [localBlock release]; 86 | } 87 | 88 | - (void)unregisterRoute: (NSString *)route method: (MAHTTPMethod)method 89 | { 90 | dispatch_async(_routesQueue, ^{ 91 | NSMutableDictionary *handlerByRoutes = [[_routes objectAtIndex:method] mutableCopy]; 92 | [handlerByRoutes removeObjectForKey:route]; 93 | [_routes replaceObjectAtIndex:method withObject:handlerByRoutes]; 94 | [handlerByRoutes release]; 95 | }); 96 | } 97 | 98 | - (MAAsyncHTTPRequestHandler)registeredRoute:(NSString *)route method: (MAHTTPMethod)method 99 | { 100 | __block MAAsyncHTTPRequestHandler resultHandler = nil; 101 | 102 | dispatch_sync(_routesQueue, ^{ 103 | 104 | if([_routes count] > method) 105 | { 106 | NSMutableDictionary *handlerByRoutes = [[_routes objectAtIndex:method] mutableCopy]; 107 | 108 | if([handlerByRoutes count] > 0) 109 | { 110 | NSString *routeWithoutSuffix = [[route stringByDeletingPathExtension] copy]; 111 | 112 | resultHandler = [handlerByRoutes objectForKey:routeWithoutSuffix]; 113 | 114 | if(!resultHandler) 115 | { 116 | NSMutableArray *path = [[routeWithoutSuffix componentsSeparatedByString:@"/"] mutableCopy]; 117 | NSInteger countIdx = [path count]-1; 118 | 119 | MAAsyncHTTPRequestHandler resultHandler = nil; 120 | 121 | while (countIdx > 0) 122 | { 123 | [path removeLastObject]; 124 | 125 | NSString *shortPath = [path componentsJoinedByString:@"/"]; 126 | 127 | if([handlerByRoutes objectForKey: shortPath]) 128 | { 129 | resultHandler = [handlerByRoutes objectForKey: shortPath]; 130 | break; 131 | } 132 | 133 | countIdx--; 134 | } 135 | 136 | [path release]; 137 | } 138 | 139 | [routeWithoutSuffix release]; 140 | } 141 | 142 | [handlerByRoutes release]; 143 | } 144 | 145 | if(!resultHandler) 146 | resultHandler = [[_routes objectAtIndex: kMAHTTPNotDefined] objectForKey: defaultRequestRoute]; 147 | 148 | resultHandler = [resultHandler copy]; 149 | }); 150 | 151 | return [resultHandler autorelease]; 152 | } 153 | 154 | - (int)port 155 | { 156 | return [_listener port]; 157 | } 158 | 159 | - (void)_readRequestContent: (MAAsyncReader *)reader writer: (MAAsyncWriter *)writer request: (MAHTTPRequest *)request 160 | { 161 | [reader readBytes:[request expectedContentLength] callback: ^(NSData *data, BOOL prematureEOF) { 162 | [request setContent:data]; 163 | 164 | MAAsyncHTTPRequestHandler handler = [self registeredRoute: [request resource] method: [request method]]; 165 | handler(request, writer); 166 | 167 | [reader invalidate]; 168 | }]; 169 | } 170 | 171 | - (void)_gotConnection: (MAAsyncReader *)reader writer: (MAAsyncWriter *)writer 172 | { 173 | [reader readUntilCString: defaultHTTPHeaderBodySeparator callback: ^(NSData *data, BOOL prematureEOF) { 174 | if(data) 175 | { 176 | MAHTTPRequest *request = [[MAHTTPRequest alloc] initWithHeader: data]; 177 | if([request expectedContentLength] == 0) 178 | { 179 | MAAsyncHTTPRequestHandler handler = [self registeredRoute: [request resource] method: [request method]]; 180 | handler(request, writer); 181 | 182 | [reader invalidate]; 183 | } 184 | else 185 | { 186 | [self _readRequestContent:reader writer:writer request:request]; 187 | } 188 | 189 | [request release]; 190 | } 191 | }]; 192 | } 193 | 194 | @end 195 | -------------------------------------------------------------------------------- /MAAsyncHost.h: -------------------------------------------------------------------------------- 1 | // 2 | // MAAsyncHost.h 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/8/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | // code will be the domain of the CFStreamError 13 | // error code will be in userinfo "cfcode" 14 | extern NSString * const MACFStreamNSErrorDomain; 15 | 16 | @class MAAsyncReader; 17 | @class MAAsyncWriter; 18 | 19 | @interface MAAsyncHost : NSObject 20 | { 21 | CFHostRef _cfhost; 22 | dispatch_queue_t _queue; 23 | BOOL _resolving; 24 | NSArray *_addresses; 25 | NSMutableArray *_resolveBlocks; 26 | } 27 | 28 | + (id)hostWithName: (NSString *)name; 29 | 30 | - (id)initWithName: (NSString *)name; 31 | 32 | - (void)resolve: (void (^)(NSArray *addresses, CFStreamError error))block; 33 | 34 | // this will automatically resolve the host and then try to connect to all of the resolved addresses 35 | // in sequence until one works 36 | // errors will be in the MACFStreamNSErrorDomain if resolution fails 37 | - (void)connectToPort: (int)port callback: (void (^)(MAAsyncReader *reader, MAAsyncWriter *writer, NSError *error))block; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /MAAsyncHost.m: -------------------------------------------------------------------------------- 1 | // 2 | // MAAsyncHost.m 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/8/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import "MAAsyncHost.h" 10 | 11 | #include 12 | 13 | #import "MAAsyncSocketUtils.h" 14 | 15 | 16 | NSString * const MACFStreamNSErrorDomain = @"com.mikeash.MACFStreamNSErrorDomain"; 17 | 18 | @implementation MAAsyncHost 19 | 20 | + (void)_resolutionThread 21 | { 22 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 23 | NSPort *port = [NSPort port]; 24 | [[NSRunLoop currentRunLoop] addPort: port forMode: NSDefaultRunLoopMode]; 25 | [[NSRunLoop currentRunLoop] run]; 26 | [pool release]; 27 | } 28 | 29 | + (void)_resolutionGetRunloop: (NSMutableArray *)array 30 | { 31 | [array addObject: (id)CFRunLoopGetCurrent()]; 32 | } 33 | 34 | + (CFRunLoopRef)_resolutionRunloop 35 | { 36 | static CFRunLoopRef runloop; 37 | static dispatch_once_t pred; 38 | dispatch_once(&pred, ^{ 39 | NSThread *thread = [[NSThread alloc] initWithTarget: self selector: @selector(_resolutionThread) object: nil]; 40 | [thread start]; 41 | 42 | NSMutableArray *runloopArray = [NSMutableArray array]; 43 | [self performSelector: @selector(_resolutionGetRunloop:) onThread: thread withObject: runloopArray waitUntilDone: YES]; 44 | runloop = (CFRunLoopRef)[runloopArray lastObject]; 45 | CFRetain(runloop); 46 | [thread release]; 47 | }); 48 | return runloop; 49 | } 50 | 51 | + (id)hostWithName: (NSString *)name 52 | { 53 | return [[[self alloc] initWithName: name] autorelease]; 54 | } 55 | 56 | - (id)initWithName: (NSString *)name 57 | { 58 | if((self = [self init])) 59 | { 60 | _cfhost = CFHostCreateWithName(NULL, (CFStringRef)name); 61 | _queue = dispatch_queue_create("com.mikeash.MAAsyncHost", NULL); 62 | _resolveBlocks = [[NSMutableArray alloc] init]; 63 | } 64 | return self; 65 | } 66 | 67 | - (void)dealloc 68 | { 69 | CFRelease(_cfhost); 70 | dispatch_release(_queue); 71 | [_addresses release]; 72 | [_resolveBlocks release]; 73 | 74 | [super dealloc]; 75 | } 76 | 77 | - (void)_callResolveBlocksAddresses: (NSArray *)addresses error: (CFStreamError)error 78 | { 79 | void (^block)(NSArray *addresses, CFStreamError error); 80 | for(block in _resolveBlocks) 81 | block(addresses, error); 82 | [_resolveBlocks removeAllObjects]; 83 | } 84 | 85 | static void ResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *errorPtr, void *info) 86 | { 87 | MAAsyncHost *self = info; 88 | CFStreamError error = { 0, 0 }; 89 | if(errorPtr) 90 | error = *errorPtr; 91 | 92 | assert(!self->_addresses); 93 | 94 | NSArray *addresses = [(NSArray *)CFHostGetAddressing(self->_cfhost, NULL) copy]; 95 | 96 | dispatch_async(self->_queue, ^{ 97 | if(error.domain) 98 | { 99 | [self _callResolveBlocksAddresses: nil error: error]; 100 | } 101 | else 102 | { 103 | self->_addresses = [addresses copy]; 104 | [self _callResolveBlocksAddresses: self->_addresses error: (CFStreamError){ 0, 0 }]; 105 | } 106 | }); 107 | 108 | [addresses release]; 109 | } 110 | 111 | - (void)resolve: (void (^)(NSArray *addresses, CFStreamError error))block 112 | { 113 | dispatch_async(_queue, ^{ 114 | if(_addresses) 115 | { 116 | block(_addresses, (CFStreamError){ 0, 0 }); 117 | } 118 | else 119 | { 120 | if(!_resolving) 121 | { 122 | CFHostClientContext ctx = { 0, self, CFRetain, CFRelease, NULL }; 123 | CFHostSetClient(_cfhost, ResolveCallback, &ctx); 124 | 125 | CFHostScheduleWithRunLoop(_cfhost, [[self class] _resolutionRunloop], kCFRunLoopDefaultMode); 126 | 127 | CFStreamError error; 128 | Boolean success = CFHostStartInfoResolution(_cfhost, kCFHostAddresses, &error); 129 | 130 | if(!success) 131 | { 132 | block(nil, error); 133 | } 134 | else 135 | { 136 | [_resolveBlocks addObject: block]; 137 | _resolving = YES; 138 | } 139 | } 140 | else 141 | [_resolveBlocks addObject: block]; 142 | } 143 | }); 144 | } 145 | 146 | - (NSError *)_errorForCFStreamError: (CFStreamError)cferror 147 | { 148 | NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: 149 | [NSNumber numberWithInteger: cferror.error], @"cfcode", 150 | nil]; 151 | NSError *error = [NSError errorWithDomain: MACFStreamNSErrorDomain code: cferror.domain userInfo: userInfo]; 152 | return error; 153 | } 154 | 155 | - (void)_connectToPort: (int)port addressEnumerator: (NSEnumerator *)enumerator lastError: (NSError *)lastError callback: (void (^)(MAAsyncReader *reader, MAAsyncWriter *writer, NSError *error))block 156 | { 157 | NSData *address = [enumerator nextObject]; 158 | if(!address) 159 | { 160 | if(!lastError) 161 | lastError = [self _errorForCFStreamError: (CFStreamError){ kCFStreamErrorDomainNetDB, NO_DATA }]; 162 | block(nil, nil, lastError); 163 | } 164 | else 165 | { 166 | MAAsyncSocketConnect(address, port, ^(MAAsyncReader *reader, MAAsyncWriter *writer, NSError *error) { 167 | if(reader && writer) 168 | block(reader, writer, nil); 169 | else 170 | [self _connectToPort: port addressEnumerator: enumerator lastError: error callback: block]; 171 | }); 172 | } 173 | } 174 | 175 | - (void)connectToPort: (int)port callback: (void (^)(MAAsyncReader *reader, MAAsyncWriter *writer, NSError *error))block 176 | { 177 | [self resolve: ^(NSArray *addresses, CFStreamError error) { 178 | if(!addresses) 179 | { 180 | block(nil, nil, [self _errorForCFStreamError: error]); 181 | } 182 | else 183 | [self _connectToPort: port addressEnumerator: [addresses objectEnumerator] lastError: nil callback: block]; 184 | }]; 185 | } 186 | 187 | @end 188 | -------------------------------------------------------------------------------- /MAAsyncIO.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 45; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 64BCBED81356E3DB009E9BFF /* MAHTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 64BCBED71356E3DB009E9BFF /* MAHTTPRequest.m */; }; 11 | 8DD76F9A0486AA7600D96B5E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* main.m */; settings = {ATTRIBUTES = (); }; }; 12 | 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; 13 | C27C0A1D12A7314C00FAE8E6 /* MAAsyncReader.m in Sources */ = {isa = PBXBuildFile; fileRef = C27C0A1C12A7314C00FAE8E6 /* MAAsyncReader.m */; }; 14 | C2BB084812AF1AEB00BC232D /* MAFDRefcount.m in Sources */ = {isa = PBXBuildFile; fileRef = C2BB084712AF1AEB00BC232D /* MAFDRefcount.m */; }; 15 | C2D3956412A9DAA30058481D /* MAFDSource.m in Sources */ = {isa = PBXBuildFile; fileRef = C2D3956312A9DAA30058481D /* MAFDSource.m */; }; 16 | C2D3959F12A9DD050058481D /* MAAsyncWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = C2D3959E12A9DD050058481D /* MAAsyncWriter.m */; }; 17 | C2D3994A12AFFF7C0058481D /* MAAsyncHost.m in Sources */ = {isa = PBXBuildFile; fileRef = C2D3994912AFFF7C0058481D /* MAAsyncHost.m */; }; 18 | C2D39A0C12B0019F0058481D /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C2D39A0B12B0019F0058481D /* CoreServices.framework */; }; 19 | C2D39AC512B00A820058481D /* MAAsyncSocketUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = C2D39AC412B00A820058481D /* MAAsyncSocketUtils.m */; }; 20 | C2D39D8F12B03A630058481D /* MAAsyncSocketListener.m in Sources */ = {isa = PBXBuildFile; fileRef = C2D39D8E12B03A630058481D /* MAAsyncSocketListener.m */; }; 21 | C2D3A23A12B179070058481D /* MAAsyncHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = C2D3A23912B179070058481D /* MAAsyncHTTPServer.m */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXCopyFilesBuildPhase section */ 25 | 8DD76F9E0486AA7600D96B5E /* CopyFiles */ = { 26 | isa = PBXCopyFilesBuildPhase; 27 | buildActionMask = 8; 28 | dstPath = /usr/share/man/man1/; 29 | dstSubfolderSpec = 0; 30 | files = ( 31 | ); 32 | runOnlyForDeploymentPostprocessing = 1; 33 | }; 34 | /* End PBXCopyFilesBuildPhase section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 08FB7796FE84155DC02AAC07 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 38 | 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 39 | 64BCBED61356E3DB009E9BFF /* MAHTTPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MAHTTPRequest.h; sourceTree = ""; }; 40 | 64BCBED71356E3DB009E9BFF /* MAHTTPRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MAHTTPRequest.m; sourceTree = ""; }; 41 | 8DD76FA10486AA7600D96B5E /* MAAsyncIO */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = MAAsyncIO; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | C27C0A1B12A7314C00FAE8E6 /* MAAsyncReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MAAsyncReader.h; sourceTree = ""; }; 43 | C27C0A1C12A7314C00FAE8E6 /* MAAsyncReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MAAsyncReader.m; sourceTree = ""; }; 44 | C2BB084612AF1AEB00BC232D /* MAFDRefcount.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MAFDRefcount.h; sourceTree = ""; }; 45 | C2BB084712AF1AEB00BC232D /* MAFDRefcount.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MAFDRefcount.m; sourceTree = ""; }; 46 | C2D3956212A9DAA30058481D /* MAFDSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MAFDSource.h; sourceTree = ""; }; 47 | C2D3956312A9DAA30058481D /* MAFDSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MAFDSource.m; sourceTree = ""; }; 48 | C2D3959D12A9DD050058481D /* MAAsyncWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MAAsyncWriter.h; sourceTree = ""; }; 49 | C2D3959E12A9DD050058481D /* MAAsyncWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MAAsyncWriter.m; sourceTree = ""; }; 50 | C2D395E712A9E4890058481D /* README.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.markdown; sourceTree = ""; }; 51 | C2D3960412A9EA140058481D /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 52 | C2D3994812AFFF7C0058481D /* MAAsyncHost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MAAsyncHost.h; sourceTree = ""; }; 53 | C2D3994912AFFF7C0058481D /* MAAsyncHost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MAAsyncHost.m; sourceTree = ""; }; 54 | C2D39A0B12B0019F0058481D /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 55 | C2D39AC312B00A820058481D /* MAAsyncSocketUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MAAsyncSocketUtils.h; sourceTree = ""; }; 56 | C2D39AC412B00A820058481D /* MAAsyncSocketUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MAAsyncSocketUtils.m; sourceTree = ""; }; 57 | C2D39D8D12B03A630058481D /* MAAsyncSocketListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MAAsyncSocketListener.h; sourceTree = ""; }; 58 | C2D39D8E12B03A630058481D /* MAAsyncSocketListener.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MAAsyncSocketListener.m; sourceTree = ""; }; 59 | C2D3A23812B179070058481D /* MAAsyncHTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MAAsyncHTTPServer.h; sourceTree = ""; }; 60 | C2D3A23912B179070058481D /* MAAsyncHTTPServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MAAsyncHTTPServer.m; sourceTree = ""; }; 61 | /* End PBXFileReference section */ 62 | 63 | /* Begin PBXFrameworksBuildPhase section */ 64 | 8DD76F9B0486AA7600D96B5E /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */, 69 | C2D39A0C12B0019F0058481D /* CoreServices.framework in Frameworks */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | 08FB7794FE84155DC02AAC07 /* MAAsyncIO */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | C2D3960412A9EA140058481D /* LICENSE */, 80 | C2D395E712A9E4890058481D /* README.markdown */, 81 | 08FB7795FE84155DC02AAC07 /* Source */, 82 | 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, 83 | 1AB674ADFE9D54B511CA2CBB /* Products */, 84 | ); 85 | name = MAAsyncIO; 86 | sourceTree = ""; 87 | }; 88 | 08FB7795FE84155DC02AAC07 /* Source */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 08FB7796FE84155DC02AAC07 /* main.m */, 92 | C27C0A1B12A7314C00FAE8E6 /* MAAsyncReader.h */, 93 | C27C0A1C12A7314C00FAE8E6 /* MAAsyncReader.m */, 94 | C2D3959D12A9DD050058481D /* MAAsyncWriter.h */, 95 | C2D3959E12A9DD050058481D /* MAAsyncWriter.m */, 96 | C2D3994812AFFF7C0058481D /* MAAsyncHost.h */, 97 | C2D3994912AFFF7C0058481D /* MAAsyncHost.m */, 98 | C2D39AC312B00A820058481D /* MAAsyncSocketUtils.h */, 99 | C2D39AC412B00A820058481D /* MAAsyncSocketUtils.m */, 100 | C2D39D8D12B03A630058481D /* MAAsyncSocketListener.h */, 101 | C2D39D8E12B03A630058481D /* MAAsyncSocketListener.m */, 102 | C2D3A23812B179070058481D /* MAAsyncHTTPServer.h */, 103 | C2D3A23912B179070058481D /* MAAsyncHTTPServer.m */, 104 | 64BCBED61356E3DB009E9BFF /* MAHTTPRequest.h */, 105 | 64BCBED71356E3DB009E9BFF /* MAHTTPRequest.m */, 106 | C2D3956212A9DAA30058481D /* MAFDSource.h */, 107 | C2D3956312A9DAA30058481D /* MAFDSource.m */, 108 | C2BB084612AF1AEB00BC232D /* MAFDRefcount.h */, 109 | C2BB084712AF1AEB00BC232D /* MAFDRefcount.m */, 110 | ); 111 | name = Source; 112 | sourceTree = ""; 113 | }; 114 | 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 08FB779EFE84155DC02AAC07 /* Foundation.framework */, 118 | C2D39A0B12B0019F0058481D /* CoreServices.framework */, 119 | ); 120 | name = "External Frameworks and Libraries"; 121 | sourceTree = ""; 122 | }; 123 | 1AB674ADFE9D54B511CA2CBB /* Products */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 8DD76FA10486AA7600D96B5E /* MAAsyncIO */, 127 | ); 128 | name = Products; 129 | sourceTree = ""; 130 | }; 131 | /* End PBXGroup section */ 132 | 133 | /* Begin PBXNativeTarget section */ 134 | 8DD76F960486AA7600D96B5E /* MAAsyncIO */ = { 135 | isa = PBXNativeTarget; 136 | buildConfigurationList = 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "MAAsyncIO" */; 137 | buildPhases = ( 138 | 8DD76F990486AA7600D96B5E /* Sources */, 139 | 8DD76F9B0486AA7600D96B5E /* Frameworks */, 140 | 8DD76F9E0486AA7600D96B5E /* CopyFiles */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | ); 146 | name = MAAsyncIO; 147 | productInstallPath = "$(HOME)/bin"; 148 | productName = MAAsyncIO; 149 | productReference = 8DD76FA10486AA7600D96B5E /* MAAsyncIO */; 150 | productType = "com.apple.product-type.tool"; 151 | }; 152 | /* End PBXNativeTarget section */ 153 | 154 | /* Begin PBXProject section */ 155 | 08FB7793FE84155DC02AAC07 /* Project object */ = { 156 | isa = PBXProject; 157 | buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "MAAsyncIO" */; 158 | compatibilityVersion = "Xcode 3.1"; 159 | developmentRegion = English; 160 | hasScannedForEncodings = 1; 161 | knownRegions = ( 162 | English, 163 | Japanese, 164 | French, 165 | German, 166 | ); 167 | mainGroup = 08FB7794FE84155DC02AAC07 /* MAAsyncIO */; 168 | projectDirPath = ""; 169 | projectRoot = ""; 170 | targets = ( 171 | 8DD76F960486AA7600D96B5E /* MAAsyncIO */, 172 | ); 173 | }; 174 | /* End PBXProject section */ 175 | 176 | /* Begin PBXSourcesBuildPhase section */ 177 | 8DD76F990486AA7600D96B5E /* Sources */ = { 178 | isa = PBXSourcesBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | 8DD76F9A0486AA7600D96B5E /* main.m in Sources */, 182 | C27C0A1D12A7314C00FAE8E6 /* MAAsyncReader.m in Sources */, 183 | C2D3956412A9DAA30058481D /* MAFDSource.m in Sources */, 184 | C2D3959F12A9DD050058481D /* MAAsyncWriter.m in Sources */, 185 | C2BB084812AF1AEB00BC232D /* MAFDRefcount.m in Sources */, 186 | C2D3994A12AFFF7C0058481D /* MAAsyncHost.m in Sources */, 187 | C2D39AC512B00A820058481D /* MAAsyncSocketUtils.m in Sources */, 188 | C2D39D8F12B03A630058481D /* MAAsyncSocketListener.m in Sources */, 189 | C2D3A23A12B179070058481D /* MAAsyncHTTPServer.m in Sources */, 190 | 64BCBED81356E3DB009E9BFF /* MAHTTPRequest.m in Sources */, 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXSourcesBuildPhase section */ 195 | 196 | /* Begin XCBuildConfiguration section */ 197 | 1DEB927508733DD40010E9CD /* Debug */ = { 198 | isa = XCBuildConfiguration; 199 | buildSettings = { 200 | ALWAYS_SEARCH_USER_PATHS = NO; 201 | COPY_PHASE_STRIP = NO; 202 | GCC_DYNAMIC_NO_PIC = NO; 203 | GCC_ENABLE_FIX_AND_CONTINUE = YES; 204 | GCC_MODEL_TUNING = G5; 205 | GCC_OPTIMIZATION_LEVEL = 0; 206 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 207 | INSTALL_PATH = /usr/local/bin; 208 | PRODUCT_NAME = MAAsyncIO; 209 | WARNING_CFLAGS = ( 210 | "-W", 211 | "-Wall", 212 | "-Wno-unused-parameter", 213 | ); 214 | }; 215 | name = Debug; 216 | }; 217 | 1DEB927608733DD40010E9CD /* Release */ = { 218 | isa = XCBuildConfiguration; 219 | buildSettings = { 220 | ALWAYS_SEARCH_USER_PATHS = NO; 221 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 222 | GCC_MODEL_TUNING = G5; 223 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 224 | INSTALL_PATH = /usr/local/bin; 225 | PRODUCT_NAME = MAAsyncIO; 226 | WARNING_CFLAGS = ( 227 | "-W", 228 | "-Wall", 229 | "-Wno-unused-parameter", 230 | ); 231 | }; 232 | name = Release; 233 | }; 234 | 1DEB927908733DD40010E9CD /* Debug */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 238 | GCC_C_LANGUAGE_STANDARD = gnu99; 239 | GCC_OPTIMIZATION_LEVEL = 0; 240 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 241 | GCC_WARN_UNUSED_VARIABLE = YES; 242 | ONLY_ACTIVE_ARCH = YES; 243 | PREBINDING = NO; 244 | SDKROOT = macosx10.6; 245 | }; 246 | name = Debug; 247 | }; 248 | 1DEB927A08733DD40010E9CD /* Release */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; 252 | GCC_C_LANGUAGE_STANDARD = gnu99; 253 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 254 | GCC_WARN_UNUSED_VARIABLE = YES; 255 | PREBINDING = NO; 256 | SDKROOT = macosx10.6; 257 | }; 258 | name = Release; 259 | }; 260 | /* End XCBuildConfiguration section */ 261 | 262 | /* Begin XCConfigurationList section */ 263 | 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "MAAsyncIO" */ = { 264 | isa = XCConfigurationList; 265 | buildConfigurations = ( 266 | 1DEB927508733DD40010E9CD /* Debug */, 267 | 1DEB927608733DD40010E9CD /* Release */, 268 | ); 269 | defaultConfigurationIsVisible = 0; 270 | defaultConfigurationName = Release; 271 | }; 272 | 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "MAAsyncIO" */ = { 273 | isa = XCConfigurationList; 274 | buildConfigurations = ( 275 | 1DEB927908733DD40010E9CD /* Debug */, 276 | 1DEB927A08733DD40010E9CD /* Release */, 277 | ); 278 | defaultConfigurationIsVisible = 0; 279 | defaultConfigurationName = Release; 280 | }; 281 | /* End XCConfigurationList section */ 282 | }; 283 | rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; 284 | } 285 | -------------------------------------------------------------------------------- /MAAsyncReader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MAAsyncReader.h 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/1/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | typedef void (^MAReadCallback)(NSData *data, BOOL prematureEOF); // prematureOF = EOF hit before condition met 13 | 14 | @class MAFDSource; 15 | 16 | @interface MAAsyncReader : NSObject 17 | { 18 | MAFDSource *_fdSource; 19 | int _fd; 20 | 21 | BOOL _reading; 22 | BOOL _isEOF; 23 | 24 | NSMutableData *_buffer; 25 | 26 | void (^_errorHandler)(int); 27 | 28 | NSRange (^_condition)(NSData *); 29 | MAReadCallback _readCallback; 30 | } 31 | 32 | // initialization 33 | - (id)initWithFileDescriptor: (int)fd; // sets fd nonblocking, uses MAFDRetain/MAFDRelease to manage it 34 | 35 | // setup 36 | - (void)setErrorHandler: (void (^)(int err))handlerBlock; 37 | - (void)setTargetQueue: (dispatch_queue_t)queue; // default is normal global queue 38 | 39 | // reading 40 | // condition returns range of delimeter 41 | // everything up to range.location is passed to the callback 42 | // everything within the range is deleted from the buffer 43 | // return NSNotFound in range.location to signal "keep reading" 44 | // or use the MAKeepReading constant 45 | - (void)readUntilCondition: (NSRange (^)(NSData *buffer))condBlock 46 | callback: (MAReadCallback)callbackBlock; 47 | 48 | - (void)readBytes: (NSUInteger)bytes callback: (MAReadCallback)callbackBlock; 49 | - (void)readUntilData: (NSData *)data callback: (MAReadCallback)callbackBlock; 50 | - (void)readUntilCString: (const char *)cstr callback: (MAReadCallback)callbackBlock; 51 | - (void)readUntilEOFCallback: (MAReadCallback)callbackBlock; 52 | 53 | // stopping 54 | - (void)invalidate; 55 | 56 | @end 57 | 58 | extern const NSRange MAKeepReading; 59 | -------------------------------------------------------------------------------- /MAAsyncReader.m: -------------------------------------------------------------------------------- 1 | // 2 | // MAAsyncReader.m 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/1/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import "MAAsyncReader.h" 10 | 11 | #import "MAFDSource.h" 12 | 13 | 14 | const NSRange MAKeepReading = { NSNotFound, 0 }; 15 | 16 | @interface MAAsyncReader () 17 | 18 | - (void)_read; 19 | - (void)_checkCondition; 20 | 21 | @end 22 | 23 | @implementation MAAsyncReader 24 | 25 | - (id)initWithFileDescriptor: (int)fd 26 | { 27 | if((self = [self init])) 28 | { 29 | _fdSource = [[MAFDSource alloc] initWithFileDescriptor: fd type: DISPATCH_SOURCE_TYPE_READ]; 30 | _fd = fd; 31 | 32 | __block MAAsyncReader *weakSelf = self; 33 | [_fdSource setEventCallback: ^{ [weakSelf _read]; }]; 34 | 35 | _buffer = [[NSMutableData alloc] init]; 36 | } 37 | return self; 38 | } 39 | 40 | - (void)dealloc 41 | { 42 | [_fdSource invalidate]; 43 | [_fdSource release]; 44 | [_buffer release]; 45 | [_errorHandler release]; 46 | [_condition release]; 47 | [_readCallback release]; 48 | 49 | [super dealloc]; 50 | } 51 | 52 | - (void)setErrorHandler: (void (^)(int err))handlerBlock 53 | { 54 | handlerBlock = [handlerBlock copy]; 55 | [_errorHandler release]; 56 | _errorHandler = handlerBlock; 57 | } 58 | 59 | - (void)setTargetQueue: (dispatch_queue_t)queue 60 | { 61 | [_fdSource setTargetQueue: queue]; 62 | } 63 | 64 | - (void)readUntilCondition: (NSRange (^)(NSData *buffer))condBlock 65 | callback: (MAReadCallback)callbackBlock 66 | { 67 | NSAssert(!_reading, @"Can't start a MAAsyncReader read while a read is already pending"); 68 | 69 | _reading = YES; 70 | 71 | _condition = [condBlock copy]; 72 | _readCallback = [callbackBlock copy]; 73 | [self retain]; // make sure we stick around until the read is done 74 | 75 | dispatch_async([_fdSource queue], ^{ 76 | [_fdSource resume]; 77 | [self _checkCondition]; 78 | }); 79 | } 80 | 81 | - (void)readBytes: (NSUInteger)bytes callback: (MAReadCallback)callbackBlock 82 | { 83 | [self readUntilCondition: ^(NSData *buffer) { return [buffer length] >= bytes ? NSMakeRange(bytes, 0) : MAKeepReading; } 84 | callback: callbackBlock]; 85 | } 86 | 87 | - (void)readUntilData: (NSData *)data callback: (MAReadCallback)callbackBlock 88 | { 89 | [self readUntilCondition: ^(NSData *buffer) { 90 | return [buffer rangeOfData: data options: 0 range: NSMakeRange(0, [buffer length])]; 91 | } 92 | callback: callbackBlock]; 93 | } 94 | 95 | - (void)readUntilCString: (const char *)cstr callback: (MAReadCallback)callbackBlock 96 | { 97 | [self readUntilData: [NSData dataWithBytes: cstr length: strlen(cstr)] callback: callbackBlock]; 98 | } 99 | 100 | - (void)readUntilEOFCallback: (MAReadCallback)callbackBlock 101 | { 102 | [self readUntilCondition: ^(NSData *buffer) { return MAKeepReading; } callback: callbackBlock]; 103 | } 104 | 105 | - (void)invalidate 106 | { 107 | [_fdSource invalidate]; 108 | } 109 | 110 | - (void)_read 111 | { 112 | NSUInteger howmuch = [_fdSource bytesAvailable]; 113 | howmuch = MAX(howmuch, 128U); // read no less than 128 bytes 114 | howmuch = MIN(howmuch, 8192U); // read no more than 8kB 115 | 116 | NSUInteger oldLength = [_buffer length]; 117 | [_buffer setLength: oldLength + howmuch]; 118 | 119 | ssize_t result = read(_fd, (char *)[_buffer mutableBytes] + oldLength, howmuch); 120 | NSUInteger didRead = MAX(result, 0); // if -1 (got an error), that means we read 0 bytes 121 | [_buffer setLength: oldLength + didRead]; 122 | 123 | if(result < 0) 124 | { 125 | if(errno != EAGAIN && errno != EINTR) 126 | if(_errorHandler) 127 | _errorHandler(errno); 128 | } 129 | else 130 | { 131 | if(result == 0) 132 | _isEOF = YES; 133 | [self _checkCondition]; 134 | } 135 | } 136 | 137 | - (void)_fireReadCallback: (NSData *)data prematureEOF: (BOOL)flag 138 | { 139 | // a fancy dance so that the callback can set up a new callback without breaking everything 140 | MAReadCallback localReadCallback = _readCallback; 141 | _readCallback = nil; 142 | 143 | [_condition release]; 144 | _condition = nil; 145 | 146 | localReadCallback(data, flag); 147 | [localReadCallback release]; 148 | 149 | [self release]; // balance the retain from readUntilCondition: 150 | } 151 | 152 | - (void)_checkCondition 153 | { 154 | if(_condition) 155 | { 156 | NSRange r = _condition(_buffer); 157 | if(r.location != NSNotFound) 158 | { 159 | _reading = NO; 160 | [_fdSource suspend]; 161 | 162 | NSRange chunkRange = NSMakeRange(0, r.location); 163 | NSData *chunk = [_buffer subdataWithRange: chunkRange]; 164 | 165 | NSRange deleteRange = NSMakeRange(0, NSMaxRange(r)); 166 | [_buffer replaceBytesInRange: deleteRange withBytes: NULL length: 0]; 167 | 168 | [self _fireReadCallback: chunk prematureEOF: NO]; 169 | } 170 | else if(_isEOF) 171 | { 172 | [_fdSource suspend]; 173 | [self _fireReadCallback: _buffer prematureEOF: YES]; 174 | } 175 | } 176 | } 177 | 178 | @end 179 | -------------------------------------------------------------------------------- /MAAsyncSocketListener.h: -------------------------------------------------------------------------------- 1 | // 2 | // MAAsyncSocketListener.h 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/8/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @class MAAsyncReader; 13 | @class MAAsyncWriter; 14 | @class MAFDSource; 15 | 16 | @interface MAAsyncSocketListener : NSObject 17 | { 18 | } 19 | 20 | + (id)listenerWithAddress: (NSData *)address error: (NSError **)error; 21 | + (id)listenerWith4and6WithPortRange: (NSRange)r tryRandom: (BOOL)tryRandomPorts error: (NSError **)error; 22 | 23 | - (int)port; 24 | 25 | - (void)setAcceptCallback: (void (^)(MAAsyncReader *reader, MAAsyncWriter *writer, NSData *peerAddress))block; 26 | 27 | - (void)invalidate; 28 | 29 | @end 30 | 31 | @interface MAAsyncSimpleSocketListener : MAAsyncSocketListener 32 | { 33 | MAFDSource *_source; 34 | int _fd; 35 | int _port; 36 | 37 | void (^_callback)(MAAsyncReader *reader, MAAsyncWriter *writer, NSData *peerAddress); 38 | } 39 | 40 | - (id)initWithAddress: (NSData *)address error: (NSError **)error; 41 | 42 | @end 43 | 44 | @interface MAAsyncCompoundSocketListener : MAAsyncSocketListener 45 | { 46 | NSMutableArray *_innerListeners; 47 | } 48 | 49 | - (void)addListener: (MAAsyncSocketListener *)listener; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /MAAsyncSocketListener.m: -------------------------------------------------------------------------------- 1 | // 2 | // MAAsyncSocketListener.m 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/8/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import "MAAsyncSocketListener.h" 10 | 11 | #import 12 | #import 13 | #import 14 | 15 | #import "MAAsyncReader.h" 16 | #import "MAAsyncWriter.h" 17 | #import "MAFDRefcount.h" 18 | #import "MAFDSource.h" 19 | 20 | 21 | @implementation MAAsyncSocketListener 22 | 23 | + (id)listenerWithAddress: (NSData *)address error: (NSError **)error 24 | { 25 | return [[[MAAsyncSimpleSocketListener alloc] initWithAddress: address error: error] autorelease]; 26 | } 27 | 28 | + (id)listenerWith4and6WithPortRange: (NSRange)r tryRandom: (BOOL)tryRandomPorts error: (NSError **)error 29 | { 30 | NSError *localError = nil; 31 | if(!error) 32 | error = &localError; 33 | 34 | NSMutableData *addr4data = [NSMutableData dataWithLength: sizeof(struct sockaddr_in)]; 35 | NSMutableData *addr6data = [NSMutableData dataWithLength: sizeof(struct sockaddr_in6)]; 36 | struct sockaddr_in *addr4 = [addr4data mutableBytes]; 37 | struct sockaddr_in6 *addr6 = [addr6data mutableBytes]; 38 | 39 | addr4->sin_len = sizeof(*addr4); 40 | addr6->sin6_len = sizeof(*addr6); 41 | 42 | addr4->sin_family = AF_INET; 43 | addr6->sin6_family = AF_INET6; 44 | 45 | addr4->sin_addr.s_addr = INADDR_ANY; 46 | addr6->sin6_addr = in6addr_any; 47 | 48 | MAAsyncCompoundSocketListener *listener = nil; 49 | 50 | for(NSUInteger i = 0; tryRandomPorts || i < r.length; i++) 51 | { 52 | int port = (i < r.length 53 | ? i + r.location 54 | : mach_absolute_time()) % 65536; 55 | 56 | // 0 has special meaning, which screws up the rest of the code 57 | if(port == 0) 58 | continue; 59 | 60 | addr4->sin_port = htons(port); 61 | addr6->sin6_port = htons(port); 62 | 63 | MAAsyncSimpleSocketListener *listener4 = [[MAAsyncSimpleSocketListener alloc] initWithAddress: addr4data error: error]; 64 | if(listener4) 65 | { 66 | MAAsyncSimpleSocketListener *listener6 = [[MAAsyncSimpleSocketListener alloc] initWithAddress: addr6data error: error]; 67 | if(listener6) 68 | { 69 | listener = [[MAAsyncCompoundSocketListener alloc] init]; 70 | [listener addListener: listener4]; 71 | [listener addListener: listener6]; 72 | [listener4 release]; 73 | [listener6 release]; 74 | [listener autorelease]; 75 | break; 76 | } 77 | // everything from here is an error case 78 | [listener4 release]; 79 | } 80 | 81 | // if it's NOT EADDRINUSE or EACCES then report it 82 | if(![[*error domain] isEqual: NSPOSIXErrorDomain] || 83 | ([*error code] != EADDRINUSE && [*error code] != EACCES)) 84 | break; 85 | } 86 | 87 | return listener; 88 | } 89 | 90 | - (int)port 91 | { 92 | [self doesNotRecognizeSelector: _cmd]; 93 | return 0; 94 | } 95 | 96 | - (void)setAcceptCallback: (void (^)(MAAsyncReader *reader, MAAsyncWriter *writer, NSData *peerAddress))block 97 | { 98 | [self doesNotRecognizeSelector: _cmd]; 99 | } 100 | 101 | - (void)invalidate 102 | { 103 | [self doesNotRecognizeSelector: _cmd]; 104 | } 105 | 106 | @end 107 | 108 | @interface MAAsyncSimpleSocketListener () 109 | 110 | - (void)_accept; 111 | 112 | @end 113 | 114 | @implementation MAAsyncSimpleSocketListener 115 | 116 | - (id)initWithAddress: (NSData *)address error: (NSError **)error 117 | { 118 | if((self = [self init])) 119 | { 120 | const struct sockaddr_in *addrPtr = [address bytes]; 121 | 122 | int fd = socket(addrPtr->sin_family, SOCK_STREAM, 0); 123 | if(fd == -1) 124 | { 125 | if(error) 126 | *error = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil]; 127 | [self release]; 128 | return nil; 129 | } 130 | 131 | int yes = 1; 132 | setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); 133 | 134 | int result = bind(fd, [address bytes], [address length]); 135 | if(result == -1) 136 | { 137 | if(error) 138 | *error = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil]; 139 | close(fd); 140 | [self release]; 141 | return nil; 142 | } 143 | 144 | result = listen(fd, SOMAXCONN); 145 | if(result == -1) 146 | { 147 | if(error) 148 | *error = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil]; 149 | close(fd); 150 | [self release]; 151 | return nil; 152 | } 153 | 154 | _source = [[MAFDSource alloc] initWithFileDescriptor: fd type: DISPATCH_SOURCE_TYPE_READ]; 155 | MAFDRelease(fd); 156 | _fd = fd; 157 | _port = ntohs(addrPtr->sin_port); 158 | 159 | __block MAAsyncSimpleSocketListener *weakSelf = self; 160 | [_source setEventCallback: ^{ [weakSelf _accept]; }]; 161 | } 162 | return self; 163 | } 164 | 165 | - (void)dealloc 166 | { 167 | [self invalidate]; 168 | 169 | [super dealloc]; 170 | } 171 | 172 | - (int)port 173 | { 174 | return _port; 175 | } 176 | 177 | - (void)setAcceptCallback: (void (^)(MAAsyncReader *reader, MAAsyncWriter *writer, NSData *peerAddress))block 178 | { 179 | if(!_source) 180 | return; 181 | 182 | dispatch_async([_source queue], ^{ 183 | if(!_callback && block) 184 | [_source resume]; 185 | if(_callback && !block) 186 | [_source suspend]; 187 | 188 | id copiedBlock = [block copy]; 189 | [_callback release]; 190 | _callback = copiedBlock; 191 | }); 192 | } 193 | 194 | - (void)invalidate 195 | { 196 | [self setAcceptCallback: nil]; 197 | [_source invalidate]; 198 | } 199 | 200 | - (void)_accept 201 | { 202 | NSMutableData *peerAddress = [NSMutableData dataWithLength: 256]; 203 | socklen_t peerLen = [peerAddress length]; 204 | int newfd = accept(_fd, [peerAddress mutableBytes], &peerLen); 205 | if(newfd == -1) 206 | { 207 | NSLog(@"%s error %d (%s)", __func__, errno, strerror(errno)); 208 | return; 209 | } 210 | 211 | [peerAddress setLength: peerLen]; 212 | 213 | MAAsyncReader *reader = [[MAAsyncReader alloc] initWithFileDescriptor: newfd]; 214 | MAAsyncWriter *writer = [[MAAsyncWriter alloc] initWithFileDescriptor: newfd]; 215 | MAFDRelease(newfd); 216 | 217 | _callback(reader, writer, peerAddress); 218 | [reader release]; 219 | [writer release]; 220 | } 221 | 222 | @end 223 | 224 | @implementation MAAsyncCompoundSocketListener 225 | 226 | - (id)init 227 | { 228 | if((self = [super init])) 229 | { 230 | _innerListeners = [[NSMutableArray alloc] init]; 231 | } 232 | return self; 233 | } 234 | 235 | - (void)dealloc 236 | { 237 | [self invalidate]; 238 | [_innerListeners release]; 239 | 240 | [super dealloc]; 241 | } 242 | 243 | - (void)addListener: (MAAsyncSocketListener *)listener 244 | { 245 | [_innerListeners addObject: listener]; 246 | } 247 | 248 | - (int)port 249 | { 250 | return [_innerListeners count] ? [(MAAsyncSocketListener *)[_innerListeners lastObject] port] : -1; 251 | } 252 | 253 | - (void)setAcceptCallback: (void (^)(MAAsyncReader *reader, MAAsyncWriter *writer, NSData *peerAddress))block 254 | { 255 | for(MAAsyncSocketListener *listener in _innerListeners) 256 | [listener setAcceptCallback: block]; 257 | } 258 | 259 | - (void)invalidate 260 | { 261 | for(MAAsyncSocketListener *listener in _innerListeners) 262 | [listener invalidate]; 263 | } 264 | 265 | @end 266 | 267 | -------------------------------------------------------------------------------- /MAAsyncSocketUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // MAAsyncSocketUtils.h 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/8/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @class MAAsyncReader; 13 | @class MAAsyncWriter; 14 | 15 | void MAAsyncSocketConnect(NSData *address, int port, void (^block)(MAAsyncReader *reader, MAAsyncWriter *writer, NSError *error)); 16 | -------------------------------------------------------------------------------- /MAAsyncSocketUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // MAAsyncSocketUtils.m 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/8/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import "MAAsyncSocketUtils.h" 10 | 11 | #import 12 | 13 | #import "MAAsyncReader.h" 14 | #import "MAAsyncWriter.h" 15 | #import "MAFDRefcount.h" 16 | #import "MAFDSource.h" 17 | 18 | 19 | void MAAsyncSocketConnect(NSData *address, int port, void (^block)(MAAsyncReader *reader, MAAsyncWriter *writer, NSError *error)) 20 | { 21 | char localaddr[[address length]]; 22 | [address getBytes: localaddr]; 23 | 24 | struct sockaddr *sockaddr = (struct sockaddr *)localaddr; 25 | ((struct sockaddr_in *)sockaddr)->sin_port = htons(port); 26 | 27 | int fd = socket(sockaddr->sa_family, SOCK_STREAM, 0); 28 | if(fd == -1) 29 | { 30 | block(nil, nil, [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil]); 31 | } 32 | else 33 | { 34 | // implicitly sets the socket nonblocking 35 | MAFDSource *source = [[MAFDSource alloc] initWithFileDescriptor: fd type: DISPATCH_SOURCE_TYPE_WRITE]; 36 | MAFDRelease(fd); 37 | 38 | int result = connect(fd, sockaddr, [address length]); 39 | 40 | void (^completion)(void) = ^{ 41 | [source suspend]; 42 | 43 | int so_err, gso_err; 44 | socklen_t len = sizeof(so_err); 45 | gso_err = getsockopt(fd, SOL_SOCKET, SO_ERROR, &so_err, &len); 46 | if(gso_err) 47 | { 48 | block(nil, nil, [NSError errorWithDomain: NSPOSIXErrorDomain code:gso_err userInfo: nil]); 49 | } 50 | else if(so_err) 51 | { 52 | block(nil, nil, [NSError errorWithDomain: NSPOSIXErrorDomain code:so_err userInfo: nil]); 53 | } 54 | else 55 | { 56 | MAAsyncReader *reader = [[MAAsyncReader alloc] initWithFileDescriptor: fd]; 57 | MAAsyncWriter *writer = [[MAAsyncWriter alloc] initWithFileDescriptor: fd]; 58 | block(reader, writer, nil); 59 | [reader release]; 60 | [writer release]; 61 | } 62 | [source invalidate]; 63 | [source release]; 64 | }; 65 | 66 | if(result != -1) 67 | { 68 | completion(); 69 | } 70 | else if(errno == EINPROGRESS) 71 | { 72 | [source setEventCallback: completion]; 73 | [source resume]; 74 | } 75 | else 76 | { 77 | block(nil, nil, [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil]); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /MAAsyncWriter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MAAsyncWriter.h 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/3/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @class MAFDSource; 13 | 14 | @interface MAAsyncWriter : NSObject 15 | { 16 | MAFDSource *_fdSource; 17 | int _fd; 18 | 19 | NSMutableData *_buffer; 20 | 21 | void (^_errorHandler)(int); 22 | void (^_didWriteCallback)(void); 23 | void (^_eofCallback)(void); 24 | 25 | BOOL _invalidateWhenEmptyBuffer; 26 | } 27 | 28 | // initialization 29 | - (id)initWithFileDescriptor: (int)fd; // sets fd nonblocking, uses MAFDRetain/MAFDRelease to manage it 30 | 31 | // setup 32 | - (void)setErrorHandler: (void (^)(int err))block; 33 | - (void)setTargetQueue: (dispatch_queue_t)queue; // default is normal global queue 34 | 35 | // notification 36 | - (void)setDidWriteCallback: (void (^)(void))block; 37 | - (void)setEOFCallback: (void (^)(void))block; 38 | 39 | // writing 40 | - (void)writeData: (NSData *)data; 41 | - (void)writeCString: (const char *)cstr; 42 | 43 | // buffer inspection, can only be called from a callback 44 | - (NSUInteger)bufferSize; 45 | 46 | // stopping 47 | - (void)invalidate; 48 | - (void)invalidateWhenEmptyBuffer; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /MAAsyncWriter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MAAsyncWriter.m 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/3/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import "MAAsyncWriter.h" 10 | 11 | #import "MAFDSource.h" 12 | 13 | 14 | @interface MAAsyncWriter () 15 | 16 | - (void)_write; 17 | 18 | @end 19 | 20 | @implementation MAAsyncWriter 21 | 22 | - (id)initWithFileDescriptor: (int)fd 23 | { 24 | if((self = [self init])) 25 | { 26 | _fdSource = [[MAFDSource alloc] initWithFileDescriptor: fd type: DISPATCH_SOURCE_TYPE_WRITE]; 27 | _fd = fd; 28 | 29 | __block MAAsyncWriter *weakSelf = self; 30 | [_fdSource setEventCallback: ^{ [weakSelf _write]; }]; 31 | 32 | _buffer = [[NSMutableData alloc] init]; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)dealloc 38 | { 39 | [self invalidate]; 40 | 41 | [super dealloc]; 42 | } 43 | 44 | - (void)setErrorHandler: (void (^)(int err))block 45 | { 46 | block = [block copy]; 47 | [_errorHandler release]; 48 | _errorHandler = block; 49 | } 50 | 51 | - (void)setTargetQueue: (dispatch_queue_t)queue 52 | { 53 | [_fdSource setTargetQueue: queue]; 54 | } 55 | 56 | - (void)setDidWriteCallback: (void (^)(void))block 57 | { 58 | block = [block copy]; 59 | [_didWriteCallback release]; 60 | _didWriteCallback = block; 61 | } 62 | 63 | - (void)setEOFCallback: (void (^)(void))block 64 | { 65 | block = [block copy]; 66 | [_eofCallback release]; 67 | _eofCallback = block; 68 | } 69 | 70 | - (void)writeData: (NSData *)data 71 | { 72 | dispatch_async([_fdSource queue], ^{ 73 | NSUInteger previousBufferLength = [_buffer length]; 74 | [_buffer appendData: data]; 75 | if(!previousBufferLength) 76 | { 77 | [self retain]; // keep the object alive until it's done writing 78 | [_fdSource resume]; 79 | } 80 | }); 81 | } 82 | 83 | - (void)writeCString: (const char *)cstr 84 | { 85 | [self writeData: [NSData dataWithBytes: cstr length: strlen(cstr)]]; 86 | } 87 | 88 | - (NSUInteger)bufferSize 89 | { 90 | return [_buffer length]; 91 | } 92 | 93 | - (void)invalidate 94 | { 95 | [_fdSource invalidate]; 96 | 97 | [_fdSource release]; 98 | _fdSource = nil; 99 | 100 | [_buffer release]; 101 | _buffer = nil; 102 | 103 | [_errorHandler release]; 104 | _errorHandler = nil; 105 | 106 | [_didWriteCallback release]; 107 | _didWriteCallback = nil; 108 | 109 | [_eofCallback release]; 110 | _eofCallback = nil; 111 | } 112 | 113 | - (void)invalidateWhenEmptyBuffer { 114 | dispatch_async([_fdSource queue], ^{ 115 | if([_buffer length] == 0) 116 | [self invalidate]; 117 | else 118 | _invalidateWhenEmptyBuffer = YES; 119 | }); 120 | } 121 | 122 | - (void)_write 123 | { 124 | BOOL emptyBuffer = YES; 125 | 126 | if([_buffer length]) 127 | { 128 | ssize_t result = write(_fd, [_buffer bytes], [_buffer length]); 129 | NSUInteger didWrite = MAX(result, 0); // -1 (error) means wrote 0 bytes 130 | [_buffer replaceBytesInRange: NSMakeRange(0, didWrite) withBytes: NULL length: 0]; 131 | emptyBuffer = ![_buffer length]; 132 | 133 | if(emptyBuffer) 134 | [_fdSource suspend]; 135 | 136 | if(result < 0) 137 | { 138 | if(errno != EAGAIN && errno != EINTR) 139 | if(_errorHandler) 140 | _errorHandler(errno); 141 | } 142 | else if(result == 0) 143 | { 144 | if(_eofCallback) 145 | _eofCallback(); 146 | } 147 | else 148 | { 149 | if(_didWriteCallback) 150 | _didWriteCallback(); 151 | } 152 | } 153 | 154 | if(emptyBuffer) 155 | { 156 | if(_invalidateWhenEmptyBuffer) 157 | [self invalidate]; 158 | [self release]; // balance the retain in writeData: 159 | } 160 | } 161 | 162 | @end 163 | -------------------------------------------------------------------------------- /MAFDRefcount.h: -------------------------------------------------------------------------------- 1 | // 2 | // MAFDRefcount.h 3 | // MAAsyncIO 4 | // 5 | // Created by Mike Ash on 12/7/10. 6 | // Copyright 2010 Mike Ash. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | int MAFDRetain(int fd); 13 | void MAFDRelease(int fd); 14 | -------------------------------------------------------------------------------- /MAFDRefcount.m: -------------------------------------------------------------------------------- 1 | // 2 | // MAFDRefcount.m 3 | // MAAsyncIO 4 | // 5 | // Created by Mike Ash on 12/7/10. 6 | // Copyright 2010 Mike Ash. All rights reserved. 7 | // 8 | 9 | #import "MAFDRefcount.h" 10 | 11 | 12 | static CFMutableDictionaryRef gRefcounts; 13 | static dispatch_queue_t gQueue; 14 | 15 | static void Init(void) 16 | { 17 | static dispatch_once_t pred; 18 | dispatch_once(&pred, ^{ 19 | gRefcounts = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); 20 | gQueue = dispatch_queue_create("com.mikesah.MAFDRefcount", NULL); 21 | }); 22 | } 23 | 24 | static int GetRefcount(int fd) 25 | { 26 | void *value; 27 | Boolean present = CFDictionaryGetValueIfPresent(gRefcounts, (void *)(intptr_t)fd, (void *)&value); 28 | return present ? (intptr_t)value : 1; 29 | } 30 | 31 | static void SetRefcount(int fd, int count) 32 | { 33 | if(count == 1) // 1 is represented by not having an entry 34 | CFDictionaryRemoveValue(gRefcounts, (void *)(intptr_t)fd); 35 | else 36 | CFDictionarySetValue(gRefcounts, (void *)(intptr_t)fd, (void *)(intptr_t)count); 37 | } 38 | 39 | static void Destroy(int fd) 40 | { 41 | close(fd); 42 | } 43 | 44 | int MAFDRetain(int fd) 45 | { 46 | Init(); 47 | 48 | dispatch_sync(gQueue, ^{ 49 | SetRefcount(fd, GetRefcount(fd) + 1); 50 | }); 51 | 52 | return fd; 53 | } 54 | 55 | void MAFDRelease(int fd) 56 | { 57 | Init(); 58 | 59 | dispatch_sync(gQueue, ^{ 60 | int count = GetRefcount(fd); 61 | if(count == 1) 62 | Destroy(fd); 63 | else 64 | SetRefcount(fd, count - 1); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /MAFDSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // MAFDSource.h 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/3/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | #define FD_SOURCE_DEBUG 1 13 | 14 | @interface MAFDSource : NSObject 15 | { 16 | dispatch_source_t _source; 17 | dispatch_queue_t _queue; 18 | int _fd; 19 | 20 | #if FD_SOURCE_DEBUG 21 | int _suspendCount; 22 | #endif 23 | } 24 | 25 | - (id)initWithFileDescriptor: (int)fd type: (dispatch_source_type_t)type; // takes ownership of fd 26 | 27 | - (void)setEventCallback: (dispatch_block_t)block; 28 | - (void)setTargetQueue: (dispatch_queue_t)queue; 29 | - (dispatch_queue_t)queue; 30 | 31 | - (NSUInteger)bytesAvailable; 32 | 33 | - (void)suspend; 34 | - (void)resume; 35 | - (void)invalidate; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /MAFDSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // MAFDSource.m 3 | // MAAsyncIO 4 | // 5 | // Created by Michael Ash on 12/3/10. 6 | // Copyright 2010 Michael Ash. All rights reserved. 7 | // 8 | 9 | #import "MAFDSource.h" 10 | 11 | #import "MAFDRefcount.h" 12 | 13 | 14 | @implementation MAFDSource 15 | 16 | - (id)initWithFileDescriptor: (int)fd type: (dispatch_source_type_t)type 17 | { 18 | if((self = [self init])) 19 | { 20 | _queue = dispatch_queue_create("com.mikeash.MAAsyncReader", NULL); 21 | 22 | _fd = MAFDRetain(fd); 23 | 24 | _source = dispatch_source_create(type, fd, 0, _queue); 25 | dispatch_source_set_cancel_handler(_source, ^{ MAFDRelease(fd); }); 26 | 27 | #if FD_SOURCE_DEBUG 28 | _suspendCount = 1; 29 | #endif 30 | 31 | int flags = fcntl(_fd, F_GETFL, 0); 32 | fcntl(_fd, F_SETFL, flags | O_NONBLOCK); 33 | } 34 | return self; 35 | } 36 | 37 | - (void)dealloc 38 | { 39 | [self invalidate]; 40 | dispatch_release(_queue); 41 | 42 | [super dealloc]; 43 | } 44 | 45 | - (void)setEventCallback: (dispatch_block_t)block 46 | { 47 | dispatch_source_set_event_handler(_source, block); 48 | } 49 | 50 | - (void)setTargetQueue: (dispatch_queue_t)queue 51 | { 52 | dispatch_set_target_queue(_queue, queue); 53 | } 54 | 55 | - (dispatch_queue_t)queue 56 | { 57 | return _queue; 58 | } 59 | 60 | - (NSUInteger)bytesAvailable 61 | { 62 | return dispatch_source_get_data(_source); 63 | } 64 | 65 | - (void)suspend 66 | { 67 | dispatch_suspend(_source); 68 | #if FD_SOURCE_DEBUG 69 | _suspendCount++; 70 | #endif 71 | } 72 | 73 | - (void)resume 74 | { 75 | dispatch_resume(_source); 76 | #if FD_SOURCE_DEBUG 77 | _suspendCount--; 78 | assert(_suspendCount >= 0); 79 | #endif 80 | } 81 | 82 | - (void)invalidate 83 | { 84 | dispatch_block_t block = ^{ 85 | #if FD_SOURCE_DEBUG 86 | if(_suspendCount != 1) 87 | { 88 | NSLog(@"invalidating %@ with suspend count of %d", self, _suspendCount); 89 | abort(); 90 | } 91 | #endif 92 | if(_source) 93 | { 94 | dispatch_resume(_source); 95 | dispatch_source_cancel(_source); 96 | dispatch_release(_source); 97 | _source = NULL; 98 | } 99 | }; 100 | 101 | if(dispatch_get_current_queue() != _queue) 102 | dispatch_sync(_queue, block); 103 | else 104 | block(); 105 | } 106 | 107 | @end 108 | -------------------------------------------------------------------------------- /MAHTTPRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // MAHTTPRequest.h 3 | // MAAsyncIO 4 | // 5 | // Created by Raphael Bartolome on 14.04.11. 6 | // Copyright 2011 Raphael Bartolome. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | enum kMAHTTPMethod { 12 | kMAHTTPGetMethod, 13 | kMAHTTPPostMethod, 14 | kMAHTTPPutMethod, 15 | kMAHTTPHeadMethod, 16 | kMAHTTPDeleteMethod, 17 | kMAHTTPTraceMethod, 18 | kMAHTTPOptionsMethod, 19 | kMAHTTPConnectMethod, 20 | kMAHTTPPathMethod, 21 | kMAHTTPNotDefined, 22 | }; 23 | 24 | typedef NSUInteger MAHTTPMethod; 25 | 26 | @interface MAHTTPRequest : NSObject { 27 | @private 28 | NSString *_resource; 29 | NSString *_method; 30 | NSInteger _headerLength; 31 | NSMutableDictionary *_header; 32 | NSMutableDictionary *_formValues; 33 | NSData *_content; 34 | } 35 | 36 | - (id)initWithHeader: (NSData *)header; 37 | 38 | - (NSDictionary *)header; 39 | - (NSInteger)headerLength; 40 | 41 | - (NSString *)methodString; 42 | - (MAHTTPMethod)method; 43 | - (NSString *)resource; 44 | - (NSString *)resourceExtension; 45 | 46 | - (NSInteger)expectedContentLength; 47 | 48 | - (id)formValueForKey: (NSString *)key; 49 | - (NSDictionary *)formValues; 50 | 51 | - (void)setContent: (NSData *)data; 52 | - (NSData *)content; 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /MAHTTPRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // MAHTTPRequest.m 3 | // MAAsyncIO 4 | // 5 | // Created by Raphael Bartolome on 14.04.11. 6 | // Copyright 2011 Raphael Bartolome. All rights reserved. 7 | // 8 | 9 | #import "MAHTTPRequest.h" 10 | 11 | 12 | /* 13 | kMAHTTPGetMethod, 14 | kMAHTTPPostMethod, 15 | kMAHTTPPutMethod, 16 | kMAHTTPHeadMethod, 17 | kMAHTTPDeleteMethod, 18 | kMAHTTPTraceMethod, 19 | kMAHTTPOptionsMethod, 20 | kMAHTTPConnectMethod, 21 | kMAHTTPPathMethod, 22 | kMAHTTPNotDefined, 23 | */ 24 | @implementation MAHTTPRequest 25 | 26 | - (void)setMethod: (NSString *)method 27 | { 28 | [_method release]; 29 | _method = [method copy]; 30 | } 31 | 32 | - (NSString *)_decodeURL: (NSString *)string 33 | { 34 | [string retain]; 35 | NSString *result = (NSString *)CFURLCreateStringByReplacingPercentEscapes(kCFAllocatorDefault, 36 | (CFStringRef)string, 37 | CFSTR("")); 38 | 39 | if(result == NULL) 40 | { 41 | return [string autorelease]; 42 | } 43 | else 44 | { 45 | [string release]; 46 | return [result autorelease]; 47 | } 48 | } 49 | 50 | - (void)_parseFormValues: (NSString *)kvps 51 | { 52 | [kvps retain]; 53 | NSArray *splitKVP = [kvps componentsSeparatedByString: @"&"]; 54 | 55 | for(NSUInteger i = 0; i < [splitKVP count]; i++) 56 | { 57 | NSString *kvp = [splitKVP objectAtIndex:i]; 58 | NSScanner *scanner = [[NSScanner alloc] initWithString:kvp]; 59 | 60 | NSString *key = NULL; 61 | NSString *value = NULL; 62 | if([scanner scanUpToString:@"=" intoString:&key]) 63 | { 64 | value = [kvp substringFromIndex:[scanner scanLocation]+1]; 65 | } 66 | 67 | NSString *decodedKey = [[self _decodeURL:key] retain]; 68 | NSString *decodedValue = [[self _decodeURL:value] retain]; 69 | 70 | [_formValues setObject:decodedValue forKey:decodedKey]; 71 | 72 | [decodedKey release]; 73 | [decodedValue release]; 74 | 75 | [scanner release]; 76 | } 77 | 78 | [kvps release]; 79 | } 80 | 81 | - (void)_parseResource: (NSString *)resource 82 | { 83 | [resource retain]; 84 | 85 | if([self method] != kMAHTTPPostMethod) 86 | { 87 | NSArray *splitMethodValues = [resource componentsSeparatedByString: @"?"]; 88 | 89 | if([splitMethodValues count] >= 1) 90 | { 91 | _resource = [[splitMethodValues objectAtIndex:0] copy]; 92 | 93 | if([splitMethodValues count] > 1) 94 | { 95 | [self _parseFormValues:[splitMethodValues objectAtIndex:1]]; 96 | } 97 | } 98 | } 99 | else 100 | { 101 | _resource = [resource copy]; 102 | } 103 | 104 | [resource release]; 105 | } 106 | 107 | - (void)_parseHeader: (NSData *)header 108 | { 109 | [header retain]; 110 | 111 | _headerLength = [header length]; 112 | 113 | NSString *headerAsString = [[NSString alloc] initWithData: header encoding: NSUTF8StringEncoding]; 114 | NSArray *parts = [headerAsString componentsSeparatedByString: @"\n"]; 115 | 116 | for(NSUInteger i = 0; i < [parts count]; i++) 117 | { 118 | if(i == 0) 119 | { 120 | NSArray *methodSplit = [[parts objectAtIndex:0] componentsSeparatedByString: @" "]; 121 | [self setMethod:[methodSplit objectAtIndex:0]]; 122 | [self _parseResource:[methodSplit objectAtIndex:1]]; 123 | } 124 | else 125 | { 126 | NSArray *partSplit = [[parts objectAtIndex:i] componentsSeparatedByString: @" "]; 127 | NSString *keyPart = [partSplit objectAtIndex:0]; 128 | NSString *key = [keyPart substringToIndex:[keyPart lengthOfBytesUsingEncoding:NSUTF8StringEncoding]-1]; 129 | [_header setObject:[partSplit objectAtIndex:1] forKey:key]; 130 | } 131 | } 132 | 133 | [headerAsString release]; 134 | [header release]; 135 | } 136 | 137 | - (id)initWithHeader: (NSData *)header 138 | { 139 | if ((self = [super init])) 140 | { 141 | _header = [[NSMutableDictionary alloc] initWithCapacity:0]; 142 | _formValues = [[NSMutableDictionary alloc] initWithCapacity:0]; 143 | 144 | [self _parseHeader:header]; 145 | } 146 | 147 | return self; 148 | } 149 | 150 | - (NSDictionary *)header 151 | { 152 | return _header; 153 | } 154 | 155 | - (NSInteger)headerLength 156 | { 157 | return _headerLength; 158 | } 159 | 160 | - (NSString *)methodString 161 | { 162 | return _method; 163 | } 164 | 165 | - (MAHTTPMethod)method 166 | { 167 | if([self methodString]) 168 | { 169 | if([[self methodString] isEqualToString:@"GET"]) 170 | return kMAHTTPGetMethod; 171 | else if([[self methodString] isEqualToString:@"POST"]) 172 | return kMAHTTPPostMethod; 173 | else if([[self methodString] isEqualToString:@"PUT"]) 174 | return kMAHTTPPutMethod; 175 | else if([[self methodString] isEqualToString:@"HEAD"]) 176 | return kMAHTTPHeadMethod; 177 | else if([[self methodString] isEqualToString:@"DELETE"]) 178 | return kMAHTTPDeleteMethod; 179 | else if([[self methodString] isEqualToString:@"TRACE"]) 180 | return kMAHTTPTraceMethod; 181 | else if([[self methodString] isEqualToString:@"OPTIONS"]) 182 | return kMAHTTPOptionsMethod; 183 | else if([[self methodString] isEqualToString:@"CONNECT"]) 184 | return kMAHTTPConnectMethod; 185 | else if([[self methodString] isEqualToString:@"PATH"]) 186 | return kMAHTTPPathMethod; 187 | } 188 | 189 | return kMAHTTPNotDefined; 190 | } 191 | 192 | - (NSString *)resource 193 | { 194 | return _resource; 195 | } 196 | 197 | - (NSString *)resourceExtension 198 | { 199 | return [_resource pathExtension]; 200 | } 201 | 202 | - (NSInteger)expectedContentLength 203 | { 204 | if([_header objectForKey:@"Content-Length"]) 205 | return [[_header objectForKey:@"Content-Length"] integerValue]; 206 | 207 | return 0; 208 | } 209 | 210 | - (id)formValueForKey: (NSString *)key 211 | { 212 | return [_formValues objectForKey:key]; 213 | } 214 | 215 | - (NSDictionary *)formValues 216 | { 217 | return _formValues; 218 | } 219 | 220 | - (void)setContent: (NSData *)data 221 | { 222 | [_content release]; 223 | _content = [data copy]; 224 | 225 | if([self method] == kMAHTTPPostMethod && 226 | [_header objectForKey:@"Content-Type"] && 227 | [[_header objectForKey:@"Content-Type"] isEqualToString:@"application/x-www-form-urlencoded"]) 228 | { 229 | [self _parseFormValues:[NSString stringWithUTF8String:[data bytes]]]; 230 | } 231 | } 232 | 233 | - (NSData *)content 234 | { 235 | return _content; 236 | } 237 | 238 | - (void)dealloc 239 | { 240 | [_formValues release]; 241 | [_method release]; 242 | [_resource release]; 243 | [_content release]; 244 | [_header release]; 245 | [super dealloc]; 246 | } 247 | 248 | @end 249 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | MAAsyncIO 2 | --------- 3 | 4 | MAAsyncIO is a wrapper around Grand Central Dispatch file descriptor sources. It's still a work in progress. Please feel free to make additions or requests. 5 | 6 | MAAsyncIO is distributed under a BSD license, which can be found in the LICENSE file. 7 | 8 | 9 | Reading 10 | ------- 11 | 12 | `MAAsyncReader` handles reading. Create one using `initWithFileDescriptor:`. Optionally set an error handler and a target queue. 13 | 14 | The basic reading method is `readUntilCondition:callback:`. This reads data from the file descriptor into a buffer. Each time it reads, the condition block is invoked. If the condition block returns a byte index, that much data is sliced off the buffer and the callback is called with that chunk of data. If the condition block returns `NSNotFound`, then it keeps reading. 15 | 16 | Several convenience methods are implemented on top of this. The `readBytes:callback:` method reads exactly the number of bytes requested, and passes them to the callback. `readUntilData:callback:` reads until the provided data is found within the buffer, and gives the callback everything that was found up to that point. `readUntilCString:callback:` does the same, except the data is provided as a C string. 17 | 18 | As an example, here's how you could read one line from a file: 19 | 20 | MAAsyncReader *reader = [[MAAsyncReader alloc] initWithFileDescriptor: someFD]; 21 | [reader readUntilCString: "\n" callback: ^(NSData *lineData) { 22 | // do something with 'line' 23 | }]; 24 | 25 | This is all done completely asynchronously and nonblocking. By default, the "do something" code will run on the global dispatch queue, meaning it runs in the background and concurrently. By using `setTargetQueue:`, you can make it run on the dispatch queue of your choice, including the main thread. 26 | 27 | 28 | Writing 29 | ------- 30 | 31 | `MAAsyncWriter` handles writing. As with the reader, you create one with `initWithFileDescriptor:` and optionally set an error handler and target queue. 32 | 33 | You can call `writeData:` and `writeCString:` as much as you want. The data so written is appended to a buffer, and that buffer is then written to the file descriptor as needed. 34 | 35 | If you wish to regulate the rate at which data is written, you can use the write callback and the `-bufferSize` method. For example, here's how you could fetch or generate new data to write any time the buffer drops below 4kB of data: 36 | 37 | MAAsyncWriter *writer = [[MAAsyncWriter alloc] initWithFileDescriptor: someFD]; 38 | [writer setDidWriteCallback: ^{ 39 | if([writer bufferSize] < 4096) 40 | [writer writeData: [self _generateMoreData]]; 41 | }]; 42 | 43 | // get the ball rolling 44 | [writer writeData: [self _generateMoreData]]; 45 | 46 | 47 | Descriptor Management 48 | --------------------- 49 | 50 | File descriptors are managed using a reference counting scheme similar to that used by Cocoa for memory management. 51 | 52 | The `MAFDRetain` function increments the reference count of a file descriptor. The `MAFDRelease` function decrements the reference count, and closes the file descriptor if it reaches zero. File descriptors are considered to be created with a reference count of `1`. `MAAsyncReader` and `MAAsyncWriter` will retain their file descriptors while working on them, and release them when done. 53 | 54 | Here's an example of how to properly use these functions: 55 | 56 | int fd = open(...); // implicit retain count of 1 57 | MAAsyncReader *reader = [[MAAsyncReader alloc] initWithFileDescriptor: fd]; 58 | MAFDRelease(fd); 59 | 60 | // now use reader 61 | 62 | These semantics make it possible to share an fd among multiple objects, so that you can have a reader and a writer both pointing at the same file descriptor: 63 | 64 | int fd = open(...); // implicit retain count of 1 65 | MAAsyncReader *reader = [[MAAsyncReader alloc] initWithFileDescriptor: fd]; 66 | MAAsyncWriter *writer = [[MAAsyncWriter alloc] initWithFileDescriptor: fd]; 67 | MAFDRelease(fd); 68 | 69 | // now use reader and writer 70 | 71 | 72 | Sockets 73 | ------- 74 | 75 | The async readers and writers make a natural interface to TCP sockets, whose unpredictable delays make asynchronous handling extremely advantageous. MAAsyncIO provides ways to create reader/writer pairs for a connected socket. 76 | 77 | The `MAAsyncSocketConnect` function connects to an address/port pair and then invokes its callback, passing a reader/writer pair that the callback can then use. 78 | 79 | The `MAAsyncHost` function provides asynchronous DNS lookups using a block callback. It can also attempt a connection to the looked up addresses by trying them sequentially with `MAAsyncSocketConnect`. With this, you can easily connect to a remote server: 80 | 81 | [[MAAsyncHost hostWithName: @"www.google.com"] connectToPort: 80 callback: ^(MAAsyncReader *reader, MAAsyncWriter *writer, NSError *error) { 82 | // check error 83 | // connected, use reader/writer to communicate 84 | }]; 85 | 86 | `MAAsyncSocketListener` can be used to bind a listening socket that automatically accepts new connections and invokes a callback with a reader/writer pair fon the new connection. The `+listenerWithAddress:error:` method can be used to create a listener that's bound to a single address. The `+listenerWith4and6WithPortRange:tryRandom:error:` is a more sophisticated method which will bind both an IPv4 and IPv6 socket to the same port. You can specify a port range to try, and specify whether or not to try random ports if all of the ports in the given range are taken. If you don't care about the port, you can pass `NSMakeRange(0, 0)` as the range to have it only try random ports. 87 | 88 | Here's an example server which simply writes "hello" to any client: 89 | 90 | MAAsyncSocketListener *listener = [MAAsyncSocketListener listenerWith4and6WithPortRange: NSMakeRange(0, 0) tryRandom: YES error: NULL]; 91 | [listener setAcceptCallback: ^(MAAsyncReader *reader, MAAsyncWriter *writer, NSData *peerAddress) { 92 | [writer writeCString: "hello"]; 93 | }]; 94 | 95 | 96 | Work In Progress 97 | ---------------- 98 | 99 | As stated above, MAAsyncIO is a work in progress. In particular, the following areas are deficient: 100 | 101 | - The reader has no buffer limits, so it can go forever if the data never meets the conditions. 102 | 103 | - The rules for when it's safe to invalidate objects (e.g. you can't invalidate a suspended `MAFDSource`) are too confusing. Everything should be made more robust in this respect. 104 | 105 | - Everything needs more and better tests. 106 | 107 | I plan to gradually work on these, but help is always appreciated. 108 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import 4 | 5 | #import "MAAsyncHost.h" 6 | #import "MAAsyncHTTPServer.h" 7 | #import "MAAsyncReader.h" 8 | #import "MAAsyncWriter.h" 9 | #import "MAAsyncSocketListener.h" 10 | #import "MAAsyncSocketUtils.h" 11 | #import "MAFDRefcount.h" 12 | 13 | 14 | static void WithPool(void (^block)(void)) 15 | { 16 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 17 | block(); 18 | [pool release]; 19 | } 20 | 21 | static const char *gFunctionName; 22 | static int gFailureCount; 23 | 24 | static void Test(void (*func)(void), const char *name) 25 | { 26 | WithPool(^{ 27 | gFunctionName = name; 28 | int failureCount = gFailureCount; 29 | NSLog(@"Testing %s", name); 30 | func(); 31 | NSLog(@"%s: %s", name, failureCount == gFailureCount ? "SUCCESS" : "FAILED"); 32 | }); 33 | } 34 | 35 | #define TEST(func) Test(func, #func) 36 | 37 | #define TEST_ASSERT(cond, ...) do { \ 38 | if(!(cond)) { \ 39 | gFailureCount++; \ 40 | NSString *message = [NSString stringWithFormat: @"" __VA_ARGS__]; \ 41 | NSLog(@"%s:%d: assertion failed: %s %@", gFunctionName, __LINE__, #cond, message); \ 42 | } \ 43 | } while(0) 44 | 45 | static BOOL WaitFor(int (^block)(void)) 46 | { 47 | NSProcessInfo *pi = [NSProcessInfo processInfo]; 48 | 49 | NSTimeInterval start = [pi systemUptime]; 50 | __block BOOL stop; 51 | do 52 | { 53 | WithPool(^{ 54 | stop = block() != 0; 55 | }); 56 | } while(!stop && [pi systemUptime] - start < 10); 57 | 58 | return stop; 59 | } 60 | 61 | static MAAsyncReader *Reader(int fd) 62 | { 63 | MAAsyncReader *reader = [[MAAsyncReader alloc] initWithFileDescriptor: fd]; 64 | [reader setErrorHandler: ^(int err) { 65 | NSLog(@"got error %d (%s)", err, strerror(err)); 66 | abort(); 67 | }]; 68 | return [reader autorelease]; 69 | } 70 | 71 | static MAAsyncWriter *Writer(int fd) 72 | { 73 | MAAsyncWriter *writer = [[MAAsyncWriter alloc] initWithFileDescriptor: fd]; 74 | [writer setErrorHandler: ^(int err) { 75 | NSLog(@"got error %d (%s)", err, strerror(err)); 76 | abort(); 77 | }]; 78 | return [writer autorelease]; 79 | } 80 | 81 | static void WithPipe(void (^block)(MAAsyncReader *reader, MAAsyncWriter *writer)) 82 | { 83 | int fds[2]; 84 | int ret = pipe(fds); 85 | TEST_ASSERT(ret == 0); 86 | 87 | int readFD = fds[0]; 88 | int writeFD = fds[1]; 89 | 90 | MAAsyncReader *reader = Reader(readFD); 91 | MAAsyncWriter *writer = Writer(writeFD); 92 | 93 | MAFDRelease(readFD); 94 | MAFDRelease(writeFD); 95 | 96 | block(reader, writer); 97 | } 98 | 99 | static void TestOpenRelease(void) 100 | { 101 | // make sure the FD refcounting stuff can handle a release as its first action 102 | // original version of it could not. 103 | int fd = open("/dev/null", O_RDONLY); 104 | TEST_ASSERT(fd != -1); 105 | MAFDRelease(fd); 106 | } 107 | 108 | static void TestDevNull(void) 109 | { 110 | for(int i = 0; i < 1000; i++) 111 | WithPool(^{ 112 | int fd = open("/dev/null", O_RDONLY); 113 | 114 | MAAsyncReader *reader = Reader(fd); 115 | MAFDRelease(fd); 116 | 117 | __block BOOL didRead = NO; 118 | [reader readUntilCondition: ^NSRange (NSData *buffer) { return MAKeepReading; } 119 | callback: ^(NSData *data, BOOL prematureEOF) { 120 | TEST_ASSERT(prematureEOF); 121 | didRead = YES; 122 | }]; 123 | WaitFor(^int { return didRead; }); 124 | }); 125 | } 126 | 127 | static void TestPipe(void) 128 | { 129 | for(int i = 0; i < 1000; i++) 130 | WithPool(^{ 131 | WithPipe(^(MAAsyncReader *reader, MAAsyncWriter *writer) { 132 | NSData *d1 = [NSData dataWithBytes: "12345" length: 5]; 133 | NSData *d2 = [NSData dataWithBytes: "abcdef" length: 6]; 134 | NSData *d3 = [NSData dataWithBytes: "ghijkl" length: 6]; 135 | 136 | __block BOOL done = NO; 137 | 138 | [reader readBytes: 5 callback: ^(NSData *data, BOOL prematureEOF) { 139 | TEST_ASSERT([data isEqualToData: d1]); 140 | [reader readUntilCString: "\n" callback: ^(NSData *data, BOOL prematureEOF) { 141 | TEST_ASSERT([data isEqualToData: d2]); 142 | [reader readUntilCString: "\r\n" callback: ^(NSData *data, BOOL prematureEOF) { 143 | TEST_ASSERT([data isEqualToData: d3]); 144 | [reader readUntilCString: "\r\n" callback: ^(NSData *data, BOOL prematureEOF) { 145 | TEST_ASSERT(data && [data length] == 0); 146 | done = YES; 147 | }]; 148 | }]; 149 | }]; 150 | }]; 151 | 152 | [writer writeData: d1]; 153 | [writer writeData: d2]; 154 | [writer writeCString: "\n"]; 155 | [writer writeData: d3]; 156 | [writer writeCString: "\r\n"]; 157 | [writer writeCString: "\r\n"]; 158 | 159 | TEST_ASSERT(WaitFor(^int { return done; })); 160 | }); 161 | }); 162 | } 163 | 164 | static void TestReadToEOF(void) 165 | { 166 | WithPipe(^(MAAsyncReader *reader, MAAsyncWriter *writer) { 167 | char *str = "hello world"; 168 | NSData *data = [NSData dataWithBytes: str length: strlen(str)]; 169 | [writer writeData: data]; 170 | [writer invalidateWhenEmptyBuffer]; 171 | __block BOOL done = NO; 172 | [reader readUntilEOFCallback: ^(NSData *indata, BOOL prematureEOF) { 173 | TEST_ASSERT(prematureEOF); 174 | TEST_ASSERT([indata isEqual: data]); 175 | done = YES; 176 | }]; 177 | TEST_ASSERT(WaitFor(^int { return done; })); 178 | }); 179 | } 180 | 181 | static void TestHost(void) 182 | { 183 | __block BOOL done1 = NO; 184 | __block BOOL done2 = NO; 185 | [[MAAsyncHost hostWithName: @"localhost"] resolve: ^(NSArray *addresses, CFStreamError error) { 186 | TEST_ASSERT(addresses); 187 | TEST_ASSERT(!error.domain); 188 | done1 = YES; 189 | }]; 190 | [[MAAsyncHost hostWithName: @"sdrgaoigdsaeuindthaeuihtedonutidenoscuhdnhe"] resolve: ^(NSArray *addresses, CFStreamError error) { 191 | TEST_ASSERT(!addresses); 192 | TEST_ASSERT(error.domain == kCFStreamErrorDomainNetDB); 193 | TEST_ASSERT(error.error == EAI_NONAME); 194 | done2 = YES; 195 | }]; 196 | 197 | TEST_ASSERT(WaitFor(^int { return done1; })); 198 | TEST_ASSERT(WaitFor(^int { return done2; })); 199 | } 200 | 201 | static void TestHostMultipleResolution(void) 202 | { 203 | __block BOOL done1 = NO; 204 | __block BOOL done2 = NO; 205 | 206 | MAAsyncHost *host = [MAAsyncHost hostWithName: @"localhost"]; 207 | [host resolve: ^(NSArray *addresses, CFStreamError error) { 208 | TEST_ASSERT(addresses); 209 | TEST_ASSERT(!error.domain); 210 | done1 = YES; 211 | }]; 212 | [host resolve: ^(NSArray *addresses, CFStreamError error) { 213 | TEST_ASSERT(addresses); 214 | TEST_ASSERT(!error.domain); 215 | done2 = YES; 216 | }]; 217 | 218 | TEST_ASSERT(WaitFor(^{ return done1 && done2; })); 219 | } 220 | 221 | static void TestSocketConnect(void) 222 | { 223 | __block BOOL done = NO; 224 | [[MAAsyncHost hostWithName: @"localhost"] connectToPort: 12346 callback: ^(MAAsyncReader *reader, MAAsyncWriter *writer, NSError *error) { 225 | TEST_ASSERT(reader && writer, @"%@", error); 226 | if(reader && writer) 227 | { 228 | [writer writeCString: "GET /\n\n"]; 229 | [reader readBytes: 1 callback: ^(NSData *data, BOOL prematureEOF) { 230 | done = YES; 231 | }]; 232 | } 233 | }]; 234 | 235 | TEST_ASSERT(WaitFor(^int { return done; })); 236 | } 237 | 238 | static void TestSocketListen(void) 239 | { 240 | NSError *error; 241 | MAAsyncSocketListener *listener = [MAAsyncSocketListener listenerWith4and6WithPortRange: NSMakeRange(1, 20) tryRandom: YES error: &error]; 242 | TEST_ASSERT(listener, @"%@", error); 243 | } 244 | 245 | static void TestSocketBoth(void) 246 | { 247 | NSError *error; 248 | MAAsyncSocketListener *listener = [MAAsyncSocketListener listenerWith4and6WithPortRange: NSMakeRange(1, 20) tryRandom: YES error: &error]; 249 | TEST_ASSERT(listener, @"%@", error); 250 | 251 | __block BOOL done1 = NO; 252 | [listener setAcceptCallback: ^(MAAsyncReader *reader, MAAsyncWriter *writer, NSData *peerAddress) { 253 | [reader readBytes: 1 callback: ^(NSData *data, BOOL prematureEOF) { 254 | TEST_ASSERT(*(const char *)[data bytes] == 'a'); 255 | [writer writeCString: "b"]; 256 | done1 = YES; 257 | }]; 258 | }]; 259 | 260 | __block BOOL done2 = NO; 261 | [[MAAsyncHost hostWithName: @"localhost"] connectToPort: [listener port] callback: ^(MAAsyncReader *reader, MAAsyncWriter *writer, NSError *error) { 262 | TEST_ASSERT(reader && writer, @"%@", error); 263 | if(reader && writer) 264 | { 265 | [writer writeCString: "a"]; 266 | [reader readBytes: 1 callback: ^(NSData *data, BOOL prematureEOF) { 267 | TEST_ASSERT(*(const char *)[data bytes] == 'b'); 268 | done2 = YES; 269 | }]; 270 | } 271 | }]; 272 | TEST_ASSERT(WaitFor(^{ return done1 && done2; })); 273 | } 274 | 275 | static void TestSocketClosing(void) 276 | { 277 | MAAsyncSocketListener *listener = [MAAsyncSocketListener listenerWith4and6WithPortRange: NSMakeRange(0, 0) tryRandom: YES error: NULL]; 278 | TEST_ASSERT(listener); 279 | 280 | [listener setAcceptCallback: ^(MAAsyncReader *reader, MAAsyncWriter *writer, NSData *peerAddress) { 281 | [reader invalidate]; 282 | [writer invalidate]; 283 | }]; 284 | 285 | __block BOOL done = NO; 286 | [[MAAsyncHost hostWithName: @"localhost"] connectToPort: [listener port] callback: ^(MAAsyncReader *reader, MAAsyncWriter *writer, NSError *error) { 287 | TEST_ASSERT(reader && writer, @"%@", error); 288 | if(reader && writer) 289 | { 290 | [reader readBytes: 1 callback: ^(NSData *data, BOOL prematureEOF) { 291 | TEST_ASSERT(prematureEOF); 292 | done = YES; 293 | }]; 294 | } 295 | }]; 296 | TEST_ASSERT(WaitFor(^int { return done; })); 297 | } 298 | 299 | static void TestHTTP(void) 300 | { 301 | NSError *error; 302 | MAAsyncHTTPServer *server = [[MAAsyncHTTPServer alloc] initWithPort: -1 error: &error]; 303 | TEST_ASSERT(server, @"%@", error); 304 | 305 | NSData *data = [@"testing 1 2 3" dataUsingEncoding: NSUTF8StringEncoding]; 306 | 307 | [server registerDefaultRouteHandler: ^(MAHTTPRequest *request, MAAsyncWriter *writer) { 308 | [writer writeData: data]; 309 | }]; 310 | 311 | NSURL *url = [NSURL URLWithString: 312 | [NSString stringWithFormat: @"http://localhost:%d/", [server port]]]; 313 | NSURLRequest *request = [NSURLRequest requestWithURL: url]; 314 | 315 | NSURLResponse *response; 316 | NSData *responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error]; 317 | 318 | TEST_ASSERT(responseData, @"%@", error); 319 | TEST_ASSERT([responseData isEqualToData: data]); 320 | 321 | [server release]; 322 | } 323 | 324 | int main(int argc, const char **argv) 325 | { 326 | WithPool(^{ 327 | TEST(TestOpenRelease); 328 | TEST(TestDevNull); 329 | TEST(TestPipe); 330 | TEST(TestReadToEOF); 331 | TEST(TestHost); 332 | TEST(TestHostMultipleResolution); 333 | TEST(TestSocketConnect); 334 | TEST(TestSocketListen); 335 | TEST(TestSocketBoth); 336 | TEST(TestSocketClosing); 337 | TEST(TestHTTP); 338 | 339 | NSString *message; 340 | if(gFailureCount) 341 | message = [NSString stringWithFormat: @"FAILED: %d total assertion failure%s", gFailureCount, gFailureCount > 1 ? "s" : ""]; 342 | else 343 | message = @"SUCCESS"; 344 | NSLog(@"Tests complete: %@", message); 345 | }); 346 | return 0; 347 | } 348 | --------------------------------------------------------------------------------