本次分享http协议,共分为三部分,这是第三部分,主要讲解一个完整的http请求都经过哪些步骤,当我们在地址栏中输入网址,到返回页面都经历了什么
1.输入网址
当我们在浏览器中输入网址的时候,浏览器就已经在只能匹配可能的 url 了,他会从历史纪录,书签等地方查找已经输入的字符串所对应的 url ,然后给出智能提示,,让你可以补全 url 地址,对于 google 的 chrome 浏览器,他甚至会直接从缓存中把网页展示出来,就是说你还没有按下 enter 页面就展示出来了。
2.浏览器查找域名的 IP 地址
1.请求一旦发起,浏览器数显要做的事情就是解析这个域名,一般来说,浏览器会首先查看浏览器的 DNS 缓存中是否有缓存纪录,如果有就直接展示,否则就查看本地的 Hosts 文件,看看有没有和这个域名对应的规则,如果有的话就直接使用 Hosts 文件里的 IP 地址。
2.如果本地的 Hosts 文件没有找到对应的 IP 地址,浏览器会发出一个 DNS 请求到本地的 DNS 服务器,本地 DNS 服务器一般都是你网络接入的服务器商提供,如中国电信,中国移动。
3.查询你输入的网址的 DNS 请求到达本地的 DNS 服务器之后,本地的 DNS 服务器会首先查询他的缓存纪录,如果缓存中有此条纪录,就直接返回结果,此过程是递归的方式查询,如果没有,本地的 DNS 服务器还要向 DNS 根服务器进行查询。
4.根 DNS 服务器没有纪录具体的域名和 IP 地址的对应关系,而是告诉本地的 DNS 服务器你可以去域服务器上去继续查询,并给出域服务器的地址,此过程是迭代过程。
5.本地的 DNS 服务器继续向域服务器发出请求,在这个例子中,请求的对象是 .com 域服务器 .com 域服务器收到请求后,也不会直接返回域名和 IP 地址的对应关系,而是告诉本地的 DNS 服务器你的域名和解析服务器的地址。
6.最后,本地 DNS 服务器向域名的解析服务器发出请求,这时就能收到域名和 IP 地址的对应关系,本地的 DNS 服务器不仅要把 IP 地址返回给用户电脑,还有把这个对应关系保存在缓存中,以便下次用户查询时,可以直接返回结果,加快网络访问。
3.浏览器向 web 服务器发送一个 http 请求
拿到域名对应的 IP 地址后,浏览器会以一个随机端口(1024 < 端口 < 65535)向服务器的 web 程序(常用的有 httpd,nginx等) 80端口发起 TCP 连接请求。这个连接请求发到服务器端后(这中间通过各种路由设备,局域网内除外),进入到网卡,然后进入到内核 TCP/IP协议栈(用于识别该连接请求,解析包,一层一层的剥开),还有可能要经过 netfilter 防火墙(属于内核模块)的过滤,最终到达 web 程序,最终建立了 TCP/IP 连接。
建立了 TCP 连接之后,发起一个 http 请求,一个典型的 http request header 一般需要包括请求的方法,例如 GET 或者 POST 等,不常有的有 PUT,DELETE,HEAD,OPTION 以及 TRACE 方法,一般的浏览器只能发起 GET 或者 POST 请求。
客户端向服务器端发起 http 请求的时候,会有一些请求信息,请求信息包含三部分:
- 请求方法 URI 协议/版本呢
- 请求头(request header)
- 请求正文
下面是一个完整的 http 请求的例子
GET/sample.jsp HTTP/1.1
Accept:image/gif.image/jpeg,*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding:gzip,deflate
username=jinqiao&password=1234
注意:最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头。
4.服务器的永久重定向响应
服务器给浏览器返回一个 301 永久重定向响应,这样浏览器就会访问 " http://www.google.com/ " 而不是 " http://google.com/ " 。
为什么服务器一定要重定向而不是直接发送给用户想看的网页内容呢?其中一个原因跟网站排名有关系,如果一个网页有两个网址,就像:http://www.yy.com 和 http:/yy.com ,搜索引擎会认为他们是两个网站,结果造成每个搜索连接都减少从而降低排名。而搜索引擎知道 301 永久重定向是什么意思,这样就会把访问带有 www 和不带 www 的网站地址归到同一个排名下。还有就是用不同的地址会造成缓存友好性变差,当一个页面有好几个名字时,他可能会在缓存里出现好几次。
5.浏览器跟踪重定向地址
现在浏览器知道了 " http://www.google.com "才是要访问的正确地址,所以他会发送里一个 http 请求。
6.服务器处理请求
经过前面的重重步骤,我们终于将我们的 http 请求发送到了服务器这里,其实前面的重定向已经是到达服务器了,那么,服务器时如何吃力我们的请求的呢?
后端从在固定的端口接收到 TCP 报文开始,它会对 TCP 连接进行处理,对 http 协议进行解析并按照报文格式进一步封装成 HTTP Request 对象,供上层使用。
一些大一点的网站会将你的请求到反向代理服务器中,因为当网站访问量非常大,网站越来越慢,一台服务器已经不够用了,于是将同一应用部署到多台服务器上,将大量的用户请求分配给多台机器处理,此时,客户端不是直接通过 http 协议访问某网站应用服务器,而是先请求到 Nginx,Nginx 在请求应用服务器,然后将结果返回给客户端,这里 Nginx 的作用是反向代理服务器。同时也带来了一个好处,其中一台服务器万一挂了,只要还有其他服务器正常运行,就不会影响用户正常使用。
通过 Nginx 的反向代理,我们到达了 web 服务器,服务器端脚本处理我们的请求,访问我们的数据库,获取需要获取的内容等等。
7.服务器返回一个 http 响应
经过前面的 6 个步骤,服务器收到了我们的请求,也处理我们的请求,到这一步,它会把它的处理结果返回,也就是返回一个 http 响应。
http 响应与 http 请求类似,http 响应也由三部分构成,分别是:
- 状态行
- 响应头(Response Header)
- 响应正文
HTTP/1.1 200 OK
Date: Sat, 31 Dec 2005 23:59:59 GMT
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 122
<html>
<head>
<title>http</title>
</head>
<body>
<!-- body goes here -->
</body>
</html>
状态行:
状态行由协议版本,数字形式的状态码,以及响应的状态描述,各元素之间用空格分隔
响应头:
响应头部:由关键字 / 值对组成,每行一对,关键字和值用英文符号 " : " 分隔。
响应正文:
包含着我们需要的一些具体信息,比如 cookie,html,image,后端返回的请求数据等,这里需要注意,响应正文和响应头之间由一行空行,表示响应头的信息到空行为止。
8.浏览器显示 HTML
在浏览器没有完整接收 html 文档时,他就已经开始显示这个页面了,浏览器是如何把页面呈现在屏幕上的呢?不同浏览器解析的过程可能不太一样,在这里我只介绍 webkit 的渲染过程,这个过程包括:
解析 html 以构建 DOM 树 --> 构建 render 树 --> 布局 render 树 --> 绘制 render 树
浏览器在解析 html 文件时,会 " 自上而下 "加载,并在加载过程中进行解析渲染,在解析过程中,如果遇到外部请求资源时,如图片,外链的 css ,iconfont 等,请求过程是异步的,并不会影响 html 文档进行加载。
解析过程中,浏览器首先会解析 html 文件构建 DOM 树,然后解析 css 文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上,这个过程比较复杂,涉及到两个概念:reflow(回流)和 repain(重绘)。
DOM 节点中的各个元素都是一盒模型的形式存在,这些都需要浏览器去计算其位置和大小,这个过程称为 reflow(回流),当盒模型的大小,位置及其他属性,如颜色,字体等都确定下来之后,浏览器便开始绘制内容,这个过程称为 repain(重绘)。
页面在首次加载时必然会经历 reflow 和 repain ,reflow 和 repain 的过程是非常消耗性能的,尤其是在移动设备上,它会破坏用户体验,有时会造成卡顿,所以我们应该尽可能少的减少 reflow 和 repain。
当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。因为JS有可能会修改DOM,最为经典的document.write,这意味着,在JS执行完成前,后续所有资源的下载可能是没有必要的,这是js阻塞后续资源下载的根本原因。所以我明平时的代码中,js是放在html文档末尾的。
JS的解析是由浏览器中的JS解析引擎完成的,比如谷歌的是V8。JS是单线程运行,也就是说,在同一个时间内只能做一件事,所有的任务都需要排队,前一个任务结束,后一个任务才能开始。但是又存在某些任务比较耗时,如IO读写等,所以需要一种机制可以先执行排在后面的任务,这就是:同步任务(synchronous)和异步任务(asynchronous)。
JS的执行机制就可以看做是一个主线程加上一个任务队列(task queue)。同步任务就是放在主线程上执行的任务,异步任务是放在任务队列中的任务。所有的同步任务在主线程上执行,形成一个执行栈;异步任务有了运行结果就会在任务队列中放置一个事件;脚本运行时先依次运行执行栈,然后会从任务队列里提取事件,运行任务队列中的任务,这个过程是不断重复的,所以又叫做事件循环(Event loop)。
9.浏览器发送请求获取嵌入在 HTML 中的资源(如图片,音频,视频,css,js等等)
其实这个步骤可以并列在步骤8中,在浏览器显示HTML时,它会注意到需要获取其他地址内容的标签。这时,浏览器会发送一个获取请求来重新获得这些文件。比如我要获取外图片,CSS,JS文件等,类似于下面的链接:
图片:http://static.ak.fbcdn.net/rsrc.php/z12E0/hash/8q2anwu7.gif
CSS式样表:http://static.ak.fbcdn.net/rsrc.php/z448Z/hash/2plh8s4n.css
JavaScript 文件:http://static.ak.fbcdn.net/rsrc.php/zEMOA/hash/c8yzb6ub.js
这些地址都要经历一个和HTML读取类似的过程。所以浏览器会在DNS中查找这些域名,发送请求,重定向等等...
不像动态页面,静态文件会允许浏览器对其进行缓存。有的文件可能会不需要与服务器通讯,而从缓存中直接读取,或者可以放到CDN中