# 应用层之HTTP协议

HTTP协议是`Hyper Text Transfer Protocol`（超文本传输协议）的缩写，是用于从万维网（`WWW:World Wide Web` ）服务器传输超文本到本地浏览器的传送协议。HTTP基于TCP/IP通信协议来传递数据（HTML 文件, 图片文件, 查询结果等），是一个属于应用层的面向对象的协议，**默认端口号是 80**，由于其简捷、快速的方式，适用于分布式超媒体信息系统。 它于1990年被提出，由于其简洁性、快速性等特点，被广泛应用，并且经过不断的完善和发展，功能也越来越强大，目前已经发展到`HTTP/2`版本。**HTTP协议工作于客户端-服务端架构之上**，浏览器作为HTTP客户端通过URL向HTTP服务端即Web服务器发送所有请求，Web服务器根据接收到的请求后，向客户端发送响应信息。

## :pencil2: URL

URL，全称是`Uniform Resource Locator`，中文叫统一资源定位符，是互联网上用来标识某一处资源的地址。以下面这个URL为例，介绍下普通URL的各部分组成：

```
http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=24618&page=1#name
```

一个完整的URL包括以下几部分：

1. 协议部分：该URL的协议部分为`“http:”`，这代表网页使用的是HTTP协议。在Internet中可以使用多种协议，如HTTP，FTP等等本例中使用的是HTTP协议。在`“http:”`后面的`“//”`为分隔符。
2. 域名部分：该URL的域名部分为`“www.aspxfans.com”`。一个URL中，也可以使用IP地址作为域名使用。
3. 端口部分：跟在域名后面的是端口，域名和端口之间使用`“:”`作为分隔符。端口不是一个URL必须的部分，如果省略端口部分，将采用默认端口。
4. 虚拟目录部分：从域名后的第一个`“/”`开始到最后一个`“/”`为止，是虚拟目录部分。虚拟目录也不是一个URL必须的部分。本例中的虚拟目录是`“/news/”`。
5. 文件名部分：从域名后的最后一个`“/”`开始到`“?”`为止，是文件名部分，如果没有`“?”`，则是从域名后的最后一个`“/”`开始到`“#”`为止，是文件部分，如果没有`“?”`和`“#”`，那么从域名后的最后一个`“/”`开始到结束，都是文件名部分。本例中的文件名是`“index.asp”`。文件名部分也不是一个URL必须的部分，如果省略该部分，则使用默认的文件名。
6. 锚部分：从`“#”`开始到最后，都是锚部分。本例中的锚部分是`“name”`。锚部分也不是一个URL必须的部分。
7. 参数部分：从`“?”`开始到`“#”`为止之间的部分为参数部分，又称搜索部分、查询部分。本例中的参数部分为`“boardID=5&ID=24618&page=1”`。参数可以允许有多个参数，参数与参数之间用`“&”`作为分隔符。

### :paintbrush: URL与URI

HTTP使用统一资源标识符（`Uniform Resource Identifiers, URI`）来传输数据和建立连接。

**URI，`uniform resource identifier`，统一资源标识符，用来唯一的标识一个资源。**

Web上可用的每种资源如HTML文档、图像、视频片段、程序等都是一个来URI来定位的，URI一般由三部组成：①访问资源的命名机制；②存放资源的主机名；③资源自身的名称，由路径表示，着重强调于资源。

**URL，`uniform resource locator`，统一资源定位器，它是一种具体的URI，即URL可以用来标识一个资源，而且还指明了如何locate这个资源。**

URL是Internet上用来描述信息资源的字符串，主要用在各种WWW客户程序和服务器程序上，特别是著名的Mosaic。 采用URL可以用一种统一的格式来描述各种信息资源，包括文件、服务器的地址和目录等，URL一般由三部组成： ①协议(或称为服务方式) ；②存有该资源的主机IP地址(有时也包括端口号) ；③主机资源的具体地址，如目录和文件名等。

**URN，`uniform resource name`，统一资源命名，是通过名字来标识资源，它命名资源但不指定如何定位资源，比如`mailto:java-net@java.sun.com`。**

URI是以一种抽象的，高层次概念定义统一资源标识，而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。在Java中，一个URI实例可以代表绝对的，也可以是相对的，只要它符合URI的语法规则，URI类不包含任何访问资源的方法，它唯一的作用就是解析。而URL实例则不仅符合语义，还包含了定位该资源的信息，因此它不能是相对的，并且一个URL类的实例可以打开一个到达资源的流。

## :pencil2: `Http`的特点

1. 简单快速：客户向服务器请求服务时，只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单，使得HTTP服务器的程序规模小，因而通信速度很快。
2. 灵活（媒体独立）：HTTP允许传输任意类型的数据对象。正在传输的类型由`Content-Type`加以标记。
3. 无连接：无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求，并收到客户的应答后，即断开连接。采用这种方式可以节省传输时间。
4. 无状态：HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息，则它必须重传，这样可能导致每次连接传送的数据量增大。另一方面，在服务器不需要先前信息时它的应答就较快。
5. 支持B/S及C/S模式。

## :pencil2: 版本历史

### :paintbrush: 1、HTTP/0.9

最早版本发布于`1991`年，功能极其简单，不涉及数据包的传输，默认使用80端口，只有一个`GET`请求方法，且服务器只能响应`HTML`格式的字符串，服务器响应后即关闭连接。

### :paintbrush: 2、HTTP/1.0

`1996`年`5`月，`HTTP/1.0`发布，引入了`POST`和`HEAD`命令，大大增强了交互功能，任何格式的内容都可以发送，为互联网的大发展奠定了基础；同时，除了数据部分，每次通信还要求包含头信息（`HTTP header`），来描述一些`meta`数据。

新增功能还包括：状态码（`status code`）、多字符集支持、多部分发送（`multi-part type`）、权限（`authorization`）、缓存（`cache`）、内容编码（`content encoding`）等。

> **缺点：**`HTTP/1.0`版的主要缺点是，由于`TCP`的每次连接都需要客户端和服务端进行`3`次握手，但是连接成功后只能发送一次请求，然后连接就断开了，如果需要多次请求，这样效率就很低。但是，为了解决多次请求效率低下的问题，有一个非标准的`connection`字段暂时解决了该问题。

```
Connection: keep-alive
```

> 这样就可以复用`TCP`连接，直到客户端或者服务端主动关闭连接。但是这不是标志字段，不同的实现可能行为不一致，所以是一种暂时的解决方案。

### :paintbrush: 3、HTTP/1.1

&#x20;`1997`年`1`月，`HTTP/1.1`版本发布，它进一步完善了`HTTP`协议，一直到今天还在使用，是最流行的版本；`HTTP/1.1`新增了许多特性：

#### 1.持久连接

`HTTP/1.1`默认`TCP`连接不关闭，可以被多个请求复用，不用声明`Connection: keep-alive`。客户端可以在最后一个请求时，主动发送`Connection: close`，明确要求服务器关闭`TCP`连接，或者不发送，那么客户端和服务器发现对方一段时间没有活动，就会主动关闭连接。目前，对于同一个域名，大多数浏览器允许同时建立`6`个持久连接。

#### 2.管道机制

`HTTP/1.1`引入了管道机制（`pipelining`），即在同一个`TCP`连接里面，客户端可以同时发送多个请求，这样只是改进了客户端`HTTP`协议请求的效率，服务器还是按照请求的先后顺序来响应。

#### 3.Content-Length

`Content-Length`字段显示本次响应的数据长度，如果数据被压缩，则是压缩后的长度。在`Connection: keep-alive`条件下，`Content-Length`是必须的；反之，和`HTTP/1.0`一样，`Content-Length`不是必须的。

#### 4.分块传输编码

使用`Content-Lenght`字段的前提条件是，服务器发送响应之前，必须知道响应的数据长度。但是，对于一些耗时的动态操作来说，服务器要等到所有操作完成后，才能发送数据，效率不高。因此，`HTTP`采用了“流模式（`stream`）”，即“分块传输编码”（`chunked transfer encoding`）方式，表明响应的数据长度未定，这样就可以产生一块数据，就发送一块数据，提高服务器的响应效率。只要请求或响应的头信息里有`Transfer-Encoding`字段，就表明响应将由数量未定的数据组成。每个非空的数据块之前，都会有一个`16`进制的数值，表示这个块的长度；最后一个是大小为`0`的块，表示本次响应数据发送完了。

#### 5.其它特性

新增了许多的请求方式：`PUT`、`PATCH`、`OPTIONS`、`DELETE`、`TRACE`，还增加了`Host`字段，用来指定服务器的域名，就可以把同一请求发送给不同的网站，为虚拟主机的发展奠定了基础。

> 缺点：虽然`HTTP/1.1`版允许复用`TCP`连接，但是同一个`TCP`连接里面，所有的数据通信是按次序进行的。服务器只有处理完一个回应，才会进行下一个回应。要是前面的回应特别慢，后面就会有许多请求排队等着，这称为"队头堵塞"（`Head-of-line blocking`）。
>
> 为了避免这个问题，只有两种方法：一是减少请求数，二是同时多开持久连接。这导致了很多的网页优化技巧，比如合并脚本和样式表、将图片嵌入`CSS`代码、域名分片（`domain sharding`）等等。如果`HTTP`协议设计得更好一些，这些额外的工作是可以避免的。

### :paintbrush: 4、HTTP/2

2015年，HTTP/2发布，主要解决了`HTTP1.1`的效率不高的问题，新增了二进制协议、多工、数据流、头信息压缩等等功能，具体介绍如下：

#### 1.二进制协议

`HTTP/2`是一个彻底的二进制协议，头信息和数据都是二进制，统称为**帧**（`frame`）：头信息帧和数据帧。二进制的优势是：可以定义额外的帧，以适应未来更高级的应用。

#### 2.多工

`HTTP/2`复用`TCP`连接，客户端和服务器都可以同时发送多个请求或响应，不用按照顺序一一对应，避免了**对头堵塞**，实现了双向的、实时的通信。

#### 3.数据流

`HTTP/2`定义每个请求或响应的所有数据为一个数据流（`stream`），每个数据流都有一个唯一的编号，数据包发送的时候，都必须标记数据流ID，用来区分它属于哪个数据流。协议规定：客户端发出的数据流的ID位奇数，服务器发出的数据流ID为偶数。

另外，数据流发送过程中，客户端和服务器都可以随时发送信号而不用关闭`TCP`连接，以便其它请求使用。但是`HTTP1.1`就不行，它取消请求的方式必须的关闭连接。`HTTP/2`还指定了数据流的优先级，优先级越高，服务器就优先处理。

#### 4.头信息压缩

`HTTP` 协议不带有状态，每次请求都必须附上所有信息。所以，请求的很多字段都是重复的，比如`Cookie`和`User Agent`，一模一样的内容，每次请求都必须附带，这会浪费很多带宽，也影响速度。

`HTTP/2` 对这一点做了优化，引入了头信息压缩机制（`header compression`）。一方面，头信息使用`gzip`或`compress`压缩后再发送；另一方面，客户端和服务器同时维护一张头信息表，所有字段都会存入这个表，生成一个索引号，以后就不发送同样字段了，只发送索引号，这样就提高速度了。

#### 5.服务器推送

`HTTP/2`允许服务器主动向客户端发送资源，主要是服务器经过预测，把一些客户端可能请求的资源主动发送给客户端，提高用户体验。

## :pencil2: 请求消息Request

HTTP的请求报文包括：**请求行(request line)**、**请求头部(header)**、**空行** 和 **请求数据** 四个部分组成。

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MC6AJbz2s3xaqTrvQrb%2F-MC6KO1lc0m46wee-FuO%2F23.png?alt=media\&token=cc9c9db8-73ad-48b6-892d-01ff8cb495c2)

&#x20;抓包的`request`结构如下：

```http
GET /mix/76.html?name=kelvin&password=123456 HTTP/1.1
Host: www.fishbay.cn
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
```

**1.请求行：**`GET`为请求类型，`/mix/76.html?name=kelvin&password=123456`为要访问的资源，`HTTP/1.1`是协议版本

**2.请求头部：**&#x4ECE;第二行起为请求头部，`Host`指出请求的目的地（主机域名）；`User-Agent`是客户端的信息，它是检测浏览器类型的重要信息，由浏览器定义，并且在每个请求中自动发送。

**3.空行：**&#x8BF7;求头后面必须有一个空行

**4.请求数据：**&#x8BF7;求的数据也叫请求体，可以添加任意的其它数据。这个例子的请求体为空。

### :paintbrush: 1、请求头

**常用标准请求头字段有：**

1. **Accept：**&#x6307;浏览器或其他客户可以接爱的`MIME`文件格式。
2. **Accept-Encoding：**&#x6307;出浏览器可以接受的编码方式。
3. **Accept-Langeuage：**&#x6307;出浏览器可以接受的语言种类，如`en`或`en-us`，指英语。
4. **Connection：**&#x7528;来告诉服务器是否可以维持固定的HTTP连接。
5. **Cookie：**&#x6D4F;览器用这个属性向服务器发送`Cookie`。
6. **Host：**&#x5BF9;应网址URL中的Web名称和端口号。
7. **User-Agent：**&#x5BA2;户浏览器名称。
   * **兼容性问题**
8. **Referer：**&#x8868;明产生请求的网页URL。
   * 防盗链
   * 统计工作
9. **Content-Type：**&#x7528;来表明`request`的内容类型（适用POST和PUT请求）。&#x20;
10. **Accept-Charset：**&#x6307;出浏览器可以接受的字符编码。英文浏览器的默认值是`ISO-8859-1`。
11. **If-Modified-Since：**&#x5982;：`Tue, 11 Jul 2000 18:23:51 GMT`，告诉服务器，缓冲中有这个资源文件，并带上文件的时间。
12. **Date：**&#x6D4F;览器发送该http请求的时间。

更多字段<https://www.cnblogs.com/widget90/p/7650890.html>。

### :paintbrush: 2、请求方法

`HTTP1.0`定义了三种请求方法： `GET, POST` 和 `HEAD`方法。

`HTTP1.1`新增了五种请求方法：`OPTIONS, PUT, DELETE, TRACE` 和 `CONNECT` 方法。

|    方法   | 描述                                                                     |
| :-----: | ---------------------------------------------------------------------- |
|   GET   | 请求指定的页面信息，并返回实体主体。                                                     |
|   HEAD  | 类似于get请求，只不过返回的响应中没有具体的内容，用于获取报头。                                      |
|   POST  | 向指定资源提交数据进行处理请求（例如提交表单或者上传文件）。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 |
|   PUT   | 从客户端向服务器传送的数据取代指定的文档的内容。                                               |
|  DELETE | 请求服务器删除指定的页面。                                                          |
| CONNECT | HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。                                       |
| OPTIONS | 允许客户端查看服务器的性能。                                                         |
|  TRACE  | 回显服务器收到的请求，主要用于测试或诊断。                                                  |

### :paintbrush: 3、GET和POST

|           | GET                                                                                      | POST                                                                      |
| --------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| 后退按钮/刷新按钮 | 无害                                                                                       | 数据会被重新提交（浏览器应该告知用户数据会被重新提交）。                                              |
| 书签        | 可收藏为书签                                                                                   | 不可收藏为书签                                                                   |
| 缓存        | 能被缓存                                                                                     | 不能缓存                                                                      |
| 编码类型      | `application/x-www-form-urlencoded`                                                      | `application/x-www-form-urlencoded` 或 `multipart/form-data`。为二进制数据使用多重编码。 |
| 历史        | 参数保留在浏览器历史中。                                                                             | 参数不会保存在浏览器历史中。                                                            |
| 对数据长度的限制  | 是的。当发送数据时，GET 方法向 URL 添加数据；URL 的长度是受限制的（URL 的最大长度是 2048 个字符）。                            | 无限制。                                                                      |
| 对数据类型的限制  | 只允许 ASCII 字符。                                                                            | 没有限制。也允许二进制数据。                                                            |
| 安全性       | 与 POST 相比，GET 的安全性较差，因为所发送的数据是 URL 的一部分。使用GET提交的数据可能会造成`CSRF`攻击。在发送密码或其他敏感信息时绝不要使用 GET ！ | POST 比 GET 更安全，因为参数不会被保存在浏览器历史或 web 服务器日志中。                               |
| 可见性       | 数据在 URL 中对所有人都是可见的。                                                                      | 数据不会显示在 URL 中。                                                            |

1. GET提交，请求的数据会附在URL之后（就是把数据放置在HTTP协议头中），以`?`分割URL和传输数据，多个参数用`&`连接；如果数据是英文字母/数字，原样发送，如果是空格，转换为`+`，如果是中文/其他字符，则直接把字符串用`BASE64`加密。POST提交：把提交的数据放置在是HTTP包的包体中。因此，GET提交的数据会在地址栏中显示出来，而POST提交，地址栏不会改变。
2. 首先声明：HTTP协议没有对传输的数据大小进行限制，HTTP协议规范也没有对URL长度进行限制。

   而在实际开发中存在的限制主要有：

   **GET：**&#x7279;定浏览器和服务器对URL长度有限制，例如IE对URL长度的限制是2083字节(2K+35)。对于其他浏览器，如Netscape、FireFox等，理论上没有长度限制，其限制取决于操作系统的支持。因此对于GET提交时，传输数据就会受到URL长度的限制。

   **POST：**&#x7531;于不是通过URL传值，理论上数据不受限。但实际各个WEB服务器会规定对`post`提交数据大小进行限制，Apache、IIS6都有各自的配置。
3. GET方式需要使用`Request.QueryString`来取得变量的值，而POST方式通过`Request.Form`来获取变量的值。

### :paintbrush: 4、条件GET

HTTP 条件 GET 是 HTTP 协议为了减少不必要的带宽浪费，提出的一种方案。详见 [RFC2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) 。

HTTP 条件 GET 使用的时机：客户端之前已经访问过某网站，并打算再次访问该网站。

HTTP 条件 GET 使用的方法：客户端向服务器发送一个包询问是否在上一次访问网站的时间后是否更改了页面，如果服务器没有更新，显然不需要把整个网页传给客户端，客户端只要使用本地缓存即可，如果服务器对照客户端给出的时间已经更新了客户端请求的网页，则发送这个更新了的网页给用户。

> 下面是一个具体的发送接受报文示例：
>
> 客户端发送请求：

```http
 GET / HTTP/1.1  
 Host: www.sina.com.cn:80  
 If-Modified-Since: Thu, 4 Feb 2010 20:39:13 GMT  
 Connection: Close  
```

> 第一次请求时，服务器端返回请求数据，之后的请求，服务器根据请求中的 `If-Modified-Since` 字段判断响应文件没有更新，如果没有更新，服务器返回一个 `304 Not Modified`响应，告诉浏览器请求的资源在浏览器上没有更新，可以使用已缓存的上次获取的文件。

```http
 HTTP/1.0 304 Not Modified  
 Date: Thu, 04 Feb 2010 12:38:41 GMT  
 Content-Type: text/html  
 Expires: Thu, 04 Feb 2010 12:39:41 GMT  
 Last-Modified: Thu, 04 Feb 2010 12:29:04 GMT  
 Age: 28  
 X-Cache: HIT from sy32-21.sina.com.cn  
 Connection: close 
```

> 如果服务器端资源已经更新的话，就返回正常的响应。

### :paintbrush: 5、幂等性

幂等性是数学中的一个概念，表达的是N次变换与1次变换的结果相同，在这里幂等性即为无论请求几次，最后的结果都是一样的。get为查询，所以为幂等；put为修改，为幂等；delete为删除，为幂等；post为增加或修改等，不为幂等。

## :pencil2: 响应消息Response

一般情况下，服务器收到客户端的请求后，就会有一个`HTTP`的响应消息，HTTP响应也由`4`部分组成，分别是：**状态行**、**响应头**、**空行** 和 **响应体**。

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MC6AJbz2s3xaqTrvQrb%2F-MC6KQYCkUqNvwdMi0qu%2F22.png?alt=media\&token=4b7de2ef-ef36-4e9d-97fc-f90e5546188c)

抓包的`response`结构如下：

```http
HTTP/1.1 200 OK
Server: nginx
Date: Mon, 20 Feb 2017 09:13:59 GMT
Content-Type: text/plain;charset=UTF-8
Vary: Accept-Encoding
Cache-Control: no-store
Pragrma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Content-Encoding: gzip
Transfer-Encoding: chunked
Proxy-Connection: Keep-alive

{"code":200,"notice":0,"follow":0,"forward":0,"msg":0,"comment":0,"pushMsg":null,"friend":{"snsCount":0,"count":0,"celebrityCount":0},"lastPrivateMsg":null,"event":0,"newProgramCount":0,"createDJRadioCount":0,"newTheme":true}
```

**1.状态行：**&#x72B6;态行由协议版本号、状态码、状态消息组成。

**2.响应头：**&#x54CD;应头是客户端可以使用的一些信息，如：`Date`（生成响应的日期）、`Content-Type`（MIME类型及编码格式）、`Connection`（默认是长连接）等等。

**3.空行：**&#x54CD;应头和响应体之间必须有一个空行。

**4.响应体：**&#x54CD;应正文，本例中是键值对信息。

### :paintbrush: 1、响应头

**常用标准响应头字段有：**

1. **Status：**&#x8BBE;置HTTP响应状态。
2. **Server：**&#x670D;务器名称。如：`Server: Apache/2.4.1 (Unix)`。
3. **Content-Type：**&#x8BBE;置响应体的MIME类型。&#x20;
4. **Content-Encoding：**&#x8BBE;置数据使用的编码类型。发送的数据还可以被压缩后再发送，该字段说明了数据压缩的方法。如：`gzip`、`compress`、`deflate`。客户端还可以在请求时，用`Accept-Encoding`字段说明自己可以接受哪些压缩方法。`Accept-Encoding: gzip, deflate`。
5. **Content-Language：**&#x4E3A;封闭内容设置自然语言或者目标用户语言。
6. **Content-Length：**&#x54CD;应体的字节长度。
7. **Connection：**`close/Keep-Alive`，设置当前连接和hop-by-hop协议请求字段列表的控制选项。

### :paintbrush: 2、数据类型

`1.0`版规定，头信息必须是ASCII码，后面的数据可以是任何格式，因此，服务器在响应的时候，也要告诉客户端，数据是什么格式。`Content-Type`字段的常用值：`text/plain`、`text/html`、`text/css`、`image/jpeg`、`image/png`、`image/svg+xml`、`audio/mp4`、`video/mp4`、`application/javascript`、`application/pdf`、`application/zip`、`application/atom+xml`。另外，这些数值统称为`MIME type`，每个值包括一级类型和二级类型，之间用斜杠分隔，用户也可以自定义该类型，还可以在尾部使用分号，添加参数：`Content-Type: text/html; charset=utf-8`表示发送的是网页，并且编码是的UTF-8格式，`Accept: */*` 表示客户端可以接受任何格式的数据。

更多数据类型<https://www.runoob.com/http/http-content-type.html>。

### :paintbrush: 3、状态码

#### :sparkler: 3.1、HTTP状态码分类

HTTP状态码由三个十进制数字组成，第一个十进制数字定义了状态码的类型，后两个数字没有分类的作用。HTTP状态码共分为5种类型：

| HTTP状态码分类 |                         |
| --------- | ----------------------- |
| 分类        | 分类描述                    |
| 1\*\*     | 信息，服务器收到请求，需要请求者继续执行操作  |
| 2\*\*     | 成功，操作被成功接收并处理           |
| 3\*\*     | 重定向，需要进一步的操作以完成请求       |
| 4\*\*     | 客户端错误，请求包含语法错误或无法完成请求   |
| 5\*\*     | 服务器错误，服务器在处理请求的过程中发生了错误 |

#### :sparkler: 3.2、常见的状态码

1. 101 Switching Protocols      //切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议，例如，切换到HTTP的新版本协议
2. 200 OK                                    //客户端请求成功
3. 204 No Content                     //无内容。服务器成功处理，但未返回内容。在未更新网页的情况下，可确保浏览器继续显示当前文档
4. 301 Moved Permanently     //永久移动。请求的资源已被永久的移动到新URI，返回信息会包括新的URI，浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
5. 302 Moved Temporarily      //请求临时重定向
6. 304 Not Modified                 //文件未修改，可以直接使用缓存的文件
7. 400 Bad Request                  //客户端请求有语法错误，不能被服务器所理解
8. 401 Unauthorized                 //请求未经授权，这个状态代码必须和`WWW-Authenticate`报头域一起使用
9. 403 Forbidden                       //服务器收到请求，但是拒绝提供服务
10. 404 Not Found                      //请求资源不存在，eg：输入了错误的URL
11. 500 Internal Server Error     //服务器发生不可预期的错误
12. 503 Server Unavailable        //服务器当前不能处理客户端的请求，一段时间后可能恢复正常

更多状态码<http://www.runoob.com/http/http-status-codes.html>。

## :pencil2: 工作原理

HTTP协议定义Web客户端如何从Web服务器请求Web页面，以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文，请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应，响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。以下是 HTTP 请求/响应的步骤：

**1、客户端连接到Web服务器：**&#x4E00;个HTTP客户端，通常是浏览器，与Web服务器的HTTP端口（默认为80）建立一个TCP套接字连接。

**2、发送HTTP请求：**&#x901A;过TCP套接字，客户端向Web服务器发送一个文本的请求报文，一个请求报文由请求行、请求头部、空行和请求数据4部分组成。

**3、服务器接受请求并返回HTTP响应：**&#x57;eb服务器解析请求，定位请求资源。服务器将资源复本写到TCP套接字，由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。

**4、释放连接TCP连接：**&#x82E5; connection 模式为close，则服务器主动关闭TCP连接，客户端被动关闭连接，释放TCP连接；若 connection 模式为keep-alive，则该连接会保持一段时间，在该时间内可以继续接收请求。

**5、客户端浏览器解析HTML内容：**&#x5BA2;户端浏览器首先解析状态行，查看表明请求是否成功的状态代码。然后解析每一个响应头，响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML，根据HTML的语法对其进行格式化，并在浏览器窗口中显示。

> 在浏览器地址栏键入URL，按下回车之后会经历以下流程：
>
> 1、浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址；
>
> 2、解析出 IP 地址后，根据该 IP 地址和默认端口 80，和服务器建立TCP连接；
>
> 3、浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求，该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器；
>
> 4、服务器对浏览器请求作出响应，并把对应的 html 文本发送给浏览器；
>
> 5、释放 TCP连接；
>
> 6、浏览器将该 html 文本解析并显示内容。

## :pencil2: Transfer-Encoding

`Transfer-Encoding` 是一个用来标示 `HTTP` 报文传输格式的头部值。尽管这个取值理论上可以有很多，但是当前的 `HTTP` 规范里实际上只定义了一种传输取值——`chunked`。

如果一个`HTTP`消息（请求消息或应答消息）的`Transfer-Encoding`消息头的值为`chunked`，那么消息体由数量未定的块组成，并以最后一个大小为`0`的块为结束。每一个非空的块都以该块包含数据的字节数（字节数以十六进制表示）开始，跟随一个`CRLF` （回车及换行），然后是数据本身，最后块`CRLF`结束。在一些实现中，块大小和`CRLF`之间填充有**白空格**（`0x20`）。

最后一块是单行，由块大小（`0`），一些可选的填充白空格，以及`CRLF`。最后一块不再包含任何数据，但是可以发送可选的尾部，包括消息头字段。消息最后以`CRLF`结尾。

一个示例响应如下：

```http
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

25
This is the data in the first chunk

1C
and this is the second one

3
con

8
sequence

0
```

> 注意：
>
> 1. `chunked` 和 `multipart` 两个名词在意义上有类似的地方，不过在 `HTTP` 协议当中这两个概念则不是一个类别的。`multipart` 是一种 `Content-Type`，标示 `HTTP` 报文内容的类型，而 `chunked` 是一种传输格式，标示报头将以何种方式进行传输。
> 2. `chunked` 传输不能事先知道内容的长度，只能靠最后的空 `chunk` 块来判断，因此对于下载请求来说，是没有办法实现进度的。在浏览器和下载工具中，偶尔我们也会看到有些文件是看不到下载进度的，即采用 chunked 方式进行下载。
> 3. `chunked` 的优势在于，服务器端可以边生成内容边发送，无需事先生成全部的内容。`HTTP/2` 不支持 `Transfer-Encoding: chunked`，因为 HTTP/2 有自己的 `streaming` 传输方式（Source：[MDN - Transfer-Encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding)）。

## :pencil2: Http持久连接

`HTTP` 协议采用“请求-应答”模式，当使用普通模式，即非 `Keep-Alive` 模式时，每个**请求/应答**客户和服务器都要新建一个连接，完成之后立即断开连接（HTTP 协议为无连接的协议）；当使用 `Keep-Alive` 模式（又称持久连接、连接重用）时，`Keep-Alive` 功能使客户端到服务器端的连接持续有效，当出现对服务器的后继请求时，`Keep-Alive` 功能避免了建立或者重新建立连接。

在 HTTP 1.0 版本中，并没有官方的标准来规定 `Keep-Alive` 如何工作，因此实际上它是被附加到 `HTTP 1.0`协议上，如果客户端浏览器支持 `Keep-Alive` ，那么就在`HTTP`请求头中添加一个字段 `Connection: Keep-Alive`，当服务器收到附带有 `Connection: Keep-Alive` 的请求时，它也会在响应头中添加一个同样的字段来使用 `Keep-Alive` 。这样一来，客户端和服务器之间的HTTP连接就会被保持，不会断开（直到超过 `Keep-Alive` 规定的时间），当客户端发送另外一个请求时，就使用这条已经建立的连接。

由于 HTTP 1.0 没有官方的 `Keep-Alive` 规范，并且也已经基本被淘汰，在 `HTTP 1.1` 版本中，默认情况下所有连接都被保持，如果加入 `"Connection: close"` 才关闭。目前大部分浏览器都使用 `HTTP 1.1` 协议，也就是说默认都会发起 `Keep-Alive` 的连接请求了，所以是否能完成一个完整的 `Keep-Alive` 连接就看服务器设置情况。

> **注意：**
>
> 1. `HTTP Keep-Alive` 简单说就是保持当前的TCP连接，避免了重新建立连接。
> 2. `HTTP` 长连接不可能一直保持，例如 `Keep-Alive: timeout=5, max=100`，表示这个TCP通道可以保持5秒，`max=100`表示这个长连接最多接收`100`次请求就断开。
> 3. `HTTP` 是一个无状态协议，这意味着每个请求都是独立的，**Keep-Alive** 没能改变这个结果。另外，`Keep-Alive`也不能保证客户端和服务器之间的连接一定是活跃的，在 `HTTP1.1` 版本中也如此。唯一能保证的就是当连接被关闭时你能得到一个通知，所以不应该让程序依赖于 `Keep-Alive` 的保持连接特性，否则会有意想不到的后果。
> 4. 使用长连接之后，客户端、服务端怎么知道本次传输结束呢？两部分：1. 判断传输数据是否达到了`Content-Length` 指示的大小；2. 动态生成的文件没有 `Content-Length` ，它是分块传输`（chunked）`，这时候就要根据 `chunked` 编码来判断，`chunked` 编码的数据在最后有一个空 `chunked` 块，表明本次传输数据结束。

## :pencil2: Http Pipelining（管线化）

默认情况下 `HTTP` 协议中每个传输层连接只能承载一个 `HTTP` 请求和响应，浏览器会在收到上一个请求的响应之后，再发送下一个请求。在使用持久连接的情况下，某个连接上消息的传递类似于`请求1 -> 响应1 -> 请求2 -> 响应2 -> 请求3 -> 响应3`。

`HTTP Pipelining`（管线化）是将多个 `HTTP` 请求整批提交的技术，在传送过程中不需等待服务端的回应。使用 `HTTP Pipelining` 技术之后，某个连接上的消息变成了类似这样`请求1 -> 请求2 -> 请求3 -> 响应1 -> 响应2 -> 响应3`。

> 注意：
>
> 1. 管线化机制通过持久连接（`persistent connection`）完成，仅 `HTTP/1.1` 支持此技术（`HTTP/1.0`不支持）。
> 2. 只有 GET 和 HEAD 请求可以进行管线化，而 POST 则有所限制。
> 3. 初次创建连接时不应启动管线机制，因为对方（服务器）不一定支持 HTTP/1.1 版本的协议。
> 4. 管线化不会影响响应到来的顺序，如上面的例子所示，响应返回的顺序并未改变。
> 5. `HTTP /1.1` 要求服务器端支持管线化，但并不要求服务器端也对响应进行管线化处理，只是要求对于管线化的请求不失败即可。
> 6. 由于上面提到的服务器端问题，开启管线化很可能并不会带来大幅度的性能提升，而且很多服务器端和代理程序对管线化的支持并不好，因此现代浏览器如 `Chrome` 和 `Firefox` 默认并未开启管线化支持。

## :pencil2: 会话跟踪

> 1. 什么是会话？
>
>    客户端打开与服务器的连接发出请求到服务器响应客户端请求的全过程称之为会话。
> 2. 什么是会话跟踪？
>
>    会话跟踪指的是对同一个用户对服务器的连续的请求和接受响应的监视。
> 3. 为什么需要会话跟踪？
>
>    浏览器与服务器之间的通信是通过`HTTP`协议进行通信的，而`HTTP`协议是”无状态”的协议，它不能保存客户的信息，即一次响应完成之后连接就断开了，下一次的请求需要重新连接，这样就需要判断是否是同一个用户，所以才有会话跟踪技术来实现这种要求。

### :paintbrush: 会话跟踪常用的方法

#### 1、URL 重写

URL(统一资源定位符)是Web上特定页面的地址，URL重写的技术就是在URL结尾添加一个附加数据以标识该会话,把会话ID通过URL的信息传递过去，以便在服务器端进行识别不同的用户。

#### 2、隐藏表单域

将会话ID添加到HTML表单元素中提交到服务器，此表单元素并不在客户端显示

#### 3、Cookie

`Cookie` 是Web 服务器发送给客户端的一小段信息，客户端请求时可以读取该信息发送到服务器端，进而进行用户的识别。对于客户端的每次请求，服务器都会将 `Cookie` 发送到客户端,在客户端可以进行保存,以便下次使用。

客户端可以采用两种方式来保存这个 `Cookie` 对象，一种方式是保存在客户端内存中，称为临时 `Cookie`，浏览器关闭后这个 `Cookie` 对象将消失。另外一种方式是保存在客户机的磁盘上，称为永久 `Cookie`。以后客户端只要访问该网站，就会将这个 `Cookie` 再次发送到服务器上，前提是这个 `Cookie` 在有效期内，这样就实现了对客户的跟踪。

`Cookie` 是可以被客户端禁用的。

#### 4、Session:

每一个用户都有一个不同的 `session`，各个用户之间是不能共享的，是每个用户所独享的，在 `session` 中可以存放信息。

在服务器端会创建一个 `session` 对象，产生一个 `sessionID` 来标识这个 `session` 对象，然后将这个 `sessionID` 放入到 `Cookie` 中发送到客户端，下一次访问时，`sessionID` 会发送到服务器，在服务器端进行识别不同的用户。

`Session` 的实现依赖于 `Cookie`，如果 `Cookie` 被禁用，那么 `session` 也将失效。

## :pencil2:内容协商

HTTP协议的内容协商消息头包括：i.内容协商消息头，ii.缓存控制消息头，iii.条件控制消息头。

## :pencil2: Http缓存机制

`Http`缓存主要涉及三个角色：一是浏览器，二是浏览器的缓存数据库，三是服务器。当浏览器端向服务器发出**第一次**请求时：

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCKdA26wU4rsip7kYPu%2F-MCKftwJNthVHyrWbCQ1%2F39.png?alt=media\&token=b56bc355-62c6-4678-94fe-0ca814013574)

首次请求后即如上流程图所示执行，而当浏览器再次执行同样的请求时，会根据不同的缓存类型执行不同的操作。**缓存分为两种：强缓存和协商缓存。**

&#x20;已存在缓存数据时，仅基于**强制缓存，**&#x8BF7;求数据的流程如&#x4E0B;**：**

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCLbUKyKZbzjT6aW71A%2F-MCLcB31jCtsy-dQ0p54%2F42.png?alt=media\&token=7f93910a-efce-4af4-81d4-58c139fe428d)

&#x20;已存在缓存数据时，仅基于**协商缓存，**&#x8BF7;求数据的流程如&#x4E0B;**：**

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCLbUKyKZbzjT6aW71A%2F-MCLcHEF-0TrkUVL3tBq%2F45.png?alt=media\&token=567dbf24-3185-41e8-9bcb-dee92bbaf58b)

> 强制缓存如果生效，不需要再和服务器发生交互，而对比缓存不管是否生效，都需要与服务端发生交互。两类缓存规则可以同时存在，强制缓存优先级高于对比缓存，也就是说，当执行强制缓存的规则时，如果缓存生效，直接使用缓存，不再执行对比缓存规则。

### :paintbrush: **1、强缓存**

不会向服务器发送请求，直接从缓存中读取资源，在chrome控制台的Network选项中可以看到该请求返回200的状态码，并且`size`显示`from disk cache`或`from memory cache`两种（灰色表示缓存）。

1. `from memory cache`：从内存中获取/一般缓存更新频率较高的js、图片、字体等资源
2. `from disk cache`：从磁盘中获取/一般缓存更新频率较低的js、css等资源

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCKdA26wU4rsip7kYPu%2F-MCLKzA4VNrAiE-ioAyW%2F41.png?alt=media\&token=70eccffa-5097-48c8-a633-9f6b0983597f)

而控制强缓存过期时间的主要有两个规则字段：

* Expire：是一个`HTTP1.0`标准下的字段，其指定了一个日期/时间， 在这个日期/时间之后，HTTP响应被认为是过时的。另一个问题是，到期时间是由服务端生成的，但是客户端时间可能跟服务端时间有误差，这就会导致缓存命中的误差。所以HTTP 1.1 的版本，使用Cache-Control替代，如果请求中还有一个置了 `“max-age”` 或者 `“s-max-age”` 指令的`Cache-Control`响应头，那么 `Expires` 头就会被忽略。&#x20;
* Cache-Control：通用消息头用于在 `http` 请求和响应中通过指定指令来实现缓存机制。其常用的几个取值有：&#x20;
  * private：客户端可以缓存&#x20;
  * public：客户端和代理服务器都可以缓存&#x20;
  * max-age=xxx：缓存的内容将在xxx 秒后失效&#x20;
  * s-max-age=xxx：同s-max-age，但仅适用于共享缓存(比如各个代理)，并且私有缓存中忽略。&#x20;
  * no-cache：需要使用协商缓存来验证缓存数据&#x20;
  * no-store：所有内容都不会缓存，强缓存和协商缓存都不会触发&#x20;
  * must-revalidate：缓存必须在使用之前验证旧资源的状态，并且不可使用过期资源。

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCKdA26wU4rsip7kYPu%2F-MCLS0In3bYeQyTvtLNr%2F42.webp?alt=media\&token=d9f9d525-9073-4b85-84b1-98a83c4dffd6)

### :paintbrush: **2、协商缓存**

当强缓存过期未命中或者响应报文`Cache-Control`中有`must-revalidate`标识必须每次请求验证资源的状态时，便使用协商缓存的方式去处理缓存文件。

浏览器每次请求时会与服务器进行协商，与服务器端对比判断资源是否进行了修改更新。如果服务器端的资源没有修改，那么就会返回304状态码，告诉浏览器可以使用缓存中的数据，这样就减少了服务器的数据传输压力。如果数据有更新就会返回200状态码，服务器就会返回更新后的资源并且将缓存信息一起返回。跟协商缓存相关的`header`头属性有（`ETag/If-Not-Match 、Last-Modified/If-Modified-Since`）请求头和响应头需要成对出现。

* **`ETag`和`If-None-Match`:**

`Etag`是上一次加载资源时，服务器返回&#x7684;**`response header`**，是对该资源的一种**唯一标识，**&#x53EA;要资源有变化，`Etag`就会重新生成。

浏览器在下一次加载资源向服务器发送请求时，会将上一次返回的`Etag`值放&#x5230;**`request header`**&#x91CC;&#x7684;**`If-None-Match`**&#x91CC;，服务器接受到`If-None-Match`的值后，会拿来跟该资源文件的`Etag`值**做比较**，如果相同，则表示资源文件没有发生改变，命中协商缓存。

* **`Last-Modified`和`If-Modified-Since`**

`Last-Modified`是该资源文件**最后一次更改时间，**&#x670D;务器会&#x5728;**`response header`**&#x91CC;返回，同时浏览器会将这个值保存起来，下一次发送请求时，放&#x5230;**`request headr`**&#x91CC;&#x7684;**`If-Modified-Since`**&#x91CC;，服务器在接收到后也会**做对比**，如果相同则命中协商缓存。

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCLX0HlKC5DWrT1Zjsd%2F-MCLYfBywYf_XVj_lDiQ%2F43.webp?alt=media\&token=b391e2a0-1020-4a44-b47e-53bcb208b32a)

> 在精确度上，`Etag`要优于`Last-Modified`，`Last-Modified`的时间单位是**秒**，如果某个文件在1秒内改变了多次，那么他们的`Last-Modified`其实并没有体现出来修改，但是`Etag`每次都会改变确保了精度。\
> 在性能上，`Etag`要逊于`Last-Modified`，毕竟`Last-Modified`只需要记录时间，而`Etag`需要服务器通过算法来计算出一个`hash`值。\
> 在优先级上，服务器校验优先考虑`Etag`，如果资源的`Etag`和`if-none-match`相等，即所请求的资源没有变化，此时浏览器即可以使用缓存数据库中的数据，此时`http`的请求状态码为304，请求的资源未变化。 如果请求字段中没有`if-none-match`，就使用`if-modified-since`来判断。如果`if-modified-since`的值和所请求的资源时间一致，即所请求的资源相同，浏览器即可以使用缓存数据库中的数据。

:pen\_fountain: **3、浏览器缓存过程**

1. 浏览器**第一次**加载资源，服务器返回200，浏览器将资源文件从服务器上请求下载下来，并&#x628A;**`response header`**&#x53CA;该请求的**返回时间**(要与`Cache-Control`和`Expires`对比)一并缓存；
2. 下一次加载资源时，先比较当前时间和上一次返回200时的**时间差**，如果没有超过`Cache-Control`设置的`max-age`，则没有过期，命中强缓存，不发请求直接从本地缓存读取该文件（如果浏览器不支持`HTTP1.1`，则用`Expires`判断是否过期）；
3. **如果时间过期**，则向服务器发送`header`带&#x6709;**`If-None-Match`**&#x548C;**`If-Modified-Since`** 的请求；
4. 服务器收到请求后，**优先根据`Etag`**&#x7684;值判断被请求的文件有没有做修改，`Etag`值一致则没有修改，命中协商缓存，返回304；如果不一致则有改动，直接返回新的资源文件带上新的`Etag`值并返回 200；
5. 如果服务器收到的请求没有`Etag`值，则&#x5C06;**`If-Modified-Since`**&#x548C;被请求文件的最后修改时间做比对，一致则命中协商缓存，返回304；不一致则返回新&#x7684;**`last-modified`和文件**并返回 20&#x30;**。**

![浏览器第二次请求](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCLX0HlKC5DWrT1Zjsd%2F-MCL_ob5e5Yq4FsGqLB9%2F40.png?alt=media\&token=57460d92-e86e-4232-82e2-7f838d2bb8ec)

### :pen\_fountain: **4、用户行为对浏览器缓存的控制**

**地址栏访问**

链接跳转是正常用户行为，将会触发浏览器缓存机制【浏览器发起请求，按照正常流程，本地检查是否过期，或者服务器检查新鲜度，最后返回内容】

**F5刷新：**

浏览器会设置`max-age=0`，跳过强缓存判断，会进行协商缓存判断【浏览器直接对本地的缓存文件过期，但是会带上`If-Modifed-Since`，`If-None-Match`（如果上一次`response`带`Last-Modified`, `Etag`）这就意味着服务器会对文件检查新鲜度，返回结果可能是304，也有可能是200.】

**ctrl+F5强制刷新：**

跳过强缓存和协商缓存，直接从服务器拉取资源。【浏览器不仅会对本地文件过期，而且不会带上`If-Modifed-Since`，`If-None-Match`，相当于之前从来没有请求过，返回结果是200.】

![](https://2394460149-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MB7Io-ER4uA8ffGsDSz%2F-MCLX0HlKC5DWrT1Zjsd%2F-MCLaxhl5bUlGK7KnSH6%2F44.webp?alt=media\&token=a4634dee-19c9-47df-b4ec-5e7f61704266)

#### **如何不缓存**

**Cache-Control其他字段：**

* no-cache: 虽然字面意义是“不要缓存”**，**&#x4F46;它实际上的机制是，仍然对资源使用缓存，但每一次在使用缓存之前必须向服务器对缓存资源进行验证。
* no-store: 不使用任何缓存。

禁止缓存：

```
Cache-Control: no-cache, no-store, must-revalidate
```

**Expires：设为当前时间之前**

#### **前端开发设置不缓存：**

在引用js、css文件的`url`后边加上 `?+Math.random()`

```markup
<script type=“text/javascript” src=“/js/test.js?+Math.random()”></script> 
```

设置html页面不让浏览器缓存的方法

```markup
<meta http-equiv="pragma" content="no-cache"> 
<meta http-equiv="Cache-Control" content="no-cache, must-revalidate"> 
<meta http-equiv="expires" content="Wed, 26 Feb 1997 00:00:00 GMT">
```

## :pencil2: 跨站攻击

### :paintbrush: 1、CSRF（Cross-site request forgery，跨站请求伪造）

CSRF是伪造请求，冒充用户在站内的正常操作。例如，一论坛网站的发贴是通过 GET 请求访问，点击发贴之后 JS 把发贴内容拼接成目标 URL 并访问：

```
http://example.com/bbs/create_post.php?title=标题&content=内容
```

那么，我们只需要在论坛中发一帖，包含一链接：

```
http://example.com/bbs/create_post.php?title=我是脑残&content=哈哈
```

只要有用户点击了这个链接，那么他们的帐户就会在不知情的情况下发布了这一帖子。可能这只是个恶作剧，但是既然发贴的请求可以伪造，那么删帖、转帐、改密码、发邮件全都可以伪造。

&#x20;**如何防范 CSRF 攻击**？

#### 1、关键操作只接受 POST 请求

#### 2、验证码

CSRF 攻击的过程，往往是在用户不知情的情况下构造网络请求。所以如果使用验证码，那么每次操作都需要用户进行互动，从而简单有效的防御了CSRF攻击。

但是如果你在一个网站作出任何举动都要输入验证码会严重影响用户体验，所以验证码一般只出现在特殊操作里面，或者在注册时候使用。

#### 3、检测 Referer

常见的互联网页面与页面之间是存在联系的，比如你在 `www.baidu.com` 应该是找不到通往`www.google.com` 的链接的，再比如你在论坛留言，那么不管你留言后重定向到哪里去了，之前的那个网址一定会包含留言的输入框，这个之前的网址就会保留在新页面头文件的 `Referer` 中

通过检查 `Referer` 的值，我们就可以判断这个请求是合法的还是非法的，但是问题出在服务器不是任何时候都能接受到 `Referer` 的值，所以 Referer Check 一般用于监控 CSRF 攻击的发生，而不用来抵御攻击。

#### 4、Token

目前主流的做法是使用 `Token` 抵御 CSRF 攻击。下面通过分析 CSRF 攻击来理解为什么 Token 能够有效

CSRF 攻击要成功的条件在于攻击者能够预测所有的参数从而构造出合法的请求。所以根据不可预测性原则，我们可以对参数进行加密从而防止 `CSRF` 攻击。

另一个更通用的做法是保持原有参数不变，另外添加一个参数 Token，其值是随机的。这样攻击者因为不知道 Token 而无法构造出合法的请求进行攻击。

Token 使用原则

* Token 要足够随机——只有这样才算不可预测
* Token 是一次性的，即每次请求成功后要更新`Token`——这样可以增加攻击难度，增加预测难度
* Token 要注意保密性——敏感操作使用 `post`，防止 `Token` 出现在 URL 中

**注意**：过滤用户输入的内容**不能**阻挡 CSRF，我们需要做的是过滤请求的**来源**。

### :paintbrush: 2、XSS（Cross Site Scripting，跨站脚本攻击）

XSS 全称“跨站脚本”，是注入攻击的一种。其特点是不对服务器端造成任何伤害，而是通过一些正常的站内交互途径，例如发布评论，提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本，作为内容发布到了页面上，其他用户访问这个页面的时候就会运行这些脚本。

运行预期之外的脚本带来的后果有很多中，可能只是简单的恶作剧——一个关不掉的窗口，也可以是盗号或者其他未授权的操作。

XSS 是实现 CSRF 的诸多途径中的一条，但绝对不是唯一的一条。一般习惯上把通过 XSS 来实现的 CSRF 称为 XSRF。

**如何防御 XSS 攻击？**

理论上，所有可输入的地方没有对输入数据进行处理的话，都会存在 XSS 漏洞，漏洞的危害取决于攻击代码的威力，攻击代码也不局限于 `script`。防御 XSS 攻击最简单直接的方法，就是过滤用户的输入。

如果不需要用户输入 HTML，可以直接对用户的输入进行 HTML escape 。下面一小段脚本：

```
<script>window.location.href=”http://www.baidu.com”;</script>
```

经过 escape 之后就成了：

```
&lt;script&gt;window.location.href=&quot;http://www.baidu.com&quot;&lt;/script&gt;
```

它现在会像普通文本一样显示出来，变得无毒无害，不能执行了。

当我们需要用户输入 HTML 的时候，需要对用户输入的内容做更加小心细致的处理。仅仅粗暴地去掉 `script` 标签是没有用的，任何一个合法 HTML 标签都可以添加 `onclick` 一类的事件属性来执行 JavaScript。更好的方法可能是，将用户的输入使用 HTML 解析库进行解析，获取其中的数据。然后根据用户原有的标签属性，重新构建 HTML 元素树。构建的过程中，所有的标签、属性都只从**白名单**中拿取。
