123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- /*
- Copyright (c) 2012-2013, Pierre-Olivier Latour
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * 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.
- * The name of Pierre-Olivier Latour may not be used to endorse
- or promote products derived from this software without specific
- prior written permission.
-
- 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 PIERRE-OLIVIER LATOUR 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.
- */
- #import <TargetConditionals.h>
- #if TARGET_OS_IPHONE
- #import <MobileCoreServices/MobileCoreServices.h>
- #endif
- #import <netinet/in.h>
- #import "GCDWebServerPrivate.h"
- #define kMaxPendingConnections 16
- static BOOL _run;
- NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
- static NSDictionary* _overrides = nil;
- if (_overrides == nil) {
- _overrides = [[NSDictionary alloc] initWithObjectsAndKeys:
- @"text/css", @"css",
- nil];
- }
- NSString* mimeType = nil;
- extension = [extension lowercaseString];
- if (extension.length) {
- mimeType = [_overrides objectForKey:extension];
- if (mimeType == nil) {
- CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (ARC_BRIDGE CFStringRef)extension, NULL);
- if (uti) {
- mimeType = ARC_BRIDGE_RELEASE(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
- CFRelease(uti);
- }
- }
- }
- return mimeType;
- }
- NSString* GCDWebServerUnescapeURLString(NSString* string) {
- return ARC_BRIDGE_RELEASE(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""),
- kCFStringEncodingUTF8));
- }
- NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
- NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
- NSScanner* scanner = [[NSScanner alloc] initWithString:form];
- [scanner setCharactersToBeSkipped:nil];
- while (1) {
- NSString* key = nil;
- if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) {
- break;
- }
- [scanner setScanLocation:([scanner scanLocation] + 1)];
-
- NSString* value = nil;
- if (![scanner scanUpToString:@"&" intoString:&value]) {
- break;
- }
-
- key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
- value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
- [parameters setObject:GCDWebServerUnescapeURLString(value) forKey:GCDWebServerUnescapeURLString(key)];
-
- if ([scanner isAtEnd]) {
- break;
- }
- [scanner setScanLocation:([scanner scanLocation] + 1)];
- }
- ARC_RELEASE(scanner);
- return parameters;
- }
- static void _SignalHandler(int signal) {
- _run = NO;
- printf("\n");
- }
- @implementation GCDWebServerHandler
- @synthesize matchBlock=_matchBlock, processBlock=_processBlock;
- - (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock {
- if ((self = [super init])) {
- _matchBlock = [matchBlock copy];
- _processBlock = [processBlock copy];
- }
- return self;
- }
- - (void)dealloc {
- ARC_RELEASE(_matchBlock);
- ARC_RELEASE(_processBlock);
-
- ARC_DEALLOC(super);
- }
- @end
- @implementation GCDWebServer
- @synthesize handlers=_handlers, port=_port;
- + (void)initialize {
- [GCDWebServerConnection class]; // Initialize class immediately to make sure it happens on the main thread
- }
- - (id)init {
- if ((self = [super init])) {
- _handlers = [[NSMutableArray alloc] init];
- }
- return self;
- }
- - (void)dealloc {
- if (_source) {
- [self stop];
- }
-
- ARC_RELEASE(_handlers);
-
- ARC_DEALLOC(super);
- }
- - (NSString*)bonjourName {
- CFStringRef name = _service ? CFNetServiceGetName(_service) : NULL;
- return name && CFStringGetLength(name) ? ARC_BRIDGE_RELEASE(CFStringCreateCopy(kCFAllocatorDefault, name)) : nil;
- }
- - (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)handlerBlock {
- DCHECK(_source == NULL);
- GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock processBlock:handlerBlock];
- [_handlers insertObject:handler atIndex:0];
- ARC_RELEASE(handler);
- }
- - (void)removeAllHandlers {
- DCHECK(_source == NULL);
- [_handlers removeAllObjects];
- }
- - (BOOL)start {
- return [self startWithPort:8080 bonjourName:@""];
- }
- static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* error, void* info) {
- @autoreleasepool {
- if (error->error) {
- LOG_ERROR(@"Bonjour error %i (domain %i)", error->error, (int)error->domain);
- } else {
- LOG_VERBOSE(@"Registered Bonjour service \"%@\" in domain \"%@\" with type '%@' on port %i", CFNetServiceGetName(service), CFNetServiceGetDomain(service), CFNetServiceGetType(service), CFNetServiceGetPortNumber(service));
- }
- }
- }
- - (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name {
- DCHECK(_source == NULL);
- int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- if (listeningSocket > 0) {
- int yes = 1;
- setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
- setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes));
-
- struct sockaddr_in addr4;
- bzero(&addr4, sizeof(addr4));
- addr4.sin_len = sizeof(addr4);
- addr4.sin_family = AF_INET;
- addr4.sin_port = htons(port);
- addr4.sin_addr.s_addr = htonl(INADDR_ANY);
- if (bind(listeningSocket, (void*)&addr4, sizeof(addr4)) == 0) {
- if (listen(listeningSocket, kMaxPendingConnections) == 0) {
- _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue);
- dispatch_source_set_cancel_handler(_source, ^{
-
- @autoreleasepool {
- int result = close(listeningSocket);
- if (result != 0) {
- LOG_ERROR(@"Failed closing socket (%i): %s", errno, strerror(errno));
- } else {
- LOG_DEBUG(@"Closed listening socket");
- }
- }
-
- });
- dispatch_source_set_event_handler(_source, ^{
-
- @autoreleasepool {
- struct sockaddr addr;
- socklen_t addrlen = sizeof(addr);
- int socket = accept(listeningSocket, &addr, &addrlen);
- if (socket > 0) {
- int yes = 1;
- setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof(yes)); // Make sure this socket cannot generate SIG_PIPE
-
- NSData* data = [NSData dataWithBytes:&addr length:addrlen];
- Class connectionClass = [[self class] connectionClass];
- GCDWebServerConnection* connection = [[connectionClass alloc] initWithServer:self address:data socket:socket]; // Connection will automatically retain itself while opened
- #if __has_feature(objc_arc)
- [connection self]; // Prevent compiler from complaining about unused variable / useless statement
- #else
- [connection release];
- #endif
- } else {
- LOG_ERROR(@"Failed accepting socket (%i): %s", errno, strerror(errno));
- }
- }
-
- });
-
- if (port == 0) { // Determine the actual port we are listening on
- struct sockaddr addr;
- socklen_t addrlen = sizeof(addr);
- if (getsockname(listeningSocket, &addr, &addrlen) == 0) {
- struct sockaddr_in* sockaddr = (struct sockaddr_in*)&addr;
- _port = ntohs(sockaddr->sin_port);
- } else {
- LOG_ERROR(@"Failed retrieving socket address (%i): %s", errno, strerror(errno));
- }
- } else {
- _port = port;
- }
-
- if (name) {
- _service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), CFSTR("_http._tcp"), (ARC_BRIDGE CFStringRef)name, _port);
- if (_service) {
- CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
- CFNetServiceSetClient(_service, _NetServiceClientCallBack, &context);
- CFNetServiceScheduleWithRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes);
- CFStreamError error = {0};
- CFNetServiceRegisterWithOptions(_service, 0, &error);
- } else {
- LOG_ERROR(@"Failed creating CFNetService");
- }
- }
-
- dispatch_resume(_source);
- LOG_VERBOSE(@"%@ started on port %i", [self class], (int)_port);
- } else {
- LOG_ERROR(@"Failed listening on socket (%i): %s", errno, strerror(errno));
- close(listeningSocket);
- }
- } else {
- LOG_ERROR(@"Failed binding socket (%i): %s", errno, strerror(errno));
- close(listeningSocket);
- }
- } else {
- LOG_ERROR(@"Failed creating socket (%i): %s", errno, strerror(errno));
- }
- return (_source ? YES : NO);
- }
- - (BOOL)isRunning {
- return (_source ? YES : NO);
- }
- - (void)stop {
- DCHECK(_source != NULL);
- if (_source) {
- if (_service) {
- CFNetServiceUnscheduleFromRunLoop(_service, CFRunLoopGetMain(), kCFRunLoopCommonModes);
- CFNetServiceSetClient(_service, NULL, NULL);
- CFRelease(_service);
- _service = NULL;
- }
-
- dispatch_source_cancel(_source); // This will close the socket
- ARC_DISPATCH_RELEASE(_source);
- _source = NULL;
-
- LOG_VERBOSE(@"%@ stopped", [self class]);
- }
- _port = 0;
- }
- @end
- @implementation GCDWebServer (Subclassing)
- + (Class)connectionClass {
- return [GCDWebServerConnection class];
- }
- + (NSString*)serverName {
- return NSStringFromClass(self);
- }
- @end
- @implementation GCDWebServer (Extensions)
- - (BOOL)runWithPort:(NSUInteger)port {
- BOOL success = NO;
- _run = YES;
- void* handler = signal(SIGINT, _SignalHandler);
- if (handler != SIG_ERR) {
- if ([self startWithPort:port bonjourName:@""]) {
- while (_run) {
- CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true);
- }
- [self stop];
- success = YES;
- }
- signal(SIGINT, handler);
- }
- return success;
- }
- @end
- @implementation GCDWebServer (Handlers)
- - (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block {
- [self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
-
- return ARC_AUTORELEASE([[class alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
-
- } processBlock:block];
- }
- - (GCDWebServerResponse*)_responseWithContentsOfFile:(NSString*)path {
- return [GCDWebServerFileResponse responseWithFile:path];
- }
- - (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path {
- NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path];
- if (enumerator == nil) {
- return nil;
- }
- NSMutableString* html = [NSMutableString string];
- [html appendString:@"<html><body>\n"];
- [html appendString:@"<ul>\n"];
- for (NSString* file in enumerator) {
- if (![file hasPrefix:@"."]) {
- NSString* type = [[enumerator fileAttributes] objectForKey:NSFileType];
- NSString* escapedFile = [file stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
- DCHECK(escapedFile);
- if ([type isEqualToString:NSFileTypeRegular]) {
- [html appendFormat:@"<li><a href=\"%@\">%@</a></li>\n", escapedFile, file];
- } else if ([type isEqualToString:NSFileTypeDirectory]) {
- [html appendFormat:@"<li><a href=\"%@/\">%@/</a></li>\n", escapedFile, file];
- }
- }
- [enumerator skipDescendents];
- }
- [html appendString:@"</ul>\n"];
- [html appendString:@"</body></html>\n"];
- return [GCDWebServerDataResponse responseWithHTML:html];
- }
- - (void)addHandlerForBasePath:(NSString*)basePath localPath:(NSString*)localPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge {
- if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) {
- #if __has_feature(objc_arc)
- __unsafe_unretained GCDWebServer* server = self;
- #else
- GCDWebServer* server = self;
- #endif
- [self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
-
- if (![requestMethod isEqualToString:@"GET"]) {
- return nil;
- }
- if (![urlPath hasPrefix:basePath]) {
- return nil;
- }
- return ARC_AUTORELEASE([[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
-
- } processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
-
- GCDWebServerResponse* response = nil;
- NSString* filePath = [localPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
- BOOL isDirectory;
- if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]) {
- if (isDirectory) {
- if (indexFilename) {
- NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename];
- if ([[NSFileManager defaultManager] fileExistsAtPath:indexPath isDirectory:&isDirectory] && !isDirectory) {
- return [server _responseWithContentsOfFile:indexPath];
- }
- }
- response = [server _responseWithContentsOfDirectory:filePath];
- } else {
- response = [server _responseWithContentsOfFile:filePath];
- }
- }
- if (response) {
- response.cacheControlMaxAge = cacheAge;
- } else {
- response = [GCDWebServerResponse responseWithStatusCode:404];
- }
- return response;
-
- }];
- } else {
- DNOT_REACHED();
- }
- }
- - (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block {
- if ([path hasPrefix:@"/"] && [class isSubclassOfClass:[GCDWebServerRequest class]]) {
- [self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
-
- if (![requestMethod isEqualToString:method]) {
- return nil;
- }
- if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) {
- return nil;
- }
- return ARC_AUTORELEASE([[class alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
-
- } processBlock:block];
- } else {
- DNOT_REACHED();
- }
- }
- - (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block {
- NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
- if (expression && [class isSubclassOfClass:[GCDWebServerRequest class]]) {
- [self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
-
- if (![requestMethod isEqualToString:method]) {
- return nil;
- }
- if ([expression firstMatchInString:urlPath options:0 range:NSMakeRange(0, urlPath.length)] == nil) {
- return nil;
- }
- return ARC_AUTORELEASE([[class alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
-
- } processBlock:block];
- } else {
- DNOT_REACHED();
- }
- }
- @end
|