http cache的存取管理实现类,采用了高度封装,所用的所有类在头文件都只有前置声明,头文件定义了一系列的函数来供外部模块使用,
这些函数的第一个参数都是被封闭的内部类的指针,在实现中,对指针指向的对象进行操作(这些操作也一般是 内部封闭定义实现的)
例子:
.h定义了class Interface
在.cc中实际定义了Implement 继承自Interface,
并且定义了一些方法 Interface_do(Interface* A)
其实现为: (implement *) A -> do().
实现有些类似于存储设备与内存之间的映射同步,每个被存储或者将要存储的cache都在内存中有一个相应的结构体cacheEntry来进行映射,
而所有的cache的集合则映射为 spdyCache这个结构体,不过该结构体实际功能较少,只负责全局性质的cache 初始化,同步等.
cacheEntry有3种不同状态,对应3个不同阶段:
(1) 正在cache http request的header
(2) 正在cache http response的header
(3)正在cache http response的 content
cacheEntry会维护两个fd, rfd和wfd,其中,rfd用于如果cache文件已经存在的话,用于读取此文件的fd,而wfd则是如果cache文件不存在,或者是
准备更新此cache时,会创建一个临时文件用于将新的cache的信息写入(在全部写完后会通过rename来覆盖掉原来的cache文件),wfd就是对于此临时文件写入的fd.
cacheEntry有一个load方法来将已经存在在磁盘上的cache文件读入到cacheEntry内存结构体中,不过只先读取header, content在以后真正的需要的时候直接读取,load方法在语义上设计的不好,因为load在有磁盘cache的情况下,才会进行上面的操作,而如果cache在磁盘上不存在,那么load会创建一个对应于该url的临时cache文件来对网络响应进行磁盘cache,这其实已经脱离了load的范畴,容易让人产生误解,后来加入了一个load_readonly函数来对cache进行只读,只有在磁盘cache存在的情况下,该函数才work,其实可以考虑将此函数作为load的内部实现子函数的,现在的分离造成了code duplicate.
对于字符串,直接保存在文件中,在读取时会找不到边界,因此在写入字符串时,会先将字符串的长度写入,并且固定占4个字节。在读取时,也先固定的读取4个字节,得到字符串的长度,然后按照长度进行读取。
为了实现,使用了readFully和 writeFully工具函数,
bool writeFully(int fd, const void* buffer, size_t length) 向fd中写入buffer中存储的length字节的数据,因为对于文件write,只有有文件,基本就可以全部写入成功,因此,
如果没有完全写入length长度数据,那么认为writeFully失败
int readFully(int fd, void* buffer, size_t length) 从fd中读取length长度的数据,存入到buffer中,因为read文件不一定可以read制定的length那么多,所以此函数返回的是实际read的字节数
readint32函数的实现直接使用了readFully: readFully(fd, i(uint32), sizeof(*i))
为了对已经被cache的cacheEntry进行更新,cacheEntry增加了fork(), 作用如名字,就是将当前cache的磁盘文件完整的copy一份到临时cache文件,并且内存中的cacheEntry的状态要变为 REQUEST_HEADERS, 内部的headers也要清空,表示此cache已经失效,需要真正的发出http请求来的到最新的response, 而更新的基础则是fork出来的这份临时拷贝,最后更新完以后,这份临时拷贝会覆盖原来的旧cache文件.
load_headers就是对已经存在磁盘的cache进行读取,将其存储的header信息保存在cacheEntry自己的headers中. headers是一个双重角色,在cache存在有效的情况下,headers保存的是从cache读出的信息,而如果cache无效或需要更新,那么headers保存的是真正的http response回来时所携带的header信息。
两个重要的阶段标示函数: start_response() 和 start_response_data()
start_response() 代表开始等待http response的header的到来,首先要断定此时cacheEntry的state一定是 RESPONSE_HEADERS或之前,
如果state已经是RESPONSE_HEADERS, 那么这个状态不需要改变,因此直接返回即可,但如果是之前的状态,那么就要做相应的状态切换:
首先要看此cacheEntry是否已经被cache,如果已经被cache了,那么就从cache中读取resp的header到内存中的headers,如果没有被cache, 那么就向临时磁盘cache文件中写入空字符串来表示开始写入header, 并且要清空内存中的headers,因为要等待真正的网络响应中的header的到来。
start_response_data()代表开始等待http response 的content的到来,首先要检测cacheEntry的state,如果不是RESPONSE_DATA状态,那么就需要将状态切换到该状态,
因为RESPONSE_DATA前必须经过RESPONSE_HEADERS这个状态,因此必须先调用一次start_response(),然后,如果wfd和 rfd都是有效的,那么说明此cache是需要更新的cache(因为旧的cache文件存在), 那么需要更新一下保存在headers中的header(新的http response会更新某些header值,比如304会更新age),如果此cacheEntry的cached为false(表明此磁盘cache需要更新), 那么就将更新过的headers写入到临时cache文件中,以在后面更新对应的磁盘cache文件.
个人觉得这个两个函数的设计不是很好,状态切换应该是一种被动触发式的过渡,但是在这里某些地方的调用变成了一种主动式的调用, 这样很容易造成状态混乱,并且以后维护扩展时,是否改调用此函数,也不是很好判断,不过这两个函数其实做的事情不多,算是简单的轻量级函数,因此还可以容忍, 这么设计的好处就是可以不遵循某种固定的状态切换顺序,比较灵活.
并且两个函数也都是在同时考虑了磁盘cache可用和不可用的情况,而可用和不可用情况下,行为差别很大,拆分一下可能更合适。
start_response本身语义上感觉是在等待http response的到来,但实际还包含了如果cache存在,就从cache读取header信息的操作,似乎时有些歧义的.
比如:start_response()即在entry_needs_revalidate()被调到,也在entry_add_response_header()被调到,而前者的前提是磁盘cache有效进行查询,后者的前提是磁盘cache无效需要更新,如果能区分开,在code逻辑上更清晰一些.
同上,begin_headers名字起的也不好,这个函数做的实际操作就是将entry内部的headerIterator指向headers的开头,以进行下一步的读取header,说白了就是读取header的操作,只不过是磁盘cache有效时,才真正有东西可读,因此begin_headers改成 prepare_read_headers更为合适,不过考虑到其是内部函数,而外部包装的函数名字是entry_read_response_headers,倒也可以接受.
TurboCache作为一个cache整体的内存抽象代表,其实真正做的事情不多,只是一个对外的简单接口罢了,主要的操作是init和sync,
init其实就是创建了存放cache的文件夹(如果没有的话),而sync则是读取当前已经存在磁盘的cache情况,进行check,通过的cacheEntryId装在added中,而check失败或者超过了最大cache存储数量的cache则被删除,并将其id将在removed中,然后发给server,告知本地cache的分布情况.
后来又增加了add_page_id和remove_page_id,本质也是增加和删除对应的page_id对应的cache.
对于cache的删除,有两种选择,一种只是单纯的删除此cache对应在内存中的cacheEntry,直接delete,并且会关闭相应的rfd和wfd, 另外一种是evict,不仅内存Entry被删除,磁盘上的cache文件也会被移除,是真正的删除cache.
headerCacheable名字起得也不好,看名字像直接判断某个header是否可以被cache,但是其实是根据respone的header来判断此http respone是否可以被cache,对于cache-control这个header,如果其值是no-store,那么就是不可cache,如果状态是 200, 301~304,那么可以cache(这一点和规范不一样,但是是为了和服务器保持一致),
因为目前不支持http range操作,因此对于有range header的response也不cache
在对已经存在的cache进行更新时,在接收完http response的header以后,也就是start_response_data真正开始接收data前,要用收到的headers对旧的cache进行更新,更新哪些需要根据http resp的状态码来决定,如果是200, 那么根据规范,原来的整个cache都可以被覆盖,而如果是304,那么只需要更新一些header就可以,而content则直接copy原来cache的就可以。
在写入header时,将header的名字和value分开以writestr()写入.
add_header函数用来将某个header加入到cacheEntry的headers中,在加入前要用headerCacheable判断,其实也有些不合适,因为之前说过了,headerCachable是用header来判断http resp是否可以被cache,不过在实际使用时,如果add_header时因为headerCachable判断失败,那么也会直接放弃并且清掉之前的cache,倒也不错,但是从直接使用上看,是不合适的。
add_repsone_data(const void* buffer, size_t length, bool finished)函数的操作就是将实际的收到的http resp的content写入到wfd代表的临时文件中,content数据存在buffer中,写入length个字符,最后有个重要的属性,就是finished,代表http resp的content已经全部收完,是写入到wfd的最后一批数据,在这最后一批数据写完以后,就会直接调用finish()函数进行最后的收尾.
finish函数执行最后的收尾工作,根据回复的statusCode也有不同的操作,如果是200,那么之前的旧cache已经完全被覆盖,而新的http resp的所有信息已经被写入到了wfd,因此不用做什么copy的操作,而对于304,因为content是直接使用旧cache的content,因此在这一步的时候,要将旧cache的content全部写入到wfd代表的新cache文件,这一步为了提高效率,直接使用了sendfile函数,直接在内核态中读出写入,不需要write和read这种内核态用户态切来切去。不过注意的是sendfile在android2.2并没有,在全部的数据都已经写入到wfd以后,执行fdatasync来保证这些数据真正的写到了磁盘上,然后就可以关闭wfd,再将wfd代表的临时cache文件重命名为原来旧cache文件的名字,这样就实现了磁盘cache文件的更新,同时此cacheEntry的cached也设为true,同时为了patch,还要更新turboCache的page_id map,代表此page_id标示的cache已经存储在本地了.
get_age()获得此cache的寿命,有两种情况,一种是已经存在了可用的cache,那么此cache的basetime就是旧磁盘cache的st_mtime(上一次内容修改的时间,其实就是该cache创建时的时间), 否则,basetime就是默认的0,那么在load_header时,如果cache是可用的,如果basetime是0,那么basetime就取当前的时间,而实际load出来的age就是 age本身的值加上 当前时间减去basetime, 这样, 如果是用之前的cache,那么age就是本身cache的age加上在本地存储的这段时间,
在get_age时, 先从headers中找到age,然后就用 time(NULL) – basetime + age的值(如果找不到age header, 那么age值为0)。
另外一个关键的函数needs_revalidate(ImageQuality iq),(前提条件是已经接收到了http resp的header,所以要保证调用start_respone())就是判断此磁盘cache是否需要重新validate,iq标示当前选择图片质量,如果此cache的图片质量和iq不一致(因为图像质量有很多种),那么就需要重新validate。
然后就是要获取max_age, -1代表没有能标示max-age的header, 0代表需要revalidate此cache(max-age = 0在规范中就表示强制revvalidate),>0的值则代表可用的max-age,
如果有cache-control或者pragma这个header标示了no-cache/must-revalidate(no-store不需要判断,因为在add_header时就砍掉了), 那么必然revalidate, 然后就是看是否有”s-maxage”/”max-age”,如果有,取其值,没有则返回-1.
RFC 2616:
age
The age of a response is the time since it was sent by, or
successfully validated with, the origin server.
Cache-Control: max-age=0
to force any intermediate caches to validate their copies directly
with the origin server, or
Cache-Control: no-cache
to force any intermediate caches to obtain a new copy from the origin
server.
The Cache-Control: max-age directive was not properly defined for
responses. (Section 14.9.3)
Alternatively,
it MAY be specified using the max-age directive in a response. When
the max-age cache-control directive is present in a cached response,
the response is stale if its current age is greater than the age
value given (in seconds) at the time of a new request for that
resource. The max-age directive on a response implies that the
response is cacheable (i.e., "public") unless some other, more
restrictive cache directive is also present.
If a response includes both an Expires header and a max-age
directive, the max-age directive overrides the Expires header, even
if the Expires header is more restrictive.
如果没有标示max-age的header,那么就要考虑expires, 如果expires比当前时间早,那么就需要revalidate,
get_age函数结合age返回了此cache的最后有效时间,和max_age比较。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
Winform也可以这么好看? 对于Winform很多人的刻板印象就是拖拉拽,简单生产界面,但是这样对于界面的效果,它并不会很好,虽然简单,快,但是效果也是极差,所以有很多人就去使用WPF,去写xml的语法写界面,但是我个人非常不习惯这种xml的写法,但是有时…