Leveraging HTTP缓存

不只是服务端的缓存,你通过设置HTTP头可以使用客户端缓存。

在这个小节中,我们将会讨论基于Last-ModifiedETag头的全页缓存。

准备

按照官方指南http://www.yiiframework.com/doc-2.0/guide-start-installation.html的描述,使用Composer包管理器创建一个新的yii2-app-basic应用。

  1. 创建并运行migration:
  1. <?php
  2. use yii\db\Migration;
  3. class m160308_093233_create_example_tables extends Migration
  4. {
  5. public function up()
  6. {
  7. $this->createTable('{{%article}}', [
  8. 'id' => $this->primaryKey(),
  9. 'created_at' => $this->integer()->unsigned()->notNull(),
  10. 'updated_at' =>
  11. $this->integer()->unsigned()->notNull(),
  12. 'title' => $this->string()->notNull(),
  13. 'text' => $this->text()->notNull(),
  14. ]);
  15. }
  16. public function down()
  17. {
  18. $this->dropTable('{{%article}}');
  19. }
  20. }
  1. 创建Article模型:
  1. <?php
  2. namespace app\models;
  3. use Yii;
  4. use yii\behaviors\TimestampBehavior;
  5. use yii\db\ActiveRecord;
  6. class Article extends ActiveRecord
  7. {
  8. public static function tableName()
  9. {
  10. return '{{%article}}';
  11. }
  12. public function behaviors()
  13. {
  14. return [
  15. TimestampBehavior::className(),
  16. ];
  17. }
  18. }
  1. 创建带有如下动作的博客控制器:
  1. <?php
  2. namespace app\controllers;
  3. use app\models\Article;
  4. use yii\web\Controller;
  5. use yii\web\NotFoundHttpException;
  6. class BlogController extends Controller
  7. {
  8. public function actionIndex()
  9. {
  10. $articles = Article::find()->orderBy(['id' =>
  11. SORT_DESC])->all();
  12. return $this->render('index', array(
  13. 'articles' => $articles,
  14. ));
  15. }
  16. public function actionView($id)
  17. {
  18. $article = $this->findModel($id);
  19. return $this->render('view', array(
  20. 'article' => $article,
  21. ));
  22. }
  23. public function actionCreate()
  24. {
  25. $n = rand(0, 1000);
  26. $article = new Article();
  27. $article->title = 'Title #' . $n;
  28. $article->text = 'Text #' . $n;
  29. $article->save();
  30. echo 'OK';
  31. }
  32. public function actionUpdate($id)
  33. {
  34. $article = $this->findModel($id);
  35. $n = rand(0, 1000);
  36. $article->title = 'Title #' . $n;
  37. $article->text = 'Text #' . $n;
  38. $article->save();
  39. echo 'OK';
  40. }
  41. private function findModel($id)
  42. {
  43. if (($model = Article::findOne($id)) !== null) {
  44. return $model;
  45. } else {
  46. throw new NotFoundHttpException('The requested page does not exist.');
  47. }
  48. }
  49. }
  1. 添加views/blog/index.php视图:
  1. <?php
  2. use yii\helpers\Html;
  3. $this->title = 'Articles';;
  4. $this->params['breadcrumbs'][] = $this->title;
  5. ?>
  6. <?php foreach($articles as $article): ?>
  7. <h3><?= Html::a(Html::encode($article->title), ['view',
  8. 'id' => $article->id]) ?></h3>
  9. <div>Created <?= Yii::$app->formatter->asDatetime($article->created_at) ?></div>
  10. <div>Updated <?= Yii::$app->formatter->asDatetime($article->updated_at) ?></div>
  11. <?php endforeach ?>
  1. 添加视图views/blog/view.php
  1. <?php
  2. use yii\helpers\Html;
  3. $this->title = $article->title;
  4. $this->params['breadcrumbs'][] = ['label' => 'Articles', 'url' => ['index']];
  5. $this->params['breadcrumbs'][] = $this->title;
  6. ?>
  7. <h1><?= Html::encode($article->title) ?></h1>
  8. <div>Created <?= Yii::$app->formatter->asDatetime($article->created_at) ?></div>
  9. <div>Updated <?= Yii::$app->formatter->asDatetime($article->updated_at) ?></div>
  10. <hr />
  11. <p><?= Yii::$app->formatter->asNtext($article->text) ?></p>

如何做…

执行如下步骤来leverage HTTP缓存:

  1. 访问http://yii-book.app/index.php?r=blog/create三次,来创建三个文章。
  2. 打开如下博客地址:

Leveraging HTTP缓存 - 图1

  1. 在你的浏览器中打开开发者控制台,每次刷新页面都可以看到200 OK的响应状态:

Leveraging HTTP缓存 - 图2

  1. 打开BlogController并附加如下行为:
  1. <?php
  2. class BlogController extends Controller
  3. {
  4. public function behaviors()
  5. {
  6. return [
  7. [
  8. 'class' => 'yii\filters\HttpCache',
  9. 'only' => ['index'],
  10. 'lastModified' => function ($action, $params) {
  11. return Article::find()->max('updated_at');
  12. },
  13. ],
  14. [
  15. 'class' => 'yii\filters\HttpCache',
  16. 'only' => ['view'],
  17. 'etagSeed' => function ($action, $params) {
  18. $article = $this->findModel(\Yii::$app->request->get('id'));
  19. return serialize([$article->title, $article->text]);
  20. },
  21. ],
  22. ];
  23. }
  24. // ...
  25. }
  1. 接下来,刷新页面几次,并检查服务器返回的是304 Not Modified,而不是200 OK

Leveraging HTTP缓存 - 图3

  1. 使用如下URL打开相关页面,更新相关文章:http://yiibook.app/index.php?r=blog/update
  2. 更新过博客页面以后,检查服务器首次返回的是200 OK,接着就是304 Not Modified,并确认你在页面上看到了新的更新时间:

Leveraging HTTP缓存 - 图4

  1. 从我们的页面上打开任何页面:

Leveraging HTTP缓存 - 图5

确认服务器首次返回的是200 OK,以及接下来的请求是304 Not Modified

工作原理…

在HTTP头的帮助下,你的浏览器用基于时间和基于内容的方法,来检查缓存响应内容的可用性。

上次修改时间

这个方法建议服务端必须返回每个文章的上次修改时间。在存储这个这个日期之后,我们的浏览器可以在接下来的每次请求中,在If-Modified-Since头设置这个值。

我们必须附加这个action过滤器到我们的控制器中,并指定lastModified回到:

  1. <?php
  2. class BlogController extends Controller
  3. {
  4. public function behaviors()
  5. {
  6. return [
  7. [
  8. 'class' => 'yii\filters\HttpCache',
  9. 'only' => ['index'],
  10. 'lastModified' => function ($action, $params) {
  11. return Article::find()->max('updated_at');
  12. },
  13. ],
  14. // ...
  15. ];
  16. }
  17. // ...
  18. }

\yii\filters\HttpCache类调用这个回调,并将返回值和$_SERVER['HTTP_IF_MODIFIED_SINCE']系统变量进行比较。如果这个文章没有改变,HttpCache将会发送一个轻量级的304响应头,而且不需要运行这个动作。

但是,如果文档更新了,这个缓存将会失效,服务端将会返回一个完整的响应。

Leveraging HTTP缓存 - 图6

作为Last-Modified的备选或者补充,你可以使用ETag

Entity标签

如果我们没有存储上次修改时间,我们可以使用自定义hash,它可以基于文章的内容生成。

例如,我们可以为我们的文档使用一个内容标题,来做了一个hash值:

  1. class BlogController extends Controller
  2. {
  3. public function behaviors()
  4. {
  5. return [
  6. [
  7. 'class' => 'yii\filters\HttpCache',
  8. 'only' => ['view'],
  9. 'etagSeed' => function ($action, $params) {
  10. $article =
  11. $this->findModel(\Yii::$app->request->get('id'));
  12. return serialize([$article->title,
  13. $article->text]);
  14. },
  15. ],
  16. ];
  17. }
  18. // ...
  19. }

这个HttpCache过滤器会将这个tag附加到服务器响应的ETag头变量上。

在存储了ETag之后,我们的浏览器会为接下来的每次请求附加它在If-None-Match头上。

如果这个文档仍然为改变,HttpCache将会发送一个轻量级的304响应头,并且不需要运行这个动作。

Leveraging HTTP缓存 - 图7

Leveraging HTTP缓存 - 图8

当这个缓存是合法的,我们的应用将会发送304 Not Modified响应头,而不是页面内容,而且不会重复运行控制器和动作。

参考