( 6)前端页面渲染在浏览器接收到服务器返回的前端页面之后,即开始服务器的渲染过程。渲染主要包含了 HTML DOM 解析与 CSS 解析以及最终的页面渲染这几个部分。HTML 解析器的主要工作是对 HTML 文档进行解析,生成解析树。解析树是以 DOM 元素以及属性为节点的树。DOM 是文档对象模型 (Document Object Model) 的缩写,它是 HTML 文档的对象表示,同时也是 HTML 元素面向外部 ( 如 Javascript) 的接口。树的根部是 "Document" 对象。整个 DOM 和 HTML 文档几乎是一对一的关系。
(1)当我们在浏览器内输入某个地址后,经历了如下的步骤网页才最终呈现在我们面前:( 1)URL 解析浏览器会首先判断你输入的是否为有效的 URL,还是属于需要传输给搜索引擎的默认搜索关键字。并且浏览器还会检查自带的 “ 预加载 HSTS(HTTP 严格传输安全)” 列表,这个列表里包含了那些请求浏览器只使用 HTTPS 进行连接的网站。如果网站在这个列表里,浏览器会使用 HTTPS 而不是 HTTP 协议,否则,最初的请求会使用 HTTP 协议发送。接下来,浏览器会检查你的输入字符是否含有特殊非 ASCII 关键字,如果含有特殊的字符会进行 UTF8 编码,部分特殊的网站会要求进行 GBK 编码。
( 2)DNS 解析在 URL 解析完成之后,浏览器会根据本地的 hosts 文件或者向本机 / 网关设置的 DNS 服务器发起域名解析请求。DNS 的解析过程中,浏览器会首先检查本机是否有域名缓存,如果没有的话会向 DNS 服务器发起请求,如果子 DNS 服务器不存在该记录则会递归向父层级的 DNS 发起请求。我之前在进行 iOS 开发的时候还碰到 IPV6 的问题,即苹果要求 iOS 应用能够在 IPV6 环境下正常运行,那么这个时候 DNS 服务器发现如果你请求的是 IPV6 的地址,对于仅有 IPV4 地址的服务器其会提供一个 NAT64 的功能,即保证客户端虽然为 IPV6 地址,也能和 IPV4 的服务器正常通信。
( 3)TCP/IP 协议传输
当浏览器得到了目标服务器的 IP 地址,以及 URL 中给出来端口号(http 协议默认端口号是 80, https 默认端口号是 443),它会调用系统库函数 socket ,请求一个 TCP 流套接字,对应的参数是 AF_INET/AF_INET6 和 SOCK_STREAM 。TCP 的建立连接与关闭连接分别是三次握手与四次握手,概述如下 :
第一次握手:Client 将标志位 SYN 置为 1,随机产生一个值 seq=J,并将该数据包发送给 Server,Client 进入 SYN_SENT 状态,等待 Server 确认。 2. 第二次握手:Server 收到数据包后由标志位 SYN=1 知道 Client 请求建立连接,Server 将标志位 SYN 和 ACK 都置为 1,ack=J+1 ,随机产生一个值 seq=K,并将该数据包发送给 Client 以确认连接请求,Server 进入 SYN_RCVD 状态。 3. 第三次握手:Client 收到确认后,检查 ack 是否为 J+1,ACK 是否为 1,如果正确则将标志位 ACK 置为 1,ack=K+1 ,并将该数据包发 送给 Server,Server 检查 ack 是否为 K+1,ACK 是否为 1,如果正确则连接建立成功,Client 和 Server 进入 ESTABLISHED 状态,完成三次握手,随后 Client 与 Server 之间可以开始传输数据了。 关闭连接时 : 1. 第一次挥手:Client 发送一个 FIN,用来关闭 Client 到 Server 的数据传送,Client 进入 FIN_WAIT_1 状态。 2. 第二次挥手:Server 收到 FIN 后,发送一个 ACK 给 Client,确认序号为收到序号 +1(与 SYN 相同,一个 FIN 占用一个序号), Server 进入 CLOSE_WAIT 状态。 3. 第三次挥手:Server 发送一个 FIN,用来关闭 Server 到 Client 的数据传送,Server 进入 LAST_ACK 状态。 4. 第四次挥手:Client 收到 FIN 后,Client 进入 TIME_WAIT 状态,接着发送一个 ACK 给 Server,确认序号为收到序号 +1,Server 进入 CLOSED 状态,完成四次挥手。
( 4)HTTP 请求封装
通常 HTTP 消息包括客户机向服务器的请求消息和服务器向客户机的响应消息。这两种类型的消息由一个起始行,一个或者多个头域,一个只是头域结束的空行和可选的消息体组成。HTTP 的头域包括通用头,请求头,响应头和实体头四个部分。每个头域由一个域名,冒号(: )和域值三部分组成。域名是大小写无关的,域值前可以添加任何数量的空格符,头域可以被扩展为多行,在每行开始处,使用至少一个空格或制表符。
我们在正常的开发中主要会遵循 RESTful 风格,即主要以 GET、POST 、 PUT、DELETE 这四个动词进行请求,每个动词表征不同的含义,同时会将资源名与资源编号放在 URL 中增加可读性。
( 5)Nginx/Apache 中间件服务器在 Nginx、Apache 服务器中,可以配置 Virtual Host,即根据不同的域名指向不同的目录。而对于 PHP 这样需要动态处理的请求,会考虑使用 FastCGI 进行处理。同时,在服务器中我们也往往需要进行 XSS/SQLInjection/CSRF 等常见的网络攻击手段的防护。
( 6)前端页面渲染在浏览器接收到服务器返回的前端页面之后,即开始服务器的渲染过程。渲染主要包含了 HTML DOM 解析与 CSS 解析以及最终的页面渲染这几个部分。HTML 解析器的主要工作是对 HTML 文档进行解析,生成解析树。解析树是以 DOM 元素以及属性为节点的树。DOM 是文档对象模型 (Document Object Model) 的缩写,它是 HTML 文档的对象表示,同时也是 HTML 元素面向外部 ( 如 Javascript) 的接口。树的根部是 "Document" 对象。整个 DOM 和 HTML 文档几乎是一对一的关系。
通过遍历 DOM 节点树创建一个 “Frame 树 ” 或 “ 渲染树 ”,并计算每个节点的各个 CSS 样式值 2. 通过累加子节点的宽度,该节点的水平内边距 (padding)、边框 (border) 和外边距 (margin),自底向上的计算 "Frame 树 " 中每个节点首的选 (preferred) 宽度 3. 通过自顶向下的给每个节点的子节点分配可行宽度,计算每个节点的实际宽度 4. 通过应用文字折行、累加子节点的高度和此节点的内边距 (padding)、边框 (border) 和外边距 (margin),自底向上的计算每个节点的高度 5. 使用上面的计算结果构建每个节点的坐标 6. 当存在元素使用 floated,位置有 absolutely 或 relatively 属性的时候,会有更多复杂的计算,详见http://dev.w3.org/csswg/css2/ 和 http://www.w3.org/Style/CSS/current-work 创建 layer( 层 ) 来表示页面中的哪些部分可以成组的被绘制,而不用被重新栅格化处理。每个帧对象都被分配给一个层页面上的每个层都被分配了纹理 (?) 7. 每个层的帧对象都会被遍历,计算机执行绘图命令绘制各个层,此过程可能由 CPU 执行栅格化处理,或者直接通过 D2D/SkiaGL 在 GPU 上绘制 8. 上面所有步骤都可能利用到最近一次页面渲染时计算出来的各个值,这样可以减少不少计算量计算出各个层的最终位置,一组命令由 Direct3D/OpenGL 发出,GPU 命令缓冲区清空,命令传至 GPU 并异步渲染,帧被送到 Window Server。 ( 7)Ajax 数据获取我在前端开发中是习惯使用 fetch 库进行数据抓取,同时如果涉及到跨域数据抓取,还会使用 JSONP 跨域或者 CORS 跨域请求协议。