HTTP(全称为 "超文本传输协议")是一种应用非常广泛的应用层协议。
HTTP 往往是基于传输层的 TCP 协议实现的。(HTTP1.0, HTTP1.1, HTTP2.0 均为TCP, HTTP3 基于 UDP 实现)目前我们主要使用的还是 HTTP1.1 和 HTTP2.0。
HTTP 协议工作原理
基于 请求-响应 模式
HTTP协议规定,请求从客户端发出,最后服务器端响应该请求并返回。
当我们在浏览器中输入一个"网址",此时浏览器就会给对应的服务器发送一个 HTTP 请求。对方服务器收到这个请求之后,经过计算处理,就会返回一个 HTTP 响应。浏览器对收到 HTTP 响应进行解析,并在浏览器窗口中显示。
无状态
HTTP 是一种不保存状态,即无状态(stateless)协议。 HTTP 协议自身不对请求和响应之间的通信状态进行保存,也就是说协议对于发送过的请求或响应都不做持久化处理。因为它的每个请求都是完全独立的,每个请求包含了处理这个请求所需的完整的数据,发送请求不涉及到状态变更即使在 HTTP/1.1 上,同一个连接允许传输多个 HTTP 请求的情况下,如果第一个请求出错了,后面的请求一般也能够继续处理(当然,如果导致协议解析失败、消息分片错误之类的自然是要除外的)。
最初的http协议只是用来浏览静态文件的,无状态协议已经足够,这样实现的负担也很轻。随着web的发展,它需要变得有状态,于是引入了Cookie、Session等机制来实现有状态连接。
对于有状态协议来说,如果将会话状态与连接绑定在一起,那么如果连接意外断开,整个会话就会丢失,重新连接之后一般需要从头开始。而HTTP这样的无状态协议,使用元数据(如Cookies头)来维护会话,使得会话与连接本身独立起来,这样即使连接断开了,会话状态也不会受到严重伤害,保持会话也不需要保持连接本身。无状态协议的主要缺点在于,单个请求需要的所有信息都必须要包含在请求中一次发送到服务端,这导致单个消息的结构需要比较复杂,必须能够支持大量元数据,因此HTTP消息的解析要比其他许多协议都要复杂得多。同时,这也导致了相同的数据在多个请求上往往需要反复传输,例如同一个连接上的每个请求都需要传输Host、 Authentication、 Cookies、 Server等往往是完全重复的元数据,一定程度上降低了协议的效率。
无连接
无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,短时间内没有等到客户端的新请求,就断开连接。采用这种方式可以节省传输时间,并且可以提高并发性能。
HTTP 协议格式
HTTP 是一个文本格式的协议,有请求和响应两种格式。
HTTP 请求
- 首行:[方法] + [URL] + [版本]
- Header:请求的属性,用冒号分割的键值对,每组属性之间用\n分隔
- 空行:遇到空行表示 Header 部分结束
- Body:该部分允许为空字符串。如果Body存在,则在Header中会有一个 Content-Length 属性来标识 Body 的长度
HTTP 响应
- 首行:[版本号] + [状态码] + [状态码解释]
- Header:请求的属性,用冒号分割的键值对,每组属性之间用\n分隔
- 空行:遇到空行表示 Header 部分结束
- 该部分允许为空字符串。如果Body存在,则在Header中会有一个 Content-Length 属性来标识 Body 的长度。如果服务器反悔了一个html页面,那么html页面内容就在body中
总结
为什么 HTTP 报文中存在"空行"?
因为 HTTP 协议并没有规定包头部分的键值对有多少个。空行相当于是"报头的结束标记",或者是"报头和正文之间的分隔符"。 HTTP 在传输层依赖 TCP 协议,TCP 是面向字节流的,如果没有这个空行,就会出现"粘包问题"。
HTTP 请求方法(method)
GET 和 POST 的区别
GET 和 POST 没有本质的区别,双方可以互换场景。不过在使用习惯上存在一些差异: GET 通常是把传递给服务器的数据放到 query string 中;POST 则是通常放到 body 中(并非绝对, GET 也可以使用 body , POST 也可以使用 query string 。使用的前提是客户端/服务器都按照一样的方式来处理代码)。语义上也存在差异:GET 通常用来获取数据;POST 通常用来提交数据(登陆+上传)。
关于 GET 和 POST 之间的差别的说法1、(错误的)GET 请求能传递的数据量有上限,POST 传递的数据量没有上限。
这个说法是一个"历史遗留"问题,早期版本的浏览器(硬件资源匮乏),针对 GET 请求的 URL 的长度做出了限制。但是实际上,RFC 标准文档中并没有明确规定 URL 能有多长。目前的浏览器和服务器的实现过程中,URL 可以非常长(甚至可以传递一些图片这样的数据)。
2、(错误的)GET 请求传递数据不安全,POST 请求传递数据更安全
这种说法的依据是如果使用 GET 请求来实现登录,会把用户名和密码放到 URL 中,进一步显示到浏览器的地址栏里,于是便可以被别人轻易看到。相比之下,POST 放在 body 中,不会在界面上显示出来,所以更安全。
但是实际上,即使信息被存放在 body 中,我们也可以通过抓包轻松获取到。相比之下,安全性的关键在于加密,即使数据被轻易获取,也难以被破解。
3、(错误的)GET 只能给服务器传输文本数据;POST 可以给服务器传输文本数据和二进制数据。
首先,GET 也不是不可以使用 body 直接存放二进制数据;其次,GET 也可以把二进制数据进行 base64 转码,放到 URL 的 query string 中。
4、(不够准确)GET 请求是幂等的;POST 请求是不幂等的。
幂等是一个数学概念,输入相同的内容,输出是稳定的。
GET 和 POST 具体是否是幂等,取决于代码实现,像广告搜索这一类的 GET 请求就是不幂等的。不过 RFC 标准文档上建议 GET 请求实现成幂等的。
5、(不够准确)GET 请求可以被浏览器缓存,POST 不可以被缓存
幂等性的延续。如果请求是幂等的,自然就可以缓存。
6、(正确的)GET 请求可以被浏览器收藏夹收藏,POST 不能
收藏的时候可能会丢失body。和技术关系不大,看用户需求。
HTTP header
Host
表示服务器主机的地址和端口。通常是从 HTTP URL 中提取出来的,不过在使用代理的情况下,可能和 URL 中的内容不同。
Content-Length
body 中数据的长度,请求中有 body 才会有这个属性。
Content-Type
body 中数据的格式,请求中有 body 才会有这个属性。
User-Agent(UA)
客户端使用的操作系统以及浏览器的名称和版本。
Referer
描述了当前页面是由哪个页面跳转而来。如果直接在地址栏输入 URL 或点击收藏夹中的网页则没有 Referer。
Cookie
Cookie 中存储了一个字符串, 这个数据可能是客户端(网页)自行通过 JS 写入的, 也可能来自于服务器(服务器在 HTTP 响应的header 中通过 Set-Cookie 字段给浏览器返回数据)。往往可以通过这个字段实现"身份标识"的功能(每个不同的域名下都可以有不同的 Cookie, 不同网站之间的 Cookie 并不冲突)。后续再请求这个服务器的时候,就会把 Cookie 中的内容自动带入到请求中,发送给服务器。服务器通过 Cookie 的内容做一些逻辑上的处理。
HTTP 状态码
200 OK
最常用的状态码,表示访问成功。
301 Moved Permanently
永久重定向。到浏览器收到这种响应时,后续的请求都会被自动改成新的地址。301 通过 Location 字段来表示要重定向到的新地址。
302 Move temporarily
临时重定向。登陆页面中经常遇到,用于实现登陆成功后自动跳转到主页。也是通过 Location 字段来表示要重定向到的新地址。
403 Forbidden
表示访问被拒绝。有的页面通常需要用户具有一定的权限才能访问(如登录后才能访问),如果没有登录直接访问,就容易出现 403。
404 Not Found
没有找到资源。
405 Method Not Allowed
用户请求用的方法被禁止。服务器不一定支持所有方法(或者不允许用户使用一些其他的方法)。
500 Internal Server Error
服务器出现内部错误。一般是服务器的代码执行过程中遇到了一些特殊情况(服务器异常崩溃)会产生这个状态码。
504 Gateway Timeout
当服务器负载比较大时,服务器处理单条请求的时候消耗的时间就会很长,就可能会导致出现超时的情况。