You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by sh...@apache.org on 2016/07/25 07:57:31 UTC

[45/57] [abbrv] cordova-plugins git commit: Squashed 'local-webserver/src/ios/GCDWebServer/' changes from 15caa9c..55104e5

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer.xcodeproj/xcshareddata/xcschemes/GCDWebServers (Mac).xcscheme
----------------------------------------------------------------------
diff --git a/GCDWebServer.xcodeproj/xcshareddata/xcschemes/GCDWebServers (Mac).xcscheme b/GCDWebServer.xcodeproj/xcshareddata/xcschemes/GCDWebServers (Mac).xcscheme
new file mode 100644
index 0000000..b905059
--- /dev/null
+++ b/GCDWebServer.xcodeproj/xcshareddata/xcschemes/GCDWebServers (Mac).xcscheme	
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0720"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "NO"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "CEE28CD01AE004D800F4023C"
+               BuildableName = "GCDWebServers.framework"
+               BlueprintName = "GCDWebServers (Mac)"
+               ReferencedContainer = "container:GCDWebServer.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "E24039241BA09207000B7089"
+               BuildableName = "Tests.xctest"
+               BlueprintName = "Tests (Mac)"
+               ReferencedContainer = "container:GCDWebServer.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "CEE28CD01AE004D800F4023C"
+            BuildableName = "GCDWebServers.framework"
+            BlueprintName = "GCDWebServers (Mac)"
+            ReferencedContainer = "container:GCDWebServer.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "CEE28CD01AE004D800F4023C"
+            BuildableName = "GCDWebServers.framework"
+            BlueprintName = "GCDWebServers (Mac)"
+            ReferencedContainer = "container:GCDWebServer.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer.xcodeproj/xcshareddata/xcschemes/GCDWebServers (iOS).xcscheme
----------------------------------------------------------------------
diff --git a/GCDWebServer.xcodeproj/xcshareddata/xcschemes/GCDWebServers (iOS).xcscheme b/GCDWebServer.xcodeproj/xcshareddata/xcschemes/GCDWebServers (iOS).xcscheme
new file mode 100644
index 0000000..73e3e0a
--- /dev/null
+++ b/GCDWebServer.xcodeproj/xcshareddata/xcschemes/GCDWebServers (iOS).xcscheme	
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0720"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "NO"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "CEE28CEE1AE0051F00F4023C"
+               BuildableName = "GCDWebServers.framework"
+               BlueprintName = "GCDWebServers (iOS)"
+               ReferencedContainer = "container:GCDWebServer.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "CEE28CEE1AE0051F00F4023C"
+            BuildableName = "GCDWebServers.framework"
+            BlueprintName = "GCDWebServers (iOS)"
+            ReferencedContainer = "container:GCDWebServer.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "CEE28CEE1AE0051F00F4023C"
+            BuildableName = "GCDWebServers.framework"
+            BlueprintName = "GCDWebServers (iOS)"
+            ReferencedContainer = "container:GCDWebServer.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "CEE28CEE1AE0051F00F4023C"
+            BuildableName = "GCDWebServers.framework"
+            BlueprintName = "GCDWebServers (iOS)"
+            ReferencedContainer = "container:GCDWebServer.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer.xcodeproj/xcshareddata/xcschemes/GCDWebServers (tvOS).xcscheme
----------------------------------------------------------------------
diff --git a/GCDWebServer.xcodeproj/xcshareddata/xcschemes/GCDWebServers (tvOS).xcscheme b/GCDWebServer.xcodeproj/xcshareddata/xcschemes/GCDWebServers (tvOS).xcscheme
new file mode 100644
index 0000000..920fa99
--- /dev/null
+++ b/GCDWebServer.xcodeproj/xcshareddata/xcschemes/GCDWebServers (tvOS).xcscheme	
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0720"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "NO"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "E2DDD18A1BE69404002CE867"
+               BuildableName = "GCDWebServers.framework"
+               BlueprintName = "GCDWebServers (tvOS)"
+               ReferencedContainer = "container:GCDWebServer.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "E2DDD18A1BE69404002CE867"
+            BuildableName = "GCDWebServers.framework"
+            BlueprintName = "GCDWebServers (tvOS)"
+            ReferencedContainer = "container:GCDWebServer.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "E2DDD18A1BE69404002CE867"
+            BuildableName = "GCDWebServers.framework"
+            BlueprintName = "GCDWebServers (tvOS)"
+            ReferencedContainer = "container:GCDWebServer.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "E2DDD18A1BE69404002CE867"
+            BuildableName = "GCDWebServers.framework"
+            BlueprintName = "GCDWebServers (tvOS)"
+            ReferencedContainer = "container:GCDWebServer.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Core/GCDWebServer.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServer.h b/GCDWebServer/Core/GCDWebServer.h
index ff414c9..b4afb4c 100644
--- a/GCDWebServer/Core/GCDWebServer.h
+++ b/GCDWebServer/Core/GCDWebServer.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -52,7 +52,7 @@ typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod,
  *  recommended to return a GCDWebServerErrorResponse on error so more useful
  *  information can be returned to the client.
  */
-typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
+typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(__kindof GCDWebServerRequest* request);
 
 /**
  *  The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock
@@ -65,7 +65,7 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r
  *  useful information can be returned to the client.
  */
 typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* response);
-typedef void (^GCDWebServerAsyncProcessBlock)(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
+typedef void (^GCDWebServerAsyncProcessBlock)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock);
 
 /**
  *  The port used by the GCDWebServer (NSNumber / NSUInteger).
@@ -79,7 +79,7 @@ extern NSString* const GCDWebServerOption_Port;
  *  the name will automatically take the value of the GCDWebServerOption_ServerName
  *  option. If this option is set to nil, Bonjour will be disabled.
  *
- *  The default value is an empty string.
+ *  The default value is nil.
  */
 extern NSString* const GCDWebServerOption_BonjourName;
 
@@ -91,6 +91,29 @@ extern NSString* const GCDWebServerOption_BonjourName;
 extern NSString* const GCDWebServerOption_BonjourType;
 
 /**
+ *  Request a port mapping in the NAT gateway (NSNumber / BOOL).
+ *
+ *  This uses the DNSService API under the hood which supports IPv4 mappings only.
+ *
+ *  The default value is NO.
+ *
+ *  @warning The external port set up by the NAT gateway may be different than
+ *  the one used by the GCDWebServer.
+ */
+extern NSString* const GCDWebServerOption_RequestNATPortMapping;
+
+/**
+ *  Only accept HTTP requests coming from localhost i.e. not from the outside
+ *  network (NSNumber / BOOL).
+ *
+ *  The default value is NO.
+ *
+ *  @warning Bonjour and NAT port mapping should be disabled if using this option
+ *  since the server will not be reachable from the outside network anyway.
+ */
+extern NSString* const GCDWebServerOption_BindToLocalhost;
+
+/**
  *  The maximum number of incoming HTTP requests that can be queued waiting to
  *  be handled before new ones are dropped (NSNumber / NSUInteger).
  *
@@ -202,10 +225,22 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
 /**
  *  This method is called after the Bonjour registration for the server has
  *  successfully completed.
+ *
+ *  Use the "bonjourServerURL" property to retrieve the Bonjour address of the
+ *  server.
  */
 - (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server;
 
 /**
+ *  This method is called after the NAT port mapping for the server has been
+ *  updated.
+ *
+ *  Use the "publicServerURL" property to retrieve the public address of the
+ *  server.
+ */
+- (void)webServerDidUpdateNATPortMapping:(GCDWebServer*)server;
+
+/**
  *  This method is called when the first GCDWebServerConnection is opened by the
  *  server to serve a series of HTTP requests.
  *
@@ -352,6 +387,14 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
 @property(nonatomic, readonly) NSURL* bonjourServerURL;
 
 /**
+ *  Returns the server's public URL.
+ *
+ *  @warning This property is only valid if the server is running and NAT port
+ *  mapping is active.
+ */
+@property(nonatomic, readonly) NSURL* publicServerURL;
+
+/**
  *  Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS)
  *  using the default Bonjour name.
  *
@@ -512,6 +555,14 @@ extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess;
  *
  *  @warning The interpretation of the "level" argument depends on the logging
  *  facility used at compile time.
+ *
+ *  If using the built-in logging facility, the log levels are as follow:
+ *  DEBUG = 0
+ *  VERBOSE = 1
+ *  INFO = 2
+ *  WARNING = 3
+ *  ERROR = 4
+ *  EXCEPTION = 5
  */
 + (void)setLogLevel:(int)level;
 

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Core/GCDWebServer.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServer.m b/GCDWebServer/Core/GCDWebServer.m
index 852a9db..815fab3 100644
--- a/GCDWebServer/Core/GCDWebServer.m
+++ b/GCDWebServer/Core/GCDWebServer.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -38,6 +38,7 @@
 #endif
 #endif
 #import <netinet/in.h>
+#import <dns_sd.h>
 
 #import "GCDWebServerPrivate.h"
 
@@ -47,9 +48,13 @@
 #define kDefaultPort 8080
 #endif
 
+#define kBonjourResolutionTimeout 5.0
+
 NSString* const GCDWebServerOption_Port = @"Port";
 NSString* const GCDWebServerOption_BonjourName = @"BonjourName";
 NSString* const GCDWebServerOption_BonjourType = @"BonjourType";
+NSString* const GCDWebServerOption_RequestNATPortMapping = @"RequestNATPortMapping";
+NSString* const GCDWebServerOption_BindToLocalhost = @"BindToLocalhost";
 NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections";
 NSString* const GCDWebServerOption_ServerName = @"ServerName";
 NSString* const GCDWebServerOption_AuthenticationMethod = @"AuthenticationMethod";
@@ -73,9 +78,9 @@ GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Info;
 #endif
 #elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
 #if DEBUG
-int GCDWebServerLogLevel = LOG_LEVEL_DEBUG;
+DDLogLevel GCDWebServerLogLevel = DDLogLevelDebug;
 #else
-int GCDWebServerLogLevel = LOG_LEVEL_INFO;
+DDLogLevel GCDWebServerLogLevel = DDLogLevelInfo;
 #endif
 #endif
 
@@ -170,6 +175,12 @@ static void _ExecuteMainThreadRunLoopSources() {
   dispatch_source_t _source6;
   CFNetServiceRef _registrationService;
   CFNetServiceRef _resolutionService;
+  DNSServiceRef _dnsService;
+  CFSocketRef _dnsSocket;
+  CFRunLoopSourceRef _dnsSource;
+  NSString* _dnsAddress;
+  NSUInteger _dnsPort;
+  BOOL _bindToLocalhost;
 #if TARGET_OS_IPHONE
   BOOL _suspendInBackground;
   UIBackgroundTaskIdentifier _backgroundTask;
@@ -242,7 +253,9 @@ static void _ExecuteMainThreadRunLoopSources() {
   GWS_LOG_DEBUG(@"Did connect");
   
 #if TARGET_OS_IPHONE
-  [self _startBackgroundTask];
+  if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
+    [self _startBackgroundTask];
+  }
 #endif
   
   if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) {
@@ -283,8 +296,6 @@ static void _ExecuteMainThreadRunLoopSources() {
     [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
     _backgroundTask = UIBackgroundTaskInvalid;
     GWS_LOG_DEBUG(@"Did end background task");
-  } else {
-    GWS_DNOT_REACHED();
   }
 }
 
@@ -367,7 +378,10 @@ static void _NetServiceRegisterCallBack(CFNetServiceRef service, CFStreamError*
     } else {
       GCDWebServer* server = (__bridge GCDWebServer*)info;
       GWS_LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]);
-      CFNetServiceResolveWithTimeout(server->_resolutionService, 1.0, NULL);
+      if (!CFNetServiceResolveWithTimeout(server->_resolutionService, kBonjourResolutionTimeout, NULL)) {
+        GWS_LOG_ERROR(@"Failed starting Bonjour resolution");
+        GWS_DNOT_REACHED();
+      }
     }
   }
 }
@@ -381,7 +395,7 @@ static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* e
       }
     } else {
       GCDWebServer* server = (__bridge GCDWebServer*)info;
-      GWS_LOG_INFO(@"%@ now reachable at %@", [server class], server.bonjourServerURL);
+      GWS_LOG_INFO(@"%@ now locally reachable at %@", [server class], server.bonjourServerURL);
       if ([server.delegate respondsToSelector:@selector(webServerDidCompleteBonjourRegistration:)]) {
         [server.delegate webServerDidCompleteBonjourRegistration:server];
       }
@@ -389,6 +403,41 @@ static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* e
   }
 }
 
+static void _DNSServiceCallBack(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, uint32_t externalAddress, DNSServiceProtocol protocol, uint16_t internalPort, uint16_t externalPort, uint32_t ttl, void* context) {
+  GWS_DCHECK([NSThread isMainThread]);
+  @autoreleasepool {
+    GCDWebServer* server = (__bridge GCDWebServer*)context;
+    if ((errorCode == kDNSServiceErr_NoError) || (errorCode == kDNSServiceErr_DoubleNAT)) {
+      struct sockaddr_in addr4;
+      bzero(&addr4, sizeof(addr4));
+      addr4.sin_len = sizeof(addr4);
+      addr4.sin_family = AF_INET;
+      addr4.sin_addr.s_addr = externalAddress;  // Already in network byte order
+      server->_dnsAddress = GCDWebServerStringFromSockAddr((const struct sockaddr*)&addr4, NO);
+      server->_dnsPort = ntohs(externalPort);
+      GWS_LOG_INFO(@"%@ now publicly reachable at %@", [server class], server.publicServerURL);
+    } else {
+      GWS_LOG_ERROR(@"DNS service error %i", errorCode);
+      server->_dnsAddress = nil;
+      server->_dnsPort = 0;
+    }
+    if ([server.delegate respondsToSelector:@selector(webServerDidUpdateNATPortMapping:)]) {
+      [server.delegate webServerDidUpdateNATPortMapping:server];
+    }
+  }
+}
+
+static void _SocketCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void* data, void* info) {
+  GWS_DCHECK([NSThread isMainThread]);
+  @autoreleasepool {
+    GCDWebServer* server = (__bridge GCDWebServer*)info;
+    DNSServiceErrorType status = DNSServiceProcessResult(server->_dnsService);
+    if (status != kDNSServiceErr_NoError) {
+      GWS_LOG_ERROR(@"DNS service error %i", status);
+    }
+  }
+}
+
 static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValue) {
   id value = [options objectForKey:key];
   return value ? value : defaultValue;
@@ -461,18 +510,18 @@ static inline NSString* _EncodeBase64(NSString* string) {
   dispatch_source_set_event_handler(source, ^{
     
     @autoreleasepool {
-      struct sockaddr remoteSockAddr;
+      struct sockaddr_storage remoteSockAddr;
       socklen_t remoteAddrLen = sizeof(remoteSockAddr);
-      int socket = accept(listeningSocket, &remoteSockAddr, &remoteAddrLen);
+      int socket = accept(listeningSocket, (struct sockaddr*)&remoteSockAddr, &remoteAddrLen);
       if (socket > 0) {
         NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen];
         
-        struct sockaddr localSockAddr;
+        struct sockaddr_storage localSockAddr;
         socklen_t localAddrLen = sizeof(localSockAddr);
         NSData* localAddress = nil;
-        if (getsockname(socket, &localSockAddr, &localAddrLen) == 0) {
+        if (getsockname(socket, (struct sockaddr*)&localSockAddr, &localAddrLen) == 0) {
           localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen];
-          GWS_DCHECK((!isIPv6 && localSockAddr.sa_family == AF_INET) || (isIPv6 && localSockAddr.sa_family == AF_INET6));
+          GWS_DCHECK((!isIPv6 && localSockAddr.ss_family == AF_INET) || (isIPv6 && localSockAddr.ss_family == AF_INET6));
         } else {
           GWS_DNOT_REACHED();
         }
@@ -495,6 +544,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
   GWS_DCHECK(_source4 == NULL);
   
   NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
+  BOOL bindToLocalhost = [_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue];
   NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
   
   struct sockaddr_in addr4;
@@ -502,17 +552,16 @@ static inline NSString* _EncodeBase64(NSString* string) {
   addr4.sin_len = sizeof(addr4);
   addr4.sin_family = AF_INET;
   addr4.sin_port = htons(port);
-  addr4.sin_addr.s_addr = htonl(INADDR_ANY);
+  addr4.sin_addr.s_addr = bindToLocalhost ? htonl(INADDR_LOOPBACK) : 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;
+    struct sockaddr_in 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);
+    if (getsockname(listeningSocket4, (struct sockaddr*)&addr, &addrlen) == 0) {
+      port = ntohs(addr.sin_port);
     } else {
       GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno);
     }
@@ -523,7 +572,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
   addr6.sin6_len = sizeof(addr6);
   addr6.sin6_family = AF_INET6;
   addr6.sin6_port = htons(port);
-  addr6.sin6_addr = in6addr_any;
+  addr6.sin6_addr = bindToLocalhost ? in6addr_loopback : in6addr_any;
   int listeningSocket6 = [self _createListeningSocket:YES localAddress:&addr6 length:sizeof(addr6) maxPendingConnections:maxPendingConnections error:error];
   if (listeningSocket6 <= 0) {
     close(listeningSocket4);
@@ -554,8 +603,9 @@ static inline NSString* _EncodeBase64(NSString* string) {
   _source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
   _source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
   _port = port;
+  _bindToLocalhost = bindToLocalhost;
   
-  NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, @"");
+  NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, nil);
   NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
   if (bonjourName) {
     _registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (__bridge CFStringRef)bonjourType, (__bridge CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port);
@@ -571,9 +621,34 @@ static inline NSString* _EncodeBase64(NSString* string) {
       if (_resolutionService) {
         CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context);
         CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
+      } else {
+        GWS_LOG_ERROR(@"Failed creating CFNetService for resolution");
       }
     } else {
-      GWS_LOG_ERROR(@"Failed creating CFNetService");
+      GWS_LOG_ERROR(@"Failed creating CFNetService for registration");
+    }
+  }
+  
+  if ([_GetOption(_options, GCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) {
+    DNSServiceErrorType status = DNSServiceNATPortMappingCreate(&_dnsService, 0, 0, kDNSServiceProtocol_TCP, htons(port), htons(port), 0, _DNSServiceCallBack, (__bridge void*)self);
+    if (status == kDNSServiceErr_NoError) {
+      CFSocketContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
+      _dnsSocket = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(_dnsService), kCFSocketReadCallBack, _SocketCallBack, &context);
+      if (_dnsSocket) {
+        CFSocketSetSocketFlags(_dnsSocket, CFSocketGetSocketFlags(_dnsSocket) & ~kCFSocketCloseOnInvalidate);
+        _dnsSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _dnsSocket, 0);
+        if (_dnsSource) {
+          CFRunLoopAddSource(CFRunLoopGetMain(), _dnsSource, kCFRunLoopCommonModes);
+        } else {
+          GWS_LOG_ERROR(@"Failed creating CFRunLoopSource");
+          GWS_DNOT_REACHED();
+        }
+      } else {
+        GWS_LOG_ERROR(@"Failed creating CFSocket");
+        GWS_DNOT_REACHED();
+      }
+    } else {
+      GWS_LOG_ERROR(@"Failed creating NAT port mapping (%i)", status);
     }
   }
   
@@ -592,6 +667,22 @@ static inline NSString* _EncodeBase64(NSString* string) {
 - (void)_stop {
   GWS_DCHECK(_source4 != NULL);
   
+  if (_dnsService) {
+    _dnsAddress = nil;
+    _dnsPort = 0;
+    if (_dnsSource) {
+      CFRunLoopSourceInvalidate(_dnsSource);
+      CFRelease(_dnsSource);
+      _dnsSource = NULL;
+    }
+    if (_dnsSocket) {
+      CFRelease(_dnsSocket);
+      _dnsSocket = NULL;
+    }
+    DNSServiceRefDeallocate(_dnsService);
+    _dnsService = NULL;
+  }
+  
   if (_registrationService) {
     if (_resolutionService) {
       CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
@@ -619,6 +710,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
 #endif
   _source4 = NULL;
   _port = 0;
+  _bindToLocalhost = NO;
   
   _serverName = nil;
   _authenticationRealm = nil;
@@ -664,7 +756,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
 
 - (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error {
   if (_options == nil) {
-    _options = [options copy];
+    _options = options ? [options copy] : @{};
 #if TARGET_OS_IPHONE
     _suspendInBackground = [_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue];
     if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start:error])
@@ -715,7 +807,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
 
 - (NSURL*)serverURL {
   if (_source4) {
-    NSString* ipAddress = GCDWebServerGetPrimaryIPAddress(NO);  // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice
+    NSString* ipAddress = _bindToLocalhost ? @"localhost" : GCDWebServerGetPrimaryIPAddress(NO);  // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice
     if (ipAddress) {
       if (_port != 80) {
         return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]];
@@ -742,6 +834,17 @@ static inline NSString* _EncodeBase64(NSString* string) {
   return nil;
 }
 
+- (NSURL*)publicServerURL {
+  if (_source4 && _dnsService && _dnsAddress && _dnsPort) {
+    if (_dnsPort != 80) {
+      return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", _dnsAddress, (int)_dnsPort]];
+    } else {
+      return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", _dnsAddress]];
+    }
+  }
+  return nil;
+}
+
 - (BOOL)start {
   return [self startWithPort:kDefaultPort bonjourName:@""];
 }
@@ -854,7 +957,12 @@ static inline NSString* _EncodeBase64(NSString* string) {
       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]]];
+          NSRange range = [result rangeAtIndex:i];
+          // range is {NSNotFound, 0} "if one of the capture groups did not participate in this particular match"
+          // see discussion in -[NSRegularExpression firstMatchInString:options:range:]
+          if (range.location != NSNotFound) {
+            [captures addObject:[urlPath substringWithRange:range]];
+          }
         }
       }
 
@@ -910,7 +1018,10 @@ static inline NSString* _EncodeBase64(NSString* string) {
   for (NSString* file in enumerator) {
     if (![file hasPrefix:@"."]) {
       NSString* type = [[enumerator fileAttributes] objectForKey:NSFileType];
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
       NSString* escapedFile = [file stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+#pragma clang diagnostic pop
       GWS_DCHECK(escapedFile);
       if ([type isEqualToString:NSFileTypeRegular]) {
         [html appendFormat:@"<li><a href=\"%@\">%@</a></li>\n", escapedFile, file];
@@ -984,7 +1095,7 @@ static inline NSString* _EncodeBase64(NSString* string) {
   [XLSharedFacility setMinLogLevel:level];
 #elif defined(__GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__)
   GCDWebServerLogLevel = level;
-#else
+#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__)
   GCDWebServerLogLevel = level;
 #endif
 }

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Core/GCDWebServerConnection.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServerConnection.h b/GCDWebServer/Core/GCDWebServerConnection.h
index 8e4aaf5..d353c8b 100644
--- a/GCDWebServer/Core/GCDWebServerConnection.h
+++ b/GCDWebServer/Core/GCDWebServerConnection.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Core/GCDWebServerConnection.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServerConnection.m b/GCDWebServer/Core/GCDWebServerConnection.m
index d4e3f39..d6c369e 100644
--- a/GCDWebServer/Core/GCDWebServerConnection.m
+++ b/GCDWebServer/Core/GCDWebServerConnection.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -548,6 +548,8 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
           }
         }
         if (_request) {
+          _request.localAddressData = self.localAddressData;
+          _request.remoteAddressData = self.remoteAddressData;
           if ([_request hasBody]) {
             [_request prepareForWriting];
             if (_request.usesChunkedTransferEncoding || (extraData.length <= _request.contentLength)) {
@@ -757,22 +759,26 @@ static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) {
 - (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 {
-    _handler.asyncProcessBlock(request, completion);
+    _handler.asyncProcessBlock(request, [completion copy]);
   }
   @catch (NSException* exception) {
     GWS_LOG_EXCEPTION(exception);
   }
 }
 
+// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
 static inline BOOL _CompareResources(NSString* responseETag, NSString* requestETag, NSDate* responseLastModified, NSDate* requestLastModified) {
-  if ([requestETag isEqualToString:@"*"] && (!responseLastModified || !requestLastModified || ([responseLastModified compare:requestLastModified] != NSOrderedDescending))) {
-    return YES;
-  } else {
-    if ([responseETag isEqualToString:requestETag]) {
+  if (requestLastModified && responseLastModified) {
+    if ([responseLastModified compare:requestLastModified] != NSOrderedDescending) {
+      return YES;
+    }
+  }
+  if (requestETag && responseETag) {  // Per the specs "If-None-Match" must be checked after "If-Modified-Since"
+    if ([requestETag isEqualToString:@"*"]) {
       return YES;
     }
-    if (responseLastModified && requestLastModified && ([responseLastModified compare:requestLastModified] != NSOrderedDescending)) {
+    if ([responseETag isEqualToString:requestETag]) {
       return YES;
     }
   }

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Core/GCDWebServerFunctions.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServerFunctions.h b/GCDWebServer/Core/GCDWebServerFunctions.h
index a8b2857..e5be05c 100644
--- a/GCDWebServer/Core/GCDWebServerFunctions.h
+++ b/GCDWebServer/Core/GCDWebServerFunctions.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Core/GCDWebServerFunctions.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServerFunctions.m b/GCDWebServer/Core/GCDWebServerFunctions.m
index d81af0b..25e41ca 100644
--- a/GCDWebServer/Core/GCDWebServerFunctions.m
+++ b/GCDWebServer/Core/GCDWebServerFunctions.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -182,11 +182,17 @@ NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
 }
 
 NSString* GCDWebServerEscapeURLString(NSString* string) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
   return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8));
+#pragma clang diagnostic pop
 }
 
 NSString* GCDWebServerUnescapeURLString(NSString* string) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
   return CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8));
+#pragma clang diagnostic pop
 }
 
 NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
@@ -240,7 +246,7 @@ NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL inclu
 NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
   NSString* address = nil;
 #if TARGET_OS_IPHONE
-#if !TARGET_IPHONE_SIMULATOR
+#if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_TV
   const char* primaryInterface = "en0";  // WiFi interface on iOS
 #endif
 #else
@@ -261,8 +267,10 @@ NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) {
   struct ifaddrs* list;
   if (getifaddrs(&list) >= 0) {
     for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) {
-#if TARGET_IPHONE_SIMULATOR
-      if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1"))  // Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator
+#if TARGET_IPHONE_SIMULATOR  || TARGET_OS_TV
+        // Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator
+        // Assumption holds for Apple TV running tvOS
+      if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1"))
 #else
       if (strcmp(ifap->ifa_name, primaryInterface))
 #endif

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h b/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h
index 7af51a2..6e98381 100644
--- a/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h
+++ b/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Core/GCDWebServerPrivate.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServerPrivate.h b/GCDWebServer/Core/GCDWebServerPrivate.h
index 1ed54e8..c9c6868 100644
--- a/GCDWebServer/Core/GCDWebServerPrivate.h
+++ b/GCDWebServer/Core/GCDWebServerPrivate.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -49,11 +49,21 @@
 #import "GCDWebServerStreamedResponse.h"
 
 /**
+ *  Check if a custom logging facility should be used instead.
+ */
+
+#if defined(__GCDWEBSERVER_LOGGING_HEADER__)
+
+#define __GCDWEBSERVER_LOGGING_FACILITY_CUSTOM__
+
+#import __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")
+#elif defined(__has_include) && __has_include("XLFacilityMacros.h")
 
 #define __GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__
 
@@ -77,15 +87,15 @@
  *  it as a logging facility.
  */
 
-#elif defined(__has_include) && __has_include("DDLogMacros.h")
+#elif defined(__has_include) && __has_include("CocoaLumberjack/CocoaLumberjack.h")
 
-#import "DDLogMacros.h"
+#import <CocoaLumberjack/CocoaLumberjack.h>
 
 #define __GCDWEBSERVER_LOGGING_FACILITY_COCOALUMBERJACK__
 
 #undef LOG_LEVEL_DEF
 #define LOG_LEVEL_DEF GCDWebServerLogLevel
-extern int GCDWebServerLogLevel;
+extern DDLogLevel GCDWebServerLogLevel;
 
 #define GWS_LOG_DEBUG(...) DDLogDebug(__VA_ARGS__)
 #define GWS_LOG_VERBOSE(...) DDLogVerbose(__VA_ARGS__)
@@ -95,16 +105,6 @@ extern int GCDWebServerLogLevel;
 #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__
-
-#import __GCDWEBSERVER_LOGGING_HEADER__
-
-/**
  *  If all of the above fail, then use GCDWebServer built-in
  *  logging facility.
  */
@@ -119,7 +119,7 @@ typedef NS_ENUM(int, GCDWebServerLoggingLevel) {
   kGCDWebServerLoggingLevel_Info,
   kGCDWebServerLoggingLevel_Warning,
   kGCDWebServerLoggingLevel_Error,
-  kGCDWebServerLoggingLevel_Exception,
+  kGCDWebServerLoggingLevel_Exception
 };
 
 extern GCDWebServerLoggingLevel GCDWebServerLogLevel;
@@ -211,6 +211,8 @@ extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOO
 
 @interface GCDWebServerRequest ()
 @property(nonatomic, readonly) BOOL usesChunkedTransferEncoding;
+@property(nonatomic, readwrite) NSData* localAddressData;
+@property(nonatomic, readwrite) NSData* remoteAddressData;
 - (void)prepareForWriting;
 - (BOOL)performOpen:(NSError**)error;
 - (BOOL)performWriteData:(NSData*)data error:(NSError**)error;

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Core/GCDWebServerRequest.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServerRequest.h b/GCDWebServer/Core/GCDWebServerRequest.h
index 3b517b6..c7bc31b 100644
--- a/GCDWebServer/Core/GCDWebServerRequest.h
+++ b/GCDWebServer/Core/GCDWebServerRequest.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -158,6 +158,30 @@ extern NSString* const GCDWebServerRequestAttribute_RegexCaptures;
 @property(nonatomic, readonly) BOOL acceptsGzipContentEncoding;
 
 /**
+ *  Returns the address of the local peer (i.e. server) for the request
+ *  as a raw "struct sockaddr".
+ */
+@property(nonatomic, readonly) NSData* localAddressData;
+
+/**
+ *  Returns the address of the local peer (i.e. server) for the request
+ *  as a string.
+ */
+@property(nonatomic, readonly) NSString* localAddressString;
+
+/**
+ *  Returns the address of the remote peer (i.e. client) for the request
+ *  as a raw "struct sockaddr".
+ */
+@property(nonatomic, readonly) NSData* remoteAddressData;
+
+/**
+ *  Returns the address of the remote peer (i.e. client) for the request
+ *  as a string.
+ */
+@property(nonatomic, readonly) NSString* remoteAddressString;
+
+/**
  *  This method is the designated initializer for the class.
  */
 - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query;

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Core/GCDWebServerRequest.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServerRequest.m b/GCDWebServer/Core/GCDWebServerRequest.m
index cc14993..dc929a7 100644
--- a/GCDWebServer/Core/GCDWebServerRequest.m
+++ b/GCDWebServer/Core/GCDWebServerRequest.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -88,7 +88,9 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
 - (BOOL)open:(NSError**)error {
   int result = inflateInit2(&_stream, 15 + 16);
   if (result != Z_OK) {
-    *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
+    if (error) {
+      *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
+    }
     return NO;
   }
   if (![super open:error]) {
@@ -114,7 +116,9 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
     _stream.avail_out = (uInt)maxLength;
     int result = inflate(&_stream, Z_NO_FLUSH);
     if ((result != Z_OK) && (result != Z_STREAM_END)) {
-      *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
+      if (error) {
+        *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
+      }
       return NO;
     }
     length += maxLength - _stream.avail_out;
@@ -153,6 +157,8 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
   NSString* _noneMatch;
   NSRange _range;
   BOOL _gzipAccepted;
+  NSData* _localAddress;
+  NSData* _remoteAddress;
   
   BOOL _opened;
   NSMutableArray* _decoders;
@@ -164,7 +170,7 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
 @implementation GCDWebServerRequest : NSObject
 
 @synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length, ifModifiedSince=_modifiedSince, ifNoneMatch=_noneMatch,
-            byteRange=_range, acceptsGzipContentEncoding=_gzipAccepted, usesChunkedTransferEncoding=_chunked;
+            byteRange=_range, acceptsGzipContentEncoding=_gzipAccepted, usesChunkedTransferEncoding=_chunked, localAddressData=_localAddress, remoteAddressData=_remoteAddress;
 
 - (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
   if ((self = [super init])) {
@@ -180,6 +186,7 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
     if (lengthHeader) {
       NSInteger length = [lengthHeader integerValue];
       if (_chunked || (length < 0)) {
+        GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _url);
         GWS_DNOT_REACHED();
         return nil;
       }
@@ -194,8 +201,8 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
       _length = NSUIntegerMax;
     } else {
       if (_type) {
-        GWS_DNOT_REACHED();
-        return nil;
+        GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _url);
+        _type = nil;  // Content-Type without Content-Length or chunked-encoding doesn't make sense
       }
       _length = NSUIntegerMax;
     }
@@ -304,6 +311,14 @@ NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerReque
   [_attributes setValue:attribute forKey:key];
 }
 
+- (NSString*)localAddressString {
+  return GCDWebServerStringFromSockAddr(_localAddress.bytes, YES);
+}
+
+- (NSString*)remoteAddressString {
+  return GCDWebServerStringFromSockAddr(_remoteAddress.bytes, YES);
+}
+
 - (NSString*)description {
   NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path];
   for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) {

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Core/GCDWebServerResponse.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServerResponse.h b/GCDWebServer/Core/GCDWebServerResponse.h
index 01ef2ab..2ec2dee 100644
--- a/GCDWebServer/Core/GCDWebServerResponse.h
+++ b/GCDWebServer/Core/GCDWebServerResponse.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Core/GCDWebServerResponse.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Core/GCDWebServerResponse.m b/GCDWebServer/Core/GCDWebServerResponse.m
index b0a220b..8357ab7 100644
--- a/GCDWebServer/Core/GCDWebServerResponse.m
+++ b/GCDWebServer/Core/GCDWebServerResponse.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -94,7 +94,9 @@
 - (BOOL)open:(NSError**)error {
   int result = deflateInit2(&_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
   if (result != Z_OK) {
-    *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
+    if (error) {
+      *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
+    }
     return NO;
   }
   if (![super open:error]) {
@@ -130,7 +132,9 @@
         if (result == Z_STREAM_END) {
           _finished = YES;
         } else if (result != Z_OK) {
-          *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
+          if (error) {
+            *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil];
+          }
           return nil;
         }
         length += maxLength - _stream.avail_out;
@@ -238,7 +242,7 @@
 
 - (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block {
   if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) {
-    [_reader asyncReadDataWithCompletion:block];
+    [_reader asyncReadDataWithCompletion:[block copy]];
   } else {
     NSError* error = nil;
     NSData* data = [_reader readData:&error];

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Requests/GCDWebServerDataRequest.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Requests/GCDWebServerDataRequest.h b/GCDWebServer/Requests/GCDWebServerDataRequest.h
index ef94b97..5048d08 100644
--- a/GCDWebServer/Requests/GCDWebServerDataRequest.h
+++ b/GCDWebServer/Requests/GCDWebServerDataRequest.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Requests/GCDWebServerDataRequest.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Requests/GCDWebServerDataRequest.m b/GCDWebServer/Requests/GCDWebServerDataRequest.m
index 4f0ed75..840e985 100644
--- a/GCDWebServer/Requests/GCDWebServerDataRequest.m
+++ b/GCDWebServer/Requests/GCDWebServerDataRequest.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -51,7 +51,9 @@
     _data = [[NSMutableData alloc] init];
   }
   if (_data == nil) {
-    *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed allocating memory"}];
+    if (error) {
+      *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed allocating memory"}];
+    }
     return NO;
   }
   return YES;

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Requests/GCDWebServerFileRequest.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Requests/GCDWebServerFileRequest.h b/GCDWebServer/Requests/GCDWebServerFileRequest.h
index 427a800..ad29eab 100644
--- a/GCDWebServer/Requests/GCDWebServerFileRequest.h
+++ b/GCDWebServer/Requests/GCDWebServerFileRequest.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Requests/GCDWebServerFileRequest.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Requests/GCDWebServerFileRequest.m b/GCDWebServer/Requests/GCDWebServerFileRequest.m
index d0c2118..adf67a5 100644
--- a/GCDWebServer/Requests/GCDWebServerFileRequest.m
+++ b/GCDWebServer/Requests/GCDWebServerFileRequest.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -56,7 +56,9 @@
 - (BOOL)open:(NSError**)error {
   _file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
   if (_file <= 0) {
-    *error = GCDWebServerMakePosixError(errno);
+    if (error) {
+      *error = GCDWebServerMakePosixError(errno);
+    }
     return NO;
   }
   return YES;
@@ -64,7 +66,9 @@
 
 - (BOOL)writeData:(NSData*)data error:(NSError**)error {
   if (write(_file, data.bytes, data.length) != (ssize_t)data.length) {
-    *error = GCDWebServerMakePosixError(errno);
+    if (error) {
+      *error = GCDWebServerMakePosixError(errno);
+    }
     return NO;
   }
   return YES;
@@ -72,7 +76,9 @@
 
 - (BOOL)close:(NSError**)error {
   if (close(_file) < 0) {
-    *error = GCDWebServerMakePosixError(errno);
+    if (error) {
+      *error = GCDWebServerMakePosixError(errno);
+    }
     return NO;
   }
 #ifdef __GCDWEBSERVER_ENABLE_TESTING__

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h b/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h
index 2463ca2..832c2e7 100644
--- a/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h
+++ b/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m b/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m
index e1c0015..c2fc9bf 100644
--- a/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m
+++ b/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -376,7 +376,9 @@ static NSData* _dashNewlineData = nil;
   NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary");
   _parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files];
   if (_parser == nil) {
-    *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed starting to parse multipart form data"}];
+    if (error) {
+      *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed starting to parse multipart form data"}];
+    }
     return NO;
   }
   return YES;
@@ -384,7 +386,9 @@ static NSData* _dashNewlineData = nil;
 
 - (BOOL)writeData:(NSData*)data error:(NSError**)error {
   if (![_parser appendBytes:data.bytes length:data.length]) {
-    *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed continuing to parse multipart form data"}];
+    if (error) {
+      *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed continuing to parse multipart form data"}];
+    }
     return NO;
   }
   return YES;
@@ -394,7 +398,9 @@ static NSData* _dashNewlineData = nil;
   BOOL atEnd = [_parser isAtEnd];
   _parser = nil;
   if (!atEnd) {
-    *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed finishing to parse multipart form data"}];
+    if (error) {
+      *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Failed finishing to parse multipart form data"}];
+    }
     return NO;
   }
   return YES;

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h b/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h
index e36eac3..9735380 100644
--- a/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h
+++ b/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m b/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m
index f210fa0..2c5fcc5 100644
--- a/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m
+++ b/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Responses/GCDWebServerDataResponse.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Responses/GCDWebServerDataResponse.h b/GCDWebServer/Responses/GCDWebServerDataResponse.h
index b0c6493..6e06cd8 100644
--- a/GCDWebServer/Responses/GCDWebServerDataResponse.h
+++ b/GCDWebServer/Responses/GCDWebServerDataResponse.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Responses/GCDWebServerDataResponse.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Responses/GCDWebServerDataResponse.m b/GCDWebServer/Responses/GCDWebServerDataResponse.m
index ea02799..12cd12b 100644
--- a/GCDWebServer/Responses/GCDWebServerDataResponse.m
+++ b/GCDWebServer/Responses/GCDWebServerDataResponse.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Responses/GCDWebServerErrorResponse.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Responses/GCDWebServerErrorResponse.h b/GCDWebServer/Responses/GCDWebServerErrorResponse.h
index 381b122..dad0114 100644
--- a/GCDWebServer/Responses/GCDWebServerErrorResponse.h
+++ b/GCDWebServer/Responses/GCDWebServerErrorResponse.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Responses/GCDWebServerErrorResponse.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Responses/GCDWebServerErrorResponse.m b/GCDWebServer/Responses/GCDWebServerErrorResponse.m
index c2e4422..ef6a991 100644
--- a/GCDWebServer/Responses/GCDWebServerErrorResponse.m
+++ b/GCDWebServer/Responses/GCDWebServerErrorResponse.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Responses/GCDWebServerFileResponse.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Responses/GCDWebServerFileResponse.h b/GCDWebServer/Responses/GCDWebServerFileResponse.h
index 19d284d..050e92f 100644
--- a/GCDWebServer/Responses/GCDWebServerFileResponse.h
+++ b/GCDWebServer/Responses/GCDWebServerFileResponse.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Responses/GCDWebServerFileResponse.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Responses/GCDWebServerFileResponse.m b/GCDWebServer/Responses/GCDWebServerFileResponse.m
index 2004327..a2b7c3c 100644
--- a/GCDWebServer/Responses/GCDWebServerFileResponse.m
+++ b/GCDWebServer/Responses/GCDWebServerFileResponse.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -142,11 +142,15 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
 - (BOOL)open:(NSError**)error {
   _file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
   if (_file <= 0) {
-    *error = GCDWebServerMakePosixError(errno);
+    if (error) {
+      *error = GCDWebServerMakePosixError(errno);
+    }
     return NO;
   }
   if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) {
-    *error = GCDWebServerMakePosixError(errno);
+    if (error) {
+      *error = GCDWebServerMakePosixError(errno);
+    }
     close(_file);
     return NO;
   }
@@ -158,7 +162,9 @@ static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) {
   NSMutableData* data = [[NSMutableData alloc] initWithLength:length];
   ssize_t result = read(_file, data.mutableBytes, length);
   if (result < 0) {
-    *error = GCDWebServerMakePosixError(errno);
+    if (error) {
+      *error = GCDWebServerMakePosixError(errno);
+    }
     return nil;
   }
   if (result > 0) {

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Responses/GCDWebServerStreamedResponse.h
----------------------------------------------------------------------
diff --git a/GCDWebServer/Responses/GCDWebServerStreamedResponse.h b/GCDWebServer/Responses/GCDWebServerStreamedResponse.h
index 4f39625..2731b7c 100644
--- a/GCDWebServer/Responses/GCDWebServerStreamedResponse.h
+++ b/GCDWebServer/Responses/GCDWebServerStreamedResponse.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -29,8 +29,8 @@
 
 /**
  *  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.
+ *  The block must return either a chunk of data, an empty NSData when done, or
+ *  nil on error and set the "error" argument which is guaranteed to be non-NULL.
  */
 typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);
 
@@ -39,13 +39,10 @@ typedef NSData* (^GCDWebServerStreamBlock)(NSError** error);
  *  except the streamed data can be returned at a later time allowing for
  *  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 call "completionBlock" passing the new chunk of data when ready,
+ *  an empty NSData when done, or nil on error and pass a NSError.
  *
- *  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.
+ *  The block cannot call "completionBlock" more than once per invocation.
  */
 typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlock completionBlock);
 

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebServer/Responses/GCDWebServerStreamedResponse.m
----------------------------------------------------------------------
diff --git a/GCDWebServer/Responses/GCDWebServerStreamedResponse.m b/GCDWebServer/Responses/GCDWebServerStreamedResponse.m
index 7f96943..4669617 100644
--- a/GCDWebServer/Responses/GCDWebServerStreamedResponse.m
+++ b/GCDWebServer/Responses/GCDWebServerStreamedResponse.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebUploader/GCDWebUploader.bundle/css/index.css
----------------------------------------------------------------------
diff --git a/GCDWebUploader/GCDWebUploader.bundle/css/index.css b/GCDWebUploader/GCDWebUploader.bundle/css/index.css
index aed15e4..53b0247 100644
--- a/GCDWebUploader/GCDWebUploader.bundle/css/index.css
+++ b/GCDWebUploader/GCDWebUploader.bundle/css/index.css
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebUploader/GCDWebUploader.bundle/index.html
----------------------------------------------------------------------
diff --git a/GCDWebUploader/GCDWebUploader.bundle/index.html b/GCDWebUploader/GCDWebUploader.bundle/index.html
index 28d371c..1bfd28e 100644
--- a/GCDWebUploader/GCDWebUploader.bundle/index.html
+++ b/GCDWebUploader/GCDWebUploader.bundle/index.html
@@ -1,5 +1,5 @@
 <!--
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -65,7 +65,7 @@
       <div id="alerts"></div>
       
       <div class="btn-toolbar">
-        <button type="button" class="btn btn-primary fileinput-button">
+        <button type="button" class="btn btn-primary fileinput-button" id="upload-file">
           <span class="glyphicon glyphicon-upload"></span> Upload Files&hellip;
           <input id="fileupload" type="file" name="files[]" multiple>
         </button>

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebUploader/GCDWebUploader.bundle/js/index.js
----------------------------------------------------------------------
diff --git a/GCDWebUploader/GCDWebUploader.bundle/js/index.js b/GCDWebUploader/GCDWebUploader.bundle/js/index.js
index 5bd9218..012926d 100644
--- a/GCDWebUploader/GCDWebUploader.bundle/js/index.js
+++ b/GCDWebUploader/GCDWebUploader.bundle/js/index.js
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -178,6 +178,17 @@ function _reload(path) {
 
 $(document).ready(function() {
   
+  // Workaround Firefox and IE not showing file selection dialog when clicking on "upload-file" <button>
+  // Making it a <div> instead also works but then it the button doesn't work anymore with tab selection or accessibility
+  $("#upload-file").click(function(event) {
+    $("#fileupload").click();
+  });
+  
+  // Prevent event bubbling when using workaround above
+  $("#fileupload").click(function(event) {
+    event.stopPropagation();
+  });
+  
   $("#fileupload").fileupload({
     dropZone: $(document),
     pasteZone: null,

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebUploader/GCDWebUploader.h
----------------------------------------------------------------------
diff --git a/GCDWebUploader/GCDWebUploader.h b/GCDWebUploader/GCDWebUploader.h
index 02ff30d..d2c92e9 100644
--- a/GCDWebUploader/GCDWebUploader.h
+++ b/GCDWebUploader/GCDWebUploader.h
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/GCDWebUploader/GCDWebUploader.m
----------------------------------------------------------------------
diff --git a/GCDWebUploader/GCDWebUploader.m b/GCDWebUploader/GCDWebUploader.m
index d88a45a..9bb4453 100644
--- a/GCDWebUploader/GCDWebUploader.m
+++ b/GCDWebUploader/GCDWebUploader.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -292,9 +292,11 @@
 @synthesize uploadDirectory=_uploadDirectory, allowedFileExtensions=_allowedExtensions, allowHiddenItems=_allowHidden,
             title=_title, header=_header, prologue=_prologue, epilogue=_epilogue, footer=_footer;
 
+@dynamic delegate;
+
 - (instancetype)initWithUploadDirectory:(NSString*)path {
   if ((self = [super init])) {
-    NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"GCDWebUploader" ofType:@"bundle"]];
+    NSBundle* siteBundle = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[GCDWebUploader class]] pathForResource:@"GCDWebUploader" ofType:@"bundle"]];
     if (siteBundle == nil) {
       return nil;
     }

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/Mac/main.m
----------------------------------------------------------------------
diff --git a/Mac/main.m b/Mac/main.m
index f446ce0..33b25e5 100644
--- a/Mac/main.m
+++ b/Mac/main.m
@@ -1,5 +1,5 @@
 /*
- Copyright (c) 2012-2014, Pierre-Olivier Latour
+ Copyright (c) 2012-2015, Pierre-Olivier Latour
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
@@ -72,6 +72,10 @@ typedef enum {
   [self _logDelegateCall:_cmd];
 }
 
+- (void)webServerDidUpdateNATPortMapping:(GCDWebServer*)server {
+  [self _logDelegateCall:_cmd];
+}
+
 - (void)webServerDidConnect:(GCDWebServer*)server {
   [self _logDelegateCall:_cmd];
 }
@@ -141,9 +145,11 @@ int main(int argc, const char* argv[]) {
     NSString* authenticationRealm = nil;
     NSString* authenticationUser = nil;
     NSString* authenticationPassword = nil;
+    BOOL bindToLocalhost = NO;
+    BOOL requestNATPortMapping = NO;
     
     if (argc == 1) {
-      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]));
+      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] [--localhost]\n\n", basename((char*)argv[0]));
     } else {
       for (int i = 1; i < argc; ++i) {
         if (argv[i][0] != '-') {
@@ -188,6 +194,10 @@ int main(int argc, const char* argv[]) {
         } else if (!strcmp(argv[i], "-authenticationPassword") && (i + 1 < argc)) {
           ++i;
           authenticationPassword = [NSString stringWithUTF8String:argv[i]];
+        } else if (!strcmp(argv[i], "--localhost")) {
+          bindToLocalhost = YES;
+        } else if (!strcmp(argv[i], "--nat")) {
+          requestNATPortMapping = YES;
         }
       }
     }
@@ -354,7 +364,7 @@ int main(int argc, const char* argv[]) {
         fprintf(stdout, "Running in Async Response mode");
         webServer = [[GCDWebServer alloc] init];
         [webServer addHandlerForMethod:@"GET"
-                                  path:@"/"
+                                  path:@"/async"
                           requestClass:[GCDWebServerRequest class]
                      asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
           
@@ -364,6 +374,29 @@ int main(int argc, const char* argv[]) {
           });
           
         }];
+        [webServer addHandlerForMethod:@"GET"
+                                  path:@"/async2"
+                          requestClass:[GCDWebServerRequest class]
+                     asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock handlerCompletionBlock) {
+          
+          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+            
+            __block int countDown = 10;
+            GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/plain" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock readerCompletionBlock) {
+              
+              dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+                
+                NSData* data = countDown ? [[NSString stringWithFormat:@"%i\n", countDown--] dataUsingEncoding:NSUTF8StringEncoding] : [NSData data];
+                readerCompletionBlock(data, nil);
+                
+              });
+              
+            }];
+            handlerCompletionBlock(response);
+            
+          });
+          
+        }];
         break;
       }
       
@@ -386,6 +419,8 @@ int main(int argc, const char* argv[]) {
         fprintf(stdout, "\n");
         NSMutableDictionary* options = [NSMutableDictionary dictionary];
         [options setObject:@8080 forKey:GCDWebServerOption_Port];
+        [options setObject:@(requestNATPortMapping) forKey:GCDWebServerOption_RequestNATPortMapping];
+        [options setObject:@(bindToLocalhost) forKey:GCDWebServerOption_BindToLocalhost];
         [options setObject:@"" forKey:GCDWebServerOption_BonjourName];
         if (authenticationUser && authenticationPassword) {
           [options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm];

http://git-wip-us.apache.org/repos/asf/cordova-plugins/blob/394cfb12/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 357586a..acaa870 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,6 @@ Overview
 [![Platform](http://cocoapod-badges.herokuapp.com/p/GCDWebServer/badge.png)](https://github.com/swisspol/GCDWebServer)
 [![License](http://img.shields.io/cocoapods/l/GCDWebServer.svg)](LICENSE)
 
-*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)
 * Well designed API with fully documented headers for easy integration and customization
@@ -26,6 +24,7 @@ Extra built-in features:
 * [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
+* NAT port mapping (IPv4 only)
 
 Included extensions:
 * [GCDWebUploader](GCDWebUploader/GCDWebUploader.h): subclass of ```GCDWebServer``` that implements an interface for uploading and downloading files using a web browser
@@ -45,7 +44,9 @@ Getting Started
 
 Download or check out the [latest release](https://github.com/swisspol/GCDWebServer/releases) of GCDWebServer then add the entire "GCDWebServer" subfolder to your Xcode project. If you intend to use one of the extensions like GCDWebDAVServer or GCDWebUploader, add these subfolders as well.
 
-Alternatively, you can install GCDWebServer using [CocoaPods](http://cocoapods.org/) by simply adding this line to your Xcode project's Podfile:
+If you add the files directly then (1) link to `libz` (via Target > Build Phases > Link Binary With Libraries) and (2) add `$(SDKROOT)/usr/include/libxml2` to your header search paths (via Target > Build Settings > HEADER_SEARCH_PATHS).
+
+Alternatively, you can install GCDWebServer using [CocoaPods](http://cocoapods.org/) by simply adding this line to your Podfile:
 ```
 pod "GCDWebServer", "~> 3.0"
 ```
@@ -58,11 +59,29 @@ Or this line for GCDWebDAVServer:
 pod "GCDWebServer/WebDAV", "~> 3.0"
 ```
 
+And finally run `$ pod install`.
+
+You can also use [Carthage](https://github.com/Carthage/Carthage) by adding this line to your Cartfile (3.2.5 is the first release with Carthage support):
+```
+github "swisspol/GCDWebServer" ~> 3.2.5
+```
+
+Then run `$ carthage update` and add the generated frameworks to your Xcode projects (see [Carthage instructions](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application)).
+
+Help & Support
+==============
+
+For help with using GCDWebServer, it's best to ask your question on Stack Overflow with the [`gcdwebserver`](http://stackoverflow.com/questions/tagged/gcdwebserver) tag.
+
+Be sure to read this entire README first though!
+
 Hello World
 ===========
 
 These code snippets show how to implement a custom HTTP server that runs on port 8080 and returns a "Hello World" HTML page to any request. Since GCDWebServer uses GCD blocks to handle requests, no subclassing or delegates are needed, which results in very clean code.
 
+**IMPORTANT:** If not using CocoaPods, be sure to add the `libz` shared system library to the Xcode target for your app.
+
 **OS X version (command line tool):**
 ```objectivec
 #import "GCDWebServer.h"
@@ -134,83 +153,29 @@ int main(int argc, const char* argv[]) {
 ***webServer.swift***
 ```swift
 import Foundation
+import GCDWebServers
+
+func initWebServer() {
 
-let webServer = GCDWebServer()
+    let webServer = GCDWebServer()
 
-webServer.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self) { request in
+    webServer.addDefaultHandlerForMethod("GET", requestClass: GCDWebServerRequest.self, processBlock: {request in
     return GCDWebServerDataResponse(HTML:"<html><body><p>Hello World</p></body></html>")
+        
+    })
+    
+    webServer.runWithPort(8080, bonjourName: "GCD Web Server")
+    
+    print("Visit \(webServer.serverURL) in your web browser")
 }
-
-webServer.runWithPort(8080, bonjourName: nil)
-
-println("Visit \(webServer.serverURL) in your web browser")
 ```
 
 ***WebServer-Bridging-Header.h***
 ```objectivec
-#import "GCDWebServer.h"
-#import "GCDWebServerDataResponse.h"
-```
-
-Asynchronous HTTP Responses
-===========================
-
-New in GCDWebServer 3.0 is the ability to process HTTP requests aysnchronously i.e. add handlers to the server which generate their ```GCDWebServerResponse``` asynchronously. This is achieved by adding handlers that use a ```GCDWebServerAsyncProcessBlock``` instead of a ```GCDWebServerProcessBlock```. Here's an example:
-
-**(Synchronous version)** The handler blocks while generating the HTTP response:
-```objectivec
-[webServer addDefaultHandlerForMethod:@"GET"
-                         requestClass:[GCDWebServerRequest class]
-                         processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
-  
-  GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
-  return response;
-  
-}];
-```
-
-**(Asynchronous version)** The handler returns immediately and calls back GCDWebServer later with the generated HTTP response:
-```objectivec
-[webServer addDefaultHandlerForMethod:@"GET"
-                         requestClass:[GCDWebServerRequest class]
-                    asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
-  
-  // Do some async operation like network access or file I/O (simulated here using dispatch_after())
-  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-    GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
-    completionBlock(response);
-  });
-
-}];
-```
-
-**(Advanced asynchronous version)** The handler returns immediately a streamed HTTP response which itself generates its contents asynchronously:
-```objectivec
-[webServer addDefaultHandlerForMethod:@"GET"
-                         requestClass:[GCDWebServerRequest class]
-                    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;
-
-}];
+#import <GCDWebServers/GCDWebServer.h>
+#import <GCDWebServers/GCDWebServerDataResponse.h>
 ```
 
-*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
 =============================
 
@@ -274,6 +239,7 @@ Serving a Static Website
 
 GCDWebServer includes a built-in handler that can recursively serve a directory (it also lets you control how the ["Cache-Control"](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) header should be set):
 
+**OS X version (command line tool):**
 ```objectivec
 #import "GCDWebServer.h"
 
@@ -318,6 +284,66 @@ Handlers require 2 GCD blocks:
 
 Note that most methods on ```GCDWebServer``` to add handlers only require the ```GCDWebServerProcessBlock``` or ```GCDWebServerAsyncProcessBlock``` as they already provide a built-in ```GCDWebServerMatchBlock``` e.g. to match a URL path with a Regex.
 
+Asynchronous HTTP Responses
+===========================
+
+New in GCDWebServer 3.0 is the ability to process HTTP requests aysnchronously i.e. add handlers to the server which generate their ```GCDWebServerResponse``` asynchronously. This is achieved by adding handlers that use a ```GCDWebServerAsyncProcessBlock``` instead of a ```GCDWebServerProcessBlock```. Here's an example:
+
+**(Synchronous version)** The handler blocks while generating the HTTP response:
+```objectivec
+[webServer addDefaultHandlerForMethod:@"GET"
+                         requestClass:[GCDWebServerRequest class]
+                         processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
+  
+  GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
+  return response;
+  
+}];
+```
+
+**(Asynchronous version)** The handler returns immediately and calls back GCDWebServer later with the generated HTTP response:
+```objectivec
+[webServer addDefaultHandlerForMethod:@"GET"
+                         requestClass:[GCDWebServerRequest class]
+                    asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
+  
+  // Do some async operation like network access or file I/O (simulated here using dispatch_after())
+  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+    GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
+    completionBlock(response);
+  });
+
+}];
+```
+
+**(Advanced asynchronous version)** The handler returns immediately a streamed HTTP response which itself generates its contents asynchronously:
+```objectivec
+[webServer addDefaultHandlerForMethod:@"GET"
+                         requestClass:[GCDWebServerRequest class]
+                         processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
+  
+  NSMutableArray* contents = [NSMutableArray arrayWithObjects:@"<html><body><p>\n", @"Hello World!\n", @"</p></body></html>\n", nil];  // Fake data source we are reading from
+  GCDWebServerStreamedResponse* response = [GCDWebServerStreamedResponse responseWithContentType:@"text/html" asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) {
+    
+    // Simulate a delay reading from the fake data source
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+      NSString* string = contents.firstObject;
+      if (string) {
+        [contents removeObjectAtIndex:0];
+        completionBlock([string dataUsingEncoding:NSUTF8StringEncoding], nil);  // Generate the 2nd part of the stream data
+      } else {
+        completionBlock([NSData data], nil);  // Must pass an empty NSData to signal the end of the stream
+      }
+    });
+    
+  }];
+  return response;
+  
+}];
+```
+
+*Note that you can even combine both the asynchronous and advanced asynchronous versions to return asynchronously an asynchronous HTTP response!*
+
 GCDWebServer & Background Mode for iOS Apps
 ===========================================
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org