整理一下关于HTTP的知识点。
一、基本概念
HTTP定义
超文本传输协议(HyperText Transfer Protocol),一个基于请求与响应模式的、无效的、应用层的协议。常基于TCP的连接方式,绝大数的Web开发都是建立在HTTP 协议之上的Web应用。简单明了就是一个基于应用层的通信规范。
URL(uniform resource locator)
统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。一般由三部组成:①协议(或称为服务方式)②存有该资源的主机IP地址(有时也包括端口号)③主机资源的具体地址。
URI(uniform resource identifier)
统一资源标识符,用来唯一的标识一个资源。URI包括URL和URN,URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。一个URI实例可以代表绝对的,也可以是相对的,只要它符合URI的语法规则。
二、协议格式
Request
HTTP的请求包括:请求行(请求方法、URL、协议版本)、请求头部、空行 和 请求数据 四个部分组成。
Response
响应体包括状态行(版本、状态码和状态信息)、响应头、空行和响应体。
状态码
- 1xx: 指示信息--表示请求已接收,继续处理
- 2xx: 成功--表示请求已被成功接收、理解、接受
- 3xx: 重定向--要完成请求必须进行更进一步的操作
- 4xx: 客户端错误--请求有语法错误或请求无法实现
- 5xx: 服务器端错误--服务器未能实现合法的请求
更多请参考HTTP 响应代码
三、HTTPS简介
HTTP是明文传输的,而HTTPS(全称:HyperText Transfer Protocol over Secure Socket Layer)是经过加密的,可以理解为HTTP+SSL/TLS, 即 HTTP 下加入 SSL 层(安全套接字层,介于HTTP与TCP直接)。SSL有信息加密、完整性校验和身份验证的功能。
信息加密: SSL采用堆成加密和非对称加密组合的形式。
完整性校验: 数字签名。
身份验证:
首先服务端有自己的一对公钥和私钥,然后他拿着公钥、组织信息、个人信息(域名)等去证书颁发机构(Certificate Authority,简称CA)申请认证。如果审核通过,CA会给申请者下发证书,证书包含以下信息:申请者公钥、申请者的组织信息和个人信息、签发机构 CA的信息、有效时间、证书序列号等信息的明文,同时包含一个签名。 其中签名的产生算法:首先,使用散列函数计算公开的明文信息的信息摘要,然后,采用 CA的私钥对信息摘要进行加密,密文即签名。
客户端 Client 向服务器 Server 发出请求时,Server 返回证书文件。客户端 Client 读取证书中的相关的明文信息,采用相同的散列函数计算得到信息摘要,然后,利用对应 CA的公钥解密签名数据,对比证书的信息摘要,如果一致,则可以确认证书的合法性,即服务器的公开密钥是值得信赖的。客户端还会验证证书相关的域名信息、有效时间等信息; 客户端会内置信任CA的证书信息(包含公钥),如果CA不被信任,则找不到对应 CA的证书,证书也会被判定非法。
HTTPS工作流程
- Client发起一个HTTPS的请求,根据RFC2818的规定,Client知道需要连接Server的443(默认)端口。
- Server把事先配置好的公钥证书(public key certificate)返回给客户端。
- Client验证公钥证书:比如是否在有效期内,证书的用途是不是匹配Client请求的站点,是不是在CRL吊销列表里面,它的上一级证书是否有效,这是一个递归的过程,直到验证到根证书(操作系统内置的Root证书或者Client内置的Root证书)。如果验证通过则继续,不通过则显示警告信息。
- Client使用伪随机数生成器生成加密所使用的对称密钥,然后用证书的公钥加密这个对称密钥,发给Server。
- Server使用自己的私钥(private key)解密这个消息,得到对称密钥。至此,Client和Server双方都持有了相同的对称密钥。
- Server使用对称密钥加密“明文内容A”,发送给Client。
- Client使用对称密钥解密响应的密文,得到“明文内容A”。
- Client再次发起HTTPS的请求,使用对称密钥加密请求的“明文内容B”,然后Server使用对称密钥解密密文,得到“明文内容B”。
————————————————————转自深入理解HTTPS工作原理
四、关于HTTP/1.0、HTTP/1.1和HTTP/2.0
4.1http长连接
http最初就是设计成短连接的,因为一般网站资源一次性请求完之后就可以渲染给用户浏览了,没必要一直维持连接,这样会浪费连接资源。但是,一个网站可能会有很多资源要请求,如果每请求一个资源,就创建一个连接,然后关闭,这样代价太大了,我们希望短时间内可以复用tcp连接,所以http1.0就引入了
keep-alive
。
HTTP 1.0
中默认是关闭的,需要在http头加入Connection: Keep-Alive
,才能启用Keep-Alive
,服务端的返回报文头中,也会包含相同的内容。HTTP 1.1
中默认启用Keep-Alive,如果加入Connection: close
,才关闭。
keep-alive
是客户端和服务端的一个约定,如果开启keep-alive
,则服务端在返回 response 后不关闭 TCP 连接。同样的,在接收完响应报文后,客户端也不关闭连接,发送下一个 HTTP 请求时会重用该连接。
如果客户端在接收完所有的信息之后还没有关闭连接,则服务端相应的资源还在被占用,直到达到超时时间,服务端主动断开连接。具体的连接复用时间的长短,通常是由web服务器控制的,tomcat中,我们可以server.xml中配置maxKeepAliveRequests和keepAliveTimeout属性;springboot配置server.tomcat.connection-timeout设置最大连接时长。
- maxKeepAliveRequests:一个连接上,最多可以发起多少次请求,默认100,超过这个次数后会关闭。
- keepAliveTimeout:底层socket连接最多保持多长时间,默认60秒,超过这个时间连接会被关闭。
客户端如何设置?
在我们用到的几乎所有工具都是默认开启长连接的。
对于浏览器而言,几乎你现在用的浏览器(包括 IE6)都默认使用keep-alive
了。
JDK8自带的HttpURLConnection
,默认启用keepAlive
,支持HTTP / 1.1
和HTTP / 1.0
持久连接,使用后的HttpURLConnection
会放入缓存中供以后的同host:port
的请求重用,底层的socket
在keepAlive
超时之前不会关闭。
HttpURLConnection
受以下system properties
控制:
http.keepAlive=<boolean>(默认值:true)
,是否启用keepAlive
,如果设置为false
,则HttpURLConnection
不会缓存,使用完后会关闭socket
连接。http.maxConnections=<int>(默认值:5)
,每个目标host缓存socket连接的最大数。
如果在
HttpURLConnection
的header中加入Connection: close
,则此连接不会启用keepAlive
。想要启用keepAlive
,程序请求完毕后,必须调用HttpURLConnection.getInputStream().close()
(表示归还长连接给缓存,以供下次同host:port的请求重用底层socket连接),而不能调用HttpURLConnection.disconnect()
(表示关闭底层socket
连接,不会启用keepAlive)
Apache HttpClient 默认为每个地址保留 2 个长连接,连接池中最多共保留 20 个连接。
4.2 节约带宽
HTTP 1.1支持只发送header信息(不带任何body信息),如果服务器认为客户端有权限请求服务器,则返回100,否则返回401。客户端如果接受到100,才开始把请求body发送到服务器。这样当服务器返回401的时候,客户端就可以不用发送请求body了,节约了带宽。另外HTTP还支持传送内容的一部分。这样当客户端已经有一部分的资源后,只需要跟服务器请求另外的部分资源即可。这是支持文件断点续传的基础。
4.3 Host域
Host
头是HTTP/1.1
里新增的,所有HTTP/1.1
请求报文中必须包含一个Host
头字段。对于缺少Host
头或者含有超过一个Host
头的HTTP/1.1
请求,可能会收到400(Bad Request)状态码。
Host主要用来实现虚拟主机技术,当我们发送一个请求时,先通过DNS域名解析,得到ip,然后建立tcp连接,当服务器(以nginx为例)收到请求时,就会解析http请求host字段来判断你是访问的哪个server配置下的资源。
4.4 HTTP/2.0
HTTPS安全性好,但是牺牲了性能,SSL握手,对速度会有一定程度的降低,同时大量的加密解密操作对服务端CPU造成很大的压力。于是乎在2012谷歌提出了SPDY(读作“SPeeDY”),他基于SSL,是介于HTTP和SSL之间的协议,主要有以下特点:
- 降低延迟,针对HTTP高延迟的问题,SPDY优雅的采取了多路复用(multiplexing)。多路复用通过多个请求stream共享一个tcp连接的方式,解决了HOL blocking的问题,降低了延迟同时提高了带宽的利用率。
- 请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接共享的基础之上有可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,这样重要的请求就会优先得到响应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文件等加载,这样可以保证用户能第一时间看到网页内容。
- header压缩。前面提到HTTP1.x的header很多时候都是重复多余的。选择合适的压缩算法可以减小包的大小和数量。
- 基于HTTPS的加密协议传输,大大提高了传输数据的可靠性。
- 服务端推送(server push),采用了SPDY的网页,例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的 文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。
参照SPDY的设计,HTTP/2.0诞生了,它和SPDY的区别:
- HTTP2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPS
- HTTP2.0 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DEFLATE
- 新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
- 多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。多路复用
- 还有header压缩以及服务端推送(server push)。
——————————————————————HTTPS和HTTP2.0详解
五、浏览器请求HTTP
5.1 浏览器请求网页的过程
- 域名解析。
首先Chrome浏览器会解析 www.linux178.com 这个域名对应的IP地址。怎么解析到对应的IP地址?
Chrome浏览器会首先搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存)。
如果浏览器自身缓存找不到则会查看系统的DNS缓存,如果找到且没有过期则停止搜索解析到此结束.
而如果本机没有找到DNS缓存,则浏览器会发起一个DNS的系统调用,就会向本地配置的首选DNS服务器发起域名解析请求(通过的是UDP协议向DNS的53端口发起请求,这个请求是递归的请求,也就是运营商的DNS服务器必须得提供给我们该域名的IP地址),运营商的DNS服务器首先查找自身的缓存,找到对应的条目,且没有过期,则解析成功。如果没有找到对应的条目,则有运营商的DNS代我们的浏览器发起迭代DNS解析请求,它首先是会找根域的DNS的IP地址(这个DNS服务器都内置13台根域的DNS的IP地址),找打根域的DNS地址,就会向其发起请求(请问www.linux178.com这个域名的IP地址是多少啊?),根域发现这是一个顶级域com域的一个域名,于是就告诉运营商的DNS我不知道这个域名的IP地址,但是我知道com域的IP地址,你去找它去,于是运营商的DNS就得到了com域的IP地址,又向com域的IP地址发起了请求(请问www.linux178.com这个域名的IP地址是多少?),com域这台服务器告诉运营商的DNS我不知道www.linux178.com这个域名的IP地址,但是我知道linux178.com这个域的DNS地址,你去找它去,于是运营商的DNS又向linux178.com这个域名的DNS地址(这个一般就是由域名注册商提供的,像万网,新网等)发起请求(请问www.linux178.com这个域名的IP地址是多少?),这个时候linux178.com域的DNS服务器一查,诶,果真在我这里,于是就把找到的结果发送给运营商的DNS服务器,这个时候运营商的DNS服务器就拿到了www.linux178.com这个域名对应的IP地址,并返回给Windows系统内核,内核又把结果返回给浏览器,终于浏览器拿到了www.linux178.com对应的IP地址,该进行一步的动作了。
————————————————————转自一次完整的浏览器请求流程
- 建立TCP连接
- 发起HTTP(或HTTPS)请求
- 服务器响应html
- 浏览器解析html,并请求html的静态资源(js,css,图片等)
- 页面渲染
5.2 跨域
浏览器的同源政策:
- 无法获取非同源网页的 cookie、localstorage 和 indexedDB。
- 无法访问非同源网页的 DOM (iframe)。
- 无法向非同源地址发送 AJAX 请求 或 fetch 请求(可以发送,但浏览器拒绝接受响应)。
同源指的是协议、域名和端口三者相同。
为什么要做这种限制呢?
假如用户张三登录了一个网站P,在浏览器留下了Cookie,然后又去访问网站B,假设B站里面有段js代码是取请求P站的接口(或者B站里嵌了个子页面P站),那在去P站取数据的同时带上了P站的Cookie,就拿到了在P站的个人数据。B站可以把这些数据传给服务器,或者带着P站的Cookie在P站乱搞修改个人信息,这就很不安全。
如何允许跨域?
- 跨域资源共享协议CORS(Cross-origin resource sharing)
浏览器发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求。
服务端设置响应头:
- Access-Control-Allow-Origin(必含) – 允许的域名,只能填 *(通配符)或者单域名。
- Access-Control-Allow-Methods(必含) – 这允许跨域请求的 http 方法(常见有 POST、GET、OPTIONS)。
- Access-Control-Allow-Headers(当预请求中包含 Access-Control-Request-Headers 时必须包含) – 这是对预请求当中 Access-Control-Request-Headers 的回复,和上面一样是以逗号分隔的列表,可以返回所有支持的头部。
- Access-Control-Allow-Credentials(可选) – 表示是否允许发送Cookie,只有一个可选值:true(必为小写)。如果不包含cookies,请略去该项,而不是填写false。这一项与 XmlHttpRequest 对象当中的 withCredentials 属性应保持一致,即 withCredentials 为true时该项也为true;withCredentials 为false时,省略该项不写。反之则导致请求失败。
- Access-Control-Max-Age(可选) – 以秒为单位的缓存时间。在有效时间内,浏览器无须为同一请求再次发起预检请求。
浏览器根据接收到的响应头里的
Access-Control-Allow-origin
字段做匹配,若无该字段,说明不允许跨域,从而抛出一个错误;若有该字段,则对字段内容和当前域名做比对,如果同源,则说明可以跨域,浏览器接受该响应;若不同源,则说明该域名不可跨域,浏览器不接受该响应,并抛出一个错误。
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)
简单请求是指满足以下条件的(一般只考虑前面两个条件即可):
- 使用 GET、POST、HEAD 其中一种请求方法。
- HTTP的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID,Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain。
- 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;
XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。请求中没有使用 ReadableStream 对象。
除了简单请求就是非简单请求了,非简单请求
- JSONP
JSONP 的原理就是利用 <script> 标签的 src 属性没有跨域的限制,通过指向一个需要访问的地址,由服务端返回一个预先定义好的 Javascript 函数的调用,并且将服务器数据以该函数参数的形式传递过来,此方法需要前后端配合完成。
- 服务器代理(nginx反向代理)
————————————————引自彻底理解浏览器的跨域
5.3 浏览器缓存
HTTP缓存介绍及在spring boot中设置HTTP缓存
参考资料
Http——Keep-Alive机制
HTTP/1.0、HTTP/1.1 以及 HTTP/2.0主要区别
http 请求中host字段作用
深入理解HTTPS工作原理
HTTPS和HTTP2.0详解