什么是“ETag”?
Etag 是一种标识,一般附带在响应头部中,值是页面内容的哈希值,用来判断资源(页面,json,xml)有没有修改,如果没有修改,就返回 304 状态码,有修改则生成新的 Etag 值。
浏览器根据状态码判断是否缓存过期。
服务器生成 Etag,和客户端保存 Etag ,并在请求中附带 Etag 的过程如下:
- 客户端请求一个页面(A)。
- 服务器返回页面A,并在给A加上一个ETag。
- 客户端展现该页面,并将页面连同ETag一起缓存。
- 客户再次请求页面A,并将上次请求时服务器返回的ETag一起传递给服务器。
- 服务器检查该ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304(未修改——Not Modified)和一个空的响应体。
用法示例
在典型用法中,当一个URL被请求,Web服务器会返回资源和其相应的ETag值,它会被放置在HTTP的“ETag”字段中:ETag: “686897696a7c876b7e”
然后,客户端可以决定是否缓存这个资源和它的ETag。以后,如果客户端想再次请求相同的URL,将会发送一个包含已保存的ETag和“If-None-Match”字段的请求。If-None-Match: “686897696a7c876b7e”
客户端请求之后,服务器可能会比较客户端的ETag和当前版本资源的ETag。如果ETag值匹配,这就意味着资源没有改变,服务器便会发送回一个极短的响应,包含HTTP “304 未修改”的状态。304状态告诉客户端,它的缓存版本是最新的,并应该使用它。
然而,如果ETag的值不匹配,这就意味着资源很可能发生了变化,那么,一个完整的响应就会被返回,包括资源的内容,就好像ETag没有被使用。这种情况下,客户端可以用新返回的资源和新的ETag替代先前的缓存版本。
Lumen中的使用
在lumen框架中可以通过后置中间件的形式给接口统一设置304
cache304 中间件
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CacheMiddleware
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$response = $next($request);
$etag = md5($response->getContent());
$requestETag = str_replace('"', '', $request->getETags());
if ($requestETag && $requestETag[0] == $etag) {
// Modifies the response so that it conforms to the rules defined for a 304 status code.
$response->setNotModified();
}
$response->setETag($etag);
# 其他设置点可以看源码
# return $response->setMaxAge(30);
return $response;
}
}
中间件注册
$app->routeMiddleware([
'cache304' => App\Http\Middleware\CacheMiddleware::class,
]);
路由引用
$router->get('/api/getTrack', ['middleware' => ['jwt', 'cache304'], 'uses' => 'TrackController@getTrack']);
注意:nginx开启gzip模块后ETAG丢失问题
上述设置要检查是否Nginx开启gzip模块导致ETAG丢失,若丢失可以设置etag为弱etag
Lumen设置etag方法如下:
/**
* Sets the ETag value.
*
* @param string|null $etag The ETag unique identifier or null to remove the header
* @param bool $weak Whether you want a weak ETag or not
*
* @return $this
*
* @final
*/
public function setEtag(string $etag = null, bool $weak = false): object
{
if (null === $etag) {
$this->headers->remove('Etag');
} else {
if (0 !== strpos($etag, '"')) {
$etag = '"'.$etag.'"';
}
$this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag);
}
return $this;
}
设置弱etag方法:
$response->setETag($etag, true);