JavaScript如何工作:网络层内部+如何优化其性能和安全性
历史
四十九年前,创建了一个名为ARPAnet的东西。这是一个早期的分组交换网络,也是实现TCP / IP套件的第一个网络。该网络在加州大学和斯坦福研究院之间建立了联系。20年后,Tim Berners-Lee发布了一个名为“Mesh”的提案,该提案后来更为人称为万维网。在那49年里,互联网已经走过了漫长的道路,从仅仅两台计算机交换数据包开始,到达超过7500万台服务器,38亿人使用互联网和1.3B网站。
在这篇文章中,我们将尝试分析现代浏览器采用哪些技术来自动提升性能(甚至不知道它),并且我们将特别放大浏览器网络层。最后,我们将提供一些关于如何帮助浏览器提升Web应用性能的想法。
概观
现代网络浏览器专为快速,高效和安全地交付网络应用/网站而设计。数百个组件运行在不同的层上,从流程管理和安全沙箱到GPU流水线,音频和视频等等,Web浏览器看起来更像是一个操作系统,而不仅仅是一个软件应用程序。
浏览器的整体性能取决于许多大型组件:解析,布局,样式计算,JavaScript和WebAssembly执行,渲染,当然还有网络堆栈。
工程师经常认为网络堆栈是一个瓶颈。这通常是这种情况,因为在解除其他步骤之前,需要从互联网获取所有资源。为了提高网络层的效率,它不仅需要扮演简单的套接字管理员的角色。它作为一种非常简单的资源获取机制呈现给我们,但它实际上是一个拥有自己的优化标准,API和服务的整个平台。
作为Web开发人员,我们不必担心单个TCP或UDP数据包,请求格式化,缓存和其他所有事情。整个复杂性由浏览器处理,因此我们可以专注于我们正在开发的应用程序。但是,了解发生了什么,可以帮助我们创建更快,更安全的应用程序。
从本质上讲,用户开始与浏览器交互时会发生以下情况:
用户在浏览器地址栏中输入一个URL
给定Web上资源的URL,浏览器首先检查本地和应用程序缓存,并尝试使用本地副本来完成请求。
如果缓存无法使用,浏览器将从URL中获取域名,并从DNS请求服务器的IP地址。如果该域被缓存,则不需要DNS查询。
浏览器会创建一个HTTP数据包,说明它请求位于远程服务器上的网页。
数据包被发送到TCP层,在TCP数据包的顶部添加它自己的信息。此信息是维护启动会话所必需的。
然后将数据包交给IP层,主要工作是找出将数据包从用户发送到远程服务器的方式。这些信息也存储在数据包的顶部。
数据包被发送到远程服务器。
一旦收到数据包,就会以类似的方式发送回应。
W3C Navigation Timing规范提供浏览器API以及浏览器中每个请求的生命周期背后的时间和性能数据。让我们来看看这些组件,因为它们在提供最佳用户体验方面起着至关重要的作用:
整个联网过程非常复杂,有许多不同的层次可能会成为瓶颈。这就是为什么浏览器努力通过使用各种技术来提高性能的原因,以便整个网络通信的影响最小。
套接字管理
让我们从一些术语开始:
起源 - 三重应用协议,域名和端口号(例如https,www.example.com,443)
套接字池 - 属于同一来源的一组套接字(所有主流浏览器都将最大池大小限制为6个套接字)
JavaScript和WebAssembly 不要让我们管理各个网络套接字的生命周期,这是一件好事!这不仅可以让我们的手保持清洁,而且还可以让浏览器自动进行大量的性能优化,其中一些包括套接字重用,请求优先级和后期绑定,协议协商,强制连接限制等等。
实际上,现代浏览器会花费更多的时间来将请求管理周期与套接字管理分开。套接字按池组织,按原点分组,每个池强制实施自己的连接限制和安全约束。待处理的请求排队,优先,然后绑定到池中的单个套接字。除非服务器有意关闭连接,否则可以在多个请求中自动重用相同的套接字!
由于新的TCP连接的开通需要额外的成本,因此连接的重复使用对其本身具有很大的性能优势。默认情况下,浏览器使用所谓的“keepalive”机制,这可以节省在发出请求时打开新连接到服务器的时间。打开一个新的TCP连接的平均时间是:
Local requests — 23ms
Transcontinental requests — 120ms
Intercontinental requests — 225ms
这种架构打开了其他一些优化机会的大门。这些请求可以根据其优先级以不同的顺序执行。浏览器可以优化所有套接字上的带宽分配,或者可以在预期请求时打开套接字。
正如我之前提到的,这一切都是由浏览器管理的,并不需要我们的任何工作。但这并不一定意味着我们无法做任何事情来帮助。选择正确的网络通信模式,传输类型和频率,选择协议以及调整/优化服务器堆栈可以在提高应用程序的整体性能方面发挥重要作用。
有些浏览器甚至更进一步。例如,Chrome可以自我教导自己在使用它时速度更快。它根据访问的网站和典型的浏览模式进行学习,以便在用户做任何事情之前预测可能的用户行为并采取行动。最简单的例子是当用户悬停在链接上时预先呈现页面。
网络安全和沙盒
允许浏览器管理单个套接字具有另一个非常重要的目的:通过这种方式,浏览器可以对不可信的应用程序资源执行一致的安全和策略约束。例如,浏览器不允许直接访问原始网络套接字,因为这可以使任何恶意应用程序与任何主机进行任意连接。浏览器还强制执行连接限制,以保护服务器以及客户端免受资源耗尽。
浏览器格式化所有传出请求,以强化一致且格式良好的协议语义来保护服务器。同样,响应解码自动完成,以保护用户免受恶意服务器的侵害。
TLS
传输层安全性(TLS)是一种通过计算机网络提供通信安全性的加密协议。它在许多应用程序中广泛使用,其中之一是网页浏览。网站可以使用TLS来保护其服务器和Web浏览器之间的所有通信。
整个TLS握手由以下步骤组成:
客户端向客户端发送“Client hello”消息,以及客户端的随机值和支持的密码套件。
服务器通过向客户端发送“服务器问候”消息以及服务器的随机值进行响应。
服务器将其证书发送给客户端,并可以向客户端请求类似的证书。服务器发送“服务器已完成”消息。
如果服务器已经从客户端请求证书,则客户端发送它。
客户端创建一个随机的预主密钥,并使用服务器证书中的公钥对其进行加密,并将加密的预主密钥发送给服务器。
服务器收到预主密钥。服务器和客户端根据预主密钥生成主密钥和会话密钥。
客户端向服务器发送“更改密码规范”通知,以指示客户端将开始使用新的会话密钥进行散列和加密消息。客户端还发送“客户端已完成”消息。
服务器收到“更改密码规范”并使用会话密钥将其记录层安全状态切换为对称加密。服务器向客户端发送“服务器已完成”消息。
客户端和服务器现在可以通过他们建立的安全通道交换应用程序数据。所有从客户端发送到服务器并返回的消息均使用会话密钥加密。
如果任何验证失败 - 例如服务器正在使用自签名证书,则会警告用户。
同源政策
如果两个页面的协议,端口(如果指定了一个)和主机相同,则两个页面的起源相同。
以下是可能嵌入跨源的资源的一些示例:
JavaScript与<script src=”…”></script>。语法错误的错误消息仅适用于同源脚本
CSS与<link rel=”stylesheet” href=”…”>。由于CSS的宽松语法规则,跨源CSS需要正确的Content-Type标头。浏览器的限制因人而异
图像与 <img>
带有<video>和<audio>的媒体文件
插件<object>,<embed>和<applet>
带有@ font-face的字体。一些浏览器允许使用交叉来源的字体,其他浏览器则需要相同来源的字体。
任何与<frame>和<iframe>。网站可以使用X-Frame-Options标题来阻止这种形式的跨源交互。
以上列表远非完整; 其目标是突出工作中的“最小特权”原则。浏览器只公开应用程序代码所需的API和资源:应用程序提供数据和URL,浏览器格式化请求并处理每个连接的完整生命周期。
值得注意的是,“同源策略”没有单一概念。相反,有一组相关机制强制限制DOM访问,Cookie和会话状态管理,网络以及浏览器的其他组件。
资源和客户端状态缓存
最好和最快的请求是未提出的请求。在分派请求之前,浏览器会自动检查其资源缓存,执行必要的验证检查,并在满足指定条件时返回资源的本地副本。如果本地资源在高速缓存中不可用,则会发出网络请求,并且响应会自动放入高速缓存中以供后续访问(如果允许)。
浏览器自动评估每个资源上的缓存指令
如果可能,浏览器会自动重新验证过期资源
浏览器自动管理缓存和资源逐出的大小
管理高效和优化的资源缓存很困难。值得庆幸的是,浏览器代表了我们的整个复杂性,我们需要做的是确保我们的服务器返回适当的缓存指令; 要了解更多信息,请参阅客户端上的缓存资源。您确实为网页上的所有资源提供了Cache-Control,ETag和Last-Modified响应标头,对吗?
最后,浏览器经常被忽视但关键的功能是提供身份验证,会话和cookie管理。浏览器为每个来源维护单独的“Cookie JAR”,提供必要的应用程序和服务器API来读取和写入新的Cookie,会话和身份验证数据,并自动附加和处理相应的HTTP头以代表我们自动执行整个过程。
一个例子:
将会话状态管理推迟到浏览器的便利性的一个简单但说明性示例:可以在多个选项卡或浏览器窗口之间共享经过身份验证的会话,反之亦然; 单个选项卡中的注销操作将使所有其他打开的窗口中的打开会话失效。
应用程序API和协议
走上提供网络服务的阶梯,我们终于到达了应用程序API和协议。正如我们所看到的,较低层提供了一系列关键服务:套接字和连接管理,请求和响应处理,各种安全策略的执行,缓存等等。每次我们启动一个HTTP或一个XMLHttpRequest,一个长期服务器发送的事件或WebSocket会话,或打开一个WebRTC连接,我们都与这些底层服务的一部分或全部进行交互。
没有单一的最佳协议或API。每个非平凡的应用程序都需要根据各种需求混合使用不同的传输:与浏览器缓存的交互,协议开销,消息延迟,可靠性,数据传输类型等等。某些协议可能会提供低延迟传输(例如,Server-Sent Events,WebSocket),但可能不符合其他关键条件,例如在所有情况下都能利用浏览器缓存或支持有效的二进制传输。
您可以通过几件事来改善Web应用程序的性能和安全性
在请求中始终使用“Connection:Keep-Alive”标题。浏览器默认这样做。确保服务器使用相同的机制。
使用适当的Cache-Control,Etag和Last-Modified标题,以便您可以保存浏览器的一些下载时间。
花时间调整和优化您的Web服务器。这就是真正的魔法发生的地方!请记住,该流程是否针对每个Web应用程序以及您要传输的数据的类型都非常具体。
始终使用TLS!特别是如果您在应用程序中有任何类型的身份验证。
研究浏览器在您的应用程序中提供并实施哪些安全策略。
相关推荐
写过小程序的应该知道,微信的request不封装基本上不能用,写的显的太冗长,而且是回调式的,回调地狱什么的就不说了,可读性差。下面是我的封装代码,顺便支持一下promise。加了登录锁后的代码如下: