高能回顾之HTTP协议

1,921 阅读15分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

超文本传输协议是目前使用最广泛的应用层协议。在网站、App、开放接口中都可以看到它。相信平时工作中已经多多少少接触过这个协议,该文我会挑选其中一部分重点介绍,帮助你学习 HTTP 协议。

HTTP 的本质

全称为“超文本传输协议”

协议: HTTP 是一个作为应用层协议。它使用计算机能够理解的语言确立了一种计算机之间交流通信的规范,以及相关的各种控制和错误处理方式。

传输: HTTP 是一个在计算机世界里专门用来在两点之间传输数据的约定和规范。

超文本: 它是文字、图片、音频和视频等的混合体,最关键的是含有“超链接”,能够从一个“超文本”跳跃到另一个“超文本”,形成复杂的非线性、网状的结构关系。

在互联网世界里,HTTP协议主要规定了客户端和服务器之间的通信格式,它通常跑在 TCP/IP 协议栈之上,依靠 IP 协议实现寻址和路由、TCP 协议实现可靠数据传输、DNS 协议实现域名查找、SSL/TLS 协议实现安全通信。此外,还有一些协议依赖于 HTTP,例如 WebSocket、DNS 等。这些协议相互交织,构成了一个协议网,而 HTTP 则处于中心地位。

HTTP是全双工还是半双工?

我认为HTTP协议不能用全双工或者说半双工来定义,因为它的本质是客户端(用户)和服务端(网站)之间请求和应答的标准。其次上层协议不用这个名词,物理层链路层那个级别才用这几个名词。

所以这个问题是有歧义的~

URI和URL的区别

URI,全称为统一资源标识符,作用很简单,就是区分互联网上不同的资源,但是它不是我们常说的网址,网址指的是URL(统一资源定位符),URI包含了URN和URL两个部分。

scheme:表示协议名,比如http, https, file等等。后面必须和://连在一起。

user:passwd@:表示登录主机时的用户信息,不过很不安全,不推荐使用,也不常用。

host:port:表示主机名和端口。

path:表示请求路径,标记资源所在位置。

query:表示查询参数,为key=val这种形式,多个键值对之间用&隔开。

fragment:表示 URI 所定位的资源内的一个锚点,浏览器可以根据这个锚点跳转到对应的位置。

HTTP 请求通过 URL 传递参数时,不能超过 2048 字节(2KB),因为不同浏览器对于 URL 的最大长度限制略有不同,并且对超出最大长度的处理逻辑也有差异,2048 字节是取所有浏览器的最小值。

从URL输入到页面展现到底发生什么?

HTTP请求过程

  1. 用户通过浏览器发送请求操作,比如输入网址并回车,或是点击链接,接着浏览器获取了这个事件。
  2. DNS查询,在发送消息前,我们需要知道目标对象的IP地址,将域名解析成IP。
  3. 浏览器向服务端发出 TCP 连接请求。
  4. 服务程序接受浏览器的连接请求,并经过 TCP 三次握手建立连接。
  5. 浏览器将请求数据打包成 HTTP 协议格式的数据包,并发送HTTP请求,数据包经过网络传输,最终到达服务端程序。
  6. 服务端程序拿到这个数据包后,同样以 HTTP 协议格式解包,获取到客户端的意图。
  7. 得知客户端意图后进行处理,比如提供静态文件或者调用服务端程序获得动态结果
  8. 服务器将响应结果(可能是 HTML 或者图片等)按照 HTTP 协议格式打包,发回HTTP响应,经过网络传输最终达到浏览器。
  9. 浏览器拿到数据包后,以 HTTP 协议的格式解包,然后解析数据,假设这里的数据是 HTML。
  10. 浏览器将 HTML 文件展示在页面上

HTTP报文结构

HTTP 协议主要由三大部分组成:

  • 起始行(start line):描述请求或响应的基本信息;

    • 请求报文:方法 + 路径 + http版本
    • 响应报文:http版本 + 状态码 + 原因
  • 请求头(header):使用 key-value 形式更详细地说明报文;

  • 请求体(body):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据。

HTTP 请求通过 body 传递内容时,必须控制长度,超出最大长度后,后端解析会出错。

说明:nginx 默认限制是 1MB,tomcat 默认限制为 2MB,当确实有业务需要传较大内容时,可以调大服务器端的限制

HTTP请求方法

http/1.1规定了以下请求方法(注意,都是大写):

  • GET: 通常用来获取资源
  • HEAD: 获取资源的元信息,区别在于HEAD不含有呈现数据,而仅仅是HTTP头信息。
  • POST: 提交数据,即上传数据
  • PUT: 修改数据
  • DELETE: 删除资源(几乎用不到)
  • CONNECT: 建立连接隧道,用于代理服务器
  • OPTIONS: 列出可对资源实行的请求方法,用来跨域请求
  • TRACE: 追踪请求-响应的传输路径

Get和Post的区别

  • 缓存的角度,Get会被缓存,Post不会
  • 传输的角度,GET请求传输大小有限制,大小在2KB,POST请求,理论上大小是不会限制的,但是实际上各个服务器会规定POST提交数据大小。
  • 编码的角度,Get需要进行URL编码,因为它只能传递ASCII字符,POST没有限制
    • 对整个URL进行编码,不对A-Z a-z 0-9 ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) #进行编码。编码后,它输出符号的utf-8形式,并且在每个字节前加上% ,所以Url编码通常也被称为百分号编码,%百分号加上两位的字符(十六进制)的utf-8编码
  • 参数的角度,Get一般放到URL中,Post放在请求体中
  • 幂等性的角度,Get天生幂等,Post不是

HTTP状态码

RFC 规定 HTTP 的状态码为三位数,被分为五类:

  • 1xx-表示协议的中间状态,还需要后续操作
  • 2xx-表示成功状态
  • 3xx-重定向状态,资源位置发生变动,需要重新请求
  • 4xx-请求报文有误
  • 5xx-服务端发生错误

详细请看 => HTTP响应状态码

HTTP特点和缺点

  1. HTTP 最大的优点是简单、灵活和易于扩展;
  2. HTTP 是无状态的,可以轻松实现集群化,扩展性能,但有时也需要用 Cookie 技术来实现“有状态”;
    • 一些应用仅仅只是为了获取一些数据,不需要保存连接上下文信息,无状态反而减少了网络开销,成为了 http 的优点。
    • 在需要长连接的场景中,需要保存大量的上下文信息,以免传输大量重复的信息,那么这时候无状态就是 http 的缺点了。
      • 在 HTTP/1.0 中,每次 HTTP 请求都会创建一个新的 TCP 连接,请求完成后之后这个 TCP 连接就会被关闭,这种通信模式的效率不高。
      • 在 HTTP/1.1 中,引入了 HTTP 长连接的概念,使用长连接的 HTTP 协议,会在响应头加入 Connection:keep-alive。这样当浏览器完成一次请求后,浏览器和服务器之间的 TCP 连接不会关闭,再次访问这个服务器上的网页时,浏览器会继续使用这一条已经建立的连接,也就是说两个请求可能共用一个 TCP 连接。
  1. HTTP 是明文传输,数据完全肉眼可见,能够方便地研究分析,但也容易被窃听;
  2. HTTP 是不安全的,无法验证通信双方的身份,也不能判断报文是否被窜改;

HTTP 1.0 和 HTTP 1.1 有什么区别

HTTP长连接和短连接

HTTP长连接

浏览器向服务器进行一次HTTP会话访问后,并不会直接关闭这个连接,而是会默认保持一段时间,那么下一次浏览器继续访问的时候就会再次利用到这个连接。

HTTP/1.1版本中,默认的连接都是长连接,我们可以通过在消息头中设置如下字段进行指定:

Connection: keep-alive
Keep-Alive: timeout=5, max=1000
  • timeout:指定了一个空闲连接需要保持打开状态的最小时长(以秒为单位)。需要注意的是,如果没有在传输层设置 keep-alive TCP message 的话,大于 TCP 层面的超时设置会被忽略。
  • max:在连接关闭之前,在此连接可以发送的请求的最大值。在非管道连接中,除了 0 以外,这个值是被忽略的,因为需要在紧跟着的响应中发送新一次的请求。HTTP 管道连接则可以用它来限制管道的使用。

其实这里的长连接实现依然是基于TCP的。

HTTP短连接

  • 浏览器向服务器每进行一次HTTP操作都要建立一个新的连接。
  • HTTP/1.0版本中默认是短链接

Content/Accept系列字段

Accept

代表发送端(客户端)希望接受的数据类型

比如:Accept:text/xml;

代表客户端希望接受的数据类型是xml类型

Content-Type

代表发送端(客户端|服务器)发送的实体数据的数据类型

body 里带参数时必须设置 Content-Type

比如:Content-Type:text/html;代表发送端发送的数据格式是html。

主要有四个部分:数据格式、压缩方式、支持语言、字符集

数据格式: Content-type和Accept

压缩方式: Content-Encoding和Accept-Encoding

支持语言: Content-Language和Accept-Language

字符集: 接收端对应为Accept-Charset,而在发送端并没有对应的Content-Charset, 而是直接放在了Content-Type中,以charset属性指定。

// 发送端
Content-Type: text/html; charset=utf-8
// 接收端
Accept-Charset: charset=utf-8

以一张图来总结一下吧:

Cookie 和 Session

Session 和 Cookie 的主要目的就是为了弥补 HTTP 的无状态特性

Cookie 技术

Cookie 是 HTTP 报文的一个请求头,Web 应用可以将用户的标识信息或者其他一些信息(用户名等)存储在 Cookie 中,和服务端建立会话后,每次 HTTP 请求报文中都会包含 Cookie,通常,它用于判断两个请求是否来自于同一个浏览器,例如用户保持登录状态。

  • Cookie 由服务器生成,发送给浏览器。
  • Cookie 本质上就是一份存储在用户本地的文件,里面包含了每次请求中都需要传递的信息

HttpOnly

当Cookie内容被设置成了 HttpOnly, 即客户端脚本无法读取,只能从服务端读取和操作。

  • 设置 HttpOnly 的作用就在于通过阻止 JS 读取 Cookie 来防止XSS攻击。

Session 技术

由于 Cookie 以明文的方式存储在本地,而 Cookie 中往往带有用户信息,这样就造成了非常大的安全隐患。而 Session 的出现解决了这个问题,Session 可以理解为服务器端开辟的存储空间, 用来存储客户端在同一个会话期间的一些操作记录。

Session 如何判断是否是同一会话

服务器第一次接收到请求时,开辟了一块 Session 空间(创建了Session对象),同时生成一个 sessionId ,并通过响应头的Set-Cookie:JSESSIONID=XXXXXXX命令,向客户端发送要求设置 Cookie 的响应; 客户端收到响应后,在本机客户端设置了一个JSESSIONID=XXXXXXX的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束;

接下来客户端每次向同一个网站发送请求时,请求头都会带上该 Cookie信息(包含 sessionId ), 然后,服务器通过读取请求头中的 Cookie 信息,获取名称为 JSESSIONID 的值,得到此次请求的 sessionId。

缺点

不适用于负载均衡的场景,机器A的session会话在机器B上是看不到的

HTTP代理

HTTP是基于请求-响应模型的协议,一般由客户端发请求,服务器来进行响应,当然也有特殊情况,那就是代理服务器, 对于客户端而言,表现为服务器进行响应;而对于源服务器,表现为客户端发起请求,具有双重身份

功能: 负载均衡、保障安全、缓存代理

正向代理和反向代理

正向代理帮助客户端访问客户端自己访问不到的服务器,然后将结果返回给客户端。

反向代理拿到客户端的请求,将请求转发给其他的服务器,主要的场景是维持服务器集群的负载均衡,换句话说,反向代理帮其它的服务器拿到请求,然后选择一个合适的服务器,将请求转交给它。

因此,两者的区别就很明显了:

  • 正向代理即是客户端代理, 代理客户端, 服务端不知道实际发起请求的客户端;
  • 反向代理即是服务端代理, 代理服务端, 客户端不知道实际提供服务的服务端

什么是跨域

「同源策略」 是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。

浏览器遵循同源策略(scheme协议、host主机、port端口都相同则为同源),非同源站点有这样一些限制:

  • Cookie、LocalStorage、IndexedDB 等存储性内容无法读取
  • DOM 节点和 Js对象无法获得
  • AJAX 请求发送后,结果被浏览器拦截(注意是 请求发送出去了,也拿到结果了,只是被浏览器截胡了
  • 限制XMLHttpRequest请求

跨域请求的响应一般会被浏览器所拦截,注意,是被浏览器拦截,响应其实是成功到达客户端了

CORS解决跨域

CORS(Cross-Origin Resource Sharing)其实是 W3C 的一个标准,全称是跨域资源共享

CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现

浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。

服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。

虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求复杂请求

简单请求

浏览器根据请求方法和请求头的特定字段,将请求做了一下分类,具体来说规则是这样,凡是同时满足下面条件的属于简单请求:

条件1:使用下列方法之一:

  • GET
  • HEAD
  • POST

条件2:Content-Type 的值仅限于下列三者之一:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

响应请求中的重要字段:

Access-Control-Allow-Origin字段后面通常携带的是域名,是服务器告诉浏览器是否拦截这个响应,这是必需的字段。

Access-Control-Allow-Credentials 这个字段是一个布尔值,表示是否允许发送 Cookie,对于跨域请求,浏览器对这个字段默认值设为 false,而如果需要拿到浏览器的 Cookie,需要添加这个响应头并设为true, 并且在前端也需要设置withCredentials属性

非简单请求

非简单请求相对于简单请求,不同主要体现在两个方面:预检请求响应字段。

预检请求

OPTIONS / HTTP/1.1
Origin: 当前地址
Host: xxx.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header

预检请求的方法是OPTIONS,同时会加上Origin源地址和Host目标地址,这很简单,同时也会加上两个关键的字段:

  • Access-Control-Request-Method, 列出 CORS 请求用到哪个HTTP方法
  • Access-Control-Request-Headers,指定 CORS 请求将要加上什么请求头

预检请求的响应

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
  • Access-Control-Allow-Origin: 表示可以允许请求的源,可以填具体的源名,也可以填*表示允许任意源请求。
  • Access-Control-Allow-Methods: 表示允许的请求方法列表。
  • Access-Control-Allow-Headers: 表示允许发送的请求头字段
  • Access-Control-Max-Age: 预检请求的有效期,在此期间,不用发出另外一条预检请求。

HTTP和HTTPS有什么区别

关于HTTPS详解看我的这篇文章:结合对称加密、非对称加密浅析HTTPS协议

  • 端口号 :HTTP 默认是 80,HTTPS 默认是 443。
  • URL 前缀 :HTTP 的 URL 前缀是 http://,HTTPS 的 URL 前缀是 https://。
  • 安全性和资源消耗: HTTPS是加密传输,会更安全,同时加密会耗费更多的服务器资源

本文正在参加「金石计划 . 瓜分6万现金大奖」