74 Star 225 Fork 62

calvinwilliams / hetao

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
How_to_design_a_web_server.md 34.23 KB
一键复制 编辑 原始数据 按行查看 历史
calvinwilliams 提交于 2020-04-11 15:16 . UPDATE TO V0.1.1.0B

如何设计一个Web服务器

版本修订

文档版本号 修订日期 修订人 修订内容
v0.1.0 2020-03-22 厉华 创建
增加 什么是Web服务器
增加 预备知识
增加 架构设计
v0.2.0 2020-03-23 厉华 增加 模块设计
v0.3.0 2020-03-29 厉华 增加 跨平台Windows实现
增加 HTTPS实现
增加 minihetao
增加 辅助工具
v0.4.0 2020-03-29 厉华 跟随hetao v0.1.0.0调整内容

1. 什么是Web服务器

Web服务器一般指网站服务器,是部署在网络中的一种计算机程序,对外采用HTTP/HTTPS协议作为对外接口规范,向浏览器等客户端提供文档查询、放置更新文档、上传下载文件等服务。现代Web服务器还具备代理转发、负载均衡、应用逻辑等附加功能。

Web服务的工作流程分为五个步骤:连接、请求、本地处理、响应、断开。首先浏览器通过DNS获得Web服务器IP和PORT地址信息,与之建立TCP连接,然后发送HTTP请求,Web服务器接收和解析请求,找到本地静态网页文档或动态生成网页,响应回浏览器,双方断开TCP连接。

hetao_http_process_flow.png

现代Web服务器还承担代理转发负载均衡功能

hetao_http_forward_process_flow.png

本文以我在2017年完全自研的Web服务器开源产品hetao作为示例,讲解如何设计一个Web服务器,尽量不涉及到编程语言和具体代码实现。

2. 预备知识

2.1. HTTP/HTTPS协议

2.1.1. HTTP

HTTP协议的设计在1989年的欧洲核子研究组织所发起,经历了最初的v0.9版,以及较为完善的v1.0版,在1999年公布的RFC2616定义了现今广泛使用的v1.1版,在2015年互联网工程任务组提交发布了v2.0版。

HTTP(网络七层)是建立在TCP(网络四层)上的一种通讯协议,它在TCP连接续存期内定义了通讯数据交换格式。

tcp_and_http_protocol.png

HTTP请求格式如下:

(请求方法)(空格)(URI)(空格)(请求协议和版本)\r\n
(请求头1名字)(空格;可选):(空格;可选)(请求头1值)\r\n
...
(请求头n名字)(空格;可选):(空格;可选)(请求头n值)\r\n
\r\n
(请求体;可选)

HTTP请求示例如下:

GET / HTTP/1.1
Host: www.163.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

POST /user HTTP/1.1
Host: www.test.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-length: 31

username=calvin&password=123456

浏览器向Web服务器提交HTTP请求时,请求URL会被拆分为几段,作为HTTP请求数据来源,比如:

http://www.test.com/users

会被拆成

GET /users HTTP/1.1
Host: www.test.com
...

HTTP请求头选项列表如下:

HTTP请求头选项名字 值示例 值类型 说明
Host www.163.com 字符串 浏览器希望访问Web服务器的域名,用于多虚拟主机环境
User-Agent Mozilla/5.0 (Windows NT... 字符串 浏览器方的软件环境
Accept text/html,application/... 字符串 浏览器可以接受的文本类型和优先级,供服务端参考
Accept-Language zh-CN,zh;q=0.8,zh-TW;... 字符串 浏览器可以接受的文本语言代码,供服务端参考
Accept-Encoding gzip, deflate, br 字符串 浏览器可以接受的数据编码(压缩)算法
Connection keep-alive 枚举字符串 本次请求响应结束后是否需要保持连接用于下一次请求响应
Content-length 31 整型 HTTP体的长度;如果出现则表示有请求体
(其它自定义请求头选项名) ... ... ...

HTTP响应格式如下:

(响应协议和版本)(空格)(状态码)(空格)(状态码描述)\r\n
(响应头1选项名字)(空格;可选):(空格;可选)(响应头1选项值)\r\n
...
(响应头n选项名字)(空格;可选):(空格;可选)(响应头n选项值)\r\n
\r\n
(响应体;可选)

HTTP响应示例如下:

HTTP/1.1 200 OK
Date: Sun, 15 Mar 2020 07:39:12 GMT
Content-type: text/html; charset=GBK
Expires: Sun, 15 Mar 2020 07:40:29 GMT
Server: nginx

HTTP/1.1 200 OK
Date: Sun, 15 Mar 2020 07:39:12 GMT
Content-type: text/html; charset=GBK
Expires: Sun, 15 Mar 2020 07:40:29 GMT
Server: nginx
Content-length: 319

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb18030" />
<title>Welcome</title>
</head>
<body>
Hello HETAO
</body>
</html>

HTTP响应头选项列表如下:

HTTP响应头选项名字 值示例 值类型 说明
Server nginx 字符串 Web服务器软件名
Content-type text/html; charset=GBK 字符串 文本类型和编码
Expires Sun, 15 Mar 2020 07:40:29 GMT 网页过期时间
Content-encoding gzip 响应体数据的编码(压缩)算法
Content-length 31 整型 HTTP体的长度;如果出现则表示有响应体

2.1.2. HTTPS

所谓HTTPS只不过是在TCP三步握手完成后,HTTP分组开始前,做SSL握手,协商双方加解密算法和密钥。SSL握手分组较复杂,下图是根据hetao代码分析出来的分组图:

https_protocol.png

3. 架构设计

3.1. 功能架构图

hetao_function_architecture.png

hetao的层次结构分为七层,从下到上分别是公共基础层、进程管理层、事件管理层、事件处理层、安全控制层、应用入口层、应用API层。

公共基础层提供基础的数据结构算法库、工程技术库,以及本项目公共复用代码如宏、函数等。

进程管理层负责软件入口、进程/线程结构,以及进程/线程管理和控制。

由于hetao基于多路复用事件模型,事件管理层负责事件总线的创建、维护和销毁,以及各事件类的分派。

事件处理层负责网络IO事件、文件系统IO事件和内部事件的具体处理。

安全控制层负责TCP安全控制、HTTP会话安全控制、HTTP报文安全限制等。

应用入口层和API层是为SOCGI模块新增,用于hetao搭载应用逻辑,实现应用动态库管理、应用开发部署规范、实例生命周期、应用编程上下文访问管理,以及SOCGI API、RESTful API编程接口库。

3.2. 配置架构图

hetao_config_architecture.png

hetao配置架构图表达了配置文件的内容结构布局和内部数据结构层次。

hetao进程结构为“管理进程+多个工作进程”,可配置工作进程数量,一般的,如果做纯静态资源服务器配置为服务器CPU核数,如果做应用服务器配置为CPU核数*2。

CPU亲和性用于把某个进程绑定在某个CPU核上,尽量减少CPU切换上下文开销,提高性能。

Accept锁用在多个工作进程在接受TCP新连接时的防止旧版本操作系统惊群现象,并设计了进程间调配TCP连接数均衡算法,一般情况下可不用开启。

日志文件分两类:全局调试日志和网站日志。全局调试日志文件名默认为error.log,记录着内部处理和数据流转的动作细节和状态信息,根据日志等级过滤输出,为内部调试和生产报错提供全局跟踪。网站日志根据不同网站配置为不同的日志文件,默认名为access.log,建议名为access_(网站域名).log,记录着网站的所有HTTP访问记录,为数据分析和攻击防御提供专向信息。

安全限制用于TCP连接、HTTP会话、HTTP报文的安全控制,防御攻击。

TCP选项用于TCP功能选项,比如改善延迟。

HTTP选项用于HTTP功能选项,比如启用HTTP报文压缩、超时。

错误页面用于HTTP出现可控错误时导向页面文件名配置。每个HTTP错误码对应一个错误页面文件名。

媒体类型描述表配置了页面文件名扩展名与媒体类型描述之间的关系,用于组织HTTP响应头选项Content-type。每个文件扩展名对应一个媒体类型描述。

hetao允许创建多个侦听地址,每个侦听地址上运行多个网站域(又称虚拟主机),每个网站域有独立的域名本地文档根目录索引文件列表访问日志文件名,还有一组域名重定向配置、重写URI配置、代理转发配置、SOCGI配置。代理转发即转发HTTP请求到下游服务器(一般为应用服务器),通过比较URI文件扩展名与代理转发资源文件扩展名来确定HTTP请求是否需要代理转发,然后使用负载均衡算法在一组下游服务器地址中挑选一个进行转发。SOCGI用于hetao把HTTP请求转交给指定的应用动态库来处理,通过比较URI文件扩展名与SOCGI资源文件扩展名确定当前HTTP请求是否需要交给应用动态库处理,如果配置成空则达成RESTful效果,socgi应用动态库文件名设置应用动态库文件名。

hetao允许网站域下面再细分URI子配置下挂各自的域名重定向重写URI代理转发SOCGI配置,比如不同URI对应不同的代理转发下游服务器集群或应用动态库文件。

3.3. 开发架构图

hetao_development_architecture.png

hetao进程架构由一个监视进程和多个工作进程组组成,监视进程负责监视工作进程,一旦工作进程崩溃则重新创建一个新的工作进程。每个工作进程由一个定时器线程和一个工作线程组成。

定时器线程负责执行一些定时任务,比如定期获取时间信息并刷新时间缓存。

工作线程负责创建事件总线并处理侦听TCP连接事件、收发上下游HTTP通讯数据事件和文件系统文件变动事件,当接收完整HTTP请求后处理从文件系统读取资源文件或触发代理转发或装载应用动态库处理。

当启用SOCGI功能时,完整HTTP请求会被交付给应用动态库处理,动态库调用hetao SOCGI API获取HTTP请求信息,做业务处理,然后调用hetao SOCGI API组织HTTP响应信息,最后hetao把HTTP响应发回给HTTP请求方。

hetao在SOCGI接口中还提供了hetao RESTful API,允许应用快速创建RESTful应用。

4. 模块设计

4.1. HTTP解析器

Web服务器的核心是HTTP解析器,其功能和性能决定Web服务器质量。

hetao采用我之前自研的HTTP解析器开源项目fasterhttp作为其HTTP解析核心。fasterhttp的目标是提供一个流式、支持完整HTTP协议v1.x、极高性能的HTTP解析器,更重要的是它与使用者之间功能边界清晰,专注做好组装和解析HTTP协议报文,不负责通讯管理、通讯模型和进程/线程框架等,方便嵌入到各式各样的场景中使用,且性能十分突出,可能是世界上最快的通用HTTP解析器。目前该解析器已用在了很多重要地方,比如某城商行核心系统新一代中间件。有关fasterhttp具体参见giteegithub

fasterhttp使用十分简单:

use_fasterhttp.png

创建HTTP会话时调用函数CreateHttpEnv创建HTTP环境。

  1. 由于hetao采用非堵塞多路复用通讯模型,HTTP请求阶段,服务端反复调用函数ReceiveHttpRequestNonblock读socket数据并流式解析掉,如果HTTP协议报文还未收完整则函数返回FASTERHTTP_INFO_NEED_MORE_HTTP_BUFFER,如果收完整则函数返回0,返回其它值则表示出现错误。

  2. 当接收完整HTTP请求后,应用可调用GetHttpHeaderPtr_METHODGetHttpHeaderPtr_URIQueryHttpHeaderPtrGetHttpBodyPtr等函数获得请求信息。

  3. 应用处理完自己的业务逻辑后,调用FormatHttpResponseStartLineGetHttpResponseBufferStrcpy*HttpBufferStrcat*HttpBufferMemcat*HttpBuffer等函数构造HTTP响应报文,然后进入HTTP响应阶段。

  4. HTTP响应阶段,服务端当出现可写事件时调用函数SendHttpResponseNonblock写数据到socket,如果一次写不完则返回FASTERHTTP_INFO_TCP_SEND_WOULDBLOCK,如果写完则返回0,返回其它值则表示出现错误。

  5. HTTP请求响应结束后,调用函数ResetHttpEnv重置HTTP环境,或者调用函数DestroyHttpEnv销毁环境。

use_fasterhttp_for_forwarding.png

  1. 当启用代理转发时,HTTP请求阶段,hetao代理方作为客户端反复调用函数SendHttpRequestNonblock转发HTTP请求到下游服务器,直到发送完整HTTP请求,切换为HTTP响应阶段。

  2. HTTP响应阶段,hetao代理方作为客户端反复调用函数ReceiveHttpResponseNonblock接收HTTP响应直至收完,然后开启hetao代理方响应HTTP上游请求方通讯过程。

4.2. 对象层次结构

4.2.1. hetao内部环境

hetao_internal_object_hierarchy.png

hetao监视进程初始化时会创建一个内部环境结构HetaoEnv,该结构包含配置文件信息,如媒体类型描述表、侦听地址,还包含运行中所需的所有信息。创建工作进程时为每个工作进程继承一份,在内部各个层次各个模块中传递该结构,简化代码设计。

内部环境结构具体包含信息如下:

  • 进程信息结构数组
  • 媒体类型哈希表
  • IP限制哈希表
  • 静态资源内容缓存链表
  • 侦听会话链表
  • HTTP会话池
  • 命令管道会话

4.2.2. 进程信息结构数组

hetao_process_info_object.png

监视进程初始化内部环境HetaoEnv时,分配一块大内存用以存放进程信息结构数组,每个工作进程写自己所属结构单元,监视进程可以随时通过结构数组查询工作进程状态,如进程PID,每个工作进程也能通过结构数组访问其它工作进程状态,如目前HTTP会话数量

4.2.3. 媒体类型哈希表

hetao_mime_type_object.png

监视进程初始化内部环境HetaoEnv时,装载配置文件中的媒体类型描述表构建媒体类型哈希表,响应每一个HTTP请求时,根据请求资源文件扩展名查询媒体类型哈希表,组织HTTP响应头选项Content-type

因为媒体类型在装载配置时就已经确定数量,后面使用中只有查询,且要求快速查询,所以采用哈希表。

4.2.4. IP限制哈希表

hetao_ip_limits_object.png

监视进程初始化内部环境HetaoEnv时,预分配哈希空间用以存放IP限制信息链表数组,哈希单元数量为2^16个,如果发生哈希冲撞则哈希节点上追加链表节点。

因为维护IP限制信息要求快速,所以采用哈希表,又因为IP数量较多,全放在哈希表中占用内存很大,故采用哈希表+链表的二级数据结构。

4.2.5. 静态资源内容缓存链表

hetao_html_cache_object.png

hetao处理每个静态资源的HTTP请求时,会把文件数据在内存中缓存一份,下次再有相同资源的请求到来时,直接从内存获取,以降低HTTP处理时间。

每缓存一个静态资源文件,都会在静态资源内容缓存链表中追加一个节点,节点内含路径文件名文件数据gzip压缩的文件数据deflate压缩的文件数据,同时挂接到文件系统监视树和文件名树上,注册文件系统inotify事件,后续一旦发生文件变动,文件系统会主动通知hetaohetao从监视树定位文件做清理。

因为静态资源文件缓存数量不定,所以采用链表,又为了加快后续相同静态资源文件查询效率,加了文件名树,又因为使用了文件系统inotify,所以加了文件系统监视树,最后由于Windows操作系统上用WIN32 API ReadDirectoryChangesW和完成端口实现文件系统监视,复用了文件名树,也不能用文件系统监视树代替链表存放数据。

4.2.6. 侦听会话链表

hetao_listen_session_object.png

监视进程初始化内部环境HetaoEnv时,把所有侦听地址装载为链表,每个侦听地址信息结构对应配置文件信息结构。

4.2.6.1. 域名重定向哈希表

hetao_redirect_domain_object.png

因为域名重定向信息数量在装载配置时就已经确定,所以采用哈希表存放,以提高查询性能。

4.2.6.2. URI重写链表

hetao_uri_rewrite_object.png

因为原URI配置支持正则表达式,URI重写在处理每一个HTTP请求时只能遍历所有配置,所以采用链表存放。

4.2.6.3. 代理转发服务器链表

hetao_forward_server_object.png

因为代理转发负载均衡算法支持轮询和最少连接数,下游服务器信息存放就采用链表,再创建一棵最少连接服务器树用作同步排序。

4.2.6.4. SOCGI子环境

hetao_socgi_object.png

配置文件中的配置项直接装载到SOCGI子环境中,同时定义了动态库打开句柄动态库入口函数指针用于缓存打开信息,一次打开重复使用,如果处于更新目标需要重打开,目前只能通过重启或优雅重启hetao来实现,后面会考虑引入文件系统事件监视自动重新打开。

4.2.6.5. 细分URI子配置

hetao_uri_location_object.png

website下的redirect、rewrite、forward、socgi是针对该website所有URI,hetao还支持细分URI的redirect、rewrite、forward、socgi。比如可以针对某个URI启用重写URI,针对某个URI设置不同的代理转发下游服务器集群网址,针对某个URI装载不用的应用动态库处理HTTP请求等。

4.2.7. HTTP会话池

hetao_http_session_object.png

每个TCP连接到来时都会分配一个HTTP会话结构,TCP断开时回收该结构,为了提高性能,采用预分配HTTP会话块,一个块是一个HTTP会话结构数组,当一个块中所有HTTP会话结构都被分配完时,申请一个新块,即批量申请一批HTTP会话结构,当一个块中还有未用的HTTP会话结构,则直接分配走,当一个块中所有HTTP会话结构都为未用,释放该会话块。

每个HTTP会话块中还创建一个已使用和未使用HTTP会话链表与之对应,用于关联同一个块中的单元。

全局创建一颗已使用HTTP会话活跃超时时间戳树和累计超时时间戳树,同步排序,用于快速查询即将超时的HTTP会话。活跃超时指连续没有数据收发的超时,累计超时指每次HTTP请求和响应总时间超时。

HTTP会话结构中包含网络地址信息、HTTP报文环境、SSL握手环境,还有代理转发相关信息。

4.2.8. 命令管道会话

命令管道会话结构是一个空结构,用于配合事件总线注册需要提供会话结构。

4.3. 进程/线程结构

hetao_processes_and_threads.png

4.3.1. 监视-工作进程组

hetao启动后,装载配置文件,初始化内部环境,转换为守护进程,监视进程创建工作进程组(还有命令管道、事件总线等)并监视其异常,一旦崩溃则重启之。

4.3.2. 定时-工作线程组

每个工作进程创建一个定时器线程和一个工作线程。

定时器线程负责每秒获取当前时间戳并格式化时间字符串,提供给日志等模块缓存使用,避免大量获取当前时间操作和格式化字符串操作。

工作线程把命令管道事件和文件系统事件订阅注册到事件总线,进入事件循环,每次等待、拉取和处理一批事件。每批事件处理前检查如果定时器线程刷新过时间戳,清理一批活跃超时和累计超时的现存HTTP会话。

4.4. 事件总线

hetao_event_handler_tree.png

从事件总线中等待、取出一批事件,依次处理每个事件。

事件主要包括网络IO事件、文件系统订阅事件和命令管道事件。

单线程的多路复用的事件处理都采用非堵塞、快速处理为原则,每个事件处理不能耗费太长时间,否则会卡住线程导致暂停对外提供服务,但单线程绑定CPU核能减少CPU切换时间片、IO操作也往往大大慢于本地计算,最终能提高程序处理并发度和吞吐。

4.4.1. 网络IO事件

hetao_network_io_event_handler_tree.png

初始化时,侦听会话订阅可读事件注册进事件总线中,TCP新连接被接受后也会订阅HTTP会话的可读事件注册进事件总线中。

所以判断事件分支处理:

  • 如果是侦听会话事件的可读事件:调用通讯模块的函数OnAcceptingSocket,接受TCP新连接,向HTTP会话管理模块申请一个HTTP会话,订阅可读事件进事件总线,如果启用了HTTPS,设置SSL开始握手标志并初次调用函数OnAcceptingSslSocket
  • 如果是侦听会话事件的错误、其它事件:如果发生此类事件,表明侦听已损坏,立即结束工作进程,监视进程会重新创建一个干净的工作进程接替之。
  • 如果是HTTP会话的可读事件:如果设置了SSL握手标志但还未客户端握手完成,调用通讯模块函数OnAcceptingSslSocket继续接受客户端SSL握手;如果设置了转发SSL握手标志但还未SSL握手服务端完成,调用通讯模块函数OnConnectingSslForward继续发起SSL握手服务端;如果设置了代理转发标志,调用通讯模块函数OnReceivingSocket继续接收转发服务端响应数据;其它情况,调用通讯模块函数OnReceivingSocket继续接收客户端请求数据。
  • 如果是HTTP会话的可写事件:如果设置了SSL标志但还未接收客户端SSL握手完成,调用通讯模块函数OnAcceptingSslSocket继续接受客户端SSL握手;如果设置了SSL转发标志但还未SSL握手服务端完成,调用通讯模块函数OnAcceptingSslSocket继续接受客户端SSL握手;如果设置了代理转发标志,当转发状态为连接中,调用通讯模块函数OnConnectingForward继续转发连接服务端,当转发状态为连接完成,调用通讯模块函数OnSendingForward继续转发请求数据到服务端;其它情况,调用通讯模块函数OnSendingSocket继续发送应数据到客户端。
  • 如果是HTTP会话的断开事件:调用HTTP会话管理模块函数SetHttpSessionUnused回收HTTP会话。
  • 如果是HTTP会话的错误事件:当正处在转发中,调用通讯模块函数OnConnectingForward测试转发连接状态并做相应处置;其它情况,调用HTTP会话管理模块函数SetHttpSessionUnused回收HTTP会话。
  • 如果是HTTP会话的其它事件:调用HTTP会话管理模块函数SetHttpSessionUnused回收HTTP会话。

4.4.2. 文件系统订阅事件

hetao_fs_event_handler_tree.png

HTTP静态资源请求到来时,hetao会读取文件并响应回去,同时把静态资源文件缓存在内存中,并订阅该文件的文件系统变动事件。后续一旦发生该文件的编辑、改名、删除等变化,文件系统会主动通知hetao清理其缓存。

4.4.3. 命令管道事件

hetao_cmd_pipe_event_handler_tree.png

初始化时,命令管道订阅可读事件注册进事件总线。当监视进程想要通知工作进程做一些事情时,会通过命令管道发送命令字节,工作线程做相应处理。

4.5. 处理HTTP请求

hetao_process_http_request.png

当网络IO事件接收到完整HTTP报文后,进入处理HTTP请求,处理分八个环节:

  1. 确定虚拟主机:根据HTTP请求头中的选项Host在配置中查找对应的虚拟主机(网站域)。
  2. 细分URI子配置匹配:如果存在细分URI子配置,正则匹配location,如果不存在则直接使用website下的缺省配置。
  3. 域名重定向:主要用于重定向域名的虚拟主机。
  4. 重写URI:根据配置规则中正则表达式匹配符合条件格式的URI,转换成新URI。
  5. 处理HTTP请求前:在所有后续其它环节前的统一处理。
  6. 代理转发:用资源文件扩展名匹配配置中的扩展名,把符合扩展名的资源请求通过负载均衡转发到下游服务器,一般用于Web-App架构中的Web层(静态资源响应+反向代理)。
  7. 应用动态库:使用应用提供的程序逻辑来处理HTTP请求,一般用于Web-App架构中的App层(业务逻辑处理)。
  8. 读取静态资源文件:打开静态资源文件,读取文件数据,构造文件缓存。
  9. 处理HTTP请求前:在所有前续其它环节后的统一处理。

4.6. 静态资源文件缓存管理

静态资源文件缓存是为了提高服务端HTTP性能而引入的一种机制,第一次访问静态资源文件时同时把文件数据缓存在内存中,下次直接读取内存即可,由于存在内存和文件系统一致性问题,故引入文件系统事件订阅来让文件系统监视和主动通知服务端清理缓存。

静态资源文件缓存管理分三个阶段。

hetao_htmlcache_read_file_and_registe_inotify.png

创建缓存阶段:

  1. 浏览器发送HTTP请求给hetao
  2. hetao向文件缓存查询,没有找到缓存的文件数据。
  3. hetao向文件系统读取文件数据。
  4. hetao把文件数据写入缓存。
  5. hetao向文件系统订阅注册该文件变动事件。
  6. hetao返回文件数据响应给浏览器。

hetao_htmlcache_read_cache.png

使用缓存阶段:

  1. 浏览器发送HTTP请求给hetao
  2. hetao向文件缓存查询,找到缓存的文件数据。
  3. hetao返回文件数据响应给浏览器。

hetao_htmlcache_handler_fs_event_and_remove_inotify.png

清理缓存阶段:

  1. 文件系统监视每一个订阅注册文件。
  2. 一旦某个文件发生变动文件系统主动通知hetao
  3. hetao清理缓存中该文件数据。

4.7. SOCGI

hetao_process_http_request.png

在HTTP请求处理过程中,用户希望用自己的逻辑替代Web服务器的逻辑,hetao支持搭载应用动态库,在HTTP请求处理的某个环节,如果应用动态库中存在相应函数,则执行该函数,替代hetao实现细节。这些函数分为两类:入口函数和HTTP信息API函数,入口函数又分生命周期管理函数、和HTTP处理环节入口函数。

4.7.1. 生命周期管理函数

InitHttpApplication用于Web服务器启动时装载应用动态库后调用,一般做创建业务环境。

CleanHttpApplication用于Web服务器停止时卸载应用动态库前调用,一般做销毁业务环境。

4.7.2. HTTP处理环节入口函数

RedirectHttpDomain用户自定义域名重定向逻辑。

RewriteHttpUri用户自定义重写URI逻辑。

BeforeProcessHttpResourceHTTP请求处理前用户自定义逻辑,比如无HTTP体报错信息的HTTP请求头选项通证权限检查逻辑。

ProcessHttpResource用户HTTP自定义请求处理逻辑,比如报错信息方HTTP体的HTTP请求头选项通证权限检查逻辑。

SelectForwardServer用户自定义选择下游服务器。(待实现)

CallHttpApplication调用用户应用动态库逻辑(业务处理逻辑)。

GetHttpResource用户自定义组织HTTP资源文件数据逻辑。

AfterProcessHttpResourceHTTP请求处理后用户自定义逻辑。

4.7.3. HTTP信息API函数

SOCGISetUserDataSOCGIGetUserData用于向HTTP上下文环境中设置和取出用户自定义数据,在动态库实例多个函数中共享信息时使用。

SOCGIGetConfigPathfilename用于把Web服务器配置中的动态库附带配置文件名传递给应用。

SOCGIGetHttpHeaderPtr_METHODSOCGIGetHttpHeaderPtr_URISOCGIGetHttpHeaderPtr_VERSIONSOCGIQueryHttpHeaderPtrSOCGIGetHttpBodyPtr等用于让应用获取HTTP请求信息。

SOCGIFormatHttpResponse用于让应用组织HTTP响应信息。

4.8. REST

hetao在SOCGI层上又实现了REST应用控制器。

hetaoREST应用控制器用当前HTTP请求到预配置的REST配置表中查询符合HTTP方法、URI条件的路由条目,调用对应的入口函数实现业务处理逻辑。业务开发方只要直接编写业务逻辑代码,通过配置挂接入口即可,分拣路由由REST应用控制器负责。

4.8.1. RESTful信息API函数

RESTCreateRestServiceControler用于根据路由配置表构造一个RESTful控制器。

RESTDispatchRestServiceControler用于根据当前HTTP请求和路由配置表分拣到对应的业务逻辑入口。

RESTDestroyRestServiceControler用于销毁RESTful控制器。

HttpApplicationContext用于在业务逻辑中获取HTTP上下文环境。

RESTGetHttpMethodPtrRESTGetHttpUriPtrRESTGetHttpUriPathsCountRESTGetHttpUriPathPtrRESTGetHttpUriQueriesCountRESTGetHttpUriQueryKeyPtrRESTGetHttpUriQueryValuePtrRESTGetHttpRequestBodyPtr等用于RESTful代码中让应用获取HTTP请求信息。

RESTFormatHttpResponse用于RESTful代码中让应用组织HTTP响应信息。

4.9. 附加选项

4.9.1. TCP选项

如果启用配置项tcp_options.nodelay(0为禁用,1为启用;默认启用),一旦有数据从hetao提交到操作系统就会立即发送网络,能大幅降低TCP通讯延迟,如果禁用,将等待一会儿凑后面的数据合并延迟发送,能增加系统吞吐量。

如果启用配置项tcp_options.linger(-1为禁用,大于等于0为启用;默认禁用),当发送缓冲区还有数据待发送时,禁用会等待发送完再关闭TCP,如果启用则会等待一段时间。

4.9.2. HTTP选项

如果启用配置项http_options.compress_on(0为禁用,1为启用;默认启用),按浏览器建议做压缩,否则无视浏览器建议统统不压缩。

hetao作为服务端接收发送两个字节之间不能停顿http_options.timeout(默认为30)秒,否则超时断开。

hetao作为服务端处理整个HTTP请求和响应总耗时不能大于http_options.elapse(默认为60)秒,否则超时断开。

hetao作为客户端做代理转发时一旦连不上下游服务器,就禁用该服务器http_options.forward_disable(默认为60)秒。

4.10. 侦听轮转

在低版本操作系统中,多个进程/线程在事件总线上同时等待侦听事件会造成惊群现象,hetao为了避免这个问题设计了侦听轮转机制:每次只有一个工作进程处于接受下一批TCP新连接状态,处理完后挑选一个目前HTTP会话最少的工作进程担此重任。这样还能使各个工作进程处理的HTTP会话数更均衡。

4.11. 安全机制

4.11.1. 文件系统限制

hetao对缓存的静态资源文件大小存在限制,当文件大小小于limits.max_file_cache(默认1MB)才予以缓存,防止大文件占用大内存。

4.11.2. TCP限制

hetao对累计最多HTTP会话存在限制,当保持的HTTP会话数量到达limits.max_http_session_count时会拒绝TCP新连接。

如果启用配置项limits.max_connections_per_ip(-1为禁用;默认禁用),将限制每个IP最多建立HTTP会话数量。

4.11.3. HTTP限制

如果启用配置项limits.max_headers_count(0为禁用;默认128),每个HTTP请求报文的头选项数量不能超过该配置值,防御请求者恶意构造的HTTP报文。

如果启用配置项limits.max_headers_length(0为禁用;默认4KB),每个HTTP请求报文的头大小不能超过该配置值,防御请求者恶意构造的HTTP报文。

如果启用配置项limits.max_header_content_length(0为禁用;默认4MB),每个HTTP请求报文的体大小不能超过该配置值,防御请求者恶意构造的HTTP报文。

4.12. 负载均衡算法

目前hetao支持两种负载均衡算法:轮询算法和最少连接数算法。

要开启负载均衡,在配置文件listen.website.forward.forward_type配置要代理转发下游服务器的HTTP资源文件扩展名。

4.12.1. 轮询算法

listen.website.forward.forward_rule配置轮询算法"R",在listen.website.forward.forward_server配置下游服务器地址数组,当代理转发触发时,hetao将依次轮询挑选下游服务器,第一次挑第一个,第二次挑第二个,挑完最后一个后重新挑第一个。

4.12.2. 最少连接数算法

listen.website.forward.forward_rule配置轮询算法"L",在listen.website.forward.forward_server配置下游服务器地址数组,当代理转发触发时,hetao将挑选一个当前保持转发连接最少的一个下游服务器。

4.13. 热重载

hetao_restart_graceful.png

热重载可以实现hetao使用新版主程序或修改后的配置文件工作而不用对外停止服务。

其技术原理为发送信号SIGUSR2给老版监视进程,老版监视进程复制成一个新进程,重新装载配置文件,新进程的代码映像来自于新版主程序文件,如果有相同侦听端口也会被继承下来,然后新版监视进程照旧创建工作进程组,这时新来两版监视器和工作进程同时工作,确认新版工作正常后发送信号SIGTERM给老版监视进程,让其和老版工作进程结束。期间始终对外提供服务,没有无服务时间窗口。

5. 跨平台Windows实现

hetao的官方Windows工程构建IDE是VS2008,解决方案文件src/vc2008/vc2008.sln负责构建主程序文件hetao、所有依赖库和辅助工具,解决方案文件test/vc2008/vc2008.sln负责构建所有测试用应用动态库。

由于Windows和Linux底层提供的功能和接口差异,hetao的代码实现上采用条件编译方式兼顾两种操作系统的接口调用,以下为主要功能的接口差异:

功能 Linux实现 Windows实现
注册为服务 (无) OpenSCManager,CreateService,
ChangeServiceConfig2
创建进程 fork hetao()->
CreateProcess("--child")->
hetao
结束合并进程 waitpid OpenProcess,
WaitForSingleObject
创建线程 pthread_create CreateThread
事件总线 epoll_create,epoll_ctl CreateIoCompletionPort,
WSAIoctl
通讯收发 socket,recv,send,close WSASocket,
WSARecv,
WSASend,
closesocket
文件系统事件接口 inotify ReadDirectoryChangesW
共享内存 shmget CreateFileMapping,
MapViewOfFile
装载动态库 dlopen,dlsym,dlclose LoadLibrary,
GetProcAddress,
FreeLibrary

6. HTTPS实现

hetaoLinux版和Windows版都使用OpenSSL来实现HTTPS的SSL握手。

hetao作为服务端接受客户端SSL握手时,调用函数SSL_new创建SSL环境,接着调用函数SSL_set_fd关联TCP套接字和SSL环境,然后调用函数SSL_set_accept_state设置我是服务端,进入基于事件循环的迭代非堵塞握手处理流程。

基于事件循环的迭代非堵塞握手处理流程中任何方向的握手分组都调用函数SSL_do_handshake来处理,然后调用函数SSL_get_error询问下一步该发送还是接收,注册进事件总线,迭代等待处理事件,直到SSL握手最终完成。对方交换证书分组时调用函数SSL_get_peer_certificate获取。

hetao作为客户端做HTTP请求转发时,调用函数SSL_new创建SSL环境,接着调用函数SSL_set_fd关联TCP套接字和SSL环境,然后调用函数SSL_set_connect_state设置我是客户端,进入基于事件循环的迭代非堵塞握手处理流程。

7. minihetao

hetao提供了一个简易版软件,无需配置文件的运行起一个静态资源Web服务器minihetao,便于知道网站根目录后快速用浏览器访问网站。

Linux版minihetao运行命令行语法为:

USAGE : minihetao wwwroot

Windows版minihetao有一个对话框窗口

minihetao.png

点击选定一个网站根目录wwwroot后,点击按钮"Running"即可快速创建一个Web服务端,点击按钮"Stop"停止,按钮"Hide"缩小窗口到系统托盘,按钮"Exit"退出。

除了通过窗口创建Web服务端外,还有另一种更快速的创建方法。点击按钮"Registe folder popup-menu"注册monihetao到右键菜单中,然后在“我的电脑”里任意目录点击右键弹出快捷菜单,选择“minihetao”,就自动打开minihetao窗口,自动填充wwwroot,自动点击按钮“Running”,用户要做的只有打开浏览器访问网址“http://localhost/”,是不是很方便。

8. 辅助工具

一款优秀的软件肯定需要完备的辅助工具,hetao自带了两个辅助工具用于配置文件格式检查和应用动态库符号完整性检查。

8.1. 配置文件校验工具hetaocheckconf

Nginx怪异的需要额外学习成本的配置文件语法格式不同,hetao配置文件采用标准JSON格式,但为了在启动或热重载前发现配置错误,hetao提供了辅助工具hetaocheckconf来校验配置文件语法合法性,其运行命令行语法为:

USAGE : hetaocheckconf hetao.conf

8.2. 应用动态库校验工具hetaocheckso

hetao支持装载应用动态库用于HTTP请求时的业务逻辑处理,为了避免在运行时爆出动态库符号不完整问题,工具hetaocheckso可以在开发期检查动态库,毕竟构建时发现问题总好于测试业务时。

在开发期构建完应用动态库后立即调用工具hetaocheckso尝试挂接应用动态库,Linux上可以嵌入到makefile中,Windows可以配置到IDE中。

hetaocheckso运行命令行语法为:

USAGE : hetaocheckso *.socgi [-r]

*.socgi就是动态库文件名,无论绝对路径还是相对路径,反正站在当前目录里能让hetaocheckso打开应用动态库就行。

-r是可选参数,当存在时,如果检查应用动态库不通过(比如缺少符号),立即删除该动态库。这个参数是为了与构建工具配合,做到构建物提交出去前必须经过符号完整性校验。

C
1
https://gitee.com/calvinwilliams/hetao.git
git@gitee.com:calvinwilliams/hetao.git
calvinwilliams
hetao
hetao
release

搜索帮助