GCDWebServer 3.5.4

GCDWebServer 3.5.4

测试已测试
语言语言 Obj-CObjective C
许可证 未声明
发布最后发布2020年3月

Maintained by Pierre-Olivier Latour.



  • 作者:
  • Pierre-Olivier Latour

概览

Build Status Version Platform License

GCDWebServer 是一个现代且轻量级的基于 GCD 的 HTTP 1.1 服务器,专为嵌入 iOS、macOS 和 tvOS 应用而设计。它是从头开始编写的,并考虑以下目标

  • 具有优雅且易于使用的架构,仅使用 4 个核心类:服务器、连接、请求和响应(请参阅以下 "理解 GCDWebServer 架构")
  • 经过良好设计的 API,带有完整文档的头部,以便于集成和定制
  • 使用 Grand Central Dispatch 完全事件驱动设计构建,以提高性能和并发性
  • 不依赖于第三方源代码
  • 在友好的 New BSD 许可证 下可用

额外内置功能

  • 允许实现完全异步的 HTTP 请求处理器
  • 通过磁盘流式传输大型的 HTTP 请求或响应体,最小化内存使用
  • 支持使用 "application/x-www-form-urlencoded" 或 "multipart/form-data" 编码提交的 web 表单 解析器(包括文件上传)
  • 请求和响应 HTTP 体的 JSON 解析和序列化
  • 请求和响应 HTTP 体的 分块传输编码
  • 请求和响应 HTTP 体的 HTTP 压缩,使用 gzip
  • 支持局部文件的请求的 HTTP 范围
  • 基于密码保护的 基本摘要访问 认证
  • 自动处理在 iOS 应用中前台、后台和暂停模式之间的转换
  • 完全支持 IPv4 和 IPv6
  • NAT 端口映射(仅 IPv4)

内置的扩展

  • GCDWebUploader:是 GCDWebServer 的子类,它实现了使用网页浏览器上传和下载文件的用户界面
  • GCDWebDAVServer:是 GCDWebServer 的子类,它实现了一个类 1 WebDAV 服务器(包含对 macOS Finder 的类 2 部分支持)

不支持的功能(但对于嵌入式 HTTP 服务器来说并非必需)

  • 持久连接
  • HTTPS

要求

  • macOS 10.7 或更高版本(x86_64)
  • iOS 8.0 或更高版本(armv7, armv7s 或 arm64)
  • tvOS 9.0 或更高版本(arm64架构)
  • 仅支持ARC内存管理(如需MRC支持,请使用GCDWebServer 3.1或更早版本)

入门指南

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

或者,您可以使用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标签提问。对于错误报告和增强请求,您可以使用该项目的问题

不过,请先阅读整个README文件!

Hello World

这些代码片段展示了如何实现一个自定义HTTP服务器,该服务器在端口8080上运行,并返回一个“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>

基于Web的iOS应用上传

GCDWebUploader是GCDWebServer的一个子类,提供了一个现成的HTML 5文件上传和下载功能。这允许用户通过他们的网络浏览器中干净的用户界面,从iOS应用沙盒内的一个目录上传、下载、删除文件以及创建目录。

只需实例化并运行一个GCDWebUploader实例,然后从您的网络浏览器访问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的Web服务器。这允许用户通过任何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请求并提供响应。处理器在LIFO队列中调用,所以最后添加的处理器会覆盖之前添加的。

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

理解 GCDWebServer 的架构

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

  • GCDWebServer 管理用于监听新 HTTP 连接的套接字和服务器使用的处理程序列表。
  • GCDWebServerConnectionGCDWebServer 实例化以处理每个新的 HTTP 连接。每个实例在连接关闭前一直存活。您不能直接使用这个类,但它是公开的,以便您可以对其进行子类化以覆盖一些钩子。
  • GCDWebServerRequest 在收到 HTTP 头部后由 GCDWebServerConnection 实例创建。它包装请求并处理任何 HTTP 主体。GCDWebServer 伴随着几个 GCDWebServerRequest 的子类来处理常见情况,如将主体存储在内存中或将它流式传输到磁盘上的文件。
  • GCDWebServerResponse 由请求处理程序创建,包装 HTTP 响应头部和可选主体。GCDWebServer 伴随着几个 GCDWebServerResponse 的子类来处理常见情况,如 HTML 文本在内存中或从磁盘流式传输文件。

实现处理程序

GCDWebServer 依赖于“处理程序”来处理传入的 Web 请求并生成响应。处理程序使用 GCD 块实现,这使得提供您自己的处理程序变得非常简单。然而,它们在 GCD 中的任意线程上执行,因此必须注意线程安全性和可重入性。

处理程序需要 2 个 GCD 块

  • GCDWebServerMatchBlock 在将每个处理程序添加到 GCDWebServer 实例时被调用,每次 Web 请求启动时(即收到 HTTP 头部)。它传递关于 Web 请求的基本信息(HTTP 方法、URL、头部...),并必须决定是否要处理它。如果是,它必须返回一个新创建的 GCDWebServerRequest 实例(见上文)。如果不处理,它简单地返回 nil。
  • GCDWebServerProcessBlockGCDWebServerAsyncProcessBlock 在完全收到 Web 请求后被调用,并传递前一阶段创建的 GCDWebServerRequest 实例。它必须同步返回(如果使用 GCDWebServerProcessBlock)或异步返回(如果使用 GCDWebServerAsyncProcessBlock)一个 GCDWebServerResponse 实例(见上文)或在错误时返回 nil,这将导致向客户端返回 500 HTTP 状态码。然而,通常建议在错误时返回 GCDWebServerErrorResponse 的实例,以便能够向客户端返回更有用的信息。

请注意,大多数用于向 GCDWebServer 添加处理程序的方法仅需要 GCDWebServerProcessBlockGCDWebServerAsyncProcessBlock,因为它们已经提供了内置的 GCDWebServerMatchBlock,例如,可以与正则表达式匹配 URL 路径。

异步 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;
  
}];

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

[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 连接时,GCDWebServer 就开始一个 后台任务,仅当最后一个连接关闭时结束它。这阻止了 iOS 在应用进入后台后挂起应用,这将立即杀死客户端的 HTTP 连接。
  • 当应用处于后台时,只要仍然有新的 HTTP 连接被发起,后台任务将继续存在,iOS 不会挂起应用 长达 10 分钟(除非遇到突然的意外内存压力)。
  • 如果应用在最后一个 HTTP 连接关闭时仍处于后台,GCDWebServer 将挂起自身,停止接收新的连接,就好像您已经调用了 -stop(此行为可以通过 GCDWebServerOption_AutomaticallySuspendInBackground 选项禁用)。
  • 如果应用在没有打开HTTP连接的情况下进入后台,GCDWebServer 将立即挂起自身,停止接收新的连接,就好像您已经调用了 -stop(此行为可以通过 GCDWebServerOption_AutomaticallySuspendInBackground 选项禁用)。
  • 如果应用回到前台而 GCDWebServer 已被挂起,它将自动恢复并重新开始接收新的 HTTP 连接,就好像您已经调用了 -start

HTTP 连接通常以批量(或突发)方式启动,例如加载具有多个资源的网页。这使得难以准确检测最后一个 HTTP 连接何时被关闭:有可能两个连续的 HTTP 连接属于同一批,但中间却有一个小的延迟,而不是重叠。如果 GCDWebServer 正好在这个时候暂停,对客户端将是不利的。通过强制 GCDWebServer 在关闭最后一个 HTTP 连接之后等待额外的延迟,然后再执行任何操作,GCDWebServerOption_ConnectedStateCoalescingInterval 选项巧妙地解决了这个问题,以防这个延迟内启动新的 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];
    
}];

要实现HTTP表单,您需要一个处理器对

  • GET处理器不需要HTTP请求的任何正文,因此使用GCDWebServerRequest类。处理器生成一个包含简单HTML表单的响应。
  • POST处理器期望表单值在HTTP请求中位于正文部分,并进行百分编码。幸运的是,GCDWebServer提供了请求类GCDWebServerURLEncodedFormRequest,它可以自动解析这样的正文。处理器简单地回显用户提交的表单值。
[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。