在Linux下,eJet Web做事器编译成动态库或静态库的大小约为300K,可集成嵌入到任何运用程序中,增加运用程序利用HTTP通信和做事承载的能力,使其具备像Nginx做事器一样强大的Web功能。
eJet Web做事器完备构建在ePump框架之上,利用ePump框架的多线程事宜驱动模型,实现完全的HTTP要求<-->HTTP相应事务流程。eJet并没有创建进程或线程,利用ePump框架的事宜驱动多线程,高效地利用做事器的CPU处理能力。
eJet吸收和处理各TCP连接上的HTTP要求头和要求体,经由解析、校验、关联、实例化等处理,实行HTTP要求,或获取Web做事器特定目录下的文件,或代理客户端发起向源HTTP做事器的要求,或将HTTP要求通过FastCGI接口转发到CGI做事器,或将客户端HTTP要求交给上层设置的回调函数处理等。所有处理结果,终极以HTTP相应办法,包括HTTP相应头和相应体,通过客户端建立的TCP连接,返回给客户端。该TCP连接可以Pipe-line办法连续发送和吸收多个HTTP要乞降相应。
eJet做事器供应了作为Web做事器所需的其他各项功能,包括基于TLS/SSL的安全和加密传输、虚拟主机、资源位置Location的各种匹配策略、对要求URI实行动态脚本指令(包括rewrite、reply、return、try_files等)、在配置文件中利用HTTP变量、正向代理和反向代理、HTTP Proxy、FastCGI、HTTP Proxy Cache功能、HTTP Tunnel、MultiPart文件上传、动态库回调或接口函数回调机制、HTTP日志功能、CDN分发等。
eJet Web做事器采取JSon格式的配置文件,进行系统配置管理。对JSon语法做了一定的扩展,使得JSon支持include文件指令,支持嵌入Script脚本程序措辞。利用扩展JSon功能的配置文件,可更加灵巧、方便地扩展Web做事功能。
eJet系统大量采取了Zero-Copy、内存池、缓存等技能,来提升Web做事器处理性能和效率,加快了要求相应的处理速率,支撑更大规模的并发处理能力,支持更大规模的网络吞吐容量等。
eJet Web做事器既可以面向程序员、系统架构师供应运用程序开拓接口或直接嵌入到现有系统中,也可以面向运维工程师支配完备类似Nginx Web做事器、Web Cache、CDN回源等商业做事系统,还是面向程序员供应学习、研究开拓框架、通信系统等的空想平台。
开拓eJet Web做事器的原则是尽可能不依赖于第三方代码和库,降落版权和繁芜支配等成分带来的潜在风险。系统利用的第三方代码或库紧张为:OpenSSL库、Linux系统自带的符合POSIX标准的正则表达式regex库。gzip压缩须要依赖zlib开源库,目前没有添加进来,以是eJet Web做事器暂时不供应gzip、deflate的压缩支持。
二. JSon格式的配置文件2.1 JSON语法特点JSON的全称是JavaScript Object Notation,是一种轻量级的数据交流格式。JSON的文本格式独立于编程措辞,采取name:value对存储名称和数据,可以保存数字、字符串、逻辑值、数组、工具等数据类型,是空想的数据交流语法格式,简洁干练,易于扩展、阅读和编写,也便于程序解析和天生。
正是由于JSon语法的大略和强扩展性、采取可保存各种数据类型的name/value对语法、可嵌套JSON子工具等特性,与配置文件的配置属性特殊吻合,以是,eJet系统利用JSon格式来保存、通报、解析系统配置文件。
2.2 eJet配置文件对JSON的扩展2.2.1 分隔符eJet系统利用adif中的JSon库来解析、访问配置文件信息。JSon语法缺省格式以冒号(:)来分隔name和value,以单引号(')或双引号(")来包含name和value串,以逗号(,)作为name/value对的分隔符,以中括号[]表示数组,以大括号{}表示工具。
eJet系统采取JSon作为配置文件语法规范,为了兼容传统配置文件的编写习气,将JSon根本语法做了一些扩展,即分隔name与value的冒号(:)换成即是号(=),分隔name/value对之间的逗号(,)换身分号(;),其他根本语法不变。
2.2.2 include指令由于配置信息数据较大,须要利用不同的文件来保存不同的配置信息,借鉴C措辞/PHP措辞的include宏指令,eJet系统的JSon语法引入了include指令。扩展语法中将把"include"作为JSon语法的关键字,不会被当做工具名称和值内容来处理,而是作为嵌入其余一个文件到当前位置进行后续处理的分外指令。其语法规范如下:
include <配置文件名>;
解析JSon内容时,如果碰着include指令,就将include指令后面的文件内容加载到当前指令位置,作为当前文件内容的一部分,进行解析处理。
2.2.3 单行注释和多行注释为了增加配置文件中代码的可读性,须要对干系的定义添加详细解释、表明等内容,方便利用职员快速阅读和理解。
为支持注释功能,eJet系统的配置文件对JSON语法做了相应扩展,增加了单行注释符号#和多行注释(/ /),其语法规范如下:
# 这是单行注释,如果井号(#)不在JSon某个Key-Value对的引号里面,那么以井号开头,井号后面的内容都是注释/ 把稳:多行注释因此连在一起的/和开始 以连在一起的和/结尾,中间的内容都是注释 多行注释开闭符号,必须不能在Key-Value对的引号里面 /
注释的内容在解析时直接忽略跳过,不会被系统解析和处理。
2.2.4 script语法利用JSON格式的数据都是由name/value对构成,eJet系统中须要在配置文件中支持Script脚本程序,灵巧动态地处理HTTP要求。
eJet配置文件对JSON语法格式扩展了一种固定名称的script工具,将名称"script"作为分外工具的名称关键字,即以script为名称的工具,其内容不能作为JSON子工具处理,而是作为Script脚本程序内容,存放在工具名为script的工具中。其语法规范如下:
script = { if ($request_uri ~ '^/topic/[0-9]()/(.)\.mp4$') { set $video_flag 1; }};
在同一个JSon工具下,可以有多个script工具,自动构成script工具数组。
其余,利用分外的开闭标签<script>和</script>,也可以定义脚本程序。在这两个开闭标签中间的内容,即是Script脚本程序,并将这些内容存储到配置文件定义的任意name名称工具中,其语法规范如下:
cache file = <script> if ($request_uri ~ 'laoooke') return "${host_name}_${server_port}${req_path_only}${req_file_only}"; else if (!-f $root$request_path) { return "${host_name}_${server_port}${req_path_only}${index}"; } else if (!-x $root$request_path) { return "$root$request_path is not an executable file"; } else return "${request_header[host]}${req_path_only}else.html"; </script>;
这样,"cache file"工具的内容便是一段脚本程序,须要在阐明实行到这里时,才真正具有实际数据。
三. eJet资源管理架构3.1 三层资源定位架构eJet Web做事器的资源管理构造分成三层:
HTTP监听做事HTTPListen - 对应的是监听本地IP地址和端口后的TCP连接HTTP虚拟主机HTTPHost - 对应的是要求主机名称domainHTTP资源位置HTTPLoc - 对应的是主机下的各个资源目录一个eJet Web做事器可以启动一个到多个监听做事HTTPListen,一个监听做事下可以配置一个到多个HTTP虚拟主机,一个虚拟主机下可以配置多个资源位置HTTPLoc。这里的‘多个’没有数量限定,取决于系统的物理和内核资源限定。
3.2 HTTP监听做事 - HTTPListenHTTP监听做事HTTPListen是指eJet Web做事器在启动时,须要绑定本地某个做事器IP地址和某个端口后,启动TCP监听做事,期待吸收客户端发起TCP连接和HTTP要求数据,每个接管的HTTPCon连接一定属于某个HTTP监听做事HTTPListen。严格来说,HTTPListen卖力接管HTTPCon连接,并将要求数据存储到HTTPCon的吸收缓冲区,以是监听做事对应的是TC连接资源管理,即对应的是要求资源的domain和端口。
HTTP监听做事的配置信息格式参考如下:
listen = { local ip = ; #192.168.1.151 port = 443; forward proxy = on; ssl = on; ssl certificate = cert.pem; ssl private key = cert.key; ssl ca certificate = cacert.pem; request process library = reqhandle.so script = { #reply 302 https://ke.test.ejetsrv.com:8443$request_uri; addResHeader X-Nat-IP $remote_addr; } host = {.....} host = {.....} host = {.....}}
一台物理做事器可以安装多个网卡,每个网卡配置一个独立IP地址,HTTP监听做事可以监听某一个IP地址上的某个端口,也可以监听所有IP地址上的同一个端口。能启动监听做事的端口数量理论上是65536个,个中小于1024的端口须要有root超户权限才能监听。
HTTP监听做事HTTPListen依赖于底层ePump框架的eptcp_mlisten接口函数,通过该接口,让每一个epump监听线程都去监听指定IP地址和端口上的连接要乞降数据要求做事。对付支持REUSEPORT的操作系统内核,大量客户端发起的并发连接,将会通过内核accept系统调用均衡地分摊到各epump线程处理,对付不支持REUSEPORT的操作系统,ePump框架卖力大并发连接在各监听线程间的负载均衡。
HTTP监听做事HTTPListen可以设置当前监听为须要SSL的安全连接,并配置SSL握手所需的私钥、证书等。配置为SSL安全连接监听做事后,客户端发起的HTTP要求都必须因此https://开头的URL。
在HTTP监听做事HTTPListen里,可以设置Script脚本程序,实行各种针对要求数据进行预判断和预处理的指令。这些脚本程序的实行机遇是在收到完全的HTTP要求头后进行的。
eJet系统供应了动态库回调机制,利用动态库回调,既可以扩展eJet Web做事器能力,也可以将小型运用系统附着在eJet Web做事器上,处理客户端发起的HTTP要求。
HTTP监听做事HTTPListen下可管理多个虚拟主机HTTPHost,采取主机名称为索引主键的hashtab来管理下属的虚拟主机表。当当前监听做事的端口收到TCP要乞降数据后,根据Host要求头的主机名称,来精确匹配定位出该要求的HTTP虚拟主机HTTPHost。
3.3 HTTP虚拟主机 - HTTPHost在HTTPListen监听做事下,可以配置多个虚拟主机,虚拟主机HTTPHost是eJet Web做事器资源管理体系的第二层,将HTTPCon缓冲区的数据进行解析,创建HTTPMsg来保存解析后的HTTP要求数据,HTTP协议规范中,要求头Host携带的值内容是URL中domain信息,以是HTTP虚拟主机HTTPHost,对应的便是要求域名,或者便是一个网站。一个监听做事HTTPListen下可以投止大量的通过虚拟主机HTTPHost来管理的网站。
HTTP虚拟主机的配置信息格式参考如下:
host = { host name = ; #www.ejetsrv.com type = server | proxy | fastcgi; gzip = on; ssl certificate = cert.pem; ssl private key = cert.key; ssl ca certificate = cacert.pem; script = { #reply 302 https://ke.test.ejetsrv.com:8443$request_uri; addResHeader X-Nat-IP $remote_addr; } error page = { 400 = 400.html; 504 = 504.html; root = /opt/ejet/errpage; } root = /home/hzke/sysdoc; location = {...} location = {...} location = {...}}
HTTP虚拟主机的名称一样平常是域名格式,即多级名称体系,包含顶级域名、二级域名、三级域名等,通过DNS系统,将该域名解析到当前eJet Web做事器所在的IP地址上,如果在该IP地址上启动HTTPListen做事,那么所有利用该域名的要求都会指向到对应的HTTPHost虚拟主机。
eJet系统根据功能做事形式,对虚拟主机定义了几种类型:Server、Proxy、FastCGI等,这几种类型可以同时并存,可或在一起。
虚拟主机HTTPHost下可以设置资源的缺省目录,下属的资源位置HTTPLoc都可以复用虚拟主机的缺省目录。
如果当前虚拟主机HTTPHost的上级监听做事是建立在安全连接SSL上,那么在有多个网站即多个虚拟主机情形下,须要为每个网站配置属于该网站域名的证书、私钥等安全身份标识信息,客户端在向同一个监听做事发送要求后,采取TLS SNI机制和eJet中实现的SSL域名选择回调,来完成域名和证书的选择。
HTTPHost虚拟主机下可以设置Script脚本程序,虚拟主机下的脚本程序被实行机遇是在创建HTTPMsg实例,并设置完DocURI后开始实行资源位置实例化流程,在该流程等分别实行HTTPListen的Script脚本、HTTPHost的Script脚本、HTTPLoc的Script脚本。脚本程序的实行按照上述优先级来进行,利用脚本程序的指令来预处理HTTP要求的各种数据。
一个虚拟主机HTTPHost下可以配置多个资源位置HTTPLoc,代表访问当前域名下的不同目录。虚拟主机HTTPHost采取多种办法管理下属的资源位置HTTPLoc实例,紧张包括三种:
精确匹配要求路径的虚拟主机表 - 以要求路径名称为索引的资源位置索引表对要求路径前缀匹配的虚拟主机表 - 以要求路径前缀名称为索引的资源位置字典树对要求路径进行正则表达式运算的虚拟主机表 - 对正则表达式字符串为索引建立的资源位置列表进入当前虚拟主机后,到底采取哪个资源位置HTTPLoc,匹配规则和顺序是按照上述列表的排序来进行的,首先根据HTTP要求的路径名在资源位置索引表中精准匹配,如果没有,则对要求路径名的前缀在资源位置字典树中进行匹配检索,如果还没有匹配上,末了对资源位置列表中的每个HTTPLoc,利用其正则表达式字符串,去匹配当前要求路径名,如果还是没有匹配的资源位置HTTPLoc,那么利用当前虚拟主机的缺省资源位置。
3.4 HTTP资源位置 - HTTPLocHTTP资源位置HTTPLoc代表的是要求资源在某个监听做事下的某个虚拟主机里的目录位置,HTTPLoc代表的是要求路径,根据HTTPMsg中的客户端要求数据,终极基于各种资源匹配规则,找到HTTPListen、HTTPHost、HTTPLoc后,基本确定了当前要求的资源位置、处理办法等。一个网站对应的虚拟主机下,可以有多种功能和资源类别的资源位置HTTPLoc,如图像文件放置在image为根的目录下,PHP文件须要采取FastCGI转发给php-fpm阐明器等。
HTTP资源位置的配置信息格式参考如下:
location = { type = server; path = [ "\.(h|c|apk|gif|jpg|jpeg|png|bmp|ico|swf|js|css)$", "~" ]; root = /opt/ejet/httpdoc; index = [ index.html, index.htm ]; expires = 30D; cache_file = <script> if ($request_uri ~ 'laoke') return "${host_name}_${server_port}${req_path_only}${req_file_only}"; else if (!-f $root$request_path) { return "$root$request_path is not a regular file"; } else if (!-x $root$request_path) { return "$root$request_path is not an executable file"; } else return "${request_header[host]}${req_path_only}else.html"; </script>;}location = { path = [ '^/view/([0-9A-Fa-f]{32})$', '~' ]; type = proxy; passurl = http://cdn.ejetsrv.com/view/$1; root = /opt/cache/; cache = on; cache file = /opt/cache/${request_header[host]}/view/$1;}location = { type = fastcgi; path = [ "\.(php|php?)$", '~']; passurl = fastcgi://localhost:9000; index = [ index.php ]; root = /opt/ejet/php;}location = { path = [ '/' ]; type = server; script = { try_files $uri $uri/ /index.php?$query_string; }; index = [ index.php, index.html, index.htm ];}
HTTP资源位置HTTPLoc是通过路径名path和匹配类型matchtype来作为其标识,路径名为配置中设置的名称,客户端要求的路径名通过匹配类型定义的匹配规则来跟设置的路径名进行匹配,如果符合匹配,则该要求利用此资源位置HTTPLoc。
匹配规则matchtype一样平常定义在配置文件中path数组里的第二项,分为如下几种:
精准匹配,利用即是号'='前缀匹配,利用'^~'这两个符号区分大小写的正则表达式匹配,利用'~'符号不区分大小写的正则表达式匹配,利用'~'这两个符号通用匹配,利用'/'符号,如果没有其他匹配,任何要求都会匹配到匹配的优先级顺序为: (location =) > (location 完全路径) > (location ^~ 路径) > (location , 正则顺序) > (location 部分起始路径) > (/)
eJet系统根据功能做事形式,对资源位置HTTPLoc定义了几种类型:Server、Proxy、FastCGI等,常日情形下,一个资源位置HTTPLoc只属于一种类型。
HTTP资源位置HTTPLoc都须要一个缺省的根目录,指向当前资源所在的根路径,客户端要求的路径都是相对付当前HTTPLoc下的root跟目录来定位文件资源的。对付Proxy模式,根目录一样平常充当缓存文件的根目录,即须要对Proxy代理要求回来的内容缓存时,都保存在当前HTTPLoc下的root目录中。
每个HTTPLoc下都会有缺省文件选项,可以配置多个缺省文件,一样平常设置为index.html等。利用缺省文件的环境是客户端发起的要求只有目录形式,如http://www.xxx.com/,这时该要求访问的是HTTPLoc的根目录,eJet系统会自动地依次探求当前根目录下的各个缺省文件是否存在,如果存在就返回缺省文件给客户端。不过须要把稳的是,eJet系统中这个流程是在设置DocURI时处理的。
HTTP资源位置如果是Proxy类型或FastCGI类型,则必须配置转发地址passurl,转发地址passurl一样平常都为绝对URL地址,含有指向其他做事器的domain域名,passurl的形式取决HTTPLoc资源类型。
反向代理(Reverse Proxy)便是将HTTPLoc的资源类型设置为Proxy模式,通过设置passurl指向要代理的远程做事器URL地址,来实现反向代理功能。在反向代理模式下,passurl可以是含有匹配结果变量的URL地址,这个地址指向的是待转发的下一个Origin做事器,匹配变量如果为1、1、2等数字变量,即表示基于正则表达式匹配路径时,把第一个或第二个匹配字符串作为passurl的一部分。当然passurl可以包含任何全局变量或配置变量,利用这些变量可以更灵巧方便地处理转发数据。
在反向代理模式下,HTTPLoc资源位置下有一个cache开关,如果设置cache=on即打开Cache功能,则须要在当前HTTPLoc下设置cachefile缓存文件名。对付不同的要求地址,cachefile必须随着要求路径或参数的变革而变革,以是cachefile的取值设置须要采取HTTP变量,或者利用Script脚本来动态打算cachefile的取值。
HTTPLoc下一样平常都会支配Script脚本程序,包括rewrite、reply、try_files等,根据要求路径、要求参数、要求头、源地址等信息,决定当前资源位置是否须要重写、是否须要转移到其他地址处理等。
四. HTTP变量4.1 HTTP变量的定义HTTP变量是指在eJet Web做事器运行期间,能动态地访问HTTP要求、HTTP相应、HTTP全局管理等实例工具中的存储空间里的数据,或者访问HTTP配置文件的配置数据等等,针对这些存储空间的访问,而抽象出来的名称叫做HTTP变量。
变量的引用必须以开头,后跟变量名,如果变量名后面还有连续紧随的其他字符串,则需用{}来包括住变量名,其基本格式为:开头,后跟变量名,如果变量名后面还有连续紧随的其他字符串,则需用来包括住变量名,其基本格式为:变量名称, {变量名称},变量名称,{ 变量名称 },等等
4.2 HTTP变量的运用利用HTTP变量的场景紧张在JSon格式的配置文件中,给各个配置项目增加动态的可编程接口,就须要基于不同的HTTP要求的信息,做判断、比较、赋值、拷贝、串接等操作,这些都离不开变量,须要不同的变量名去访问不同HTTP要求中的不同信息内容,通过配置中利用变量:访问变量的值,进行条件判断、比较、匹配、加减乘除、赋值等。变量的利用样例可参考如下:
access log = { log2file = on; log file = /var/log/access.log; format = [ '$remote_addr', '-', '[$datetime[stamp]]', '"$request"', '"$request_header[host]"', '"$request_header[referer]"', '"$http_user_agent"', '$status', '$bytes_recv', '$bytes_sent' ];}script = { reply 302 https://ke.test.ejetsrv.com:8443$request_uri;}cache file = /opt/cache/${request_header[host]}/view/$1;params = { SCRIPT_FILENAME = $document_root$fastcgi_script_name; QUERY_STRING = $query_string; REQUEST_METHOD = $request_method; CONTENT_TYPE = $content_type; CONTENT_LENGTH = $content_length;}script = { if ($query[fid]) cache file = $real_path$query[fid]$req_file_ext; else if ($req_file_only) cache file = $real_path$req_file_only; else if ($query[0]) cache file = ${real_path}${query[0]}$req_file_ext; else cache file = ${real_path}index.html;}
4.3 HTTP变量的类型和利用规则
eJet系统中,共定义了四种HTTP变量类型,分别为:
匹配变量 - 基于资源位置HTTPLoc模式串匹配HTTP要求路径时匹配串,通过数字变量来访问,如1,1,2等;局部变量 - 由script脚本在实行过程中用set指令或赋值符号“=”设置的变量;配置变量 - 配置文件中Listen、Host、Location下定义的JSon Key变量,以系统会利用到的常量定义为主;参数变量 - 变量名称由系统预先定义、但值内容是在HTTPMsg创建后被赋值的变量,参数变量的值是只读不可写。变量的利用规则符合高等措辞的约定,对付同名变量,取值时优先级顺序为: 匹配变量 >匹配变量>局部变量 > 配置变量 >配置变量>参数变量
HTTP变量的值类型是弱类型,根据赋值、运算的规则等高下文环境的变革,来确定被利用时变量是数字型、字符型等。除了匹配变量外,其他变量的名称必须是大小写字母和下划线_组合而成,其他字符涌如今变量名里则该变量一定是非法无效变量。变量的定义很大略,前面加上美元符号$,后面利用变量名称,系统就会认为是HTTP变量。美元符号后的变量名称也可以通过大括号{}来跟跟其他字符串区隔。
如果变量的值内容包含多个,那么该变量是数组变量,数组变量是通过中括号[]和数字下标序号来访问数组的各个元素,如$query[1]访问是要求参数中的第一个参数的值。
匹配变量的名称为数字,以美元号冠头,如冠头,如1,$2...,其数字代表的是利用HTTPLoc定义的路径模式串,去匹配当前HTTP要求路径时,被匹配成功的多个子串的数字序号。匹配变量的寿命周期是HTTPMsg实例化成功即准确找到HTTPLoc资源位置实例后开始,到HTTP相应被成功地发送到客户端后,HTTPMsg被销毁时为止。
局部变量的名称由字母和下划线组成,是script脚本在实行过程中用set指令或赋值符号“=”设置的变量,其寿命周期是从变量被创建之后到该HTTPMsg被销毁这段期间,而HTTPMsg则是用户HTTP要求到达时创建,成功返回Response后被摧毁。
配置变量是JSon格式的配置文件中定义的Key-Value对中,以Key为名称的变量,变量的值是设置的Value内容。在配置文件中位于Location、Host、Listen下定义的Key-Value赋值语句对,左侧为变量名,右侧为变量值,用$符号可以直接引用这些变量定义的内容;在Listen、Host、Location下定义的配置变量,紧张因此系统中可能利用到的常量定义为主,这些常量定义也可以利用script脚本来动态定义其常量值,此外,用户可以额外定义系统配置中非缺省常量,我们称之为动态配置变量。
参数变量是系统预定义的有固定名称的一种变量类型,参数变量一样平常指向HTTP要求的各种信息、eJet系统定义的全局变量等。参数变量的名称是eJet系统预先定义并公布,但大部分变量的内容是跟HTTP要求HTTPMsg干系的,即不同的要求HTTPMsg,参数变量名的值也是随着变革的。一样平常哀求,参数变量是只读不可写变量,即参数变量的值不能被脚本程序改变,只能读取访问。
4.4 预定义的参数变量列表和实现事理比较其他三种变量,参数变量是被利用最多、最有访问代价的变量,参数变量是系统预先定义的固定名称变量,变量的值是随着HTTP要求HTTPMsg的不同而不同。通过参数变量,配置文件中可以根据要求的信息,灵巧动态地决定干系配置选项的赋值内容,从而扩展eJet做事器的能力,减少因额外功能扩展升级eJet系统的定制开销。
参数变量一样平常由eJet系统预先定义发布,其变量的值内容是跟随HTTP要求HTTPMsg的变革而变革,但变量名称是全局统一通用,以是参数变量也有时称为全局变量。
eJet系统预定义的参数变量如下:
remote_addr - HTTP要求的源IP地址remote_port - HTTP要求的源端口server_addr - HTTP要求的做事器IP地址server_port - HTTP要求的做事器端口request_method - HTTP要求的方法,如GET、POST等scheme - HTTP要求的协议,如http、https等host_name - HTTP要求的主机名称request_path - HTTP要求的路径query_string - HTTP要求的Query参数串req_path_only - HTTP要求的只含目录的路径名req_file_only - HTTP要求路径中的文件名称req_file_base - HTTP要求路径中的文件基本名req_file_ext - HTTP要求路径中文件扩展名real_file - HTTP要求对应的真实文件路径名real_path - HTTP要求对应的真实文件所在目录名bytes_recv - HTTP要求吸收到的客户端字节数bytes_sent - HTTP相应发送给客户真个字节数status - HTTP相应的状态码document_root - HTTP要求的资源位置根路径fastcgi_script_name - HTTP要求中经由脚本运行后的DocURI的路径名content_type - HTTP要求的内容MIME类型content_length - HTTP要求体的内容长度absuriuri - HTTP要求的绝对URIuri - HTTP要求源URI的路径名request_uri - HTTP要求源URI内容document_uri - HTTP要求经由脚本运行后的DocURI内容request - HTTP要求行http_user_agent - HTTP要求用户代理http_cookie - HTTP要求的Cookie串server_protocol - HTTP要求的协议版本ejet_version - eJet系统的版本号request_header - HTTP要求的头信息数组,通过带有数字下标或要求头名称的中括号来访问cookie - HTTP要求的Cookie数组,通过带有数字下标或Cookie名称的中括号来访问query - HTTP要求的Query参数数组,通过带有数字下标或参数名称的中括号来访问response_header - HTTP相应的头信息数组,通过带有数字下标或相应头名称的中括号来访问datetime - 系统日期韶光数组,不带中括号是系统韶光,带createtime或stamp的中括号则访问HTTPMsg创建韶光和末了韶光date - 系统日期数组,同上time - 系统韶光,同上随着运用处景的扩展,根据须要还可以扩展定义其他名称的参数变量。总体来说,利用上述参数变量,基本可以访问HTTP要求干系的所有信息,能知足绝大部分场景的需求。
系统中预定义的参数变量,都是指向特定的根本数据构造的某个成员变量,在该数据构造实例化后,其成员变量的地址指针就会被动态地赋值给预定义的参数变量,从而将地址指针指向的内容关联到参数变量上。
在设置预定义参数变量名时,一样平常须要设置关联的数据构造、数据构造的成员变量地址或位置、成员变量类型(字符、短整数、整数、长整数、字符串、字符指针、frame_t)、符号类型、存储长度等,eJet系统中坚持一个这样的参数变量数组,分别完成参数变量数据的初始化,通过hashtab_t来快速定位和读写访问数组中的参数变量。
获取参数变量的实际值时,须要通报HTTPMsg这个数据构造的实例指针,根据参数变量名快速找到参数变量数组的参数变量实例,根据参数变量的信息,和传入的实例指针,定位到该实际成员变量的内存指针和大小,从内存中取出该成员变量的值。
五. HTTP Script脚本5.1 HTTP Script脚本定义eJet系统在配置文件上扩展了Script脚本措辞的语法定义,对JSon语法规范进行扩展,定义了一套符合JavaScript和C措辞的编程语法,并供应Script脚本阐明器,实现一定的编程和解释实行功能。
Script脚本是由一系列符合定义的语法规则而编写的代码语句组成,代码语句风格类似Javascript和C措辞,每条语句由一到多条指令构成,并以分号;结尾。
5.2 Script脚本嵌入位置HTTP Script脚本程序的嵌入位置,共有两种。第一种嵌入位置是在配置文件的Listen、Host、Location下,通过增加JSon工具script,将脚本程序作为script工具的内容,来实现配置文件中嵌入脚本编程功能。在这种位置中,插入script脚本代码的语法共定义了三种:
script = {....}; script = if()... else...; <script> .... </script>
其余一种嵌入Script脚本程序的位置,是在JSon中的Key-Value对中,在Value里增加分外闭合标签<script> Script Codes </script>,在标签里面嵌入Script脚本代码,实行完代码后返回的内容,作为Key的值,这种办法使得JSon规范中Key的值可以动态地由Script脚本程序打算得来。在Listen、Host或Location的常量赋值中,Value内容可以是script脚本,如
cache file = <script> if ()... return... </script>
对adif 根本库中的json.c文件做了修正扩展,使得Json工具都能支持script脚本定义的这几种语法,如果某个工具下有名称为script的数据项,就认为该数据项下的Value值为脚本内容。这就将名称script作为Json的缺省常量名称了,利用时轻易不要利用script作为变量名。
5.3 Script脚本范例HTTP Script脚本程序示例如下:
script = { if ($query[fid]) "cache file" = $req_path_only$query[fid]$req_file_ext; else if ($req_file_only) "cache file" = ${req_path_only}index.html; else "cache file" = $req_path_only$req_file_only; } cache file = <script> if ($query[fid]) return $req_path_only$query[fid]$req_file_ext; else if ($req_file_only) return ${req_path_only}index.html; else return $req_path_only$req_file_only; </script> <script> if ($query[fid]) "cache file" = $req_path_only$query[fid]$req_file_ext; else if ($req_file_only) "cache file" = ${req_path_only}index.html; else "cache file" = $req_path_only$req_file_only; </script> <script> if ($scheme == "http://") rewrite ^(.)$ https://$host$1; </script>
HTTP Script脚本程序的阐明实行,是在创建HTTPMsg实例并设置完DocURI后,开始实行资源位置实例化流程,在实例化过程中,分别实行HTTPListen的Script脚本、HTTPHost的Script脚本、HTTPLoc的Script脚本。
5.4 Script脚本语句script脚本是由一系列语句构成的程序,语法类似于JavaScript和C语音,紧张包括如下语句:
5.4.1 条件语句条件语句紧张以if、else if、else组成,基本语法为:
if (判断条件) { ... } else if (判断条件) { ... } else { ... }
判断条件至少包含一个变量或常量,通过对一个或多个变量的值进行判断或比较,取出结果为TRUE或FALSE,来决定实行分支,判断条件包括如下几种情形:
(a) 判断条件中只包含一个变量;(b) 判断条件中包含了两个变量;(c) 文件或目录属性的判断;判断比较操作紧张包括:
(a) 变量1 == 变量2,判断是否相等,两个变量值内容相同为TRUE,否则为FALSE(b) 变量1 != 变量2,判断不相等,两个变量值内容不相同为TRUE,否则为FALSE(c) 变量名,判断变量值,变量定义了、且变量值不为NULL、且变量值不为0,则为TRUE,否则为FALSE(d) !变量名,变量值取反判断,变量未定义,或变量值为NULL、或变量值为0,则为TRUE,否则为FALSE(e) 变量1 ^~ 变量2,变量1中的起始部分因此变量2开头,则为TRUE,否则为FALSE(f) 变量1 ~ 变量2,在变量1中查找变量2中的区分大小写正则表达式,如果匹配则为TRUE,否则为FALSE(g) 变量1 ~ 变量2,在变量1中查找变量2中的不区分大小写正则表达式,如果匹配则为TRUE,否则为FALSE(h) -f 变量,取变量值字符串对应的文件存在,则为TRUE,否则为FALSE(i) !-f 变量,取变量值字符串对应的文件不存在,则为TRUE,否则为FALSE(j) -d 变量,取变量值字符串对应的目录存在,则为TRUE,否则为FALSE(k) !-d 变量,取变量值字符串对应的目录存在,则为TRUE,否则为FALSE(l) -e 变量,取变量值字符串对应的文件、目录、链接文件存在,则为TRUE,否则为FALSE(m) !-e 变量,取变量值字符串对应的文件、目录、链接文件不存在,则为TRUE,否则为FALSE(n) -x 变量,取变量值字符串对应的文件存在并且可实行,则为TRUE,否则为FALSE(o) !-x 变量,取变量值字符串对应的文件不存在或不可实行,则为TRUE,否则为FALSE5.4.2 赋值语句赋值语句紧张由set语句构成,eJet系统中局部变量的创建和赋值是通过set语句来完成的。其语法如下:
set $变量名 value;
5.4.3 返回语句
返回语句也即是return语句,将script闭合标签内嵌入的Scirpt脚本代码实行运算后的结果,或Key-Value对中Value内嵌的脚本程序,阐明实行后的结果返回给Key变量,基本语法为:
return $变量名;return 常量;
其利用形态如下:
cache file = <script> if ($user_agent ~ "MSIE") return $real_file; </script>;
5.4.4 相应语句
相应语句也便是reply语句,实行该语句后,eJet系统将终止当前HTTP要求HTTPMsg的任何处理,直接返回HTTP相应给客户端,其语法如下:
reply 状态码 [ URL或相应体 ];
如果返回的状态码是 444,则直接断开 TCP 连接,不发送任何内容给客户端。
调用Reply指令时,可以利用的状态码有:204,400,402-406,408,410, 411, 413, 416 与 500-504。如果不带状态码直接返回 URL 则被视为 302。其利用形态如下:
if ($http_user_agent ~ curl) { reply 200 'COMMAND USER\n'; } if ($http_user_agent ~ Mozilla) { reply 302 http://www.baidu.com?$args; } reply 404;
eJet系统在阐明器阐明实行Script代码时,先实行Listen下的script脚本、再实行Host下的script脚本,末了再实行Location下的script脚本。在实行下一个脚本之前,先判断刚刚实行的script脚本是否已经Reply了或者已经关闭当前HTTPMsg了。如果Reply了或关闭当前了,则直接返回,无需连续解析并实行后续的script脚本程序。
5.4.5 rewrite语句eJet系统中的URL重写是通过Script脚本来实现的,分别借鉴了Apache和Nginx的成功履历。
rewrite语句实现URL重写功能,当客户HTTP要求到达Web Server并创建HTTPMsg后,分别依次实行Listen、Host、Location下的script脚本程序,rewrite语句位于这些script脚本程序之中,rewrite语句会改变要求DocURL,一旦改变要求DocURL,在依次实行完这些script脚本程序之后,连续基于新的DocURL去匹配新的Host、Location,并连续依次实行该Host、Location下的script脚本程序,如此循环,是否连续循环实行,取决于rewrite的flag标记。
rewrite基本语法如下:
rewrite regex replacement [flag];
实行该语句时是用regex的正则表达式去匹配DocURI,并将匹配到的DocURI更换成新的DocURI(replacement),如果有多个rewrite语句,则用新的DocURI,连续实行下一条语句。
flag标记可以沿用Nginx设计的4个标记外,还增加了proxy或forward标记。其标记定义如下:
(a) last停滞所有rewrite干系指令,利用新的URI进行Location匹配。(b) break停滞所有rewrite干系指令,不再连续新的URI进行Location匹配,直策应用当前URI进行HTTP处理。(c) redirect利用replacement中的URI,以302重定向返回给客户端。(d) permament利用replacement中的URI,以301重定向返回给客户端。(e) proxy | forward利用replacement中的URI,向Origin做事器发起Proxy代理要求,并将Origin要求返回的相应结果返回给客户端。由于reply语句功能很强大,rewrite中的redirect和permament标记所定义和实现的功能,基本都在reply中实现了,这两个标记实在没有多大必要。
rewrite利用案例如下:
rewrite ^/(.) https://www.ezops.com/$1 permanent;rewrite ^/search/(.)$ /search.php?p=$1?;要求的URL: http://xxxx.com/search/some-search-keywords重写后URL: http://xxxx.com/search.php?p=some-search-keywordsrewrite ^/user/([0-9]+)/(.+)$ /user.php?id=$1&name=$2?;要求的URL: http://xxxx.com/user/47/dige重写后URL: http://xxxx.com/user.php?id=47&name=digerewrite ^/index.php/(.)/(.)/(.)$ /index.php?p1=$1&p2=$2&p3=$3?;要求的URL: http://xxxx.com/index.php/param1/param2/param3重写后URL: http://xxxx.com/index.php?p1=param1&p2=param2&p3=param3rewrite ^/wiki/(.)$ /wiki/index.php?title=$1?;要求的URL:http://xxxx.com/wiki/some-keywords重写后URL:http://xxxx.com/wiki/index.php?title=some-keywordsrewrite ^/topic-([0-9]+)-([0-9]+)-(.)\.html$ viewtopic.php?topic=$1&start=$2?;要求的URL:http://xxxx.com/topic-1234-50-some-keywords.html重写后URL:http://xxxx.com/viewtopic.php?topic=1234&start=50rewrite ^/([0-9]+)/.$ /aticle.php?id=$1?;要求的URL:http://xxxx.com/88/future重写后URL:http://xxxx.com/atricle.php?id=88
在eJet系统中,replacement后加?和不加?是有差别的,加?意味着query参数没了,不加则会自动把源URL中的query串(?query)添加到更换后的URL中。
5.4.6 addReqHeader语句特定情形下,须要对客户端要求添加额外的要求头,交给后续处理程序,如运用层处理程序、PHP程序、Proxy、Origin做事器等等,来处理或利用到这些信息。譬如在作为HTTP Proxy功能时,发送给远程Origin做事器的要求中都须要添加两个要求头:一个是X-Real-IP,另一个是X-Forwarded-For,利用本语句可以很方便地实现了。
其基本语法为:
addReqHeader <header name> <header value>;
不能是空格字符,以字母开头后跟字母、数字和下划线_的字符串,可以用双引号圈定起来; 是任意字符串,可以以引号包含起来,字符串中可包含变量。
利用案例如下:
if ($proxied) { addReqHeader X-Real-IP $remote_addr; addReqHeader X-Forwarded-For $remote_addr;}
5.4.7 addResHeader语句
其基本语法为:
addResHeader <header name> <header value>;
5.4.8 delReqHeader语句
其基本语法为:
delReqHeader <header name>;
5.4.9 delResHeader语句
其基本语法为:
delResHeader <header name>;
5.4.10 try_files 语句
try_files 是一个主要的指令,建议位于Location、Host下面。利用该指令,依次测试列表中的文件是否存在,存在就将其设置DocURI,如不不存在,则将末了的URI设置为DocURI,或给客户端返回状态码code。
try_files基本语法如下:
try_files file ... uri;或 try_files file ... =code;
5.4.11 注释语句
Script脚本程序中,如果一行撤除空格字符外,以#号打头,那么当前行为注释行,不被阐明器阐明实行;其余通过C措辞代码块注释标记 / xxx /也被eJet系统采取。
5.5 Script脚本阐明器eJet系统在处理HTTPMsg的实例化过程中,成功定位到HTTPHost、HTTPLoc等资源位置后,开始阐明实行这三个层级资源管理框架下的脚本程序,实行的顺序依次为HTTPListen、HTTPHost、HTTPLoc下的Script脚本程序。
eJet系统的Script阐明器是逐行逐字进行扫描和识别,提取出Token后,分别匹配上述语句指令,再递归根据各个语句的扫描、识别和处理。这里细节不做描述!
eJet接管客户端发起的TCP连接,吸收该连接上的HTTP要求数据,解析HTTP要求头,创建HTTPMsg来保存要求数据后,须要理解客户端发送HTTP要求的目的,即确定HTTP要求的资源在哪个虚拟主机下的那个存储位置,这个过程便是HTTPMsg的实例化流程。
如4.1中所述,eJet Web做事器的资源管理构造分成三层:
HTTP监听做事HTTPListen - 对应的是监听本地IP地址和端口后的TCP连接HTTP虚拟主机 - 对应的是要求主机名称domainHTTP资源位置HTTPLoc - 对应的是主机下的各个资源目录一个eJet Web做事器可以监听本机的一个或多个IP地址、一个或多个端口,期待不同客户真个TCP连接要求,分别对应到多个监听做事HTTPListen;在每一个监听做事下,可以配置一个到多个HTTP虚拟主机HTTPHost,每个虚拟主机对应的是一个网站,管理不同类别的文件资源、网络资源等位置信息HTTPLoc;每个资源位置包含详细的文件存储路径,或网络地址等信息。
6.2 匹配虚拟主机和资源位置HTTPMsg的实例化因此DocURI地址的信息来匹配虚拟主机和资源位置的,DocURI的缺省地址是客户端发起的HTTP要求URI。
HTTPMsg的实例化过程中改变的地址是DocURI,再次匹配虚拟主机和资源位置的也是DocURI的信息。对用户要求URI进行资源定位过程中,由于补足资源目录下的缺省文件名、或利用rewrite、try_files等指令改变要求地址等操作,都会改变DocURI。
当eJet接管客户端连接时创建HTTPCon,并绑定监听做事HTTPListen实例,当吸收到要求数据后,创建HTTPMsg,并将该连接上的HTTPListen实例通报到的HTTPMsg工具保存。
HTTPMsg根据DocURI的主机名称,查找当前HTTPListen下的主机表,用Hashtab的精准匹配,找到HTTPHost虚拟主机实例工具。
绑定了HTTPHost后,利用DocURI的路径名,分别采取路径名进行精准匹配、前缀匹配、正则表达式匹配三种匹配规则,找到资源位置HTTPLoc,如果三种匹配都没有匹配到,则采取缺省的资源位置HTTPLoc。
6.3 实行脚本程序HTTPMsg实例工具设置了三个层级的资源工具后,分别读取各自的脚本程序,阐明并实行这些程序代码。
实行脚本程序的优先级是: 首先实行HTTPListen监听做事下的脚本程序,其次实行HTTPHost虚拟主机下的脚本程序,末了实行HTTPLoc资源位置下的脚本程序。
脚本程序实行过程中,如果调用Reply指令直接给客户端返回相应,那么终止当前所有的Script脚本运行,退出实例化过程,并完成相应的发送后,终止当前要求做事。
如果实行脚本时,调用了rewrite、try_files指令,并且重新改写了DocURI,则会涌现HTTPMsg实例化过程的嵌套实行,即重新实行4.7.2节描述的重新匹配虚拟主机和资源位置,并实行新的脚本程序。需把稳的是,eJet系统中HTTPMsg实例化过程中,递归嵌套次数不超过16次。
脚本程序实行期间,可根据要求信息(如IP地址、终端类型、特定要求头、要求目的URL等),利用各种脚本指令,动态设置或改变成员变量值或干系属性。
七. TLS/SSL7.1 TLS/SSL、OpenSSL先容SSL的全称为Secure Socket Layer,即安全套接字层,是Netscape于90年代研发,位于TCP协议之上,利用PKI安全加密体系来实现认证和加密传输,SSL当前最新版本为3.0。
SSL协议分为两层:
SSL记录协议(SSL Record Protocol):在TCP之上,为高层协议供应数据封装、压缩、加密等功能,定义传输格式SSL握手协议(SSL Handshake Protocol):在SSL记录协议之上,对通讯双方进行身份认证、协商加密算法、交流密钥等。TLS的全称是Transport Layer Security,即传输层安全协议,当前最新版为TLS 1.3,是IETF(Internet Engineering Task Force,互联网工程任务组)制订的一种新的协议,建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本。
同样TLS协议由两层组成:
TLS 记录协议(TLS Record)TLS 握手协议(TLS Handshake)SSL/TLS协议供应的做事紧张有:
认证。认证客户端和做事器,确保数据发送到精确的客户端和做事器;加密。加密数据以防止数据中途被盗取;同等性。掩护数据的完全性,确保数据在传输过程中不被改变实现TLS/SSL协议的开源软件是OpenSSL,是澳洲人Eric Young、Tim Hudson于90年代开源的SSLeay根本上演化过来的,采取标准C措辞编写,广泛用于利用加密和安全的环境。
7.2 eJet集成OpenSSL客户端发起HTTP要求,如果scheme是https,则须要建立SSL/TLS over TCP的安全连接到eJet做事器系统,
eJet系统作为做事器端吸收客户端HTTP要乞降作为客户端向Origin做事器发送HTTP要求时,都会利用到SSL连接,调用OpenSSL的方法有一些差别。
eJet作为做事器端利用SSL时,利用OpenSSL的基本流程共有九个步骤。
初始化OpenSSL库系统初始化时,首先调用SSL_library_init初始化OpenSSL库,调用SSL_add_ssl_algorithms()添加SSL缺省算法,加载缺点信息定义串,如果根据SSL连接实例能获取到HTTPCon工具,需创建SSL连接索引,并利用该连接索引,将SSL Socket连接实例和HTTPCon工具关联。
配置证书和私钥在系统配置Listen下,设置HTTPListen监听做事下是否支持SSL,及缺省的证书、私钥和CA证书,并在每个域名对应的虚拟主机下,配置启用SSL所需的做事器证书、私钥和CA证书。示例如下:
listen = { local ip = ; # any IP address port = 443; forward proxy = off; ssl = on; ssl certificate = cert.pem; ssl private key = cert.key; ssl ca certificate = cacert.pem; host = { host name = www.yunzhai.cn type = server; ssl certificate = yzcert.pem; ssl private key = yzcert.key; ssl ca certificate = yzcacert.pem;...... }......}
初始化SSL_Ctx
在系统初始化末了,开始启动HTTPListen做事前,加载监听做事和其下各虚拟主机时,分别根据证书、私钥和CA证书,创建HTTPListen的缺省SSL_Ctx实例,或创建各虚拟主机HTTPHost下的SSL_Ctx。
创建SSL_Ctx的过程先调用SSL_CTX_new创建实例,随后加载证书和私钥,并校验证书和私钥是否匹配,如果存在CA证书,还需加载CA证书。
末了,启用SNI(Server Name Indication)机制,设置一个回调函数来处理不同域名对应不同的证书和私钥,在SSL启动Handshake时,先发送ClientHello要求,个中携带了当前连接对应的域名,做事器端收到ClientHello时,会以域名为参数,调用回调函数,选择与之相对应的SSL_Ctx。
接管连接并创建SSL SocketeJet做事器收到客户真个TCP连接要求时,创建HTTPCon实例,保存连接信息后,HTTPCon需关联HTTPListen,并根据HTTPListen中的ssl_link配置选项,来创建SSL Socket连接实例,其过程紧张包括:利用SSL_new创建SSL实例,调用SSL_set_fd设置当前连接的文件描述符,调用SSL_set_ex_data将当前SSL工具和HTTPCon实例工具关联起来。末了,设置当前HTTPCon的ssl_handshaked状态为未建立握手状态。
根据域名选择对应的SSL_Ctx一个监听端口下,可以有多个证书,用于不同的主机名,客户端HTTPS要求到达时,须要利用得当的证书来完成后续SSL握手和加密通信,这是采取TLS规范的SNI机制来实现的。
在创建SSL_Ctx时,需设置多域名选择的回调函数,SSL握手开始时的ClientHello要求携带要求的域名名称,回调函数根据SSL_get_servername获取到域名名称,在当前HTTPListen下查找该名称对应的虚拟主机HTTPHost,并调用SSL_set_SSL_CTX,将当前HTTPCon中的SSL连接的SSL_Ctx高下文实例设置为该HTTPHost下的sslctx,即可实现证书选择和切换操作。
SSL握手对付接管客户端要求的环境,SSL握手过程是在SSL_accept中实现的,由于网络抖动等成分,握手过程中往来的数据须要通过多次读写事宜来驱动完成,在http_pump处理IOE_READ和IOE_WRITE时,须要判断当前HTTPCon的ssl_handshaked状态,如果没有握手成功,则相应这两个ePump事宜时,都须要调用SSL_accept。
eJet还须要根据SSL_accept的缺点状态码,来添加对当前TCP连接的读就绪或写就绪监听处理,并在http_pump中处理读写事宜。这是非壅塞通信下建立SSL连接必须要把稳的步骤。
如果SSL_accept返回成功,则将HTTPCon的ssl_handshaked设置为已完成握手状态,并调用http_cli_recv来吸收SSL上的数据。
在SSL连接上吸收数据eJet系统封装了一个针对HTTPCon的数据吸收函数,同时兼容有SSL连接和没有SSL连接这两种情形,函数定义如下:
int http_con_read (void vcon, frame_p frm, int num, int err);
ePump框架在当前连接有数据可读时,回调http_pump处理IOE_READ事宜,如果完成了握手过程,则调用这个函数来读取数据。如果是SSL连接,该函数调用SSL_read来读取数据,如果读取成功,返回的是解密完成后的数据长度,并将解密后的数据存入缓冲区,把稳:这里有两次拷贝(从内核拷贝到临时缓冲区,再从临时缓冲区拷贝到目标缓冲区),须要优化。
如果SSL_read返回0,则当前连接涌现故障,需关闭连接。如果返回值小于0,则调用SSL_get_error来处理缺点码,对付SSL_ERROR_WANT_READ和SSL_ERROR_WANT_WRITE两种情形须要调用ePump接口设置添加读写就绪监听。
在SSL连接上发送数据eJet系统中发送数据流程一样平常是用chunk_t数据构造管理数据,调用writev和sendfile将数据通过网络发送给对方,在SSL连接情形下,eJet系统同样封装了两个类似的函数:
int http_con_writev (void vcon, void piov, int iovcnt, int num, int err);int http_con_sendfile (void vcon, int filefd, int64 pos, int64 size, int num, int err);
这两个函数同时兼容有SSL连接和没有SSL连接这两种情形,在没有SSL连接情形下,直接调用tcp_writev和tcp_sendfile。
在有SSL连接情形下,调用SSL_write函数,要写入的明文数据调用SSL_write后被加密并传输给对方。如果发送成功返回的是写入数据的长度,如果返回0,则当前连接涌现故障,需关闭连接。如果返回值小于0,则须要调用SSL_get_error来处理缺点码,对付SSL_ERROR_WANT_READ和SSL_ERROR_WANT_WRITE两种情形须要调用ePump接口设置添加读写就绪监听。
关闭SSL连接在处理完成数据读写操作,或者网络缺点等情形,当前HTTPCon会被关闭,如果是SSL连接则需开释SSL实例,分别调用SSL_shutdown和SSL_free来完成资源开释。
以上九个步骤是eJet系统作为HTTP做事器时利用SSL连接来传输数据的基本流程,对付eJet系统作为HTTP客户端环境,过程基本类似,这里不再赘述。
八. Chunk传输编码解析HTTP 1.1协议增加了Transfer-Encoding: chunked的头类型,表示体的内容长度不能确定,需采取分块传输编码办法,将体发送给对方。
Chunked Transfer Coding分块传输编码是由多个Chunk块组成,每个Chunk块包括两部分,十六进制的分块数据长度加上可选的分块扩展加上\r\n、实际分块数据加上\r\n,分块传输编码的结尾因此分块数据长度为0的分块组成。
分块传输数据格式如下:
chunked body = chunk-size[; chunk-ext-nanme [= chunk-ext-value]]\r\n ... 0\r\n [footer] \r\n
chunk size因此16进制表示的长度,footer一样平常因此\r\n结尾的entity-header,一样平常都忽略掉。
eJet系统利用HTTPChunk数据构造来解析chunk分块传输编码的体数据,利用chunk_t数据构造来打包分块传输编码。HTTPChunk数据构造包含chunk_t成员实例,用于存储解析成功的Chunk数据块,每一个Chunk数据块解析状态和信息用ChunkItem来存储管理,HTTPChunk中用item_list来管理多个ChunkItem。
采取chunk分块传输编码的体,实际情形是一边传输一边解析,解析过程要充分考虑到当前吸收缓冲区内容的不完全性,这是由HTTPChunk里的http_chunk_add_bufptr来实现的,函数定义如下:
int http_chunk_add_bufptr (void vchk, void vbgn, int len, int rmlen);
vbgn和len指向需解析的体数据,rmlen是解析成功后实际用于chunk分块传输编码的字节数量。
eJet在碰着chunk分块传输编码的体时,每次收到读事宜,就将数据读取到缓冲区,将缓冲区所有数据交给这个解析函数解析处理,返回的rmlen值是被解析和处理的字节数,将处理完的数据从缓冲区移除掉。通过http_chunk_gotall来判断是否吸收到全部chunk分块传输编码的所有数据,如果没有,循环地用新吸收的数据调用该函数来解析和处理,直至成功吸收完毕。
九. 反向代理和正向代理9.1 判断是否为代理要求反向代理是将不同的Origin做事器代理给客户端,客户端不做任何代理配置发起正常的HTTP要求到反向代理做事器,反向代理做事器根据配置的路径规则,代理访问不同的Origin做事器并将相应结果返回给客户端,让客户端认为反向代理做事器便是其访问的Origin做事器。
正向代理须要求客户端设置正向代理做事器地址,明确给定Origin做事器地址,哀求正向代理做事器想给定的Origin做事器转发要乞降相应。
上面描述的反向代理做事器,在这里便是eJet Web做事器,除了充当Web做事器功能外,还可以充当正向代理做事器和反向代理做事器。
eJet系统在HTTPMsg实例化完成后,首先要检讨的是当前要求是否为Proxy代理要求:
是否在rewrite时启动forward到一个新的Origin做事器的动作,如果是则代理转发到新的URL是否为正向代理,正向代理的要求地址request URI是绝对URI,如果是则代理转发到绝对URI上判断当前资源位置HTTPLoc是否配置了反向代理,以及反向代理指向的Origin做事器,如果是,根据规则天生访问Origin做事器的URL地址以上三种情形中,第一种和第三种为反向代理,第二种为正向代理,对应的配置样例如下:
location = { #rewrite ... forward type = server; path = ['/5g/', '^~' ]; script = { rewrite ^/5g/.tpl$ http://temple.ejetsrv.com/getres.php forword; }}# HTTP要求行是绝对URI地址GET http://cdn.ejetsrv.com/view/23C87F23D909B47E2187A0DB83AF07D3 HTTP/1.1....location = { # 反向代理配置 path = [ '^/view/([0-9A-Fa-f]{32})$', '~' ]; type = proxy; passurl = http://cdn.ejetsrv.com/view/$1;......}
无论是正向代理,还是反向代理,末了转发要求的操作流程基本类似,即需明确指向新Origin做事器的URL地址,作为下一步转发地址,主动建立到Origin做事器的HTTPCon连接,组装新的HTTPMsg要求,发送要求并期待相应,将相应结果转发到源HTTPMsg中,发送给客户端。
如果是代理要求,包括正向代理或反向代理,eJet须要做Proxy代理转发处理。
9.2 代理要求的实时转发须要重点先容的是实时转发源要求到Origin做事器的流程。代理转发时先创建一个代理转发的HTTPMsg实例,将源要求HTTPMsg实例的要求数据复制到代理要求HTTPMsg中,如果HTTP要求含有要求体时,代理转发流程有两种实现办法:
一种办法是存储转发,即吸收完所有的HTTP要求体后,再复制到代理转发HTTPMsg中,末了发送出去另一种办法实时转发,即吸收一部分体就发送一部分体,直到全部发送完毕为了确保代理转发效率和降落存储花费,eJet系统采取实时转发模式。
源要求的体内容保存在HTTPCon的rcvstream中,相应IOE_READ事宜时将网络内容读取到该缓冲区后,就要调用http_proxy_srv_send来实时转发。转发的数据包括代理要求头、上次未发送成功的体、及当期位于HTTPCon缓冲区中的rcvstream,严格按照吸收的顺序来发送。
每次未发送成功的体,将会从HTTPCon的rcvstream中拷贝出来,转存到代理要求HTTPMsg中的req_body_stream中,作为临时缓冲区保存累次未能发送的体。当从源HTTPCon中吸收到新数据、或到Origin做事器的目的HTTPCon中可写就绪时,都会启动http_proxy_srv_send的实时发送流程,而优先发送的体便是代理要求中req_body_stream中的内容。
源要求的体有三种情形:
没有体内容存在以Content-Length来标识大小的体内容存在以Transfer-Encoding标识分块传输编码的体内容实时转发须要处理这三种情形,终极通过http_con_writev来发送给对方。发送不堪利的剩余内容,须要从源HTTPCon中拷贝到代理要求HTTPMsg中的req_body_stream中。
实时转发最大问题是拥塞问题,即源HTTPCon上的要求数据发送速率很快,但到Origin做事器的目的HTTPCon连接的发送速率比较慢,导致大量的数据会堆积到代理HTTPMsg中req_body_stream中,花费大量内存,严重时会导致内存花费过大系统崩溃。
代理实时转发模式的拥塞问题根源在于两条线路传输速率不对等导致,只要发送侧速率大于吸收侧速率,拥塞问题就会涌现。办理拥塞问题需从源头来考虑,判断是否拥塞的标准是堆积的内存缓冲区超过一定的阈值,一旦内存堆积超过阈值,就断定为拥塞,需限定客户端连续发送任何内容,直到解除拥塞后连续发送。
9.3 代理相应的实时转发代理要求转发给Origin做事器后,会返回相应,包括相应头和相应体,eJet处理相应头的吸收和处理编码。
和HTTP要求的实时转发类似,代理的相应也须要实时转发给客户端。
根据代理HTTPMsg内部成员proxiedl连判断当前是否为代理,对Origin返回的相应头信息进行预处理:
如果是301/302跳转,当前代理是反向代理,并且系统许可自动重定向,则需重新发送重定向要求;如果须要缓存到本地存储系统,采取缓存处理流程,见4.20章节其他环境就按照代理相应来处理复制所有的相应状态码和相应头到源HTTPMsg中,并将相应HTTPCon的吸收缓冲区rcvstream数据实时转发到源HTTPCon中,同样地,HTTPCon中没有发送不堪利的数据,转存到源HTTPMsg中的res_body_stream中临时缓存起来。每次当源HTTPCon可写就绪、或代理HTTPCon有数据可读并读取成功后,都会调用http_proxy_cli_send,优先发送的是堆积在res_body_stream中的数据。
其他后续流程类似要求的实时转发。
十. FastCGI机制和启动PHP的流程10.1 FastCGI基本信息FastCGI是CGI(Common Gateway Interface)的开放式扩展规范,其技能规范见网址 http://www.mit.edu/~yandros/doc/specs/fcgi-spec.html
对静态HTML页面中嵌入动态脚本程序的内容,如PHP、ASP等,须要由特定的脚本阐明器来阐明运行,并动态天生新的页面,这个过程须要eJet Web做事器和脚本程序阐明器之间有一个数据交互接口,这个接口便是CGI接口,考虑到性能局限,早期的独立进程模式的CGI接口发展成FastCGI接口规范。习气地,我们把阐明器称之为CGI做事器。
利用CGI接口规范的页面脚本程序可以利用任何支持标准输入STDIN、标准输出STDOUT、环境变量的编程措辞来编写,如PHP、Perl、Python、TCL等。在传统CGI规范的fork-and-execute模式中,Web做事器会为每个HTTP要求,创建一个新进程、阐明实行、返回相应、销毁进程,这是个很重的事情流程。
FastCGI对CGI这种重模式进行了简化,脚本阐明器和Web做事器之间的交互,通过Unix Socket或TCP协议来实现,Web做事器收到须要阐明实行的HTTP要求时,建立并坚持通信连接到CGI做事器,按照FastCGI通信规范发送要求,并吸收相应,这个流程比较CGI模式,大大提升了性能和并发处理能力。
PHP阐明器名称为php-fpm(php FastCGI Processor Manager),作为FastCGI通信做事器监听来自Web做事器的连接要求,并吸收连接上的数据,进行解析、阐明实行后,返回相应给Web做事器端。php-fpm的配置项中,启动监听做事:
; The address on which to accept FastCGI requests.; Valid syntaxes are:; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on; a specific port;; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on; a specific port;; 'port' - to listen on a TCP socket to all addresses; (IPv6 and IPv4-mapped) on a specific port;; '/path/to/unix/socket' - to listen on a unix socket.; Note: This value is mandatory.listen = /run/php-fpm/www.sock;listen = 9000
10.2 eJet如何启用FastCGI
eJet收到客户真个HTTP要求并创建HTTPMsg和完成HTTPMsg实例化后,根据资源位置HTTPLoc是否将资源类型设置为FastCGI、并且设置了指向CGI做事器地址的passurl,如果都设置这两个参数,则当前要求会被当做FastCGI要求转发给CGI做事器。
启用FastCGI的参数配置如下:
location = { type = fastcgi; path = [ "\.(php|php?)$", '~']; passurl = fastcgi://127.0.0.1:9000; #passurl = unix:/run/php-fpm/www.sock; index = [ index.php ]; root = /data/wwwroot/php;}
只假如要求DocURL中路径名称因此.php或.php5等结尾,当前要求都会被FastCGI转发。
在获取转发URL地址时,是复制配置中的passurl地址,即CGI做事器地址,不能把HTTP要求中的路径和query参数信息添加在这个转发URL后面。转发地址有两种形态:
采取TCP协议的CGI做事器地址,以fastcgi://打头,后跟IP地址和端口,或域名和端口;采取Unix Socket的CGI做事器地址,以unix:打头,后跟Unix Socket的路径文件名。passurl地址指向CGI做事器,eJet做事器可以支持很多个CGI做事器。
eJet获取到FastCGI转发地址后,根据该地址创建或打开CGI做事器FcgiSrv工具实例,建立TCP连接或Unix Socket连接到该做事器的FcgiCon实例,为当前HTTP要求创建FcgiMsg实例,将HTTP要求信息按照FastCGI规范封装到FcgiMsg中,并启动发送流程,将要求发送到CGI做事器。
10.3 FastCGI的通信规范FastCGI通信依赖于C/S模式的可靠的流式的连接,协议定义了十种通信PDU(Protocol Data Unit)类型,每个PDU都由两部分组成:一部分是FastCGI Header头部,另一部分是FastCGI体,FastCGI的PDU是严格8字节对齐,PDU总长度不敷8的倍数,须要添加Padding补齐8字节对齐。FastCGI的PDU头格式如下:
typedef struct fastcgi_header { uint8 version; uint8 type; uint16 reqid; uint16 contlen; uint8 padding; uint8 reserved;} FcgiHeader, fcgi_header_t;
上面定义的协议头格式中,version版本号1个字节,缺省值为1,type为PDU类型1个字节,共计定义了10种类型,reqid为PDU的序号,两字节BigEndian整数,contlen是PDU体的内容长度,两字节BigEndian整数,1字节的padding是PDU体不是8字节的倍数时,须要补齐8字节对齐所添补的字节数,保留1字节。
个中PDU类型共有十种,分别定义如下:
/ Values for type component of FCGI_Header /#define FCGI_BEGIN_REQUEST 1#define FCGI_ABORT_REQUEST 2#define FCGI_END_REQUEST 3#define FCGI_PARAMS 4#define FCGI_STDIN 5#define FCGI_STDOUT 6#define FCGI_STDERR 7#define FCGI_DATA 8#define FCGI_GET_VALUES 9#define FCGI_GET_VALUES_RESULT 10#define FCGI_UNKNOWN_TYPE 11#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
个中从Web做事器发送给CGI做事器的PDU类型为:BEGIN_REQUEST、ABORT_REQUEST、PARAMS、STDIN、GET_VALUES等,从CGI做事器返回给Web做事器的PDU类型为:END_REQUEST、STDOUT、STDERR、GET_VALUES_RESULT等。
根据PDU type值,PDU体格式也都不一样,分别定义为:
typedef struct { uint8 roleB1; uint8 roleB0; uint8 flags; uint8 reserved[5];} FCGI_BeginRequest;/ Values for role component of FCGI_BeginRequest /#define FCGI_RESPONDER 1#define FCGI_AUTHORIZER 2#define FCGI_FILTER 3
BEGIN_REQUEST是发送数据到CGI做事器时,第一个必须发送的PDU。个中的角色role是两个字节组成,高位在前、低位在后,一样平常情形role值为RESPONSER,即哀求CGI做事器充当Responder来处理后续的PARAMS和STDIN要求数据。字段flags是指当前连接keep-alive还是返回数据后立即关闭。
第二个需发送到CGI做事器的PDU是PARAMS,其格式是由FcgiHeader加上带有长度的name/value对组成,PDU体格式如下:
typedef struct { uint8 namelen; //namelen < 0x80 uint32 lnamelen; //namelen >= 0x80 uint8 valuelen; //valuelen < 0x80 uint32 lvaluelen; //valuelen >= 0x80 uint8 name; //[namelen]; uint8 value; //[valuelen];} FCGI_PARAMS;
FastCGI中的PARAMS PDU是将HTTP要求头信息和预定义的Key-Value头信息发送给CGI做事器,这些信息都是Key-Value键值对。如果key或value的数据长度在128字节以内,其长度字段只需一个字节即可,如果大于或即是128字节,则其长度字段必须用BigEndian格式的4字节。在对HTTP要求头和预定义头Key-Value对信息封装编码成PARAMS PDU时,每个Header字段的编码格式为:先是Header的name长度,再是value长度,随后是name长度的name数据内容,末了是value长度的value数据内容。
1字节namelen或4字节namelen + 1字节valuelen或4字节valuelen + name + value
所有头信息按照上述编码格式打包完成后,总长度如果不是8的倍数,打算需不全8字节对齐的padding数量,将这些数据添补到FcgiHeader中。
第三个要发送到CGI做事器的PDU是STDIN,STDIN PDU是由FcgiHeader加上实际数据组成。把稳的是STDIN数据长度不能大于65535,如果HTTP要求中体数据大于65535,须要对体拆分成多个STDIN包,使得每个STDIN PDU的体长度都在65536字节以下。须要特殊把稳的是,所有数据内容拆分成多个STDIN PDU完成后,末了还须要添加一个别长度为0的STDIN PDU,表示所有的STDIN数据发送完毕。
当eJet系统收到HTTP要求并须要FastCGI转发是,按照以上三类数据包协议格式,将HTTP要求打包封装,并发送成功后,就等等CGI做事器的处理和相应了。
CGI做事器返回的PDU一样平常如下:
如果涌现要求格式缺点或其他缺点,会返回STDERR数据,其体是缺点内容,将缺点内容取出来可以直接返回给客户端。
正常情形下,CGI做事器会返回一个到多个STDOUT PDU,STDOUT的体是实际的数据内容,最大长度小于65536。须要将这些STDOUT的内容整合在一起,作为HTTP相应内容。需把稳的是STDOUT内容中,也包含部分HTTP相应头信息,其格式遵照HTTP规范,每个相应头有key-value对构成,以\r\n换行符结束,相应头和相应体之间相隔一个空行\r\n。
全部STDOUT数据结束后,紧接着返回的是END_REQUEST PDU,其格式是8字节的FcgiHeader,加上8字节的体,其体定义如下:
typedef struct { uint32 app_status; uint8 protocol_status; uint8 reserved[3];} FCGI_EndRequest;/ Values for protocolStatus component of FCGI_EndRequest /#define FCGI_REQUEST_COMPLETE 0#define FCGI_CANT_MPX_CONN 1#define FCGI_OVERLOADED 2#define FCGI_UNKNOWN_ROLE 3
eJet做事器收到END_REQUEST时,就表示CGI做事器已经返回全部的相应数据了,将这些数据发送给客户端,即可结束当前处理。
10.4 FastCGI的实时转发eJet系统将HTTP要求实时转发给CGI做事器,基本过程跟Proxy代理转发类似,包括实时转发、流量拥塞掌握等。
个中在吸收CGI做事器的相应数据时,须要解析以流式返回的STDOUT PDU的数据,但相应数据的总长度并未返回,eJet对这些相应数据的实时转发是采取Transfer-Encoding分块传输编码模式。为了减少相应数据的多次拷贝,FcgiCon中每次数据读就绪时,存入rcvstream缓冲区的数据,连同rcvstream一起移入到发起HTTP要求的源HTTPMsg内的res_rcvs_list列表中,并将解析成功的内容指针存入到res_body_chunk里,类似客户端访问本地文件一样,通过http_cli_send发送给客户端。
十一. HTTP Cache系统11.1 HTTP Cache功能设置HTTP Cache是指Web做事器充当HTTP Proxy代理做事器(包括正向代理和反向代理),通过HTTP协议向Origin做事器下载文件,然后转发给客户端,这些文件在转发给客户真个同时,缓存在代理做事器确当地存储中,下次再有相同要求时,根据干系缓存策略决定本地文件是否被命中,如果命中,则该要求无需向Origin做事器要求下载,直接将缓存中命中的文件读取出来返回给客户端,从而节省网络开销。
在配置文件中配置正向代理或反向代理的地方,都可以开启cache功能,并基于配置脚本动态设置缓存文件名等缓存选项。
location = { path = [ '^/view/([0-9A-Fa-f]{32})$', '~' ]; type = proxy; passurl = http://cdn.yunzhai.cn/view/$1; # 反向代理配置缓存选项 root = /opt/cache/; cache = on; cache file = /opt/cache/${request_header[host]}/view/$1;}send request = { max header size = 32K; / 正向代理配置的缓存选项 / root = /opt/cache/fwpxy; cache = on; cache file = <script> if ($req_file_only) return "${host_name}_${server_port}${req_path_only}${req_file_only}"; else if ($index) return "${host_name}_${server_port}${req_path_only}${index}"; else return "${host_name}_${server_port}${req_path_only}index.html"; </script>;}
在配置中启动了缓存功能后,还要根据Origin做事器返回的相应头指定的缓存策略,来决定当前下载文件是否保存在本地、缓存文件保存多永劫光等。HTTP相应头中有几个头是卖力缓存策略的:
Expires: Wed, 21 Oct 2020 07:28:00 GMT (Response Header)Cache-Control: max-age=73202 (Response Header)Cache-Control: public, max-age=73202 (Response Header)Last-Modified: Mon, 18 Dec 2019 12:35:00 GMT (Response Header)If-Modified-Since: Fri, 05 Jul 2019 02:14:23 GMT (Request Header) ETag: 627Af087-27C8-32A9E7B10F (Response Header)If-None-Match: 627Af087-27C8-32A9E7B10F (Request Header)
Proxy代理做事器须要处理Origin做事器返回的相应头,紧张是Expires、Cache-Control、Last-Modified、ETag等。根据Cache-Control的缓存策略决定当前文件是否缓存:如果是no-cache或no-store,或者设定了max-age=0,或者设定了must-revalidate等都不能将当前文件保存到缓存文件中。如果设置了max-age大于0则根据max-age值、Expires值、Last-Modified值、ETag值来判断下次要求是否利用该缓存文件。
11.2 eJet系统Cache存储架构eJet系统是否启动缓存由配置信息来设定。如果是反向代理,HTTP要求对应的HTTPLoc下的反向代理开关cache是否开启,即cache=on,cache file项是否设置,来决定是否启动缓存功能;如果是正向代理,在send request选项中,是否启动cache,以及cache file命名规则是否设置,决定是否启动缓存管理。
启动了cache功能,还须要根据当前要求转发给Origin后,返回的相应头中,是否有Cache管理的头信息,来确定当前返回的相应体是否缓存,以及确定当前缓存的干系信息。
缓存的Raw文件内容存储在上述配置中以cache file命名的文件中,当文件所有内容全都下载并存储起来前,文件名后须要增加扩展名.tmp,以表示当前存储文件正不才载中,还不是一个完全的文件,但已经缓存的内容则可以被命中利用。
cache管理信息则存储在缓存信息管理文件(Cache Information Management File)中,简称为CacheInfo文件,CacheInfo文件的存储位置在Raw缓存文件所在目录下建立一个隐蔽目录.cacheinfo,CacheInfo文件就存放该隐蔽目录下,CacheInfo文件名是在Raw存储文件后增加后缀.cacinf,譬如Raw缓存文件为foo.jpg,则缓存信息管理文件路径为: .cacheinfo/foo.jpg.cacinf
CacheInfo文件的构造包括三部分:Cache头信息(96字节)、Raw存储碎片管理信息。Cache头信息是固定的96字节,其构造如下:
/ 96 bytes header of cache information file /typedef struct cache_info_s { char cache_file; void hcache; char info_file; void hinfo; uint8 initialized; uint32 mimeid; uint8 body_flag; int header_length; int64 body_length; int64 body_rcvlen; / Cache-Control: max-age=0, private, must-revalidate Cache-Control: max-age=7200, public Cache-Control: no-cache / uint8 directive; //0-max-age 1-no cache 2-no store uint8 revalidate; //0-none 1-must-revalidate uint8 pubattr; //0-unknonw 1-public 2-private(only browser cache) time_t ctime; time_t expire; int maxage; time_t mtime; char etag[36]; FragPack frag; } CacheInfo;
在头信息之后存放的是存储内容碎片管理信息,每个碎片单元为8字节:
typedef struct frag_pack { int64 offset; int64 length;} FragPack;
内存中采取动态有序数组来管理每一个碎片块,相邻块就须要合并成一个块,完全文件只有一个块。将这些碎片块信息按照8字节顺序存储在这个区域中。每当文件有新内容写入时,内存碎片块数组要完成合并等更新,并将最新结果更新到这个区域。碎片块信息管理的是Raw存储文件中从Origin做事器下载并实际存储的数据存储状态,每块因此偏移量和长度来唯一标识,相邻的碎片块合并,完全文件只有一个碎片块。
11.3 eJet系统缓存处理流程eJet系统作为正向代理或反向代理做事器,实现边下载边缓存、完全缓存时无需代理转发直接返回缓存内容给客户端等功能,可以实现对大大小小的Origin文件的实时缓存功能,包括碎片存储、随机存储等。
(1)全局管理CacheInfo工具
系统掩护一个全局的CacheInfo工具哈希表,以Raw缓存文件名作为唯一标识和索引,如果存在多个用户要求同一个须要缓存的Origin文件时,只打开或创建一个CacheInfo工具,该工具成员由互斥锁来保护。而每个对同一Origin文件的HTTP要求,要求位置、偏移量、读写Raw缓存文件的句柄等都保存在各自的HTTPMsg实例工具中。
CacheInfo工具是管理和存放Raw缓存文件的各项元信息,对外暴露的紧张接口是: cache_info_open, cache_info_create, cache_info_close, cache_info_add_frag等
用户发起Origin文件要求时,先调用cache_info_open打开CacheInfo工具,如果不存在,则在收到Origin的成功相应后,调用cache_info_create创建CacheInfo工具。每次调用cache_info_open时,如果CacheInfo工具已经在内存中,则将count计数加1,只有count计数为0时才可以删除开释CacheInfo工具。当HTTPMsg成功返回给用户后,须要关闭CacheInfo工具,调用cache_info_close,首先将count计数减1,如果count大于0,直接返回不做资源开释。
(2)向Origin做事器转发Proxy代理要求
eJet收到HTTP客户要求时,如果是Proxy要求,则调用http_proxy_cache_open检测并打开缓存,先根据要求URL对应的HTTPLoc配置信息或正向代理对应的send request配置信息,决定当前代理模式下的HTTP要求是否启用了Cache功能,如果启用了Cache功能,并且Cache File变量设置了精确的Raw缓存文件名,将该缓存文件名保存在HTTPMsg工具的res_file_name中。
检讨该缓存文件是否存在,如果存在则直接将该缓存文件返回给客户端即可。注:在没有收到全部字节数据之前Raw缓存文件名是实际缓存文件后加.tmp做扩展名。
如果该文件不存在,以该缓存文件名为参数,调用cache_info_open打开CacheInfo工具,如果不存在缓存信息工具CacheInfo,则返回并直接将客户端要求转发到Origin做事器。
如果存在CacheInfo工具,也便是存在以.tmp为扩展名的Raw缓存文件和以.cacinf为扩展名的缓存信息文件,则判断当前要求的内容(Range规范指定的要求区域)是否全部包含在Raw缓存文件中,如果包含了,则直接将该部分内容返回给客户端,无需向Origin做事器发送HTTP下载要求;如果不包含,则须要向Origin做事器发送要求,但本地缓存中已经有的内容不必重新要求,而是将客户端要求的区域(Range规范指定的范围)中尚未缓存到本地的起始位置和长度打算出来,组成新的Range规范,向Origin发送HTTP要求。
(3)处理Origin做事器返回的相应头
当HTTP要求转发到Origin做事器并返回相应后,正常情形是将Proxy代理要求HTTPMsg中所有的相应头全部复制一份到源要求HTTPMsg的相应头中,包括状态码也复制过去。
但对付启用了Cache=on并且CacheInfo也已经打开的情形,则须要改动源要求HTTPMsg的相应头,即调用http_cache_response_header来完成:删除掉不必要的相应头,改动HTTP相应体的内容传输格式,即选择Content-Length办法还是Transfer-Encoding: chunked办法,并将状态码修正成206还是200,修正Content-Range的值内容,由于源要求的Range和向Origin做事器发起的Proxy代理要求的Range不一定是同等的。并根据CacheInfo信息决定是否增加Expires和Cache-Control等相应头,等等
随后,对Origin做事器返回的HTTP相应头进行解析,调用http_proxy_cache_parse来完成:分别解析Expires、ETag、Last-Modified、Cache-Control等相应头,基于这些相应头信息,再次判断当前相应内容是否须要缓存Cache=on。
如果不须要缓存:则将Cache设置为off,并关闭已经打开的CacheInfo(乃至删除掉CacheInfo文件和Raw缓存文件),最紧张的是检讨源要求的Range范围和Proxy代理要求的Range范围是否同等,如果不一致,则须要重新将源HTTP要求原样再发送一次,并打消当前Proxy代理要求的所有信息。由于将源HTTP要求HTTPMsg中Cache设置为off了,后续重新发送的Proxy代理要求将不启用缓存功能,直策应用实时转发模式。如果两个要求的Range同等,则直接将当前代理要求的相应体内容采取实时转发模式,发送给客户端。
如果须要缓存:解析出相应头中的Content-Range中的信息,如果之前用cache_info_open打开CacheInfo工具失落败,则此时需调用cache_info_create来创建CacheInfo工具,如果创建失落败(内存不足、目录不存在等)则关闭缓存功能,用实时转发模式发送相应。随后,提取这次相应的信息,并保存到CacheInfo工具中,打开或创建Raw缓存文件,最主要的几点是:打开或创建的Raw缓存文件句柄存放在源要求的HTTPMsg中,并将该文件seek写定位到Range或Content-Range头中指定的偏移位置上,在此位置上存放Proxy代理要求中的相应体。末了,将CacheInfo工具的最新内容写入到缓存信息文件中。
(4)存储Origin做事器返回的相应体
任何开启了Cache功能的HTTP要求,只要要求的内容不在本地缓存中,都须要向Origin做事器以Proxy模式转发HTTP要求,在处理完代理要求的相应头后,须要将相应体存储到Raw缓存文件适当位置,将存储位置信息更新到缓存信息文件中,并启动向客户端发送相应。
存储Proxy代理要求的相应体是调用http_proxy_srv_cache_store来实现的:先验证当前源HTTPMsg是否为pipeline后面的要求,是否Cache=on等。将代理要求HTTPcon吸收缓冲区中的内容作为要存储的相应体内容,进行大略解析判断,
(a)如果相应体是Content-Length格式:打算还剩余多少内容没收到,并比拟吸收缓冲区内容。如果剩余内容为0,则已经全部收到了要求的内容,关闭当前HTTP代理,并将res_body_chunk设置为结束。如果还有很多剩余内容没收到,则将吸收缓冲区写入到.tmp的Raw缓存文件中,写文件句柄在源HTTPMsg工具中,将写入成功数据块的文件位置和长度信息,追加到CacheInfo工具中,并更新到缓存信息文件里,将代理要求HTTPCon缓冲区中已经写入Raw缓存文件的内容删除掉。末了再判断,刚才从缓冲区追加写入到文件的内容是否全部收齐了,如果收齐了,关闭当前HTTP代理。
(b)如果相应体是Transfer-Encoding: chunked格式:这种格式并不知道相应体总长度是多少,也不知道剩余还有多少内容,返回的相应体因此一块一块数据块编码办法,每个数据块前面是当前数据块长度(16进制)加上\r\n,每个数据块结尾也加上\r\n为结尾。只有收到一个长度为0的数据块,才知道全部相应体已经结束和收齐了。由于网络传输的繁芜性,每次吸收数据时,并不一定会完全地收齐一个完全的数据块,以是须要将吸收缓冲区的数据交给http_chunk模块判断,是否为接续块、是否收到结尾块等。
处理吸收缓冲区数据前,先判断是否收齐了全部相应体,如果收齐了,设置res_body_chunk结束状态,关闭当前代理。将吸收缓冲区的所有内容添加到http_chunk中解析判断,得出缓冲区的内容哪些是接续的数据块,是否收齐等,将吸收缓冲区中那些接续数据块部分写入到.tmp的Raw缓存文件中,个中写文件句柄存放在源HTTPMsg工具中,更新总长度,删除吸收缓冲区中已经写入的内容,并将写入成功的数据块的文件位置和长度信息,追加到CacheInfo工具中,并更新到缓存信息文件里。末了判断,如果全部数据块都吸收完好了,关闭当前HTTP代理,关闭当前HTTP代理,同时正式打算并确定当前收齐了所有数据,设置实际的文件长度。
(c)末了启动发送缓存文件数据到客户端。
(5)向源HTTPMsg的客户端发送相应
发送的相应包括相应头和位于缓存文件中的相应体,调用http_proxy_cli_cache_send来处理:
通过HTTP的承载协议TCP来发送数据前,须要有序地整理待发送的数据内容,一样平常情形下,待发送的数据内容包括缓冲区数据、文件数据(完全文件内容、部分文件内容等)、未知的须要网络要求的数据等等,这些数据的总长度有可能知道、也可能不知道,这些待发送数据一样平常情形下,都位于不同存储位置,譬如在内存中、硬盘上、网络里等,其特点是分布式的、不连续的、碎片化的、乃至内容长度非常大(大到内存都不可能全部容纳的极度情形),管理这些不连续的、碎片化、乃至超大块头数据,是由数据构造chunk_t来实现的。
chunk_t数据构造供应了各种功能接口,包括添加各种数据(内存块、文件名、文件描述符、文件指针等)、有序整理、统一输出、检索等访问接口,最紧张的功能是该数据构造办理了不同种别数据整合在一起,仿照成为了一个大缓冲区,大大减少了数据读写拷贝产生的巨额性能开销,大大减少了内存花费。利用该数据构造,只需将要发送的各种数据内容,通过chunk_t的各种数据追加接口,添加到该数据构造的实例工具中,末了通过tcp_writev或tcp_sendfile来实现数据高效、快速、零拷贝办法的传输发送。
基于以上逻辑,向客户端发送数据的紧张事情是如何将待发送内容添加到源HTTPMsg中的res_body_chunk中:
(a)首先打算出res_body_chunk中累计存放的相应体数据总长度,加上源HTTP要求文件的起始位置(如果有Range取其起始位置,如果没有Range,缺省为0),得到当前要追加发送给客户真个数据在缓存文件中的位置偏移量。分别考虑两种相应体编码格式的处理情形;
(b)如果相应体是通过Content-Length来标识:
先用HTTP相应总长度减去chunk中的相应体总长度,就打算出剩余的有待添加的数据长度。通过CacheInfo的碎片数据管理接口,查询出当前Raw缓存文件中,以(a)中计算出的缓存文件偏移量位置,查出可用的数据长度有多少。
如果Raw缓存文件中存在可用数据,比拟剩余数据长度,截取多余部分。将该Raw缓存文件名、文件偏移位置、截取处理过的可用数据长度等作为参数,调用chunk添加数据接口,添加到res_body_chunk中,如果跟chunk中之前存储且未发送出去的数据是接续的,合并处理。如果添加到chunk中的数据总长度达到或超过源要求HTTPMsg的相应总长度,则将res_body_chunk设置结束状态,启动TCP发送流程。
如果Raw缓存文件中不存在可用数据,则判断是否向Origin做事器发送HTTP代理要求:当前源HTTP要求中没有其他的代理要求存在、Raw缓存文件数据不完全、源HTTP要求的数据范围不在Raw缓存文件中,这三个条件都知足时,则须要向Origin做事器发送HTTP代理要求。这个代理要求是HTTP GET要求,可能跟源HTTP要求方法不一样,只是获取缓存数据的某一部分内容,其Range值是从源要求起始位置开始,去查找实际Raw缓存文件存储情形,得出的空缺处偏移位置。该HTTP代理要求,只卖力下载数据存储到本地缓存文件,其相应头信息并不更新到缓存信息文件中。
(c)如果相应体的编码格式为Transfer-Encoding: chunked时:
通过CacheInfo的碎片数据管理接口,查询出当前Raw缓存文件中,以(a)中计算出的缓存文件偏移量位置,查出可用的数据长度有多少。
如果Raw缓存文件中存在可用数据,将可用数据长度截成最多50个1M大小的数据块,将Raw缓存文件名、1M数据块起始位置、长度作为参数添加到res_body_chunk中。如果添加到chunk中的数据总长度达到或超过源要求HTTPMsg的相应总长度,则将res_body_chunk设置结束状态,启动TCP发送流程。
如果Raw缓存文件中不存在可用数据,则与上述(b)流程类似。
(d)如果源HTTPMsg中统计发送给客户真个相应数据总长度小于res_body_chunk中的总长度,开始发送chunk中的数据。
(6)发送相应给客户真个流程是标准通用的流程
基于HTTP Proxy的缓存数据存储、发送、缓存信息管理掩护等功能全部实现完成。
十二. HTTP TunnelHTTP Tunnel是在客户端和Origin做事器之间,通过Tunnel网关,建立传输隧道的通信办法,eJet做事器可以充当HTTP Tunnel网关,分别与客户端和Origin做事器之间建立两个TCP连接,并在这两个连接之间进行数据的实时转发。根据RFC 2616规范,HTTP CONNECT要求方法是建立HTTP Tunnel的基本办法。
HTTP Tunnel最常用的场景是HTTP Proxy正向代理做事器,代理转发客户端https的安全连接要求到Origin做事器,一样平常情形下,须要采取端到真个TLS/SSL连接,这时,客户端会考试测验发送CONNECT方法的HTTP要求,建立一条通过Proxy做事器,到达Origin做事器的连接隧道,即两个TCP连接串联来实时转发数据,通过这个连接隧道,进行TLS/SSL的安全握手、认证、密钥交流、数据加密等,从而实现端到真个安全数据传输。
十三. eJet的Callback回调机制13.1 eJet回调机制eJet系统供应了HTTP要求交付给运用程序处理的回调机制,回调机制是事宜驱动模型中底层系统异步调用上层处理函数的编程模式,上层运用系统需事先将函数实现设置到底层系统的回调函数指针中。
eJet系统供应了两种回调机制,一种是在启动eJet时,设置的全局回调函数,另一种是在系统配置文件中位于监听做事下的动态库配置回调机制。
13.2 eJet全局回调函数全局回调函数的设置是在启动eJet系统时,运用层可以实现HTTP处理函数,来处理所有HTTP要求的HTTPMsg,这是程序级的回调机制,须要将eJet代码嵌入到运用系统中来实现回调处理。
设置全局回调的API如下:
int http_set_reqhandler (void httpmgmt, RequestHandler reqhandler, void cbobj);
个中,httpmgmt是eJet系统创建的全局管理入口HTTPMgmt工具实例, reqhandler是运用层实现的回调函数,cbobj是运用层回调函数的第一个回调参数,eJet每次调用回调函数时,必须携带的第一个参数便是cbobj。
运用层回调函数的原型如下:
typedef int RequestHandler (void cbobj, void vmsg);
个中,cbobj是设置全局回调函数时通报回调参数,vmsg是当前封装HTTP要求的HTTPMsg实例工具。
运用程序将系统管理所需的数据构造(包括运用层配置、数据库连接、用户管理等)封装好,创建并初始化一个cbobj工具,作为设置回调函数时的回调参数。通过回调参数,已经HTTPMsg要求工具,可以将要求信息和运用程序内的数据工具建立各种关联关系。
13.3 eJet动态库回调eJet系统其余一种回调是利用动态库的回调办法,这是松耦合型的、修正配置文件就可以完成回调处理的办法。运用程序无需改动eJet的任何代码,只需在配置中添加含有路径的动态库文件名,即可以实现回调功能,个中动态库必须实现三个固定名称的函数,且遵照eJet约定的函数原型定义。
配置文件中添加动态库回调的位置:
listen = { local ip = ; port = 8181; request process library = reqhandle.so app.conf......
eJet系统启动期间,加载配置文件后,解析三层资源架构的第一步HTTPListen时,其配置项下的动态库会被加载,加载过程为:
加载配置项指定动态库文件;根据函数名http_handle_init,获取动态库中的初始化函数指针;根据函数名http_handle,获取动态库中的回调处理函数指针;根据函数名http_handle_clean,获取动态库中的打消函数指针;实行动态库初始化函数,并返回初始化后的回调参数工具。在eJet系统退出时,会调用http_handle_clean来开释初始化过程分配的资源。
动态库在实现回调时,必须含有这三个函数名:http_handle_init、http_handle、http_handle_clean,其函数原型定义如下:
typedef void HTTPCBInit (void httpmgmt, int argc, char argv);typedef void HTTPCBClean (void hcb);typedef int RequestHandler (void cbobj, void vmsg);
个中回调函数http_handle的第一个参数cbobj是由http_handle_init返回的结果工具,vmsg即是eJet系统的HTTPMsg实例工具。
13.4 回调函数利用HTTPMsg的成员函数eJet系统通过通报HTTPMsg实例工具给回调函数,来处理HTTP要求。HTTP工具封装了HTTP要求的所有信息,回调函数在处理要求时,可以添加各种相应数据到HTTPMsg中,包括相应状态、相应头、相应体等。
访问要求头信息或添加相应数据的操作,既可以直接对HTTPMsg的成员变量进行数据读取或写入,也可以通过调用HTTPMsg内置的指针函数来进行处理,HTTPMsg中封装了很多函数调用,通过这些函数,基本可实现eJet系统HTTP要求处理的各种操作。这些例子函数如下:
......char (GetRootPath) (void vmsg); int (GetPath) (void vmsg, char path, int len);int (GetRealPath) (void vmsg, char path, int len);int (GetRealFile) (void vmsg, char path, int len);int (GetLocFile) (void vmsg, char p, int len, char f, int flen, char d, int dlen); int (GetQueryP) (void vmsg, char pquery, int plen);int (GetQuery) (void vmsg, char query, int len);int (GetQueryValueP) (void vmsg, char key, char pval, int vallen);int (GetQueryValue) (void vmsg, char key, char val, int vallen);int (GetReqContentP) (void vmsg, void pform, int plen); int (GetReqFormJsonValueP) (void vmsg, char key, char ppval, int vallen);int (GetReqFormJsonValue) (void vmsg, char key, char pval, int vallen);int (SetStatus) (void vmsg, int code, char reason);int (AddResHdr) (void vmsg, char na, int nlen, char val, int vlen);int (DelResHdr) (void vmsg, char name, int namelen); int (SetResEtag) (void vmsg, char etag, int etaglen);int (SetResContentType) (void vmsg, char type, int typelen);int (SetResContentLength) (void vmsg, int64 len);int (AddResContent) (void vmsg, void body, int64 bodylen);int (AddResContentPtr) (void vmsg, void body, int64 bodylen);int (AddResFile) (void vmsg, char filename, int64 startpos, int64 len);int (Reply) (void vmsg);int (RedirectReply) (void vmsg, int status, char redurl);......
eJet通过设置回调函数的两种接口机制,将客户真个HTTP要求转交给特定的运用程序来处理,充分利用Web开拓的各种前端技能,扩展运用程序与用户前真个交互能力。