轻量级iOS/OSX服务器GCDWebServer
简述
GCDWebServer是一个基于GCD的轻量级服务器框架,用于内嵌到OSX或者iOS系统的应用中提供HTTP1.1的服务。
实现目标
1.设计优雅,易于使用。仅仅包含4个核心类:server, connection, request and response
2.设计良好的API。头文件注释齐全,非常易于继承和定制个性化需求。
3.事件驱动模型。基于GCD框架,实现最佳性能和并发。
4.不依赖任何第三方源码。
5.符合新的BSD许可协议。
额外特性
1.针对http请求,支持完全异步处理
2.针对较大HTTP请求和响应流,采用内存最优化策略
3.支持解析使用"application/x-www-form-urlencoded" 或者 "multipart/form-data"编码格式提交的html表单
4.支持对json格式的请求或响应进行解析和序列化
5.HTTP请求或响应采用分块传输编码
6.HTTP请求和响应采用gzip方式压缩
7.对本地文件的请求支持多种HTTP类型
8.采用通用、简单的密码保护访问认证机制
9.支持在app前台、后台或挂起时自动处理事务
10.完全支持ipv4和ipv6
拓展功能
1.文件上传功能。提供通过浏览器实现文件上传和下载的接口。GCDWebUploader(->GCDWebServer)
2.DAV文件系统服务。DAV不仅被看作HTTP的扩展,甚至被看作一种网络文件系统。(GCDWebDAVServer->GCDWebServer)
不支持
1.长连接
2.https请求
系统要求
1.OS X 10.7 or later (x86_64)
2.iOS 5.0 or later (armv7, armv7s or arm64)
3.仅支持ARC
如何开始
下载最新源码,将整个GCDWebServer目录拷贝到自己的xcode项目目录下,importGCDWebServer.h头文件即可。如果需要使用GCDWebDAVServer或GCDWebUploader,同样拷贝对应的目录到自己的项目工程下。
或者你也可以通过cocoaPods方式引入:
在xcode项目的podfile文件中添加如下语句:
pod "GCDWebServer", "~> 3.0”
如果需要使用上传功能,用下面的替换:
pod "GCDWebServer/WebUploader", "~> 3.0”
如果需要使用DAV功能,用下面的替换:
pod "GCDWebServer/WebDAV", "~> 3.0”
Hello World
由于GCDWebServer采用GCD代码块实现请求处理handler,代码清晰、整洁,因此不需要子类化或者代理方式。
基于iOS应用的实现步骤:
1.Create server
2.添加请求处理句柄(Handle)
3.启动服务器,监听8080端口
异步HTTP响应生成
GCDWebServer 3.0开始,框架拥有异步处理HTTP请求的能力,形如,为服务添加不同的handles来异步生成GCDWebServerResponse响应。要想实现这个功能,添加handle实现时需要使用GCDWebServerAsyncProcessBlock替换GCDWebServerProcessBlock。
生成静态网页
GCDWebServer包含一个内建的handler,可以提供递归地目录服务(也可以让你控制如何设置缓存控制的头部信息)。
#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服务器运行在同一个应用程序中,只要他们监听不同的端口即可。
然后,向服务器添加一个或多个Handler:每个Handler可以处理一个外部传入的Web请求,同时提供/生成响应。Handles处理队列是一个后进先出队列,所以最新添加的handler会覆盖最先添加的handler。
最后,在一个给定的端口上开启服务。
理解GCDWebServer的架构
GCDWebServer架构由4个核心类组成:
1.GCDWebServer辅助管理监听HTTP连接的套接字以及服务器使用的处理器列表。
2.GCDWebServerConnection继承自GCDWebServer,负责处理每个HTTP连接。每个实例都保持连接直到连接关闭。你不能直接使用这个类,但它是暴露的,所以你可以子类化它用于重写一些钩子。
3.GCDWebServerRequest由GCDWebServerConnection的实例在拿到http信息头后创建。它负责 把请求和处理HTTP信息体封装起来。GCDWebServer自带的几个GCDWebServerRequest子类负责处理普通请求,比如保存请求信息在内存或在磁盘上的文件上。
4.GCDWebServerResponse由请求程序的handle创建,负责将响应的HTTP头和body封装起来。GCDWebServer自带的几个GCDWebServerResponse 子类负责处理响应,比如保存请求信息在内存或在磁盘上的文件上。
实现Handlers
GCDWebServer依靠handlers去处理Web请求并生成响应。handlers由GCD块实现,使得你可以很容易实现它们。然而,他们会在GCD中执行任意线程,所以要特别注意线程安全和重入问题。
1.GCDWebServerMatchBlock被添加到GCDWebServer的实例中,当任意请求开始时将会被调用(例如收到http请求的头部信息)。它负责为web请求传递基本信息,并决定是否继续处理。如果是,它必须返回一个新的带有请求信息的GCDWebServerRequest实例(见上文)。否则,它只返回nil。
2.GCDWebServerProcessBlock或GCDWebServerAsyncProcessBlock将在web请求完全接收完毕后调用,并负责传递由上一步生成的GCDWebServerRequest实例。它必须返回同步(如果使用GCDWebServerProcessBlock)或异步(如果使用GCDWebServerAsyncProcessBlock)的GCDWebServerResponse实例(见上文)或带500HTTP状态码返回给客户端的nil。当然,更推荐返回GCDWebServerErrorResponse实例,这样一来可以更多有用的信息返回给客户端。
注意,大多数添加handlers的GCDWebServer方法只期望GCDWebServerProcessBlock或GCDWebServerAsyncProcessBlock,因为他们已经提供了一个内置的GCDWebServerMatchBlock,例如带有正则表达式匹配的URL路径。
GCDWebServer的后台模式处理
当在iOS应用中处理网络操作时,你必须小心处理当iOS应用程序进入后台的情况。通常情况,当应用程序在后台时,你必须停止所有网络服务器,当应用程序返回到前台时再重新启动。考虑到服务器可能有正在进行的连接时,他们需要停止,这种场景可能会变得相当复杂。
幸运的是,GCDWebServer已经自动为你处理了上述所有事情。
1.在第一个HTTP连接打开时,GCDWebServer将开启一个后台任务,当最后一个HTTP连接断开时这个后台任务将结束。这可以防止当iOS App进入后台时挂起,挂起后app会立即杀死所有与客户端直连的HTTP连接。
当应用程序进入后台时,只要新的HTTP连接被启动,这个后台任务将继续存在和并且iOS不会挂起应用程序(除非突然和意外的内存压力下)。
如果当最后一个HTTP连接被关闭应用程序仍然在后台,只要你调用stop方法,GCDWebServer将自己暂停并停止接收新的连接。(这种行为可以通过GCDWebServerOption_AutomaticallySuspendInBackground选项禁用)。
2.如果应用程序切换到后台并且没有HTTP连接是打开的,只要你调用stop方法,GCDWebServer将立即暂停和停止接收新的连接。(这种行为可以与GCDWebServerOption_AutomaticallySuspendInBackground选项禁用)。
3.如果应用程序切换回到前台,而GCDWebServer已经暂停,只要你调用start方法,它会自动恢复,开始再次接收新的HTTP连接。
HTTP连接往往以包的形式开始向外并发,例如加载一个有多资源的网页。这使得它很难准确地检测出最后的HTTP连接已关闭:很有可能2个属于同一个分包但连续的HTTP连接将被一个短暂的延迟而非重叠分离。如果客户端刚好在两个连接之间的延迟部分挂起,这将会使问题变得很糟糕。GCDWebServerOption_AutomaticallySuspendInBackground选项可以优雅地解决这个问题,通过在最后一个HTTP连接关闭后强迫GCDWebServer等待一个额外的延迟,防止一个新的连接在延迟期间被分离。
GCDWebServer中的日志
为实现调试和查看信息,每当发生什么时,GCDWebServer都会对当前的服务状态进行日志记录。此外,当使用debug模式而不是release模式构建GCDWebServer,它将记录下更多的信息,也进行了大量的内部一致性检查。为了实现这一行为,编译GCDWebServer时指定预处理器常量 DEBUG =1。在Xcode的目标设置中,也可以通过增加DEBUG = 1到编译设置GCC_PREPROCESSOR_DEFINITIONS中。最后,你还可以在运行时调用+[GCDWebServer setLogLevel:]方法实现对冗长的日志进行进行控制。
默认情况下,所有通过GCDWebServer记录的日志都会被发送到其内置的日志中心,它只是输出到stderr(假设一个终端类型设备已连接)。为了更好地融入你的应用程序的其余部分或由于记录的信息量较大,你可能希望使用另一个日志记录中心。
GCDWebServer自动支持XLFacility(GCDWebServer的作者实现,已开源)和CocoaLumberjack。如果他们是在同一个Xcode项目中, GCDWebServer自动使用它们而不是内置日志中心(更多细节见 GCDWebServerPrivate.h文件)。
当然,也支持自定义日志功能,更多信息见GCDWebServer.h。
应用实例1:实现HTTP请求重定向
下面是一个将’/’重定向到’/index.html’的例子,使用GCDWebServerResponse提供的非常好用的方法(这个方法将设置HTTP状态并自动定位头文件)
{code}
[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的表单,你需要两个handlers:
1.GET处理handler不需要HTTP请求中的body信息,因此采用 GCDWebServerRequest类。该handler程序将生产一个包含一个简单的HTML表单响应。
2.POST处理handler需要HTTP请求中经过encode后的body信息中的表单值。幸运的是,GCDWebServer提供请求的类GCDWebServerURLEncodedFormRequest可自动解析这些信息。该处理程序只简单地从用户提交的表单中获取返回值。
[webServer addHandlerForMethod:@"GET"
path:@"/"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
NSString* html = @" \
\
\
Value: \
\
\
\
";
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:@"%@", value];
return [GCDWebServerDataResponse responseWithHTML:html];
}];
应用实例3:实现动态网页
GCDWebServer提供一个扩展的GCDWebServerDataResponse类,可以返回根据模板和一组变量(使用格式%变量%)生成的HTML内容。这是一个非常基础的模板系统,作为一个起点引入,最终通过子类GCDWebServerResponse实现更高级的模板系统。
假设你有一个网站目录在你的应用程序中,包含HTML模板文件及相应的CSS,脚本和图片,那么,把它变成一个动态网站将会跟容易:
{code}
// 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];
];
{code}
虽然可嵌入app的轻量级服务器还有CocoaHTTPServer,简单试用下就可以明显发现,GCDWebServer更加适合。