计算机网络协议系列 - HTTP 协议篇:在 PHP 应用中实现 HTTP 缓存之网关缓存篇

- 2 mins

计算机网络协议系列(四十八)

上篇分享中,给大家介绍了如何通过响应头设置浏览器缓存来实现 HTTP 缓存,今天我们还是在 Laravel 项目中基于网关缓存来实现 HTTP 缓存,比起浏览器缓存,网关缓存更易于通过服务端代码进行维护和控制,同时还可以被多个客户端共享,所以更推荐在实际项目中以这种方式实现 HTTP 缓存。

原理概述

这里要介绍的网关缓存主要就是反向代理缓存,常见的反向代理服务器有 Nginx、Varnish、Squid 等,但是这里为了简化模型,将使用 Symfony 框架提供的 HTTP Cache 功能来做演示,Laravel 框架底层的 HTTP 模块是基于 Symfony 的,所以我们很容易在 Laravel 框架中基于 Symfony 的 HTTPCache 模块来实现 HTTP 缓存,更加方便的是,还有一个现成的 Laravel HTTP Cache 扩展包 barryvdh/laravel-httpcache 对 Symfony 的 HTTP 缓存功能进行了封装,以便我们在 Laravel 项目中快速接入以实现 HTTP 缓存。

有关 Symfony 的 HTTP Cache 功能可以参考 Symfony 文档,这里我们将重点放在基于 laravel-httpcache 扩展包在 Laravel 项目中演示基于网关缓存实现 HTTP 缓存上。

安装扩展包

首先,我们通过 Composer 在 Laravel 项目根目录下安装这个扩展包:

    composer require barryvdh/laravel-httpcache 

然后,在 app/Http/Kernel.phpweb 中间件组中添加一个中间件:

    \Barryvdh\HttpCache\Middleware\CacheRequests::class,

这样,我们就可以在 routes/web.php 定义的路由中应用 HTTP 网关缓存了,之所以叫做网关缓存,是因为这个缓存存放在服务器网关而非客户端浏览器或中间代理中,这里的网关就是 Symfony 底层基于 PHP 实现的简单反向代理服务器了(工业级反向代理缓存服务器还是使用 Varnish 或 Squid)。

Expires

就是这么简单,接下来我们可以编写路由来演示基于底层 Symfony 网关实现的 HTTP 缓存了,首先我们来看基于 Expires 响应头的缓存:

    Route::get('expires', function () {
        return response('Test Expires Header')
            ->setPublic()
            ->setExpires(new DateTime(date(DATE_RFC7231, time() + 3600)));
    });

由于缓存要存放在服务器网关中,所以 Cache-Control 响应头中需要设置 public 属性(默认是 private),我们可以通过 setPublic 方法来实现这一目的,然后通过 setExpires 方法设置缓存过期时间,这样,在浏览器访问该路由,首次访问的时候,会从服务器读取最新资源数据,同时 Symfony 网关会设置相应的响应头 X-Symfony-Cache: miss,store,表示缓存未命中,已存储:

img

缓存记录默认存储在 Laravel 项目的 storage/httpcache 目录下,再次访问该路由,就会从网关缓存获取资源了,这可以通过 X-Symfony-Cache: fresh 响应头得知:

img

Cache-Control

接下来,我们来看下基于 Cache-Control 响应头实现的网关缓存,相应的路由定义如下:

    Route::get('cache_control', function () {
        return response('Test Cache-Control Header')->setTtl(3600);
    });

max-age 用于设置本地缓存有效期,如果是代理缓存或网关缓存,则需要通过 s-maxage 属性来设置,所以在上述代码中我们使用了 setTtl 方法而不是 setClientTtl,该方法会同时设置 Cache-Control 响应头的 s-maxage 以及 public 属性,这样,我们在浏览器中访问该路由,首次访问当然还是不会命中,但缓存会被存储到网关:

img

再次访问,就可以从缓存获取资源了:

img

If-Modified-Since/Last-Modified

下面我们再以 If-Modified-Since/Last-Modified 为例演示一个对比缓存的例子,编写路由定义如下:

    Route::get('no_cache', function () {
        $response = response('Test If-Modified-Since/Last-Modified Http Cache');
        $response->headers->addCacheControlDirective('no-cache', true);
        $response->setTtl(3600);
        $response->setLastModified(new DateTime(date(DATE_RFC7231)));
        return $response;
    });

我们设置响应头 Cache-Control 字段值为:no-cache,public,s-maxage=3600,表示需要进行缓存新鲜度检测,如果缓存未过期,则返回 304 响应,否则返回最新资源,同样首次访问该路由的时候缓存未命中,但会保存下来:

img

再次访问,则返回 304 响应,然后从网关缓存获取缓存副本作为响应实体返回给客户端:

img

If-None-Match/Etag 实现思路也是类似,这里不再单独演示了。

以上就是 Laravel 项目中实现通过浏览器缓存和网关缓存实现 HTTP 缓存的大致思路,有了 HTTP 缓存,就可以降低服务器负载和网络带宽,从而提高服务器性能,加快用户访问页面速度,在实际项目中,你可以将 Symfony 网关替换成 Varnish 之类的工业级软件,效果会更好,在 Laravel 中使用 Varnish 可以使用 spatie/laravel-varnish 这个扩展包。另外,需要注意的是,以上 HTTP 缓存都会存储完整的响应实体,即整个页面,所以 HTTP 缓存多适用于静态页面或文件的存储,如果你想要缓存数据片段的话,则更适合通过 Memcached 或 Redis 之类的缓存方案来解决。

rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora