HTTP协议全称为HyperText Transfer Protocol
(超文本传输协议),它是WWW和IETF发布的一系列RFC的规范。比如我们现在常用的HTTP/1.1
是RFC 2616
定义的。
RFC
是一种当某机构或某人对开发出的一套标准或设想,要征询外界意见时,发布在Internet上的文件,该文件相当于一个说明文件,用于其他人阅读并提出意见。
HTTP协议是在TCP协议之上的,当然也可以构建于TLS/SSL之上,此时它就成为了更具安全性的HTTPS
。
HTTP可以基于UDP方式传输吗?
我们知道UDP是不可靠传输,只负责发出数据包,并不管目标主机是否接收到。而HTTP又是无状态的协议(关于
无状态协议
的概念可以看这篇知乎的问答:HTTP是一个无状态的协议。这句话里的无状态是什么意思?),没头脑配上鱼的记忆,这就是一场灾难......。HTTP在任何传输层协议都可以,所以按理来讲也可以用UDP,但是可能要做好删库跑路的准备。
HTTP1和HTTP2的消息结构对于用户来说是透明的,不需要对旧的配置文件进行改动就可以过渡到HTTP2。两者之间的区别如下:
对于HTTP2,较大的区别在于它将 HTTP/1.x 消息分成帧并嵌入到流 (stream) 中。数据帧和报头帧分离,这将允许报头压缩。将多个流组合,这是一个被称为 **多路复用 (multiplexing)***的过程,它允许更有效的底层 TCP 连接。
抓一下HTTP的包,来分析一下 HTTP的消息结构:
HTTP的消息结构包括请求消息结构和响应消息结构,下面对此分别进行解析。
HTTP请求消息包括请求行、请求头部、请求数据以及填充符\r
、\n
。下面是请求报文的一般格式:
对照此结构,我们可以知道在上图中的报文里的请求方式为GET
,以及它的请求路径,还有它请求头部的数据。
各后端开发语言的Web开发框架都会提供对请求报文的解析支持,可以通过类似
request.getHead
方法来获取请求的头部,并对其进行相关操作。
请求头能给我们提供很多信息,这里推荐这篇博客,写的挺全的:HTTP请求头--那些你需要记住的基础知识
From Data和Request Payload
对于GET
方法的请求参数,就是直接拼接在请求URL后面,但对于POST
的请求数据,游览器有两种处理方法:Form Data
和Request Payload
。
比如当我们使用HTML表单的方式直接提交数据,那么POST
数据是这样的:
如果使用ajax
来提交,那么格式会发生变化:
我们可以看到ajax默认的提交的coontent-type
为text/plain;charset=UTF-8
,而此时表单的数据跟GET
参数一样被拼接在一起。
而当我们指定Content-type
为application/x-www-form-urlencoded
时,数据格式又发生了变化,传到后端时就变成了json
数据:
根据上面的示例,我们可以看出POST
提交对于游览器有两种解析方法,一种是FormData
,一种是Request Payload
,而它们都是属于请求体的内容,表现形式取决于content-type
的指定。
这也是为什么有的时候对于POST请求,后端获取不到值的原因:由于
content-type
的指定导致数据传输形式的不同。
在以前的HTTP中,一般都是使用GET
和POST
请求资源,但是在更新后的HTTP1.1
中新增了五种请求方法:OPTIONS
、PUT
、PATCH
、DELETE
、TRACE
、 CONNECT
,这些请求方法的增加是RESTFUL
风格接口实现的基础。具体的常用方法说明如下:
请求方法 | 说明 |
---|---|
GET | 请求指定的页面信息,并返回实体主体。 |
HEAD | 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头 |
POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。 |
PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 |
DELETE | 请求服务器删除指定的页面。 |
CONNECT | 预留给能够将连接改为管道方式的代理服务器。 |
OPTIONS | 允许客户端查看服务器的性能。 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
PATCH | 是对 PUT 方法的补充,用来对已知资源进行局部更新 。 |
响应消息的结构和请求消息差不多,主要关注的部分是响应头和响应数据,响应数据就是后端返回给前端的数据,下面我们来看看响应头包含的信息和作用。
响应头会告诉游览器服务端的相关信息和数据信息等,比如下面所示:
对于CORS
,它是一种跨资源共享机制,当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求,游览器本身默认是不支持跨域请求的,所以我们需要在响应头进行配置,告诉游览器请求方是可以获取该资源的,你就不要拦截了。
对于Transfer-Encoding
要和Content-Encoding
进行比较,Conotent-Encoding
是内容编码的策略,比如是否进行压缩,目的是为了优化传输。而Transfer-Encoding
是为了指定报文的格式,比如为了减少每次TCP三次握手的时间,我们采用持久连接获取数据,即打开一个连接传输数据后并不关闭连接,而是可以进行复用。如果我们不指定数据的长度的话,那么游览器会因为连接没有中断而认为数据没有传输完,那么请求就会一直处于padding
状态,此时我们可以通过设置Content-Length
来告诉游览器这个数据的长度是多少。虽然这种方法可以解决问题,可以如果频繁地读取大量数据,那么不断获取数据长度就会影响服务器的性能,而改进的方法就是指定Transfer-Encoding
。
在头部加入 Transfer-Encoding: chunked
之后。就表示进行分块编码,每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF,和分块数据结尾的 CRLF。最后一个分块长度值为0,对应的分块数据没有内容,表示数据传输结束。
对比Content-Encoding
和Transfer-Encoding
,我们可以看出Transfer-Encoding
是作用于传输的格式,而Content-Encoding
则是指定游览器获取数据后对数据进行的解析格式。
Vary
则是作用于代理服务器/缓存/CDN,指定对于请求的判断表哦准,比如Vary
中有User-Agent
,即对于相同的请求,如果用户使用不同的网页打开,那么代理服务器/缓存/CDN会认为是不同的请求,而如果Vary
中没有User-Agent
,那么它们会认为是相同的页面,直接给用户返回缓存的页面。
服务端和游览器的Cookie
在以前,因为HTTP的无状态性,我们经常使用Cookie来存储用户凭证,一般来说就是服务端将相关信息存入一个new Cookie()
中,然后用response.setCookie(cookie)
就可以了,而客户端则可以收到这个Cookie。这样就让人很迷,我客户端啥都没干,怎么就获取到了Cookie?
实际上,当服务单设置了Cookie后,将会给返回游览器的响应消息的消息头加上set-cookie
信息,游览器会根据响应消息头来自动设置Cookie,不需要前端显式进行操作,而对于后面的请求,每次也会自动带上Cookie。
Cookie的工作机制是由RFC 6265
规定的,对于Cookie还可以指定过期时间、特定域名(用于防止跨域)、路径。
服务端和游览器的Session
处理Cookie外,Web应用还可以使用Session来记录客户端的状态,它是服务端记录客户端状态的机制。服务端会给客户端分发一个SessionID作为唯一标识,对于SessionID可以存储在Cookie中,也可以作为请求URL中的参数,服务端每次处理请求前都会看下这个SessionID是否存在于自己存储的数据中。
Session和Cookie的区别可以用这么个情况来表示:
老王开了一家理发店,因为老王的记性很差,所以为了给老客户优惠,都会给他们一张卡片 ,卡片上面有该客户的个人信息,客户来理发时老王只要看下卡片就知道怎么办了。卡片记录这客户的信息,而卡片在客户手上。这就是Cookie。
后来老王的客户越来越多 ,为了更好地提高服务 ,老王就在卡片上面加了很多内容,比如客户的消费次数、一般什么时候来、喜欢什么发型、脾气怎么样等等,这样卡片就越来越大,让客户每次带着都不方便。所以老王就把信息做成一本书,放在自己这里,每一项信息都有一个对应的唯一id,老王只要把id写在卡片上给对应的用户,下次用户只要带一张小小的卡片过来,给老王一看卡片上的id,老王就可以查出该用户的信息了。卡片只是记录了用户id,而详细的数据是在老王这里。这就是Session。
对于HTTP协议的规则,可以查看MDN HTTP协议。