GCDWebServer 是一个基于 GCD 的现代轻量级 HTTP 1.1 服务器,旨在嵌入到 iOS、macOS 和 tvOS 应用程序中。它的编写从零开始,并考虑了以下目标:
额外的内置功能
包含的扩展
GCDWebServer
的子类,实现了一个使用 web 浏览器上传和下载文件的接口GCDWebServer
的子类,实现了一个 1 类 WebDAV 服务器(对 macOS Finder 具有部分 2 类支持)不支持什么(但对于嵌入式 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!
以下代码片段展示了如何实现一个在端口 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>
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
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
类的实例。请注意,只要它们监听不同的端口,您可以在同一应用程序中运行多个 web 服务器。
然后,您向服务器添加一个或多个“处理程序”:每个处理程序都有机会处理传入的 web 请求并提供响应。处理程序按 LIFO 队列调用,因此最新添加的处理程序会覆盖任何先前添加的处理程序。
最后,您在给定的端口上启动服务器。
GCDWebServer 的架构仅包含 4 个核心类
GCDWebServer
实例化,用于处理每个新的 HTTP 连接。每个实例都保持活动状态,直到连接关闭。您不能直接使用此类,但它已公开,因此您可以对其进行子类化以覆盖某些钩子。GCDWebServerConnection
实例在收到 HTTP 标头后创建。它包装请求并处理 HTTP 正文(如果有)。GCDWebServer 附带 几个 GCDWebServerRequest
的子类,用于处理常见情况,例如将正文存储在内存中或将其流式传输到磁盘上的文件。GCDWebServerResponse
的子类,用于处理常见情况,例如内存中的 HTML 文本或从磁盘流式传输文件。GCDWebServer 依赖于“处理程序”来处理传入的 web 请求并生成响应。处理程序使用 GCD 代码块实现,这使得提供您自己的处理程序非常容易。但是,它们在 GCD 内的任意线程上执行,因此必须特别注意线程安全和可重入性。
处理程序需要 2 个 GCD 代码块
GCDWebServer
实例的每个处理程序上调用 GCDWebServerMatchBlock
。它被传递 web 请求的基本信息(HTTP 方法、URL、标头...),并且必须决定是否要处理它。如果是,则必须返回使用此信息创建的新 GCDWebServerRequest
实例(见上文)。否则,它只需返回 nil。GCDWebServerProcessBlock
或 GCDWebServerAsyncProcessBlock
,并将上一步创建的 GCDWebServerRequest
实例传递给它。它必须同步(如果使用 GCDWebServerProcessBlock
)或异步(如果使用 GCDWebServerAsyncProcessBlock
)返回 GCDWebServerResponse
实例(见上文),或者在出错时返回 nil,这将导致向客户端返回 500 HTTP 状态代码。但是,建议在出错时返回 GCDWebServerErrorResponse 的实例,以便可以向客户端返回更有用的信息。请注意,GCDWebServer
上的大多数添加处理程序的方法只需要 GCDWebServerProcessBlock
或 GCDWebServerAsyncProcessBlock
,因为它们已经提供了一个内置的 GCDWebServerMatchBlock
,例如,将 URL 路径与 Regex 匹配。
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 响应!
在 iOS 应用程序中进行网络操作时,您必须仔细处理 当 iOS 将应用程序置于后台时会发生什么。通常,您必须在应用程序进入后台时停止任何网络服务器,并在应用程序返回前台时重新启动它们。考虑到服务器在需要停止时可能具有正在进行的连接,这可能会变得非常复杂。
幸运的是,GCDWebServer 会自动为您完成所有这些操作
-stop
一样(可以使用 GCDWebServerOption_AutomaticallySuspendInBackground
选项禁用此行为)。-stop
一样(可以使用 GCDWebServerOption_AutomaticallySuspendInBackground
选项禁用此行为)。-start
一样。HTTP 连接通常以批次(或突发)方式启动,例如,当加载包含多个资源的网页时。这使得准确检测最后一个 HTTP 连接何时关闭变得困难:同一批次中的 2 个连续 HTTP 连接可能会被一个小的延迟分隔,而不是重叠。如果 GCDWebServer 在两者之间暂停自身,这对客户端来说是不利的。GCDWebServerOption_ConnectedStateCoalescingInterval
选项通过强制 GCDWebServer 在最后一个 HTTP 连接关闭后等待一些额外的延迟,以防在此延迟内启动新的 HTTP 连接,从而优雅地解决了此问题。
为了调试和信息目的,每当发生某些事情时,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。
这是一个处理程序的示例,该处理程序使用 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 表单,您需要一对处理程序
GCDWebServerRequest
类。处理程序生成包含简单 HTML 表单的响应。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];
}];
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];
];
GCDWebServer 最初是为 iPad 的 ComicFlow 漫画阅读器应用程序编写的。它允许用户通过 WiFi 使用他们的 web 浏览器连接到他们的 iPad,然后在应用程序内部上传、下载和组织漫画文件。
ComicFlow 是 完全开源的,您可以在 WebServer.h 和 WebServer.m 文件中看到它是如何使用 GCDWebServer 的。