# 具体应用
## 连接管理
### 1. 短连接与长连接
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
- 从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 `Connection : close`;
- 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 `Connection : Keep-Alive`。
### 2. 流水线
默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。
流水线是在同一条长连接上发出连续的请求,而不用等待响应返回,这样可以避免连接延迟。
## Cookie
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。
HTTP/1.1 引入 Cookie 来**保存状态信息**。
Cookie 是服务器发送到用户浏览器并**保存在本地的一小块数据**,在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。
Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(本地存储和会话存储)或 IndexedDB。
### 1. 用途
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
### 2. 创建过程
服务器发送的响应报文包含 **Set-Cookie 首部字段**,客户端得到响应报文后把 Cookie 内容保存到浏览器中。
```html
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
[page content]
```
客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。
```html
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
```
### 3. 分类
- 会话期 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。
- 持久性 Cookie:指定一个特定的过期时间(Expires)或有效期(max-age)之后就成为了持久性的 Cookie。
```html
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
```
### 4. 作用域
Domain 标识指定了哪些主机可以接受 Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了 Domain,则一般包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如 developer.mozilla.org)。
Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。例如,设置 Path=/docs,则以下地址都会匹配:
- /docs
- /docs/Web/
- /docs/Web/HTTP
### 5. Session
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
服务器为每个 Session 分配一个唯一的 Session ID。Session 被创建后,调用 Session 相关方法向 Session 中添加内容,而这些内容保存在服务端,响应到客户端的只有 Session ID。
客户端再次发送请求后,会根据这个 Session ID,查找到相应的 Session。
Session 可以**存储在服务器**上的文件、数据库或者内存中。也可以**将 Session 存储在 Redis 这种内存型数据库中,效率会更高**。
使用 Session 维护用户登录状态的过程如下:
- 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
- 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID;
- 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中;
- 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。
应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。
> **补充:Session 的实现方式**
> - 使用Cookie 来实现
服务器为每个Session分配一个唯一的JSESSIONID,并通过cookie发送给客户端。当客户端发起新的请求的时候,会在请求中携带这个JSESSIONID,这样服务器就能找到该客户端对应的Session。
> - 使用 URL 回写来实现
URL回写是指,服务器在发送给浏览器的所有连接中,都携带JSESSIONID的参数。
TOMCAT是默认使用两种方式,当 Cookie 被禁用就使用 URL 回写的方式。
### 6. 浏览器禁用 Cookie
此时无法使用 Cookie 来保存用户信息,只能使用 Session。除此之外,不能再将 Session ID 存放到 Cookie 中,而是使用 URL 重写技术,将 Session ID 作为 URL 的参数进行传递。
### 7. Cookie 与 Session 选择
- Cookie 只能存储 ASCII 码字符串,
Session 则可以存取任何类型的数据,因此在考虑数据复杂性时首选 Session;
- Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密;
- 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。若考虑减轻服务器负担,应当使用 Cookie。
## 缓存
### 1. 优点
- 缓解服务器压力;
- 降低客户端获取资源的延迟:缓存通常位于内存中,读取缓存的速度更快。并且缓存在地理位置上也有可能比源服务器来得近,例如浏览器缓存。
### 2. 实现方法
- 让代理服务器进行缓存;
- 让客户端浏览器进行缓存。
### 3. Cache-Control
HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。
**3.1 禁止进行缓存**
no-store 指令规定不能对请求或响应的任何一部分进行缓存。
```html
Cache-Control: no-store
```
**3.2 强制确认缓存**
no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效才将能使用该缓存对客户端的请求进行响应。
```html
Cache-Control: no-cache
```
**3.3 私有缓存和公共缓存**
private 指令规定了将资源作为私有缓存,只能被单独用户所使用,一般存储在用户浏览器中。
```html
Cache-Control: private
```
public 指令规定了将资源作为公共缓存,可以被多个用户所使用,一般存储在代理服务器中。
```html
Cache-Control: public
```
**3.4 缓存过期机制**
max-age 指令出现在请求报文中,并且缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。
max-age 指令出现在响应报文中,表示缓存资源在缓存服务器中保存的时间。
```html
Cache-Control: max-age=31536000
```
Expires 首部字段也可以用于告知缓存服务器该资源什么时候会过期。
```html
Expires: Wed, 04 Jul 2012 08:26:05 GMT
```
- 在 HTTP/1.1 中,会优先处理 max-age 指令;
- 在 HTTP/1.0 中,max-age 指令会被忽略掉。
### 4. 缓存验证
需要先了解 ETag 首部字段的含义,它是资源的唯一标识。URL 不能唯一表示资源,例如 `http://www.google.com/` 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。
```html
ETag: "82e22293907ce725faf67773957acd12"
```
可以将缓存资源的 ETag 值放入 If-None-Match 首部,服务器收到该请求后,判断缓存资源的 ETag 值和资源的最新 ETag 值是否一致,如果一致则表示缓存资源有效,返回 304 Not Modified。
```html
If-None-Match: "82e22293907ce725faf67773957acd12"
```
Last-Modified 首部字段也可以用于缓存验证,它包含在源服务器发送的响应报文中,指示源服务器对资源的最后修改时间。但是它是一种弱校验器,因为只能精确到一秒,所以它通常作为 ETag 的备用方案。如果响应首部字段里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 OK。如果请求的资源从那时起未经修改,那么返回一个不带有消息主体的 304 Not Modified 响应。
```html
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
```
```html
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
```