问题引入
请求如下地址:github.com/flyingfox01…。浏览器到底发生了什么?
从浏览器提供的两个工具入手可以获取一些信息:网络调试工具、性能分析API。
chrome devtool network
查看Chrome的network,可以看到到底经过了些什么阶段。
详细解释可以参考:timing-explanation
- 资源调度
-
- 排队(Queueing): 在请求队列中的时间。
-
-
- 有更高优先级
- 多于6个并发请求数(HTTP/1.0、HTTP/1.1)
- 浏览器在磁盘缓存中短暂分配空间
-
- 连接开始
-
- 停滞不前(Stalled): 请求可能因排队中描述的任何原因而停止。
- 代理协商(Proxy negotiation): 与代理服务器连接进行协商所花费的时间。
- DNS查找(DNS Lookup): 解析域名对应的IP地址。
- 初始连接(Initial Connection / Connecting): 建立连接所花费的时间,包括TCP握手/重试和协商SSL。
- SLL握手(Secure Sockets Layer, SSL): 完成SSL握手所花费的时间。
- 请求响应
-
- ServiceWorker 准备:浏览器正在启动服务工作者。
- 对ServiceWorker 的请求:请求正在发送给服务工作者。
- 请求发送(Request sent:) 发出网络请求所花费的时间,通常为一毫秒的时间。
- 等待(TTFB) (Waiting[TFFB]): 浏览器正在等待响应的第一个字节。TTFB 代表 Time To First Byte。此时间包括 1 次往返延迟和服务器准备响应所用的时间。
- 内容下载(Content Download): 接收响应数据所花费的时间。
- 接收推送:浏览器正在通过 HTTP/2 服务器推送接收此响应的数据。
- 阅读推送:浏览器正在读取之前接收到的本地数据。
performance.timing
详细可以参考w3.org的图解:
下面以这两个信息为主对浏览器输入网址后的行为做详细介绍:
1. URL解析和缓存
1.1 URL解析
6个组成部分,例如:
- 协议(必选):https
- 主机(必选):github.com
- 端口:443
- 路径:flyingfox01/design-pattern-in-typescript/search
- 查询参数:?q=singleton&type=commits
- 锚点:#test
如不是URL,则会用浏览器默认的搜索引擎搜索该字符串。
1.2 卸载页面(Prompt for unload)
先卸载原来页面,释放占用的内存。
1.3 重定向(redirect)
因为浏览器可能记录本机的地址已经永久跳转成新的地址,所以一开始浏览器就先要判断下需不需要重定向,以及重定向到哪里;
1.4 应用缓存(App cache)
根据是否需向服务器发起HTTP请求,将缓存过程划分为两个部分:强缓存和协商缓存,强缓存优先于协商缓存。
- 强缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。
- 协商缓存,让客户端与服务器之间能实现缓存文件是否更新的验证、提升缓存的复用率,将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。
HTTP缓存都是从第二次请求开始的:
- 第一次请求资源时,服务器返回资源,并在response header中回传资源的缓存策略;
- 第二次请求时
-
- 浏览器判断这些请求参数,击中强缓存就直接200(from cache)。from memory cache 和 from disk cach
- 否则就把请求参数加到request header头中传给服务器,看是否击中协商缓存,击中则返回304(not modified)
- 否则服务器会返回新的资源。
| http头 | http状态码 | 规则 | 解释 |
|---|---|---|---|
| Cache-Control | 200(from cache) | 未失效,强制使用本地缓存 | 到了HTTP/1.1,Expires已经被Cache-Control替代(解决前后端时间不一致的问题)(1)public:所有内容都将被缓存(客户端和代理服务器都可缓存)(2)private:所有内容只有客户端可以缓存,Cache-Control的默认取值(3)no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定(4)no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存(5)max-age=xxx (xxx is numeric) :缓存内容将在xxx秒后失效 |
| Expires | 200(from cache) | 未失效,强制使用本地缓存 | HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求的结果缓存的到期时间,即再次发送请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。 |
| Etag | 304(not modified) | 未失效,或未f5刷新,协商使用本地缓存 | Etag是服务器响应请求时,当前资源文件的一个唯一标识(由服务器生成)。If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求对比后返回304/200。 |
| Last-Modified | 304(not modified) | 未失效,或未f5刷新,协商使用本地缓存 | Last-Modified是服务器响应请求时,返回该资源文件在服务器最后被修改的时间。If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求对比后返回304/200。 |
| 200 | 后端资源 |
哪些请求不能被缓存?
1、HTTP 信息头中包含 Cache-Control:no-cache,pragma:no-cache,或 Cache-Control:max-age=0 等告诉浏览器不用缓存的请求
2、需要根据 Cookie,认证信息等决定输入内容的动态请求是不能被缓存的
3、经过 HTTPS 安全加密的请求
4、POST 请求无法被缓存
5、HTTP 响应头中不包含 Last-Modified/Etag,也不包含 Cache-Control/Expires 的请求无法被缓存
2 网络部分
先介绍下5层网络协议:
| 五层网络协议 | 协议 | 作用 |
|---|---|---|
| 应用层 | DNS,HTTP | DNS解析成IP并发送http请求 |
| 传输层 | TCP,UDP | 建立TCP连接(3次握手) |
| 网络层 | IP,ARP | IP寻址 |
| 数据链路层 | PPP | 封装成帧 |
| 物理层 | 利用物理介质传输比特流 | 物理传输(通过双绞线,电磁波等各种介质) |
客户端请求:
- 从应用层发动http请求
- 到传输层通过三次握手建立tcp/ip连接
- 再到网络层的ip寻址
- 再到数据链路层的封装成帧
- 最后在物理层通过物理介质传输。
服务端响应:反之。
2.1 DNS域名解析
DNS服务器( Domain Name System,域名解析系统)会根据用户提供的域名查找对应的IP地址。
域名解析服务器是基于UDP协议实现的一个应用程序,通常通过监听53端口来获取客户端的域名解析请求。
DNS查找过程为:
- 浏览器首先搜索浏览器自身缓存的DNS记录。 浏览器自身也带有一层DNS缓存,Chrome
缓存1000条DNS解析结果,缓存时间大概在一分钟左右。(Chrome浏览器通过输入:chrome://net-internals/#dns打开DNS缓存页面)
-
如果浏览器缓存中没有找到需要的记录或记录已经过期,则搜索hosts文件和操作系统缓存。在Windows操作系统中,可以通过 ipconfig /displaydns命令查看本机当前的DNS缓存。通过hosts文件,你可以手动指定一个域名和其对应的IP解析结果,并且该结果一旦被使用,同样会被`缓存到操作系统缓存中。Windows系统的hosts文件在C:\Windows\System32\drivers\etc下,linux系统的hosts文件在/etc/hosts下。
-
如果在hosts文件和操作系统缓存中没有找到需要的记录或记录已经过期,则向域名解析服务器发送解析请求。其实第一台被访问的域名解析服务器就是我们平时在设置中填写的DNS服务器一项,当操作系统缓存中也没有命中的时候,系统会向DNS服务器正式发出解析请求。这里是真正意义上开始解析一个未知的域名。一般一台域名解析服务器会被地理位置临近的大量用户使用(特别是ISP的DNS),一般常见的网站域名解析都能在这里命中。
-
如果域名解析服务器也没有该域名的记录,则开始递归+迭代解析。
-
获取域名对应的IP后,一步步向上返回,直到返回给浏览器。
2.2 建立TCP连接
连接建立时会有三次握手,连接释放时会有四次挥手。通过抓包工具WireShark可以看到具体情况。
建立连接的方式-客户服务器方式
- TCP 连接的建立都是采用CS(client - server)客户服务器方式
- 主动发起连接建立的应用进程叫做客户(client)
- 被动等待连接建立的应用进程叫做服务器(server)
建立连接过程中要解决以下三个问题:
-
要使每一方能够确知对方的存在
-
要允许双方协商一些参数(如最大报文段长度,最大窗口大小,服务质量等)
-
能够对运输实体资源(如缓存大小,连接表中的项目等)进行分配
TCP报文头部格式
2.3 发起HTTP请求
发送HTTP请求的过程就是构建HTTP请求报文并通过TCP协议发送到服务器指定端口(HTTP协议80/8080, HTTPS协议443)
HTTP协议:超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议,所有的WWW文件都必须遵守这个标准
HTTP是应用层协议,也是一个无状态的协议,它的任务是与服务器交换信息,至于怎么连到服务器,怎么保证数据正确,HTTP不管(连接是否可靠,数据是否正确,是否丢包的任务是由TCP协议负责的),它总是假设数据是正确地传输的
HTTP协议通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS
HTTP请求报文是由三部分组成::请求行、请求头和请求体。 HTTP报文头格式
请求行
请求行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本
请求报头
请求报头允许客户端向服务器传递请求的附加信息和客户端自身的信息。常见的请求报头有
| 名称 | 解释 |
|---|---|
| Accept | 接受类型,表示浏览器支持的MIME类型 |
| Accept-Encoding | 浏览器支持的压缩类型,如gzip等,超出类型不能接收 |
| Content-Type | 客户端发送出去实体内容的类型 |
| Cache-Control | 指定请求和响应遵循的缓存机制 |
| If-Modifyied-Since | 用来匹配文件是否有变动 |
| If-None-Match | 用来匹配文件内容是否改变 |
| cookie | 有cookie并且同域访问时会自动带上 |
| Connection | 当浏览器与服务器通信时对于长连接如何进行处理,如keep-alive |
| Origin | 最初的请求是从哪里发起的 |
| Host | 请求服务器的url |
| Referer | 该页面的来源URL |
| User-Agent | 用户代理:记录用户的浏览器类型、操作系统及版本、CPU 类型、浏览器渲染引擎、浏览器语言、浏览器插件等信息 |
请求体
| Content-Type | 解释 | 在postman中等价于 |
|---|---|---|
| application/x-www-form-urlencoded | 能传输文本(不能传输数据流,文件)的键值对,不能用来上传二进制数据流,比如图片上传等 | x-www-form-urlencoded |
| multipart/form-data | 能传输数据流(上传)的键值对,既可以上传(多个)文件等二进制数据,也可以上传表单键值对 | form-data |
| application/json | 告诉服务端消息主体是序列化的JSON字符串 | raw中的json |
| text/xml | 是一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范 | raw中的text/xml |
| application/octet-stream | 只可以上传二进制数据,通常用来上传文件,由于没有键值,所以,一次只能上传一个文件 | binary |
2.4 HTTP响应
从服务器接收请求到对应后台接收到请求,内部会有很多处理,包括:负载均衡和后台处理
负载均衡
对于大型项目,并发访问很大,一台服务器吃不消,一般会有若干台服务器组成一个集群,然后配合反向代理实现均衡负载。均衡负载不止一种实现方式。
概括的说:用户发送的请求指向调度服务器(反向代理服务器,比如nginx的均衡负载),然后调度服务器根据实际的调度算法,分配不同的请求给对应的集群中的服务器执行,然后调度服务器等待实际服务器的HTTP响应,并且反馈给用户。
后台处理(略)
下面是个真实的响应报文,包含响应行、响应头、响应体三部分组成
响应行
响应行由报文协议及版本和状态码及描述组成,如:HTTP/1.1 200 OK
HTTP状态码介绍:
| 状态码 | 英文名称 | 描述 |
|---|---|---|
| 100 | Continue | 继续。客户端应继续其请求 |
| 101 | Switching Protocols | 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 |
| 200 | OK | 请求成功。一般用于GET与POST请求 |
| 201 | Created | 已创建。成功请求并创建了新的资源 |
| 202 | Accepted | 已接受。已经接受请求,但未处理完成 |
| 203 | Non-Authoritative Information | 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 |
| 204 | No Content | 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 |
| 205 | Reset Content | 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 |
| 206 | Partial Content | 部分内容。服务器成功处理了部分GET请求 |
| 300 | Multiple Choices | 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 |
| 301 | Moved Permanently | 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 |
| 302 | Found | 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI |
| 303 | See Other | 查看其它地址。与301类似。使用GET和POST请求查看 |
| 304 | Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
| 305 | Use Proxy | 使用代理。所请求的资源必须通过代理访问 |
| 306 | Unused | 已经被废弃的HTTP状态码 |
| 307 | Temporary Redirect | 临时重定向。与302类似。使用GET请求重定向 |
| 400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
| 401 | Unauthorized | 请求要求用户的身份认证 |
| 402 | Payment Required | 保留,将来使用 |
| 403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
| 404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 |
| 405 | Method Not Allowed | 客户端请求中的方法被禁止 |
| 406 | Not Acceptable | 服务器无法根据客户端请求的内容特性完成请求 |
| 407 | Proxy Authentication Required | 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 |
| 408 | Request Time-out | 服务器等待客户端发送的请求时间过长,超时 |
| 409 | Conflict | 服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突 |
| 410 | Gone | 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 |
| 411 | Length Required | 服务器无法处理客户端发送的不带Content-Length的请求信息 |
| 412 | Precondition Failed | 客户端请求信息的先决条件错误 |
| 413 | Request Entity Too Large | 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 |
| 414 | Request-URI Too Large | 请求的URI过长(URI通常为网址),服务器无法处理 |
| 415 | Unsupported Media Type | 服务器无法处理请求附带的媒体格式 |
| 416 | Requested range not satisfiable | 客户端请求的范围无效 |
| 417 | Expectation Failed | 服务器无法满足Expect的请求头信息 |
| 500 | Internal Server Error | 服务器内部错误,无法完成请求 |
| 501 | Not Implemented | 服务器不支持请求的功能,无法完成请求 |
| 502 | Bad Gateway | 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应 |
| 503 | Service Unavailable | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 |
| 504 | Gateway Time-out | 充当网关或代理的服务器,未及时从远端服务器获取请求 |
| 505 | HTTP Version not supported | 服务器不支持请求的HTTP协议的版本,无法完成处理 |
常用的响应头名称和解释:
| 名称 | 解释 |
|---|---|
| Content-Encoding | 服务器返回对应的编码格式 |
| Content-Type | 服务端返回的实体内容的类型 |
| Max-age | 客户端本地资源应该缓存多少秒 |
| Last-Modified | 请求资源的最后修改时间 |
| ETag | 请求变量的实体标签的当前值,是一种文件指纹,只要文件改变,ETag立刻变,带有Last-Modified时,ETag优先级最高 |
| Set-Cookie | 设置和页面关联的cookie,服务器通过这个头部把cookie传给客户端 |
| Connection | 表示是否需要持久连接,如Keep-alive |
| Assess-Control-Allow-Origin | 服务器允许的请求Origin头部 |
| Access-Control-Allow-Headers | 服务器允许的请求Headers |
| Access-Control-Allow-Methods | 服务器允许的请求方法 |
响应体
响应头之后紧跟着一个空行,然后接响应体。响应体就是Web服务器发送到客户端的实际内容。除网页外,响应体还可以是诸如Word、Excel或PDF等其他类型的文档,具体是哪种文档类型由Content-Type指定的MIME类型决定。
GET请求和POST请求的区别
1、GET在浏览器回退时是无害的,而POST会再次提交请求
2、GET产生的URL地址可以被Bookmark,而POST不可以。
3、GET请求会被浏览器主动cache,而POST不会,除非手动设置。
4、GET请求只能进行url编码,而POST支持多种编码方式。
5、GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
6、GET请求在URL中传送的参数是有长度限制的,而POST么有。
7、对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
8、GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
9、GET参数通过URL传递,POST放在Request body中。
HTTP VS HTTPS,https多了安全层
| HTTP | 应用层 |
|---|---|
| TSL/SSL | 安全层 |
| TCP | 传输层 |
| IP | 网络层 |
| 网络接口 | 数据链路层 |
-
1、安全性不同:HTTP明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
-
2、网站申请流程不同:使用HTTPS协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
-
3、响应速度不同:HTTP页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS 除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
-
4、资源消耗不同:HTTPS其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源。
-
5、默认端口不同:HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。在网络模型中,HTTP工作于应用层,而HTTPS工作在传输层
-
6、对搜索排名的提升:百度和谷歌两大搜索引擎都已经明确表示,HTTPS网站将会作为搜索排名的一个重要权重指标。也就是说HTTPS网站比起HTTP网站在搜索排名中更有优势。
UDP协议和TCP协议区别
| 对比项 | UDP | TCP |
|---|---|---|
| 是否链接 | 无链接,无需建立连接就可以直接发送大量数据 | 面向链接,在发送数据前先需要建立连接,然后再发送数据 |
| 是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 |
| 发送确认 | 没有发送确认 | 有发送确认 |
| 连接个数 | 支持一对一,一对多,多对一和多对多通信 | 只能一对一通信 |
| 传输方式 | 面向报文 | 面向字节流 |
| 传输速度 | 传输会更快 | 传输的速度比较慢 |
| 错误校验 | 也会错误检查,但会丢弃错误的数据包 | 会进行错误校验,并能够进行错误恢复 |
| 握手协议 | 无握手协议 | 有握手协议 ,例如 SYN,SYN-ACK,ACK |
| 首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
| 适用场景 | 适用于实时应用:IP电话、视频会议、直播等 | 适用于要求可靠传输的应用:文件传输等 |
3. 渲染
3.1 解析HTML构建DOM Tree
拿到服务器返回的网页后,首先,根据顶部定义的DTD类型进行对应的解析方式,网页的解析将会被交给内部的GUI渲染线程处理
渲染线程中的HTML解释器,将HTML网页和资源从字节流解释转换成字符流,再通过词法分析器将字符流解释成词,之后经过语法分析器根据词构建成节点,最后通过这些节点组建一个DOM树
这个过程中,如果遇到的节点是 JS 代码,就会调用 JS引擎 对 JS代码进行解释执行,此时由于 JS引擎 和 GUI渲染线程 的互斥,GUI渲染线程 就会被挂起,渲染过程停止,如果 JS 代码的运行中对DOM树进行了修改,那么DOM的构建需要从新开始
如果节点需要依赖其他资源,图片/CSS等等,就会调用网络模块的资源加载器来加载它们,它们是异步的,不会阻塞当前DOM树的构建
如果遇到的是 JS 资源URL(没有标记异步),则需要停止当前DOM的构建,直到 JS 的资源加载并被 JS引擎 执行后才继续构建DOM
3.2 解析CSS构建CSSOM Tree
CSS解释器会将CSS文件解释成内部表示结构,生成CSS规则树,这个过程也是和DOM解析类似的,CSS 字节转换成字符,接着词法解析与法解析,最后构成 CSS对象模型(CSSOM) 的树结构
构建的过程中浏览器得递归 DOM 树来确定元素到底是什么样式,为了 CSSOM 的完整性,只有等构建完毕才能进入到下一个阶段,所以就算 DOM 已经构建完了,也得等 CSSOM,然后才能进入下一个阶段
3.3 构建渲染树 (Render Tree)
接着 CSSOM 树和 DOM 树 合并成渲染树,也叫呈现树,渲染树 只包含渲染网页所需的节点,然后用于计算每个可见元素的布局,并输出给绘制流程,将像素渲染到屏幕上
3.4 布局 (Layout)
渲染树 同时包含了屏幕上的所有可见内容及其样式信息,有了渲染树,再接着就会进入布局 ( layout ) 阶段了,到目前为止,浏览器计算了哪些节点应该是可见的以及它们的计算样式,但还没有计算它们在设备视口内的确切位置和大小,这就是 布局 ( Layout ) 阶段,也称为 自动重排 或 回流 ( Reflow )
此阶段一般意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树
3.5 绘制 (Painting)
经由前几步我们知道了哪些节点可见、它们的计算样式以及几何信息,将这些信息及渲染树中的每个节点转换成屏幕上的实际像素,也就是俗称的 绘制 或 栅格化 阶段
3.6 合成(Composite)
最后一步合成( composite ),这一步骤浏览器会将各层信息发送给GPU,GPU将各层合成,显示在屏幕上
参考资料: