阅读列表

基于ETag的HTTP Get缓存托管技术

本文将以Angular前端为例,向你展示如何编写一个自主实现缓存的HTTP Get 服务。

Oceanic

发表于 2024年11月19日

加入到阅读列表分享本文到 X

在我们创建网站的时候,经常需要利用HTTP Get的方法从服务器加载数据,例如一个在售图书列表程序,会将图书信息以JSON的形式从服务器端加载到前端Web,这是现代网站、浏览器和互联网的常见工作模式。但是这种基于HTTP Get 的模式存在一个令人头痛的问题:前端网页无法知道什么时候需要继续使用浏览器的缓存文件,什么时候需要重新下载新的版本。

以下面的Angular程序为例:

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [],
  template: `
    <button (click)="getData()">Get Data</button>
    <p>{{message}}</p>`
})
export class AppComponent {
message : string = '';
constructor(private http: HttpClient){}
getData() : void{
const headers = new HttpHeaders({ 'Content-Type': 'application/json'});
   	this.http.get<any>('http://127.0.0.1/test.json', { headers: headers})
      	.subscribe({next : (data) => {
        		this.message = data.message;
      	}});
	}
}

我们的网页程序通过http get请求从服务器端(127.0.0.1)加载了一个名为test.json的文件。在完成第一次请求后,随后再次点击Get Data按钮,我们始终会得到disk cache中的内容(注意灰色的Status 200和Size一栏的提示):

如果服务器端test.json文件的内容发生改变,用户将无法得到最新版本的文件。

对于这个恼人的情况,一种解决方案是在进行HTTP请求的时候,给网址后面加上一个版本标签,当我们的文件发生改变时,就改变这个版本标签:

getData(version : string) : void{
	…
   this.http.get<any>('http://127.0.0.1/test.json?ver=' + version, { headers: headers})
	…
}

但是我们马上会遇到新的问题:如何通知客户端我们的文件发生了改变呢?尤其是我们有无数个*.json文件的时候,我们需要维护一个庞大的版本系统,然后在初始化时下载这个版本信息文件。

另一个解决方案,也是前端始终使用缓存内容的原因,就是我们并没有使用Cache-Control标头来指定缓存的最大有效时间,这很好理解,因为我们在大多数情况下都不清楚一个文件到底应该缓存多少时间。服务器端文件内容的改变,往往都是不可预知的。

如果我们把Cache-Control设置为no-cache(就像我们需要对版本信息文件做的那样),我们不仅会失去缓存这个提升用户体验的工具,还会增加服务器端的开销。

为了解决这个问题,我们将设计一个基于ETag的缓存服务,用来接管浏览器的缓存方案。虽然我们使用Angular进行演示,但只要按照本文的思路,你可以做出适合各种前端工具,包括适合jQuery的解决方案。

ETag是服务器根据文件的信息生成的一个字符串,每当所依赖的信息发生改变时,这个标签就会相应的发生变化。对于Apache服务器,我们需要在.htaccess中设置服务器向客户端发送这个标签:

Header set Access-Control-Expose-Headers "ETag"

如果你使用AWS的S3,需要在Permissions页面的CORS配置数组中加上对ETag的支持:

"ExposeHeaders": ["ETag"],

现在我们略微修改getData的代码,让服务器返回ETag数据:

getData() : void{
    const headers = new HttpHeaders({ 'Content-Type': 'application/json'});
    this.http.get<any>('http://127.0.0.1/test.json', { headers: headers, observe: 'response'})
      .subscribe({next : (data) => {
        console.log(data.headers.get('ETag'));
      }});
 }

这是一篇需要付费阅读的文章,请完成支付以阅读完整的文章