Http基础
输入URL按下回车发生了了啥
三次握手
输入地址后,Web服务器与浏览器之间需要建立连接
此处建立连接需要经过三次握手,也就是经过**STN、SYN/ACK、ACK**三个包,浏览器与服务器的TCP连接就建立起来了
HTTP协议工作
浏览器按照HTTP协议规定的格式,通过TCP发送了一个"GET/HTTP/1.1"请求报文
Web服务器回复了第五个包,表示刚才的报文已经收到了,在TCP协议层面确认,然后Web服务器开始解析这个报文,看看浏览器需要干啥
解析完之后将浏览器所需的文件在磁盘读出来,拼成HTTP格式的报文发回去,也就是第六个包"HTTP/1.1 200 OK",底层还是走TCP协议
然后浏览器收到之后也给服务器回复一个TCP的ACK确认,即第七个包
此处还有4个包,这是浏览器自动请求了网站图标的"favicon.ico"文件,浏览器会确认收到这个包,然后解析,重新发回去一个404的包,浏览器收到后回复一个ACK包
这样,键入URL在按下回车的全过程就结束了
最简单的浏览器HTTP请求过程
- 浏览器从地址栏输入中获取服务器的 IP地址和端口
- 使用TCP的三次握手与服务器建立连接
- 浏览器向服务器发送拼好的报文
- 服务器收到报文后处理请求,同样拼好报文后再发给浏览器
- 浏览器解析报文,渲染输出页面
更加详细的过程查看文章:juejin.cn/post/713128…
使用域名访问服务器
浏览器发现网址不是一个数字形式的IP地址,那肯定就是一个域名了,于是就会发起域名解析动作,通过访问一系列的域名解析动作,试图把这个域名翻译成TCP/IP协议中的IP地址
由于域名解析的过程太复杂,座椅在域名解析过程中有多级缓存,浏览器会先看一下自己的缓存中有没有,没有再去找操作系统的缓存,再没有再去检查本机的域名解析文件hosts,如果找不到再通过本地DNS、根DNS、顶级DNS、权威DNS解析查找
HTTP报文长什么样子
其实,HTTP并不用管怎么运输,因为运输由底层的TCP/IP协议负责
而HTTP协议的核心部分应该是传输的报文内容
报文结构
我们之前学习过TCP的报文格式,他要在实际传输的数据之前附加一个20字节的头部数据,存储TCP协议必须的额外信息,例如发送方的端口号,接收端的端口号,包序号等等
而我们必须依靠这个TCP头才能将数据包正确传输,到达目的地之后将头部去掉,就可以拿到真正的数据
而HTTP协议与TCP协议类似,同样需要加上一些头数据,不过这些数据与TCP不同,因为他是一个纯文本的协议,所以头数据都是ASCII码的文本
HTTP协议的请求报文和响应报文的结构基本相同,由以下三部分构成:
- 起始行:描述请求或响应的基本信息
- 头部字段集合:使用
key-value形式更详细的说明报文 - 消息正文:实际传输的数据,不一定是纯文本,也可以是图片等二进制数据
其中前两部分经常被合称为请求头或响应头,也就是**header,消息正文称为实体,也就是body**
HTTP协议规定报文必须有header,但可以没有body,在header之后必须要有空行,也就是CRLF,所以,一个完整的HTTP报文应该是这样的:
虽然HTTP协议对header大小没有限制,但是各个Web服务器不允许过大的请求头,应该头部太大会占用大量的服务器资源
请求行
请求报文中的起始行也就是请求行,描述了客户端想要如何操作服务器端的资源
请求行由三部分构成:
- 请求方法:动词,如GET/POST,表示对资源的操作
- 请求目标:通常是一个URL,标记了请求方法要操作的资源
- 版本号:表示报文要使用的HTTP协议版本
GET / HTTP/1.1
状态行
状态行是响应报文中的起始行,并不叫作响应行,意思是服务器响应的状态
状态行也是由三部分构成:
- 版本号:表示报文要使用的HTTP协议版本
- 状态码:一个三位数,用代码的形式表示处理的结果,如200、500
- 原因:作为数字状态码的补充,是更详细的解释文字,帮助人理解原因
HTTP/1.1 200 OK
HTTP/1.1 404 Not Found
头部字段
请求行或状态行加上头部字段集合就构成了HTTP报文中的完整的请求头和响应头,如图:
其中,响应头的结构与请求头类似,只要将请求行替换一下就可以了
头部字段是key-value的形式,之间使用:分隔,最后用CRLF换行表示字段结束
由于HTTP字段灵活,所以不仅可以使用标准里面的已有的头,如Host,还可以任意添加自定义头,这位HTTP协议带来无限的扩展可能
其中,头部字段需要注意几点:
- 字段名不区分大小写,但是最好首字母大写
- 字段名不允许出现空格,但是可以使用
-连接,不能使用_ - 字段名后面必须紧跟
:,不能有空格,但是:后面的字段前可以有多个空格 - 字段的顺序没有意义
- 字段原则上不能重复,除非该字段本身语义允许,如
Set-Cookie
常用头字段
HTTP的头部字段可以分为四大类:
- 通用字段:在请求头和响应头中都可以出现
- 请求字段:只能出现在请求头中,进一步说明请求信息或者额外的附加条件
- 响应字段:只能出现在响应头中,补充说明响应报文的信息
- 实体字段:实际上属于通用字段,但是专门用来描述body的额外信息
实际上,我们对HTTP报文的解析和处理就是主要对头字段的处理
所以这里,我们先来了解一下几个常用的头字段:
-
Host
属于请求字段,只能出现在请求头中
这是唯一一个HTTP/1.1规范中要求必须出现的字段,也就是说如果没有
Host,就是一个错误的报文Host字段告诉服务器这个请求应该由哪个主机来处理,当一个计算机上托管了多个虚拟主机的时候,服务器端就需要用Host来选择,有点像是一个简单的路由重定向 -
User-Agent
属于请求字段, 只能出现在请求头中
使用一个字符串来描述发起HTTP请求的客户端,服务器可以依据它来返回最适合此浏览器显示的页面
-
Date
属于通用字段,但是常出现在响应头中
表示HTTP报文创建的时间,客户端可以使用这个时间再搭配其他字段决定缓存策略
-
Server
属于响应字段,只能出现在响应头中
告诉客户端当前正在提供Web服务的软件名称和版本号
这个字段不是必要的,因为服务器的一部分信息会通过这个字段暴露给外界,如果这时候该版本恰好存在bug,那么可能被攻击
所以,有的响应头中要么没有这个字段,要么给出完全无关的描述信息
-
Content-Length
属于实体字段
表示报文中body的长度,也就是请求头或响应头空行后面数据的长度
服务器看到这个字段就知道后于有多少数据了,如果没有这个字段,那么body就是不定长的,需要使用chunked方式分段传输
如何理解请求方法
在上一节我们已经了解了请求头的构成,所以现在我们来了解一下请求头的请求方法
标准请求方法
那么为什么要有请求方法这个东西呢?
用HTTP协议构建一个超链接文档系统,需要使用URL来定位这些文档,也就是资源,所以操作这些不同的资源需要用不同的请求方式
目前HTTP/1.1规定了八种方法,单词必须是大写形式:
- GET:获取资源,可以理解为读取或下载数据
- HEAD:获取资源的元信息
- POST:向资源提交数据,相当于写入或上传数据
- PUT:类似POST
- DELETE:删除资源
- CONNECT:建立特殊的连接隧道
- OPTIONS:列出可对资源实行的方法
- TRACE:追踪请求 - 响应的传输路径
GET/HEAD
GET方法是HTTP协议中用的最多的请求方式
他的含义是请求从服务器获取资源,这个资源既可以是静态的文本、图片等,也可以是由Java动态生成页面或者其他格式的数据
这个方法虽然简单,但是搭配URL和其他头字段就能实现对资源更精细的操作
比如可以在URL之后使用#,获取页面后直接定位到某个标签所在的位置;Range字段就是范围请求,只获取资源的一部分数据
HEAD方法与GET类似,也是请求从服务器获取资源,服务器的处理机制也是一样的,但是服务器不会返回请求的实体数据,只会传回响应头,也就是资源的元信息
HEAD方法常用在检查一个文件是否存在,只需要发送一个HEAD请求就可以了,不用把整个文件取下来;或者检查文件是否有最新版本,服务器会在响应头中把文件的修改时间传回来
POST/PUT
POST和PUT方法 跟GET与HEAD相反,这两个操作是向URL指定的资源提交数据,数据就放在报文的body中
POST也经常用到,只要是向服务器发送数据,我们一般用的都是POST,这里就不多讲述
而PUT与POST类似,也可以向服务器提交数据,但是与POST有一点不同,通常POST表示的是新建含义,而PUT指的是修改含义
DELETE
指服务器删除资源,由于危险性太大,所以服务器通常不会执行真正的删除操作,而是对资源做一个删除标记
所以这个请求方法不常用
CONNECT
要求服务器列出可对资源实行的操作方法,在响应头的Allow字段中返回
由于功能有限,所以有得服务器干脆没有实现(Nginx)
TRACE
多用于HTTP链路的测试或诊断,可以显示出请求-响应的传输路径
但是会存在漏洞,会泄露网站的信息,所以也很少使用
安全和幂等
-
安全
这里的安全指的是请求方法不会破坏服务器上大的资源,即不会对服务器上的资源造成实质的修改
所以只有GET和HEAD是安全的,因为他们是只读操作,所以无论请求多少次,服务器上的数据都是安全的
而POST等请求方法,都会修改服务器上资源,所以是不安全的
-
幂等
这里指多次执行相同的操作,结果也是相同的
所以很显然,GET和HEAD也是幂等的
而DELETE可以删除同一个资源,效果都是资源不存在,所以也是幂等的
但是POST是新增或提交数据,多次提交数据会创建多个资源,所以不是幂等的
PUT是替换或更新数据,多次更新一个资源,资源还是第一次更新的状态,所以是幂等的
能否写出正确的网址
URI,也就是统一资源标识符,因为它经常出现在浏览器的地址栏中,所以俗称网络地址,简称网址
严格地说,URL并不完全等于网址,因为其包含URL和URN两个部分,在HTTP中网址实际上是URL——统一资源定位符,因为URL太普及,所以经常将两者视为相等
但是URI十分重要,我们需要深入学习URI
URI的格式
URI本质上就是一个字符串,作用是唯一地标记资源的位置或者名字
它不仅能够标记万维网的资源,也可以标记像邮件系统、本地文件系统等任意资源,而资源既可以是存在磁盘上的静态文件,也可以是Java提供的动态服务
这是URI最常用的形式:
URI基本格式
scheme
URI第一部分叫做scheme,也就是协议名,表示资源应该使用哪种协议来访问
最常见的就是http还有https了,还有不是很常见的协议名,例如ftp、file等
这个scheme是必须的,因为浏览器或者应用程序需要根据scheme来判断下一步应该怎么走
在scheme之后,必须是三个特定的字符:// ,把scheme和后面部分隔开
authority
这一部分在://之后,表示资源所在的主机名,通常的形式是**host:port**,即主机名加端口号
主机是IP地址或者域名的形式,必须要有,否则浏览器就找不到服务器
但是端口可以省略,因为有默认端口号,例如HTTP的默认端口号为80,HTTPS默认端口号为443
path
有了主机名和端口号,再加上标记资源所在位置的path,浏览器就可以连接服务器访问资源了
URI中的path采用了UNIX的/风格,URI的path必须以/开始,也就是必须包含/,所以不能将其误认为属于前面authority
接下来我们看几个例子
http://nginx.org
http://www.chrono.com:8080/11-1
https://tools/ietf.org/html/rfc7230
file:///D:/http_style/www/
第一个URI中的协议名是http,主机名是nginx.org,使用了默认端口80,路径部分也被省略了,默认就是一个/,表示根目录
第二和第三比较简单,不做分析
第四个URI的协议名是file,表示是本地文件,后面的前两个斜杆,属于分隔符://中的//,而后面的 /D:/http_style/www/是路径,中间的主机名被省略,这其实是file类型URI的特例,它允许省略主机名,默认是本机localhost
HTTP报文中的URI
在地址栏中输入http://www.chrono.com:8080/11-1,可以发现返回的请求行是GET /11-1 HTTP/1.1,可以发现这里和浏览器中输入的http://www.chrono.com:8080/11-1,有很大的不同,协议名和主机都不见了,只剩下后面的部分
这是因为协议名和主机名分别出现在请求行的版本号和请求头的Host字段中了,没必要重复
所以通过这里,我们得出一个结论:客户端和服务器看到的URI是不一样的,客户端看到的必须是完整的URI,而服务器看到的只是报文请求行中被删除了协议名和主机名的URI
URI的查询参数
我们现在已经可以用“协议名 + 主机名 + 路径”的方式,可以定位到网络上任何资源了,但这还不够,有时候我们需要在操作资源的时候附加一些额外修饰参数
所以现在我们需要在URI后面加上一个query部分,他在path之后,用一个?开始,但是不包含?,表示对资源附加的额外要求
查询参数有自己的一套形式,使用key=value的字符串,这些KV值用&连接,浏览器和客户端都可以按照这个格式将长串的查询参数解析成可理解的形式
URI的完整格式
这个才是URI的完整格式,相比常用的形式,这个格式多了两个部分
第一个是身份信息user:passwd@ ,表示登录主机时的用户名和密码,但是现在已经不推荐使用了,因为他把敏感信息以明文的形式暴露出来,存在严重的安全隐患
第二个是片段标识符#fragment,他是URI所定位的资源内部的一个锚点(标签) ,浏览器可以在获取资源后直接跳转到他指示的位置,但是片段标识符仅能由浏览器这样的客户端使用,服务器看不到,也就是说浏览器永远不会把带#fragment的URI发送给服务器,服务器永远不会用这种方式处理资源
URI的编码
如果在URI中出现了汉字等非英语的语言,那么URI会怎么做?
URI引入了一套编码机制,对于ASCII码以外的字符集和特殊字符做一个特殊操作,把他们转换成与URI语义不冲突的形式,也就是转义
转义的规则就是直接把非ASCII码或者特殊字符转换成十六进制字节值,然后在前面加上一个%
例如,空格被转义成%20,?被转义成%3F等,而中文通常使用UTF-8编码后再转义
所以现在,URI就可以支持任意字符集用任何语言来标记资源了
响应状态码应该怎么用
这一节,我们来了解一下状态行中的状态码
状态码
RFC标准把状态码分成五类,用数字的第一位表示分类,而0~99不用,所以状态码的范围就变成了100~599
这五类的具体含义分别是:
- 1xx:提示信息,表示目前是协议处理的中间状态,还需要后续的操作
- 2xx:成功,报文已经收到并被正确处理
- 3xx:重定向,资源位置发生变动,需要客户端重新发送请求
- 4xx:客户端错误,请求报文有误,服务器无法处理
- 5xx:服务器错误,服务器在处理请求的时候内部发生了错误
接下来,我们对一些重要的状态码进行分析
1xx
1xx这类状态码属于提示信息,是协议处理的中间状态,实际用到的时候很少
能够见到的就是**101 Switching Protocols**,他的意思是客户端使用Upgrade头字段,要求在HTTP协议的基础上改成其他的协议继续通信,如WebSocket
如果服务器也同意变更协议,就会发送状态码101,在这之后数据的传输就不会使用HTTP了
2xx
2xx类状态码表示服务器收到并成功处理了客户端的请求
200 OK:表示一切正常,服务器如客户端期望的那样返回了处理结果,如果是非HEAD请求,通常在响应头后面都有body数据
204 No Content:含义与200 OK基本相同,但是响应头后面没有body数据
206 Partial Content:HTTP分块下载或断点续传的基础,在客户端发送范围请求、要求获取资源的部分数据时出现,与200一样,也是服务器成功处理了请求,但是body中的数据不是资源的全部,而是其中一部分
206状态码还会伴随着头字段Content-Range,表示报文中body数据的具体范围,例如Content-Range: bytes 0-99/2000,意思是此次获取的是总计2000字节的前100个字节
3xx
3xx类状态码表示客户端请求的资源发生了变动,客户端必须使用新的URI重新发送请求来获取资源,也就是常说的重定向,包括著名的301、302跳转
301 Moved Permanently:也就是永久重定向,含义是此次请求的资源已经不存在了,需要改用新的URI进行再次访问
302 Found:以前的描述短语是Moved Temporarily,也就是临时重定向,意思是请求的资源还在,但是需要暂时用另一个URI来访问
301和302都会在响应头中使用字段Location指明后面要跳转的URI,最终的效果都是浏览器都会重定向到新的URI,而两者的区别在于语义,一个是永久,一个是临时
304 Not Modified:用于If-Modified-Since等条件请求,表示资源未修改,用于缓存控制,不具有通常的跳转含义,但是可以理解为重定向到缓存的文件(缓存重定向)
4xx
4xx类状态码表示客户端发送的请求报文有误,服务器无法处理
400 Bad Request:表示请求报文有错误,但具体的错误并没有明确提及
403 Forbidden:这里实际上不是客户端的请求出错,而是表示服务器禁止访问资源,原因可能是信息敏感、法律禁止等,如果服务器友好会在body详细说明拒绝请求的原因
404 Not Found:表示资源在本服务器上未找到,所以无法提供给客户端,但是现在的意思不仅仅是未找到了,还有很多原因
405 Method Not Allowed:不允许使用某些方法操作资源
406 Not Acceptable:资源无法满足客户端的请求条件,例如请求中文但只有英文
408 Request Timeout:请求超时
409 Conflict:多个请求发生了冲突,可以理解为多线程并发时的竞态
413 Request Entity Too Large:请求报文中的body太大
414 Request-URI Too Long:请求行的URI太大
429 Too Many Requests:客户端发送太多请求,通常是由于服务器的限连策略
431 Request Header Fields Too Large:请求头某个字段或总体太大
5xx
5xx类状态码表示客户端请求报文正确,但是服务器在处理的时候内部发生了错误,无法返回应有的响应数据
500 Internal Server Error:与400类似,也是一个通用的错误码,具体发生什么错误我们并不知道,但是对于服务器来说应该是好事,因为这样不会把服务器的详细信息暴露给外界,防止黑客攻击
501 Not Implemented:表示客户端请求的功能还不支持
502 Bad Gateway:表示服务器作为网关或者代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发送了错误,但是具体的原因也不知道
503 Service Unavailable:表示服务器很忙,暂时无法响应服务,这是一个临时的状态,可能过几秒后就没那么忙了,可以继续提供服务,所以通常还带有一个**Retry-After字段,表示客户端可以在多久之后再次尝试发送请求**
HTTP有哪些特点
灵活可扩展
HTTP是一个灵活可扩展的传输协议
它随着互联网的发展不断完善,像请求方法、状态码等都是后续加上去的,而body也从一开始的文本形式或HTML到现在的各种图片和音视频等形式
而RFC文档,是对已有扩展的承认和标准化
可靠传输
HTTP是一个可靠的传输协议
因为HTTP是基于TCP/IP的,而TCP协议本身就是一个可靠的传输协议,所以HTTP也就继承了这个特性,能够在请求方和应答方之间可靠的传输数据
具体做法与TCP/UDP差不多,都是对实际数据的传输做了一层包装,加上一个头,然后调用Socket API,通过TCP/IP协议栈发送或者接收
这里的可靠的含义需要注意一下,可靠指的只是在下层用多种手段尽量包装数据的完整送达,如果网络不稳定,也可能发送失败
应用层协议
HTTP是一个应用层的协议
不仅只有HTTP这个应用层协议,还有像FTP(只能传输文件),SMTP(只能发送邮件),SSH(只能远程登陆)等,这些由于局限在很少的应用场景,而HTTP凭借可携带任意头字段和实体数据的报文结构、连接控制、缓存代理等,打败了众多应用层协议
请求 - 应答
HTTP协议使用的是请求-应答通信模式
这个模式是HTTP协议最根本的通信模型,明确了协议通信双方的定位,永远是请求方先发起连接和请求,然后应答方收到请求后才能答复
这里的请求方和应答方不是绝对的,在浏览器-服务器场景中,通常服务器都是应答方,但是如果服务器用作代理连接后端服务器,那么他就可能同时扮演请求和应答方的角色
无状态
HTTP是无状态的
状态其实就是客户端或者服务器中保存的一些数据或者标志,记录了通信过程中的一些变化信息
HTTP建立连接之前,客户端和服务器永远处在一种毫不知情的状态,每次发的报文也是相互独立的、没有任何的联系,收发报文也不会对客户端或服务器产生任何影响,连接后也不会要求保存任何信息
形象的说就是没有记忆能力,假如一个请求需要验证身份,第一次浏览器发送完请求,服务器验证完返回相关数据,第二次该浏览器又发一个请求,这次服务器还是得重新验证浏览器的身份
在这里对比一下UDP协议,UDP协议也是无连接无状态的,顺序发包乱序收包,数据包发出去之后就不管了,收到后也不会整理顺序,而HTTP是有连接无状态,顺序发包顺序收包
虽然是无状态的,但是也可以在协议的框架中给他打个补丁,增加这个特性
其他特点
除了这五大特点,还有许多特点,如传输的实体数据可缓存可压缩,可分段获取数据,支持身份认证,支持国际化语言等,但是这些不能作为基本特点,因为这些都是基于第一个灵活可扩展的特点衍生出来的
HTTP的优缺点
简单、灵活、易于扩展
这是HTTP最突出的优点
HTTP协议很简单,基本的报文格式就是header + body,可以降低学习的门槛
在简单的这个基础设计理念之下,HTTP又多出了灵活和易于扩展的优点
由于HTTP协议中的请求方法、URI、状态码等每一个核心组成要素都没有被写死,允许开发者任意定制补充,所以也赋予了HTTP这个特点
灵活、易于扩展这个特性还表现在HTTP对可靠传输的定义上,不限制具体的下层协议,不仅可以使用TCP,还可以使用SSL/TLS等,下层可以随意变化,上层语义始终保持稳定
应用广泛、环境成熟
随着互联网的普及,HTTP已经延伸到世界上每一个角落了,软硬件环境都非常成熟了
并且在开发领域还具有天然的跨语言、跨平台的优越性,不用限定某种编程语言或者操作系统
无状态
无状态对于HTTP来说其实既是优点也是缺点
-
优点
因为服务器没有记忆能力,所以不需要额外的资源来记录状态信息,不仅实现上简单一点,还能减轻服务器的负担
并且,无状态也就表示着服务器都是相同的,可以很容易地组成集群,让负载均衡把请求转发到任意一台服务器,不会因为状态不一致导致处理出错
-
缺点
由于服务器没有记忆能力,所以在一些需要验证身份的时候,在同一个客户端多次发送请求时,服务器不知道这些请求的关联性,所以每一次都得确定身份信息
明文
明文的意思就是协议中的报文不使用二进制数据,而使用简单可阅读的文本形式
对于TCP/UDP 这些二进制协议,优点显而易见,就是不需要借助任何外部工具,可以在抓包之后直接查看或者修改,为开发调试工作带来便利
但是缺点也是一样显而易见,报文的所有信息都会暴露在外面,在传输链路上每一个环节都毫无隐私可言,只要侵入了这个链路中的某个设备,就可以实现对通信的窥视
免费WiFi陷阱就是这样一个原理,利用了明文传输的特点,将流量截获保存,如果有银行卡号等敏感信息就危险了
不安全
不安全这个缺点与明文相关,但是不完全等同
明文只是机密方面的一个缺点,在身份验证和完整性校验这两方面HTTP也是欠缺的
首先是身份验证,HTTP并没有提供有效的手段来确认通信双方的真实身份,虽然协议中有一个基本的认证机制,但是由于明文传输缺点,这个机制非常容易被攻破,所以如果进入一个页面一模一样的假冒网站,就很可能被盗走各种私人信息
其次是完整性校验,数据在传输过程中容易被篡改而无法验证真伪
为了解决这个不安全的缺点,就出现了HTTPS
性能
HTTP的性能,不算差,不够好
HTTP协议基于TCP/IP,并且使用了请求 - 应答的通信模式,性能的关键也就在这两点上
TCP的性能是不差的,因为其经过了很多优化,足以应付大多数场景,但是现在互联网的特点是移动和高并发,不能保证稳定的连接质量,所以TCP层面上HTTP协议有时候就会表现得不够好
而请求应答模式则加剧了这种问题,这就是著名的队头阻塞,当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一并被阻塞,会导致客户端迟迟收不到数据
为了解决这个问题,HTTP官方就有缓存来解决,非官方的方式更多
而现在的最终解决方案就是使用:HTTP/2和HTTP/3