当前位置: 首页 > news >正文

浏览器工作原理与实践--WebAPI:XMLHttpRequest是怎么实现的

在上一篇文章中我们介绍了setTimeout是如何结合渲染进程的循环系统工作的,那本篇文章我们就继续介绍另外一种类型的WebAPI——XMLHttpRequest。

自从网页中引入了JavaScript,我们就可以操作DOM树中任意一个节点,例如隐藏/显示节点、改变颜色、获得或改变文本内容、为元素添加事件响应函数等等, 几乎可以“为所欲为”了。

不过在XMLHttpRequest出现之前,如果服务器数据有更新,依然需要重新刷新整个页面。而XMLHttpRequest提供了从Web服务器获取数据的能力,如果你想要更新某条数据,只需要通过XMLHttpRequest请求服务器提供的接口,就可以获取到服务器的数据,然后再操作DOM来更新页面内容,整个过程只需要更新网页的一部分就可以了,而不用像之前那样还得刷新整个页面,这样既有效率又不会打扰到用户。

关于XMLHttpRequest,本来我是想一带而过的,后来发现这个WebAPI用于教学非常好。首先前面讲了那么网络内容,现在可以通过它把HTTP协议实践一遍;其次,XMLHttpRequest是一个非常典型的WebAPI,通过它来讲解浏览器是如何实现WebAPI的很合适,这对于你理解其他WebAPI也有非常大的帮助,同时在这个过程中我们还可以把一些安全问题给串起来。

但在深入讲解XMLHttpRequest之前,我们得先介绍下同步回调和异步回调这两个概念,这会帮助你更加深刻地理解WebAPI是怎么工作的。

回调函数 VS 系统调用栈

那什么是回调函数呢(Callback Function)?

将一个函数作为参数传递给另外一个函数,那作为参数的这个函数就是回调函数。简化的代码如下所示:

let callback = function(){console.log('i am do homework')
}
function doWork(cb) {console.log('start do work')cb()console.log('end do work')
}
doWork(callback)

在上面示例代码中,我们将一个匿名函数赋值给变量callback,同时将callback作为参数传递给了doWork()函数,这时在函数doWork()中callback就是回调函数。

上面的回调方法有个特点,就是回调函数callback是在主函数doWork返回之前执行的,我们把这个回调过程称为同步回调。

既然有同步回调,那肯定也有异步回调。下面我们再来看看异步回调的例子:

let callback = function(){console.log('i am do homework')
}
function doWork(cb) {console.log('start do work')setTimeout(cb,1000)   console.log('end do work')
}
doWork(callback)

在这个例子中,我们使用了setTimeout函数让callback在doWork函数执行结束后,又延时了1秒再执行,这次callback并没有在主函数doWork内部被调用,我们把这种回调函数在主函数外部执行的过程称为异步回调。

现在你应该知道什么是同步回调和异步回调了,那下面我们再深入点,站在消息循环的视角来看看同步回调和异步回调的区别。理解了这些,可以让你从本质上理解什么是回调。

我们还是先来回顾下页面的事件循环系统,通过《15 | 消息队列和事件循环:页面是怎么“活”起来的?》的学习,你应该已经知道浏览器页面是通过事件循环机制来驱动的,每个渲染进程都有一个消息队列,页面主线程按照顺序来执行消息队列中的事件,如执行JavaScript事件、解析DOM事件、计算布局事件、用户输入事件等等,如果页面有新的事件产生,那新的事件将会追加到事件队列的尾部。所以可以说是消息队列和主线程循环机制保证了页面有条不紊地运行。

这里还需要补充一点,那就是当循环系统在执行一个任务的时候,都要为这个任务维护一个系统调用栈。这个系统调用栈类似于JavaScript的调用栈,只不过系统调用栈是Chromium的开发语言C++来维护的,其完整的调用栈信息你可以通过chrome://tracing/来抓取。当然,你也可以通过Performance来抓取它核心的调用信息,如下图所示:

消息循环系统调用栈记录

这幅图记录了一个Parse HTML的任务执行过程,其中黄色的条目表示执行JavaScript的过程,其他颜色的条目表示浏览器内部系统的执行过程。

通过该图你可以看出来,Parse HTML任务在执行过程中会遇到一系列的子过程,比如在解析页面的过程中遇到了JavaScript脚本,那么就暂停解析过程去执行该脚本,等执行完成之后,再恢复解析过程。然后又遇到了样式表,这时候又开始解析样式表……直到整个任务执行完成。

需要说明的是,整个Parse HTML是一个完整的任务,在执行过程中的脚本解析、样式表解析都是该任务的子过程,其下拉的长条就是执行过程中调用栈的信息。

每个任务在执行过程中都有自己的调用栈,那么同步回调就是在当前主函数的上下文中执行回调函数,这个没有太多可讲的。下面我们主要来看看异步回调过程,异步回调是指回调函数在主函数之外执行,一般有两种方式:

  • 第一种是把异步函数做成一个任务,添加到信息队列尾部;

  • 第二种是把异步函数添加到微任务队列中,这样就可以在当前任务的末尾处执行微任务了。

XMLHttpRequest运作机制

理解了什么是同步回调和异步回调,接下来我们就来分析XMLHttpRequest背后的实现机制,具体工作过程你可以参考下图:

XMLHttpRequest工作流程图

这是XMLHttpRequest的总执行流程图,下面我们就来分析从发起请求到接收数据的完整流程。

我们先从XMLHttpRequest的用法开始,首先看下面这样一段请求代码:

 function GetWebData(URL){/*** 1:新建XMLHttpRequest请求对象*/let xhr = new XMLHttpRequest()/*** 2:注册相关事件回调处理函数 */xhr.onreadystatechange = function () {switch(xhr.readyState){case 0: //请求未初始化console.log("请求未初始化")break;case 1://OPENEDconsole.log("OPENED")break;case 2://HEADERS_RECEIVEDconsole.log("HEADERS_RECEIVED")break;case 3://LOADING  console.log("LOADING")break;case 4://DONEif(this.status == 200||this.status == 304){console.log(this.responseText);}console.log("DONE")break;}}xhr.ontimeout = function(e) { console.log('ontimeout') }xhr.onerror = function(e) { console.log('onerror') }/*** 3:打开请求*/xhr.open('Get', URL, true);//创建一个Get请求,采用异步/*** 4:配置参数*/xhr.timeout = 3000 //设置xhr请求的超时时间xhr.responseType = "text" //设置响应返回的数据格式xhr.setRequestHeader("X_TEST","time.geekbang")/*** 5:发送请求*/xhr.send();
}

上面是一段利用了XMLHttpRequest来请求数据的代码,再结合上面的流程图,我们可以分析下这段代码是怎么执行的。

第一步:创建XMLHttpRequest对象。

当执行到let xhr = new XMLHttpRequest()后,JavaScript会创建一个XMLHttpRequest对象xhr,用来执行实际的网络请求操作。

第二步:为xhr对象注册回调函数。

因为网络请求比较耗时,所以要注册回调函数,这样后台任务执行完成之后就会通过调用回调函数来告诉其执行结果。

XMLHttpRequest的回调函数主要有下面几种:

  • ontimeout,用来监控超时请求,如果后台请求超时了,该函数会被调用;

  • onerror,用来监控出错信息,如果后台请求出错了,该函数会被调用;

  • onreadystatechange,用来监控后台请求过程中的状态,比如可以监控到HTTP头加载完成的消息、HTTP响应体消息以及数据加载完成的消息等。

第三步:配置基础的请求信息。

注册好回调事件之后,接下来就需要配置基础的请求信息了,首先要通过open接口配置一些基础的请求信息,包括请求的地址、请求方法(是get还是post)和请求方式(同步还是异步请求)。

然后通过xhr内部属性类配置一些其他可选的请求信息,你可以参考文中示例代码,我们通过xhr.timeout = 3000来配置超时时间,也就是说如果请求超过3000毫秒还没有响应,那么这次请求就被判断为失败了。

我们还可以通过xhr.responseType = "text"来配置服务器返回的格式,将服务器返回的数据自动转换为自己想要的格式,如果将responseType的值设置为json,那么系统会自动将服务器返回的数据转换为JavaScript对象格式。下面的图表是我列出的一些返回类型的描述:

假如你还需要添加自己专用的请求头属性,可以通过xhr.setRequestHeader来添加。

第四步:发起请求。

一切准备就绪之后,就可以调用xhr.send来发起网络请求了。你可以对照上面那张请求流程图,可以看到:渲染进程会将请求发送给网络进程,然后网络进程负责资源的下载,等网络进程接收到数据之后,就会利用IPC来通知渲染进程;渲染进程接收到消息之后,会将xhr的回调函数封装成任务并添加到消息队列中,等主线程循环系统执行到该任务的时候,就会根据相关的状态来调用对应的回调函数。

  • 如果网络请求出错了,就会执行xhr.onerror;

  • 如果超时了,就会执行xhr.ontimeout;

  • 如果是正常的数据接收,就会执行onreadystatechange来反馈相应的状态。

这就是一个完整的XMLHttpRequest请求流程,如果你感兴趣,可以参考下Chromium对XMLHttpRequest的实现,点击这里查看代码。

XMLHttpRequest使用过程中的“坑”

上述过程看似简单,但由于浏览器很多安全策略的限制,所以会导致你在使用过程中踩到非常多的“坑”。

浏览器安全问题是前端工程师避不开的一道坎,通常在使用过程中遇到的“坑”,很大一部分都是由安全策略引起的,不管你喜不喜欢,它都在这里。本来很完美的一个方案,正是由于加了安全限制,导致使用起来非常麻烦。

而你要做的就是去正视这各种的安全问题。也就是说要想更加完美地使用XMLHttpRequest,你就要了解浏览器的安全策略。

下面我们就来看看在使用XMLHttpRequest的过程中所遇到的跨域问题和混合内容问题。

1. 跨域问题

比如在极客邦的官网使用XMLHttpRequest请求极客时间的页面内容,由于极客邦的官网是www.geekbang.org,极客时间的官网是time.geekbang.org,它们不是同一个源,所以就涉及到了跨域(在A站点中去访问不同源的B站点的内容)。默认情况下,跨域请求是不被允许的,你可以看下面的示例代码:

var xhr = new XMLHttpRequest()
var url = 'https://time.geekbang.org/'
function handler() {switch(xhr.readyState){case 0: //请求未初始化console.log("请求未初始化")break;case 1://OPENEDconsole.log("OPENED")break;case 2://HEADERS_RECEIVEDconsole.log("HEADERS_RECEIVED")break;case 3://LOADING  console.log("LOADING")break;case 4://DONEif(this.status == 200||this.status == 304){console.log(this.responseText);}console.log("DONE")break;}
}function callOtherDomain() {if(xhr) {    xhr.open('GET', url, true)xhr.onreadystatechange = handlerxhr.send();}
}
callOtherDomain()

你可以在控制台测试下。首先通过浏览器打开www.geekbang.org,然后打开控制台,在控制台输入以上示例代码,再执行,会看到请求被Block了。控制台的提示信息如下:

Access to XMLHttpRequest at 'https://time.geekbang.org/' from origin 'https://www.geekbang.org' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

因为 www.geekbang.org 和 time.geekbang.com 不属于一个域,所以以上访问就属于跨域访问了,这次访问失败就是由于跨域问题导致的。

2. HTTPS混合内容的问题

了解完跨域问题后,我们再来看看HTTPS的混合内容。HTTPS混合内容是HTTPS页面中包含了不符合HTTPS安全要求的内容,比如包含了HTTP资源,通过HTTP加载的图像、视频、样式表、脚本等,都属于混合内容。

通常,如果HTTPS请求页面中使用混合内容,浏览器会针对HTTPS混合内容显示警告,用来向用户表明此HTTPS页面包含不安全的资源。比如打开站点 https://www.iteye.com/groups,可以通过控制台看到混合内容的警告,参考下图: 

HTTPS混合内容警告

从上图可以看出,通过HTML文件加载的混合资源,虽然给出警告,但大部分类型还是能加载的。而使用XMLHttpRequest请求时,浏览器认为这种请求可能是攻击者发起的,会阻止此类危险的请求。比如我通过浏览器打开地址 https://www.iteye.com/groups,然后通过控制台,使用XMLHttpRequest来请求 http://img-ads.csdn.net/2018/201811150919211586.jpg,这时候请求就会报错,出错信息如下图所示: 

使用XMLHttpRequest混合资源失效

总结

好了,今天我们就讲到这里,下面我来总结下今天的内容。

首先我们介绍了回调函数和系统调用栈;接下来我们站在循环系统的视角,分析了XMLHttpRequest是怎么工作的;最后又说明了由于一些安全因素的限制,在使用XMLHttpRequest的过程中会遇到跨域问题和混合内容的问题。

本篇文章跨度比较大,不是单纯地讲一个问题,而是将回调类型、循环系统、网络请求和安全问题“串联”起来了。

对比上一篇文章,setTimeout是直接将延迟任务添加到延迟队列中,而XMLHttpRequest发起请求,是由浏览器的其他进程或者线程去执行,然后再将执行结果利用IPC的方式通知渲染进程,之后渲染进程再将对应的消息添加到消息队列中。如果你搞懂了setTimeout和XMLHttpRequest的工作机制后,再来理解其他WebAPI就会轻松很多了,因为大部分WebAPI的工作逻辑都是类似的。

相关文章:

浏览器工作原理与实践--WebAPI:XMLHttpRequest是怎么实现的

在上一篇文章中我们介绍了setTimeout是如何结合渲染进程的循环系统工作的,那本篇文章我们就继续介绍另外一种类型的WebAPI——XMLHttpRequest。 自从网页中引入了JavaScript,我们就可以操作DOM树中任意一个节点,例如隐藏/显示节点、改变颜色、…...

TCP网络协议栈和Posix网络部分API总结

文章目录 Posix网络部分API综述TCP协议栈通信过程TCP三次握手和四次挥手(看下图)三次握手常见问题?为什么是三次握手而不是两次?三次握手和哪些函数有关?TCP的生命周期是从什么时候开始的? 四次挥手通信状态…...

《解释器模式(极简c++)》

本文章属于专栏- 概述 - 《设计模式(极简c版)》-CSDN博客 模式说明 方案: 对每个data建立一个单点解释器对象X,dataA和dataB之间的关系,建立一个关系解释器对象Y,这里的Y处理的是X1和X2。这样,…...

c#仿ppt案例

画曲线 namespace ppt2024 {public partial class Form1 : Form{public Form1(){InitializeComponent();}//存放所有点的位置信息List<Point> lstPosition new List<Point>();//控制开始画的时机bool isDrawing false;//鼠标点击开始画private void Form1_MouseD…...

10.图像高斯滤波的原理与FPGA实现思路

1.概念 高斯分布 图像滤波之高斯滤波介绍 图像处理算法|高斯滤波   高斯滤波(Gaussian filter)包含很多种&#xff0c;包括低通、高通、带通等&#xff0c;在图像上说的高斯滤波通常是指的高斯模糊(Gaussian Blur)&#xff0c;是一种高斯低通滤波。通常这个算法也可以用来模…...

WebGIS 地铁交通线网 | 图扑数字孪生

数字孪生技术在地铁线网的管理和运维中的应用是一个前沿且迅速发展的领域。随着物联网、大数据、云计算以及人工智能技术的发展&#xff0c;地铁线网数字孪生在智能交通和智慧城市建设中的作用日益凸显。 图扑软件基于 HTML5 的 2D、3D 图形渲染引擎&#xff0c;结合 GIS 地图…...

Docker 哲学 - push 本机镜像 到 dockerhub

注意事项&#xff1a; 1、 登录 docker 账号 docker login 2、docker images 查看本地镜像 3、注意的是 push镜像时 镜像的tag 需要与 dockerhub的用户名保持一致 eg&#xff1a;本地镜像 express:1 直接 docker push express:1 无法成功 原因docker不能识别 push到哪里 …...

大数据学习第十二天(hadoop概念)

1、服务器之间数据文件传递 1&#xff09;服务器之间传递数据&#xff0c;依赖ssh协议 2&#xff09;http协议是web网站之间的通讯协议&#xff0c;用户可已通过http网址访问到对应网站数据 3&#xff09;ssh协议是服务器之间&#xff0c;或windos和服务器之间传递的数据的协议…...

管理科学笔记

1.线性规划 画出区域&#xff0c;代入点计算最大最小值 2.最小生成树 a.断线法&#xff0c;从大的开始断 b.选择法&#xff0c;从小的开始选 3.匈牙利法 维度数量直线覆盖所有的0 4.一直选最当前路线最短路径 5.线性规划 6.决策论...

WebKit结构简介

WebKit是一款开源的浏览器引擎&#xff0c;用于渲染网页内容。它负责将HTML、CSS和JavaScript等网络资源转换为用户在屏幕上看到的图形界面。WebKit是一个跨平台的引擎&#xff0c;可以在多种操作系统上运行&#xff0c;如Windows、macOS、Linux等。 以下是一篇关于WebKit结构…...

Kaggle:收入分类

先看一下数据的统计信息 import pandas as pd # 加载数据&#xff08;保留原路径&#xff0c;但在实际应用中建议使用相对路径或环境变量&#xff09; data pd.read_csv(r"C:\Users\11794\Desktop\收入分类\training.csv", encodingutf-8, encoding_errorsrepl…...

【Go】十七、进程、线程、协程

文章目录 1、进程、线程2、协程3、主死从随4、启动多个协程5、使用WaitGroup控制协程退出6、多协程操作同一个数据7、互斥锁8、读写锁9、deferrecover优化多协程 1、进程、线程 进程作为资源分配的单位&#xff0c;在内存中会为每个进程分配不同的内存区域 一个进程下面有多个…...

深入剖析JavaScript中的this(上)

在Javascript中&#xff0c;this 关键字是一个非常重要的概念&#xff0c;this这个关键字可以说是很常见也用的很多&#xff0c;说它简单也很简单&#xff0c;说它难也很难。我们经常会用到this&#xff0c;也经常会因为this头疼&#xff0c;是一个经常被误解和误用的概念&…...

Junit深入讲解(JAVA单元测试框架)

1、此处用的是Junit5&#xff0c;此处pom文件需要引的依赖是 <dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.9.1</version><scope>test</scope></depende…...

Spring boot如何执行单元测试?

Spring Boot 提供了丰富的测试功能&#xff0c;主要由以下两个模块组成&#xff1a; spring-boot-test&#xff1a;提供测试核心功能。spring-boot-test-autoconfigure&#xff1a;提供对测试的自动配置。 Spring Boot 提供了一个 spring-boot-starter-test一站式启动器&…...

Django详细教程(一) - 基本操作

文章目录 前言一、安装Django二、创建项目1.终端创建项目2.Pycharm创建项目&#xff08;专业版才可以&#xff09;3.默认文件介绍 三、创建app1.app介绍2.默认文件介绍 四、快速上手1.写一个网页步骤1&#xff1a;注册app 【settings.py】步骤2&#xff1a;编写URL和视图函数对…...

Qt编译QScintilla(C++版)过程记录,报错-lqscintilla2_qt5d、libqscintilla2_qt5找不到问题解决

Qt编译QScintilla [C版] 过程记录 本文是编译该 QScintilla 组件库供 QtCreater 开发 C 桌面软件 流程记录一、编译环境 系统&#xff1a; Windows 10Qt&#xff1a;Qt 5.14.2编译套件&#xff1a;MinGW 64Qscintilla&#xff1a;QScintilla_src-2.11.6 二、下载链接 网站链…...

android QtScrcpy 共享屏幕 获取本地Address

android QtScrcpy https://gitee.com/B arryda/QtScrcpy scrcpy - 手机无线投屏到电脑 https://zhuanlan.zhihu.com/p/80264357?utm_sourcewechat_session public String getLocalIpAddress() { String ipv4; List<NetworkInterface> nilist …...

【SQL Server】1. 认识+使用

1. 创建数据库的默认存储路径 C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft SQL Server 2008 R2 当我们选择删除数据库时&#xff0c;对应路径下的文件也就删除了 2. 导入导出数据工具的路径 3. 注册数据库遇到的问题 ??? 目前的问题就是服务器新建…...

视频汇聚/安防监控/视频存储EasyCVR平台EasyPlayer播放器更新:新增【性能面板】

视频汇聚/安防监控/视频存储平台EasyCVR基于云边端架构&#xff0c;可以在复杂的网络环境中快速、灵活部署&#xff0c;平台视频能力丰富&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

Java - Mysql数据类型对应

Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...