概述
HTTP(HyperText Transfer Protocol):超文本传输协议,HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80)。一旦收到请求,服务器会向客户端返回一个状态,比如”HTTP/1.1 200 OK”,以及返回的内容,如请求的文件、错误消息、或者其它信息。
尽管TCP/IP协议是互联网上最流行的应用,HTTP协议中,并没有规定必须使用它或它支持的层。事实上,HTTP可以在任何互联网协议上,或其他网络上实现。HTTP假定其下层协议提供可靠的传输。因此,任何能够提供这种保证的协议都可以被其使用。因此也就是其在TCP/IP协议族使用TCP作为其传输层。
- 超文本:超文本指的是HTML,css,JavaScript和图片等,HTTP的出现是为了接收和发布HTML页面,经过不断的发展也可以用于接收一些音频,视频,文件等内容。

报文结构
报文结构:起始行 + 头部 + 空行 + 实体
http 请求报文和响应报文是有一定区别
起始行
请求行
- 请求报文 GET /home HTTP/1.1,也就是方法 + 路径 + http版本
状态行
- 响应报文 HTTP/1.1 200 OK ,由http版本、状态码和原因三部分组成

在起始行中,每两个部分之间用空格隔开,最后一个部分后面应该接一个换行,严格遵循ABNF语法规范。
头部
请求头

Cache-Control:
- private: 仅浏览器可以缓存
- public: 浏览器和代理服务器都可以缓存(对于private和public,前端可以认为一样,不用深究
- max-age=xxx 过期时间(重要)
- no-cache 不进行强缓存(重要),使用任何缓存前都要向服务器验证
- no-store 真正的不缓存
响应头

Content-Type:
常见的媒体格式:text/html,text/plain,image/gif
application开头的媒体格式类型:application/xml, appliation/json
multipart/form-data
实体
请求体
响应体
HTTP 请求方法
http/1.1规定了以下请求方法(注意,都是大写):
- GET: 通常用来获取资源
- HEAD: 获取资源的元信息
- POST: 提交数据,即上传数据
- PUT: 修改数据
- DELETE: 删除资源(几乎用不到)
- CONNECT: 建立连接隧道,用于代理服务器
- OPTIONS: 列出可对资源实行的请求方法,用来跨域请求
- TRACE: 追踪请求-响应的传输路径
get请求与post请求的区别
- GET 一般用来从服务器上获取资源,POST 一般用来创建资源;
- GET 是 幂等 的,即读取同一个资源,总是得到相同的数据,而 POST 不是幂等 的。GET 不会改变服务器上的资源,而 POST 会对服务器资源进行改变;
- 从请求参数形式上看,GET 请求的数据会附在 URL之后 ;而 POST 请求会把提交的数据则放置在是 HTTP请求报文的 请求体 中。
- POST 的安全性要比 GET 的安全性高,因为 GET 请求提交的数据将明文出现在 URL 上,而 POST 请求参数则被包装到请求体中,相对更安全。
- GET 请求的长度受限于浏览器或服务器对URL长度的限制,允许发送的数据量比较小,而POST请求则是没有大小限制的。
- GET在浏览器回退时是无害的,而POST会再次提交请求。
- GET产生的URL地址可以被Bookmark,而POST不可以。
- GET请求只能进行url编码,而POST支持多种编码方式。
- GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
- 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
HTTP 版本
HTTP1.0
- HTTP 1.0 浏览器与服务器只保持短暂的连接,每次请求都需要与服务器建立一个TCP连接
HTTP1.1
- 新增Connection字段,用于支持提供TCP持久连接 Connection: keep-alive
- 即TCP连接默认不关闭,可以被多个请求复用
- 虽然允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的,服务器只有处理完一个请求,才会接着处理下一个请求。如果前面的处理特别慢,后面就会有许多请求排队等着。(HTTP队头阻塞)
- 增加更多请求头和响应头来完善功能
- 新增 Host 字段,用于支持虚拟主机
- 缓存策略:If-Match,If-None-Match
- 添加了新的请求方法 put, delete, options
队头阻塞
”队头阻塞“与短连接和长连接无关,而是由 HTTP 基本的“请求 - 应答”模型所导致的。
因为 HTTP 规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。
队列里的请求没有轻重缓急的优先级,只有入队的先后顺序,排在最前面的请求被最优先处理。
如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本。
因为“请求 - 应答”模型不能变,所以“队头阻塞”问题在 HTTP/1.1 里无法解决,只能缓解
- 这在 HTTP 里就是“并发连接”(concurrent connections),也就是同时对一个域名发起多个长连接,用数量来解决质量的问题。但这种方式也存在缺陷。如果每个客户端都想自己快,建立很多个连接,用户数×并发数就会是个天文数字。服务器的资源根本就扛不住,或者被服务器认为是恶意攻击,反而会造成“拒绝服务”。所以,HTTP 协议建议客户端使用并发,但不能“滥用”并发。RFC2616 里明确限制每个客户端最多并发 2 个连接。不过实践证明这个数字实在是太小了,众多浏览器都“无视”标准,把这个上限提高到了 6~8。后来修订的 RFC7230 也就“顺水推舟”,取消了这个“2”的限制。
- “域名分片”(domain sharding)技术,还是用数量来解决质量的思路。HTTP 协议和浏览器不是限制并发连接数量吗?好,那我就多开几个域名,比如 shard1.chrono.com、shard2.chrono.com,而这些域名都指向同一台服务器 www.chrono.com,这样实际长连接的数量就又上去了
HTTP2.0
- 支持服务端推送
- 允许服务端推送资源给客户端,在响应一个页面请求中,可以把需要的其他资源一起发给客户端,免得需要再次发送请求,适合加载静态资源,比如请求html时,把css也传过去
- 支持TCP连接IO多路复用
- 在一个连接里,客户端和服务器都可以同时发送多个请求或回应,避免队头阻塞
- 二进制分帧(而非文本格式)
- 支持多个连接穿插执行,避免队头阻塞
- 首部压缩:HPACK算法
- 首先是在服务器和客户端之间建立哈希表,将用到的字段存放在这张表中,那么在传输的时候对于之前出现过的值,只需要把索引(比如0,1,2,…)传给对方即可,对方拿到索引查表就行了。这种传索引的方式,可以说让请求头字段得到极大程度的精简和复用。废除了起始行的概念。
- HTTP2.0当中废除了起始行的概念,将起始行中的请求方法、URI、状态码转换成了头字段,不过这些字段都有一个”:”前缀,用来和其它请求头区分开。
- 其次是对于整数和字符串进行哈夫曼编码,哈夫曼编码的原理就是先将所有出现的字符建立一张索引表,然后让出现次数多的字符对应的索引尽可能短,传输的时候也是传输这样的索引序列,可以达到非常高的压缩率。
- 首先是在服务器和客户端之间建立哈希表,将用到的字段存放在这张表中,那么在传输的时候对于之前出现过的值,只需要把索引(比如0,1,2,…)传给对方即可,对方拿到索引查表就行了。这种传索引的方式,可以说让请求头字段得到极大程度的精简和复用。废除了起始行的概念。
HTTP 状态码
[
](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status)
官方文档:HTTP响应状态码
RFC 规定 HTTP 的状态码为三位数,被分为五类:
- 1xx: 表示目前是协议处理的中间状态,还需要后续操作。
- 100 Continue:迄今为止的所有内容都是可行的,客户端应该继续请求,如果已经完成,则忽略它。
- 101 Switching Protocols:在HTTP升级为WebSocket的时候,如果服务器同意变更(切换协议),就会发送状态码 101。
- 102 Processing:服务器已收到并正在处理该请求,但没有响应可用
- 2xx: 表示成功状态。
- 200 OK:请求成功。
- 201 Created:该请求已成功,并因此创建了一个新的资源。这通常是在POST请求,或是某些PUT请求之后返回的响应。
- 202 Accepted:请求已经接收到,但还未响应,没有结果。意味着不会有一个异步的响应去表明当前请求的结果,预期另外的进程和服务去处理请求,或者批处理。
- 204 No Content:含义与 200 相同,但响应头后没有 body 数据。
- 206 Partial Content:表示部分内容,它的使用场景为 HTTP 分块下载和断点续传,当然也会带上相应的响应头字段Content-Range。
- 3xx: 重定向状态,资源位置发生变动,需要重新请求。
- 301 Moved Permanently:永久重定向,求的URL已被移除时使用比如网站从 HTTP 升级到了 HTTPS 了,以前的站点再也不用了,应当返回301,这个时候浏览器默认会做缓存优化,在第二次访问的时候自动访问重定向的那个地址。
- 302 Found:临时重定向,请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。
- 304 Not Modified: 当协商缓存命中时会返回这个状态码。如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码。304 响应禁止包含消息体,因此始终以消息头后的第一个空行结尾。
- 4xx: 请求报文有误。
- 400 Bad Request:
- 1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。
- 2、请求参数有误。
- 401 Unauthorized:未授权,当前请求需要用户验证。该响应必须包含一个适用于被请求资源的 WWW-Authenticate 信息头用以询问用户信息。
- 403 Forbidden:服务器已经理解请求,但是拒绝执行它。这并不是请求报文出错,而是服务器禁止访问,原因有很多,比如法律禁止、信息敏感。
- 404 Not Found:资源未找到,表示没在服务器上找到相应的资源。
- 405 Method Not Allowed:请求方法不被服务器端允许。该响应必须返回一个Allow 头信息用以表示出当前资源能够接受的请求方法的列表。
- 406 Not Acceptable: 资源无法满足客户端的条件。请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体。
- 408 Request Timeout:服务器等待了太长时间。请求超时。客户端没有在服务器预备等待的时间内完成一个请求的发送。客户端可以随时再次提交这一请求而无需进行任何更改。
- 409 Conflict:多个请求发生了冲突。
- 413 Request Entity Too Large:请求体的数据过大。
- 414 Request-URI Too Long:请求行里的 URI 太大。
- 429 Too Many Request:客户端发送的请求过多。
- 431 Request Header Fields Too Large:请求头的字段内容太大。
- 400 Bad Request:
- 5xx: 服务器端发生错误。
- 500 Internal Server Error:服务器遇到了不知道如何处理的情况。
- 501 Not Implemented:客户端请求的功能还不支持。此请求方法不被服务器支持且无法被处理。只有GET和HEAD是要求服务器支持的,它们必定不会返回此错误代码。
- 502 Bad Gateway:此错误响应表明服务器作为网关需要得到一个处理这个请求的响应,但是得到一个错误的响应。服务器自身是正常的,但访问的时候出错了。
- 503 Service Unavailable:服务器没有准备好处理请求。 常见原因是服务器因维护或重载而停机。 请注意,与此响应一起,应发送解释问题的用户友好页面。
- 504 Gateway Timeout:当服务器作为网关,不能及时得到响应时返回此错误代码。
- 505 HTTP Version Not Supported:服务器不支持请求中所使用的HTTP协议版本。
URI与URL
- URI:Uniform Resource Identifier,统一资源标识符
- URL:Uniform Resource Locator,统一资源定位符,不仅标识了资源,还指定了操作或者获取方式,同时指出了主要访问机制和网络位置;
- URN:Uniform Resource Name,统一资源名称,用特定命名空间的名字标识资源,使用URN可以在不知道其网络位置及访问方式的情况下讨论资源。
URI结构
URI 真正最完整的结构是这样的。
- scheme 表示协议名,比如http, https, file等等。后面必须和://连在一起。
- user:passwd@ 表示登录主机时的用户信息,不过很不安全,不推荐使用,也不常用。
- host:port表示主机名和端口。
- path表示请求路径,标记资源所在位置。
- query表示查询参数,为key=val这种形式,多个键值对之间用&隔开。
- fragment表示 URI 所定位的资源内的一个锚点,浏览器可以根据这个锚点跳转到对应的位置。
URI 只能使用ASCII, ASCII 之外的字符是不支持显示的,而且还有一部分符号是界定符,如果不加以处理就会导致解析出错。
因此,URI 引入了编码机制,将所有非 ASCII 码字符和界定符转为十六进制字节值,然后在前面加个%。
如,空格被转义成了%20。
URI允许统一识别资源。URI另外被分组为定位符,名称或两者,这意味着它可以描述URL,URN或两者。
URL和URN都是URI的子集
换而言之,URL和URN都是URI,但是URI不一定是URL或者URN。
举例如下:
构造一个URIhttp:*//bitpoetry.io/posts/hello.html#intro其中http:// // 是定义如何访问资源的方式bitpoetry.io/posts/hello.html // 是资源存放的位置#intro // 是资源URL是URI的一个子集,告诉我们访问资源位置的方式。在例子中,URL应该如下所示:http://bitpoetry.io/posts/hello.htmlURN是URI的子集,包括名字(给定的命名空间内),但是不包括访问方式。在例子中,URN如下所示:bitpoetry.io/posts/hello.html#intro
HTTP传输过程

HTTP 传输定长和不定长的数据
定长数据
- 对于定长包体而言,发送端在传输的时候一般会带上 Content-Length, 来指明包体的长度
- 用一个nodejs服务器来模拟一下: ```javascript const http = require(‘http’);
const server = http.createServer();
server.on(‘request’, (req, res) => { if(req.url === ‘/‘) { res.setHeader(‘Content-Type’, ‘text/plain’); res.setHeader(‘Content-Length’, 10); res.write(“helloworld”); } })
server.listen(8081, () => { console.log(“成功启动”); })
启动后访问: **localhost:8081**。<br />浏览器中显示如下:```javascripthelloworld
这是长度正确的情况,那不正确的情况是如何处理的呢
我们试着把这个长度设置的小一些:
res.setHeader('Content-Length', 8);
重启服务,再次访问,现在浏览器中内容如下:
hellowor
那后面的ld哪里去了呢?实际上在 http 的响应体中直接被截去了。
然后我们试着将这个长度设置得大一些:
res.setHeader('Content-Length', 12);
此时浏览器显示如下:
直接无法显示了。可以看到Content-Length对于 http 传输过程起到了十分关键的作用,如果设置不当可以直接导致传输失败。
不定长包体
上述是针对于定长包体,那么对于不定长包体而言是如何传输的呢?
这里就必须介绍另外一个 http 头部字段了:
Transfer-Encoding: chunked
表示分块传输数据,设置这个字段后会自动产生两个效果:
- Content-Length 字段会被忽略
- 基于长连接持续推送动态内容
我们依然以一个实际的例子来模拟分块传输,nodejs 程序如下:
const http = require('http');
const server = http.createServer();
server.on('request', (req, res) => {
if(req.url === '/') {
res.setHeader('Content-Type', 'text/html; charset=utf8');
res.setHeader('Content-Length', 10);
res.setHeader('Transfer-Encoding', 'chunked');
res.write("<p>来啦</p>");
setTimeout(() => {
res.write("第一次传输<br/>");
}, 1000);
setTimeout(() => {
res.write("第二次传输");
res.end()
}, 2000);
}
})
server.listen(8009, () => {
console.log("成功启动");
})
访问效果入下:
