轻量级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更加适合。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,517评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,140评论 25 707
  • 第一章 Nginx简介 Nginx是什么 没有听过Nginx?那么一定听过它的“同行”Apache吧!Ngi...
    JokerW阅读 32,608评论 24 1,002
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,810评论 6 13
  • 老话说久病床前无孝子。只是年轻人体验不到这句话的份量。 琼瑶阿姨的爱人,2002年就重病卧床,直到靠鼻饲管为生,而...
    秋叶大叔阅读 864评论 1 6