继续书接上文,处理函数向orm框架索要数据,orm框架需要从db获取数据时,orm 框架负责把面向对象的请求翻译成标准的 sql 语句,然后送到后端的 db 去执行,db 这里以 mysql 为例的话,那么一条 sql 进来之后,db 本身也是有缓存的,不过 db 的缓存一般是用 sql 语言 hash 来存取的,也就是说,想要缓存能够命中,除了查询的字段和方法要一样以外,查询的参数也要完全一模一样才能够使用 db 本身的查询缓存,sql 经过查询缓存器,然后就会到达查询分析器,在这里,db 会根据被搜索的数据表的索引建立情况,和 sql 语言本身的特点,来决定使用哪一个字段的索引,值得一提的是,即使一个数据表同时在多个字段建立了索引,但是对于一条 sql 语句来说,还是只能使用一个索引,所以这里就需要分析使用哪个索引效率最高了,一般来说,sql 优化在这个点上也是很重要的一个方面。
sql 由 db 返回结果集后,再由 orm 框架把结果转换成模型对象,然后由 orm 框架进行一些逻辑处理,把准备好的数据,送到视图层。在视图层把页面准备好好后,再从动态脚本解释器送回到http服务器,由http服务器把这些正文加上一个响应头,封装成一个标准的http响应包,再通过tcp ip协议,送回到客户机浏览器。
断点1: HTTP响应格式
http响应格式与http请求格式非常类似,也有三部分组成:状态行,消息报头和响应正文。
<status-line>
<headers>
<blank line>
[<response-body>]
状态行(Response Line):
HTTP-Version Status-Code Reason-Phrase
其中,Reason-Phrase表示状态代码的文本描述。状态码一般由三位数字组成,第一个数字定义了响应类别,含义如下:
1xx:指示信息--表示请求已接收,继续处理。
2xx:成功--表示请求已被成功接收、理解、接受。
3xx:重定向--要完成请求必须进行更进一步的操作。
4xx:客户端错误--请求有语法错误或请求无法实现。
5xx:服务器端错误--服务器未能实现合法的请求。
常见状态代码、状态描述的说明如下。
200 OK:客户端请求成功。
400 Bad Request:客户端请求有语法错误,不能被服务器所理解。
401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用。
403 Forbidden:服务器收到请求,但是拒绝提供服务,服务器通常会在响应正文中给出不提供服务的原因。
404 Not Found:请求资源不存在,举个例子:输入了错误的URL。
500 Internal Server Error:服务器发生不可预期的错误,导致无法完成客户端请求。
503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常。
消息报头:
同样与请求头类型,每一个报头域都由名字+“:”+空格+值 组成,消息报头域的名字是大小写无关的,常见消息报头域名:
Content-Location:为了让客户端重定向到这个页面新的位置,服务器端可以发回Location响应报头后使用重定向语句,让客户端去访问新的域名所对应的服务器上的资源;
Connection:不同于http1.0, http 1.1使用持久连接,就是服务器在发送响应后仍然在一段时间内保持这条连接,允许在同一个连接中存在多次数据请求和响应,keep_alive有效时间服务器配置参数自行决定;
WWW-Authenticate:WWW-Authenticate响应报头域必须被包含在401 (未授权的)响应消息中,这个报头域和前面讲到的Authorization 请求报头域是相关的,当客户端收到 401 响应消息,就要决定是否请求服务器对其进行验证。如果要求服务器对其进行验证,就可以发送一个包含了Authorization 报头域的请求;
Cache-Control:缓存指令:no_cahe,no_store, no_tansform, max_age......,缓存指令为单向的,且是独立的(一个消息的缓存指令不会影响另一个消息处理的缓存机制),HTTP1.0使用的类似的报头域为Pragma。
响应正文:
服务器端返回给客户端的数据:html,j s 。。。。
继续,封装好的标准的http响应包,通过tcp ip协议,历经千辛万苦,终于送回到了客户机浏览器。响应达到浏览器后,浏览器首先判断状态码,如果是200,bing go直接进入渲染过程;如果是3xx开头则去响应头找到location域,根据location的指引进行跳转,这里的跳转需要开启一个跳转计数器,是为了避免两个页面或是多个页面间形成循环跳转,当跳转次数过多之后,浏览器会报错,同时停止。如果是400或500开头的状态码,浏览器也会给出一个错误页面。
当浏览器得到一个200响应后,接下来的面临的问题就是多国语言语言的编码解析了,响应头是一个ascii的标准字符集文本,但响应的正文本质是一个字节流,对于这一坨字节流,浏览器会首先看响应头中指定的encoding域,并按照指定的encoding解析字符;如果响应头中没有encoding域,浏览器会使用一些比较智能的方式【😯,这么神奇?】,去猜测和判断这一坨字节流应该使用什么字符集解码。
断点2: HTTP Transfer_Encoding
"对于这一坨字节流,浏览器将根据响应头中指定的encoding域解析字符。。。。。。"那么问题来了,响应头中的encoding域有Transfer-Encoding和Content-Encoding吧,具体根据那个累,二者有什么关系吗?
为了解释Transfer-Encoding,事情得娓娓道来:HTTP 1.1协议响应头中默认Keep-Alive,使得浏览器可以避开缓慢的三次握手、避免遇上TCP慢启动的拥塞适应阶段等,从而重用已开的空闲持久连接,这听起来十分美妙。但问题也随之而来,对于非持久连接,浏览器可以通过连接是否关闭来界定响应实体的边界;但对于持久性型连接,显然是行不通了,那么浏览器何时才能知道此连接是否仍有数据发送呢,难道只能痴痴的等?
要解决上面这个问题,你是不是想到了利用响应头中的Content-Length域判断响应实体已结束?但Content-Length反映的是真实响应实体长度,在实际应用中,有些响应实体的长度并不易获得,比如实体内容由动态语言生成,如果要晓得实体长度,只能开一个足够大的Buffer等内容全部生成好后再计算【内存开销,TTFB(Time To First Byte)】,毕竟Content-Length位于响应头中啊!。。。。。我们需要一种不依赖Content-Length获取响应正文的边界的机制。
Transfer-Encoding正是为此而来:Transfer-Encoding【传输编码】,目前最新的HTTP规范里,只定义了一种传输编码方式——分块编码(chunked)。
分块编码相当简单,即将报文实体拆分为一系列的分块来传输:
在头部加入Transfer-Encoding: chunked之后,就代表这个报文采用了分块编码。这时,报文中的实体需要改为用一系列分块来传输。每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包含其自身行结尾的 CRLF(\r\n)以及分块数据结尾的 CRLF;最后一个分块数据长度值须为0,对应的分块数据没有内容,表述实体结束,据某个大神用Node的net模块创建了一个 TCP Server的测试页可供参考,其形式大致如下:
知晓了Transfer-Encoding的来龙去脉,我们接着说Encoding域的另一个相关字段:Content-Encoding(内容编码)。Content-Encoding,顾名思义,就是对响应正文进行压缩编码,目的是为了优化传输,比如gzip——压缩文本文件。
Content-Encoding 和 Transfer-Encoding 二者是相辅相成,结合使用的,即对Transfer-Encoding的分块再进行Content-Encoding,比如某大神telnet请求测试页面得到的http响应包:
详情可参见:https://imququ.com/post/transfer-encoding-header-in-http.html
解决了字符集的问题,接下来就是构建dom树了【浏览器内核引擎如何构建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 树,构建了一点就显示一点出来,这样用户看起来就不用等待那么久了。