校验模型解决了这个问题。在校验模型下,缓存持续保存response。不同的是,对每一个请求request,缓存都询问应用程序缓存的response是否依然有效。如果缓存仍然有效,你的应用程序应该返回一个304状态码和一个空内容。这告诉缓存它可以为请求用户返回它缓存的response。
在这个模型下,你主要节省了带宽因为描述不会发送两次到相同的客户端(而是发送一个304回复代替)。但是,如果你仔细设计你的应用程序,你可能能忍受304 response需要的最小数据并节省CPU。
304状态码意味着没有修改。它很重要因为它没有包含整整的被请求内容,而只是一个轻量级的导向集,它告诉缓存它应该使用它现在保存的版本回复请求。跟过期类似,也有两个不同的HTTP头可以被用来实现校验模型: ETag和Last-Modifed
校验和ETag头
ETag头是一个字符串(也叫"entity-tag")它是目标资源一个表现的唯一标识。它完全由你的应用程序来生成和设置。 比如,如果 /about 资源被缓存保存时取决于日期和你应用程序的返回内容。一个ETag像一个手印,被用来快速的比较一个资源的两个不同版本是否等效。
像手印,同一个资源的所有表示形式中每个ETag必须是唯一的。让我们来简单实现一个生成ETag使用md5加密的回复内容作为内容:
public function indexAction() { $response = $this->render('MyBundle:Main:index.html.twig'); $response->setETag(md5($response->getContent())); $response->isNotModified($this->getRequest()); return $response; }
Response::isNotModified()方法把和Request一起发送的ETag与Response上的ETag进行比较。如果两个匹配,方法自动设置Response状态码为304。
这个算法非常简单也非常通用,但是你需要在能计算ETag之前创建一个完整的Response,校验模型是次优选择。换句话说,它节省了带宽,单没有节省CPU利用。
Symfony2还通过向setETag()方法传入true作为第二个参数,来支持弱ETag。
校验和Last-Modified 头
Last-Modified头是校验模型的第二种形式。根据HTTP规范,”Last-Modified 头字段指定日期和时间,在这个时间源服务器相信该表现是最后被修改版。“
换句话说,应用程序决定基于自动缓存内容被缓存后是否被更新过来判断缓存的内容是否要被更新过。举个例子,你可以使用最新更新日期为所有需要计算资源表现的对象作为Last-Modified头的值:
public function showAction($articleSlug) { //... $articleDate = new \DateTime($article->getUdateAt()); $authorDate = new \DateTime($author->getUpdateAt());\ $date = $authorDate>$articleDate ? $authorDate : $articleDate; $response->setLastModified($date); $response->isNotModified($this->getRequest()); return $response; }
Response::isNotModified() 方法比较请求Request中的If-Modified-Since头和Response中的Last-Modified 头。如果他们相等,Response会被设置一个304状态码。
注意,If-Modified-since 请求头等于最终发送到客户端特定资源Last-Modified头。这就是如何客户端和服务端相互交流决定资源自从它被缓存后是否被更新。
使用校验优化你的代码:
任何缓存策略的主要目的都是减轻应用程序的加载。换句话说,你的应用程序做的越少来返回304 response,越好。Response::isNotModified()方法通过暴露一个简单有效的模式做到了。
public funcation showAction($articleSlug) { //获取最小信息来计算ETag或者Last-Modified值(基于Request,数据是从数据库或者一个键值对存储实例中获取。 $article = //... //创建一个Response带有一个ETag 和/或者 一个Last-Modified 头 $response = new Response(); $response->setETag($article->computeETag()); $response->setLastModified($article->getPublishedAt()); //为给定的Request检查Response没有被修改 if($response->isNotModified($this->getRequest())){ //立刻返回304 Response return $response; }else{ //做一些更多的工作-比如获取更多的数据 $comment=https://www.jb51.net//... //或者用你已经开启的$response渲染一个模版 return $this->render('MyBundle:MyController:article.html.twig', array('article'=>$article, 'comments' =>$comments), $response ); } }
当Response没有被修改后,isNotModified()自动设置response的状态码为304,移除response的内容,移除一些不需要为304存在的头。
不同的回复响应:
到目前为止,我们已经假设了每个URI只有一个目标资源的表示。默认情况下,HTTP缓存通过使用URI的资源作为缓存键被执行。如果两个人请求同一个可缓存资源的URI,第二个用户将获取缓存版本。有时候这些不够,不同版本的用一个URI需要被按照一个或者多个请求头的值来被缓存。举个例子,如果当客户端支持你压缩页面时,任何给定的URI都有两种表示:一个是客户端支持压缩时,一个是不支持时的表示。这时候请求头的Accept-Encoding值将决定使用哪个。