# 应用层

##

## 应用层

### 应用层概述

应用层的作用是为应用进程之间的通讯和交互制定规则。

### HTTP 超文本传输协议

http的特点

* 无状态：协议对客户端没有状态存储，对事物处理没有“记忆”能力，比如访问一个网站需要反复进行登录操作
* 无连接：HTTP/1.1之前，由于无状态特点，每次请求需要通过TCP三次握手四次挥手，和服务器重新建立连接。比如某个客户机在短时间多次请求同一个资源，服务器并不能区别是否已经响应过用户的请求，所以每次需要重新响应请求，需要耗费不必要的时间和流量。
* 基于请求和响应：基本的特性，由客户端发起请求，服务端响应
* 简单快速、灵活
* 通信使用明文、请求和响应不会对通信方进行确认、无法保护数据的完整性

#### 请求和响应报文

请求报文

1. 第一行是请求方法、URL、版本协议
2. 首部
3. 空行分隔首部和主体
4. 内容主体

```http
GET http://www.example.com/ HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: max-age=0
Host: www.example.com
If-Modified-Since: Thu, 17 Oct 2019 07:18:26 GMT
If-None-Match: "3147526947+gzip"
Proxy-Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 xxx

param1=1&param2=2
```

响应报文

1. 第一行是版本协议、状态码及描述
2. 首部
3. 空行分隔首部和主体
4. 响应的主体

```http
HTTP/1.1 200 OK
Age: 529651
Cache-Control: max-age=604800
Connection: keep-alive
Content-Encoding: gzip
Content-Length: 648
Content-Type: text/html; charset=UTF-8
Date: Mon, 02 Nov 2020 17:53:39 GMT
Etag: "3147526947+ident+gzip"
Expires: Mon, 09 Nov 2020 17:53:39 GMT
Keep-Alive: timeout=4
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Proxy-Connection: keep-alive
Server: ECS (sjc/16DF)
Vary: Accept-Encoding
X-Cache: HIT

<!doctype html>
<html>
<head>
    <title>Example Domain</title>
    // 省略... 
</body>
</html>
```

**URL**

URL全称**统一资源定位符**，他是URI(**统一资源标识符**)的子集，用于定位资源的路径。

#### HTTP方法

* GET 获取资源
* POST 传送数据
* DELETE 删除资源
*   PUT 上传文件。

    > 由于自身不带验证机制，任何人都可以上传文件，因此存在安全性问题，一般不使用该方法。
* HEAD
* PATCH 对资源进行部分修改
* OPTIONS
* CONNECT
* TRACE

| http方法   | 作用               | 备注                                                  |
| -------- | ---------------- | --------------------------------------------------- |
| **get**  | 获取资源             |                                                     |
| **post** | 传送数据             |                                                     |
| delete   | 删除文件             | 同样不带验证机制                                            |
| put      | 上传文件             | 由于自身不带验证机制，任何人都可以上传文件，因此存在安全性问题，一般不使用该方法。           |
| patch    | 对资源进行部分修改        |                                                     |
| options  | 查询支持的方法          | 会返回 `Allow: GET, POST, HEAD, OPTIONS` 这样的内容。常用于跨域请求 |
| head     | 获取报文首部           | 和get类似,但不返回主体,主要用于确认url有效性及资源更新日期等                  |
| connect  | 要求在与代理服务器通信时建立隧道 | 使用 SSL（安全套接层）和 TLS（传输层安全）协议把通信内容加密后经网络隧道传输。         |
| trace    | 追踪路径             | 服务器会将通信路径返回给客户端。                                    |

#### HTTP状态码

| 状态码 | 类别                     | 含义            |
| --- | ---------------------- | ------------- |
| 1XX | Informational（信息性状态码）  | 接收的请求正在处理     |
| 2XX | Success（成功状态码）         | 请求正常处理完毕      |
| 3XX | Redirection（重定向状态码）    | 需要进行附加操作以完成请求 |
| 4XX | Client Error（客户端错误状态码） | 服务器无法处理请求     |
| 5XX | Server Error（服务器错误状态码） | 服务器处理请求出错     |

* 1xx 信息
  * 100 目前为止一切正常, 客户端应该继续请求
  * 101 正在切换协议，常见websocket
* 2xx 成功
  * 200 请求成功
  * 204 表示该请求已经成功了，但是客户端客户不需要离开当前页面
* 3xx 重定向
  * 301 永久重定向
  * 302 临时重定向
  * 303 和 302 有着相同的功能，但是 303 明确要求客户端应该采用 GET 方法获取资源。
  * 304 服务端已经执行了GET，但文件未变化，也就是说可以使用缓存的内容。
* 4xx 客户端错误
  * 400 请求报文存在语法错误
  * 403 请求被拒绝
  * 404 资源不存在
* 500 服务器错误
  * 500 服务器正在执行请求时发生错误
  * 503 服务器暂时处于超负荷或停机维护

#### HTTP首部

有 4 种类型的首部字段：通用首部字段、请求首部字段、响应首部字段和实体首部字段。

各种首部字段及其含义如下（不需要全记，仅供查阅）：

**通用首部字段**

| 首部字段名             | 说明                    |
| ----------------- | --------------------- |
| Cache-Control     | 控制缓存的行为               |
| Connection        | 控制不再转发给代理的首部字段、管理持久连接 |
| Date              | 创建报文的日期时间             |
| Pragma            | 报文指令                  |
| Trailer           | 报文末端的首部一览             |
| Transfer-Encoding | 指定报文主体的传输编码方式         |
| Upgrade           | 升级为其他协议               |
| Via               | 代理服务器的相关信息            |
| Warning           | 错误通知                  |

**请求首部字段**

| 首部字段名               | 说明                                |
| ------------------- | --------------------------------- |
| Accept              | 用户代理可处理的媒体类型                      |
| Accept-Charset      | 优先的字符集                            |
| Accept-Encoding     | 优先的内容编码                           |
| Accept-Language     | 优先的语言（自然语言）                       |
| Authorization       | Web 认证信息                          |
| Expect              | 期待服务器的特定行为                        |
| From                | 用户的电子邮箱地址                         |
| Host                | 请求资源所在服务器                         |
| If-Match            | 比较实体标记（ETag）                      |
| If-Modified-Since   | 比较资源的更新时间                         |
| If-None-Match       | 比较实体标记（与 If-Match 相反）             |
| If-Range            | 资源未更新时发送实体 Byte 的范围请求             |
| If-Unmodified-Since | 比较资源的更新时间（与 If-Modified-Since 相反） |
| Max-Forwards        | 最大传输逐跳数                           |
| Proxy-Authorization | 代理服务器要求客户端的认证信息                   |
| Range               | 实体的字节范围请求                         |
| Referer             | 对请求中 URI 的原始获取方                   |
| TE                  | 传输编码的优先级                          |
| User-Agent          | HTTP 客户端程序的信息                     |

**响应首部字段**

| 首部字段名              | 说明             |
| ------------------ | -------------- |
| Accept-Ranges      | 是否接受字节范围请求     |
| Age                | 推算资源创建经过时间     |
| ETag               | 资源的匹配信息        |
| Location           | 令客户端重定向至指定 URI |
| Proxy-Authenticate | 代理服务器对客户端的认证信息 |
| Retry-After        | 对再次发起请求的时机要求   |
| Server             | HTTP 服务器的安装信息  |
| Vary               | 代理服务器缓存的管理信息   |
| WWW-Authenticate   | 服务器对客户端的认证信息   |

**实体首部字段**

| 首部字段名            | 说明             |
| ---------------- | -------------- |
| Allow            | 资源可支持的 HTTP 方法 |
| Content-Encoding | 实体主体适用的编码方式    |
| Content-Language | 实体主体的自然语言      |
| Content-Length   | 实体主体的大小        |
| Content-Location | 替代对应资源的 URI    |
| Content-MD5      | 实体主体的报文摘要      |
| Content-Range    | 实体主体的位置范围      |
| Content-Type     | 实体主体的媒体类型      |
| Expires          | 实体主体过期的日期时间    |
| Last-Modified    | 资源的最后修改日期时间    |

#### 短连接与长连接

当浏览器访问一个包含多张图片的 HTML 页面时，除了请求访问的 HTML 页面资源，还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接，那么开销会很大。

长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。

* 从 HTTP/1.1 开始默认是长连接的，如果要断开连接，需要由客户端或者服务器端提出断开，使用 `Connection : close`；
* 在 HTTP/1.1 之前默认是短连接的，如果需要使用长连接，则使用 `Connection : Keep-Alive`。

#### Cookie

HTTP 协议是无状态的，主要是为了让 HTTP 协议尽可能简单，使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。

Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据，它会在浏览器之后向同一服务器再次发起请求时被携带上，用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据，因此会带来额外的性能开销（尤其是在移动环境下）。

Cookie 曾一度用于客户端数据的存储，因为当时并没有其它合适的存储办法而作为唯一的存储手段，但现在随着现代浏览器开始支持各种各样的存储方式，Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地，如使用 Web storage API（本地存储和会话存储）或 IndexedDB。

**用途**

* 会话状态管理（如用户登录状态、购物车、游戏分数或其它需要记录的信息）
* 个性化设置（如用户自定义设置、主题等）
* 浏览器行为跟踪（如跟踪分析用户行为等）

**创建过程**

服务器发送的响应报文包含 Set-Cookie 首部字段，客户端得到响应报文后把 Cookie 内容保存到浏览器中。

```
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

[page content]
```

客户端之后对同一个服务器发送请求时，会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。

```
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
```

**分类**

* 会话期 Cookie：浏览器关闭之后它会被自动删除，也就是说它仅在会话期内有效。
* 持久性 Cookie：指定过期时间（Expires）或有效期（max-age）之后就成为了持久性的 Cookie。

```
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
```

**作用域**

Domain 标识指定了哪些主机可以接受 Cookie。如果不指定，默认为当前文档的主机（不包含子域名）。如果指定了 Domain，则一般包含子域名。例如，如果设置 Domain=mozilla.org，则 Cookie 也包含在子域名中（如 developer.mozilla.org）。

Path 标识指定了主机下的哪些路径可以接受 Cookie（该 URL 路径必须存在于请求 URL 中）。以字符 %x2F ("/") 作为路径分隔符，子路径也会被匹配。例如，设置 Path=/docs，则以下地址都会匹配：

* /docs
* /docs/Web/
* /docs/Web/HTTP

**JavaScript**

浏览器通过 `document.cookie` 属性可创建新的 Cookie，也可通过该属性访问非 HttpOnly 标记的 Cookie。

```
document.cookie = "yummy_cookie=choco";
document.cookie = "tasty_cookie=strawberry";
console.log(document.cookie);
```

**HttpOnly**

标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。跨站脚本攻击 (XSS) 常常使用 JavaScript 的 `document.cookie` API 窃取用户的 Cookie 信息，因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。

```
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
```

**Secure**

标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记，敏感信息也不应该通过 Cookie 传输，因为 Cookie 有其固有的不安全性，Secure 标记也无法提供确实的安全保障。

**Session**

除了可以将用户信息通过 Cookie 存储在用户浏览器中，也可以利用 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 管理用户状态之外，还需要对用户进行重新验证，比如重新输入密码，或者使用短信验证码等方式。

#### HTTP缓存

页面第一次加载时，会将资源进行缓存，在过期之前再次请求该资源，则会直接使用缓存，而不会重新发送请求，这样有利于节省资源和加快速度。

http缓存分为强缓存和协商缓存。

**强缓存**

在缓存资源过期之前再次请求，则会使用强缓存。

强缓存通过`expires`和`cache-control`控制资源过期时间，`cache-control`的优先级高于`expires`。

`cache-control`接收的值有

* max-age: value 设置过期时间，表示多少s后过期
* no-store 不使用缓存
* no-cache 缓存，但是每次都要向服务器验证
* private 响应只能被客户端缓存
* public 响应可以被任意缓存

**协商缓存**

当超过过期时间之后再请求缓存资源时，则会向服务器协商，客户端将缓存标识传给服务器，服务器拿着资源标识去匹配，如果资源没有更新，则返回304状态码并重新设置过期时间，客户端会读取本地缓存；如果不匹配，则表示资源有更新，服务器将新数据和新的资源标识一起返回给客户端。

发送需要发送资源标识和最后修改时间，但是服务器发给客户端的和客户端发给服务器都要这两个字段，因此共有这四个字段，两两出现。

* etag 资源唯一标识
* last-modified 最后修改时间
* if-None-Match 客户端缓存资源标识
* if-Modified-Since 客户端发送给服务器的资源最后修改时间

#### HTTPS

HTTPS基于HTTP，通过SSL或TLS提供加密处理数据、验证对方身份以及数据完整性保护功能。

\*\*HTTPS = HTTP + SSL协议或TLS协议。\*\*也就是说https本质上是基于http协议的，只不过增加了一个安全层(SSL协议或TLS协议)。

HTTPS的目的是解决http传输过程中不安全的问题，简单来说它解决了三件事：

* 数据明文传输，易泄露隐私
* 无法对服务器进行身份认证。
* 数据传输过程中易被篡改

**加密**

解决数据泄露的最好方式是对数据进行加密。https使用了公钥加密和对称密钥加密结合的方式来加密数据，保证数据不会在传输过程中泄露信息。

公钥加密是服务器将公钥传给客户端，而私钥自己保存，客户端拿到公钥后对数据进行加密，然后发送到服务器上，只有服务器的私钥才能解密，也就是说只要服务器的私钥不泄露，安全性还是很高的。

对称加密是加密和解密都是同一个密钥，这种方式效率比较高，但是有个很大的问题，就是如何将密钥传给客户端，如果直接传输，那么就会很容易造成密钥泄露，那么加密就形同虚设了。

公钥加密的特点是安全性高，但是效率低，而对称密钥加密的特点是效率高，但是安全性低。

https利用了两者的特点，先用公钥加密的方式将密钥传给客户端，这样解决了密钥泄露的风险，然后双方再使用对称密钥的方式进行加密通讯。

**身份认证**

https通过数字证书来解决身份认证的问题。

第三方权威机构，对服务器进行认证，然后对服务器的公开密钥进行数字签名，并且将此公钥放入到为此服务器颁发的数字证书之中。

当客户端请求https时，服务器只需将数字证书发送给客户端，客户端可以通过数字证书进行认证，数字证书有两个作用。

* 一是数字证书是经过权威机构加密过的，浏览器中内置解密密钥，同时数字证书难以伪造，因此将服务器公钥放入数字证书中可以避免公钥被篡改或者替换。
* 二是客户端可以通过数字证书来对服务器进行身份认证，判断该服务器是否可以信赖。

**避免篡改**

HTTPS在发送数据时会附加一种叫做MAC(消息身份验证码)的报文摘要，MAC能够查知报文是否遭到篡改，从而保护报文的完整性。具体做法是接收方接收加密数据后用秘钥解密获得原文和摘要信息，然后将算法将内容生成在摘要信息，然后和发送过来的摘要信息作比对，如果不匹配则证明内容被篡改。

**HTTPS握手流程**

可以分为三步： 密钥协商、认证、加密

1. 双发“协商”要使用的加密组件和SSL(或TLS)版本
2. 服务器发送数字证书，客户端进行验证，并且从中去除公钥
3. 客户端用取出来的公钥加密随机密码串，并发送服务器，服务器接收后用私钥解密，然后双发使用随机密码串来进行对称密钥加密通讯。

具体步骤

![HTTPS通信](https://raw.githubusercontent.com/const-love-365-10000/cloudImg/master/img/epub_907764_181.jpeg)

步骤1： 客户端通过发送Client Hello报文开始SSL通信。报文中包含客户端支持的SSL的指定版本、加密组件（Cipher Suite）列表（所使用的加密算法及密钥长度等）。步骤2： 服务器可进行SSL通信时，会以Server Hello报文作为应答。和客户端一样，在报文中包含SSL版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。步骤3： 之后服务器发送Certificate报文。报文中包含公开密钥证书。步骤4： 最后服务器发送Server Hello Done报文通知客户端，最初阶段的SSL握手协商部分结束。步骤5: SSL第一次握手结束之后，客户端以Client KeyExchange报文作为回应。报文中包含通信加密中使用的一种被称为Pre-master secret的随机密码串。该报文已用步骤3中的公开密钥进行加密。步骤6： 接着客户端继续发送Change Cipher Spec报文。该报文会提示服务器，在此报文之后的通信会采用Pre-master secret密钥加密。步骤7： 客户端发送Finished报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够成功，要以服务器是否能够正确解密该报文作为判定标准。步骤8： 服务器同样发送Change Cipher Spec报文。步骤9： 服务器同样发送Finished报文。步骤10： 服务器和客户端的Finished报文交换完毕之后，SSL连接就算建立完成。当然，通信会受到SSL的保护。从此处开始进行应用层协议的通信，即发送HTTP请求。步骤11： 应用层协议通信，即发送HTTP响应。步骤12： 最后由客户端断开连接。断开连接时，发送close\_notify报文。上图做了一些省略，这步之后再发送TCP FIN报文来关闭与TCP的通信。

#### HTTP2

HTTP2的目标是提高传输性能。

http2的特点

* 多路复用
* 二进制协议
* 首部压缩

**二进制协议**

http1.1是基于文本的，而http2则是基于二进制的，http消息被定义成数据帧发送。

**多路复用**

http1.1是一种同步的、请求-响应式的协议，一条连接中当发送一条请求后，需要等待接收完响应后才能发送另一条请求，这种方式效率低下。

而http2实现了多路复用，即允许单条连接上同时执行多条请求。

http2通过使用`二进制分帧层`，将一条请求或响应封装成帧，并标上一个流(消息)的标识符，发送方可以将多条请求同时发送出去，当接收方接收一条流的所有帧后，可以将帧合成完整的消息。

http2提高效率的重点在于他不用等待响应接收后才能发送下一个请求，而是可以变接收响应边发送请求，这样的效率更高。

#### Get与Post的区别

* get方法如果不改变url，可能会存在缓存问题，一般会在后面加上时间戳来重新加载。
* get请求参数是放在url中，而post参数是放到请求体里。
* post方式请求时，需要设置编码类型，一般是在请求头里的`content-type`里设置，值有`application/x-www-form-urlencoded`、`application/json`、`multipart/formdata`等格式。
* get主要是用作获取数据，而post是修改数据
* get的参数是放到url中，也就是说参数的长度取决于浏览器的url限制长度。

### DNS 域名系统

DNS全称域名系统，他的作用是实现ip地址与域名的互转。

DNS传输使用的是UDP，这样能减少性能开销，此外域名服务器还设有缓存，会将查询结果缓存一定时间，来减少查询次数。

域名系统查询过程(迭代)

* 浏览器将域名发送给本地域名服务器，并成为本地域名服务器的一个客户
* 本地域名服务器查询根域名服务器，根域名服务器给出下一步要查询的域名服务器
* 本地域名服务器查询顶级域名服务器，顶级域名服务器给出下一步要查询的域名服务器
* 本地域名服务器查询二级域名服务器，二级域名服务器给出下一步要查询的域名服务器
* .........
* 本地服务器查询权限域名服务器，并获取主机的ip地址
* 本地域名服务器将查询结果告诉主机

域名系统查询过程(递归)

* 浏览器将域名发送给本地域名服务器，并成为本地域名服务器的一个客户
* 本地域名服务器查询根域名服务器
* 根域名查询顶级域名服务器
* 顶级域名服务器查询二级域名服务器
* 二级域名服务器查询权限域名服务器
* 权限域名服务器查询到主机的ip地址，并将结果返回给二级域名服务器
* 二级域名服务器将查询结果返回给顶级域名服务器
* 顶级域名服务器将查询结果返回给跟域名服务器
* 根域名服务器将查询结果返回给本地域名服务器
* 本地域名服务器将查询结果返回给主机
