12 Commits
2.5.3 ... 3.0.1

Author SHA1 Message Date
Pierre-Olivier Latour
e59cf4b6df Bumped version 2014-10-12 23:31:42 -07:00
Pierre-Olivier Latour
9e8f0e00f3 Updated iOS app for iOS 8 SDK 2014-10-12 00:47:12 -07:00
Pierre-Olivier Latour
d7650a71e0 Lowered deployment targets 2014-10-11 01:02:01 -07:00
Pierre-Olivier Latour
420ddc3eac Replaced preprocessor constant "NDEBUG" by the more standard "DEBUG" one and flipped behavior accordingly 2014-10-10 10:27:07 -07:00
Pierre-Olivier Latour
143e38c968 Added README and podspec files to Xcode project for convenience 2014-10-10 10:18:20 -07:00
Pierre-Olivier Latour
8e9fe4c4c1 Update README.md 2014-10-09 12:31:05 -07:00
Pierre-Olivier Latour
95bccff2f7 Fix 2014-10-09 10:16:13 -07:00
Pierre-Olivier Latour
780a608d6c Update README.md 2014-10-09 09:57:08 -07:00
Pierre-Olivier Latour
18d93bbf47 Bumped version 2014-10-09 09:54:08 -07:00
Pierre-Olivier Latour
b35ebd7d58 #92 Added support for async handlers 2014-10-09 09:53:03 -07:00
Pierre-Olivier Latour
a11b047233 Renamed GCDWebServerStreamingBlock to GCDWebServerStreamBlock 2014-10-09 09:43:59 -07:00
Pierre-Olivier Latour
4eac9d4f8e Upgrade to Xcode 6.1 2014-10-09 09:09:34 -07:00
12 changed files with 210 additions and 77 deletions

View File

@@ -7,7 +7,7 @@
Pod::Spec.new do |s|
s.name = 'GCDWebServer'
s.version = '2.5.3'
s.version = '3.0.1'
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
s.license = { :type => 'BSD', :file => 'LICENSE' }
s.homepage = 'https://github.com/swisspol/GCDWebServer'
@@ -28,7 +28,6 @@ Pod::Spec.new do |s|
cs.ios.frameworks = 'MobileCoreServices', 'CFNetwork'
cs.osx.library = 'z'
cs.osx.framework = 'SystemConfiguration'
cs.compiler_flags = '-DNDEBUG' # TODO: Only set this for Release configuration
end
s.subspec 'WebDAV' do |cs|

View File

@@ -112,6 +112,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; };
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; };
E26DC18719E84B2200C68DDC /* GCDWebServer.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = GCDWebServer.podspec; sourceTree = "<group>"; };
E26DC18819E84BC000C68DDC /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
E28BAE1618F99C810095C089 /* GCDWebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServer.h; sourceTree = "<group>"; };
E28BAE1718F99C810095C089 /* GCDWebServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServer.m; sourceTree = "<group>"; };
E28BAE1818F99C810095C089 /* GCDWebServerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerConnection.h; sourceTree = "<group>"; };
@@ -183,6 +185,8 @@
08FB7794FE84155DC02AAC07 /* LittleCMS */ = {
isa = PBXGroup;
children = (
E26DC18819E84BC000C68DDC /* README.md */,
E26DC18719E84B2200C68DDC /* GCDWebServer.podspec */,
E28BAE1418F99C810095C089 /* GCDWebServer */,
E2A0E80718F3432600C580B1 /* GCDWebDAVServer */,
E2BE850618E77ECA0061360B /* GCDWebUploader */,
@@ -370,7 +374,7 @@
08FB7793FE84155DC02AAC07 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0600;
LastUpgradeCheck = 0610;
};
buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "GCDWebServer" */;
compatibilityVersion = "Xcode 3.2";
@@ -490,7 +494,7 @@
1DEB928608733DD80010E9CD /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD)";
MACOSX_DEPLOYMENT_TARGET = 10.7;
PRODUCT_NAME = GCDWebServer;
SDKROOT = macosx;
};
@@ -499,7 +503,7 @@
1DEB928708733DD80010E9CD /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD)";
MACOSX_DEPLOYMENT_TARGET = 10.7;
PRODUCT_NAME = GCDWebServer;
SDKROOT = macosx;
};
@@ -510,6 +514,7 @@
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = __GCDWEBSERVER_ENABLE_TESTING__;
HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2";
ONLY_ACTIVE_ARCH = YES;
@@ -529,6 +534,7 @@
"-Wno-padded",
"-Wno-documentation",
"-Wno-documentation-unknown-command",
"-Wno-objc-missing-property-synthesis",
);
};
name = Debug;
@@ -537,7 +543,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
GCC_PREPROCESSOR_DEFINITIONS = NDEBUG;
GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = __GCDWEBSERVER_ENABLE_TESTING__;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
HEADER_SEARCH_PATHS = "$(SDKROOT)/usr/include/libxml2";
@@ -548,9 +553,9 @@
E22112761690B4DF0048D2B2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD)";
CODE_SIGN_IDENTITY = "iPhone Developer";
INFOPLIST_FILE = iOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
PRODUCT_NAME = GCDWebServer;
PROVISIONING_PROFILE = "";
SDKROOT = iphoneos;
@@ -561,9 +566,9 @@
E22112771690B4DF0048D2B2 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD)";
CODE_SIGN_IDENTITY = "iPhone Developer";
INFOPLIST_FILE = iOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
PRODUCT_NAME = GCDWebServer;
PROVISIONING_PROFILE = "";
SDKROOT = iphoneos;

View File

@@ -33,8 +33,8 @@
/**
* Log levels used by GCDWebServer.
*
* @warning kGCDWebServerLogLevel_Debug is only available if "NDEBUG" is not
* defined when building.
* @warning kGCDWebServerLogLevel_Debug is only functional if the preprocessor
* constant "DEBUG" is is non-zero at build time.
*/
typedef NS_ENUM(int, GCDWebServerLogLevel) {
kGCDWebServerLogLevel_Debug = 0,
@@ -69,6 +69,19 @@ typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod,
*/
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
/**
* The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
* except the GCDWebServerResponse can be returned to the server at a later time
* allowing for asynchronous generation of the response.
*
* The block must eventually call "completionBlock" passing a GCDWebServerResponse
* or nil on error, which will result in a 500 HTTP status code returned to the client.
* It's however recommended to return a GCDWebServerErrorResponse on error so more
* useful information can be returned to the client.
*/
typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* response);
typedef void (^GCDWebServerAsyncProcessBlock)(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
/**
* The port used by the GCDWebServer (NSNumber / NSUInteger).
*
@@ -289,7 +302,7 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
- (instancetype)init;
/**
* Adds a handler to the server to handle incoming HTTP requests.
* Adds to the server a handler that generates responses synchronously when handling incoming HTTP requests.
*
* Handlers are called in a LIFO queue, so if multiple handlers can potentially
* respond to a given request, the latest added one wins.
@@ -298,6 +311,16 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
*/
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
/**
* Adds to the server a handler that generates responses asynchronously when handling incoming HTTP requests.
*
* Handlers are called in a LIFO queue, so if multiple handlers can potentially
* respond to a given request, the latest added one wins.
*
* @warning Addling handlers while the server is running is not allowed.
*/
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock;
/**
* Removes all handlers previously added to the server.
*
@@ -392,22 +415,44 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
/**
* Adds a default handler to the server to handle all incoming HTTP requests
* with a given HTTP method.
* with a given HTTP method and generate responses synchronously.
*/
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
/**
* Adds a default handler to the server to handle all incoming HTTP requests
* with a given HTTP method and generate responses asynchronously.
*/
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a specific case-insensitive path.
* HTTP method and a specific case-insensitive path and generate responses
* synchronously.
*/
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a path matching a case-insensitive regular expression.
* HTTP method and a specific case-insensitive path and generate responses
* asynchronously.
*/
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a path matching a case-insensitive regular expression and
* generate responses synchronously.
*/
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
/**
* Adds a handler to the server to handle incoming HTTP requests with a given
* HTTP method and a path matching a case-insensitive regular expression and
* generate responses asynchronously.
*/
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block;
@end
@interface GCDWebServer (GETHandlers)
@@ -444,8 +489,9 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
/**
* Sets the current log level below which logged messages are discarded.
*
* The default level is either DEBUG or INFO if "NDEBUG" is defined at build-time.
* It can also be set at runtime with the "logLevel" environment variable.
* The default level is INFO (or DEBUG if the preprocessor constant "DEBUG"
* is non-zero at build time).
* It can also be set at run time with the "logLevel" environment variable.
*/
+ (void)setLogLevel:(GCDWebServerLogLevel)level;

View File

@@ -62,10 +62,10 @@ NSString* const GCDWebServerAuthenticationMethod_Basic = @"Basic";
NSString* const GCDWebServerAuthenticationMethod_DigestAccess = @"DigestAccess";
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
#ifdef NDEBUG
GCDWebServerLogLevel GCDLogLevel = kGCDWebServerLogLevel_Info;
#else
#if DEBUG
GCDWebServerLogLevel GCDLogLevel = kGCDWebServerLogLevel_Debug;
#else
GCDWebServerLogLevel GCDLogLevel = kGCDWebServerLogLevel_Info;
#endif
#endif
@@ -114,25 +114,25 @@ static void _ExecuteMainThreadRunLoopSources() {
@interface GCDWebServerHandler () {
@private
GCDWebServerMatchBlock _matchBlock;
GCDWebServerProcessBlock _processBlock;
GCDWebServerAsyncProcessBlock _asyncProcessBlock;
}
@end
@implementation GCDWebServerHandler
@synthesize matchBlock=_matchBlock, processBlock=_processBlock;
@synthesize matchBlock=_matchBlock, asyncProcessBlock=_asyncProcessBlock;
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock {
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
if ((self = [super init])) {
_matchBlock = [matchBlock copy];
_processBlock = [processBlock copy];
_asyncProcessBlock = [processBlock copy];
}
return self;
}
- (void)dealloc {
ARC_RELEASE(_matchBlock);
ARC_RELEASE(_processBlock);
ARC_RELEASE(_asyncProcessBlock);
ARC_DEALLOC(super);
}
@@ -345,9 +345,15 @@ static void _ExecuteMainThreadRunLoopSources() {
return type && CFStringGetLength(type) ? ARC_BRIDGE_RELEASE(CFStringCreateCopy(kCFAllocatorDefault, type)) : nil;
}
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)handlerBlock {
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock {
[self addHandlerWithMatchBlock:matchBlock asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
completionBlock(processBlock(request));
}];
}
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock {
DCHECK(_options == nil);
GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock processBlock:handlerBlock];
GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock asyncProcessBlock:processBlock];
[_handlers insertObject:handler atIndex:0];
ARC_RELEASE(handler);
}
@@ -754,6 +760,12 @@ static inline NSString* _EncodeBase64(NSString* string) {
@implementation GCDWebServer (Handlers)
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
[self addDefaultHandlerForMethod:method requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
completionBlock(block(request));
}];
}
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
if (![requestMethod isEqualToString:method]) {
@@ -761,10 +773,16 @@ static inline NSString* _EncodeBase64(NSString* string) {
}
return ARC_AUTORELEASE([[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
} processBlock:block];
} asyncProcessBlock:block];
}
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
[self addHandlerForMethod:method path:path requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
completionBlock(block(request));
}];
}
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block {
if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
@@ -776,13 +794,19 @@ static inline NSString* _EncodeBase64(NSString* string) {
}
return ARC_AUTORELEASE([[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
} processBlock:block];
} asyncProcessBlock:block];
} else {
DNOT_REACHED();
}
}
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
[self addHandlerForMethod:method pathRegex:regex requestClass:aClass asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
completionBlock(block(request));
}];
}
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)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) {
@@ -808,7 +832,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
[request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures];
return ARC_AUTORELEASE(request);
} processBlock:block];
} asyncProcessBlock:block];
} else {
DNOT_REACHED();
}
@@ -1114,7 +1138,7 @@ static void _LogResult(NSString* format, ...) {
_LogResult(@" Bodies not matching:\n Expected: %lu bytes\n Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length);
success = NO;
#if !TARGET_OS_IPHONE
#ifndef NDEBUG
#if DEBUG
if (GCDWebServerIsTextContentType([expectedHeaders objectForKey:@"Content-Type"])) {
NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];
NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]];

View File

@@ -138,13 +138,14 @@
/**
* Assuming a valid HTTP request was received and -preflightRequest: returned nil,
* this method is called to process the request.
* this method is called to process the request by executing the handler's
* process block.
*/
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block;
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion;
/**
* Assuming a valid HTTP request was received and either -preflightRequest:
* or -processRequest:withBlock: returned a non-nil GCDWebServerResponse,
* or -processRequest:completion: returned a non-nil GCDWebServerResponse,
* this method is called to override the response.
*
* You can either modify the current response and return it, or return a

View File

@@ -373,15 +373,24 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (ARC_BRIDGE CFStringRef)GCDWebServerFormatRFC822([NSDate date]));
}
- (void)_startProcessingRequest {
DCHECK(_responseMessage == NULL);
GCDWebServerResponse* preflightResponse = [self preflightRequest:_request];
if (preflightResponse) {
[self _finishProcessingRequest:preflightResponse];
} else {
[self processRequest:_request completion:^(GCDWebServerResponse* processResponse) {
[self _finishProcessingRequest:processResponse];
}];
}
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- (void)_processRequest {
- (void)_finishProcessingRequest:(GCDWebServerResponse*)response {
DCHECK(_responseMessage == NULL);
BOOL hasBody = NO;
GCDWebServerResponse* response = [self preflightRequest:_request];
if (!response) {
response = [self processRequest:_request withBlock:_handler.processBlock];
}
if (response) {
response = [self overrideResponse:response forRequest:_request];
}
@@ -471,7 +480,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
NSError* localError = nil;
if ([_request performClose:&localError]) {
[self _processRequest];
[self _startProcessingRequest];
} else {
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
@@ -480,7 +489,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
}];
} else {
if ([_request performClose:&error]) {
[self _processRequest];
[self _startProcessingRequest];
} else {
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
@@ -501,7 +510,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
NSError* localError = nil;
if ([_request performClose:&localError]) {
[self _processRequest];
[self _startProcessingRequest];
} else {
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
@@ -572,7 +581,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_BadRequest];
}
} else {
[self _processRequest];
[self _startProcessingRequest];
}
} else {
_request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery];
@@ -772,16 +781,14 @@ static NSString* _StringFromAddressData(NSData* data) {
return response;
}
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block {
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion {
LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
GCDWebServerResponse* response = nil;
@try {
response = block(request);
_handler.asyncProcessBlock(request, completion);
}
@catch (NSException* exception) {
LOG_EXCEPTION(exception);
}
return response;
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26

View File

@@ -84,13 +84,7 @@ extern void GCDLogMessage(GCDWebServerLogLevel level, NSString* format, ...) NS_
#define LOG_ERROR(...) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Error) GCDLogMessage(kGCDWebServerLogLevel_Error, __VA_ARGS__); } while (0)
#define LOG_EXCEPTION(__EXCEPTION__) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Exception) GCDLogMessage(kGCDWebServerLogLevel_Exception, @"%@", __EXCEPTION__); } while (0)
#ifdef NDEBUG
#define DCHECK(__CONDITION__)
#define DNOT_REACHED()
#define LOG_DEBUG(...)
#else
#if DEBUG
#define DCHECK(__CONDITION__) \
do { \
@@ -101,6 +95,12 @@ extern void GCDLogMessage(GCDWebServerLogLevel level, NSString* format, ...) NS_
#define DNOT_REACHED() abort()
#define LOG_DEBUG(...) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Debug) GCDLogMessage(kGCDWebServerLogLevel_Debug, __VA_ARGS__); } while (0)
#else
#define DCHECK(__CONDITION__)
#define DNOT_REACHED()
#define LOG_DEBUG(...)
#endif
#endif
@@ -143,8 +143,7 @@ extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_F
@interface GCDWebServerHandler : NSObject
@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock;
@property(nonatomic, readonly) GCDWebServerProcessBlock processBlock;
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
@property(nonatomic, readonly) GCDWebServerAsyncProcessBlock asyncProcessBlock;
@end
@interface GCDWebServerRequest ()

View File

@@ -28,11 +28,11 @@
#import "GCDWebServerStreamedResponse.h"
/**
* The GCDWebServerStreamingBlock 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
* "error" argument which is guaranteed to be non-NULL.
*/
typedef NSData* (^GCDWebServerStreamingBlock)(NSError** error);
typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);
/**
* The GCDWebServerStreamedResponse subclass of GCDWebServerResponse streams
@@ -43,11 +43,11 @@ typedef NSData* (^GCDWebServerStreamingBlock)(NSError** error);
/**
* Creates a response with streamed data and a given content type.
*/
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamingBlock)block;
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
/**
* This method is the designated initializer for the class.
*/
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamingBlock)block;
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block;
@end

View File

@@ -29,17 +29,17 @@
@interface GCDWebServerStreamedResponse () {
@private
GCDWebServerStreamingBlock _block;
GCDWebServerStreamBlock _block;
}
@end
@implementation GCDWebServerStreamedResponse
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamingBlock)block {
+ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
return ARC_AUTORELEASE([[[self class] alloc] initWithContentType:type streamBlock:block]);
}
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamingBlock)block {
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
if ((self = [super init])) {
_block = [block copy];

View File

@@ -52,6 +52,7 @@ typedef enum {
kMode_WebDAV,
kMode_WebUploader,
kMode_StreamingResponse,
kMode_AsyncResponse
} Mode;
@interface Delegate : NSObject <GCDWebServerDelegate, GCDWebDAVServerDelegate, GCDWebUploaderDelegate>
@@ -142,7 +143,7 @@ int main(int argc, const char* argv[]) {
NSString* authenticationPassword = nil;
if (argc == 1) {
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | htmlFileUpload | webDAV | webUploader | streamingResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password]\n\n", basename((char*)argv[0]));
fprintf(stdout, "Usage: %s [-mode webServer | htmlPage | htmlForm | htmlFileUpload | webDAV | webUploader | streamingResponse | asyncResponse] [-record] [-root directory] [-tests directory] [-authenticationMethod Basic | Digest] [-authenticationRealm realm] [-authenticationUser user] [-authenticationPassword password]\n\n", basename((char*)argv[0]));
} else {
for (int i = 1; i < argc; ++i) {
if (argv[i][0] != '-') {
@@ -164,6 +165,8 @@ int main(int argc, const char* argv[]) {
mode = kMode_WebUploader;
} else if (!strcmp(argv[i], "streamingResponse")) {
mode = kMode_StreamingResponse;
} else if (!strcmp(argv[i], "asyncResponse")) {
mode = kMode_AsyncResponse;
}
} else if (!strcmp(argv[i], "-record")) {
recording = YES;
@@ -328,6 +331,24 @@ int main(int argc, const char* argv[]) {
break;
}
// Test async responses
case kMode_AsyncResponse: {
fprintf(stdout, "Running in Async Response mode");
webServer = [[GCDWebServer alloc] init];
[webServer addHandlerForMethod:@"GET"
path:@"/"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithData:[@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding] contentType:@"text/plain"];
completionBlock(response);
});
}];
break;
}
}
#if __has_feature(objc_arc)
fprintf(stdout, " (ARC is ON)\n");
@@ -338,7 +359,7 @@ int main(int argc, const char* argv[]) {
if (webServer) {
Delegate* delegate = [[Delegate alloc] init];
if (testDirectory) {
#ifndef NDEBUG
#if DEBUG
webServer.delegate = delegate;
#endif
fprintf(stdout, "<RUNNING TESTS FROM \"%s\">\n\n", [testDirectory UTF8String]);

View File

@@ -13,6 +13,7 @@ GCDWebServer is a modern and lightweight GCD based HTTP 1.1 server designed to b
* Available under a friendly [New BSD License](LICENSE)
Extra built-in features:
* Allow implementation of fully asynchronous handlers of incoming HTTP requests
* Minimize memory usage with disk streaming of large HTTP request or response bodies
* Parser for [web forms](http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4) submitted using "application/x-www-form-urlencoded" or "multipart/form-data" encodings (including file uploads)
* [JSON](http://www.json.org/) parsing and serialization for request and response HTTP bodies
@@ -41,15 +42,15 @@ Download or check out the [latest release](https://github.com/swisspol/GCDWebSer
Alternatively, you can install GCDWebServer using [CocoaPods](http://cocoapods.org/) by simply adding this line to your Xcode project's Podfile:
```
pod "GCDWebServer", "~> 2.0"
pod "GCDWebServer", "~> 3.0"
```
If you want to use GCDWebUploader, use this line instead:
```
pod "GCDWebServer/WebUploader", "~> 2.0"
pod "GCDWebServer/WebUploader", "~> 3.0"
```
Or this line for GCDWebDAVServer:
```
pod "GCDWebServer/WebDAV", "~> 2.0"
pod "GCDWebServer/WebDAV", "~> 3.0"
```
Hello World
@@ -146,6 +147,38 @@ println("Visit \(webServer.serverURL) in your web browser")
#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 that generate their ```GCDWebServerResponse``` asynchronously. This is achieved by adding handlers that use a ```GCDWebServerAsyncProcessBlock``` instead of a ```GCDWebServerProcessBlock```. Here's an example:
***Synchronous version***
```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***
```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);
});
}];
```
Web Based Uploads in iOS Apps
=============================
@@ -249,9 +282,9 @@ GCDWebServer relies on "handlers" to process incoming web requests and generatin
Handlers require 2 GCD blocks:
* The ```GCDWebServerMatchBlock``` is called on every handler added to the ```GCDWebServer``` instance whenever a web request has started (i.e. HTTP headers have been received). It is passed the basic info for the web request (HTTP method, URL, headers...) and must decide if it wants to handle it or not. If yes, it must return a new ```GCDWebServerRequest``` instance (see above) created with this info. Otherwise, it simply returns nil.
* The ```GCDWebServerProcessBlock``` is called after the web request has been fully received and is passed the ```GCDWebServerRequest``` instance created at the previous step. It must return a ```GCDWebServerResponse``` instance (see above) or nil on error, which will result in a 500 HTTP status code returned to the client. It's however recommended to return an instance of [GCDWebServerErrorResponse](GCDWebServer/Responses/GCDWebServerErrorResponse.h) on error so more useful information can be returned to the client.
* The ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` is called after the web request has been fully received and is passed the ```GCDWebServerRequest``` instance created at the previous step. It must return synchronously (if using ```GCDWebServerProcessBlock```) or asynchronously (if using ```GCDWebServerAsyncProcessBlock```) a ```GCDWebServerResponse``` instance (see above) or nil on error, which will result in a 500 HTTP status code returned to the client. It's however recommended to return an instance of [GCDWebServerErrorResponse](GCDWebServer/Responses/GCDWebServerErrorResponse.h) on error so more useful information can be returned to the client.
Note that most methods on ```GCDWebServer``` to add handlers only require the ```GCDWebServerProcessBlock``` 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.
GCDWebServer & Background Mode for iOS Apps
===========================================
@@ -270,20 +303,20 @@ HTTP connections are often initiated in batches (or bursts), for instance when l
Debug Builds & Custom Logging
=============================
When building GCDWebServer in "Debug" mode versus "Release" mode, GCDWebServer logs a lot more information and also performs a number of internal consistency checks. To disable this behavior, make sure to define the preprocessor constant ```NDEBUG``` when compiling GCDWebServer. In Xcode target settings, this can be done by adding ```NDEBUG``` to the build setting ```GCC_PREPROCESSOR_DEFINITIONS``` when building in Release configuration (this is done automatically for you if you use CocoaPods).
When building GCDWebServer in "Debug" mode versus "Release" mode, GCDWebServer logs a lot more information and also performs a number of internal consistency checks. To enable this behavior, define the preprocessor constant ```DEBUG=1``` when compiling GCDWebServer. In Xcode target settings, this can be done by adding ```DEBUG=1``` to the build setting ```GCC_PREPROCESSOR_DEFINITIONS``` when building in Debug configuration.
It's also possible to replace the logging system used by GCDWebServer by a custom one. Simply define the preprocessor constant ```__GCDWEBSERVER_LOGGING_HEADER__``` to the name of a header file (e.g. "MyLogging.h") that defines these macros:
```
#define LOG_DEBUG(...) // Should not do anything if NDEBUG is defined
#define LOG_DEBUG(...) // Should not do anything unless the preprocessor constant DEBUG is non-zero
#define LOG_VERBOSE(...)
#define LOG_INFO(...)
#define LOG_WARNING(...)
#define LOG_ERROR(...)
#define LOG_EXCEPTION(__EXCEPTION__)
#define DCHECK(__CONDITION__) // Should not do anything if NDEBUG is defined or abort if __CONDITION__ is false
#define DNOT_REACHED() // Should not do anything if NDEBUG is defined
#define DCHECK(__CONDITION__) // Should not do anything unless the preprocessor constant DEBUG is non-zero
#define DNOT_REACHED() // Should not do anything unless the preprocessor constant DEBUG is non-zero
```
Advanced Example 1: Implementing HTTP Redirects

View File

@@ -30,15 +30,12 @@
@interface AppDelegate () <GCDWebUploaderDelegate> {
@private
UIWindow* _window;
GCDWebUploader* _webServer;
}
@end
@implementation AppDelegate
@synthesize window=_window;
#if !__has_feature(objc_arc)
- (void)dealloc {
@@ -50,7 +47,8 @@
#endif
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
_window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
CGRect bounds = ([UIScreen instancesRespondToSelector:@selector(nativeBounds)] ? [[UIScreen mainScreen] nativeBounds] : [[UIScreen mainScreen] bounds]);
_window = [[UIWindow alloc] initWithFrame:bounds];
_window.backgroundColor = [UIColor whiteColor];
[_window makeKeyAndVisible];