24 Commits
1.2.1 ... 1.3

Author SHA1 Message Date
Pierre-Olivier Latour
47b8ea5f7c Added Drag & Drop browser file upload demo based on filedropjs.org 2014-03-20 17:55:20 -07:00
Pierre-Olivier Latour
dc7fe87878 Exposed internal utility functions 2014-03-20 17:28:54 -07:00
Pierre-Olivier Latour
cedec20673 Updated handlers convenience API 2014-03-20 12:55:09 -07:00
Pierre-Olivier Latour
ed0f3ac68e Enable -Weverything for Debug builds 2014-03-20 08:56:54 -07:00
Pierre-Olivier Latour
0a48f42ccb Exposed mode to CLI 2014-03-20 08:53:29 -07:00
Pierre-Olivier Latour
6c1439405d Moved more ivars to class extensions 2014-03-20 08:52:32 -07:00
Pierre-Olivier Latour
79e041eae5 Merge branch 'Range-Requests' 2014-03-20 08:38:47 -07:00
Pierre-Olivier Latour
fb02b9f9d1 Fix 2014-03-19 21:17:21 -07:00
Pierre-Olivier Latour
1b163b1b8b #27 Updated API to expose range requests support 2014-03-19 21:15:25 -07:00
Pierre-Olivier Latour
eb6589e27a Fix 2014-03-19 21:10:02 -07:00
Pierre-Olivier Latour
0a34a0b205 #27 Only set "Accept-Ranges" header on files 2014-03-19 21:08:20 -07:00
Pierre-Olivier Latour
1e99e91407 #27 Initial pass at HTTP range requests support 2014-03-19 20:57:35 -07:00
Pierre-Olivier Latour
096b07a201 #22 Exposed hooks to monitor bytes read and written 2014-03-19 09:44:15 -07:00
Pierre-Olivier Latour
e65b569ddc Move ivars to class extensions 2014-03-19 09:19:59 -07:00
Pierre-Olivier Latour
08e58e4a5a Rename "class" method arguments to "aClass" for C++ compatibility 2014-03-19 09:10:47 -07:00
Pierre-Olivier Latour
111027f413 Updated to Xcode 5.1 2014-03-19 09:07:14 -07:00
Pierre-Olivier Latour
d7e9386272 Fixed rare exception 2014-03-03 22:11:34 -08:00
Pierre-Olivier Latour
040515aff4 Update README.md 2014-02-10 10:44:36 -08:00
Pierre-Olivier Latour
bb56e1e808 Fix 2014-02-06 18:44:59 -08:00
Pierre-Olivier Latour
ff7a5c8e0a Updated copyright year 2014-02-06 18:44:04 -08:00
Pierre-Olivier Latour
7c24996be3 Removed podspec file 2014-01-29 09:24:31 -08:00
Pierre-Olivier Latour
feacf7601e Updated podspec 2014-01-29 09:16:00 -08:00
Pierre-Olivier Latour
36658278f8 Fixed more build warnings 2014-01-29 09:14:23 -08:00
Pierre-Olivier Latour
0f2f22a1b0 Updated for arm64 2014-01-29 08:38:25 -08:00
19 changed files with 577 additions and 224 deletions

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -31,14 +31,19 @@
typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery); typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request); typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
@interface GCDWebServer : NSObject { #ifdef __cplusplus
@private extern "C" {
NSMutableArray* _handlers; #endif
NSUInteger _port; NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension);
dispatch_source_t _source; NSString* GCDWebServerUnescapeURLString(NSString* string);
CFNetServiceRef _service; NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
#ifdef __cplusplus
} }
#endif
@interface GCDWebServer : NSObject
@property(nonatomic, readonly, getter=isRunning) BOOL running; @property(nonatomic, readonly, getter=isRunning) BOOL running;
@property(nonatomic, readonly) NSUInteger port; @property(nonatomic, readonly) NSUInteger port;
@property(nonatomic, readonly) NSString* bonjourName; // Only non-nil if Bonjour registration is active @property(nonatomic, readonly) NSString* bonjourName; // Only non-nil if Bonjour registration is active
@@ -60,8 +65,13 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r
@end @end
@interface GCDWebServer (Handlers) @interface GCDWebServer (Handlers)
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block; - (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
- (void)addHandlerForBasePath:(NSString*)basePath localPath:(NSString*)localPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge; // Base path is recursive and case-sensitive - (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block; // Path is case-insensitive
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block; // Path is case-insensitive - (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block; // Regular expression is case-insensitive
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block; // Regular expression is case-insensitive @end
@interface GCDWebServer (GETHandlers)
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge; // Path is case-insensitive
- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; // Path is case-insensitive
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; // Base path is recursive and case-sensitive
@end @end

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -36,6 +36,23 @@
#define kMaxPendingConnections 16 #define kMaxPendingConnections 16
@interface GCDWebServer () {
@private
NSMutableArray* _handlers;
NSUInteger _port;
dispatch_source_t _source;
CFNetServiceRef _service;
}
@end
@interface GCDWebServerHandler () {
@private
GCDWebServerMatchBlock _matchBlock;
GCDWebServerProcessBlock _processBlock;
}
@end
static BOOL _run; static BOOL _run;
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) { NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
@@ -62,7 +79,7 @@ NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
NSString* GCDWebServerUnescapeURLString(NSString* string) { NSString* GCDWebServerUnescapeURLString(NSString* string) {
return ARC_BRIDGE_RELEASE(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), return ARC_BRIDGE_RELEASE(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""),
kCFStringEncodingUTF8)); kCFStringEncodingUTF8));
} }
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) { NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
@@ -83,7 +100,11 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
[parameters setObject:GCDWebServerUnescapeURLString(value) forKey:GCDWebServerUnescapeURLString(key)]; if (key && value) {
[parameters setObject:GCDWebServerUnescapeURLString(value) forKey:GCDWebServerUnescapeURLString(key)];
} else {
DNOT_REACHED();
}
if ([scanner isAtEnd]) { if ([scanner isAtEnd]) {
break; break;
@@ -244,7 +265,7 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
} }
if (name) { if (name) {
_service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), CFSTR("_http._tcp"), (ARC_BRIDGE CFStringRef)name, _port); _service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), CFSTR("_http._tcp"), (ARC_BRIDGE CFStringRef)name, (SInt32)_port);
if (_service) { if (_service) {
CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL}; CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
CFNetServiceSetClient(_service, _NetServiceClientCallBack, &context); CFNetServiceSetClient(_service, _NetServiceClientCallBack, &context);
@@ -314,7 +335,7 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
- (BOOL)runWithPort:(NSUInteger)port { - (BOOL)runWithPort:(NSUInteger)port {
BOOL success = NO; BOOL success = NO;
_run = YES; _run = YES;
void* handler = signal(SIGINT, _SignalHandler); void (*handler)(int) = signal(SIGINT, _SignalHandler);
if (handler != SIG_ERR) { if (handler != SIG_ERR) {
if ([self startWithPort:port bonjourName:@""]) { if ([self startWithPort:port bonjourName:@""]) {
while (_run) { while (_run) {
@@ -332,16 +353,79 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
@implementation GCDWebServer (Handlers) @implementation GCDWebServer (Handlers)
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block { - (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { [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]); return ARC_AUTORELEASE([[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
} processBlock:block]; } processBlock:block];
} }
- (GCDWebServerResponse*)_responseWithContentsOfFile:(NSString*)path { - (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
return [GCDWebServerFileResponse responseWithFile:path]; if ([path hasPrefix:@"/"] && [aClass 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([[aClass 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)aClass processBlock:(GCDWebServerProcessBlock)block {
NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
if (expression && [aClass 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([[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
} processBlock:block];
} else {
DNOT_REACHED();
}
}
@end
@implementation GCDWebServer (GETHandlers)
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge {
GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
response.cacheControlMaxAge = cacheAge;
[self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return response;
}];
}
- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
[self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
GCDWebServerResponse* response = nil;
if (allowRangeRequests) {
response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment];
[response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
} else {
response = [GCDWebServerFileResponse responseWithFile:filePath isAttachment:isAttachment];
}
response.cacheControlMaxAge = cacheAge;
return response;
}];
} }
- (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path { - (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path {
@@ -350,7 +434,8 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
return nil; return nil;
} }
NSMutableString* html = [NSMutableString string]; NSMutableString* html = [NSMutableString string];
[html appendString:@"<html><body>\n"]; [html appendString:@"<!DOCTYPE html>\n"];
[html appendString:@"<html><head><meta charset=\"utf-8\"></head><body>\n"];
[html appendString:@"<ul>\n"]; [html appendString:@"<ul>\n"];
for (NSString* file in enumerator) { for (NSString* file in enumerator) {
if (![file hasPrefix:@"."]) { if (![file hasPrefix:@"."]) {
@@ -370,7 +455,7 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
return [GCDWebServerDataResponse responseWithHTML:html]; return [GCDWebServerDataResponse responseWithHTML:html];
} }
- (void)addHandlerForBasePath:(NSString*)basePath localPath:(NSString*)localPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge { - (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) { if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) {
#if __has_feature(objc_arc) #if __has_feature(objc_arc)
__unsafe_unretained GCDWebServer* server = self; __unsafe_unretained GCDWebServer* server = self;
@@ -390,19 +475,24 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
} processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { } processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
GCDWebServerResponse* response = nil; GCDWebServerResponse* response = nil;
NSString* filePath = [localPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]]; NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
BOOL isDirectory; BOOL isDirectory;
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]) { if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]) {
if (isDirectory) { if (isDirectory) {
if (indexFilename) { if (indexFilename) {
NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename]; NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename];
if ([[NSFileManager defaultManager] fileExistsAtPath:indexPath isDirectory:&isDirectory] && !isDirectory) { if ([[NSFileManager defaultManager] fileExistsAtPath:indexPath isDirectory:&isDirectory] && !isDirectory) {
return [server _responseWithContentsOfFile:indexPath]; return [GCDWebServerFileResponse responseWithFile:indexPath];
} }
} }
response = [server _responseWithContentsOfDirectory:filePath]; response = [server _responseWithContentsOfDirectory:filePath];
} else { } else {
response = [server _responseWithContentsOfFile:filePath]; if (allowRangeRequests) {
response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange];
[response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
} else {
response = [GCDWebServerFileResponse responseWithFile:filePath];
}
} }
} }
if (response) { if (response) {
@@ -418,41 +508,4 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
} }
} }
- (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 @end

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -29,20 +29,7 @@
@class GCDWebServerHandler; @class GCDWebServerHandler;
@interface GCDWebServerConnection : NSObject { @interface GCDWebServerConnection : NSObject
@private
GCDWebServer* _server;
NSData* _address;
CFSocketNativeHandle _socket;
NSUInteger _bytesRead;
NSUInteger _bytesWritten;
CFHTTPMessageRef _requestMessage;
GCDWebServerRequest* _request;
GCDWebServerHandler* _handler;
CFHTTPMessageRef _responseMessage;
GCDWebServerResponse* _response;
}
@property(nonatomic, readonly) GCDWebServer* server; @property(nonatomic, readonly) GCDWebServer* server;
@property(nonatomic, readonly) NSData* address; // struct sockaddr @property(nonatomic, readonly) NSData* address; // struct sockaddr
@property(nonatomic, readonly) NSUInteger totalBytesRead; @property(nonatomic, readonly) NSUInteger totalBytesRead;
@@ -51,6 +38,8 @@
@interface GCDWebServerConnection (Subclassing) @interface GCDWebServerConnection (Subclassing)
- (void)open; - (void)open;
- (void)didUpdateBytesRead; // Called from arbitrary thread after @totalBytesRead is updated - Default implementation does nothing
- (void)didUpdateBytesWritten; // Called from arbitrary thread after @totalBytesWritten is updated - Default implementation does nothing
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block; - (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block;
- (void)close; - (void)close;
@end @end

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -45,6 +45,22 @@ static NSData* _continueData = nil;
static NSDateFormatter* _dateFormatter = nil; static NSDateFormatter* _dateFormatter = nil;
static dispatch_queue_t _formatterQueue = NULL; static dispatch_queue_t _formatterQueue = NULL;
@interface GCDWebServerConnection () {
@private
GCDWebServer* _server;
NSData* _address;
CFSocketNativeHandle _socket;
NSUInteger _bytesRead;
NSUInteger _bytesWritten;
CFHTTPMessageRef _requestMessage;
GCDWebServerRequest* _request;
GCDWebServerHandler* _handler;
CFHTTPMessageRef _responseMessage;
GCDWebServerResponse* _response;
}
@end
@implementation GCDWebServerConnection (Read) @implementation GCDWebServerConnection (Read)
- (void)_readBufferWithLength:(NSUInteger)length completionBlock:(ReadBufferCompletionBlock)block { - (void)_readBufferWithLength:(NSUInteger)length completionBlock:(ReadBufferCompletionBlock)block {
@@ -56,6 +72,7 @@ static dispatch_queue_t _formatterQueue = NULL;
if (size > 0) { if (size > 0) {
LOG_DEBUG(@"Connection received %i bytes on socket %i", size, _socket); LOG_DEBUG(@"Connection received %i bytes on socket %i", size, _socket);
_bytesRead += size; _bytesRead += size;
[self didUpdateBytesRead];
block(buffer); block(buffer);
} else { } else {
if (_bytesRead > 0) { if (_bytesRead > 0) {
@@ -140,7 +157,7 @@ static dispatch_queue_t _formatterQueue = NULL;
if (remainingLength >= 0) { if (remainingLength >= 0) {
bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) { bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) {
NSInteger result = [_request write:buffer maxLength:size]; NSInteger result = [_request write:buffer maxLength:size];
if (result != size) { if (result != (NSInteger)size) {
LOG_ERROR(@"Failed writing request body on socket %i (error %i)", _socket, (int)result); LOG_ERROR(@"Failed writing request body on socket %i (error %i)", _socket, (int)result);
return false; return false;
} }
@@ -156,8 +173,8 @@ static dispatch_queue_t _formatterQueue = NULL;
block(NO); block(NO);
} }
} else { } else {
DNOT_REACHED();
block(NO); block(NO);
DNOT_REACHED();
} }
} else { } else {
block(NO); block(NO);
@@ -179,6 +196,7 @@ static dispatch_queue_t _formatterQueue = NULL;
DCHECK(data == NULL); DCHECK(data == NULL);
LOG_DEBUG(@"Connection sent %i bytes on socket %i", size, _socket); LOG_DEBUG(@"Connection sent %i bytes on socket %i", size, _socket);
_bytesWritten += size; _bytesWritten += size;
[self didUpdateBytesWritten];
block(YES); block(YES);
} else { } else {
LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error); LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
@@ -342,7 +360,7 @@ static dispatch_queue_t _formatterQueue = NULL;
NSInteger length = _request.contentLength; NSInteger length = _request.contentLength;
if (initialData.length) { if (initialData.length) {
NSInteger result = [_request write:initialData.bytes maxLength:initialData.length]; NSInteger result = [_request write:initialData.bytes maxLength:initialData.length];
if (result == initialData.length) { if (result == (NSInteger)initialData.length) {
length -= initialData.length; length -= initialData.length;
DCHECK(length >= 0); DCHECK(length >= 0);
} else { } else {
@@ -479,6 +497,14 @@ static dispatch_queue_t _formatterQueue = NULL;
[self _readRequestHeaders]; [self _readRequestHeaders];
} }
- (void)didUpdateBytesRead {
;
}
- (void)didUpdateBytesWritten {
;
}
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block { - (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block {
LOG_DEBUG(@"Connection on socket %i processing %@ request for \"%@\" (%i bytes body)", _socket, _request.method, _request.path, _request.contentLength); LOG_DEBUG(@"Connection on socket %i processing %@ request for \"%@\" (%i bytes body)", _socket, _request.method, _request.path, _request.contentLength);
GCDWebServerResponse* response = nil; GCDWebServerResponse* response = nil;

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -26,6 +26,7 @@
*/ */
#import <TargetConditionals.h> #import <TargetConditionals.h>
#import <AvailabilityMacros.h>
#if __has_feature(objc_arc) #if __has_feature(objc_arc)
#define ARC_BRIDGE __bridge #define ARC_BRIDGE __bridge
@@ -105,18 +106,6 @@ static inline void __LogMessage(long level, NSString* format, ...) {
#define kGCDWebServerDefaultMimeType @"application/octet-stream" #define kGCDWebServerDefaultMimeType @"application/octet-stream"
#define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) #define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#ifdef __cplusplus
extern "C" {
#endif
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension);
NSString* GCDWebServerUnescapeURLString(NSString* string);
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
#ifdef __cplusplus
}
#endif
@interface GCDWebServerConnection () @interface GCDWebServerConnection ()
- (id)initWithServer:(GCDWebServer*)server address:(NSData*)address socket:(CFSocketNativeHandle)socket; - (id)initWithServer:(GCDWebServer*)server address:(NSData*)address socket:(CFSocketNativeHandle)socket;
@end @end
@@ -125,11 +114,7 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
@property(nonatomic, readonly) NSArray* handlers; @property(nonatomic, readonly) NSArray* handlers;
@end @end
@interface GCDWebServerHandler : NSObject { @interface GCDWebServerHandler : NSObject
@private
GCDWebServerMatchBlock _matchBlock;
GCDWebServerProcessBlock _processBlock;
}
@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock; @property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock;
@property(nonatomic, readonly) GCDWebServerProcessBlock processBlock; @property(nonatomic, readonly) GCDWebServerProcessBlock processBlock;
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock; - (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -27,16 +27,7 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@interface GCDWebServerRequest : NSObject { @interface GCDWebServerRequest : NSObject
@private
NSString* _method;
NSURL* _url;
NSDictionary* _headers;
NSString* _path;
NSDictionary* _query;
NSString* _type;
NSUInteger _length;
}
@property(nonatomic, readonly) NSString* method; @property(nonatomic, readonly) NSString* method;
@property(nonatomic, readonly) NSURL* URL; @property(nonatomic, readonly) NSURL* URL;
@property(nonatomic, readonly) NSDictionary* headers; @property(nonatomic, readonly) NSDictionary* headers;
@@ -44,6 +35,7 @@
@property(nonatomic, readonly) NSDictionary* query; // May be nil @property(nonatomic, readonly) NSDictionary* query; // May be nil
@property(nonatomic, readonly) NSString* contentType; // Automatically parsed from headers (nil if request has no body) @property(nonatomic, readonly) NSString* contentType; // Automatically parsed from headers (nil if request has no body)
@property(nonatomic, readonly) NSUInteger contentLength; // Automatically parsed from headers @property(nonatomic, readonly) NSUInteger contentLength; // Automatically parsed from headers
@property(nonatomic, readonly) NSRange byteRange; // Automatically parsed from headers ([NSNotFound, 0] if request has no "Range" header, [offset, length] for byte range from beginning or [NSNotFound, -bytes] from end)
- (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query; - (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query;
- (BOOL)hasBody; // Convenience method - (BOOL)hasBody; // Convenience method
@end @end
@@ -54,71 +46,35 @@
- (BOOL)close; // Implementation required - (BOOL)close; // Implementation required
@end @end
@interface GCDWebServerDataRequest : GCDWebServerRequest { @interface GCDWebServerDataRequest : GCDWebServerRequest
@private
NSMutableData* _data;
}
@property(nonatomic, readonly) NSData* data; // Only valid after open / write / close sequence @property(nonatomic, readonly) NSData* data; // Only valid after open / write / close sequence
@end @end
@interface GCDWebServerFileRequest : GCDWebServerRequest { @interface GCDWebServerFileRequest : GCDWebServerRequest
@private
NSString* _filePath;
int _file;
}
@property(nonatomic, readonly) NSString* filePath; // Only valid after open / write / close sequence @property(nonatomic, readonly) NSString* filePath; // Only valid after open / write / close sequence
@end @end
@interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest { @interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest
@private
NSDictionary* _arguments;
}
@property(nonatomic, readonly) NSDictionary* arguments; // Only valid after open / write / close sequence @property(nonatomic, readonly) NSDictionary* arguments; // Only valid after open / write / close sequence
+ (NSString*)mimeType; + (NSString*)mimeType;
@end @end
@interface GCDWebServerMultiPart : NSObject { @interface GCDWebServerMultiPart : NSObject
@private
NSString* _contentType;
NSString* _mimeType;
}
@property(nonatomic, readonly) NSString* contentType; // May be nil @property(nonatomic, readonly) NSString* contentType; // May be nil
@property(nonatomic, readonly) NSString* mimeType; // Defaults to "text/plain" per specifications if undefined @property(nonatomic, readonly) NSString* mimeType; // Defaults to "text/plain" per specifications if undefined
@end @end
@interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart { @interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart
@private
NSData* _data;
NSString* _string;
}
@property(nonatomic, readonly) NSData* data; @property(nonatomic, readonly) NSData* data;
@property(nonatomic, readonly) NSString* string; // May be nil (only valid for text mime types @property(nonatomic, readonly) NSString* string; // May be nil (only valid for text mime types
@end @end
@interface GCDWebServerMultiPartFile : GCDWebServerMultiPart { @interface GCDWebServerMultiPartFile : GCDWebServerMultiPart
@private
NSString* _fileName;
NSString* _temporaryPath;
}
@property(nonatomic, readonly) NSString* fileName; // May be nil @property(nonatomic, readonly) NSString* fileName; // May be nil
@property(nonatomic, readonly) NSString* temporaryPath; @property(nonatomic, readonly) NSString* temporaryPath;
@end @end
@interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest { @interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest
@private
NSData* _boundary;
NSUInteger _parserState;
NSMutableData* _parserData;
NSString* _controlName;
NSString* _fileName;
NSString* _contentType;
NSString* _tmpPath;
int _tmpFile;
NSMutableDictionary* _arguments;
NSMutableDictionary* _files;
}
@property(nonatomic, readonly) NSDictionary* arguments; // Only valid after open / write / close sequence @property(nonatomic, readonly) NSDictionary* arguments; // Only valid after open / write / close sequence
@property(nonatomic, readonly) NSDictionary* files; // Only valid after open / write / close sequence @property(nonatomic, readonly) NSDictionary* files; // Only valid after open / write / close sequence
+ (NSString*)mimeType; + (NSString*)mimeType;

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -37,6 +37,76 @@ enum {
kParserState_End kParserState_End
}; };
@interface GCDWebServerRequest () {
@private
NSString* _method;
NSURL* _url;
NSDictionary* _headers;
NSString* _path;
NSDictionary* _query;
NSString* _type;
NSUInteger _length;
NSRange _range;
}
@end
@interface GCDWebServerDataRequest () {
@private
NSMutableData* _data;
}
@end
@interface GCDWebServerFileRequest () {
@private
NSString* _filePath;
int _file;
}
@end
@interface GCDWebServerURLEncodedFormRequest () {
@private
NSDictionary* _arguments;
}
@end
@interface GCDWebServerMultiPart () {
@private
NSString* _contentType;
NSString* _mimeType;
}
@end
@interface GCDWebServerMultiPartArgument () {
@private
NSData* _data;
NSString* _string;
}
@end
@interface GCDWebServerMultiPartFile () {
@private
NSString* _fileName;
NSString* _temporaryPath;
}
@end
@interface GCDWebServerMultiPartFormRequest () {
@private
NSData* _boundary;
NSUInteger _parserState;
NSMutableData* _parserData;
NSString* _controlName;
NSString* _fileName;
NSString* _contentType;
NSString* _tmpPath;
int _tmpFile;
NSMutableDictionary* _arguments;
NSMutableDictionary* _files;
}
@end
static NSData* _newlineData = nil; static NSData* _newlineData = nil;
static NSData* _newlinesData = nil; static NSData* _newlinesData = nil;
static NSData* _dashNewlineData = nil; static NSData* _dashNewlineData = nil;
@@ -70,7 +140,7 @@ static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
@implementation GCDWebServerRequest : NSObject @implementation GCDWebServerRequest : NSObject
@synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length; @synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length, byteRange=_range;
- (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { - (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
if ((self = [super init])) { if ((self = [super init])) {
@@ -88,10 +158,39 @@ static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
return nil; return nil;
} }
_length = length; _length = length;
if ((_length > 0) && (_type == nil)) { if ((_length > 0) && (_type == nil)) {
_type = [kGCDWebServerDefaultMimeType copy]; _type = [kGCDWebServerDefaultMimeType copy];
} }
_range = NSMakeRange(NSNotFound, 0);
NSString* rangeHeader = [[_headers objectForKey:@"Range"] lowercaseString];
if (rangeHeader) {
if ([rangeHeader hasPrefix:@"bytes="]) {
NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","];
if (components.count == 1) {
components = [[components firstObject] componentsSeparatedByString:@"-"];
if (components.count == 2) {
NSString* startString = [components objectAtIndex:0];
NSInteger startValue = [startString integerValue];
NSString* endString = [components objectAtIndex:1];
NSInteger endValue = [endString integerValue];
if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999"
_range.location = startValue;
_range.length = endValue - startValue + 1;
} else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-"
_range.location = startValue;
_range.length = NSUIntegerMax;
} else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
_range.location = NSNotFound;
_range.length = endValue;
}
}
}
}
if ((_range.location == NSNotFound) && (_range.length == 0)) { // Ignore "Range" header if syntactically invalid
LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
}
}
} }
return self; return self;
} }
@@ -429,8 +528,8 @@ static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
const void* dataBytes = _parserData.bytes; const void* dataBytes = _parserData.bytes;
NSUInteger dataLength = range.location - 2; NSUInteger dataLength = range.location - 2;
if (_tmpPath) { if (_tmpPath) {
int result = write(_tmpFile, dataBytes, dataLength); ssize_t result = write(_tmpFile, dataBytes, dataLength);
if (result == dataLength) { if (result == (ssize_t)dataLength) {
if (close(_tmpFile) == 0) { if (close(_tmpFile) == 0) {
_tmpFile = 0; _tmpFile = 0;
GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithContentType:_contentType fileName:_fileName temporaryPath:_tmpPath]; GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithContentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
@@ -467,8 +566,8 @@ static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
NSUInteger margin = 2 * _boundary.length; NSUInteger margin = 2 * _boundary.length;
if (_tmpPath && (_parserData.length > margin)) { if (_tmpPath && (_parserData.length > margin)) {
NSUInteger length = _parserData.length - margin; NSUInteger length = _parserData.length - margin;
int result = write(_tmpFile, _parserData.bytes, length); ssize_t result = write(_tmpFile, _parserData.bytes, length);
if (result == length) { if (result == (ssize_t)length) {
[_parserData replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0]; [_parserData replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
} else { } else {
DNOT_REACHED(); DNOT_REACHED();

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -27,14 +27,7 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@interface GCDWebServerResponse : NSObject { @interface GCDWebServerResponse : NSObject
@private
NSString* _type;
NSUInteger _length;
NSInteger _status;
NSUInteger _maxAge;
NSMutableDictionary* _headers;
}
@property(nonatomic, readonly) NSString* contentType; @property(nonatomic, readonly) NSString* contentType;
@property(nonatomic, readonly) NSUInteger contentLength; @property(nonatomic, readonly) NSUInteger contentLength;
@property(nonatomic) NSInteger statusCode; // Default is 200 @property(nonatomic) NSInteger statusCode; // Default is 200
@@ -60,11 +53,7 @@
- (id)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent; - (id)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
@end @end
@interface GCDWebServerDataResponse : GCDWebServerResponse { @interface GCDWebServerDataResponse : GCDWebServerResponse
@private
NSData* _data;
NSInteger _offset;
}
+ (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type; + (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type;
- (id)initWithData:(NSData*)data contentType:(NSString*)type; - (id)initWithData:(NSData*)data contentType:(NSString*)type;
@end @end
@@ -78,13 +67,13 @@
- (id)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; // Simple template system that replaces all occurences of "%variable%" with corresponding value (encodes using UTF-8) - (id)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; // Simple template system that replaces all occurences of "%variable%" with corresponding value (encodes using UTF-8)
@end @end
@interface GCDWebServerFileResponse : GCDWebServerResponse { @interface GCDWebServerFileResponse : GCDWebServerResponse
@private
NSString* _path;
int _file;
}
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path; + (GCDWebServerFileResponse*)responseWithFile:(NSString*)path;
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment; + (GCDWebServerFileResponse*)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment;
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range;
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
- (id)initWithFile:(NSString*)path; - (id)initWithFile:(NSString*)path;
- (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment; - (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment;
- (id)initWithFile:(NSString*)path byteRange:(NSRange)range; // Pass [NSNotFound, 0] to disable byte range entirely, [offset, length] to enable byte range from beginning of file or [NSNotFound, -bytes] from end of file
- (id)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
@end @end

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -29,6 +29,32 @@
#import "GCDWebServerPrivate.h" #import "GCDWebServerPrivate.h"
@interface GCDWebServerResponse () {
@private
NSString* _type;
NSUInteger _length;
NSInteger _status;
NSUInteger _maxAge;
NSMutableDictionary* _headers;
}
@end
@interface GCDWebServerDataResponse () {
@private
NSData* _data;
NSInteger _offset;
}
@end
@interface GCDWebServerFileResponse () {
@private
NSString* _path;
NSUInteger _offset;
NSUInteger _size;
int _file;
}
@end
@implementation GCDWebServerResponse @implementation GCDWebServerResponse
@synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge, additionalHeaders=_headers; @synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge, additionalHeaders=_headers;
@@ -155,7 +181,7 @@
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length { - (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
DCHECK(_offset >= 0); DCHECK(_offset >= 0);
NSInteger size = 0; NSInteger size = 0;
if (_offset < _data.length) { if (_offset < (NSInteger)_data.length) {
size = MIN(_data.length - _offset, length); size = MIN(_data.length - _offset, length);
bcopy((char*)_data.bytes + _offset, buffer, size); bcopy((char*)_data.bytes + _offset, buffer, size);
_offset += size; _offset += size;
@@ -227,24 +253,63 @@
return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path isAttachment:attachment]); return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path isAttachment:attachment]);
} }
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range {
return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path byteRange:range]);
}
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment]);
}
- (id)initWithFile:(NSString*)path { - (id)initWithFile:(NSString*)path {
return [self initWithFile:path isAttachment:NO]; return [self initWithFile:path byteRange:NSMakeRange(NSNotFound, 0) isAttachment:NO];
} }
- (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment { - (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment {
return [self initWithFile:path byteRange:NSMakeRange(NSNotFound, 0) isAttachment:attachment];
}
- (id)initWithFile:(NSString*)path byteRange:(NSRange)range {
return [self initWithFile:path byteRange:range isAttachment:NO];
}
- (id)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
struct stat info; struct stat info;
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) { if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
DNOT_REACHED(); DNOT_REACHED();
ARC_RELEASE(self); ARC_RELEASE(self);
return nil; return nil;
} }
if ((range.location != NSNotFound) || (range.length > 0)) {
if (range.location != NSNotFound) {
range.location = MIN(range.location, (NSUInteger)info.st_size);
range.length = MIN(range.length, info.st_size - range.location);
} else {
range.length = MIN(range.length, (NSUInteger)info.st_size);
range.location = info.st_size - range.length;
}
if (range.length == 0) {
ARC_RELEASE(self);
return nil; // TODO: Return 416 status code and "Content-Range: bytes */{file length}" header
}
}
NSString* type = GCDWebServerGetMimeTypeForExtension([path pathExtension]); NSString* type = GCDWebServerGetMimeTypeForExtension([path pathExtension]);
if (type == nil) { if (type == nil) {
type = kGCDWebServerDefaultMimeType; type = kGCDWebServerDefaultMimeType;
} }
if ((self = [super initWithContentType:type contentLength:info.st_size])) { if ((self = [super initWithContentType:type contentLength:(range.location != NSNotFound ? range.length : info.st_size)])) {
_path = [path copy]; _path = [path copy];
if (range.location != NSNotFound) {
_offset = range.location;
_size = range.length;
[self setStatusCode:206];
[self setValue:[NSString stringWithFormat:@"bytes %i-%i/%i", (int)range.location, (int)(range.location + range.length - 1), (int)info.st_size] forAdditionalHeader:@"Content-Range"];
LOG_DEBUG(@"Using content bytes range [%i-%i] for file \"%@\"", (int)range.location, (int)(range.location + range.length - 1), path);
} else {
_offset = 0;
_size = info.st_size;
}
if (attachment) { // TODO: Use http://tools.ietf.org/html/rfc5987 to encode file names with special characters instead of using lossy conversion to ISO 8859-1 if (attachment) { // TODO: Use http://tools.ietf.org/html/rfc5987 to encode file names with special characters instead of using lossy conversion to ISO 8859-1
NSData* data = [[path lastPathComponent] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES]; NSData* data = [[path lastPathComponent] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES];
NSString* fileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil; NSString* fileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil;
@@ -269,12 +334,24 @@
- (BOOL)open { - (BOOL)open {
DCHECK(_file <= 0); DCHECK(_file <= 0);
_file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY); _file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
return (_file > 0 ? YES : NO); if (_file <= 0) {
return NO;
}
if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) {
close(_file);
_file = 0;
return NO;
}
return YES;
} }
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length { - (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
DCHECK(_file > 0); DCHECK(_file > 0);
return read(_file, buffer, length); ssize_t result = read(_file, buffer, MIN(length, _size));
if (result > 0) {
_size -= result;
}
return result;
} }
- (BOOL)close { - (BOOL)close {

View File

@@ -1,18 +0,0 @@
Pod::Spec.new do |s|
s.name = 'GCDWebServer'
s.version = '1.2'
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
s.license = { :type => 'BSD', :file => 'LICENSE' }
s.homepage = 'https://github.com/swisspol/GCDWebServer'
s.summary = 'Lightweight GCD based HTTP server for Mac OS X & iOS apps'
s.source = { :git => 'https://github.com/swisspol/GCDWebServer.git', :tag => s.version.to_s }
s.requires_arc = true
s.source_files = 'CGDWebServer/*.{h,m}'
s.ios.deployment_target = '5.0'
s.osx.deployment_target = '10.7'
s.ios.framework = 'MobileCoreServices'
end

View File

@@ -79,6 +79,8 @@
E22112981690B7AA0048D2B2 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; }; E22112981690B7AA0048D2B2 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; };
E221129A1690B7B10048D2B2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; E221129A1690B7B10048D2B2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = DEVELOPER_DIR; }; E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = DEVELOPER_DIR; };
E263213318DB688E00D9DC0C /* index.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = index.html; sourceTree = "<group>"; };
E263213418DB7F7900D9DC0C /* filedrop-min.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "filedrop-min.js"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -145,6 +147,8 @@
E221128D1690B6470048D2B2 /* Mac */ = { E221128D1690B6470048D2B2 /* Mac */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E263213418DB7F7900D9DC0C /* filedrop-min.js */,
E263213318DB688E00D9DC0C /* index.html */,
E221128E1690B6470048D2B2 /* main.m */, E221128E1690B6470048D2B2 /* main.m */,
); );
path = Mac; path = Mac;
@@ -222,7 +226,7 @@
08FB7793FE84155DC02AAC07 /* Project object */ = { 08FB7793FE84155DC02AAC07 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0500; LastUpgradeCheck = 0510;
}; };
buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "GCDWebServer" */; buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "GCDWebServer" */;
compatibilityVersion = "Xcode 3.2"; compatibilityVersion = "Xcode 3.2";
@@ -291,7 +295,18 @@
1DEB928608733DD80010E9CD /* Debug */ = { 1DEB928608733DD80010E9CD /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ARCHS = "$(ARCHS_STANDARD)";
MACOSX_DEPLOYMENT_TARGET = 10.7; MACOSX_DEPLOYMENT_TARGET = 10.7;
OTHER_LDFLAGS = (
"-sectcreate",
__TEXT,
_index_html_,
"\"Mac/index.html\"",
"-sectcreate",
__TEXT,
_filedrop_js_,
"\"Mac/filedrop-min.js\"",
);
PRODUCT_NAME = GCDWebServer; PRODUCT_NAME = GCDWebServer;
SDKROOT = macosx; SDKROOT = macosx;
}; };
@@ -300,7 +315,18 @@
1DEB928708733DD80010E9CD /* Release */ = { 1DEB928708733DD80010E9CD /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ARCHS = "$(ARCHS_STANDARD)";
MACOSX_DEPLOYMENT_TARGET = 10.7; MACOSX_DEPLOYMENT_TARGET = 10.7;
OTHER_LDFLAGS = (
"-sectcreate",
__TEXT,
_index_html_,
"\"Mac/index.html\"",
"-sectcreate",
__TEXT,
_filedrop_js_,
"\"Mac/filedrop-min.js\"",
);
PRODUCT_NAME = GCDWebServer; PRODUCT_NAME = GCDWebServer;
SDKROOT = macosx; SDKROOT = macosx;
}; };
@@ -312,7 +338,16 @@
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
GCC_OPTIMIZATION_LEVEL = 0; GCC_OPTIMIZATION_LEVEL = 0;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
WARNING_CFLAGS = "-Wall"; WARNING_CFLAGS = (
"-Wall",
"-Weverything",
"-Wno-gnu-statement-expression",
"-Wno-direct-ivar-access",
"-Wno-implicit-retain-self",
"-Wno-assign-enum",
"-Wno-format-nonliteral",
"-Wno-cast-align",
);
}; };
name = Debug; name = Debug;
}; };
@@ -321,6 +356,7 @@
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
GCC_PREPROCESSOR_DEFINITIONS = NDEBUG; GCC_PREPROCESSOR_DEFINITIONS = NDEBUG;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
WARNING_CFLAGS = "-Wall"; WARNING_CFLAGS = "-Wall";
}; };
name = Release; name = Release;
@@ -328,6 +364,7 @@
E22112761690B4DF0048D2B2 /* Debug */ = { E22112761690B4DF0048D2B2 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
INFOPLIST_FILE = iOS/Info.plist; INFOPLIST_FILE = iOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 5.0; IPHONEOS_DEPLOYMENT_TARGET = 5.0;
@@ -341,6 +378,7 @@
E22112771690B4DF0048D2B2 /* Release */ = { E22112771690B4DF0048D2B2 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
INFOPLIST_FILE = iOS/Info.plist; INFOPLIST_FILE = iOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 5.0; IPHONEOS_DEPLOYMENT_TARGET = 5.0;

View File

@@ -1,4 +1,4 @@
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

11
Mac/filedrop-min.js vendored Executable file

File diff suppressed because one or more lines are too long

91
Mac/index.html Executable file
View File

@@ -0,0 +1,91 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Basic FileDrop example</title>
<script type="text/javascript" src="filedrop-min.js"></script>
<style type="text/css">
/* Essential FileDrop zone element configuration: */
.fd-zone {
position: relative;
overflow: hidden;
/* The following are not required but create a pretty box: */
width: 15em;
margin: 0 auto;
text-align: center;
}
/* Hides <input type="file"> while simulating "Browse" button: */
.fd-file {
opacity: 0;
font-size: 118px;
position: absolute;
right: 0;
top: 0;
z-index: 1;
padding: 0;
margin: 0;
cursor: pointer;
filter: alpha(opacity=0);
font-family: sans-serif;
}
/* Provides visible feedback when use drags a file over the drop zone: */
.fd-zone.over { border-color: maroon; background: #eee; }
</style>
</head>
<body>
<noscript style="color: maroon">
<h2>JavaScript is disabled in your browser. How do you expect FileDrop to work?</h2>
</noscript>
<h2 style="text-align: center">
<a href="http://filedropjs.org">FileDrop</a> basic sample
</h2>
<!-- A FileDrop area. Can contain any text or elements, or be empty.
Can be of any HTML tag too, not necessary fieldset. -->
<fieldset id="zone">
<legend>Drop a file inside&hellip;</legend>
<p>Or click here to <em>Browse</em>..</p>
<!-- Putting another element on top of file input so it overlays it
and user can interact with it freely. -->
<p style="z-index: 10; position: relative">
<input type="checkbox" id="multiple">
<label for="multiple">Allow multiple selection</label>
</p>
</fieldset>
<script type="text/javascript">
// Attach FileDrop to an area ('zone' is an ID but you can also give a DOM node):
var zone = new FileDrop('zone', {});
// Do something when a user chooses or drops a file:
zone.event('send', function (files) {
// Depending on browser support files (FileList) might contain multiple items.
files.each(function (file) {
// React on successful AJAX upload:
file.event('done', function (xhr) {
// 'this' here points to fd.File instance that has triggered the event.
alert('Done uploading ' + this.name + ', response:\n\n' + xhr.responseText);
});
// Send the file:
file.sendTo('ajax-upload');
});
});
// A bit of sugar - toggling multiple selection:
fd.addEvent(fd.byID('multiple'), 'change', function (e) {
zone.multiple(e.currentTarget || e.srcElement.checked);
});
</script>
</body>
</html>

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -25,19 +25,33 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#import <mach-o/getsect.h>
#import "GCDWebServer.h" #import "GCDWebServer.h"
static NSData* _DataFromTEXTSection(const char* name) {
unsigned long size = 0;
char* ptr = getsectdata("__TEXT", name, &size);
if (!ptr || !size) {
abort();
}
return [NSData dataWithBytesNoCopy:ptr length:size freeWhenDone:NO];
}
int main(int argc, const char* argv[]) { int main(int argc, const char* argv[]) {
BOOL success = NO; BOOL success = NO;
int mode = (argc == 2 ? MIN(MAX(atoi(argv[1]), 0), 3) : 0);
@autoreleasepool { @autoreleasepool {
GCDWebServer* webServer = [[GCDWebServer alloc] init]; GCDWebServer* webServer = [[GCDWebServer alloc] init];
switch (0) { switch (mode) {
// Simply serve contents of home directory
case 0: { case 0: {
[webServer addHandlerForBasePath:@"/" localPath:NSHomeDirectory() indexFilename:nil cacheAge:0]; [webServer addGETHandlerForBasePath:@"/" directoryPath:NSHomeDirectory() indexFilename:nil cacheAge:0 allowRangeRequests:YES];
break; break;
} }
// Renders a HTML page
case 1: { case 1: {
[webServer addDefaultHandlerForMethod:@"GET" [webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class] requestClass:[GCDWebServerRequest class]
@@ -49,6 +63,7 @@ int main(int argc, const char* argv[]) {
break; break;
} }
// Implements an HTML form
case 2: { case 2: {
[webServer addHandlerForMethod:@"GET" [webServer addHandlerForMethod:@"GET"
path:@"/" path:@"/"
@@ -79,6 +94,33 @@ int main(int argc, const char* argv[]) {
break; break;
} }
// Implements drag & drop file upload using http://filedropjs.org (requires Chrome 13+, Firefox 3.6+, IE 10+ or Safari 6+)
case 3: {
[webServer addGETHandlerForPath:@"/"
staticData:_DataFromTEXTSection("_index_html_")
contentType:@"text/html; charset=utf-8"
cacheAge:0];
[webServer addGETHandlerForPath:@"/filedrop-min.js"
staticData:_DataFromTEXTSection("_filedrop_js_")
contentType:@"application/javascript; charset=utf-8"
cacheAge:0];
[webServer addHandlerForMethod:@"POST" path:@"/ajax-upload" requestClass:[GCDWebServerFileRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
NSString* fileName = GCDWebServerUnescapeURLString([request.headers objectForKey:@"X-File-Name"]);
NSString* inPath = [(GCDWebServerFileRequest*)request filePath];
NSString* outPath = [@"/tmp" stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] removeItemAtPath:outPath error:NULL];
if ([[NSFileManager defaultManager] moveItemAtPath:inPath toPath:outPath error:NULL]) {
NSString* message = [NSString stringWithFormat:@"File uploaded to \"%@\"", outPath];
return [GCDWebServerDataResponse responseWithText:message];
} else {
return [GCDWebServerResponse responseWithStatusCode:500];
}
}];
break;
}
} }
success = [webServer runWithPort:8080]; success = [webServer runWithPort:8080];
#if !__has_feature(objc_arc) #if !__has_feature(objc_arc)

View File

@@ -10,7 +10,7 @@ GCDWebServer is a lightweight GCD based HTTP 1.1 server designed to be embedded
* Available under a friendly [New BSD License](LICENSE) * Available under a friendly [New BSD License](LICENSE)
What's not available out of the box but can be implemented on top of the API: What's not available out of the box but can be implemented on top of the API:
* Authentication like Basic Authentication * Authentication like [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)
* Web forms submitted using "multipart/mixed" * Web forms submitted using "multipart/mixed"
What's not supported (but not really required from an embedded HTTP server): What's not supported (but not really required from an embedded HTTP server):
@@ -18,8 +18,8 @@ What's not supported (but not really required from an embedded HTTP server):
* HTTPS * HTTPS
Requirements: Requirements:
* OS X 10.7 or later * OS X 10.7 or later (x86_64)
* iOS 5.0 or later * iOS 5.0 or later (armv7, armv7s or arm64)
Hello World Hello World
=========== ===========
@@ -67,7 +67,7 @@ int main(int argc, const char* argv[]) {
@autoreleasepool { @autoreleasepool {
GCDWebServer* webServer = [[GCDWebServer alloc] init]; GCDWebServer* webServer = [[GCDWebServer alloc] init];
[webServer addHandlerForBasePath:@"/" localPath:NSHomeDirectory() indexFilename:nil cacheAge:3600]; [webServer addGETHandlerForBasePath:@"/" directoryPath:NSHomeDirectory() indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
[webServer runWithPort:8080]; [webServer runWithPort:8080];
[webServer release]; [webServer release];
@@ -171,7 +171,7 @@ Assuming you have a website directory in your app containing HTML template files
NSString* websitePath = [[NSBundle mainBundle] pathForResource:@"Website" ofType:nil]; NSString* websitePath = [[NSBundle mainBundle] pathForResource:@"Website" ofType:nil];
// Add a default handler to serve static files (i.e. anything other than HTML files) // Add a default handler to serve static files (i.e. anything other than HTML files)
[self addHandlerForBasePath:@"/" localPath:websitePath indexFilename:nil cacheAge:3600]; [self addGETHandlerForBasePath:@"/" directoryPath:websitePath indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
// Add an override handler for all requests to "*.html" URLs to do the special HTML templatization // Add an override handler for all requests to "*.html" URLs to do the special HTML templatization
[self addHandlerForMethod:@"GET" [self addHandlerForMethod:@"GET"

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -29,10 +29,6 @@
#import "GCDWebServer.h" #import "GCDWebServer.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate> { @interface AppDelegate : UIResponder <UIApplicationDelegate>
@private
UIWindow* _window;
GCDWebServer* _webServer;
}
@property(retain, nonatomic) UIWindow* window; @property(retain, nonatomic) UIWindow* window;
@end @end

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -27,8 +27,17 @@
#import "AppDelegate.h" #import "AppDelegate.h"
@interface AppDelegate () {
@private
UIWindow* _window;
GCDWebServer* _webServer;
}
@end
@implementation AppDelegate @implementation AppDelegate
@synthesize window=_window;
#if !__has_feature(objc_arc) #if !__has_feature(objc_arc)
- (void)dealloc { - (void)dealloc {

View File

@@ -1,5 +1,5 @@
/* /*
Copyright (c) 2012-2013, Pierre-Olivier Latour Copyright (c) 2012-2014, Pierre-Olivier Latour
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without