概述

Build Status Version Platform License

GCDWebServer 是一个基于 GCD 的现代轻量级 HTTP 1.1 服务器,旨在嵌入到 iOS、macOS 和 tvOS 应用程序中。它的编写从零开始,并考虑了以下目标:

额外的内置功能

包含的扩展

不支持什么(但对于嵌入式 HTTP 服务器来说并非真正必需)

要求

入门指南

下载或检出 GCDWebServer 的 最新版本,然后将整个 “GCDWebServer” 子文件夹添加到您的 Xcode 项目中。如果您打算使用 GCDWebDAVServer 或 GCDWebUploader 等扩展,也请添加这些子文件夹。最后,链接到 libz(通过 Target > Build Phases > Link Binary With Libraries)并将 $(SDKROOT)/usr/include/libxml2 添加到您的头文件搜索路径中(通过 Target > Build Settings > HEADER_SEARCH_PATHS)。

或者,您可以使用 CocoaPods 安装 GCDWebServer,只需将此行添加到您的 Podfile 中

pod "GCDWebServer", "~> 3.0"

如果您想使用 GCDWebUploader,请改用此行

pod "GCDWebServer/WebUploader", "~> 3.0"

或者对于 GCDWebDAVServer 使用此行

pod "GCDWebServer/WebDAV", "~> 3.0"

最后运行 $ pod install

您也可以使用 Carthage,将此行添加到您的 Cartfile 中(3.2.5 是第一个支持 Carthage 的版本)

github "swisspol/GCDWebServer" ~> 3.2.5

然后运行 $ carthage update 并将生成的框架添加到您的 Xcode 项目中(请参阅 Carthage 说明)。

帮助与支持

如需使用 GCDWebServer 方面的帮助,最好在 Stack Overflow 上使用 gcdwebserver 标签提出您的问题。对于错误报告和增强请求,您可以使用此项目中的 issues

不过,请务必先阅读完整的 README!

Hello World

以下代码片段展示了如何实现一个在端口 8080 上运行的自定义 HTTP 服务器,并向任何请求返回 “Hello World” HTML 页面。由于 GCDWebServer 使用 GCD 代码块来处理请求,因此无需子类化或委托,从而产生非常简洁的代码。

重要提示: 如果不使用 CocoaPods,请务必将 libz 共享系统库添加到您的应用程序的 Xcode 目标中。

macOS 版本(命令行工具)

#import "GCDWebServer.h"
#import "GCDWebServerDataResponse.h"

int main(int argc, const char* argv[]) {
  @autoreleasepool {
    
    // Create server
    GCDWebServer* webServer = [[GCDWebServer alloc] init];
    
    // Add a handler to respond to GET requests on any URL
    [webServer addDefaultHandlerForMethod:@"GET"
                             requestClass:[GCDWebServerRequest class]
                             processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
      
      return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
      
    }];
    
    // Use convenience method that runs server on port 8080
    // until SIGINT (Ctrl-C in Terminal) or SIGTERM is received
    [webServer runWithPort:8080 bonjourName:nil];
    NSLog(@"Visit %@ in your web browser", webServer.serverURL);
    
  }
  return 0;
}

iOS 版本

#import "GCDWebServer.h"
#import "GCDWebServerDataResponse.h"

@interface AppDelegate : NSObject <UIApplicationDelegate> {
  GCDWebServer* _webServer;
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  
  // Create server
  _webServer = [[GCDWebServer alloc] init];
  
  // Add a handler to respond to GET requests on any URL
  [_webServer addDefaultHandlerForMethod:@"GET"
                            requestClass:[GCDWebServerRequest class]
                            processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
    
    return [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
    
  }];
  
  // Start server on port 8080
  [_webServer startWithPort:8080 bonjourName:nil];
  NSLog(@"Visit %@ in your web browser", _webServer.serverURL);
  
  return YES;
}

@end

macOS Swift 版本(命令行工具)

webServer.swift

import Foundation
import GCDWebServer

func initWebServer() {

    let webServer = GCDWebServer()

    webServer.addDefaultHandler(forMethod: "GET", request: GCDWebServerRequest.self, processBlock: {request in
            return GCDWebServerDataResponse(html:"<html><body><p>Hello World</p></body></html>")
            
        })
        
    webServer.start(withPort: 8080, bonjourName: "GCD Web Server")
    
    print("Visit \(webServer.serverURL) in your web browser")
}

WebServer-Bridging-Header.h

#import <GCDWebServer/GCDWebServer.h>
#import <GCDWebServer/GCDWebServerDataResponse.h>

iOS 应用程序中的基于 Web 的上传

GCDWebUploader 是 GCDWebServer 的子类,提供了一个即用型的 HTML 5 文件上传器和下载器。这允许用户使用 web 浏览器在其 web 浏览器中通过简洁的用户界面,从 iOS 应用程序沙箱内的目录上传、下载、删除文件和创建目录。

只需实例化并运行 GCDWebUploader 实例,然后从您的 web 浏览器访问 http://{YOUR-IOS-DEVICE-IP-ADDRESS}/

#import "GCDWebUploader.h"

@interface AppDelegate : NSObject <UIApplicationDelegate> {
  GCDWebUploader* _webUploader;
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
  _webUploader = [[GCDWebUploader alloc] initWithUploadDirectory:documentsPath];
  [_webUploader start];
  NSLog(@"Visit %@ in your web browser", _webUploader.serverURL);
  return YES;
}

@end

iOS 应用程序中的 WebDAV 服务器

GCDWebDAVServer 是 GCDWebServer 的子类,提供了一个符合 1 类标准的 WebDAV 服务器。这允许用户使用任何 WebDAV 客户端(如 Transmit (Mac)、ForkLift (Mac) 或 CyberDuck (Mac / Windows))从 iOS 应用程序沙箱内的目录上传、下载、删除文件和创建目录。

GCDWebDAVServer 也应与 macOS Finder 一起工作,因为它部分符合 2 类标准(但仅当客户端是 macOS WebDAV 实现时)。

只需实例化并运行 GCDWebDAVServer 实例,然后使用 WebDAV 客户端连接到 http://{YOUR-IOS-DEVICE-IP-ADDRESS}/

#import "GCDWebDAVServer.h"

@interface AppDelegate : NSObject <UIApplicationDelegate> {
  GCDWebDAVServer* _davServer;
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
  NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
  _davServer = [[GCDWebDAVServer alloc] initWithUploadDirectory:documentsPath];
  [_davServer start];
  NSLog(@"Visit %@ in your WebDAV client", _davServer.serverURL);
  return YES;
}

@end

提供静态网站

GCDWebServer 包含一个内置处理程序,可以递归地提供目录(它还允许您控制应如何设置 “Cache-Control” 标头)

macOS 版本(命令行工具)

#import "GCDWebServer.h"

int main(int argc, const char* argv[]) {
  @autoreleasepool {
    
    GCDWebServer* webServer = [[GCDWebServer alloc] init];
    [webServer addGETHandlerForBasePath:@"/" directoryPath:NSHomeDirectory() indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
    [webServer runWithPort:8080];
    
  }
  return 0;
}

使用 GCDWebServer

首先,您需要创建一个 GCDWebServer 类的实例。请注意,只要它们监听不同的端口,您可以在同一应用程序中运行多个 web 服务器。

然后,您向服务器添加一个或多个“处理程序”:每个处理程序都有机会处理传入的 web 请求并提供响应。处理程序按 LIFO 队列调用,因此最新添加的处理程序会覆盖任何先前添加的处理程序。

最后,您在给定的端口上启动服务器。

理解 GCDWebServer 的架构

GCDWebServer 的架构仅包含 4 个核心类

实现处理程序

GCDWebServer 依赖于“处理程序”来处理传入的 web 请求并生成响应。处理程序使用 GCD 代码块实现,这使得提供您自己的处理程序非常容易。但是,它们在 GCD 内的任意线程上执行,因此必须特别注意线程安全和可重入性

处理程序需要 2 个 GCD 代码块

请注意,GCDWebServer 上的大多数添加处理程序的方法只需要 GCDWebServerProcessBlockGCDWebServerAsyncProcessBlock,因为它们已经提供了一个内置的 GCDWebServerMatchBlock,例如,将 URL 路径与 Regex 匹配。

异步 HTTP 响应

GCDWebServer 3.0 中的新功能是异步处理 HTTP 请求的能力,即向服务器添加异步生成其 GCDWebServerResponse 的处理程序。这是通过添加使用 GCDWebServerAsyncProcessBlock 而不是 GCDWebServerProcessBlock 的处理程序来实现的。这是一个例子

(同步版本) 处理程序在生成 HTTP 响应时阻塞

[webServer addDefaultHandlerForMethod:@"GET"
                         requestClass:[GCDWebServerRequest class]
                         processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
  
  GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithHTML:@"<html><body><p>Hello World</p></body></html>"];
  return response;
  
}];

(异步版本) 处理程序立即返回,并在稍后使用生成的 HTTP 响应回调 GCDWebServer

[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);
  });

}];

(高级异步版本) 处理程序立即返回流式 HTTP 响应,该响应本身异步生成其内容

[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;
  
}];

请注意,您甚至可以结合异步版本和高级异步版本,以异步方式返回异步 HTTP 响应!

GCDWebServer & iOS 应用程序的后台模式

在 iOS 应用程序中进行网络操作时,您必须仔细处理 当 iOS 将应用程序置于后台时会发生什么。通常,您必须在应用程序进入后台时停止任何网络服务器,并在应用程序返回前台时重新启动它们。考虑到服务器在需要停止时可能具有正在进行的连接,这可能会变得非常复杂。

幸运的是,GCDWebServer 会自动为您完成所有这些操作

HTTP 连接通常以批次(或突发)方式启动,例如,当加载包含多个资源的网页时。这使得准确检测最后一个 HTTP 连接何时关闭变得困难:同一批次中的 2 个连续 HTTP 连接可能会被一个小的延迟分隔,而不是重叠。如果 GCDWebServer 在两者之间暂停自身,这对客户端来说是不利的。GCDWebServerOption_ConnectedStateCoalescingInterval 选项通过强制 GCDWebServer 在最后一个 HTTP 连接关闭后等待一些额外的延迟,以防在此延迟内启动新的 HTTP 连接,从而优雅地解决了此问题。

GCDWebServer 中的日志记录

为了调试和信息目的,每当发生某些事情时,GCDWebServer 都会广泛记录消息。此外,当在“Debug”模式与“Release”模式下构建 GCDWebServer 时,它会记录更多信息,还会执行许多内部一致性检查。要启用此行为,请在编译 GCDWebServer 时定义预处理器常量 DEBUG=1。在 Xcode 目标设置中,可以在以 “Debug” 配置构建时,将 DEBUG=1 添加到构建设置 GCC_PREPROCESSOR_DEFINITIONS 中。最后,您还可以通过调用 +[GCDWebServer setLogLevel:] 在运行时控制日志记录的详细程度。

默认情况下,GCDWebServer 记录的所有消息都发送到其内置的日志记录工具,该工具只是输出到 stderr(假设连接了终端类型设备)。为了更好地与应用程序的其余部分集成,或者由于记录的信息量,您可能需要使用其他日志记录工具。

GCDWebServer 自动支持 XLFacility(与 GCDWebServer 是同一作者,并且也是开源的):如果它在同一个 Xcode 项目中,GCDWebServer 应该自动使用它,而不是内置的日志记录工具(有关实现细节,请参阅 GCDWebServerPrivate.h)。

也可以使用自定义日志记录工具 - 有关更多信息,请参阅 GCDWebServer.h

高级示例 1:实现 HTTP 重定向

这是一个处理程序的示例,该处理程序使用 GCDWebServerResponse 上的便利方法将 “/” 重定向到 “/index.html”(它会自动设置 HTTP 状态代码和 “Location” 标头)

[self addHandlerForMethod:@"GET"
                     path:@"/"
             requestClass:[GCDWebServerRequest class]
             processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
    
  return [GCDWebServerResponse responseWithRedirect:[NSURL URLWithString:@"index.html" relativeToURL:request.URL]
                                          permanent:NO];
    
}];

高级示例 2:实现表单

要实现 HTTP 表单,您需要一对处理程序

[webServer addHandlerForMethod:@"GET"
                          path:@"/"
                  requestClass:[GCDWebServerRequest class]
                  processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
  
  NSString* html = @" \
    <html><body> \
      <form name=\"input\" action=\"/\" method=\"post\" enctype=\"application/x-www-form-urlencoded\"> \
      Value: <input type=\"text\" name=\"value\"> \
      <input type=\"submit\" value=\"Submit\"> \
      </form> \
    </body></html> \
  ";
  return [GCDWebServerDataResponse responseWithHTML:html];
  
}];

[webServer addHandlerForMethod:@"POST"
                          path:@"/"
                  requestClass:[GCDWebServerURLEncodedFormRequest class]
                  processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
  
  NSString* value = [[(GCDWebServerURLEncodedFormRequest*)request arguments] objectForKey:@"value"];
  NSString* html = [NSString stringWithFormat:@"<html><body><p>%@</p></body></html>", value];
  return [GCDWebServerDataResponse responseWithHTML:html];
  
}];

高级示例 3:提供动态网站

GCDWebServer 提供了 GCDWebServerDataResponse 类的扩展,该扩展可以返回从模板和一组变量(使用 %variable% 格式)生成的 HTML 内容。这是一个非常基本的模板系统,实际上旨在作为通过子类化 GCDWebServerResponse 构建更高级模板系统的起点。

假设您的应用程序中有一个网站目录,其中包含 HTML 模板文件以及相应的 CSS、脚本和图像,那么将其转换为动态网站非常容易

// Get the path to the website directory
NSString* websitePath = [[NSBundle mainBundle] pathForResource:@"Website" ofType:nil];

// Add a default handler to serve static files (i.e. anything other than HTML files)
[self addGETHandlerForBasePath:@"/" directoryPath:websitePath indexFilename:nil cacheAge:3600 allowRangeRequests:YES];

// Add an override handler for all requests to "*.html" URLs to do the special HTML templatization
[self addHandlerForMethod:@"GET"
                pathRegex:@"/.*\.html"
             requestClass:[GCDWebServerRequest class]
             processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
    
    NSDictionary* variables = [NSDictionary dictionaryWithObjectsAndKeys:@"value", @"variable", nil];
    return [GCDWebServerDataResponse responseWithHTMLTemplate:[websitePath stringByAppendingPathComponent:request.path]
                                                    variables:variables];
    
}];

// Add an override handler to redirect "/" URL to "/index.html"
[self addHandlerForMethod:@"GET"
                     path:@"/"
             requestClass:[GCDWebServerRequest class]
             processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
    
    return [GCDWebServerResponse responseWithRedirect:[NSURL URLWithString:@"index.html" relativeToURL:request.URL]
                                            permanent:NO];
    
];

最终示例:从 iOS 应用程序下载和上传文件

GCDWebServer 最初是为 iPad 的 ComicFlow 漫画阅读器应用程序编写的。它允许用户通过 WiFi 使用他们的 web 浏览器连接到他们的 iPad,然后在应用程序内部上传、下载和组织漫画文件。

ComicFlow 是 完全开源的,您可以在 WebServer.hWebServer.m 文件中看到它是如何使用 GCDWebServer 的。