24 Commits
3.2.1 ... 3.2.4

Author SHA1 Message Date
Pierre-Olivier Latour
e8b67264ab Bumped version 2015-06-12 09:28:46 -07:00
Pierre-Olivier Latour
3d5fd0b828 Update README.md 2015-05-12 17:46:55 -07:00
Pierre-Olivier Latour
9524d31b1b Allow harmless 'Content-Type' headers on requests 2015-05-04 09:59:03 -07:00
Pierre-Olivier Latour
a3606d6027 Don't start a background task while the app is already in background 2015-04-30 14:53:45 -07:00
Pierre-Olivier Latour
00b2c38109 Bumped version 2015-04-13 13:04:37 -07:00
Pierre-Olivier Latour
0a9d3105fc Fixed -serverURL not taking into account GCDWebServerOption_BindToLocalhost 2015-04-13 13:02:52 -07:00
Pierre-Olivier Latour
0f0a9840e4 Fixed Xcode 6.3 warnings 2015-04-13 12:54:41 -07:00
Pierre-Olivier Latour
047fdddb0e Merge pull request #155 from sergiou87/master
Add remote and local addresses to GCDWebServerRequest
2015-03-21 16:21:48 -07:00
Sergio Padrino
79d6075a84 Add remote and local addresses to GCDWebServerRequest 2015-03-22 00:17:26 +01:00
Pierre-Olivier Latour
594497d234 Updated for CocoaLumberJack 2.0 2015-03-21 11:10:10 -07:00
Pierre-Olivier Latour
1f7c0366f0 Removed Bot scheme 2015-03-13 00:23:33 -07:00
Pierre-Olivier Latour
fe472cdd54 Update README.md 2015-03-05 16:53:10 -08:00
Pierre-Olivier Latour
9c33c83351 Handle starting the server with nil options 2015-03-05 16:48:11 -08:00
Pierre-Olivier Latour
9719406303 Made _CompareResources() easier to read 2015-02-23 16:02:14 -08:00
Pierre-Olivier Latour
0b8f7ff6ad Merge remote-tracking branch 'origin/master' 2015-02-23 16:00:39 -08:00
Pierre-Olivier Latour
71c08cff73 Update README.md 2015-01-28 10:35:43 -08:00
Pierre-Olivier Latour
1a6786488a Bumped version 2015-01-15 09:34:49 -08:00
Pierre-Olivier Latour
472c7855a7 Only wipe GCDWebUploader.bundle on Debug to avoid issues on Xcode bot 2015-01-05 23:54:23 -08:00
Pierre-Olivier Latour
2fdeb9581c Added Xcode bot scheme 2015-01-05 23:47:58 -08:00
Pierre-Olivier Latour
c4310fcdf4 Addressed static analyzer warnings 2015-01-05 23:47:49 -08:00
Pierre-Olivier Latour
33645d3c6b Fixed incorrect documentation for GCDWebServerAsyncStreamBlock 2015-01-02 18:55:30 -08:00
Pierre-Olivier Latour
3618dcac7e Added asyncResponse2 mode 2015-01-02 09:41:45 -08:00
Pierre-Olivier Latour
432e3826c9 Update README.md 2014-12-08 07:49:07 -08:00
Pierre-Olivier Latour
4e31508195 Removed invalid check 2014-12-02 08:39:29 -08:00
17 changed files with 206 additions and 106 deletions

View File

@@ -602,6 +602,8 @@ static inline xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden; @synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden;
@dynamic delegate;
- (instancetype)initWithUploadDirectory:(NSString*)path { - (instancetype)initWithUploadDirectory:(NSString*)path {
if ((self = [super init])) { if ((self = [super init])) {
_uploadDirectory = [[path stringByStandardizingPath] copy]; _uploadDirectory = [[path stringByStandardizingPath] copy];

View File

@@ -7,7 +7,7 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'GCDWebServer' s.name = 'GCDWebServer'
s.version = '3.2.1' s.version = '3.2.4'
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' } s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
s.license = { :type => 'BSD', :file => 'LICENSE' } s.license = { :type => 'BSD', :file => 'LICENSE' }
s.homepage = 'https://github.com/swisspol/GCDWebServer' s.homepage = 'https://github.com/swisspol/GCDWebServer'

View File

@@ -422,8 +422,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "cd \"$BUILT_PRODUCTS_DIR\"\nrm -rf \"GCDWebUploader.bundle\"\n"; shellScript = "if [ \"$CONFIGURATION\" == \"Debug\" ]; then\n cd \"$BUILT_PRODUCTS_DIR\"\n rm -rf \"GCDWebUploader.bundle\"\nfi\n";
showEnvVarsInLog = 0;
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
@@ -536,6 +535,7 @@
"-Wno-documentation", "-Wno-documentation",
"-Wno-documentation-unknown-command", "-Wno-documentation-unknown-command",
"-Wno-objc-missing-property-synthesis", "-Wno-objc-missing-property-synthesis",
"-Wno-cstring-format-directive",
); );
}; };
name = Debug; name = Debug;

View File

@@ -74,9 +74,9 @@ GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Info;
#endif #endif
#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__) #elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
#if DEBUG #if DEBUG
int GCDWebServerLogLevel = LOG_LEVEL_DEBUG; DDLogLevel GCDWebServerLogLevel = DDLogLevelDebug;
#else #else
int GCDWebServerLogLevel = LOG_LEVEL_INFO; DDLogLevel GCDWebServerLogLevel = DDLogLevelInfo;
#endif #endif
#endif #endif
@@ -171,6 +171,7 @@ static void _ExecuteMainThreadRunLoopSources() {
dispatch_source_t _source6; dispatch_source_t _source6;
CFNetServiceRef _registrationService; CFNetServiceRef _registrationService;
CFNetServiceRef _resolutionService; CFNetServiceRef _resolutionService;
BOOL _bindToLocalhost;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
BOOL _suspendInBackground; BOOL _suspendInBackground;
UIBackgroundTaskIdentifier _backgroundTask; UIBackgroundTaskIdentifier _backgroundTask;
@@ -243,7 +244,9 @@ static void _ExecuteMainThreadRunLoopSources() {
GWS_LOG_DEBUG(@"Did connect"); GWS_LOG_DEBUG(@"Did connect");
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
[self _startBackgroundTask]; if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
[self _startBackgroundTask];
}
#endif #endif
if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) { if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) {
@@ -284,8 +287,6 @@ static void _ExecuteMainThreadRunLoopSources() {
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTask]; [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
_backgroundTask = UIBackgroundTaskInvalid; _backgroundTask = UIBackgroundTaskInvalid;
GWS_LOG_DEBUG(@"Did end background task"); GWS_LOG_DEBUG(@"Did end background task");
} else if ([UIApplication sharedApplication]) {
GWS_DNOT_REACHED();
} }
} }
@@ -556,6 +557,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
_source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO]; _source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
_source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES]; _source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
_port = port; _port = port;
_bindToLocalhost = bindToLocalhost;
NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, nil); NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, nil);
NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp"); NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
@@ -621,6 +623,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
#endif #endif
_source4 = NULL; _source4 = NULL;
_port = 0; _port = 0;
_bindToLocalhost = NO;
_serverName = nil; _serverName = nil;
_authenticationRealm = nil; _authenticationRealm = nil;
@@ -666,7 +669,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error { - (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error {
if (_options == nil) { if (_options == nil) {
_options = [options copy]; _options = options ? [options copy] : @{};
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
_suspendInBackground = [_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue]; _suspendInBackground = [_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue];
if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start:error]) if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start:error])
@@ -717,7 +720,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
- (NSURL*)serverURL { - (NSURL*)serverURL {
if (_source4) { if (_source4) {
NSString* ipAddress = GCDWebServerGetPrimaryIPAddress(NO); // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice NSString* ipAddress = _bindToLocalhost ? @"localhost" : GCDWebServerGetPrimaryIPAddress(NO); // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice
if (ipAddress) { if (ipAddress) {
if (_port != 80) { if (_port != 80) {
return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]]; return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]];

View File

@@ -548,6 +548,8 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
} }
} }
if (_request) { if (_request) {
_request.localAddressData = self.localAddressData;
_request.remoteAddressData = self.remoteAddressData;
if ([_request hasBody]) { if ([_request hasBody]) {
[_request prepareForWriting]; [_request prepareForWriting];
if (_request.usesChunkedTransferEncoding || (extraData.length <= _request.contentLength)) { if (_request.usesChunkedTransferEncoding || (extraData.length <= _request.contentLength)) {
@@ -764,15 +766,19 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
} }
} }
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
static inline BOOL _CompareResources(NSString* responseETag, NSString* requestETag, NSDate* responseLastModified, NSDate* requestLastModified) { static inline BOOL _CompareResources(NSString* responseETag, NSString* requestETag, NSDate* responseLastModified, NSDate* requestLastModified) {
if ([requestETag isEqualToString:@"*"] && (!responseLastModified || !requestLastModified || ([responseLastModified compare:requestLastModified] != NSOrderedDescending))) { if (requestLastModified && responseLastModified) {
return YES; if ([responseLastModified compare:requestLastModified] != NSOrderedDescending) {
} else {
if ([responseETag isEqualToString:requestETag]) {
return YES; return YES;
} }
if (responseLastModified && requestLastModified && ([responseLastModified compare:requestLastModified] != NSOrderedDescending)) { }
if (requestETag && responseETag) { // Per the specs "If-None-Match" must be checked after "If-Modified-Since"
if ([requestETag isEqualToString:@"*"]) {
return YES;
}
if ([responseETag isEqualToString:requestETag]) {
return YES; return YES;
} }
} }

View File

@@ -87,15 +87,15 @@
* it as a logging facility. * it as a logging facility.
*/ */
#elif defined(__has_include) && __has_include("DDLogMacros.h") #elif defined(__has_include) && __has_include("CocoaLumberjack/CocoaLumberjack.h")
#import "DDLogMacros.h" #import <CocoaLumberjack/CocoaLumberjack.h>
#define __GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__ #define __GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__
#undef LOG_LEVEL_DEF #undef LOG_LEVEL_DEF
#define LOG_LEVEL_DEF GCDWebServerLogLevel #define LOG_LEVEL_DEF GCDWebServerLogLevel
extern int GCDWebServerLogLevel; extern DDLogLevel GCDWebServerLogLevel;
#define GWS_LOG_DEBUG(...) DDLogDebug(__VA_ARGS__) #define GWS_LOG_DEBUG(...) DDLogDebug(__VA_ARGS__)
#define GWS_LOG_VERBOSE(...) DDLogVerbose(__VA_ARGS__) #define GWS_LOG_VERBOSE(...) DDLogVerbose(__VA_ARGS__)
@@ -211,6 +211,8 @@ extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOO
@interface GCDWebServerRequest () @interface GCDWebServerRequest ()
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; @property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
@property(nonatomic, readwrite) NSData* localAddressData;
@property(nonatomic, readwrite) NSData* remoteAddressData;
- (void)prepareForWriting; - (void)prepareForWriting;
- (BOOL)performOpen:(NSError**)error; - (BOOL)performOpen:(NSError**)error;
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error; - (BOOL)performWriteData:(NSData*)data error:(NSError**)error;

View File

@@ -157,6 +157,30 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
*/ */
@property(nonatomic, readonly) BOOL acceptsGzipContentEncoding; @property(nonatomic, readonly) BOOL acceptsGzipContentEncoding;
/**
* Returns the address of the local peer (i.e. server) for the request
* as a raw "struct sockaddr".
*/
@property(nonatomic, readonly) NSData* localAddressData;
/**
* Returns the address of the local peer (i.e. server) for the request
* as a string.
*/
@property(nonatomic, readonly) NSString* localAddressString;
/**
* Returns the address of the remote peer (i.e. client) for the request
* as a raw "struct sockaddr".
*/
@property(nonatomic, readonly) NSData* remoteAddressData;
/**
* Returns the address of the remote peer (i.e. client) for the request
* as a string.
*/
@property(nonatomic, readonly) NSString* remoteAddressString;
/** /**
* This method is the designated initializer for the class. * This method is the designated initializer for the class.
*/ */

View File

@@ -88,7 +88,9 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
- (BOOL)open:(NSError**)error { - (BOOL)open:(NSError**)error {
int result = inflateInit2(&_stream, 15 + 16); int result = inflateInit2(&_stream, 15 + 16);
if (result != Z_OK) { if (result != Z_OK) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; if (error) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
}
return NO; return NO;
} }
if (![super open:error]) { if (![super open:error]) {
@@ -114,7 +116,9 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
_stream.avail_out = (uInt)maxLength; _stream.avail_out = (uInt)maxLength;
int result = inflate(&_stream, Z_NO_FLUSH); int result = inflate(&_stream, Z_NO_FLUSH);
if ((result != Z_OK) && (result != Z_STREAM_END)) { if ((result != Z_OK) && (result != Z_STREAM_END)) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; if (error) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
}
return NO; return NO;
} }
length += maxLength - _stream.avail_out; length += maxLength - _stream.avail_out;
@@ -153,6 +157,8 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
NSString* _noneMatch; NSString* _noneMatch;
NSRange _range; NSRange _range;
BOOL _gzipAccepted; BOOL _gzipAccepted;
NSData* _localAddress;
NSData* _remoteAddress;
BOOL _opened; BOOL _opened;
NSMutableArray* _decoders; NSMutableArray* _decoders;
@@ -164,7 +170,7 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
@implementation GCDWebServerRequest : NSObject @implementation GCDWebServerRequest : NSObject
@synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length, ifModifiedSince=_modifiedSince, ifNoneMatch=_noneMatch, @synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length, ifModifiedSince=_modifiedSince, ifNoneMatch=_noneMatch,
byteRange=_range, acceptsGzipContentEncoding=_gzipAccepted, usesChunkedTransferEncoding=_chunked; byteRange=_range, acceptsGzipContentEncoding=_gzipAccepted, usesChunkedTransferEncoding=_chunked, localAddressData=_localAddress, remoteAddressData=_remoteAddress;
- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
if ((self = [super init])) { if ((self = [super init])) {
@@ -180,6 +186,7 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
if (lengthHeader) { if (lengthHeader) {
NSInteger length = [lengthHeader integerValue]; NSInteger length = [lengthHeader integerValue];
if (_chunked || (length < 0)) { if (_chunked || (length < 0)) {
GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _url);
GWS_DNOT_REACHED(); GWS_DNOT_REACHED();
return nil; return nil;
} }
@@ -194,8 +201,8 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
_length = NSUIntegerMax; _length = NSUIntegerMax;
} else { } else {
if (_type) { if (_type) {
GWS_DNOT_REACHED(); GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _url);
return nil; _type = nil; // Content-Type without Content-Length or chunked-encoding doesn't make sense
} }
_length = NSUIntegerMax; _length = NSUIntegerMax;
} }
@@ -304,6 +311,14 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
[_attributes setValue:attribute forKey:key]; [_attributes setValue:attribute forKey:key];
} }
- (NSString*)localAddressString {
return GCDWebServerStringFromSockAddr(_localAddress.bytes, YES);
}
- (NSString*)remoteAddressString {
return GCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES);
}
- (NSString*)description { - (NSString*)description {
NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path]; NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path];
for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) { for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) {

View File

@@ -94,7 +94,9 @@
- (BOOL)open:(NSError**)error { - (BOOL)open:(NSError**)error {
int result = deflateInit2(&_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); int result = deflateInit2(&_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
if (result != Z_OK) { if (result != Z_OK) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; if (error) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
}
return NO; return NO;
} }
if (![super open:error]) { if (![super open:error]) {
@@ -130,7 +132,9 @@
if (result == Z_STREAM_END) { if (result == Z_STREAM_END) {
_finished = YES; _finished = YES;
} else if (result != Z_OK) { } else if (result != Z_OK) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; if (error) {
*error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
}
return nil; return nil;
} }
length += maxLength - _stream.avail_out; length += maxLength - _stream.avail_out;

View File

@@ -51,7 +51,9 @@
_data = [[NSMutableData alloc] init]; _data = [[NSMutableData alloc] init];
} }
if (_data == nil) { if (_data == nil) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed allocating memory"}]; if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed allocating memory"}];
}
return NO; return NO;
} }
return YES; return YES;

View File

@@ -56,7 +56,9 @@
- (BOOL)open:(NSError**)error { - (BOOL)open:(NSError**)error {
_file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); _file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (_file <= 0) { if (_file <= 0) {
*error = GCDWebServerMakePosixError(errno); if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return NO; return NO;
} }
return YES; return YES;
@@ -64,7 +66,9 @@
- (BOOL)writeData:(NSData*)data error:(NSError**)error { - (BOOL)writeData:(NSData*)data error:(NSError**)error {
if (write(_file, data.bytes, data.length) != (ssize_t)data.length) { if (write(_file, data.bytes, data.length) != (ssize_t)data.length) {
*error = GCDWebServerMakePosixError(errno); if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return NO; return NO;
} }
return YES; return YES;
@@ -72,7 +76,9 @@
- (BOOL)close:(NSError**)error { - (BOOL)close:(NSError**)error {
if (close(_file) < 0) { if (close(_file) < 0) {
*error = GCDWebServerMakePosixError(errno); if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return NO; return NO;
} }
#ifdef __GCDWEBSERVER_ENABLE_TESTING__ #ifdef __GCDWEBSERVER_ENABLE_TESTING__

View File

@@ -376,7 +376,9 @@ static NSData* _dashNewlineData = nil;
NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary"); NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary");
_parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files]; _parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files];
if (_parser == nil) { if (_parser == nil) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed starting to parse multipart form data"}]; if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed starting to parse multipart form data"}];
}
return NO; return NO;
} }
return YES; return YES;
@@ -384,7 +386,9 @@ static NSData* _dashNewlineData = nil;
- (BOOL)writeData:(NSData*)data error:(NSError**)error { - (BOOL)writeData:(NSData*)data error:(NSError**)error {
if (![_parser appendBytes:data.bytes length:data.length]) { if (![_parser appendBytes:data.bytes length:data.length]) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed continuing to parse multipart form data"}]; if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed continuing to parse multipart form data"}];
}
return NO; return NO;
} }
return YES; return YES;
@@ -394,7 +398,9 @@ static NSData* _dashNewlineData = nil;
BOOL atEnd = [_parser isAtEnd]; BOOL atEnd = [_parser isAtEnd];
_parser = nil; _parser = nil;
if (!atEnd) { if (!atEnd) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed finishing to parse multipart form data"}]; if (error) {
*error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed finishing to parse multipart form data"}];
}
return NO; return NO;
} }
return YES; return YES;

View File

@@ -142,11 +142,15 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
- (BOOL)open:(NSError**)error { - (BOOL)open:(NSError**)error {
_file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY); _file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
if (_file <= 0) { if (_file <= 0) {
*error = GCDWebServerMakePosixError(errno); if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return NO; return NO;
} }
if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) { if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) {
*error = GCDWebServerMakePosixError(errno); if (error) {
*error = GCDWebServerMakePosixError(errno);
}
close(_file); close(_file);
return NO; return NO;
} }
@@ -158,7 +162,9 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
NSMutableData* data = [[NSMutableData alloc] initWithLength:length]; NSMutableData* data = [[NSMutableData alloc] initWithLength:length];
ssize_t result = read(_file, data.mutableBytes, length); ssize_t result = read(_file, data.mutableBytes, length);
if (result < 0) { if (result < 0) {
*error = GCDWebServerMakePosixError(errno); if (error) {
*error = GCDWebServerMakePosixError(errno);
}
return nil; return nil;
} }
if (result > 0) { if (result > 0) {

View File

@@ -29,8 +29,8 @@
/** /**
* The GCDWebServerStreamBlock is called to stream the data for the HTTP body. * The GCDWebServerStreamBlock is called to stream the data for the HTTP body.
* The block must return empty NSData when done or nil on error and set the * The block must return either a chunk of data, an empty NSData when done, or
* "error" argument which is guaranteed to be non-NULL. * nil on error and set the "error" argument which is guaranteed to be non-NULL.
*/ */
typedef NSData* (^GCDWebServerStreamBlock)(NSError** error); typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);
@@ -39,13 +39,10 @@ typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);
* except the streamed data can be returned at a later time allowing for * except the streamed data can be returned at a later time allowing for
* truly asynchronous generation of the data. * truly asynchronous generation of the data.
* *
* The block must return empty NSData when done or nil on error and set the * The block must call "completionBlock" passing the new chunk of data when ready,
* "error" argument which is guaranteed to be non-NULL. * an empty NSData when done, or nil on error and pass a NSError.
* *
* The block must regularly call "completionBlock" passing new streamed data. * The block cannot call "completionBlock" more than once per invocation.
* Eventually it must call "completionBlock" passing an empty NSData indicating
* the end of the stream has been reached, or pass nil and an NSError in case of
* error.
*/ */
typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlock completionBlock); typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlock completionBlock);

View File

@@ -292,6 +292,8 @@
@synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden, @synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden,
title=_title, header=_header, prologue=_prologue, epilogue=_epilogue, footer=_footer; title=_title, header=_header, prologue=_prologue, epilogue=_epilogue, footer=_footer;
@dynamic delegate;
- (instancetype)initWithUploadDirectory:(NSString*)path { - (instancetype)initWithUploadDirectory:(NSString*)path {
if ((self = [super init])) { if ((self = [super init])) {
NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"GCDWebUploader" ofType:@"bundle"]]; NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"GCDWebUploader" ofType:@"bundle"]];

View File

@@ -357,7 +357,7 @@ int main(int argc, const char* argv[]) {
fprintf(stdout, "Running in Async Response mode"); fprintf(stdout, "Running in Async Response mode");
webServer = [[GCDWebServer alloc] init]; webServer = [[GCDWebServer alloc] init];
[webServer addHandlerForMethod:@"GET" [webServer addHandlerForMethod:@"GET"
path:@"/" path:@"/async"
requestClass:[GCDWebServerRequest class] requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
@@ -366,6 +366,29 @@ int main(int argc, const char* argv[]) {
completionBlock(response); completionBlock(response);
}); });
}];
[webServer addHandlerForMethod:@"GET"
path:@"/async2"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock handlerCompletionBlock) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__block int countDown = 10;
GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/plain" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock readerCompletionBlock) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
readerCompletionBlock(data, nil);
});
}];
handlerCompletionBlock(response);
});
}]; }];
break; break;
} }

126
README.md
View File

@@ -6,8 +6,6 @@ Overview
[![Platform](http://cocoapod-badges.herokuapp.com/p/GCDWebServer/badge.png)](https://github.com/swisspol/GCDWebServer) [![Platform](http://cocoapod-badges.herokuapp.com/p/GCDWebServer/badge.png)](https://github.com/swisspol/GCDWebServer)
[![License](http://img.shields.io/cocoapods/l/GCDWebServer.svg)](LICENSE) [![License](http://img.shields.io/cocoapods/l/GCDWebServer.svg)](LICENSE)
*ANNOUNCEMENT: If you like GCDWebServer, check out [XLFacility](https://github.com/swisspol/XLFacility), an elegant and powerful logging facility for OS X & iOS by the same author and also open-source. XLFacility can be used seemlessly to handle logging from GCDWebServer (see "Logging in GCDWebServer" below).*
GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to be embedded in OS X & iOS apps. It was written from scratch with the following goals in mind: GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to be embedded in OS X & iOS apps. It was written from scratch with the following goals in mind:
* Elegant and easy to use architecture with only 4 core classes: server, connection, request and response (see "Understanding GCDWebServer's Architecture" below) * Elegant and easy to use architecture with only 4 core classes: server, connection, request and response (see "Understanding GCDWebServer's Architecture" below)
* Well designed API with fully documented headers for easy integration and customization * Well designed API with fully documented headers for easy integration and customization
@@ -63,6 +61,8 @@ Hello World
These code snippets show how to implement a custom HTTP server that runs on port 8080 and returns a "Hello World" HTML page to any request. Since GCDWebServer uses GCD blocks to handle requests, no subclassing or delegates are needed, which results in very clean code. These code snippets show how to implement a custom HTTP server that runs on port 8080 and returns a "Hello World" HTML page to any request. Since GCDWebServer uses GCD blocks to handle requests, no subclassing or delegates are needed, which results in very clean code.
**IMPORTANT:** If not using CocoaPods, be sure to add the `libz` shared system library to the Xcode target for your app.
**OS X version (command line tool):** **OS X version (command line tool):**
```objectivec ```objectivec
#import "GCDWebServer.h" #import "GCDWebServer.h"
@@ -137,7 +137,7 @@ import Foundation
let webServer = GCDWebServer() let webServer = GCDWebServer()
webServer.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self) { request in webServer.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: {request in
return GCDWebServerDataResponse(HTML:"<html><body><p>Hello World</p></body></html>") return GCDWebServerDataResponse(HTML:"<html><body><p>Hello World</p></body></html>")
} }
@@ -152,65 +152,6 @@ println("Visit \(webServer.serverURL) in your web browser")
#import "GCDWebServerDataResponse.h" #import "GCDWebServerDataResponse.h"
``` ```
Asynchronous HTTP Responses
===========================
New in GCDWebServer 3.0 is the ability to process HTTP requests aysnchronously i.e. add handlers to the server which generate their ```GCDWebServerResponse``` asynchronously. This is achieved by adding handlers that use a ```GCDWebServerAsyncProcessBlock``` instead of a ```GCDWebServerProcessBlock```. Here's an example:
**(Synchronous version)** The handler blocks while generating the HTTP response:
```objectivec
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
return response;
}];
```
**(Asynchronous version)** The handler returns immediately and calls back GCDWebServer later with the generated HTTP response:
```objectivec
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
// Do some async operation like network access or file I/O (simulated here using dispatch_after())
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
completionBlock(response);
});
}];
```
**(Advanced asynchronous version)** The handler returns immediately a streamed HTTP response which itself generates its contents asynchronously:
```objectivec
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/html" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
completionBlock([@"<html><body><p>Hello" dataUsingEncoding:NSUTF8StringEncoding], nil); // Generate the 1st part of the stream data
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
completionBlock([@"World</p></body></html>" dataUsingEncoding:NSUTF8StringEncoding], nil); // Generate the 2nd part of the stream data
completionBlock([NSData data], nil); // Must pass an empty NSData to signal the end of the stream
});
});
}];
return response;
}];
```
*Note that you can even combine both the asynchronous and advanced asynchronous versions to return asynchronously an asynchronous HTTP response!*
Web Based Uploads in iOS Apps Web Based Uploads in iOS Apps
============================= =============================
@@ -274,6 +215,7 @@ Serving a Static Website
GCDWebServer includes a built-in handler that can recursively serve a directory (it also lets you control how the ["Cache-Control"](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) header should be set): GCDWebServer includes a built-in handler that can recursively serve a directory (it also lets you control how the ["Cache-Control"](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) header should be set):
**OS X version (command line tool):**
```objectivec ```objectivec
#import "GCDWebServer.h" #import "GCDWebServer.h"
@@ -318,6 +260,66 @@ Handlers require 2 GCD blocks:
Note that most methods on ```GCDWebServer``` to add handlers only require the ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` as they already provide a built-in ```GCDWebServerMatchBlock``` e.g. to match a URL path with a Regex. Note that most methods on ```GCDWebServer``` to add handlers only require the ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` as they already provide a built-in ```GCDWebServerMatchBlock``` e.g. to match a URL path with a Regex.
Asynchronous HTTP Responses
===========================
New in GCDWebServer 3.0 is the ability to process HTTP requests aysnchronously i.e. add handlers to the server which generate their ```GCDWebServerResponse``` asynchronously. This is achieved by adding handlers that use a ```GCDWebServerAsyncProcessBlock``` instead of a ```GCDWebServerProcessBlock```. Here's an example:
**(Synchronous version)** The handler blocks while generating the HTTP response:
```objectivec
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
return response;
}];
```
**(Asynchronous version)** The handler returns immediately and calls back GCDWebServer later with the generated HTTP response:
```objectivec
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
// Do some async operation like network access or file I/O (simulated here using dispatch_after())
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
completionBlock(response);
});
}];
```
**(Advanced asynchronous version)** The handler returns immediately a streamed HTTP response which itself generates its contents asynchronously:
```objectivec
[webServer addDefaultHandlerForMethod:@"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
NSMutableArray* contents = [NSMutableArray arrayWithObjects:@"<html><body><p>\n", @"Hello World!\n", @"</p></body></html>\n", nil]; // Fake data source we are reading from
GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/html" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
// Simulate a delay reading from the fake data source
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString* string = contents.firstObject;
if (string) {
[contents removeObjectAtIndex:0];
completionBlock([string dataUsingEncoding:NSUTF8StringEncoding], nil); // Generate the 2nd part of the stream data
} else {
completionBlock([NSData data], nil); // Must pass an empty NSData to signal the end of the stream
}
});
}];
return response;
}];
```
*Note that you can even combine both the asynchronous and advanced asynchronous versions to return asynchronously an asynchronous HTTP response!*
GCDWebServer & Background Mode for iOS Apps GCDWebServer & Background Mode for iOS Apps
=========================================== ===========================================