【Web && 网络】从点击到呈现 — 详解一次HTTP请求

转自:http://zrj.me/archives/589

从点击到呈现 — 详解一次HTTP请求(1)

其实想这个内容很久了,一方面是回顾自己所学的 http 知识,另外一方面也是分享吧,因为目前为止没有看到网络上有类似我想写的东西。

一般来说,很多的参考资料上面都会说,http 是一个基于请求/响应的工作模式,然后画出一张浏览器和服务器的 b/s 结构图,再画上两个箭头,表示请求和响应,应该说这么解释是易懂的,一般也是够清楚的,但是对于像我这样的考据癖来说,这么简单的解释显然是不够的,在我们点击一个网址,到它能够呈现在浏览器中,展示在我们面前,这个过程中,电脑里,网络上,究竟发生了什么事情,其实是很有意思的,也是值得去探究的。

于是我想要按照自己的理解把这个过程尽可能详细的剖析出来,在行文上我打算按照时间线的方式,这样又好写,也好读,好理解。这应该会写成一个系列,刚好最近开始在看很多人都在推荐的热门书,http 权威指南,这本,http://book.douban.com/subject/10746113/,跟我想要说的差不多的,于是就一边当作是读书笔记吧

那我们就开始了,故事其实并不是从在浏览器的地址栏输入一个网址,或者我们抓着鼠标点击一个链接开始,事情的开端要追溯到服务器启动监听服务的时候,在某个未知的时刻,一台机房里普普通通的刀片服务器,加上电,启动了操作系统,随着操作系统的就绪,服务器启动了 http 服务进程,这个 http 服务的守护进程,(daemon),可能是 apache,也可能是 nginx,不管怎么说,这个 http 服务进程开始定位到服务器上的 www 文件夹,一般是位于 /var/www ,然后启动了一些附属的模块,例如 php,或者,使用
fastcgi 方式连接到 php 的 fpm 管理进程,然后,向操作系统申请了一个 tcp 连接,然后绑定在了 80 端口,调用了 accept 函数,开始了默默的监听,监听着可能来自位于地球任何一个地方的请求,随时准备做出响应。

这个时候,典型的情况下,机房里面应该还有一个数据库服务器,或许,还有一台缓存服务器,如果对于流量巨大的网站,那么动态脚本的解释器可能还有单独的物理机器来跑,如果是中小的站点,那么,上述的各色服务,甚至都可能在一台物理机上,这些服务监听之间的关系,可以参考这里,再搭一次 Apache PHP MySQL 环境,不管怎么说,他们做好了准备,静候差遣。

从点击到呈现 — 详解一次HTTP请求(2)

上回说道服务器启动了监听服务,准备迎接来自客户机的请求。那么,我们以一次典型的浏览请求来解析:

当我们开始在浏览器中输入网址的时候,浏览器其实就已经在智能的匹配可能得 url 了,他会从历史记录,书签等地方,找到已经输入的字符串可能对应的 url,然后给出智能提示,对于 google chrome 那种变态的浏览器,他甚至会直接从缓存中把网页展示出来,就是说,你还没有按下 enter,页面就出来了,这个过于奇葩,我们不详细讲。

在例如输入了 baidu 或者 qq 之类的域名后,我们可以按下 ctrl + enter ,来自动补全,生成 qq.com 或者 baidu.com 的网址,然后发起请求。请求一旦发起,浏览器首先要做的事情就是解析这个域名,一般来说,浏览器会首先查看本地硬盘的 hosts 文件,看看其中有没有和这个域名对应的规则,如果有的话就直接使用 hosts 文件里面的 ip 地址,说道这里,大家可能想到,这个地方就存在安全隐患了,如果有病毒把一些常用的域名,修改 hosts  文件,指向一些恶意的 ip,那么浏览器也会不加判断的去连接,是的,这正是很多病毒的惯用手法。

如果在本地的 hosts 文件没有能够找到对应的 ip 地址,浏览器会向 dns 域名解析服务器发起域名解析请求,dns 的域名解析是递归的,(还有另外 dns 是迭代的),递归的 dns 首先会查看自己的 dns 缓存,如果缓存能够命中,那么就从缓存中把 ip 地址返回给浏览器,如果找不到对应的域名的 ip 地址,那么就向上转发请求,然后把得到的这个域名对应的 nameserver 的地址取得,再向这个 namserver 去请求域名对应的 ip,最后把这个 ip 地址返回给浏览器,不过怎么说,这个递归查询的过程,对于浏览器来说是透明的,他只要坐等
ip地址送回来就可以了。对于 DNS 的进一步理解,可以看这里,进一步理解 DNS

得到 ip 地址后,浏览器会开始构造一个 http 请求,一个典型的 http request header 一般需要包括请求的方法,例如 GET 或者 POST 等,不常用的还有 PUT 和 DELETE 方法,更加不常用的还有 HEAD 和 OPTION 以及 TRACE 方法,一般的浏览器只能发起 GET 或者 POST 请求,应用层的 http 请求准备好后,浏览器在传输层发起一条到达服务器的 tcp 连接,这个时候应该开始三次握手的过程,tcp 包被封装到网络层的 ip 包里面,ip 包再被封装到数据链路层的数据帧结构中,再通过物理层的比特流送出去,这些分层的意义在于分工合作,数据链路层通过
CSMA/CD 协议保证了相邻两台主机之间的数据报文传递,而网络层的 ip 数据包通过不同子网之间的路由器的路由算法和路由转发,保证了互联网上两台遥远主机之间的点对点的通讯,不过这种传输是不可靠,于是可靠性就由传输层的 tcp 协议来保证,tcp 通过慢开始,乘法减小等手段来进行流量控制和拥塞避免,同时提供了两台遥远主机上进程到进程的通信,最终保证了 http 的请求头能够被远方的服务器上正在监听的 http 服务器进程收到,终于,数据包在跳与跳之间被拆了又封装,在子网与子网之间被转发了又转发,最后进入了服务器的操作系统的缓冲区,服务器的操作系统由此给正在被阻塞住的
accept 函数一个返回,将他唤醒。

从点击到呈现 — 详解一次HTTP请求(3)

请求进入服务器之后,服务器上的的 http 监听进程会得到这个请求,然后一般情况下会启动一个新的子进程去处理这个请求,同时父进程继续监听。http 服务器首先会查看重写规则,然后如果是文件真实存在,例如一些图片,或者 css js 等的静态文件,就会直接把这个文件返回,如果是一个动态的请求,那么会根据 url 重写模块的规则,把这个请求重写到一个 rest 风格的 url 上,然后根据动态语言的脚本,来决定调用什么类型的动态文件脚本解释器来处理这个请求。

我们以 php 语言为例来说的话,请求到达一个 php 的 mvc 框架之后,框架首先应该会初始化一些环境的参数,例如远端 ip,请求参数等等,然后根据请求的 url 送到一个路由器类里面去匹配路由,路由由上到下逐条匹配,一旦遇到 url 能够匹配的上,而且请求的方法也能够命中的话,那么请求就会由这个路由所定义的处理方法去处理。

请求进入处理函数之后,如果客户端所请求需要浏览的内容是一个动态的内容,那么处理函数会相应的从数据源里面取出数据,这个地方一般会有一个缓存,例如 memcached 来减小 db 的压力,如果引入了 orm 框架的话,那么处理函数直接向 orm 框架索要数据就可以了,由 orm 框架来决定是使用内存里面的缓存还是从 db 去取数据,一般缓存都会有一个过期的时间,而 orm 框架也会在取到数据回来之后,把数据存一份在内存缓存中的。

orm 框架负责把面向对象的请求翻译成标准的 sql 语句,然后送到后端的 db 去执行,db 这里以 mysql 为例的话,那么一条 sql 进来之后,db 本身也是有缓存的,不过 db 的缓存一般是用 sql 语言 hash 来存取的,也就是说,想要缓存能够命中,除了查询的字段和方法要一样以外,查询的参数也要完全一模一样才能够使用 db 本身的查询缓存,sql 经过查询缓存器,然后就会到达查询分析器,在这里,db 会根据被搜索的数据表的索引建立情况,和 sql 语言本身的特点,来决定使用哪一个字段的索引,值得一提的是,即使一个数据表同时在多个字段建立了索引,但是对于一条
sql 语句来说,还是只能使用一个索引,所以这里就需要分析使用哪个索引效率最高了,一般来说,sql 优化在这个点上也是很重要的一个方面。

sql 由 db 返回结果集后,再由 orm 框架把结果转换成模型对象,然后由 orm 框架进行一些逻辑处理,把准备好的数据,送到视图层的渲染引擎去渲染,渲染引擎负责模板的管理,字段的友好显示,也包括负责一些多国语言之类的任务。对于一条请求在 mvc 中的生命周期,可以参考这里,临摹了一个 PHP MVC 框架,在视图层把页面准备好后,再从动态脚本解释器送回到 http 服务器,由 http 服务器把这些正文加上一个响应头,封装成一个标准的 http 响应包,再通过 tcp ip 协议,送回到客户机浏览器。

从点击到呈现 — 详解一次HTTP请求(4)

历经千辛万苦,我们请求的响应终于到达了客户端的浏览器,响应到达浏览器之后,浏览器首先判断状态码,如果是 200 开头的就好办,直接进入渲染流程,如果是 300 开头的就要去相应头里面找 location 域,根据这个 location 的指引,进行跳转,这里跳转需要开启一个跳转计数器,是为了避免两个或者多个页面之间形成的循环的跳转,当跳转次数过多之后,浏览器会报错,同时停止。如果是 400 开头或者 500 开头的状态码,浏览器也会给出一个错误页面。

当浏览得到一个正确的 200 响应之后,接下来面临的一个问题就是多国语言的编码解析了,响应头是一个 ascii 的标准字符集的文本,这个还好办,但是响应的正文本质上就是一个字节流,对于这一坨字节流,浏览器要怎么去处理呢,首先浏览器会去看响应头里面指定的 encoding 域,如果有了这个东西,那么就按照指定的 encoding 去解析字符,如果没有的话,那么浏览器会使用一些比较智能的方式,去猜测和判断这一坨字节流应该使用什么字符集去解码。相关的笔记可以看这里,浏览器对编码的确定

解决了字符集的问题,接下来就是构建 dom 树了,在 html 语言嵌套正常而且规范的情况下,这种 xml 标记的语言是比较容易的能够构建出一棵 dom 树出来的,当然,对于互联网上大量的不规范的页面,不同的浏览器应该有自己不同的容错去处理。构建出来的 dom 本质上还是一棵抽象的逻辑树,构建 dom 树的过程中,如果遇到了由 script 标签包起来的 js 动态脚本代码,那么会把代码送到 js 引擎里面去跑,如果遇到了 style 标签包围起来的 css 代码,也会保存下来,用于稍后的渲染。如果遇到了 img
等引用外部文件的标签,那么浏览器会根据指定的 url 再次发起一个新的 http 请求,去把这个文件拉取回来,值得一提的是,对于同一个域名下的下载过程来说,浏览器一般允许的并发请求是有限的,通常控制在两个左右,所以如果有很多的图片的话,一般出于优化的目的,都会把这些图片使用一台静态文件的服务器来保存起来,负责响应,从而减少主服务器的压力。

dom 树构造好了之后,就是根据 dom 树和 css 样式表来构造 render 树了,这个才是真正的用于渲染到页面上的一个一个的矩形框的树,对于 render 树上每一个框,需要确定他的 x y 坐标,尺寸,边框,字体,形态,等等诸多方面的东西,render 树一旦构建完成,整个页面也就准备好了,可以上菜了。

需要说明的是,下载页面,构建 dom 树,构建 render 树这三个步骤,实际上并不是严格的先后顺序的,为了加快速度,提高效率,让用户不要等那么久,现在一般都并行的往前推进的,现代的浏览器都是一边下载,下载到了一点数据就开始构建 dom 树,也一边开始构建 render 树,构建了一点就显示一点出来,这样用户看起来就不用等待那么久了。

时间: 2024-07-31 14:26:59

【Web && 网络】从点击到呈现 — 详解一次HTTP请求的相关文章

华为网络技术大赛模拟题答案详解

华为网络技术大赛模拟题答案详解 [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article/details/51354514 一.判断题 (1)VLSM的作用是:在有类的IP地址基础上,从主机位部分划分出相应的位数做为网络位.但是在路由器上部署时,需要路由协议的支持. [解释]对,VLSM=Variable Length Subnet Mask,可变长子网掩码 (2)有效的沟通是任何组织和任何项目的基础,项目经理可以花90%或者更多的时间在沟通这方

(总结)Web性能压力测试工具之WebBench详解

PS:在运维工作中,压力测试是一项很重要的工作.比如在一个网站上线之前,能承受多大访问量.在大访问量情况下性能怎样,这些数据指标好坏将会直接影响用户体验.但是,在压力测试中存在一个共性,那就是压力测试的结果与实际负载结果不会完全相同,就算压力测试工作做的再好,也不能保证100%和线上性能指标相同.面对这些问题,我们只能尽量去想方设法去模拟.所以,压力测试非常有必要,有了这些数据,我们就能对自己做维护的平台做到心中有数. Webbench是知名的网站压力测试工具,它是由Lionbridge公司(h

构建安全的Xml Web Service系列之wse之错误代码详解

原文:构建安全的Xml Web Service系列之wse之错误代码详解 WSE3.0现在还没有中文版的可以下载,使用英文版的过程中,难免会遇到各种各样的错误,而面对一堆毫无头绪的错误异常,常常会感到迷茫和无助,我起初就觉得这个框架使用起来真的很难用,有时候一些莫名其妙的错误会困扰我半天或者几天的时间,而一些英文的解释理解起来似是而非,很难一针见血的获得哪里出了问题,我将用几篇文章将wse所涉及到的错误代码和错误消息翻译成中文,并且根据个人的一点经验,提出引发错误的原因,并总结出解决建议,不是很

Linux网络编程——进程池实现过程详解(1)

目录 进程池 父进程的实现流程 子进程的实现流程 进程池 父进程的实现流程 1.定义数据结构pChild,申请子进程数目的结构体空间 2.通过循环,socketpair创建全双工管道,创建子进程,将子进程pid,管道对端,是否忙碌等信息存储 3.socket,bind,listen,对应的端口处于监听状态 netstat 4.epoll_create创建epfd,监控socketFd和所有子进程的管道对端 5.while(1)循环 epoll_wait等待客户端的请求及子进程是否有通知 如果so

好程序员web前端学习路线大厂面试题详解

好程序员web前端学习路线大厂面试题详解,依赖注入原理 手动实现依赖注入实现方式 - var _global={ - ajax:function(){//申明服务,也可以说是内部类 - this.get=function(){ - //todo:get方式请求数据 - console.log(“get is success”); - }; - This.post=function(){ - //todo:post方式请求数据 - console.log(“post is success”); -

Web 性能压力测试工具之 Siege 详解

Siege是一款开源的压力测试工具,设计用于评估WEB应用在压力下的承受能力.可以根据配置对一个WEB站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访问下重复进行.siege可以从您选择的预置列表中请求随机的URL.所以siege可用于仿真用户请求负载,而ab则不能.但不要使用siege来执行最高性能基准调校测试,这方面ab就准确很多. Siege官网:http://www.joedog.org/Siege下载:http://www.joedog.org/pub

Linux 网络编程三(socket代码详解)

//网络编程客户端 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h>//htons()函数头文件 #include <ne

IOS研究之网络编程(二)-Cocoa Streams使用详解

 本文以及相关的系列文章是我总结的iOS网络开发方面的知识点,本文是第二篇,主要分析了Cocoa Streams中的几个重要类 Cocoa Streams实际上是Objective-C对CFNetwork的简单封装,主要包含了三个类:NSStream, NSInputStream, and NSOutputStream.本部分的接口接口比较简单,用法一目了然.我在这里就只列出接口,方便查阅.对CFNnework不明白的看IOS研究之网络编程(一)-CFNetwork使用详解 NSStream

基于Nginx搭建Web服务器及虚拟主机相关配置详解

随着计算机与Internet技术的高速发展,各种各样的Web站点也就成为面向用户的中坚力量.在各种网站服务器软件中.除了Apache外,还有一款轻量级的HTTP服务器软件--Nginx. 基于Apache搭建Web服务器可以参考博文:基于Apache搭建Web服务器详解 一.Nginx服务简介 Nginx由俄罗斯的lgor Sysoev开发,专为性能优化而开发,其最知名的优点就是它的稳定性和低系统资源消耗.以及对HTTP并发连接的高处立能力(单台物理服务器可支持30000~50000个并发请求)