第十五章 XMLHttpRequest

树莓派4B与Pixhawk 2.4.8 MAVLink脚本调试完整指南 无人机二次开发中,机载计算机与飞控的协同是核心架构。飞控负责姿态稳定与底层控制,机载计算机则承担视觉推理、任务调度等复杂运算,两者通过UART串口和MAVLink协议通信。MAVLink作为无人机领域的事实标准协议,决定了指令传输的可靠性。这种设计不仅提升了无人机的智能化水平,也为教育、科研等场景提供了灵活的扩展平台。在实践层面,树莓派4B与Pixhawk 2.4.8 的搭配是经典组合,但硬件接线电平、共地供电、串口权限等问题常成为拦路虎。本文以这套组合为例,从基础原理出发,完整梳理了从引脚连接、系统配置、 阅读详情

XMLHttpRequest(XHR)是浏览器级别的API,使客户端可以通过JavaScript编写数据传输脚本。 XHR在Internet Explorer 5中首次亮相,成为异步JavaScript和XML(AJAX)革命背后的关键技术之一,并且现在已成为几乎所有现代Web应用程序的基本构建块。

XMLHTTP改变了一切。 它将“ D”放入DHTML。 它使我们能够异步地从服务器获取数据并保留客户端上的文档状态…Outlook Web Access(OWA)团队渴望在浏览器中构建丰富的Win32之类的应用程序,从而将该技术引入了IE,从而使AJAX成为现实。
Jim Van Eaton, Outlook Web Access: A catalyst for web evolution

在XHR之前,必须刷新网页才能发送或获取客户端和服务器之间任何的状态更新。 使用XHR,可以在应用程序JavaScript代码的完全控制下异步完成此工作流程。 XHR实现了从构建页面到在浏览器中构建交互式Web应用程序的飞跃。

但是,XHR的强大功能不仅在于它启用了浏览器内的异步通信,还在于使其变得简单。 XHR是浏览器提供的应用程序API,也就是说,浏览器会自动处理所有低级连接管理、协议协商(protocol negotiation)、格式化HTTP请求等等:

  • 浏览器管理连接的建立,池化和终止。
  • 浏览器确定最佳的HTTP(S)传输(HTTP / 1.0、1.1、2)。
  • 浏览器处理HTTP缓存,重定向和内容类型协商(content-type negotiation)。
  • 浏览器强制执行安全性,身份验证和隐私约束。
  • …

我们的应用程序无需担心所有底层细节,而可以专注于业务逻辑,这些逻辑包括发起请求、管理请求的进度、以及处理服务器返回的数据。简单的API结合它在所有浏览器中无处不在的可用性,使XHR成为浏览器中联网的“瑞士军刀”。

因此,几乎每个网络用例(脚本下载,上传,流传输,甚至实时通知)都可以并且已经建立在XHR之上。 当然,这并不意味着XHR在每种情况下都是最有效的传输方式,实际上,正如我们将要看到的那样,它远非如此,但这仍然经常被用作老客户的后备传输方式,后者可能无法访问到更新的浏览器网络API。 考虑到这一点,让我们仔细研究XHR的最新功能,其用例以及性能的优缺点。

完整的XHR API及其功能的详尽分析超出了我们的讨论范围——我们的重点是性能!有关XMLHttpRequest API的概述,请参考正式的W3C标准。

XHR简史

尽管有XHR的名字,但它从未打算专门与XML绑定。 XML前缀显露了将第一个版本的XHR作为Internet Explorer 5中MSXML库的一部分发布的痕迹:

这是过去的美好日子,关键功能在发布前几天就塞满了……我意识到IE附带了MSXML库,并且我与XML团队有一些良好的联系,他们可能会帮上忙,我与当时负责该团队的Jean Paoli取得了联系,我们很快达成了一项协议,将其作为MSXML库的一部分进行发布。真正解释了XMLHTTP名称的来源是——它主要是关于HTTP的,撇开为了一起发布我因此需要将XML塞入名称中,它XML没有任何特定的关系。
Alex Hopmann, The story of XMLHTTP

Mozilla对照Microsoft建模了自己的XHR实现,并通过XMLHttpRequest接口公开了它。 随后Safari,Opera和其他浏览器的效仿,让XHR成为所有主流浏览器中事实上的标准,因此它的名称就此定型。 实际上,XHR的正式W3C工作草案规范只是在XHR广泛使用之后才于2006年发布的!

但是,尽管XHR早已流行并且在AJAX革命中发挥了关键作用,但其早期版本提供的功能有限:仅基于文本的数据传输、对上传的支持有限并且无法处理跨域请求。 为了解决这些缺点,2008年发布了“ XMLHttpRequest Level 2”草案,其中添加了许多新功能:

  • 支持请求超时
  • 支持二进制和基于文本的数据传输
  • 支持媒体类型的应用程序覆盖和响应编码
  • 支持监视每个请求的进度事件
  • 支持有效的文件上传
  • 支持安全的跨域请求

在2011年,“ XMLHttpRequest Level 2”规范与原始XMLHttpRequest工作草案合并。 因此,尽管您经常会找到对XHR版本或Level 1和Level 2的引用,但这些区别不再相关; 今天,只有一个统一的XHR规范。 实际上,所有新的XHR2特性和功能都是通过相同的XMLHttpRequest API提供的:相同的接口,更多的功能。

现在,所有现代浏览器都支持XHR2的新功能。参见caniuse.com/xhr2。 因此,无论何时提及XHR,我们都隐含地提及XHR2标准。

跨域资源共享(Cross-Origin Resource Sharing)

XHR是浏览器级别的API,可自动处理大量的底层细节,例如缓存、处理重定向、内容协商(content negotiation)、身份验证等等。 这具有双重目的。 首先,它使应用程序API更加易于使用,使我们能够专注于业务逻辑。 但是,其次,它允许浏览器对应用程序代码进行沙箱操作并强制实施一组安全性和策略约束。

XHR接口对每个请求强制执行严格的HTTP语义:应用程序提供数据和URL,浏览器格式化请求并处理每个连接的整个生命周期。 类似地,尽管XHR API允许应用程序添加自定义HTTP标头(通过setRequestHeader()方法),但仍有许多受保护的标头对应用程序代码不可用:

  • Accept-Charset, Accept-Encoding, Access-Control-*
  • Host, Upgrade, Connection, Referer, Origin
  • Cookie,Sec-*,Proxy- *和其他十几个…

浏览器将拒绝覆盖任何不安全的标头,以确保应用程序无法模拟伪造的用户代理、用户或发出请求的来源。 实际上,保护源头(origin header)尤其重要,因为这是应用于所有XHR请求“同源策略”的关键部分。

“来源”(origin)定义为应用程序协议、域名和端口号的三元组,例如(http,example.com,80)和(https,example.com,443)被认为是不同的来源。 有关更多详细信息,请参见Web Origin概念。

同源策略的动机很简单:浏览器存储用户数据,例如身份验证令牌,cookie和其他私有元数据,这些数据不能在不同的应用程序之间泄漏—例如,如果没有相同的源沙箱,则example.com中的任意脚本可以访问和操纵thirdparty.com上的用户数据!

为了解决此特定问题,XHR的早期版本仅限于同源请求,其中请求的源(origin)必须与被请求资源的源(origin)匹配:从example.com启动的XHR只能请求另一相同 example.com源(origin)的资源。或者,如果相同的源这一前提条件不符合,则浏览器将简单地拒绝发起XHR请求并引发错误。

但是,在必要时,同源策略也对XHR的实用性施加了严格的限制:如果服务器想为运行在不同源的脚本提供资源怎么办?这就是“跨源资源共享”(CORS)出现的地方! CORS为客户端跨域请求提供了一种安全的选择加入机制(opt-in mechanism):

// script origin: (http, example.com, 80)
var xhr = new XMLHttpRequest();
xhr.open('GET', '/resource.js'); /**1**/
xhr.onload = function() { ... };
xhr.send();

var cors_xhr = new XMLHttpRequest();
cors_xhr.open('GET', 'http://thirdparty.com/resource.js'); /**2**/
cors_xhr.onload = function() { ... };
cors_xhr.send();

/**1**/同源XHR请求
/**2**/跨域XHR请求

CORS请求使用相同的XHR API,唯一的区别是所请求资源的URL与执行脚本的来源不同。在上一示例中,脚本是从(http,example.com, 80)执行的,第二个XHR请求从(http,thirdparty.com,80)访问resource.js。

针对CORS请求的选择加入身份验证机制(The opt-in authentication mechanism)在较低层进行处理:发出请求时,浏览器会自动附加受保护的Origin HTTP标头,该标头会通告发出请求的来源。 然后,远程服务器可以通过在响应中返回Access-Control-Allow-Origin标头来检查Origin标头并确定是否应允许该请求:

=> Request
GET /resource.js HTTP/1.1
Host: thirdparty.com
Origin: http://example.com /**1**/
...

<= Response
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com /**2**/
...

/**1**/源标头(Origin header)由浏览器自动设置
/**2**/选择加入标头(Opt-in header)由服务器设置

在前面的示例中,thirdparty.com决定通过在响应中返回适当的访问控制标头来选择与example.com进行跨域资源共享。 另外,如果要禁止访问,可以简单地省略Access-Control-Allow-Origin标头,然后客户端的浏览器会自动让发送的请求失败。

如果第三方服务器不支持CORS,则客户端请求将失败,因为客户端始终会验证选择加入标头(the opt-in header)的存在。 作为一种特殊情况,CORS还允许服务器返回通配符(Access-Control-Allow-Origin:),以表示它允许从任何来源进行访问。 但是,在启用此策略之前,请三思!*

这样,我们都完成了,对吗? 事实并非如此,因为CORS采取了许多额外的安全预防措施来确保服务器支持CORS:

  • CORS请求忽略了用户凭据,例如cookie和HTTP身份验证。
  • 客户端只能发出“简单的跨域请求”,这对允许的请求方法(GET,POST,HEAD)以及对XHR可以发送和读取的HTTP标头的访问进行了限制。

若要启用cookie和HTTP身份验证,客户端在发出请求时必须在XHR对象上设置一个额外的属性(withCredentials),并且服务器还必须以适当的标头(Access-Control-Allow-Credentials)进行响应以表明它是故意允许该应用程序包含私人用户数据。 类似地,如果客户端需要编写或读取自定义HTTP标头,或者想要对该请求使用“非简单方法”,则它必须首先通过发出预检请求(Preflight request)来请求第三方服务器的许可:

=> Preflight request
OPTIONS /resource.js HTTP/1.1 /**1**/
Host: thirdparty.com
Origin: http://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: My-Custom-Header
...

<= Preflight response
HTTP/1.1 200 OK /**2**/
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: My-Custom-Header
...

(actual HTTP request) /**3**/

/**1**/预检请求OPTIONS以验证权限
/**2**/来自第三方源的成功预检响应
/**3**/实际的CORS请求

正式的W3C CORS规范定义了必须在何时何地使用预检请求:“简单”请求可以跳过它,但是有多种条件会触发该请求,并添加最少的往返网络延迟来验证权限。 好消息是,一旦发出预检请求,客户端就可以对其进行缓存,以避免对每个请求进行相同的验证。

所有现代浏览器均支持CORS;参见caniuse.com/cors。想要深入了解各种CORS策略和措施,请参考W3C官方标准。

用XHR下载数据(Downloading Data with XHR)

XHR可以传输基于文本的数据和二进制数据。 实际上,浏览器为各种本地数据类型提供自动编码和解码,这允许应用程序将这些类型直接传递给XHR以进行正确编码,反之亦然,以使浏览器自动解码这些类型:

ArrayBuffer
定长二进制数据缓冲区

Blob
不可变数据的二进制大对象

Document
解析HTML或XML文档

JSON
代表简单数据结构的JavaScript对象

Text
一个简单的文本字符串

浏览器可以依靠HTTP内容类型协商(content-type negotiation)来推断适当的数据类型(例如,将application/json响应解码为JSON对象),或者应用程序可以在发起XHR请求时显式覆盖数据类型:

var xhr = new XMLHttpRequest();
xhr.open('GET', '/images/photo.webp');
xhr.responseType = 'blob'; /**1**/

xhr.onload = function() {
  if (this.status == 200) {
    var img = document.createElement('img');
    img.src = window.URL.createObjectURL(this.response); /**2**/
    img.onload = function() {
        window.URL.revokeObjectURL(this.src); /**3**/
    }
    document.body.appendChild(img);
  }
};

xhr.send();

/**1**/设置返回数据类型为blob
/**2**/从Blob创建唯一的对象URI并将其设置为图像源
/**3**/加载图片后释放对象URI

请注意,我们正在以原始格式传输图像资源,而不依赖于base64编码,并且在不依赖数据URI的情况下向页面添加了图像元素。 在JavaScript中处理接收到的二进制数据时,没有网络传输开销或编码开销! XHR API使我们可以直接通过JavaScript编写高效、动态的应用程序脚本,而不管数据类型如何。

Blob接口是HTML5 File API的一部分,并充当任意数据块(二进制或文本)的不透明引用。 就其本身而言,blob引用的功能有限:您可以查询其大小,MIME类型,并将其拆分为较小的blob。 但是,它的真正作用是充当各种JavaScript API之间的有效交换机制。

用XHR上传数据(Uploading Data with XHR)

对于所有数据类型,通过XHR上传数据既简单又有效。 实际上,代码是相同的,唯一的区别是,在XHR请求上调用send()时,我们还传入一个数据对象。 其余的由浏览器处理:

var xhr = new XMLHttpRequest();
xhr.open('POST','/upload');
xhr.onload = function() { ... };
xhr.send("text string"); /**1**/

var formData = new FormData(); /**2**/
formData.append('id', 123456);
formData.append('topic', 'performance');

var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.onload = function() { ... };
xhr.send(formData); /**3**/

var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.onload = function() { ... };
var uInt8Array = new Uint8Array([1, 2, 3]); /**4**/
xhr.send(uInt8Array.buffer); /**5**/

/**1**/将简单的文本字符串上传到服务器
/**2**/通过FormData API创建动态表单
/**3**/将multipart/form-data对象上传到服务器
/**4**/创建一个无符号8位整数的定型数组(ArrayBuffer)
/**5**/将字节块上传到服务器

XHR send()方法接受DOMString,Document,FormData,Blob,File或ArrayBuffer对象,自动执行适当的编码,设置适当的HTTP内容类型,并分派请求。 是否需要发送二进制Blob或上传用户提供的文件? 简单:获取对该对象的引用,并将其传递给XHR。 实际上,通过一些额外的工作,我们还可以将大文件拆分为较小的块:

var blob = ...; /**1**/

const BYTES_PER_CHUNK = 1024 * 1024; /**2**/
const SIZE = blob.size;

var start = 0;
var end = BYTES_PER_CHUNK;

while(start < SIZE) { /**3**/
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/upload');
  xhr.onload = function() { ... };

  xhr.setRequestHeader('Content-Range', start+'-'+end+'/'+SIZE); /**4**/
  xhr.send(blob.slice(start, end)); /**5**/

  start = end;
  end = start + BYTES_PER_CHUNK;
}

/**1**/任意数据块(二进制或文本)
/**2**/将块大小设置为1 MB
/**3**/以1MB为增量迭代提供的数据
/**4**/确定上传的数据范围(开始-结束/总计)
/**5**/通过XHR上传1 MB的数据片

XHR不支持请求流,这意味着我们在调用send()时必须提供完整的有效负载。 但是,此示例说明了一个简单的应用程序变通方法:通过多个XHR请求将文件拆分并分块上传。 这种实现模式绝不是真正的请求流API的替代,但是对于某些应用程序来说,它是可行的解决方案。

上传切片大文件是一种在连接不稳定或间歇性情况下提供更健壮的API的好技术,例如,如果某个块由于连接断开而失败,则应用程序可以重试,或者稍后再恢复上传,而不是从头开始重新启动完整传输 。

监控下载和上传进度(Monitoring Download and Upload Progress)

网络连接可以是间歇性的,并且延迟和带宽是高度可变的。 那么我们如何知道XHR请求是成功、超时还是失败? XHR对象提供了一个方便的API,用于监听进度事件(表15-1),这些事件显示了请求的当前状态。

事件类型描述触发次数
loadstartTransfer has begun1次
progressTransfer is in progress0或1次
errorTransfer has failed0或1次
abortTransfer is terminated0或1次
loadTransfer is successful0或1次
loadendTransfer has finished1次

每次XHR传输均以loadstart开始,并以loadend事件结束,在此之间,将触发一个或多个其他事件以显示当前传输状态。 因此,要监视进度,应用程序可以在XHR对象上注册一组JavaScript事件侦听器:

var xhr = new XMLHttpRequest();
xhr.open('GET','/resource');
xhr.timeout = 5000; /**1**/

xhr.addEventListener('load', function() { ... }); /**2**/
xhr.addEventListener('error', function() { ... }); /**3**/

var onProgressHandler = function(event) {
  if(event.lengthComputable) {
    var progress = (event.loaded / event.total) * 100; /**4**/
    ...
  }
}

xhr.upload.addEventListener('progress', onProgressHandler); /**5**/
xhr.addEventListener('progress', onProgressHandler); /**6**/
xhr.send();

/**1**/将请求超时设置为5,000毫秒(默认值:无超时)
/**2**/注册成功请求的回调
/**3**/注册失败请求的回调
/**4**/计算传输进度
/**5**/注册上传进度事件的回调
/**6**/注册下载进度事件的回调

load event或者error event将触发一次以显示XHR传输的最终状态,而进度事件(progress event)可以触发任意次,并提供了方便的API来跟踪传输状态:我们可以将事件的loaded属性与total属性进行比较以估算传输的数据量。

要估算传输的数据量,服务器必须在响应中提供内容长度:我们无法估算分块传输的进度,因为根据定义,响应的总大小是未知的。
同样,XHR请求没有默认超时,这意味着请求可以无限期“进行中”。 作为最佳实践,请始终为应用程序设置有意义的超时并处理错误!

用XHR流数据(Streaming Data with XHR)

在某些情况下,应用程序可能需要或希望以增量方式处理数据流,例如:数据流在客户端上可用时将其上传到服务器,或者处理从服务器下载的数据。 不幸的是,尽管这是一个重要的用例,但如今还没有用于XHR流的简单,高效,跨浏览器的API:

  • send方法在上传的情况下期望完整的有效负载。
  • response,responseText和responseXML属性不是为流(streaming)设计的。

在官方XHR规范中,流从来都不是官方用例。由于没有将上传文件手动拆分为较小的单个XHR,因此没有用于将数据流从客户端传输到服务器的API。 类似地,尽管XHR2规范确实提供了一些从服务器读取部分响应的能力,但实现效率低下并且非常有限。 那是个坏消息。

好消息是,地平线上有希望! 作为XHR的一流用例,缺乏流支持是一个公认的局限性,目前正在解决此问题:

Web应用程序应具有以多种形式获取和操作数据的能力,包括随着时间的推移提供的一系列数据。 该规范定义了Streams的基本表示形式,Streams引发的错误以及读取和创建Streams的编程方式。
W3C Streams API

XHR和Streams API的结合将在浏览器中实现高效的XHR流。 但是,Streams API仍在积极讨论中,尚未在任何浏览器中使用。 因此,我们陷入了困境,对吧? 好吧,不完全是。 正如我们前面提到的,不能选择使用XHR进行流式上传,但是我们对使用XHR进行流式下载提供了有限支持:

var xhr = new XMLHttpRequest();
xhr.open('GET', '/stream');
xhr.seenBytes = 0;

xhr.onreadystatechange = function() { /**1**/
  if(xhr.readyState > 2) {
    var newData = xhr.responseText.substr(xhr.seenBytes); /**2**/
    // process newData

    xhr.seenBytes = xhr.responseText.length; /**3**/
  }
};

xhr.send();

/**1**/订阅状态和进度通知
/**2**/从部分响应中提取新数据
/**3**/更新已处理的字节偏移

该示例将在大多数现代浏览器中运行。 但是,性能不是很好,并且有很多实现方面的警告和陷阱:

  • 请注意,我们正在手动跟踪可见字节的偏移量,然后手动切片数据:responseText正在缓冲完整的响应!对于较小的传输,这可能不是问题,但是对于较大的下载,尤其是在内存受限的设备(例如手机)上,这是一个问题。释放缓冲响应的唯一方法是完成请求并打开一个新请求。
  • 只能从responseText属性中读取部分响应,这将我们限制为仅文本传输。无法读取二进制传输的部分响应。
  • 读取部分数据后,我们必须确定消息边界:应用程序逻辑必须定义其自己的数据格式,然后对流进行缓冲和解析以提取单个消息。
  • 浏览器在缓冲接收到的数据方面有不同的方式:某些浏览器可能会立即释放数据,而另一些浏览器可能会缓冲较小的响应并在较大的块中释放它们。
  • 浏览器在允许增量式读取的内容类型方面有所不同,例如,某些浏览器允许使用“ text / html”,而其他浏览器只能使用“ application / x-javascript”。

简而言之,当前,XHR流既不高效也不方便,更糟糕的是,缺少通用规范也意味着不同浏览器的实现有所不同。 因此,至少在Streams API可用之前,XHR并不是流(Streaming)的理想选择。

无需绝望! 尽管XHR可能不符合该标准,但我们还有其他针对Streaming用例进行了优化的传输方式:服务器发送的事件提供了一种便捷的API,用于将基于文本的数据从服务器传输到客户端,而WebSocket为二进制和基于文本的数据都提供了有效和双向的Streaming。

XHR流的专有API和扩展

Firefox和Internet Explorer均提供自定义的“streaming XHR extensions”:
1.Firefox支持moz-chunked-text和moz-chunked-arraybuffer
2.Internet Explorer支持ms-stream

通过将XHR对象上的responseType属性设置为上述类型之一,两个浏览器都将避免缓冲完整响应,并且还将允许从
XHR对象以增量方式读取二进制响应。 不幸的是,Chrome,Opera或其他流行的浏览器中没有等效的API。因此,
对于跨浏览器应用程序,XHR streaming仍然是不切实际的传输。

实时通知和交付(Real-Time Notifications and Delivery)

XHR提供了一种简单有效的方式来将客户端与服务器同步:必要时,客户端会派发XHR请求来更新服务器上的适当数据。 但是,同样的问题反过来却要困难得多。 如果服务器上的数据已更新,服务器如何通知客户端?

HTTP不提供任何方式让服务器启动与客户端的新连接。 因此,要接收实时通知,客户端必须要么轮询服务器以获取更新,要么利用流传输来允许服务器在新通知可用时推送新通知。 不幸的是,正如我们在上一节中所看到的,对XHR流的支持是有限的,这使我们只能进行XHR轮询。

“实时”对于不同的应用程序具有不同的含义:某些应用程序需要毫秒级的开销,而另一些应用程序可能分钟级的延迟也无不可。 要确定最佳传输,请首先为应用程序定义明确的延迟和开销目标!

用XHR轮询(Polling with XHR)

从服务器获取更新的最简单策略之一是让客户端进行定期检查:客户端可以定期发起后台XHR请求(以轮询服务器)以检查更新。 如果服务器上有新数据可用,则在响应中返回它,否则响应为空。

轮询易于实现,但经常效率也很低。 轮询间隔的选择很关键:较长的轮询间隔会导致更新的延迟交付,而较短的间隔会导致不必要的流量以及客户端和服务器的高开销。 让我们考虑最简单的示例:

function checkUpdates(url) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.onload = function() { ... }; /**1**/
  xhr.send();
}

setInterval(function() { checkUpdates('/updates') }, 60000); /**2**/

/**1**/处理从服务器收到的更新
/**2**/每60秒发出一个XHR请求

  • 每个XHR请求都是一个独立的HTTP请求,平均而言,HTTP会为请求/响应标头带来约800字节的开销(没有HTTP cookie)。
  • 当数据以可预测的间隔到达时,定期检查效果很好。 不幸的是,可预测的到达率是一个例外,不是正常现象。 因此,服务器上消息变为可用的与将消息传递到客户端之间,定期轮询将引入额外的延迟。
  • 除非仔细考虑,否则轮询通常会成为在无线网络上代价高昂的性能反模式; 请参阅消除周期性和低效率的数据传输。 唤醒收音机会消耗大量电池电量!

最佳轮询间隔是多少? 没有准确答案。 频率取决于应用程序的需求,并且在效率和消息等待时间之间存在固有的权衡。 因此,轮询非常适合轮询间隔较长,新事件以可预测的速率到达且传输的有效负载较大的应用程序。 这种组合抵消了额外的HTTP开销,并使消息传递延迟最小化。

XHR轮询的性能建模

为了说明XHR轮询的延迟和开销之间的平衡,让我们考虑一个简单的电子邮件应用程序,该应用程序使用XHR轮询来
检查服务器上的邮件更新。 实现如下:

1.客户端每60秒发送一次XHR以检查更新。
2.每个XHR请求都包含客户端已知的最新消息ID。
3.服务器将客户端ID与它的消息列表进行比较。
4.服务器以新消息列表或空列表(无更新)作为响应。

平均消息延迟时间是多少?
如果在客户端检查更新之前刚好一条新消息到达服务器,则延迟是最小的--只是客户端和服务器之间的延迟。 相反
,相同的新消息可以在客户端检查之后的瞬间到达,在这种情况下,该消息将不得不等待下一次客户端检查(60秒)
。因此,如果消息到达速率是随机的,则消息将在客户端获得消息之前在服务器上平均等待30秒。

轮询的开销是多少?
平均HTTP 1.x请求会增加800字节的请求和响应开销(请参阅[测量和控制协议开销]
(https://hpbn.co/http1x/#measuring-and-controlling-protocol-overhead))。
因为客户端已登录,所以我们还将有一个额外的身份验证Cookie和消息ID; 假设这又增加了50个字节。 因此,不
返回任何新消息的请求将产生850个字节! 现在,假设我们有10,000个客户端,所有客户端的轮询间隔均为60秒:

在这里插入图片描述

每个客户端在每个请求上发送850字节的数据,这相当于每秒167个请求,并且服务器上的入口吞吐量的持续速率为
1.13 Mbps!这是一个恒定的比率,而实际上并未向任何客户端传递任何新消息。

30秒延迟是否太高? 我们可以减少轮询间隔,但是这样做会导致更高的吞吐量和开销:相同的10,000个客户端
(但间隔为1秒)将产生超过60 Mbps的吞吐量! 简而言之,除非轮询间隔长,否则轮询将很昂贵。
用XHR长轮询(Long-Polling with XHR)

定期轮询的挑战在于,有可能进行许多不必要的空检查。 考虑到这一点,如果我们对轮询工作流程进行了少许修改(图15-1):没有可用的更新时,不返回空响应,我们保持连接空闲直到有可用的更新,这会更有效吗?
在这里插入图片描述
利用长期保留的HTTP请求(“挂起的GET”)允许服务器将数据推送到浏览器的技术通常称为“ Comet”。 但是,您也可能以其他名称遇到它,例如“反向AJAX”,“ AJAX推送”和“ HTTP推送”。

通过保持连接开放直到有可用的更新(长轮询),在服务器上数据可用后就可以立即发送到客户端。 结果,长轮询提供了消息延迟的最佳方案,并且还消除了空检查,从而减少了XHR请求的数量和轮询的总体开销。 交付更新后,长轮询请求即告完成,客户端可以发出另一个长轮询请求,并等待下一条可用消息:

function checkUpdates(url) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url);
  xhr.onload = function() { /**1**/
    ...
    checkUpdates('/updates'); /**2**/
  };
  xhr.send();
}

checkUpdates('/updates'); /**3**/

/**1**/处理收到的更新并打开新的长轮询XHR
/**2**/发出长轮询请求以进行下一次更新(并永久循环)
/**3**/发出长轮询初始XHR请求

这样,长轮询相比定期轮询总是更好的选择吗? 除非知道消息到达速率并保持恒定,否则长轮询将始终提供更好的消息延迟。 如果这是主要标准,则长轮询将是赢家。

另一方面,开销讨论需要更细微的了解。 首先,请注意,每个传递的消息仍会产生相同的HTTP开销; 每个新消息都是一个独立的HTTP请求。 但是,如果消息到达率很高,则长轮询将发出比定期轮询更多的XHR请求!

长轮询通过最小化消息等待时间来动态地适应消息到达速率,这是您可能想要或可能不想要的行为。 如果对消息延迟有一定的容忍度,则轮询可能是一种更有效的传输方式,例如,如果更新速率较高,则轮询将提供简单的“消息聚合”机制,从而可以减少请求数量并延长手机的电池寿命。

实际上,并非所有消息都具有相同的优先级或延迟要求。 因此,您可能需要考虑多种策略:在服务器上聚合低优先级更新,并为高优先级更新触发立即交付; 请参阅Nagle和高效服务器推送。

通过XHR Long-Polling进行Facebook聊天

实际上,长轮询已成为通过XHR推送实时通知的最广泛使用的方法之一。尽管它可能不是最有效的传输方式,但它简
单,健壮,并且受任何兼容XHR的浏览器支持。在2008年这种方法首次部署在诸如Facebook的Chat之类的热门产品

“我们选择的一个用户从另一个用户处获得文本的方法包括在每个Facebook页面上加载一个iframe,并让该iframe
的JavaScript通过持久连接发出HTTP GET请求,直到服务器获得客户端数据后该请求才返回。如果请求被中断或
超时,则会重新建立该请求。 这绝不是一项新技术:它是Comet的一种变体,特殊的XHR长轮询、and/or BOSH。”
----Facebook Chat, Facebook Engineering Blog

今天,我们可以通过服务器发送事件和WebSocket更加有效地提供相同的功能。话虽如此,对于许多实时框架,
XHR仍然是通用的后备策略。 如果其他所有方法均失败,请进行长轮询以营救!

XHR用例和性能(XHR Use Cases and Performance)

XMLHttpRequest使我们实现了从构建页面到在浏览器中构建交互式Web应用程序的飞跃。 首先,它启用了浏览器内的异步通信,但同样重要的是,它还使过程变得简单。 调度和控制脚本化的HTTP请求仅需要几行JavaScript代码,浏览器将处理所有其余代码:

  • 浏览器格式化HTTP请求并解析响应。
  • 浏览器强制执行相关的安全性(同源)策略。
  • 浏览器处理内容协商(例如gzip)。
  • 浏览器处理请求和响应缓存。
  • 浏览器处理身份验证,重定向等…

这样,对于遵循HTTP请求-响应周期的任何传输,XHR都是一种通用的高性能传输工具。是否需要获取需要身份验证的资源,应该在传输过程中对其进行压缩以及应该将其缓存以备将来使用?浏览器会处理所有这些以及更多的工作,使我们能够专注于应用程序逻辑!

但是,XHR也有其局限性。正如我们所看到的,流传输从来都不是XHR标准中的官方用例,而且支持也很有限:使用XHR进行流传输既不高效也不方便。不同的浏览器具有不同的行为,并且不可能进行有效的二进制流传输。简而言之,XHR不适合流式传输。

同样,通过XHR提供实时更新没有最佳的策略。定期轮询会导致高开销和消息延迟。长轮询提供了低延迟,但每条消息仍具有相同的开销;每个消息都是其自己的HTTP请求。为了同时具有低延迟和低开销,我们需要XHR流!

所以,尽管XHR是一种流行的“实时”交付机制,但它可能并不是工作表现最佳的运输方式。现代浏览器同时支持更简单和更有效的选项,例如服务器发送事件和WebSocket。因此,除非您有特定的原因要求进行XHR轮询,否则请使用它们。

前端 | 浅谈预检请求 背景不知道大家有没有发现,有时候我们在调用后台接口的时候,会请求两次,如下图的其实第一次发送的就是preflight request(预检请求),那么这篇文章将讲一下,为什么要发预检请求,什么时候会发预检请求,预检请求都做了什么一. 为什么要发预检请求我们都知道浏览器的同源策略,就是出于安全考虑,浏览器会限制从脚本发起的跨域HTTP请求,像XMLHttpRequest和Fetch都遵循同源策略。 阅读详情

相关推荐

什么是 HTTP 请求中的 Preflight 请求?

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『』发一些比较有趣的文章,有兴趣的也可以关注下。』,获取更多有趣内容。

Twinkle_sone的博客 2060

HTTP请求方法OPTIONS预检请求

浏览器对简单跨域请求和复杂跨域请求的处理区别。 浏览器对复杂跨域请求在真正发送请求之前,会先进行一次预请求,就是参数为OPTIONS的第一次请求,他的作用是用于试探性的服务器响应是否正确,即是否能接受真正的请求,如果在options请求之后获取到的响应是拒绝性质的,例如500等http状态,那么它就会停止第二次的真正请求的访问。 同时满足下列以下条件,就属于简单请求,否则属于非简单请求(参考HTT...

yinqian_Golang的博客 5827

用 SAML 2.0 服务用户认证打通 ABAP 侧系统访问

摘要: SAP ABAP系统中SAML 2.0单点登录的系统集成场景下,Authentication with Service Users机制通过动态映射SAML属性到预定义服务用户,解决身份归并问题。该机制允许ABAP根据SAML断言中的属性(如公司代码、应用角色等)选择合适服务用户,而非绑定具体自然人身份。配置时需注意条件匹配顺序、属性名称一致性,并合理设置Default Service User。此方案适用于需区分业务域而非个人身份的系统集成场景,是SAP ABAP实现灵活身份联合的重要功能。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 2244

AJAX(XMLHttpRequest)进行跨域请求方法详解(二)

2,预检请求 预检请求首先需要向另外一个域名的资源发送一个 HTTP OPTIONS 请求头,其目的就是为了判断实际发送的请求是否是安全的。下面的2种情况需要进行预检: a,不是上面的简单请求,比如使用Content-Type 为 application/xml 或 text/xml 的 POST 请求 b,在请求中设置自定义头,比如 X-JSON、X-MENGXIANHUI 等   注

Skyline 695

芯片低功耗设计实战:CPF与UPF流程选择指南(附工具对比)

本文深入探讨了芯片低功耗设计实战中CPF与UPF流程的选择策略。文章从两者本质差异与演进脉络出发,结合Cadence与Synopsys工具链的深度支持实况,为工程师提供了基于项目场景的决策框架。重点分析了在混合工具链环境下如何实施流程与验证,并强调了利用Spyglass等工具确保功耗意图无损传递的关键技巧,旨在帮助团队做出明智的流程抉择,提升设计效率与可靠性。

weixin_29197051的博客 403

智能座舱芯片大乱斗:高通8155 vs 联发科MT8676 vs 华为麒麟990A,谁才是性价比之王?

本文深入对比了高通SA8155P、联发科MT8676与华为麒麟990A三款主流智能座舱芯片的性能、生态与成本。文章从CPU/GPU/NPU算力、开发生态、成本模型及典型应用场景出发,分析了各自在智能座舱领域的优势与适用性,为开发者与车企选型提供了实战参考,并探讨了智能座舱芯片未来的技术趋势。

yhn456789的博客 798

JavaScript 网络请求与远程资源(一)-------XHR与CORS

这样的请求在发送时会有一个额外的头部Origin。

m0_61773971的博客 1689

Ajax跨域

1、应用场景: 前后端联调的时候,即前端页面发起请求调用后端的API接口的时候 2、什么是Ajax跨域? 当我们发送XMLHttpRequest请求的时候,如果请求的是别的域(主机域名、端口)不同时,那么就会产生跨域问题(客户端无法获取服务端返回的数据),说明后端没有做跨域设置。 值得注意的是:跨域的问题是发生在XMLHttpRequest请求的,也就是说,不是XMLHttpRequest请求是不会有跨域问题的。 3、为何会出现这个跨域错误? 浏览器的安全机制说起,同源策略(保证请求安全的一种机制)。 4、

码说芯语的博客 291

西门子PLC新手必看:200SMART与1200/1500的MODBUS RTU通讯实战(附完整配置流程)

本文详细解析了西门子PLC中S7-200 SMART与S7-1200/1500之间实现MODBUS RTU通讯的完整实战流程。内容涵盖硬件接线、软件配置及程序编写,重点讲解了200SMART作为从站、1200/1500作为主站的典型架构,并提供了关键功能块的使用方法和常见问题排查技巧,帮助新手快速打通不同系列PLC间的数据交换。

weixin_29214559的博客 393

Transformer模型详解(图解最完整版)

Transformer由论文提出,现在是谷歌云TPU推荐的参考模型。论文相关的Tensorflow的代码可以从GitHub获取,其作为Tensor2Tensor包的一部分。哈佛的NLP团队也实现了一个基于PyTorch的版本,并注释该论文。在本文中,我们将试图把模型简化一点,并逐一介绍里面的核心概念,希望让普通读者也能轻易理解。Transformer 与 RNN 不同,可以比较好地并行训练。

2501_91695094的博客 2698

Access to XMLHttpRequest at ‘http://localhost:8000/v1/users/staffs/‘ from origin ‘http://loca 报错

Access to XMLHttpRequest at 'http://localhost:8000/v1/users/staffs/' 这个跨域异步请求访问django服务器 from origin 'http://localhost:8080' has been blocked by CORS policy: 从vue服务器已经被跨域策略阻止 Request header field author is not allowed 这个请求头部 字段不被允许 by Access-Co

sdsdvsdvs的博客 4207

智能硬件产品:做好一款电子硬件产品,工具必不可少

上到战略规划,中至运营策略,下至人性洞察,。最初做产品时,笔者从一开始就陷入到了对工具的痴迷,包括:PS、AutoCAD、PROE、犀牛、Axure、Keyshot、Altium……大概将 2 个多月的时间和精力都消耗在了学习使用工具上。通过工具的运用也确实提高了工作效率,在仅有创意的前提下通过灵活运用工具,一年内便成功申请了多个实用新型专利、设计了多款产品的高保真原型图等。正在上传…重新上传取消。

产品线负责人 1441

第十六章:脚本化HTTP

写在本章内容前: 第十五章:事件处理 涉及到到较多的文字篇幅,介于个人精力问题,暂不更新。主要包含的内容有事件类型、注册事件处理程序、事件处理程序的调用、文档加载事件、鼠标事件、鼠标滚轮事件、拖放事件、文本事件、键盘事件等9块内容。感兴趣的朋友可以留言传内容PDF。如果不着急的话,后期可能更新。敬请关注。 超文本传输协议(HTTP)规定web浏览器如何从web服务器获取文档和向web...

weixin_33713503的博客 214

地下管网巡检机器人:基于YOLOv8与UI界面的智能化系统设计与实现

通过YOLOv8模型的目标检测功能结合实时视频流,我们能够实现对地下管网巡检机器人的智能化控制。系统通过图像识别技术帮助巡检人员快速识别管道中的各种缺陷和障碍物,从而提高巡检效率和准确性。未来,我们可以继续优化模型,增加更多的目标类别,并通过强化学习等方法提高机器人的自主巡检能力。此外,随着硬件技术的发展,未来的巡检机器人可能会具备更强的感知能力和更高的智能水平。

YOLO 528

一文搞懂数据标注

数据标注即通过分类、画框、标注、注释等,对图片、语音、文本等数据进行处理,标记对象的特征,以作为机器学习基础素材的过程。简言之,就是需要给数据做好标注,这样机器就能知道学习哪些内容。

女王の专属领地 9931

HTTP学习——跨域资源共享(CORS)

http学习

Hoorus的窝 1160

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

配置xhr.withCredentials = true对请求的影响以及预检请求

二、讨论下刚刚讲到的Options预检请求 模拟发送预检请求 , 添加自定义请求的头部字段,将请求标记为复杂请求。 三、心得感悟 一切的焦虑都来源于不甘现状又无能为力,唯有自身强大,才能无所畏惧。 行路难,多不易,不知未来,即走好当下每一步,走好了今天,才可能会有更好的明天 !

十一 1235

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

xhr的知识点

get请求 get请求的查询字符串必须使用encordURIComponent编码 post提交 默认情况下,对服务器而言,表单提交和post提交是不一样的,使用xhr模拟表单提交,第一步,将content-type头部设置 为application/x-www-formurlencoded。第二步, 注意: POST请求相比GET请求要占用更多资源。从性能方面说,发送相同数量的数据, GET 请求比 POST 请求要快两倍。 XMLHttpRequest Level...

风华绝代的博客 755

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

XMLHttpRequest.open

XMLHttpRequest.open()方法用于指定 HTTP 请求的参数,或者说初始化 XMLHttpRequest 实例对象。它一共可以接受五个参数。 void open( string method, string url, optional boolean async, optional string user, optional string passwo...

雾里看花叹朦胧 8216

XMLHttpRequest/fetch前端实现流式(stream)请求, 打印机效果

【代码】XMLHttpRequest/fetch前端实现流式(stream)请求, 打印机效果。

weixin_45184061的博客 2002

XmlHttpRequest responseType: ‘stream‘ &&图片代理服务器

它是一个存在于原生对象中的属性。在 Web API 中,XMLHttpRequest对象用于发送 HTTP 或 HTTPS 请求到服务器,并接收响应。属性就是用来指定预期从服务器返回的响应数据的类型。

小漠007的专栏 1771

触发HTTP preflight预检及跨域的处理方法

触发HTTP preflight预检及跨域的处理方法

qq_29517595的博客 2961

深入了解XMLHttpRequest

XMLHttpRequest 浏览器在 XMLHttpRequest 类上定义了他们的 HTTP API。这个类的每一个实例都表示一个独立的请求/响应,并且这个对象的属性和方法允许指定请求细节和提取响应数据。W3C在 XMLHttpRequest 规范的基础上制定了2级 XMLHttpRequest (下文简称 XHR2 )标准草案,且大部分浏览器都已经支持了。 在介绍 XMLHttpReques...

weixin_34343689的博客 491

XMLHttpRequestEventTarget与XMLHttpRequest的addEventListener事件写法

第一种: XMLHttpRequestEventTarget.onabort 包含当请求中止并且abort该对象接收到事件时要调用的函数。 XMLHttpRequestEventTarget.onerror 包含当请求遇到错误并且error此对象接收到事件时调用的函数。 XMLHttpRequestEventTarget.onload 包含在成功获取内容后HTTP请求返回并且load此对象...

jiaoqi6132的博客 2332

预检请求(preflight request)

我们先看看什么是 预检请求:A CORS preflight request is a CORS request that checks to see if the CORS protocol is understood.It is an OPTIONS request, using three HTTP request headers: Access-Control-Request-Method...

qq_21058391的博客 6035
上一篇: 第十四章 浏览器网络入门(Primer on Browser Networking)
下一篇: Chromium Mojo C++ Bindings API(中文)
HowBlinkWorks
博客等级 码龄6年 0粉丝 1原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值