mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-02-11 00:00:07 +08:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b87924776 | ||
|
|
5bda05c1f9 | ||
|
|
a8481af765 | ||
|
|
b5ad507a57 | ||
|
|
8c8e4847a5 | ||
|
|
514c09dc39 | ||
|
|
c4bf7b11e2 | ||
|
|
a933b2126e | ||
|
|
001df4ea39 | ||
|
|
75d018a375 | ||
|
|
4449e42601 | ||
|
|
c45053bc11 | ||
|
|
5070e4fc33 | ||
|
|
7c1e70a538 | ||
|
|
7102c7922e | ||
|
|
2de9418307 | ||
|
|
e59cf4b6df | ||
|
|
9e8f0e00f3 | ||
|
|
d7650a71e0 | ||
|
|
420ddc3eac | ||
|
|
143e38c968 | ||
|
|
8e9fe4c4c1 | ||
|
|
95bccff2f7 | ||
|
|
780a608d6c | ||
|
|
18d93bbf47 | ||
|
|
b35ebd7d58 | ||
|
|
a11b047233 | ||
|
|
4eac9d4f8e | ||
|
|
d1e2a1a12f | ||
|
|
54d5abd3a8 | ||
|
|
a9fee8d7e2 | ||
|
|
6b15bdaa4e | ||
|
|
3771cf4e92 |
@@ -7,7 +7,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'GCDWebServer'
|
||||
s.version = '2.5.2'
|
||||
s.version = '3.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|
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -509,7 +513,9 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = 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 +535,7 @@
|
||||
"-Wno-padded",
|
||||
"-Wno-documentation",
|
||||
"-Wno-documentation-unknown-command",
|
||||
"-Wno-objc-missing-property-synthesis",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
@@ -537,7 +544,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = NDEBUG;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
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 +555,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 +568,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;
|
||||
|
||||
@@ -30,21 +30,6 @@
|
||||
#import "GCDWebServerRequest.h"
|
||||
#import "GCDWebServerResponse.h"
|
||||
|
||||
/**
|
||||
* Log levels used by GCDWebServer.
|
||||
*
|
||||
* @warning kGCDWebServerLogLevel_Debug is only available if "NDEBUG" is not
|
||||
* defined when building.
|
||||
*/
|
||||
typedef NS_ENUM(int, GCDWebServerLogLevel) {
|
||||
kGCDWebServerLogLevel_Debug = 0,
|
||||
kGCDWebServerLogLevel_Verbose,
|
||||
kGCDWebServerLogLevel_Info,
|
||||
kGCDWebServerLogLevel_Warning,
|
||||
kGCDWebServerLogLevel_Error,
|
||||
kGCDWebServerLogLevel_Exception,
|
||||
};
|
||||
|
||||
/**
|
||||
* The GCDWebServerMatchBlock is called for every handler added to the
|
||||
* GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have
|
||||
@@ -69,6 +54,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 +287,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 +296,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 +400,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)
|
||||
@@ -437,42 +467,76 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* GCDWebServer provides its own built-in logging facility which is used by
|
||||
* default. It simply sends log messages to stderr assuming it is connected
|
||||
* to a terminal type device.
|
||||
*
|
||||
* GCDWebServer is also compatible with a limited set of third-party logging
|
||||
* facilities. If one of them is available at compile time, GCDWebServer will
|
||||
* automatically use it in place of the built-in one.
|
||||
*
|
||||
* Currently supported third-party logging facilities are:
|
||||
* - XLFacility (by the same author as GCDWebServer): https://github.com/swisspol/XLFacility
|
||||
* - CocoaLumberjack: https://github.com/CocoaLumberjack/CocoaLumberjack
|
||||
*
|
||||
* For both the built-in logging facility and CocoaLumberjack, the default
|
||||
* logging level is INFO (or DEBUG if the preprocessor constant "DEBUG"
|
||||
* evaluates to non-zero at compile time).
|
||||
*
|
||||
* It's possible to have GCDWebServer use a custom logging facility by defining
|
||||
* the "__GCDWEBSERVER_LOGGING_HEADER__" preprocessor constant in Xcode build
|
||||
* settings to the name of a custom header file (escaped like \"MyLogging.h\").
|
||||
* This header file must define the following set of macros:
|
||||
*
|
||||
* GWS_LOG_DEBUG(...)
|
||||
* GWS_LOG_VERBOSE(...)
|
||||
* GWS_LOG_INFO(...)
|
||||
* GWS_LOG_WARNING(...)
|
||||
* GWS_LOG_ERROR(...)
|
||||
* GWS_LOG_EXCEPTION(__EXCEPTION__)
|
||||
*
|
||||
* IMPORTANT: Except for GWS_LOG_EXCEPTION() which gets passed an NSException,
|
||||
* these macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG() macro
|
||||
* should not do anything unless the preprocessor constant "DEBUG" evaluates to
|
||||
* non-zero.
|
||||
*
|
||||
* The logging methods below send log messages to the same logging facility
|
||||
* used by GCDWebServer. They can be used for consistency wherever you interact
|
||||
* with GCDWebServer in your code (e.g. in the implementation of handlers).
|
||||
*/
|
||||
@interface GCDWebServer (Logging)
|
||||
|
||||
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
||||
|
||||
/**
|
||||
* Sets the current log level below which logged messages are discarded.
|
||||
* Sets the log level of the logging facility below which log 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.
|
||||
* @warning The interpretation of the "level" argument depends on the logging
|
||||
* facility used at compile time.
|
||||
*/
|
||||
+ (void)setLogLevel:(GCDWebServerLogLevel)level;
|
||||
|
||||
#endif
|
||||
+ (void)setLogLevel:(int)level;
|
||||
|
||||
/**
|
||||
* Logs a message with the kGCDWebServerLogLevel_Verbose level.
|
||||
* Logs a message to the logging facility at the VERBOSE level.
|
||||
*/
|
||||
- (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
|
||||
|
||||
/**
|
||||
* Logs a message with the kGCDWebServerLogLevel_Info level.
|
||||
* Logs a message to the logging facility at the INFO level.
|
||||
*/
|
||||
- (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
|
||||
|
||||
/**
|
||||
* Logs a message with the kGCDWebServerLogLevel_Warning level.
|
||||
* Logs a message to the logging facility at the WARNING level.
|
||||
*/
|
||||
- (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
|
||||
|
||||
/**
|
||||
* Logs a message with the kGCDWebServerLogLevel_Error level.
|
||||
* Logs a message to the logging facility at the ERROR level.
|
||||
*/
|
||||
- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);
|
||||
|
||||
/**
|
||||
* Logs an exception with the kGCDWebServerLogLevel_Exception level.
|
||||
* Logs an exception to the logging facility at the EXCEPTION level.
|
||||
*/
|
||||
- (void)logException:(NSException*)exception;
|
||||
|
||||
|
||||
@@ -61,11 +61,17 @@ NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"Automati
|
||||
NSString* const GCDWebServerAuthenticationMethod_Basic = @"Basic";
|
||||
NSString* const GCDWebServerAuthenticationMethod_DigestAccess = @"DigestAccess";
|
||||
|
||||
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
||||
#ifdef NDEBUG
|
||||
GCDWebServerLogLevel GCDLogLevel = kGCDWebServerLogLevel_Info;
|
||||
#if defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__)
|
||||
#if DEBUG
|
||||
GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Debug;
|
||||
#else
|
||||
GCDWebServerLogLevel GCDLogLevel = kGCDWebServerLogLevel_Debug;
|
||||
GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Info;
|
||||
#endif
|
||||
#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
|
||||
#if DEBUG
|
||||
int GCDWebServerLogLevel = LOG_LEVEL_DEBUG;
|
||||
#else
|
||||
int GCDWebServerLogLevel = LOG_LEVEL_INFO;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -73,16 +79,22 @@ GCDWebServerLogLevel GCDLogLevel = kGCDWebServerLogLevel_Debug;
|
||||
static BOOL _run;
|
||||
#endif
|
||||
|
||||
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
||||
#ifdef __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__
|
||||
|
||||
void GCDLogMessage(GCDWebServerLogLevel level, NSString* format, ...) {
|
||||
void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) {
|
||||
static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR", "EXCEPTION"};
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
|
||||
va_end(arguments);
|
||||
fprintf(stderr, "[%s] %s\n", levelNames[level], [message UTF8String]);
|
||||
ARC_RELEASE(message);
|
||||
static int enableLogging = -1;
|
||||
if (enableLogging < 0) {
|
||||
enableLogging = (isatty(STDERR_FILENO) ? 1 : 0);
|
||||
}
|
||||
if (enableLogging) {
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments];
|
||||
va_end(arguments);
|
||||
fprintf(stderr, "[%s] %s\n", levelNames[level], [message UTF8String]);
|
||||
ARC_RELEASE(message);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -114,25 +126,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);
|
||||
}
|
||||
@@ -143,11 +155,10 @@ static void _ExecuteMainThreadRunLoopSources() {
|
||||
@private
|
||||
id<GCDWebServerDelegate> __unsafe_unretained _delegate;
|
||||
dispatch_queue_t _syncQueue;
|
||||
dispatch_semaphore_t _sourceSemaphore;
|
||||
dispatch_group_t _sourceGroup;
|
||||
NSMutableArray* _handlers;
|
||||
NSInteger _activeConnections; // Accessed through _syncQueue only
|
||||
BOOL _connected; // Accessed on main thread only
|
||||
BOOL _disconnecting; // Accessed on main thread only
|
||||
CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only
|
||||
|
||||
NSDictionary* _options;
|
||||
@@ -159,7 +170,8 @@ static void _ExecuteMainThreadRunLoopSources() {
|
||||
BOOL _mapHEADToGET;
|
||||
CFTimeInterval _disconnectDelay;
|
||||
NSUInteger _port;
|
||||
dispatch_source_t _source;
|
||||
dispatch_source_t _source4;
|
||||
dispatch_source_t _source6;
|
||||
CFNetServiceRef _registrationService;
|
||||
CFNetServiceRef _resolutionService;
|
||||
#if TARGET_OS_IPHONE
|
||||
@@ -178,38 +190,15 @@ static void _ExecuteMainThreadRunLoopSources() {
|
||||
authenticationBasicAccounts=_authenticationBasicAccounts, authenticationDigestAccounts=_authenticationDigestAccounts,
|
||||
shouldAutomaticallyMapHEADToGET=_mapHEADToGET;
|
||||
|
||||
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
||||
|
||||
+ (void)load {
|
||||
const char* logLevel = getenv("logLevel");
|
||||
if (logLevel) {
|
||||
GCDLogLevel = atoi(logLevel);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+ (void)initialize {
|
||||
GCDWebServerInitializeFunctions();
|
||||
}
|
||||
|
||||
static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
||||
DCHECK([NSThread isMainThread]);
|
||||
GCDWebServer* server = (ARC_BRIDGE GCDWebServer*)info;
|
||||
@autoreleasepool {
|
||||
[server _didDisconnect];
|
||||
}
|
||||
server->_disconnecting = NO;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
if ((self = [super init])) {
|
||||
_syncQueue = dispatch_queue_create([NSStringFromClass([self class]) UTF8String], DISPATCH_QUEUE_SERIAL);
|
||||
_sourceSemaphore = dispatch_semaphore_create(0);
|
||||
_sourceGroup = dispatch_group_create();
|
||||
_handlers = [[NSMutableArray alloc] init];
|
||||
CFRunLoopTimerContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
|
||||
_disconnectTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, HUGE_VAL, HUGE_VAL, 0, 0, _DisconnectTimerCallBack, &context);
|
||||
CFRunLoopAddTimer(CFRunLoopGetMain(), _disconnectTimer, kCFRunLoopCommonModes);
|
||||
#if TARGET_OS_IPHONE
|
||||
_backgroundTask = UIBackgroundTaskInvalid;
|
||||
#endif
|
||||
@@ -218,14 +207,13 @@ static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
DCHECK(_connected == NO);
|
||||
DCHECK(_activeConnections == 0);
|
||||
DCHECK(_options == nil); // The server can never be dealloc'ed while running because of the retain-cycle with the dispatch source
|
||||
GWS_DCHECK(_connected == NO);
|
||||
GWS_DCHECK(_activeConnections == 0);
|
||||
GWS_DCHECK(_options == nil); // The server can never be dealloc'ed while running because of the retain-cycle with the dispatch source
|
||||
GWS_DCHECK(_disconnectTimer == NULL); // The server can never be dealloc'ed while the disconnect timer is pending because of the retain-cycle
|
||||
|
||||
CFRunLoopTimerInvalidate(_disconnectTimer);
|
||||
CFRelease(_disconnectTimer);
|
||||
ARC_RELEASE(_handlers);
|
||||
ARC_DISPATCH_RELEASE(_sourceSemaphore);
|
||||
ARC_DISPATCH_RELEASE(_sourceGroup);
|
||||
ARC_DISPATCH_RELEASE(_syncQueue);
|
||||
|
||||
ARC_DEALLOC(super);
|
||||
@@ -235,17 +223,17 @@ static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
||||
|
||||
// Always called on main thread
|
||||
- (void)_startBackgroundTask {
|
||||
DCHECK([NSThread isMainThread]);
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
if (_backgroundTask == UIBackgroundTaskInvalid) {
|
||||
LOG_DEBUG(@"Did start background task");
|
||||
GWS_LOG_DEBUG(@"Did start background task");
|
||||
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
|
||||
|
||||
LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]);
|
||||
GWS_LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]);
|
||||
[self _endBackgroundTask];
|
||||
|
||||
}];
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,10 +241,10 @@ static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
||||
|
||||
// Always called on main thread
|
||||
- (void)_didConnect {
|
||||
DCHECK([NSThread isMainThread]);
|
||||
DCHECK(_connected == NO);
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
GWS_DCHECK(_connected == NO);
|
||||
_connected = YES;
|
||||
LOG_DEBUG(@"Did connect");
|
||||
GWS_LOG_DEBUG(@"Did connect");
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
[self _startBackgroundTask];
|
||||
@@ -270,12 +258,13 @@ static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
||||
- (void)willStartConnection:(GCDWebServerConnection*)connection {
|
||||
dispatch_sync(_syncQueue, ^{
|
||||
|
||||
DCHECK(_activeConnections >= 0);
|
||||
GWS_DCHECK(_activeConnections >= 0);
|
||||
if (_activeConnections == 0) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (_disconnecting) {
|
||||
CFRunLoopTimerSetNextFireDate(_disconnectTimer, HUGE_VAL);
|
||||
_disconnecting = NO;
|
||||
if (_disconnectTimer) {
|
||||
CFRunLoopTimerInvalidate(_disconnectTimer);
|
||||
CFRelease(_disconnectTimer);
|
||||
_disconnectTimer = NULL;
|
||||
}
|
||||
if (_connected == NO) {
|
||||
[self _didConnect];
|
||||
@@ -291,16 +280,16 @@ static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
||||
|
||||
// Always called on main thread
|
||||
- (void)_endBackgroundTask {
|
||||
DCHECK([NSThread isMainThread]);
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
if (_backgroundTask != UIBackgroundTaskInvalid) {
|
||||
if (_suspendInBackground && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) && _source) {
|
||||
if (_suspendInBackground && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) && _source4) {
|
||||
[self _stop];
|
||||
}
|
||||
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
|
||||
_backgroundTask = UIBackgroundTaskInvalid;
|
||||
LOG_DEBUG(@"Did end background task");
|
||||
GWS_LOG_DEBUG(@"Did end background task");
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,10 +297,10 @@ static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
||||
|
||||
// Always called on main thread
|
||||
- (void)_didDisconnect {
|
||||
DCHECK([NSThread isMainThread]);
|
||||
DCHECK(_connected == YES);
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
GWS_DCHECK(_connected == YES);
|
||||
_connected = NO;
|
||||
LOG_DEBUG(@"Did disconnect");
|
||||
GWS_LOG_DEBUG(@"Did disconnect");
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
[self _endBackgroundTask];
|
||||
@@ -324,13 +313,22 @@ static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
||||
|
||||
- (void)didEndConnection:(GCDWebServerConnection*)connection {
|
||||
dispatch_sync(_syncQueue, ^{
|
||||
DCHECK(_activeConnections > 0);
|
||||
GWS_DCHECK(_activeConnections > 0);
|
||||
_activeConnections -= 1;
|
||||
if (_activeConnections == 0) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ((_disconnectDelay > 0.0) && (_source != NULL)) {
|
||||
CFRunLoopTimerSetNextFireDate(_disconnectTimer, CFAbsoluteTimeGetCurrent() + _disconnectDelay);
|
||||
_disconnecting = YES;
|
||||
if ((_disconnectDelay > 0.0) && (_source4 != NULL)) {
|
||||
if (_disconnectTimer) {
|
||||
CFRunLoopTimerInvalidate(_disconnectTimer);
|
||||
CFRelease(_disconnectTimer);
|
||||
}
|
||||
_disconnectTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + _disconnectDelay, 0.0, 0, 0, ^(CFRunLoopTimerRef timer) {
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
[self _didDisconnect];
|
||||
CFRelease(_disconnectTimer);
|
||||
_disconnectTimer = NULL;
|
||||
});
|
||||
CFRunLoopAddTimer(CFRunLoopGetMain(), _disconnectTimer, kCFRunLoopCommonModes);
|
||||
} else {
|
||||
[self _didDisconnect];
|
||||
}
|
||||
@@ -349,41 +347,47 @@ static void _DisconnectTimerCallBack(CFRunLoopTimerRef timer, void* info) {
|
||||
return type && CFStringGetLength(type) ? ARC_BRIDGE_RELEASE(CFStringCreateCopy(kCFAllocatorDefault, type)) : nil;
|
||||
}
|
||||
|
||||
- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)handlerBlock {
|
||||
DCHECK(_options == nil);
|
||||
GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock processBlock: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 {
|
||||
GWS_DCHECK(_options == nil);
|
||||
GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock asyncProcessBlock:processBlock];
|
||||
[_handlers insertObject:handler atIndex:0];
|
||||
ARC_RELEASE(handler);
|
||||
}
|
||||
|
||||
- (void)removeAllHandlers {
|
||||
DCHECK(_options == nil);
|
||||
GWS_DCHECK(_options == nil);
|
||||
[_handlers removeAllObjects];
|
||||
}
|
||||
|
||||
static void _NetServiceRegisterCallBack(CFNetServiceRef service, CFStreamError* error, void* info) {
|
||||
DCHECK([NSThread isMainThread]);
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
@autoreleasepool {
|
||||
if (error->error) {
|
||||
LOG_ERROR(@"Bonjour registration error %i (domain %i)", (int)error->error, (int)error->domain);
|
||||
GWS_LOG_ERROR(@"Bonjour registration error %i (domain %i)", (int)error->error, (int)error->domain);
|
||||
} else {
|
||||
GCDWebServer* server = (ARC_BRIDGE GCDWebServer*)info;
|
||||
LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]);
|
||||
GWS_LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]);
|
||||
CFNetServiceResolveWithTimeout(server->_resolutionService, 1.0, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* error, void* info) {
|
||||
DCHECK([NSThread isMainThread]);
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
@autoreleasepool {
|
||||
if (error->error) {
|
||||
if ((error->domain != kCFStreamErrorDomainNetServices) && (error->error != kCFNetServicesErrorTimeout)) {
|
||||
LOG_ERROR(@"Bonjour resolution error %i (domain %i)", (int)error->error, (int)error->domain);
|
||||
GWS_LOG_ERROR(@"Bonjour resolution error %i (domain %i)", (int)error->error, (int)error->domain);
|
||||
}
|
||||
} else {
|
||||
GCDWebServer* server = (ARC_BRIDGE GCDWebServer*)info;
|
||||
LOG_INFO(@"%@ now reachable at %@", [server class], server.bonjourServerURL);
|
||||
GWS_LOG_INFO(@"%@ now reachable at %@", [server class], server.bonjourServerURL);
|
||||
if ([server.delegate respondsToSelector:@selector(webServerDidCompleteBonjourRegistration:)]) {
|
||||
[server.delegate webServerDidCompleteBonjourRegistration:server];
|
||||
}
|
||||
@@ -406,160 +410,197 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
return ARC_AUTORELEASE([[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding]);
|
||||
}
|
||||
|
||||
- (BOOL)_start:(NSError**)error {
|
||||
DCHECK(_source == NULL);
|
||||
|
||||
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
|
||||
NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, @"");
|
||||
NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
|
||||
NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
|
||||
int listeningSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
- (int)_createListeningSocket:(BOOL)useIPv6
|
||||
localAddress:(const void*)address
|
||||
length:(socklen_t)length
|
||||
maxPendingConnections:(NSUInteger)maxPendingConnections
|
||||
error:(NSError**)error {
|
||||
int listeningSocket = socket(useIPv6 ? PF_INET6 : PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (listeningSocket > 0) {
|
||||
int yes = 1;
|
||||
setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||
|
||||
struct sockaddr_in addr4;
|
||||
bzero(&addr4, sizeof(addr4));
|
||||
addr4.sin_len = sizeof(addr4);
|
||||
addr4.sin_family = AF_INET;
|
||||
addr4.sin_port = htons(port);
|
||||
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (bind(listeningSocket, (void*)&addr4, sizeof(addr4)) == 0) {
|
||||
if (bind(listeningSocket, address, length) == 0) {
|
||||
if (listen(listeningSocket, (int)maxPendingConnections) == 0) {
|
||||
LOG_DEBUG(@"Did open listening socket %i", listeningSocket);
|
||||
_serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
|
||||
NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
|
||||
if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
|
||||
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||
_authenticationBasicAccounts = [[NSMutableDictionary alloc] init];
|
||||
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
|
||||
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
|
||||
[_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
|
||||
}];
|
||||
} else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
|
||||
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||
_authenticationDigestAccounts = [[NSMutableDictionary alloc] init];
|
||||
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
|
||||
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
|
||||
[_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username];
|
||||
}];
|
||||
}
|
||||
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
|
||||
_mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
||||
_disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
|
||||
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue);
|
||||
dispatch_source_set_cancel_handler(_source, ^{
|
||||
|
||||
@autoreleasepool {
|
||||
int result = close(listeningSocket);
|
||||
if (result != 0) {
|
||||
LOG_ERROR(@"Failed closing listening socket: %s (%i)", strerror(errno), errno);
|
||||
} else {
|
||||
LOG_DEBUG(@"Did close listening socket %i", listeningSocket);
|
||||
}
|
||||
}
|
||||
dispatch_semaphore_signal(_sourceSemaphore);
|
||||
|
||||
});
|
||||
dispatch_source_set_event_handler(_source, ^{
|
||||
|
||||
@autoreleasepool {
|
||||
struct sockaddr remoteSockAddr;
|
||||
socklen_t remoteAddrLen = sizeof(remoteSockAddr);
|
||||
int socket = accept(listeningSocket, &remoteSockAddr, &remoteAddrLen);
|
||||
if (socket > 0) {
|
||||
NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen];
|
||||
|
||||
struct sockaddr localSockAddr;
|
||||
socklen_t localAddrLen = sizeof(localSockAddr);
|
||||
NSData* localAddress = nil;
|
||||
if (getsockname(socket, &localSockAddr, &localAddrLen) == 0) {
|
||||
localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen];
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
}
|
||||
|
||||
int noSigPipe = 1;
|
||||
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE
|
||||
|
||||
GCDWebServerConnection* connection = [[_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened
|
||||
#if __has_feature(objc_arc)
|
||||
[connection self]; // Prevent compiler from complaining about unused variable / useless statement
|
||||
#else
|
||||
[connection release];
|
||||
#endif
|
||||
} else {
|
||||
LOG_ERROR(@"Failed accepting socket: %s (%i)", strerror(errno), errno);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (port == 0) {
|
||||
struct sockaddr addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
if (getsockname(listeningSocket, &addr, &addrlen) == 0) {
|
||||
struct sockaddr_in* sockaddr = (struct sockaddr_in*)&addr;
|
||||
_port = ntohs(sockaddr->sin_port);
|
||||
} else {
|
||||
LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno);
|
||||
}
|
||||
} else {
|
||||
_port = port;
|
||||
}
|
||||
|
||||
if (bonjourName) {
|
||||
_registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (ARC_BRIDGE CFStringRef)bonjourType, (ARC_BRIDGE CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port);
|
||||
if (_registrationService) {
|
||||
CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
|
||||
|
||||
CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context);
|
||||
CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
||||
CFStreamError streamError = {0};
|
||||
CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError);
|
||||
|
||||
_resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService);
|
||||
if (_resolutionService) {
|
||||
CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context);
|
||||
CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(@"Failed creating CFNetService");
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_resume(_source);
|
||||
LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
|
||||
if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_delegate webServerDidStart:self];
|
||||
});
|
||||
}
|
||||
GWS_LOG_DEBUG(@"Did open %s listening socket %i", useIPv6 ? "IPv6" : "IPv4", listeningSocket);
|
||||
return listeningSocket;
|
||||
} else {
|
||||
if (error) {
|
||||
*error = GCDWebServerMakePosixError(errno);
|
||||
}
|
||||
LOG_ERROR(@"Failed starting listening socket: %s (%i)", strerror(errno), errno);
|
||||
GWS_LOG_ERROR(@"Failed starting %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
|
||||
close(listeningSocket);
|
||||
}
|
||||
} else {
|
||||
if (error) {
|
||||
*error = GCDWebServerMakePosixError(errno);
|
||||
}
|
||||
LOG_ERROR(@"Failed binding listening socket: %s (%i)", strerror(errno), errno);
|
||||
GWS_LOG_ERROR(@"Failed binding %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
|
||||
close(listeningSocket);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (error) {
|
||||
*error = GCDWebServerMakePosixError(errno);
|
||||
}
|
||||
LOG_ERROR(@"Failed creating listening socket: %s (%i)", strerror(errno), errno);
|
||||
GWS_LOG_ERROR(@"Failed creating %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
|
||||
}
|
||||
return (_source ? YES : NO);
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 {
|
||||
dispatch_group_enter(_sourceGroup);
|
||||
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, kGCDWebServerGCDQueue);
|
||||
dispatch_source_set_cancel_handler(source, ^{
|
||||
|
||||
@autoreleasepool {
|
||||
int result = close(listeningSocket);
|
||||
if (result != 0) {
|
||||
GWS_LOG_ERROR(@"Failed closing %s listening socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
|
||||
} else {
|
||||
GWS_LOG_DEBUG(@"Did close %s listening socket %i", isIPv6 ? "IPv6" : "IPv4", listeningSocket);
|
||||
}
|
||||
}
|
||||
dispatch_group_leave(_sourceGroup);
|
||||
|
||||
});
|
||||
dispatch_source_set_event_handler(source, ^{
|
||||
|
||||
@autoreleasepool {
|
||||
struct sockaddr remoteSockAddr;
|
||||
socklen_t remoteAddrLen = sizeof(remoteSockAddr);
|
||||
int socket = accept(listeningSocket, &remoteSockAddr, &remoteAddrLen);
|
||||
if (socket > 0) {
|
||||
NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen];
|
||||
|
||||
struct sockaddr localSockAddr;
|
||||
socklen_t localAddrLen = sizeof(localSockAddr);
|
||||
NSData* localAddress = nil;
|
||||
if (getsockname(socket, &localSockAddr, &localAddrLen) == 0) {
|
||||
localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen];
|
||||
GWS_DCHECK((!isIPv6 && localSockAddr.sa_family == AF_INET) || (isIPv6 && localSockAddr.sa_family == AF_INET6));
|
||||
} else {
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
|
||||
int noSigPipe = 1;
|
||||
setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE
|
||||
|
||||
GCDWebServerConnection* connection = [[_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened
|
||||
#if __has_feature(objc_arc)
|
||||
[connection self]; // Prevent compiler from complaining about unused variable / useless statement
|
||||
#else
|
||||
[connection release];
|
||||
#endif
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
return source;
|
||||
}
|
||||
|
||||
- (BOOL)_start:(NSError**)error {
|
||||
GWS_DCHECK(_source4 == NULL);
|
||||
|
||||
NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
|
||||
NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
|
||||
|
||||
struct sockaddr_in addr4;
|
||||
bzero(&addr4, sizeof(addr4));
|
||||
addr4.sin_len = sizeof(addr4);
|
||||
addr4.sin_family = AF_INET;
|
||||
addr4.sin_port = htons(port);
|
||||
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
int listeningSocket4 = [self _createListeningSocket:NO localAddress:&addr4 length:sizeof(addr4) maxPendingConnections:maxPendingConnections error:error];
|
||||
if (listeningSocket4 <= 0) {
|
||||
return NO;
|
||||
}
|
||||
if (port == 0) {
|
||||
struct sockaddr addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
if (getsockname(listeningSocket4, &addr, &addrlen) == 0) {
|
||||
struct sockaddr_in* sockaddr = (struct sockaddr_in*)&addr;
|
||||
port = ntohs(sockaddr->sin_port);
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno);
|
||||
}
|
||||
}
|
||||
|
||||
struct sockaddr_in6 addr6;
|
||||
bzero(&addr6, sizeof(addr6));
|
||||
addr6.sin6_len = sizeof(addr6);
|
||||
addr6.sin6_family = AF_INET6;
|
||||
addr6.sin6_port = htons(port);
|
||||
addr6.sin6_addr = in6addr_any;
|
||||
int listeningSocket6 = [self _createListeningSocket:YES localAddress:&addr6 length:sizeof(addr6) maxPendingConnections:maxPendingConnections error:error];
|
||||
if (listeningSocket6 <= 0) {
|
||||
close(listeningSocket4);
|
||||
return NO;
|
||||
}
|
||||
|
||||
_serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
|
||||
NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
|
||||
if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
|
||||
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||
_authenticationBasicAccounts = [[NSMutableDictionary alloc] init];
|
||||
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
|
||||
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
|
||||
[_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
|
||||
}];
|
||||
} else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
|
||||
_authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
|
||||
_authenticationDigestAccounts = [[NSMutableDictionary alloc] init];
|
||||
NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
|
||||
[accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
|
||||
[_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username];
|
||||
}];
|
||||
}
|
||||
_connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
|
||||
_mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
|
||||
_disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
|
||||
|
||||
_source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
|
||||
_source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
|
||||
_port = port;
|
||||
|
||||
NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, @"");
|
||||
NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
|
||||
if (bonjourName) {
|
||||
_registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (ARC_BRIDGE CFStringRef)bonjourType, (ARC_BRIDGE CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port);
|
||||
if (_registrationService) {
|
||||
CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
|
||||
|
||||
CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context);
|
||||
CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
||||
CFStreamError streamError = {0};
|
||||
CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError);
|
||||
|
||||
_resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService);
|
||||
if (_resolutionService) {
|
||||
CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context);
|
||||
CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
||||
}
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Failed creating CFNetService");
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_resume(_source4);
|
||||
dispatch_resume(_source6);
|
||||
GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
|
||||
if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_delegate webServerDidStart:self];
|
||||
});
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)_stop {
|
||||
DCHECK(_source != NULL);
|
||||
GWS_DCHECK(_source4 != NULL);
|
||||
|
||||
if (_registrationService) {
|
||||
if (_resolutionService) {
|
||||
@@ -576,10 +617,13 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
_registrationService = NULL;
|
||||
}
|
||||
|
||||
dispatch_source_cancel(_source);
|
||||
dispatch_semaphore_wait(_sourceSemaphore, DISPATCH_TIME_FOREVER); // Wait until the cancellation handler has been called which guarantees the listening socket is closed
|
||||
ARC_DISPATCH_RELEASE(_source);
|
||||
_source = NULL;
|
||||
dispatch_source_cancel(_source6);
|
||||
dispatch_source_cancel(_source4);
|
||||
dispatch_group_wait(_sourceGroup, DISPATCH_TIME_FOREVER); // Wait until the cancellation handlers have been called which guarantees the listening sockets are closed
|
||||
ARC_DISPATCH_RELEASE(_source6);
|
||||
_source6 = NULL;
|
||||
ARC_DISPATCH_RELEASE(_source4);
|
||||
_source4 = NULL;
|
||||
_port = 0;
|
||||
|
||||
ARC_RELEASE(_serverName);
|
||||
@@ -592,14 +636,15 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
_authenticationDigestAccounts = nil;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (_disconnecting) {
|
||||
CFRunLoopTimerSetNextFireDate(_disconnectTimer, HUGE_VAL);
|
||||
_disconnecting = NO;
|
||||
if (_disconnectTimer) {
|
||||
CFRunLoopTimerInvalidate(_disconnectTimer);
|
||||
CFRelease(_disconnectTimer);
|
||||
_disconnectTimer = NULL;
|
||||
[self _didDisconnect];
|
||||
}
|
||||
});
|
||||
|
||||
LOG_INFO(@"%@ stopped", [self class]);
|
||||
GWS_LOG_INFO(@"%@ stopped", [self class]);
|
||||
if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_delegate webServerDidStop:self];
|
||||
@@ -610,17 +655,17 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
#if TARGET_OS_IPHONE
|
||||
|
||||
- (void)_didEnterBackground:(NSNotification*)notification {
|
||||
DCHECK([NSThread isMainThread]);
|
||||
LOG_DEBUG(@"Did enter background");
|
||||
if ((_backgroundTask == UIBackgroundTaskInvalid) && _source) {
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
GWS_LOG_DEBUG(@"Did enter background");
|
||||
if ((_backgroundTask == UIBackgroundTaskInvalid) && _source4) {
|
||||
[self _stop];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_willEnterForeground:(NSNotification*)notification {
|
||||
DCHECK([NSThread isMainThread]);
|
||||
LOG_DEBUG(@"Will enter foreground");
|
||||
if (!_source) {
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
GWS_LOG_DEBUG(@"Will enter foreground");
|
||||
if (!_source4) {
|
||||
[self _start:NULL]; // TODO: There's probably nothing we can do on failure
|
||||
}
|
||||
}
|
||||
@@ -649,7 +694,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
#endif
|
||||
return YES;
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
@@ -666,13 +711,13 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
|
||||
}
|
||||
#endif
|
||||
if (_source) {
|
||||
if (_source4) {
|
||||
[self _stop];
|
||||
}
|
||||
ARC_RELEASE(_options);
|
||||
_options = nil;
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,8 +726,8 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
@implementation GCDWebServer (Extensions)
|
||||
|
||||
- (NSURL*)serverURL {
|
||||
if (_source) {
|
||||
NSString* ipAddress = GCDWebServerGetPrimaryIPv4Address();
|
||||
if (_source4) {
|
||||
NSString* ipAddress = GCDWebServerGetPrimaryIPAddress(NO); // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice
|
||||
if (ipAddress) {
|
||||
if (_port != 80) {
|
||||
return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]];
|
||||
@@ -695,7 +740,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
}
|
||||
|
||||
- (NSURL*)bonjourServerURL {
|
||||
if (_source && _resolutionService) {
|
||||
if (_source4 && _resolutionService) {
|
||||
NSString* name = (ARC_BRIDGE NSString*)CFNetServiceGetTargetHost(_resolutionService);
|
||||
if (name.length) {
|
||||
name = [name substringToIndex:(name.length - 1)]; // Strip trailing period at end of domain
|
||||
@@ -730,7 +775,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
}
|
||||
|
||||
- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error {
|
||||
DCHECK([NSThread isMainThread]);
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
BOOL success = NO;
|
||||
_run = YES;
|
||||
void (*termHandler)(int) = signal(SIGTERM, _SignalHandler);
|
||||
@@ -757,6 +802,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]) {
|
||||
@@ -764,10 +815,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) {
|
||||
|
||||
@@ -779,13 +836,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();
|
||||
GWS_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) {
|
||||
@@ -793,14 +856,27 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
if (![requestMethod isEqualToString:method]) {
|
||||
return nil;
|
||||
}
|
||||
if ([expression firstMatchInString:urlPath options:0 range:NSMakeRange(0, urlPath.length)] == nil) {
|
||||
|
||||
NSArray* matches = [expression matchesInString:urlPath options:0 range:NSMakeRange(0, urlPath.length)];
|
||||
if (matches.count == 0) {
|
||||
return nil;
|
||||
}
|
||||
return ARC_AUTORELEASE([[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
|
||||
|
||||
NSMutableArray* captures = [NSMutableArray array];
|
||||
for (NSTextCheckingResult* result in matches) {
|
||||
// Start at 1; index 0 is the whole string
|
||||
for (NSUInteger i = 1; i < result.numberOfRanges; i++) {
|
||||
[captures addObject:[urlPath substringWithRange:[result rangeAtIndex:i]]];
|
||||
}
|
||||
}
|
||||
|
||||
GCDWebServerRequest* request = [[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery];
|
||||
[request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures];
|
||||
return ARC_AUTORELEASE(request);
|
||||
|
||||
} processBlock:block];
|
||||
} asyncProcessBlock:block];
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -847,7 +923,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
if (![file hasPrefix:@"."]) {
|
||||
NSString* type = [[enumerator fileAttributes] objectForKey:NSFileType];
|
||||
NSString* escapedFile = [file stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
DCHECK(escapedFile);
|
||||
GWS_DCHECK(escapedFile);
|
||||
if ([type isEqualToString:NSFileTypeRegular]) {
|
||||
[html appendFormat:@"<li><a href=\"%@\">%@</a></li>\n", escapedFile, file];
|
||||
} else if ([type isEqualToString:NSFileTypeDirectory]) {
|
||||
@@ -911,7 +987,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
|
||||
}];
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -919,44 +995,46 @@ static inline NSString* _EncodeBase64(NSString* string) {
|
||||
|
||||
@implementation GCDWebServer (Logging)
|
||||
|
||||
#ifndef __GCDWEBSERVER_LOGGING_HEADER__
|
||||
|
||||
+ (void)setLogLevel:(GCDWebServerLogLevel)level {
|
||||
GCDLogLevel = level;
|
||||
}
|
||||
|
||||
+ (void)setLogLevel:(int)level {
|
||||
#if defined(__GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__)
|
||||
[XLSharedFacility setMinLogLevel:level];
|
||||
#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
|
||||
GCDWebServerLogLevel = level;
|
||||
#else
|
||||
GCDWebServerLogLevel = level;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)logVerbose:(NSString*)format, ... {
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
LOG_VERBOSE(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
|
||||
GWS_LOG_VERBOSE(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
|
||||
va_end(arguments);
|
||||
}
|
||||
|
||||
- (void)logInfo:(NSString*)format, ... {
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
LOG_INFO(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
|
||||
GWS_LOG_INFO(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
|
||||
va_end(arguments);
|
||||
}
|
||||
|
||||
- (void)logWarning:(NSString*)format, ... {
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
LOG_WARNING(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
|
||||
GWS_LOG_WARNING(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
|
||||
va_end(arguments);
|
||||
}
|
||||
|
||||
- (void)logError:(NSString*)format, ... {
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
LOG_ERROR(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
|
||||
GWS_LOG_ERROR(@"%@", ARC_AUTORELEASE([[NSString alloc] initWithFormat:format arguments:arguments]));
|
||||
va_end(arguments);
|
||||
}
|
||||
|
||||
- (void)logException:(NSException*)exception {
|
||||
LOG_EXCEPTION(exception);
|
||||
GWS_LOG_EXCEPTION(exception);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1013,7 +1091,7 @@ static CFHTTPMessageRef _CreateHTTPMessageFromPerformingRequest(NSData* inData,
|
||||
outData.length = length;
|
||||
response = _CreateHTTPMessageFromData(outData, NO);
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
ARC_RELEASE(outData);
|
||||
}
|
||||
@@ -1033,7 +1111,7 @@ static void _LogResult(NSString* format, ...) {
|
||||
}
|
||||
|
||||
- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path {
|
||||
DCHECK([NSThread isMainThread]);
|
||||
GWS_DCHECK([NSThread isMainThread]);
|
||||
NSArray* ignoredHeaders = @[@"Date", @"Etag"]; // Dates are always different by definition and ETags depend on file system node IDs
|
||||
NSInteger result = -1;
|
||||
if ([self startWithOptions:options error:NULL]) {
|
||||
@@ -1104,7 +1182,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"]];
|
||||
@@ -1125,7 +1203,7 @@ static void _LogResult(NSString* format, ...) {
|
||||
CFRelease(expectedResponse);
|
||||
}
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1133,7 +1211,7 @@ static void _LogResult(NSString* format, ...) {
|
||||
CFRelease(request);
|
||||
}
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
_LogResult(@"");
|
||||
if (!success) {
|
||||
|
||||
@@ -48,6 +48,11 @@
|
||||
*/
|
||||
@property(nonatomic, readonly) GCDWebServer* server;
|
||||
|
||||
/**
|
||||
* Returns YES if the connection is using IPv6.
|
||||
*/
|
||||
@property(nonatomic, readonly, getter=isUsingIPv6) BOOL usingIPv6;
|
||||
|
||||
/**
|
||||
* Returns the address of the local peer (i.e. server) of the connection
|
||||
* as a raw "struct sockaddr".
|
||||
@@ -56,7 +61,7 @@
|
||||
|
||||
/**
|
||||
* Returns the address of the local peer (i.e. server) of the connection
|
||||
* as a dotted string.
|
||||
* as a string.
|
||||
*/
|
||||
@property(nonatomic, readonly) NSString* localAddressString;
|
||||
|
||||
@@ -68,7 +73,7 @@
|
||||
|
||||
/**
|
||||
* Returns the address of the remote peer (i.e. client) of the connection
|
||||
* as a dotted string.
|
||||
* as a string.
|
||||
*/
|
||||
@property(nonatomic, readonly) NSString* remoteAddressString;
|
||||
|
||||
@@ -138,13 +143,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
|
||||
|
||||
@@ -99,14 +99,14 @@ static int32_t _connectionCounter = 0;
|
||||
block(YES);
|
||||
} else {
|
||||
if (_bytesRead > 0) {
|
||||
LOG_ERROR(@"No more data available on socket %i", _socket);
|
||||
GWS_LOG_ERROR(@"No more data available on socket %i", _socket);
|
||||
} else {
|
||||
LOG_WARNING(@"No data received from socket %i", _socket);
|
||||
GWS_LOG_WARNING(@"No data received from socket %i", _socket);
|
||||
}
|
||||
block(NO);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error);
|
||||
GWS_LOG_ERROR(@"Error while reading from socket %i: %s (%i)", _socket, strerror(error), error);
|
||||
block(NO);
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ static int32_t _connectionCounter = 0;
|
||||
}
|
||||
|
||||
- (void)_readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block {
|
||||
DCHECK(_requestMessage);
|
||||
GWS_DCHECK(_requestMessage);
|
||||
[self _readData:headersData withLength:NSUIntegerMax completionBlock:^(BOOL success) {
|
||||
|
||||
if (success) {
|
||||
@@ -128,11 +128,11 @@ static int32_t _connectionCounter = 0;
|
||||
if (CFHTTPMessageIsHeaderComplete(_requestMessage)) {
|
||||
block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]);
|
||||
} else {
|
||||
LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
|
||||
GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", _socket);
|
||||
block(nil);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
|
||||
GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", _socket);
|
||||
block(nil);
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ static int32_t _connectionCounter = 0;
|
||||
}
|
||||
|
||||
- (void)_readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
|
||||
DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
|
||||
GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
|
||||
NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity];
|
||||
[self _readData:bodyData withLength:length completionBlock:^(BOOL success) {
|
||||
|
||||
@@ -159,13 +159,13 @@ static int32_t _connectionCounter = 0;
|
||||
block(YES);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||
block(NO);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket);
|
||||
GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket);
|
||||
block(NO);
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
} else {
|
||||
block(NO);
|
||||
@@ -185,7 +185,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
}
|
||||
|
||||
- (void)_readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block {
|
||||
DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]);
|
||||
GWS_DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]);
|
||||
|
||||
while (1) {
|
||||
NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)];
|
||||
@@ -205,12 +205,12 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) {
|
||||
[chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0];
|
||||
} else {
|
||||
LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||
block(NO);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket);
|
||||
GWS_LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket);
|
||||
block(NO);
|
||||
return;
|
||||
}
|
||||
@@ -222,7 +222,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket);
|
||||
GWS_LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket);
|
||||
block(NO);
|
||||
return;
|
||||
}
|
||||
@@ -258,11 +258,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
|
||||
@autoreleasepool {
|
||||
if (error == 0) {
|
||||
DCHECK(remainingData == NULL);
|
||||
GWS_DCHECK(remainingData == NULL);
|
||||
[self didWriteBytes:data.bytes length:data.length];
|
||||
block(YES);
|
||||
} else {
|
||||
LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
|
||||
GWS_LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
|
||||
block(NO);
|
||||
}
|
||||
}
|
||||
@@ -272,62 +272,64 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
}
|
||||
|
||||
- (void)_writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block {
|
||||
DCHECK(_responseMessage);
|
||||
GWS_DCHECK(_responseMessage);
|
||||
CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage);
|
||||
[self _writeData:(ARC_BRIDGE NSData*)data withCompletionBlock:block];
|
||||
CFRelease(data);
|
||||
}
|
||||
|
||||
- (void)_writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
|
||||
DCHECK([_response hasBody]);
|
||||
NSError* error = nil;
|
||||
NSData* data = [_response performReadData:&error];
|
||||
if (data) {
|
||||
if (data.length) {
|
||||
if (_response.usesChunkedTransferEncoding) {
|
||||
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
|
||||
size_t hexLength = strlen(hexString);
|
||||
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
|
||||
if (chunk == nil) {
|
||||
LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error);
|
||||
block(NO);
|
||||
return;
|
||||
GWS_DCHECK([_response hasBody]);
|
||||
[_response performReadDataWithCompletion:^(NSData* data, NSError* error) {
|
||||
|
||||
if (data) {
|
||||
if (data.length) {
|
||||
if (_response.usesChunkedTransferEncoding) {
|
||||
const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
|
||||
size_t hexLength = strlen(hexString);
|
||||
NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
|
||||
if (chunk == nil) {
|
||||
GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error);
|
||||
block(NO);
|
||||
return;
|
||||
}
|
||||
char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
|
||||
bcopy(hexString, ptr, hexLength);
|
||||
ptr += hexLength;
|
||||
*ptr++ = '\r';
|
||||
*ptr++ = '\n';
|
||||
bcopy(data.bytes, ptr, data.length);
|
||||
ptr += data.length;
|
||||
*ptr++ = '\r';
|
||||
*ptr = '\n';
|
||||
data = chunk;
|
||||
}
|
||||
char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
|
||||
bcopy(hexString, ptr, hexLength);
|
||||
ptr += hexLength;
|
||||
*ptr++ = '\r';
|
||||
*ptr++ = '\n';
|
||||
bcopy(data.bytes, ptr, data.length);
|
||||
ptr += data.length;
|
||||
*ptr++ = '\r';
|
||||
*ptr = '\n';
|
||||
data = chunk;
|
||||
}
|
||||
[self _writeData:data withCompletionBlock:^(BOOL success) {
|
||||
|
||||
if (success) {
|
||||
[self _writeBodyWithCompletionBlock:block];
|
||||
} else {
|
||||
block(NO);
|
||||
}
|
||||
|
||||
}];
|
||||
} else {
|
||||
if (_response.usesChunkedTransferEncoding) {
|
||||
[self _writeData:_lastChunkData withCompletionBlock:^(BOOL success) {
|
||||
[self _writeData:data withCompletionBlock:^(BOOL success) {
|
||||
|
||||
block(success);
|
||||
if (success) {
|
||||
[self _writeBodyWithCompletionBlock:block];
|
||||
} else {
|
||||
block(NO);
|
||||
}
|
||||
|
||||
}];
|
||||
} else {
|
||||
block(YES);
|
||||
if (_response.usesChunkedTransferEncoding) {
|
||||
[self _writeData:_lastChunkData withCompletionBlock:^(BOOL success) {
|
||||
|
||||
block(success);
|
||||
|
||||
}];
|
||||
} else {
|
||||
block(YES);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
|
||||
block(NO);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
|
||||
block(NO);
|
||||
}
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -339,11 +341,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
+ (void)initialize {
|
||||
if (_CRLFData == nil) {
|
||||
_CRLFData = [[NSData alloc] initWithBytes:"\r\n" length:2];
|
||||
DCHECK(_CRLFData);
|
||||
GWS_DCHECK(_CRLFData);
|
||||
}
|
||||
if (_CRLFCRLFData == nil) {
|
||||
_CRLFCRLFData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
|
||||
DCHECK(_CRLFCRLFData);
|
||||
GWS_DCHECK(_CRLFCRLFData);
|
||||
}
|
||||
if (_continueData == nil) {
|
||||
CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1);
|
||||
@@ -353,7 +355,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
_continueData = (NSData*)CFHTTPMessageCopySerializedMessage(message);
|
||||
#endif
|
||||
CFRelease(message);
|
||||
DCHECK(_continueData);
|
||||
GWS_DCHECK(_continueData);
|
||||
}
|
||||
if (_lastChunkData == nil) {
|
||||
_lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5];
|
||||
@@ -365,6 +367,11 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isUsingIPv6 {
|
||||
const struct sockaddr* localSockAddr = _localAddress.bytes;
|
||||
return (localSockAddr->sa_family == AF_INET6);
|
||||
}
|
||||
|
||||
- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode {
|
||||
_statusCode = statusCode;
|
||||
_responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1);
|
||||
@@ -373,15 +380,24 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (ARC_BRIDGE CFStringRef)GCDWebServerFormatRFC822([NSDate date]));
|
||||
}
|
||||
|
||||
- (void)_startProcessingRequest {
|
||||
GWS_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 {
|
||||
DCHECK(_responseMessage == NULL);
|
||||
- (void)_finishProcessingRequest:(GCDWebServerResponse*)response {
|
||||
GWS_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];
|
||||
}
|
||||
@@ -392,7 +408,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
}
|
||||
NSError* error = nil;
|
||||
if (hasBody && ![response performOpen:&error]) {
|
||||
LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error);
|
||||
GWS_LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error);
|
||||
} else {
|
||||
_response = ARC_RETAIN(response);
|
||||
}
|
||||
@@ -449,16 +465,16 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
- (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData {
|
||||
NSError* error = nil;
|
||||
if (![_request performOpen:&error]) {
|
||||
LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
|
||||
GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
return;
|
||||
}
|
||||
|
||||
if (initialData.length) {
|
||||
if (![_request performWriteData:initialData error:&error]) {
|
||||
LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||
GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
|
||||
if (![_request performClose:&error]) {
|
||||
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||
}
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
return;
|
||||
@@ -471,18 +487,18 @@ 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);
|
||||
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
}
|
||||
|
||||
}];
|
||||
} else {
|
||||
if ([_request performClose:&error]) {
|
||||
[self _processRequest];
|
||||
[self _startProcessingRequest];
|
||||
} else {
|
||||
LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
}
|
||||
}
|
||||
@@ -491,7 +507,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
- (void)_readChunkedBodyWithInitialData:(NSData*)initialData {
|
||||
NSError* error = nil;
|
||||
if (![_request performOpen:&error]) {
|
||||
LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
|
||||
GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
return;
|
||||
}
|
||||
@@ -501,9 +517,9 @@ 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);
|
||||
GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
}
|
||||
|
||||
@@ -526,7 +542,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
NSURL* requestURL = ARC_BRIDGE_RELEASE(CFHTTPMessageCopyRequestURL(_requestMessage));
|
||||
if (requestURL) {
|
||||
requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders];
|
||||
DCHECK(requestURL);
|
||||
GWS_DCHECK(requestURL);
|
||||
}
|
||||
NSString* requestPath = requestURL ? GCDWebServerUnescapeURLString(ARC_BRIDGE_RELEASE(CFURLCopyPath((CFURLRef)requestURL))) : nil; // Don't use -[NSURL path] which strips the ending slash
|
||||
NSString* queryString = requestURL ? ARC_BRIDGE_RELEASE(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped;
|
||||
@@ -557,7 +573,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
|
||||
}];
|
||||
} else {
|
||||
LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", _socket);
|
||||
GWS_LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", _socket);
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_ExpectationFailed];
|
||||
}
|
||||
} else {
|
||||
@@ -568,20 +584,20 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", _socket);
|
||||
GWS_LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", _socket);
|
||||
[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];
|
||||
DCHECK(_request);
|
||||
GWS_DCHECK(_request);
|
||||
[self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_MethodNotAllowed];
|
||||
}
|
||||
} else {
|
||||
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
} else {
|
||||
[self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
|
||||
@@ -597,7 +613,7 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
_localAddress = ARC_RETAIN(localAddress);
|
||||
_remoteAddress = ARC_RETAIN(remoteAddress);
|
||||
_socket = socket;
|
||||
LOG_DEBUG(@"Did open connection on socket %i", _socket);
|
||||
GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket);
|
||||
|
||||
[_server willStartConnection:self];
|
||||
|
||||
@@ -613,33 +629,20 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
|
||||
return self;
|
||||
}
|
||||
|
||||
static NSString* _StringFromAddressData(NSData* data) {
|
||||
NSString* string = nil;
|
||||
const struct sockaddr* addr = data.bytes;
|
||||
char hostBuffer[NI_MAXHOST];
|
||||
char serviceBuffer[NI_MAXSERV];
|
||||
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) {
|
||||
string = [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer];
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
- (NSString*)localAddressString {
|
||||
return _StringFromAddressData(_localAddress);
|
||||
return GCDWebServerStringFromSockAddr(_localAddress.bytes, YES);
|
||||
}
|
||||
|
||||
- (NSString*)remoteAddressString {
|
||||
return _StringFromAddressData(_remoteAddress);
|
||||
return GCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES);
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
int result = close(_socket);
|
||||
if (result != 0) {
|
||||
LOG_ERROR(@"Failed closing socket %i for connection: %s (%i)", _socket, strerror(errno), errno);
|
||||
GWS_LOG_ERROR(@"Failed closing socket %i for connection: %s (%i)", _socket, strerror(errno), errno);
|
||||
} else {
|
||||
LOG_DEBUG(@"Did close connection on socket %i", _socket);
|
||||
GWS_LOG_DEBUG(@"Did close connection on socket %i", _socket);
|
||||
}
|
||||
|
||||
if (_opened) {
|
||||
@@ -680,11 +683,11 @@ static NSString* _StringFromAddressData(NSData* data) {
|
||||
|
||||
_requestPath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
|
||||
_requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||
DCHECK(_requestFD > 0);
|
||||
GWS_DCHECK(_requestFD > 0);
|
||||
|
||||
_responsePath = ARC_RETAIN([NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]);
|
||||
_responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||
DCHECK(_responseFD > 0);
|
||||
GWS_DCHECK(_responseFD > 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -692,12 +695,12 @@ static NSString* _StringFromAddressData(NSData* data) {
|
||||
}
|
||||
|
||||
- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length {
|
||||
LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket);
|
||||
GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket);
|
||||
_bytesRead += length;
|
||||
|
||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||
if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) {
|
||||
LOG_ERROR(@"Failed recording request data: %s (%i)", strerror(errno), errno);
|
||||
GWS_LOG_ERROR(@"Failed recording request data: %s (%i)", strerror(errno), errno);
|
||||
close(_requestFD);
|
||||
_requestFD = 0;
|
||||
}
|
||||
@@ -705,12 +708,12 @@ static NSString* _StringFromAddressData(NSData* data) {
|
||||
}
|
||||
|
||||
- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length {
|
||||
LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket);
|
||||
GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket);
|
||||
_bytesWritten += length;
|
||||
|
||||
#ifdef __GCDWEBSERVER_ENABLE_TESTING__
|
||||
if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) {
|
||||
LOG_ERROR(@"Failed recording response data: %s (%i)", strerror(errno), errno);
|
||||
GWS_LOG_ERROR(@"Failed recording response data: %s (%i)", strerror(errno), errno);
|
||||
close(_responseFD);
|
||||
_responseFD = 0;
|
||||
}
|
||||
@@ -723,7 +726,7 @@ static NSString* _StringFromAddressData(NSData* data) {
|
||||
|
||||
// https://tools.ietf.org/html/rfc2617
|
||||
- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request {
|
||||
LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
|
||||
GWS_LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
|
||||
GCDWebServerResponse* response = nil;
|
||||
if (_server.authenticationBasicAccounts) {
|
||||
__block BOOL authenticated = NO;
|
||||
@@ -772,16 +775,14 @@ static NSString* _StringFromAddressData(NSData* data) {
|
||||
return response;
|
||||
}
|
||||
|
||||
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block {
|
||||
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;
|
||||
- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion {
|
||||
GWS_LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead);
|
||||
@try {
|
||||
response = block(request);
|
||||
_handler.asyncProcessBlock(request, completion);
|
||||
}
|
||||
@catch (NSException* exception) {
|
||||
LOG_EXCEPTION(exception);
|
||||
GWS_LOG_EXCEPTION(exception);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
|
||||
@@ -806,20 +807,20 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
|
||||
newResponse.cacheControlMaxAge = response.cacheControlMaxAge;
|
||||
newResponse.lastModifiedDate = response.lastModifiedDate;
|
||||
newResponse.eTag = response.eTag;
|
||||
DCHECK(newResponse);
|
||||
GWS_DCHECK(newResponse);
|
||||
return newResponse;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode {
|
||||
DCHECK(_responseMessage == NULL);
|
||||
DCHECK((statusCode >= 400) && (statusCode < 600));
|
||||
GWS_DCHECK(_responseMessage == NULL);
|
||||
GWS_DCHECK((statusCode >= 400) && (statusCode < 600));
|
||||
[self _initializeResponseHeadersWithStatusCode:statusCode];
|
||||
[self _writeHeadersWithCompletionBlock:^(BOOL success) {
|
||||
; // Nothing more to do
|
||||
}];
|
||||
LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket);
|
||||
GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket);
|
||||
}
|
||||
|
||||
- (void)close {
|
||||
@@ -833,8 +834,8 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
|
||||
success = [[NSFileManager defaultManager] moveItemAtPath:_requestPath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error];
|
||||
}
|
||||
if (!success) {
|
||||
LOG_ERROR(@"Failed saving recorded request: %@", error);
|
||||
DNOT_REACHED();
|
||||
GWS_LOG_ERROR(@"Failed saving recorded request: %@", error);
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
unlink([_requestPath fileSystemRepresentation]);
|
||||
}
|
||||
@@ -848,17 +849,17 @@ static inline BOOL _CompareResources(NSString* responseETag, NSString* requestET
|
||||
success = [[NSFileManager defaultManager] moveItemAtPath:_responsePath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error];
|
||||
}
|
||||
if (!success) {
|
||||
LOG_ERROR(@"Failed saving recorded response: %@", error);
|
||||
DNOT_REACHED();
|
||||
GWS_LOG_ERROR(@"Failed saving recorded response: %@", error);
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
unlink([_responsePath fileSystemRepresentation]);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_request) {
|
||||
LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead, (unsigned long)_bytesWritten);
|
||||
GWS_LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead, (unsigned long)_bytesWritten);
|
||||
} else {
|
||||
LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_bytesRead, (unsigned long)_bytesWritten);
|
||||
GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_bytesRead, (unsigned long)_bytesWritten);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,13 +57,13 @@ NSString* GCDWebServerUnescapeURLString(NSString* string);
|
||||
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
|
||||
|
||||
/**
|
||||
* On OS X, returns the IPv4 address as a dotted string of the primary connected
|
||||
* service or nil if not available.
|
||||
* On OS X, returns the IPv4 or IPv6 address as a string of the primary
|
||||
* connected service or nil if not available.
|
||||
*
|
||||
* On iOS, returns the IPv4 address as a dotted string of the WiFi interface
|
||||
* if connected or nil otherwise.
|
||||
* On iOS, returns the IPv4 or IPv6 address as a string of the WiFi
|
||||
* interface if connected or nil otherwise.
|
||||
*/
|
||||
NSString* GCDWebServerGetPrimaryIPv4Address();
|
||||
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6);
|
||||
|
||||
/**
|
||||
* Converts a date into a string using RFC822 formatting.
|
||||
|
||||
@@ -45,24 +45,24 @@ static dispatch_queue_t _dateFormatterQueue = NULL;
|
||||
|
||||
// TODO: Handle RFC 850 and ANSI C's asctime() format
|
||||
void GCDWebServerInitializeFunctions() {
|
||||
DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread
|
||||
GWS_DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread
|
||||
if (_dateFormatterRFC822 == nil) {
|
||||
_dateFormatterRFC822 = [[NSDateFormatter alloc] init];
|
||||
_dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
|
||||
_dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
|
||||
_dateFormatterRFC822.locale = ARC_AUTORELEASE([[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]);
|
||||
DCHECK(_dateFormatterRFC822);
|
||||
GWS_DCHECK(_dateFormatterRFC822);
|
||||
}
|
||||
if (_dateFormatterISO8601 == nil) {
|
||||
_dateFormatterISO8601 = [[NSDateFormatter alloc] init];
|
||||
_dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
|
||||
_dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'";
|
||||
_dateFormatterISO8601.locale = ARC_AUTORELEASE([[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]);
|
||||
DCHECK(_dateFormatterISO8601);
|
||||
GWS_DCHECK(_dateFormatterISO8601);
|
||||
}
|
||||
if (_dateFormatterQueue == NULL) {
|
||||
_dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
|
||||
DCHECK(_dateFormatterQueue);
|
||||
GWS_DCHECK(_dateFormatterQueue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,11 +204,14 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
|
||||
}
|
||||
|
||||
key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
||||
NSString* unescapedKey = key ? GCDWebServerUnescapeURLString(key) : nil;
|
||||
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
||||
if (key && value) {
|
||||
[parameters setObject:GCDWebServerUnescapeURLString(value) forKey:GCDWebServerUnescapeURLString(key)];
|
||||
NSString* unescapedValue = value ? GCDWebServerUnescapeURLString(value) : nil;
|
||||
if (unescapedKey && unescapedValue) {
|
||||
[parameters setObject:unescapedValue forKey:unescapedKey];
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value);
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
|
||||
if ([scanner isAtEnd]) {
|
||||
@@ -220,7 +223,19 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
NSString* GCDWebServerGetPrimaryIPv4Address() {
|
||||
NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) {
|
||||
NSString* string = nil;
|
||||
char hostBuffer[NI_MAXHOST];
|
||||
char serviceBuffer[NI_MAXSERV];
|
||||
if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) >= 0) {
|
||||
string = includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : [NSString stringWithUTF8String:hostBuffer];
|
||||
} else {
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
|
||||
NSString* address = nil;
|
||||
#if TARGET_OS_IPHONE
|
||||
#if !TARGET_IPHONE_SIMULATOR
|
||||
@@ -230,7 +245,7 @@ NSString* GCDWebServerGetPrimaryIPv4Address() {
|
||||
const char* primaryInterface = NULL;
|
||||
SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL);
|
||||
if (store) {
|
||||
CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4"));
|
||||
CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same
|
||||
if (info) {
|
||||
primaryInterface = [[NSString stringWithString:[(ARC_BRIDGE NSDictionary*)info objectForKey:@"PrimaryInterface"]] UTF8String];
|
||||
CFRelease(info);
|
||||
@@ -252,11 +267,8 @@ NSString* GCDWebServerGetPrimaryIPv4Address() {
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ((ifap->ifa_flags & IFF_UP) && (ifap->ifa_addr->sa_family == AF_INET)) {
|
||||
char buffer[NI_MAXHOST];
|
||||
if (getnameinfo(ifap->ifa_addr, ifap->ifa_addr->sa_len, buffer, sizeof(buffer), NULL, 0, NI_NUMERICHOST | NI_NOFQDN) >= 0) {
|
||||
address = [NSString stringWithUTF8String:buffer];
|
||||
}
|
||||
if ((ifap->ifa_flags & IFF_UP) && ((!useIPv6 && (ifap->ifa_addr->sa_family == AF_INET)) || (useIPv6 && (ifap->ifa_addr->sa_family == AF_INET6)))) {
|
||||
address = GCDWebServerStringFromSockAddr(ifap->ifa_addr, NO);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
*/
|
||||
|
||||
#import <os/object.h>
|
||||
#import <sys/socket.h>
|
||||
|
||||
/**
|
||||
* ARC <-> MRC compatibility macros.
|
||||
*/
|
||||
|
||||
#if __has_feature(objc_arc)
|
||||
#define ARC_BRIDGE __bridge
|
||||
@@ -52,6 +57,10 @@
|
||||
#define ARC_DISPATCH_RELEASE(__OBJECT__) dispatch_release(__OBJECT__)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* All GCDWebServer headers.
|
||||
*/
|
||||
|
||||
#import "GCDWebServerHTTPStatusCodes.h"
|
||||
#import "GCDWebServerFunctions.h"
|
||||
|
||||
@@ -68,43 +77,122 @@
|
||||
#import "GCDWebServerFileResponse.h"
|
||||
#import "GCDWebServerStreamedResponse.h"
|
||||
|
||||
#ifdef __GCDWEBSERVER_LOGGING_HEADER__
|
||||
/**
|
||||
* Automatically detect if XLFacility is available and if so use it as a
|
||||
* logging facility.
|
||||
*/
|
||||
|
||||
#if defined(__has_include) && __has_include("XLFacilityMacros.h")
|
||||
|
||||
#define __GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__
|
||||
|
||||
#import "XLFacilityMacros.h"
|
||||
|
||||
#define GWS_LOG_DEBUG(...) XLOG_DEBUG(__VA_ARGS__)
|
||||
#define GWS_LOG_VERBOSE(...) XLOG_VERBOSE(__VA_ARGS__)
|
||||
#define GWS_LOG_INFO(...) XLOG_INFO(__VA_ARGS__)
|
||||
#define GWS_LOG_WARNING(...) XLOG_WARNING(__VA_ARGS__)
|
||||
#define GWS_LOG_ERROR(...) XLOG_ERROR(__VA_ARGS__)
|
||||
#define GWS_LOG_EXCEPTION(__EXCEPTION__) XLOG_EXCEPTION(__EXCEPTION__)
|
||||
|
||||
#define GWS_DCHECK(__CONDITION__) XLOG_DEBUG_CHECK(__CONDITION__)
|
||||
#define GWS_DNOT_REACHED() XLOG_DEBUG_UNREACHABLE()
|
||||
|
||||
/**
|
||||
* Automatically detect if CocoaLumberJack is available and if so use
|
||||
* it as a logging facility.
|
||||
*/
|
||||
|
||||
#elif defined(__has_include) && __has_include("DDLogMacros.h")
|
||||
|
||||
#import "DDLogMacros.h"
|
||||
|
||||
#define __GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__
|
||||
|
||||
#undef LOG_LEVEL_DEF
|
||||
#define LOG_LEVEL_DEF GCDWebServerLogLevel
|
||||
extern int GCDWebServerLogLevel;
|
||||
|
||||
#define GWS_LOG_DEBUG(...) DDLogDebug(__VA_ARGS__)
|
||||
#define GWS_LOG_VERBOSE(...) DDLogVerbose(__VA_ARGS__)
|
||||
#define GWS_LOG_INFO(...) DDLogInfo(__VA_ARGS__)
|
||||
#define GWS_LOG_WARNING(...) DDLogWarn(__VA_ARGS__)
|
||||
#define GWS_LOG_ERROR(...) DDLogError(__VA_ARGS__)
|
||||
#define GWS_LOG_EXCEPTION(__EXCEPTION__) DDLogError(@"%@", __EXCEPTION__)
|
||||
|
||||
/**
|
||||
* Check if a custom logging facility should be used instead.
|
||||
*/
|
||||
|
||||
#elif defined(__GCDWEBSERVER_LOGGING_HEADER__)
|
||||
|
||||
#define __GCDWEBSERVER_LOGGING_FACILITY_CUSTOM__
|
||||
|
||||
// Define __GCDWEBSERVER_LOGGING_HEADER__ as a preprocessor constant to redirect GCDWebServer logging to your own system
|
||||
#import __GCDWEBSERVER_LOGGING_HEADER__
|
||||
|
||||
#else
|
||||
|
||||
extern GCDWebServerLogLevel GCDLogLevel;
|
||||
extern void GCDLogMessage(GCDWebServerLogLevel level, NSString* format, ...) NS_FORMAT_FUNCTION(2, 3);
|
||||
|
||||
#define LOG_VERBOSE(...) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Verbose) GCDLogMessage(kGCDWebServerLogLevel_Verbose, __VA_ARGS__); } while (0)
|
||||
#define LOG_INFO(...) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Info) GCDLogMessage(kGCDWebServerLogLevel_Info, __VA_ARGS__); } while (0)
|
||||
#define LOG_WARNING(...) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Warning) GCDLogMessage(kGCDWebServerLogLevel_Warning, __VA_ARGS__); } while (0)
|
||||
#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(...)
|
||||
/**
|
||||
* If all of the above fail, then use GCDWebServer built-in
|
||||
* logging facility.
|
||||
*/
|
||||
|
||||
#else
|
||||
|
||||
#define DCHECK(__CONDITION__) \
|
||||
#define __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__
|
||||
|
||||
typedef NS_ENUM(int, GCDWebServerLoggingLevel) {
|
||||
kGCDWebServerLoggingLevel_Debug = 0,
|
||||
kGCDWebServerLoggingLevel_Verbose,
|
||||
kGCDWebServerLoggingLevel_Info,
|
||||
kGCDWebServerLoggingLevel_Warning,
|
||||
kGCDWebServerLoggingLevel_Error,
|
||||
kGCDWebServerLoggingLevel_Exception,
|
||||
};
|
||||
|
||||
extern GCDWebServerLoggingLevel GCDWebServerLogLevel;
|
||||
extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) NS_FORMAT_FUNCTION(2, 3);
|
||||
|
||||
#if DEBUG
|
||||
#define GWS_LOG_DEBUG(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Debug) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Debug, __VA_ARGS__); } while (0)
|
||||
#else
|
||||
#define GWS_LOG_DEBUG(...)
|
||||
#endif
|
||||
#define GWS_LOG_VERBOSE(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Verbose) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); } while (0)
|
||||
#define GWS_LOG_INFO(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Info) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Info, __VA_ARGS__); } while (0)
|
||||
#define GWS_LOG_WARNING(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Warning) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Warning, __VA_ARGS__); } while (0)
|
||||
#define GWS_LOG_ERROR(...) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Error) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Error, __VA_ARGS__); } while (0)
|
||||
#define GWS_LOG_EXCEPTION(__EXCEPTION__) do { if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Exception) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Exception, @"%@", __EXCEPTION__); } while (0)
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Consistency check macros used when building Debug only.
|
||||
*/
|
||||
|
||||
#if !defined(GWS_DCHECK) || !defined(GWS_DNOT_REACHED)
|
||||
|
||||
#if DEBUG
|
||||
|
||||
#define GWS_DCHECK(__CONDITION__) \
|
||||
do { \
|
||||
if (!(__CONDITION__)) { \
|
||||
abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
#define DNOT_REACHED() abort()
|
||||
#define LOG_DEBUG(...) do { if (GCDLogLevel <= kGCDWebServerLogLevel_Debug) GCDLogMessage(kGCDWebServerLogLevel_Debug, __VA_ARGS__); } while (0)
|
||||
#define GWS_DNOT_REACHED() abort()
|
||||
|
||||
#else
|
||||
|
||||
#define GWS_DCHECK(__CONDITION__)
|
||||
#define GWS_DNOT_REACHED()
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* GCDWebServer internal constants and APIs.
|
||||
*/
|
||||
|
||||
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
|
||||
#define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||
#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain"
|
||||
@@ -125,6 +213,7 @@ extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset)
|
||||
extern BOOL GCDWebServerIsTextContentType(NSString* type);
|
||||
extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType);
|
||||
extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1,2);
|
||||
extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService);
|
||||
|
||||
@interface GCDWebServerConnection ()
|
||||
- (id)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket;
|
||||
@@ -143,8 +232,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 ()
|
||||
@@ -153,6 +241,7 @@ extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_F
|
||||
- (BOOL)performOpen:(NSError**)error;
|
||||
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error;
|
||||
- (BOOL)performClose:(NSError**)error;
|
||||
- (void)setAttribute:(id)attribute forKey:(NSString*)key;
|
||||
@end
|
||||
|
||||
@interface GCDWebServerResponse ()
|
||||
@@ -160,6 +249,6 @@ extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_F
|
||||
@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
|
||||
- (void)prepareForReading;
|
||||
- (BOOL)performOpen:(NSError**)error;
|
||||
- (NSData*)performReadData:(NSError**)error;
|
||||
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
|
||||
- (void)performClose;
|
||||
@end
|
||||
|
||||
@@ -27,6 +27,15 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Attribute key to retrieve an NSArray containing NSStrings from a GCDWebServerRequest
|
||||
* with the contents of any regular expression captures done on the request path.
|
||||
*
|
||||
* @warning This attribute will only be set on the request if adding a handler using
|
||||
* -addHandlerForMethod:pathRegex:requestClass:processBlock:.
|
||||
*/
|
||||
extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
|
||||
|
||||
/**
|
||||
* This protocol is used by the GCDWebServerConnection to communicate with
|
||||
* the GCDWebServerRequest and write the received HTTP body data.
|
||||
@@ -163,4 +172,11 @@
|
||||
*/
|
||||
- (BOOL)hasByteRange;
|
||||
|
||||
/**
|
||||
* Retrieves an attribute associated with this request using the given key.
|
||||
*
|
||||
* @return The attribute value for the key.
|
||||
*/
|
||||
- (id)attributeForKey:(NSString*)key;
|
||||
|
||||
@end
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
#import "GCDWebServerPrivate.h"
|
||||
|
||||
NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerRequestAttribute_RegexCaptures";
|
||||
|
||||
#define kZlibErrorDomain @"ZlibErrorDomain"
|
||||
#define kGZipInitialBufferSize (256 * 1024)
|
||||
|
||||
@@ -93,12 +95,12 @@
|
||||
}
|
||||
|
||||
- (BOOL)writeData:(NSData*)data error:(NSError**)error {
|
||||
DCHECK(!_finished);
|
||||
GWS_DCHECK(!_finished);
|
||||
_stream.next_in = (Bytef*)data.bytes;
|
||||
_stream.avail_in = (uInt)data.length;
|
||||
NSMutableData* decodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
|
||||
if (decodedData == nil) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
return NO;
|
||||
}
|
||||
NSUInteger length = 0;
|
||||
@@ -128,7 +130,7 @@
|
||||
}
|
||||
|
||||
- (BOOL)close:(NSError**)error {
|
||||
DCHECK(_finished);
|
||||
GWS_DCHECK(_finished);
|
||||
inflateEnd(&_stream);
|
||||
return [super close:error];
|
||||
}
|
||||
@@ -152,6 +154,7 @@
|
||||
|
||||
BOOL _opened;
|
||||
NSMutableArray* _decoders;
|
||||
NSMutableDictionary* _attributes;
|
||||
id<GCDWebServerBodyWriter> __unsafe_unretained _writer;
|
||||
}
|
||||
@end
|
||||
@@ -175,7 +178,7 @@
|
||||
if (lengthHeader) {
|
||||
NSInteger length = [lengthHeader integerValue];
|
||||
if (_chunked || (length < 0)) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
ARC_RELEASE(self);
|
||||
return nil;
|
||||
}
|
||||
@@ -190,7 +193,7 @@
|
||||
_length = NSUIntegerMax;
|
||||
} else {
|
||||
if (_type) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
ARC_RELEASE(self);
|
||||
return nil;
|
||||
}
|
||||
@@ -229,7 +232,7 @@
|
||||
}
|
||||
}
|
||||
if ((_range.location == NSUIntegerMax) && (_range.length == 0)) { // Ignore "Range" header if syntactically invalid
|
||||
LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
|
||||
GWS_LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +241,7 @@
|
||||
}
|
||||
|
||||
_decoders = [[NSMutableArray alloc] init];
|
||||
_attributes = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -252,6 +256,7 @@
|
||||
ARC_RELEASE(_modifiedSince);
|
||||
ARC_RELEASE(_noneMatch);
|
||||
ARC_RELEASE(_decoders);
|
||||
ARC_RELEASE(_attributes);
|
||||
|
||||
ARC_DEALLOC(super);
|
||||
}
|
||||
@@ -264,6 +269,10 @@
|
||||
return GCDWebServerIsValidByteRange(_range);
|
||||
}
|
||||
|
||||
- (id)attributeForKey:(NSString*)key {
|
||||
return [_attributes objectForKey:key];
|
||||
}
|
||||
|
||||
- (BOOL)open:(NSError**)error {
|
||||
return YES;
|
||||
}
|
||||
@@ -287,10 +296,10 @@
|
||||
}
|
||||
|
||||
- (BOOL)performOpen:(NSError**)error {
|
||||
DCHECK(_type);
|
||||
DCHECK(_writer);
|
||||
GWS_DCHECK(_type);
|
||||
GWS_DCHECK(_writer);
|
||||
if (_opened) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
return NO;
|
||||
}
|
||||
_opened = YES;
|
||||
@@ -298,15 +307,19 @@
|
||||
}
|
||||
|
||||
- (BOOL)performWriteData:(NSData*)data error:(NSError**)error {
|
||||
DCHECK(_opened);
|
||||
GWS_DCHECK(_opened);
|
||||
return [_writer writeData:data error:error];
|
||||
}
|
||||
|
||||
- (BOOL)performClose:(NSError**)error {
|
||||
DCHECK(_opened);
|
||||
GWS_DCHECK(_opened);
|
||||
return [_writer close:error];
|
||||
}
|
||||
|
||||
- (void)setAttribute:(id)attribute forKey:(NSString*)key {
|
||||
[_attributes setValue:attribute forKey:key];
|
||||
}
|
||||
|
||||
- (NSString*)description {
|
||||
NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path];
|
||||
for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
|
||||
|
||||
@@ -27,6 +27,12 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* The GCDWebServerBodyReaderCompletionBlock is passed by GCDWebServer to the
|
||||
* GCDWebServerBodyReader object when reading data from it asynchronously.
|
||||
*/
|
||||
typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* error);
|
||||
|
||||
/**
|
||||
* This protocol is used by the GCDWebServerConnection to communicate with
|
||||
* the GCDWebServerResponse and read the HTTP body data to send.
|
||||
@@ -39,6 +45,8 @@
|
||||
*/
|
||||
@protocol GCDWebServerBodyReader <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
* This method is called before any body data is sent.
|
||||
*
|
||||
@@ -61,6 +69,17 @@
|
||||
*/
|
||||
- (void)close;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* If this method is implemented, it will be preferred over -readData:.
|
||||
*
|
||||
* It must call the passed block when data is available, passing a non-empty
|
||||
* NSData if there is body data available, or an empty NSData there is no more
|
||||
* body data, or nil on error and pass an NSError along.
|
||||
*/
|
||||
- (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
} else {
|
||||
encodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize];
|
||||
if (encodedData == nil) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
return nil;
|
||||
}
|
||||
NSUInteger length = 0;
|
||||
@@ -136,7 +136,7 @@
|
||||
}
|
||||
encodedData.length = 2 * encodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available
|
||||
}
|
||||
DCHECK(_stream.avail_in == 0);
|
||||
GWS_DCHECK(_stream.avail_in == 0);
|
||||
} while (length == 0); // Make sure we don't return an empty NSData if not in finished state
|
||||
encodedData.length = length;
|
||||
}
|
||||
@@ -234,23 +234,28 @@
|
||||
}
|
||||
|
||||
- (BOOL)performOpen:(NSError**)error {
|
||||
DCHECK(_type);
|
||||
DCHECK(_reader);
|
||||
GWS_DCHECK(_type);
|
||||
GWS_DCHECK(_reader);
|
||||
if (_opened) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
return NO;
|
||||
}
|
||||
_opened = YES;
|
||||
return [_reader open:error];
|
||||
}
|
||||
|
||||
- (NSData*)performReadData:(NSError**)error {
|
||||
DCHECK(_opened);
|
||||
return [_reader readData:error];
|
||||
- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
|
||||
if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) {
|
||||
[_reader asyncReadDataWithCompletion:block];
|
||||
} else {
|
||||
NSError* error = nil;
|
||||
NSData* data = [_reader readData:&error];
|
||||
block(data, error);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)performClose {
|
||||
DCHECK(_opened);
|
||||
GWS_DCHECK(_opened);
|
||||
[_reader close];
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
||||
_text = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
}
|
||||
return _text;
|
||||
@@ -101,7 +101,7 @@
|
||||
if ([mimeType isEqualToString:@"application/json"] || [mimeType isEqualToString:@"text/json"] || [mimeType isEqualToString:@"text/javascript"]) {
|
||||
_jsonObject = ARC_RETAIN([NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL]);
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
}
|
||||
return _jsonObject;
|
||||
|
||||
@@ -171,22 +171,22 @@ static NSData* _dashNewlineData = nil;
|
||||
+ (void)initialize {
|
||||
if (_newlineData == nil) {
|
||||
_newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2];
|
||||
DCHECK(_newlineData);
|
||||
GWS_DCHECK(_newlineData);
|
||||
}
|
||||
if (_newlinesData == nil) {
|
||||
_newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
|
||||
DCHECK(_newlinesData);
|
||||
GWS_DCHECK(_newlinesData);
|
||||
}
|
||||
if (_dashNewlineData == nil) {
|
||||
_dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4];
|
||||
DCHECK(_dashNewlineData);
|
||||
GWS_DCHECK(_dashNewlineData);
|
||||
}
|
||||
}
|
||||
|
||||
- (id)initWithBoundary:(NSString*)boundary defaultControlName:(NSString*)name arguments:(NSMutableArray*)arguments files:(NSMutableArray*)files {
|
||||
NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil;
|
||||
if (data == nil) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
ARC_RELEASE(self);
|
||||
return nil;
|
||||
}
|
||||
@@ -259,7 +259,7 @@ static NSData* _dashNewlineData = nil;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
}
|
||||
if (_contentType == nil) {
|
||||
@@ -267,15 +267,15 @@ static NSData* _dashNewlineData = nil;
|
||||
}
|
||||
ARC_RELEASE(headers);
|
||||
} else {
|
||||
LOG_ERROR(@"Failed decoding headers in part of 'multipart/form-data'");
|
||||
DNOT_REACHED();
|
||||
GWS_LOG_ERROR(@"Failed decoding headers in part of 'multipart/form-data'");
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
if (_controlName) {
|
||||
if ([GCDWebServerTruncateHeaderValue(_contentType) isEqualToString:@"multipart/mixed"]) {
|
||||
NSString* boundary = GCDWebServerExtractHeaderValueParameter(_contentType, @"boundary");
|
||||
_subParser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:_controlName arguments:_arguments files:_files];
|
||||
if (_subParser == nil) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
success = NO;
|
||||
}
|
||||
} else if (_fileName) {
|
||||
@@ -284,12 +284,12 @@ static NSData* _dashNewlineData = nil;
|
||||
if (_tmpFile > 0) {
|
||||
_tmpPath = [path copy];
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
success = NO;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
success = NO;
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ static NSData* _dashNewlineData = nil;
|
||||
NSUInteger dataLength = range.location - 2;
|
||||
if (_subParser) {
|
||||
if (![_subParser appendBytes:dataBytes length:(dataLength + 2)] || ![_subParser isAtEnd]) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
success = NO;
|
||||
}
|
||||
ARC_RELEASE(_subParser);
|
||||
@@ -325,11 +325,11 @@ static NSData* _dashNewlineData = nil;
|
||||
[_files addObject:file];
|
||||
ARC_RELEASE(file);
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
success = NO;
|
||||
}
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
success = NO;
|
||||
}
|
||||
ARC_RELEASE(_tmpPath);
|
||||
@@ -359,7 +359,7 @@ static NSData* _dashNewlineData = nil;
|
||||
if ([_subParser appendBytes:_data.bytes length:length]) {
|
||||
[_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
success = NO;
|
||||
}
|
||||
} else if (_tmpPath) {
|
||||
@@ -367,7 +367,7 @@ static NSData* _dashNewlineData = nil;
|
||||
if (result == (ssize_t)length) {
|
||||
[_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
success = NO;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset");
|
||||
NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)];
|
||||
_arguments = ARC_RETAIN(GCDWebServerParseURLEncodedForm(string));
|
||||
DCHECK(_arguments);
|
||||
GWS_DCHECK(_arguments);
|
||||
ARC_RELEASE(string);
|
||||
|
||||
return YES;
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type {
|
||||
if (data == nil) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
ARC_RELEASE(self);
|
||||
return nil;
|
||||
}
|
||||
@@ -107,7 +107,7 @@
|
||||
- (instancetype)initWithText:(NSString*)text {
|
||||
NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding];
|
||||
if (data == nil) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
ARC_RELEASE(self);
|
||||
return nil;
|
||||
}
|
||||
@@ -117,7 +117,7 @@
|
||||
- (instancetype)initWithHTML:(NSString*)html {
|
||||
NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding];
|
||||
if (data == nil) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
ARC_RELEASE(self);
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
@implementation GCDWebServerErrorResponse
|
||||
|
||||
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
||||
DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
GCDWebServerErrorResponse* response = ARC_AUTORELEASE([[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]);
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
|
||||
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
||||
DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
GCDWebServerErrorResponse* response = ARC_AUTORELEASE([[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]);
|
||||
@@ -52,7 +52,7 @@
|
||||
}
|
||||
|
||||
+ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
||||
DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
GCDWebServerErrorResponse* response = ARC_AUTORELEASE([[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]);
|
||||
@@ -61,7 +61,7 @@
|
||||
}
|
||||
|
||||
+ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
||||
DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
GCDWebServerErrorResponse* response = ARC_AUTORELEASE([[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]);
|
||||
@@ -87,7 +87,7 @@ static inline NSString* _EscapeHTMLString(NSString* string) {
|
||||
}
|
||||
|
||||
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
||||
DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
||||
@@ -96,7 +96,7 @@ static inline NSString* _EscapeHTMLString(NSString* string) {
|
||||
}
|
||||
|
||||
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... {
|
||||
DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments];
|
||||
@@ -105,7 +105,7 @@ static inline NSString* _EscapeHTMLString(NSString* string) {
|
||||
}
|
||||
|
||||
- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
||||
DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||
GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500));
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
||||
@@ -114,7 +114,7 @@ static inline NSString* _EscapeHTMLString(NSString* string) {
|
||||
}
|
||||
|
||||
- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... {
|
||||
DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||
GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600));
|
||||
va_list arguments;
|
||||
va_start(arguments, format);
|
||||
self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments];
|
||||
|
||||
@@ -77,13 +77,13 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
|
||||
- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
|
||||
struct stat info;
|
||||
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
ARC_RELEASE(self);
|
||||
return nil;
|
||||
}
|
||||
#ifndef __LP64__
|
||||
if (info.st_size >= (off_t)4294967295) { // In 32 bit mode, we can't handle files greater than 4 GiBs (don't use "NSUIntegerMax" here to avoid potential unsigned to signed conversion issues)
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
ARC_RELEASE(self);
|
||||
return nil;
|
||||
}
|
||||
@@ -115,7 +115,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
|
||||
if (hasByteRange) {
|
||||
[self setStatusCode:kGCDWebServerHTTPStatusCode_PartialContent];
|
||||
[self setValue:[NSString stringWithFormat:@"bytes %lu-%lu/%lu", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), (unsigned long)fileSize] forAdditionalHeader:@"Content-Range"];
|
||||
LOG_DEBUG(@"Using content bytes range [%lu-%lu] for file \"%@\"", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), path);
|
||||
GWS_LOG_DEBUG(@"Using content bytes range [%lu-%lu] for file \"%@\"", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), path);
|
||||
}
|
||||
|
||||
if (attachment) {
|
||||
@@ -127,7 +127,7 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
|
||||
[self setValue:value forAdditionalHeader:@"Content-Disposition"];
|
||||
ARC_RELEASE(lossyFileName);
|
||||
} else {
|
||||
DNOT_REACHED();
|
||||
GWS_DNOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,29 @@
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#import "GCDWebServerStreamedResponse.h"
|
||||
#import "GCDWebServerResponse.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 GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock
|
||||
* except the streamed data can be returned at a later time allowing for
|
||||
* truly asynchronous generation of the data.
|
||||
*
|
||||
* The block must return empty NSData when done or nil on error and set the
|
||||
* "error" argument which is guaranteed to be non-NULL.
|
||||
*
|
||||
* The block must regularly call "completionBlock" passing new streamed data.
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* The GCDWebServerStreamedResponse subclass of GCDWebServerResponse streams
|
||||
@@ -43,11 +58,21 @@ 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;
|
||||
|
||||
/**
|
||||
* Creates a response with async streamed data and a given content type.
|
||||
*/
|
||||
+ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block;
|
||||
|
||||
/**
|
||||
* Initializes a response with streamed data and a given content type.
|
||||
*/
|
||||
- (instancetype)initWithContentType:(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 asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block;
|
||||
|
||||
@end
|
||||
|
||||
@@ -29,17 +29,31 @@
|
||||
|
||||
@interface GCDWebServerStreamedResponse () {
|
||||
@private
|
||||
GCDWebServerStreamingBlock _block;
|
||||
GCDWebServerAsyncStreamBlock _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)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block {
|
||||
return ARC_AUTORELEASE([[[self class] alloc] initWithContentType:type asyncStreamBlock:block]);
|
||||
}
|
||||
|
||||
- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block {
|
||||
return [self initWithContentType:type asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
|
||||
|
||||
NSError* error = nil;
|
||||
NSData* data = block(&error);
|
||||
completionBlock(data, error);
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block {
|
||||
if ((self = [super init])) {
|
||||
_block = [block copy];
|
||||
|
||||
@@ -54,8 +68,8 @@
|
||||
ARC_DEALLOC(super);
|
||||
}
|
||||
|
||||
- (NSData*)readData:(NSError**)error {
|
||||
return _block(error);
|
||||
- (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
|
||||
_block(block);
|
||||
}
|
||||
|
||||
- (NSString*)description {
|
||||
|
||||
45
Mac/main.m
45
Mac/main.m
@@ -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;
|
||||
@@ -308,7 +311,7 @@ int main(int argc, const char* argv[]) {
|
||||
fprintf(stdout, "Running in Streaming Response mode");
|
||||
webServer = [[GCDWebServer alloc] init];
|
||||
[webServer addHandlerForMethod:@"GET"
|
||||
path:@"/"
|
||||
path:@"/sync"
|
||||
requestClass:[GCDWebServerRequest class]
|
||||
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
||||
|
||||
@@ -324,6 +327,42 @@ int main(int argc, const char* argv[]) {
|
||||
|
||||
}];
|
||||
|
||||
}];
|
||||
[webServer addHandlerForMethod:@"GET"
|
||||
path:@"/async"
|
||||
requestClass:[GCDWebServerRequest class]
|
||||
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
||||
|
||||
__block int countDown = 10;
|
||||
return [GCDWebServerStreamedResponse responseWithContentType:@"text/plain" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
|
||||
|
||||
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];
|
||||
completionBlock(data, nil);
|
||||
|
||||
});
|
||||
|
||||
}];
|
||||
|
||||
}];
|
||||
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;
|
||||
}
|
||||
@@ -338,7 +377,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]);
|
||||
|
||||
96
README.md
96
README.md
@@ -3,7 +3,10 @@ Overview
|
||||
|
||||
[](https://travis-ci.org/swisspol/GCDWebServer)
|
||||
[](http://cocoadocs.org/docsets/GCDWebServer)
|
||||
[](http://cocoadocs.org/docsets/GCDWebServer)
|
||||
[](https://github.com/swisspol/GCDWebServer)
|
||||
[](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:
|
||||
* Elegant and easy to use architecture with only 4 core classes: server, connection, request and response (see "Understanding GCDWebServer's Architecture" below)
|
||||
@@ -13,6 +16,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
|
||||
@@ -21,6 +25,7 @@ Extra built-in features:
|
||||
* [HTTP range](https://en.wikipedia.org/wiki/Byte_serving) support for requests of local files
|
||||
* [Basic](https://en.wikipedia.org/wiki/Basic_access_authentication) and [Digest Access](https://en.wikipedia.org/wiki/Digest_access_authentication) authentications for password protection
|
||||
* Automatically handle transitions between foreground, background and suspended modes in iOS apps
|
||||
* Full support for both IPv4 and IPv6
|
||||
|
||||
Included extensions:
|
||||
* [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser
|
||||
@@ -41,15 +46,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 +151,65 @@ 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 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]
|
||||
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
|
||||
|
||||
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
|
||||
=============================
|
||||
|
||||
@@ -249,9 +313,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
|
||||
===========================================
|
||||
@@ -267,24 +331,16 @@ Fortunately, GCDWebServer does all of this automatically for you:
|
||||
|
||||
HTTP connections are often initiated in batches (or bursts), for instance when loading a web page with multiple resources. This makes it difficult to accurately detect when the *very last* HTTP connection has been closed: it's possible 2 consecutive HTTP connections part of the same batch would be separated by a small delay instead of overlapping. It would be bad for the client if GCDWebServer suspended itself right in between. The ```GCDWebServerOption_ConnectedStateCoalescingInterval``` option solves this problem elegantly by forcing GCDWebServer to wait some extra delay before performing any action after the last HTTP connection has been closed, just in case a new HTTP connection is initiated within this delay.
|
||||
|
||||
Debug Builds & Custom Logging
|
||||
=============================
|
||||
Logging in GCDWebServer
|
||||
=======================
|
||||
|
||||
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).
|
||||
Both for debugging and informational purpose, GCDWebServer logs messages extensively whenever something happens. Furthermore, when building GCDWebServer in "Debug" mode versus "Release" mode, it logs even more information but 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. Finally, you can also control the logging verbosity at run time by calling ```+[GCDWebServer setLogLevel:]```.
|
||||
|
||||
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:
|
||||
By default, all messages logged by GCDWebServer are sent to its built-in logging facility, which simply outputs to ```stderr``` (assuming a terminal type device is connected). In order to better integrate with the rest of your app or because of the amount of information logged, you might want to use another logging facility.
|
||||
|
||||
```
|
||||
#define LOG_DEBUG(...) // Should not do anything if NDEBUG is defined
|
||||
#define LOG_VERBOSE(...)
|
||||
#define LOG_INFO(...)
|
||||
#define LOG_WARNING(...)
|
||||
#define LOG_ERROR(...)
|
||||
#define LOG_EXCEPTION(__EXCEPTION__)
|
||||
GCDWebServer has automatic support for [XLFacility](https://github.com/swisspol/XLFacility) (by the same author as GCDWebServer and also open-source) and [CocoaLumberjack](https://github.com/CocoaLumberjack/CocoaLumberjack). If either of them is in the same Xcode project, GCDWebServer should use it automatically instead of the built-in logging facility (see [GCDWebServerPrivate.h](GCDWebServer/Core/GCDWebServerPrivate.h) for the implementation details).
|
||||
|
||||
#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
|
||||
```
|
||||
It's also possible to use a custom logging facility - see [GCDWebServer.h](GCDWebServer/Core/GCDWebServer.h) for more information.
|
||||
|
||||
Advanced Example 1: Implementing HTTP Redirects
|
||||
===============================================
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user