spdy_proxy 是核心角色,侧重于和clients这一端的连接管理,负责监听接受有需求的http client发来的请求,
并且为该client创建相应的信息管理(userData),对一个http client的服务的开始和结束都在spdy_proxy完成.
spdy_proxy采用的是状态机编程思想,概括起来就是在合适的阶段执行合适的任务,根据任务的执行的情况来进行下一个任务的选择。
对于出现了错误例外的case,则采用了隔离舱的思想,在某个任务阶段出现的错误,尽量将其压制在最小的影响范围内,
比如,某个错误只会对某一个http client的服务造成影响,那么就只将对该http client的服务进行中断撤销和清理相应的空间。
而如果某个错误对所有的http client的服务都造成了影响,那么就将整个proxy服务进行撤销和清理。


<1> stream_userdata 的 buffer 和 end_of_file在两个阶段扮演不同角色,
 在接受http request时, 用于存储http request和标示http request是否已经接受完,
 而在将http request(包括content)全部接受完并且放入了spdy的发送缓存中,那么将用于存储http response的内容已经标示是否已经将http response全部发完. 个人觉得这样混用很容易出问题,分成两套或者加一个标记最好.


<2> http_send和http_recv对EAGAIN所有不同处理策略.


<3>对于操作返回的结果标示不一致,有的是0代表成功,1代表失败,有的是>=0代表成功,<0 代表失败,turbo_read_callback是给binarybuffer使用的回调读取函数,因为binarybuffer的结果标示和本模块不一致,因此此函数的结果标示和本模块就不一致.


<4>TurboCache 在此编译模块中一直都是指针的存在形式,所有操作也都是用spdy_cache.h提供的外部函数将TurboCache指针作为第一个参数传入进行操作,
而sody_cache.h中对于TurboCache也只有一个Class声明,实现了对TurboCache的高度封装.


<5>stream_userdata的标示过多.


<6>http_getline中的line->append((const char *)data->buffer.data(), cr);, 还没见过这种用法。
<7>stream_userdata的turbo_can_read/write 从字面意义上看和turboRequest的关系更大,但其实实际中和stream_userdata关系紧密


<8> start_connect_thread中的pthread_create 时的一个常识,传入的最后一个参数void*(connect_thread_data* data = new connect_thread_data;), 一般都是要new出来, 然后在create出的线程中delete(如果再栈内分配,等线程起来时,调用 pthread_create的的函数可能已经退出,对象已经析构), 注意如果pthread_create失败,要在本函数释放data。当然,data搞成全局也行.


<9> 每个http_clients都有一个自己的stream_userdata, stream是在spdy的概念,代表着一次http交互, 因此对于一个http client来说, 就代表着一个stream(此http client的keep-alive在spdy中是无效的),spdy只建立一条TCP connection到远端服务器,
所有的http client请求都转化为spdy的frame通过该TCP连接发送出去,
MAX_CLIENTS规定了最多能同时为多少个http client服务, 每个http client都会对应一个 0到MAX_CLIENTS-1的index,这个index是在
接收http client时就为其分配了,可以循环分配,
同时,还有两个特殊的index: SERVER_INDEX 和 TURBO_INDEX,
index的作用就是标示, 因为使用了ppoll这种IO服务的方式来做数据的接收和发送,而ppoll则需要pollfd这种数据结构来进行操作,
考虑到pollfd本身size小,并且最多有NFDS = MAX_CLIENTS + 2 个 pollfd, 因此直接开一个静态的全局pollfd数组(大小就是NFDS)
是可以接受的(这样也方便了ppoll的使用,因为ppoll要求一个连续的pollfd数组,如果每次new的话,很可能会是离散的),
pollfd数组前MAX_CLIENTS个成员是供http client使用的,而SERVER_INDEX = MAX_CLIENTS + 0则是
由server_socket(该socket 一直在listen spdy_proxy的监听端口,监听是否有新的http client到来)使用的pollfd,
TURBO_INDEX = MAX_CLIENTS + 1 则是为turbo_socket(该socket就是建立的对外的tcp 连接的socket)使用的pollfd,
同时,除了pollfd数组外,还需要一个stream_userdata数组来保存每个http client的信息,但是,考虑到stream_userdata占的空间
比较大,因此, 直接开一个静态的stream_userdata数组有些浪费,但是为了能够根据index取到某个http client 的streamdata,因此
需要一个stream_userdata的指针数组clients[],这个数组初始为0,只有在有新的http client到来时,才会new 一个 stream_userdata,然后让此clients[为该htto client分配的index]指向此new出来的stream_userdata.


<10> index在这里成为了http client的唯一标示,并且贯穿始终。
index是在new_client()进行分配的,遍历clients[]数组,直到找到一个index,使得clients[index]为空,代表着该slot还没有
被占用, clients[]可以理解为为http client提供给服务的slots, 每个slots有一个本系统内的唯一编号(index), 每次有新的http
client到来时,就看看有没有空的slot给其使用,如果有,那么就将此服务slot和此http client通过 stream_userdata绑定起来
(其实是accpet此http client时得到的读写socket fd, accept的过程是在server_event()中处理的),如果没有,那么就直接
拒绝该client, 同时不再监听新的client的到来,直到有client离开,有可用的slot.


<11> spdy_proxy的入口函数是proxy_ppoll()(该函数其实是在java层以while循环的方式不断驱动的), 在每一次真正的poll之前,会将之前已经托管给connect 
thread的fd代表的client释放掉,因此由connect thread 托管的client已经不在proxy这个体系中,所以要清除
关于其的一切资源(除了不能将该代表client的fd关掉), poll的timeout参数为-1,无限期等待事件的道理啊,而监听的数组就是之前预分配的fds数组,
只不过并非fds的每个polldata的events都设了值,只有对某类事件有需求的client才会将自己的index所标示的fds[index]的polldata的event设置上自己需要的值,
polldata的event在这里就像是client每次提交的需求订单,可以不填,也可以根据自己的需要填写。如果ppoll返回-1,那么就代表整体出了问题
(某个polldata出问题,是不会导致ppoll返回-1的),整个proxy都会结束(因为这意味着整体都完蛋了,所以必须全部结束,等待下一次的完全重开),
在我们的实际实践中,唯一遇到的一次ppoll返回-1,是在某些设备上ppoll这个函数没有被真正的实现,因此第一次就直接返回了ENOSYS,
解决方案就是在这种情况下,回退使用了poll(和ppoll的区别在于不能指定sigmask,不过可以通过调用sigprocmask来模拟,虽然会有很小概率的问题,
不过因为实际中,sigmask的输入其实是一个空,因此在这种场景中,poll和ppoll是一样的),在本实现中,ppoll其实是通过syscall函数输入__NR_ppoll来实现的。
如果ppoll返回的>0, 那么就说明当前有某个/些client关心的事件发生了,那么,就要开始结合这些事件和相应的client进行操作了,因为是监听了整个fds数组,
因此必然要遍历整个fds数组来一一检查其revents, 而该fds数组大小默认开的是64,在实际使用中,发现,其实对于很复杂的网页,也最多只会有不超过20个
client同时连接过来,这就意味着其实这次遍历检查有2/3都是冗余的,因为根本没有client在该位置上,不过考虑到实现以及性能,这种损耗是可以接受的,
在进行检查时,如果某个fds[index]的revents不为空,那么就说明该index标示的client是有事件发生的,那么,就先把其events置为空,代表着一次订单服务的结束
(客户写订单->订单要求的物品送到->清空客户订单以便客户下次填写)。然后,如果revents中有POLLHUP/POLLERR/POLLINVAL, 那么可以认为对该client的服务发生了
某些问题,在此直接将该client释放,中断其服务(为什么不重试的原因是,这种错误一般都是不可修复错误,比如网络I/O,以及fd无效,进行重试的意义不大,
这种错误是发生在TCP或者系统层面的,和发生在HTTP层面的错误不一样),而如果出错的不是client的index,而是turbo fd的index,那么代表着turbo connection这条唯一的对外tcp连接出了问题,那么就先将turbo connection所代表的资源进行释放(disconnect_turbo),但注意的是,这并不代表着turbo_proxy这个
服务的全部结束,仅仅是一次turbo connection的重置,后面如果有新的client发来请求,会进行重新尝试连接,不过在这中间传输数据包应该是不可挽回的,因此
disconnect_turbo还会将目前所有的client的连接都强行中断(已经没有维持的必要了,反正已经丢失了数据的),如果是server_socket这个监听client连接需求的fd
出了poll的问题,那么就直接exit将整个进程干掉(在android中,因为是bind service, 还会重启)来reset整个turbo_proxy.
检查完polldata错误以后,就要处理每个polldata的有效revent了,这里就进入了各种的功能中,根据当前client所处的阶段以及返回的revent来进行相应的操作
(在合适时间干合适事情)
在轮询将所有的client/turbo/server fd的polldata事件处理一遍以后,最后还会调一次对turbo_connection的写入(本质是将数据用socket发出去),个人觉得
这一次的调用有两重作用:1:在之前处理了一系列的polldata的revents以后,很可能本来满的turbo_connection的socket的tcp级别的发送buffer又可以填入数据了
来进行发送了,那么就不浪费此次机会 2: 通过这次socket的写入,可以在这次循环的终点判断一下turbo connection socket的有效性(如果不主动调用读/写的话),
是检测不出socket当前是不是还有效,后面的code也说明了这一点,在调用完写入以后,会检查 turbo_connection的fd是不是还有效(>-1, 如果写入的时候出了
socket error, 该fd会被置为-1),如果无效,那么可以认为turbo connection此时已经故障了,应该disconnect_turbo, 等待下次的重启。
整个turbo的运行模式就是这样的不断的循环IO复用 poll的过程,每一次循环,将该做的事情做了,然后等待下一次循环的到来。


<12> 上面介绍的是运行的主体->poll循环, 但是显然不能一上来就开始循环,初始化的过程是不能少的,这就是init函数的任务,该函数是turbo模块的第一个被
调用的函数,做的工作也都是初始化,这个函数还是java层通过jni调用的,java层会传入一些java层的信息(比如认证, cache目录的路径, mcc. mnc等),该函数首先将java传递的信息与函数都存储在本编译模块中,然后就是 忽略 SIGPIPE, 设置umask(0077), 开启server_socket,并使其开始listen,同时
将server socket对应的polldata设置,监听POLLIN,并将server_maxed设为false, 如果指定了监听端口的话,那么用setsockopt将此server_socket的port指定,
在bind获得了系统为其分配的socket_addr_info以后,获取系统为其分配的listen port, 在最后会将此port返回,一直返回到java层,这样java层的client才知道
向哪个端口发送connect, 同时还会将cache进行初始化, 就是调用了cache_init函数,构建turbo cache在内存中的对象,并将对给定的目录路径进行检测等。


<13>除了上面的初始化意外,还有一个操作是可以在poll循环之前做的(也可以真正需要的时候做),就是建立turbo_connection连接,这个过程不但包括了建立tcp
连接,还包括在此基础上建立(或者说是初始化认证)spdy 一层的连接. 在进行socket连接前,
有一件事情要做的就是检测系统的网络当前是否有代理存在(对于手机来说,其实主要就是中国特色的wap网络,一般都是有代理的),网络有无代理是有区别的,
而获取代理则是回调到java层取获取的,在获得代理的信息以后,将会调用另外一个模块的turbo_connect接口将同turbo server的socket连接建立起来,并将HELLO 
frame放入到turbo_connection的发送buffer中,然后就是一位循环不断的写和读turbo_connection,来检测是否已经将HELLO通过当前的socket发送出去,并且收到
了server的回应,在确定了建立了稳定的spdy连接以后,就将turbo 对应的 fd的polldata进行设置,监听POLLIN, 同时将用户的一些信息告知turbo server,
比如设置的图片质量,使用了哪些turbo feature,以及其cache目录下已经存在哪些cache了等,最后返回0代表成功,否则就是-1失败。
 该函数除了模块内部调用外,也可以由外部java层进行触发。
 
<14>一个很重要的概念, proxy 本身 和 connection是分开的两个模块(connection有自己单独的编译模块), connection不存在对于
proxy本身其实应该没有影响,或者应该是一种很松耦合的关系,connection失败不代表这整个proxy都不能工作(当然,proxy的主体工作是要依靠connection的),
因此,disconnect_turbo 和真正的proxy都被关掉其实是不一样的,connection被关掉或者故障,proxy还可以存在,并且可以后面重启connect来建立connection。


<15> 在循环遍历polldata时,会用process_fd_event(index) 来对相应的fd进行处理, 首先通过index获取了对应的fds[index]的pollData的指针以后,
就可以直接将pollData指针传入相应的处理函数了:3种情况:
server_socket_fd: 处理最简单,server_socket从始至终只有一个任务,那就是不断的accept client发过来的tcp client请求,在accept以后,为其在clients[]数组
中找到一个空位,即为其找一位"服务员"(有效可用的index),new 一个stream_data来保存此client的相关信息,并且将其相应的polldata的fd和events也设置上,
这样才能在下一轮的poll中对其进行监听相应事件, 有一个情况是因为最多同时只有64个client接受服务,因此,如果超过了64,找不到可用的index,那么,server
socket将会暂停接受新的client的请求(server_maxed),其相应的pollData将不会监听POLLIN, 只有在有client离开以后,才会重新监听POLLIN. 不过在实际使用中,
从未出现过这种情况。而因为每一轮poll结束时,都会将每个polldata的events清空(清空订单),所以如果server_socket还要继续监听的话,要重新将自己的
polldat的events重新设上POLLIN(为新一轮的服务填写自己的订单)
turbo_socket_fd: 因为turbo_socket 和turbo_connection紧密相关,因此在处理turbo_socket时,turbo_connection应该是有效的,这样才有非错误的POLL事件,
首先,如果revents里有POLLIN的话,那么就说明turbo server回复了一些信息给proxy,需要进行读取,然后就是处理过期的push过来的stream,最后,只要有非
错误的POLL事件回来,就处理push过来,还没有被接收的stream,以及尝试对turbo_socket进行一次写入来发送数据,如果之后发泄并没有发送出所有的数据,那么
还要在下一次监听POLLOUT来讲剩下的data发送出去,最后,无条件的设上POLLIN(因为server端一直有可能有数据过来,我们也没有理由不接收)。turbo_event其实
是最复杂的,不过因为其主体实现在另外的模块中,因此这里就简单概述一下。不过有一点比较模糊的就是为何没有POLLOUT到来,也要尝试进行写入,有可能是为了
下一次的poll将POLLOUT设上,但是调用 write进行尝试,然后返回>0的值来触发设置POLLOUT,似乎有些过了,直接检查发送buffer的size,也可以,
难道是前面有POLLIN调用turbo_connection_read以后,有可能腾出可写的tcp buffer?或是前面的耗时长,在昨晚以后,tcp发送buffer有可能可写? 是一次尽力而为
的写入?
client_socket_fd: client的处理函数看上去很简单,只调用了一个函数罢了,但是其实却是最复杂的状态机模式,调用的函数是由stream_data的一个函数指针指定
的,每个函数在运行完以后,会根据当前的client的情况和状态,将其函数指针保持原样或者这位下一个状态的处理函数,这样实现了状态机模式的编程,
这些函数的输入和输出参数都一致(为了匹配函数指针),并且都遵循失败的话,返回1,成功为0的原则,如果此次调用的函数出错,那么就中断此client的服务,将
其断开disconnect, 释放socket 和 fd,不过代表其本身的stream_data的内存资源则在稍后才回真正释放,这也体现了client本身不一定以来与socket存在的概念。


<16> 工具类函数http_recv, 其作用就是从client指定的fd读取数据到client的databuffer中 ,因为使用了binaryBuffer,因此此函数也只是对binaryBuffer调用
sock_recv函数作为读取工具的一个封装,同时负责更新client的 total_received和出错处理, 而sock_recv其实就是系统的recv, 不过预设了MSG_DONTWAIT这个参数
注意的是,因为在调buffer的readFrom的时候,将client的fd转为(intptr_t 其实是int或long int, 根据cpu位数决定,不要被名字迷惑,值得是指针地址的长度,
而本身不是一个指针),再转为void*传入的(这个转换估计是为了普遍性,不用void*的话,很多其他类型就传不进readFrom了),因此在sock_recv时要转回来。
个人举得sock_recv如果将recv的错误处理也封装,其实可能更好,因为buffer本事对于readFrom使用的read_callback函数参数的细节应该是不需要了解的,不需要在
read_from中分析read_callback失败时系统的errno值,这个操作应该放在read_callback里面,read_from使用read_callback应该只关系callback的成功与失败。


<17>工具类函数http_send, 直接调用了自己封装的send函数,将client的buffer存储的数据通过socket fd发送出去,加了一个自定义的send原因是,send发送的数据
是存在binarybuffer中的,不能直接作为系统send的参数,需要将其数据的指针提取出来,并且在发送成功后,还需要手动的调用binarybuffer的consume来更新
buffer存储数据的长度,因为这部分发出的数据没有走binarybuffer的内部接口,所以binarybuffer是不会自己更新自己数据边界的,需要调用者来更新,http_send
调用了封装的send以后,还要检查send的结果,因为send也是MSG_DONTWAIT的,因此可能面临发不全或者发不出的情况,这时候还要判断EAGAIN和EWOULDBLOCK这些临时
错误,这种情况下,认为发出了0个数据即可。send的层次相对就比较好,系统send负责真正发数据,自定义send负责适配binarybuffer到指针以及binarybuffer的更新
,而http_send则只负责对于实际send的结果检查并返回合适值即可。三层分工比较明确。


<18>stream_userdata的拷贝构造函数和拷贝复制函数被private了,因为stream_data本身含有不少需要深度拷贝的成员,为了稳妥起见,禁止了这两个函数。


<19>http_begin一般是任何一个client在被接收以后的第一个处理函数,其操作也相对的简单一些,就是将http request的第一行提取出来,并且根据第一行所
携带的http命令类型, 请求的url 已经http的版本进行相应的操作, 因为是socket进行接收,因此一定不能保证在第一次调用http_begin时,第一行所有的内容
都已经从client传输到proxy这边了,因此第一个判断是该client的polldata的revents是否是POLLIN表示有数据到达,如果是,那么就进行http_recv来接收client
发来的请求内容到client的buffer中,如果http_recv因为某些不可逆的socket error失败,那么直接中断对此client服务,在读取到数据到buffer以后,因为http
request的规范规定了一行结尾的是\r\n, 因此对buffer中已经有的数据进行检查,看是否已经出现了完整的一行,如果有,那么这一行必然就是http request的
request line, 使用http_getline函数将这一行的内容复制到一个string对象中以方便进行后面的分析,Http类的parse_requestLine会讲request line包含的
请求类型 url 已经http version都提取出来,在得到url以后,还可以check一下该frame是否是mainframe(即某个页面的主体,对应的stream在server端的优先级会
提升),同时这些信息也都会保存在该client的stream_userdata中,作为在后面处理中的上下文使用, 如果确定是正确的http请求类型,并且 不是http connect
请求,那么会做一个检查,对于某些url,是不应该走turbo的,而是应该直接连接过去,对于这种url,turbo会对其进行bypass,使其不通过turbo_proxy而是去直连,
如果不需要bypass,那么就真正的需要turbo_connection来对其进行服务了,前面说过,turbo_connection不一定保证一直在可用状态(因为是基于socket,而网络本身
就是不稳定的,尤其是手机),那么这时候就会运行一次connect_turbo()来保证turbo_connection是可用的,如果这一次失败了,那么有两种善后方案,1是只中断对该
client的服务,2是将整个turbo proxy服务exit 然后重启,在这里选择了第2种,因为对于不是频繁变化的网络,这一次connect_turbo失败,下一次新的client失败的
概率还是会很大,如果一直这样,会造成turbo proxy服务不可用,但是却又不能选择直连模式,就会一直上不了网,是很糟糕的体验,而将整个service断掉重启,
在下次重启时,也会尝试connect_turbo,如果失败了,就会使用默认的直连模式,好歹保证了用户可以上网。因此在这里选择了exit整个服务,下一步也是比较关键的
一步,之前提过,proxy 和 connection 其实关系比较独立, 而proxy如果需要使用turbo_connection的话,对于每个client来说,是需要一座桥梁将其本地的信息
stream_userdata 和 turbo_connection能连接起来,这座桥梁就是TurboRequest,每个client都有自己的turboRequest,作为一个在stream_userdata和 turbo_connec
tion之间的中介,因此,在http_begin这一步,再为client开始服务时,就需要为其分配一个turboRequest对象,该对象这里设计为每次动态生成,并且stream_userda
taz中会保存一个指向其的指针,如果分配request失败,那么就中断client的服务,如果该client的请求url属于前面提到的mainFrame,就需要为其加一个特殊的自定
义header(注意,这里的添加是指添加到要发出去的该http转成的spdyFrame中,不过header的范畴在http和spdy中都是一样的),同时因为spdy frame将request line
的三部分也都变成了header,这里也需要将request line的信息作为header加到spdy frame中,这里的添加header,先只是添到stream_userdata中的一个headers类
对象中保存,在最后真正写入的时候,会从此header对象中将http request的header全部拿出来写入。而分配的turboRequest也需要知道自己所服务的client的stream
_userdata,因此turboRequest中会有一个指向该stream_userdata的指针,stream_data和turboRequest的关系是一种双向关联,这种关系个人觉得不如单向连接安全,
不过也没想到更好的方案,是否可以在这两个类之上加一个类C,该类连接这两个类,而这两个类本身不保存对方的引用,只保存对C的引用,而C在最后释放,保证了
指针安全。如果http request类型是GET,那么就可以检查当前是否有合适其使用的cache,这里只是做一个初步的url匹配,至于新鲜度等检测,则留在后面,这是因为
该http request的header还没有得到,某些header对于cache是否可用是有影响的,因此必须等到所有的header都读完。如果reuqestline已经分析处理完,那么标志着
client的这一阶段结束了,可以前往下一个节点,其http_event函数指针就会指向新的下一阶段的处理函数 http_read_headers, 而因为此时可能client的buffer中
已经有了header的数据了(socket收到的数据是随机大小的),完全可以直接执行http_event指向的下一阶段,直接return 执行http_event指向的新函数。
回到最开始,如果当前buffer没有完整的一行,但是却已经超过了requestline的最长长度,那么可以认为此client的http request是无效的,直接中断服务。
否则,只能继续等socket接收来自client的数据,这时候,client的等于是没变的,因此不需要替换http_event, 不过因为之前polldata events清零的原因
(订单清空),这时候要重新设上POLLIN来监听后面的数据(填写下一次需要的订单)
还有例外的情况是如果收到了除了POLLIN之外的事件,比如POLLOUT,但是因为这一阶段,根本不需要监听此类事件,因此可以认为其状态已经错乱,直接停止中断。


UNP有一个观点,就是在读取socket时使用buffer,是一件很危险的事情,因为这种情况下,多了一层buffer,socket的某些事件,比如已经全部读完,很可能检测不到
,不过这里这样用,是可以接受的,因为http requet是有结束的标志的,因此风险会小。


<20>http_read_headers和http_begin的大致逻辑类似,只不过http_begin只读一行就够了,而http_read_headers则要把所有的header(每个header是一行)都读完,
还是先check是否revents有POLLIN, 如果有,那么check是否够一行,如果够一行,那么提取这一行,如果该行是空行,那就代表headers全部读完了,headers结束的
标志就是/r/n/r/n, 直接运行http_headers_done函数来进行到下一状态(直接运行的原因是因为该函数不需要等待socket的IO,可以直接进行),因为http header的
name的大小写在规范中规定没有不同,因此在处理header之前要将header的name转为全大写/小写, 对于某些特殊的header要做处理:
content-length: 有此header表示在headers读完以后,还需要继续读取数据,如果value < 0 ,那么可以认为是一个无效的http request,停止服务client。同时
stream_userdata会有两个成员保存content-length的长度以及是否有
transfer-encoding/te: 这代表使用了传输编码 chunk方式,turbo暂时不支持,因此直接停止服务client,这类请求不多,不过更稳妥的可能是对该http_request进行
bypass
expect: turbo不支持,停止服务。
proxy-authorization: 该只有在requet是 http connect,并且url 是opera:control的情况下,才回对该请求进行认证, 对比init时传入的password和value,如果
不等,那么is_auther为false,否则为true
if-modified-since: 因为turbo全部使用etag来进行revalidate(如果本身response没有,那么turbo server会加),该header直接忽略,不存储在stream_userdata的
headers中
refer: 如果本身不是mainframe,那么这次当做mainframe处理,加上自定义header
user-agent: 为了简版网页,对于某些url,会讲ua做修改
在该header通过上面的判断以后,如果data是分配有turborequest的,并且不是一个hop_to_hop的header,那么才回将其写到stream_userdata的headers中,
并且将此header加到cacheEntry的request headers中, 如果cache保存header失败,那么就将cacheEntry删掉,并且此client在有response以后也不会保存到cache,
因为此时cache已经释放了,这样做应该是出于cache完整性的考虑,只要保存cache的过程中出现过一次失败,那么就认为该cache是不完整的,是不应该被保存的。
如果当前数据不够一行,那么就继续http_recv来接收数据,如果http_recv返回值<0, 可以认为http request已经接收完了,也可以直接调用http_headers_done。
如果> 0 ,那么可以认为还有数据可读,就回到最开始的逻辑重新判断是否有完整一行可以处理,如果返回0,那么可以认为此时tcp接收缓存里并没有数据可读,
这时候就只能等待下一次poll循环监听到POLLIN事件了,重新将polldata设上POLLIN,等待下一次。


<21>http_headers_done顾名思义就是http header全部处理完以后应该做的操作,首先会判断一下对于control的frame的认证是否已经完成,如果没有通过,直接回复
一个407错误。然后,如果对于某个非connect的http request, 如果其accecpt-encoding header没有,或者不含"*"/"gzip"/"deflate"这些选项,在这种情况下,
因为turbo server会强制的将response的内容进行gzip/deflate压缩,因此这种情况下的请求也需要bypass(概率很小,不过也遇到过)
,不能走turbo,否则会造成http response无法识别的问题。因此这种情况下,就要将其bypass。然后就根据http request的类型进行相应处理:
POST: 如果content-length的长度为0,那么可以认为后面没有数据要读了,这种情况下,就可以调用http_request_done了(标志着所有的处理已经完成)
如果有content,并且此时client的buffer中存储的socket传来的数据的size还小于content-length,这说明后面还需要继续读取以获得content的全部内容,那么
状态就要变化了,变为http_read_request_data了,http_event挂接的函数就变成了这个,同时将turbo_can_write设为true,标志着还有可以写入turbo_connection的
数据,turbo_can_write这个名字起得其实不好,给人的感觉是标示turbo_connection当前是否可以写,其实标示的意思是当前是否有可以写入turbo_connection的
数据,同样要更新polldata订单为POLLIN,如果当前的buffer的数据量已经比content-length大,那么也执行上面的操作,只不过是讲content-length设为0, 否则
content-length设为还有没收到的content的长度,这样在真正执行http_read_request_data才能一视同仁,其实理论上讲,对于收到了多余content-length数据的
情况,是应该视为无效请求的,对于POST请求,但是求没有content-length header情况的,应该视为一个无效的http request请求,中断对其的服务。
CONNECT: http connect 请求有两种,一种是真正的client发出的请求,另外一种则是control command, 为了从java 层控制turbo proxy的某些行为,因为proxy
采用了poll阻塞循环机制,如果java在中间直接控制的话,会打破这种循环,是很粗暴的做法,因为在这里将control command也通过http connect的方式发送到proxy
这样不会打破poll阻塞循环这种机制,对于真正的http connect,直接将其从poll循环中挪去,为其专门开一个线程来建立http connect用的隧道,然后无脑转发即可
. 对于control command,则调用相应的command处理函数。
UNKNOWN:对于未定义(或者说是proxy未实现的方法)直接返回501错误.
其他请求: 直接调用http_request_done来完成收尾工作。
然后就是针对GET请求的cache处理,首先确保cacheEntry在内存的结构体被构造出来,data->cache标示的就是该构造体,不过这个定义其实比较模糊,给人的感觉
似乎是可用的cache,但其实这只是其中一面,如果cache目录存在和url匹配的cache,那么该data->cache指向的就是该已经存在的cache,对于该cache的处理方式是
'读取',而对于那些没有cache文件在cache目录的,也会有这样的cacheEntry结构体,这时候,它代表的就是此次请求的结果将要存储的cache,对于该cache的处理
方式是"写入", 因此在一开始,要调用entry_is_cached()来得到该cache到底是读还是写: cached, 标示是否已经被cache了(在磁盘上存在cache,读)。
然后就是获得此httprequest header中的etag(就是if-none-match,也可能没有),如果是之前被cache过的,那么就尝试获得该cache的etag, 不过除了etag之外,
还有一种情况,用户是可以随时改变定义的图片质量的,如果发现当前的图片质量和cache的图片质量不一致时,cache也就无效了,所以这种情况下,应该忽略etag.
这时候轮到data的accept_304出场了,该变量表示是否可以向client回送304 response,显然,如果client的request没有etag,或者etag和cache不匹配,显然是不
应该向client回送304的(304回复的前提就是request有etag等标示有效期的header)。在有cache存在的情况下,如果cache已经过期的话,是需要revalidate的,因此
要判断cache是否需要revalidate,如果需要revalidate,那么就要调用entry_fork函数将当前的cache先copy一份进行修改更新,如果request有etag的话,那么就需要
将etag加在发送出去的spdy frame中,否则如果还在有效期内,那么就可以直接将cache返回给client即可,对于这种情况,对client的服务已经完成了,因此可以释放
data的request了(设为NULL),并且如果确实的将cache成功的发回给了client,那么就可以直接返回1,来告知可以将client占据的资源断开并释放了。
最后,如果stream_datauser的turbo request还存在的话(前面某些情况已经不需要turbo request服务了,因此可能已经释放了)
那么就认为该client是真正需要turbo request进行服务的,是真正要用到的,这基本就是最常规的流程,要经过turbo server,turbo省流有一部分是依靠图片压缩
为webp格式,因此在发出的请求的accecpt中,要加上"image/webp"来标示, 最后运行turbo_request_send标示将request真正的开始进入工作序列
(主要做的就是将之前收到的http request 转化为spdy的frame,放到turbo_connection的发送序列中)。然后继续return data->http_event,
注意这时候状态已经改变,因此http_event指向的也是不同的处理函数了。


<22>http_request_done,该函数就是之前在http_headers_done中,对于一般的client,最后所进行的一次处理函数,其实就是状态的变化以及相应信息的设置,
函数的做的事情比较简单,首先client的buffer清空(之前存储的http request的数据已经全部放到turbo connection的发送序列了,不再需要保存,并且接下来
http response的data也是在这里保存的),data的turbo_can_write为false,标示已经没有可以写入到turbo connection发送序列的数据了,该client的http
request的数据已经全部守完并且等待发送了,http_event切换到下一个状态处理函数:http_send_data,该函数的任务是讲回来的http response返回给client,
状态也契合,发出了请求,那么接下来的状态就只能是等待回复了,同时因为是要向client的fd回送数据,因此其polldata就不再监听POLLIN(因为不再有数据来了)
而是监听POLLOUT(因为要向该socket fd发送数据)。


<23>http_read_request_data, 该状态函数就是对于还有content数据没有读取的POST请求,在http_headers_done之后的状态,自然监听的还是POLLIN,该函数其实
承担了两个任务,一个是接受client的数据,另外一个是讲接收到的数据填入到turbo的发送缓存中,对于第一个任务,是否需要继续接受,其判断标准就是是否已经
将content全部接收完,如果接收完,data的end_of_file会为true,下次就不会再socket recv,如果还没收完,那么就继续收,知道确认所有的content都被收完,
是否收完是由data的content-length决定的,这个变量的名字起得其实不好,看上去像是content-length这个header的值,那么应该是一直不变的,其实它所表达的是
还没有被接收的content的长度,每次接收到N的content,该值就会-N, 直到content-length <= 0,才算接收完所有的content,所以名字改为 unrecv_content_length
可能更好,在接收完一批content以后,就会试着将这次收到的数据填充到turbo的发送序列中,函数就是http_fill_to_turbo, 比较直白,当前,该函数的调用也是和
POLLIN绑定在一起的,即只有有新数据来,才回调用http_fill_to_turbo, 这么做是没错,因为http_fill_to_turbo可以保证每次全部填充完,不会向socket一样,
不一定填完,因此只有新数据来的时候,才调用将新数据填充,是没有问题的,但是个人觉得,http_fill_to_turbo从概念上讲不应该和POLLIN绑定在一定,如果
其也不保证每次全部填完,那么和POLLIN绑定,是很容易出现,client的数据是全部接收完了,但是却还有一部分没有添到turbo发送序列中,而因为不会再有数据来,
就不会再有POLLIN,这部分数据永远填充不了,就一直这样悬着了,为fill_to_turbo搞一个flag标记是否全部填完可以解决该问题,不过考虑到stream_userdata
本身已经flag泛滥,再加一个就更过了。http_fill_to_turbo的任务就是把当前client的buffer中的数据填到turbo发送序列中,并且负责最后的收尾工作,最后的收尾
的判断标准是client的数据已经全部接收完了(end_of_file), 同时这一次client的data中的数据也全部填到了turbo发送序列中,这时候,http request的所有数据
就填入turbo发送序列了,end_of_file变为false(变换角色了,变为标示http response全部接收完的标示,因此要false), 这时候可以调用http_request_done了,
然后直接调用data->http_event到下一个状态函数,不过个人觉得,这一次调用的http_event(其实是http_send_data)应该是没有意义的(和http_headers_done最后
调用的那一次一样), 因为这时候只是将http request填到了turbo 发送序列中,而turbo的fd poll event处理是在client之后,就是说此时,http request是绝对
不会真正的发送出去的,而只是待在发送序列中,等到处理turbo poll event时,有POLLOUT的话,才有可能被发出去,这时候根本不会有http response到来,
调用http_send_data 有意义的的前提也是有http response过来,因此这次调用应该在服务client上是没有意义的, 不过出于debug的目的,在http_send_data中,
对于这一次调用会调到 ioctl(fd, FIONREAD, &nread),这一句的作用是将还残留在fd的接收buffer中的数据字节数得到,不过也仅仅是得到然后debug输出长度,
对于这一部分废弃数据(理论上讲,正常的http request是不应该携带任何 超过自定义长度的数据), 不过考虑到这部分数据也不会影响后面的response回传,
因此就采用比较宽容的做法,仅仅debug输出一下,不会将其认为是无效的http request而拒绝服务。


<24>http_fill_headers_from_turbo, 该函数发生的时机是response已经通过server返回,要对返回的结果的header进行读取和处理,stream_userdata中有一个标示
位 has_written_headers, 该标示位就表示是否已经将所有的response的header都读取完毕, 而response的header信息会先保存在为其分配的turbo_request的headers
中,因此为了从头开始读取header,要先对headers这个容器做一次rewind(倒带),确保headers容器的iterator指向的是最开头,首先要将response line的信息读取
出来,分别得到"version", "status"(就是http response的状态)等信息,如果根本没有找到status这个必须的header,那么可以认为这次response是非法的,停止
服务client, 否则如果返回的code是304, 那么说明revalidate是成功的,当前cache还是可以使用的, 而对于某些turboserver产生的错误,则要考虑回复一个301
并将该url加入到bypass url中,这样client会重新请求一次,并且被bypass(这种情况是可能发生的,比如某个url,turbo server所在网络DNS解析不出来,而
client所在的网络DNS可以解析出来,这种情况下,是应该bypass该url的),如果server返回的是304,但是之前在http_headers_done中检查etag失败(或是没有
携带etag等类似header)导致client不能接受304响应,同时对于该url的磁盘cache又是存在且可用的,那么这种情况下,不能给client回复304,只能回复200,只
不过headers使用server这次回传的最新header,而response 的content则可以直接使用磁盘cache。如果不是上面这种情况,那么就将turbo server回复的headers和
content数据直接放在data的buffer中(该buffer之前存放http request,现在则存放http response), 对于协议类型 一律使用 HTTP/1.0, status和server回复的
status一致,因为不支持keep-alive(spdy是基于stream复用一个tcp连接,不存在这种概念了),因此要加上 connection: close 这个header, 同时,如果是需要
bypass的url,那么还要为其额外加上"location"这个header以配合301,并且还要加上"cache-control: no-cache" 向指明client端不要cache这一次其实是失败的
response(不需要cache,因为url已经被记录在bypassurl中,下一次会直接将其bypass),后面做的事情就是将server回复的所有的非hop_to_hop的header加入到
client的buffer中,构建成一个http response, 同时如果之前为client开辟了建立/更新cache用的cacheEntry,那么就尝试将此resp的header加入到当前的header中
,在保存header到cache中时,如果出了差错,就要将真个cacheEntry释放,此次cache是无效的,为了cache的完整性考虑,只保存完整无误的cache。在全部做完以后
has_written_headers就可以是false了,headers已经全部读取完毕了,同时turbo_can_read也是true,表明,turbo connection那一端有可能已经有可以读取的resp 
content的数据了,可以尝试读取


<25>http_fill_from_turbo, 该函数就是从turbo的接收序列(已经存在turbo request的buffer中了)中拿出属于自己的http resp的数据,进行该函数的前提是http 
resp的headers已经被提取并且读完了,如果收到的是304 resp的话,那么后面的content理论上将是不需要的,因此读取出来以后,直接丢弃即可(必须读取出来,
否则这部分无用的数据会一直占据这内存),否则,就从request的buffer读取到client的buffer中,然后判断此次读取的返回值,如果 > 0, 那么说明本次读取到了
数据,那么可能turbo request的buffer中还有数据可以读取,而如果< 0, 那么可以认为turbo server回复的http resp已经全部返回了,如果是 == 0,那么则是
本次turbo request的buffer里的数据已经全部读取完了,但是http resp的后继数据还会有到来,会根据这三种情况设置client的end_of_file和 turbo_can_read标示
, 在读取到一批http resp的数据时,如果决定用这批数组来做cache,那么就要调用相应的函数来保存到cacheEntry中,如果写cache的过程失败,那么整个函数返回
-1标示这次读取turbo填充失败,个人觉得在这里cache失败,不需要返回-1表示整个的失败,保存cache在http中是一种锦上添花的行为,一个http resp保存为cache
失败,不代表这这次http交互都是失败的,只是没有cache用罢了,本身返回的http resp是没有问题的,这种情况下,将client的cacheEntry释放即可,表示这一次的
http交互不存cache。


<26>http_cached_response的作用就是将整个cache直接返回给client,这种情况,其实根本不需要借助tubo connection来网络交互取得数据,因为数据就在本地磁盘
上,因此在http_headers_done中,使用cache做resp时,为cleint分配的turbo request就会被释放,意味用不到了,这也体现了之前说过的stream_userdata和turbo connection独立分离的概念,不过,还有种特殊的情况会在与turbo server进行网络交互以后,再使用cache,那就是虽然client不接受304请求,但是turbo server返回来的是一个304,表示本地的cache是可以用的,这种情况下,resp的status code会设为200, 而headers则在cache的基础上使用turbo server回来的304
的新的header,然后调用http_cached_respone, 首先将client的buffer清空来存储从cache构造出来的http resp,固定使用HTTP/1.0,除了检查client是否可以接受
304以外,还要检查一下cache本身是都标示了一些server端当时回过来的error,如果是需要bypass的error,那么就要将此url加到bypasslist中(这种情况是可能发
生的,虽然一般来说在保存该cache的时候,url可能已经加入了bypasslist中,但是还有特殊情况,即某个url的cache在上一次使用时,保存了,而这一次重启程序,
访问该url,因为有一次的的cache,虽然url不再bypasslist中,但上一次使用程序时留下的cache却标注了需要bypass), 并按照之前的将status code设为301,
指定no-cache header等,然后就是读取cache的每个header,将非hop_to_hop的header加到client的buffer中, 然后,在将header全部加完以后,就代表一个状态的
到来了,要开始真正的将这些数据发送回去,会将http_event设为http_send_cached, 监听POLLOUT,一个注意事项是,对于304等status code,后面是不需要跟
content的,但是对于200这种,一般后面是有content的,这一步其实并没有将cache的content读出来放到client的buffer中,只有header,将content读取的操作放在
了http_send_cache中,这样做感觉有些割裂,不过倒也不是大问题,最后还有一个细节是如果是turbo server返回来的304导致的cache_response, 那么此时应该将
turbo request的CAN_READ去掉,表示不需要从turbo request读取数据,来避免无谓的函数调用。


<27> 总结一下cache 和 304的规则: 在client发送的http request的url 有本地的cache, 并且cache的header有etag,http request也有etag的header,并且cache
的图片质量的header值和当前用户设定的图片质量值一致,最后 cache的etag 和 本地的etag相等的情况下, client才能接收为其回复 304,其余情况皆为200.
因为只有在这种情况下,才代表着远端发起http request的client自己还有一份和proxy这里完全一致(最起码是etag,而etag基本就代表了全部)的cache,这样,
回复了304,client使用的数据和proxy这里的cache才是一致的数据(proxy的cache如果没过期或是通过了revalidate,那么就是正确的数据),才是正确的行为。
如果cache过期需要revalidate,那么就在发出的spdy request中增加一个etag的header, 值是cache的header,而不是request携带的header。
是否revalidate和是否accept304 直接没有必然的关系。