概述

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页面,经过不断的发展也可以用于接收一些音频,视频,文件等内容。

image-20210910145213312.png

报文结构

报文结构:起始行 + 头部 + 空行 + 实体

http 请求报文和响应报文是有一定区别
image-20210915150323696.png

起始行

请求行

  • 请求报文 GET /home HTTP/1.1,也就是方法 + 路径 + http版本

HTTP - 图3

状态行

  • 响应报文 HTTP/1.1 200 OK ,由http版本、状态码和原因三部分组成

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

头部

请求头

HTTP - 图5
Cache-Control:

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

响应头

image-20210915151401084.png
Content-Type:
常见的媒体格式:text/html,text/plain,image/gif
application开头的媒体格式类型:application/xml, appliation/json
multipart/form-data
image-20210915151540851.png

实体

请求体

响应体

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、状态码转换成了头字段,不过这些字段都有一个”:”前缀,用来和其它请求头区分开。
      • 其次是对于整数和字符串进行哈夫曼编码,哈夫曼编码的原理就是先将所有出现的字符建立一张索引表,然后让出现次数多的字符对应的索引尽可能短,传输的时候也是传输这样的索引序列,可以达到非常高的压缩率。

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:请求头的字段内容太大。
  • 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 真正最完整的结构是这样的。
image-20210915233628185.png

  • 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。
640 (1).jpg

举例如下:

  1. 构造一个URI
  2. http:*//bitpoetry.io/posts/hello.html#intro
  3. 其中
  4. http:// // 是定义如何访问资源的方式
  5. bitpoetry.io/posts/hello.html // 是资源存放的位置
  6. #intro // 是资源
  7. URL是URI的一个子集,告诉我们访问资源位置的方式。在例子中,URL应该如下所示:
  8. http://bitpoetry.io/posts/hello.html
  9. URN是URI的子集,包括名字(给定的命名空间内),但是不包括访问方式。在例子中,URN如下所示:
  10. bitpoetry.io/posts/hello.html#intro

HTTP传输过程

image-20210921105357172.png

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(“成功启动”); })

  1. 启动后访问: **localhost:8081**。<br />浏览器中显示如下:
  2. ```javascript
  3. helloworld

这是长度正确的情况,那不正确的情况是如何处理的呢
我们试着把这个长度设置的小一些:

res.setHeader('Content-Length', 8);

重启服务,再次访问,现在浏览器中内容如下:

hellowor

那后面的ld哪里去了呢?实际上在 http 的响应体中直接被截去了。
然后我们试着将这个长度设置得大一些:

res.setHeader('Content-Length', 12);

此时浏览器显示如下:
image-20210916161841752.png
直接无法显示了。可以看到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("成功启动");
})

访问效果入下:
image-20210917105352387.png