通过 HTTP 头缓存来提高程序性能


> https://devcenter.heroku.com/articles/increasing-application-performance-with-http-cache-headers

现在的时代,程序员可以有很多方法来提高程序性能和体验,而经常被疏忽遗忘的就是通过 HTTP 缓存来实现.

http 缓存是一个能够到绝大多数浏览器上都适用的技术,在现在的 web 应用里容易实现.合理的使用能够很大得提高的你程序的性能, 提高响应时间或者降低服务器的压力,但是不正确的使用也会造成一些问题, 内容过期不更新啊,很难 debug 问题啊. 下面我们看看到底怎么使用 http 缓存.

#概述
当浏览器把 web 上的资源保存到本地的时候就会发生 http 缓存.然后在下一次就能够很快的响应这个资源的请求. 你的程序可以给特定的资源响应头上添加一个行为来达到缓存的目的.

![屏幕截图 2015-02-04 00.13.44.jpg](https://qiita-image-store.s3.amazonaws.com/0/56713/f0bcc6f6-599d-1abb-6001-fe897b0cccb5.jpeg "屏幕截图 2015-02-04 00.13.44.jpg")

当一个 item 被完整的缓存起来后,浏览器就不会再去访问服务器,只是使用本地的缓存.
![屏幕截图 2015-02-04 00.13.52.jpg](https://qiita-image-store.s3.amazonaws.com/0/56713/09217e42-0862-cf3e-3c4b-24b345b64c55.jpeg "屏幕截图 2015-02-04 00.13.52.jpg")

举个例子, 一旦 CSS 文件从你的程序哪里下载后,浏览器就不会再去下载了.对于当前访问的用户来说.当然这个对于很多其他的资源文件也是起作用的.如文件,图片,甚至不经常更新的内容.在这些情况中, 它非常有用如果把内容缓存到本地的话.

#HTTP 缓存头
有2个比较主要的缓存头: `Cache-Control`和` Expires`.

##Cache-Control

> 没有 cahe-control 这个头的话,就其他的也不好使了

这个值通常是非常重要的设置,它可以作为一个开关的形式告诉浏览器是否缓存. 设置了这个值之后, 就能够启用缓存, 浏览器就会缓存文件按照你指定的时间长度. 如果没有这个值,就会总是去请求服务器.

`public` 资源可以不仅仅缓存到终端用户的浏览器中,而且还可以缓存到任何的中间代理那里,这样它就可以作用于其他的用户.

```ruby
Cache-Control:public
```

`private` 资源会被绕过中间的代理,只能够缓存到终端用户.
```ruby
Cache-Control:private
```

`Cache-Control` 这个头是一个组合的值,表明是否资源是公有还有私有的, 而且还要指定最大有效时间,在过期之前.这个` max-age` 值设置为时间来说明要缓存多久对于这个资源, 弄秒记的.

```ruby
Cache-Control:public, max-age=31536000
```

当这个` cache-control` 头开启并且设置了` max-age`, 这个` expires` 头是被用来指定在一个特定时间点,告诉浏览器这个过期了.

`expires`

当和` cache-control` 一起作用的时候,` expires` 简单的给了一个日期来设置在什么时间就会过期. 之后就会去服务器取最新的,然后就再等下一次的过期.

> 如果同时设置了` expires` 和` max-age`, `max-age` 会优先作用.


```ruby
Cache-Control:public
Expires: Mon, 25 Jun 2012 21:31:12 GMT
```

#条件性请求
条件性请求缓存的实现就是浏览器会询问服务器当前缓存的文件在他这里是否有更新的,如果有就会请求最新的. 在询问的时候会携带一些信息,这些信息能够让服务器判断是否有最新的信息.没有的话就返回`304`了.

![屏幕截图 2015-02-04 00.52.16.jpg](https://qiita-image-store.s3.amazonaws.com/0/56713/aae128e1-d63f-4244-31a9-584796c43996.jpeg "屏幕截图 2015-02-04 00.52.16.jpg")

虽然这样的方式会有请求到服务器,但是如果没有更新返回`304`是情况下,响应的内容是空的,可以节省返回时的带宽,这样就会节省响应时间.

##基于时间的
基于时间的条件性请求就是确保如果请求的资源已经修改了在浏览器这里缓存的情况下,它会被更新,如果浏览器手里是最新的,它就不会被请求.
实现这个缓存的方式就是通过设置` last-modified` 资源的最后跟踪时间来完成.

```ruby
Cache-Control:public, max-age=31536000
Last-Modified: Mon, 03 Jan 2011 17:45:57 GMT
```

下一次浏览器请求这个资源的时候,如果他们在这个`last-modified-since`的值之后更新过的话,它会询问当前的资源的最新内容.

##基于内容的

基于内容的就是会通过` etag` 这个值来判断,他和` last-modified` 头有点类似,除了他的值是一个内容的摘要.举个例子可以是 MD5值什么的. 这个允许服务器来鉴定当前缓存的资源内容是否和服务器这边的一样.

> 这个会在使用`last-modified` 比较困难的情况下使用.

```ruby
Cache-Control:public, max-age=31536000
ETag: "15f0fff99ed5aae4edffdd6496d7131f"

```
之后, 浏览器会请求这个` if-none-math` 请求头会和` etag`,

```ruby
If-None-Match: "15f0fff99ed5aae4edffdd6496d7131f"
```

和`if-modified-since` 头一样,如果当前版本有一样的 etag 值,那就返回一个304.


#可见性

现在的浏览器都有开发者工具,可以查看请求和响应内容,如下图,你可以看到一个初始化的请求到这个地址的. 这个时候你发现没有任何的 cache 指令.

![屏幕截图 2015-02-04 01.08.53.jpg](https://qiita-image-store.s3.amazonaws.com/0/56713/9c588d46-c214-74fb-e8a9-3a81f4520cad.jpeg "屏幕截图 2015-02-04 01.08.53.jpg")

通过添加查询参数,http://http-caching-demo.herokuapp.com/?cache=true,
应用程序开启了缓存,使用了` cache-control`和` expires` 头,两个都设置了30秒.
 
![屏幕截图 2015-02-04 01.09.02.jpg](https://qiita-image-store.s3.amazonaws.com/0/56713/925fbbfc-8816-ee8e-4bf2-6ac637272f2c.jpeg "屏幕截图 2015-02-04 01.09.02.jpg")


添加一个` etag` 参数到请求, http://http-caching-demo.herokuapp.com/?etag=true, 就会在头里添加一个 etag 的值,

![屏幕截图 2015-02-04 01.33.00.jpg](https://qiita-image-store.s3.amazonaws.com/0/56713/007989ff-c8bb-7d68-b442-4401e4208f73.jpeg "屏幕截图 2015-02-04 01.33.00.jpg")

继续深究,会发现基于 etag 的条件性请求预期执行了,通过一个初始化的请求从浏览器发送到服务器.
但是接下来的请求就会得到一个304 的响应.

![屏幕截图 2015-02-04 01.35.12.jpg](https://qiita-image-store.s3.amazonaws.com/0/56713/fa67a8b6-e2b6-c3ad-e1f2-967180cadb3c.jpeg "屏幕截图 2015-02-04 01.35.12.jpg")

![屏幕截图 2015-02-04 01.35.17.jpg](https://qiita-image-store.s3.amazonaws.com/0/56713/905b1cf8-02d8-ba5e-c04c-1aa3425030d9.jpeg "屏幕截图 2015-02-04 01.35.17.jpg")



#使用情景

##静态资源

```ruby
Cache-Control:public; max-age=31536000
Expires: Mon, 25 Jun 2013 21:31:12 GMT
```
这个基本上一般都是缓存的

##动态内容

##私有内容

```ruby
Cache-Control:private, max-age=31536000
```
#禁止缓存

```ruby
Cache-Control:no-cache, no-store
```

#各语言的实现

略