浏览器的通信能力+原生XHR实现监控文件上传进度

549 阅读5分钟

浏览器的通信能力

  • 浏览器可以代替用户完成 http 请求,代替用户解析响应结果,所以称之为用户代理 user agent

在网络层面,浏览器拥有的两大核心能力

  • 自动发出请求的能力
  • 自动解析响应的能力

1693238872268.png

  • 如上图所示,也可以不通过浏览器,直接发送请求,但是响应的结果不是开发者想看到的,那么浏览器的可以帮助开发者发送请求,并且解析响应的内容

自动发出请求的能力

当以下事情发生时,浏览器会代替用户自动发出 http 请求

  1. 用户在地址栏输入了一个 url 地址并按下回车,浏览器会自动解析 URL,并发出一个 GET 请求,同时抛弃当前页面
  2. 用户点击页面中的 a 元素,浏览器会拿到 a 元素的 href 地址,并发出 GET 请求,同时抛弃当前页面
  3. 用户点击提交按钮 <button type="submit">...</button> ,浏览器会获取按钮所在的 <form> 元素的action 地址,同时拿到 method 属性值,最后把表单数据组织到请求体,发出指定方法的请求,同时抛弃当前页面
  4. 解析 HTML 时遇到 <link><img><script><video><audio> 等元素,浏览器会获取到对应的地址,并发出 GET 请求
  5. 用户点击了刷新,浏览器会获取到当前页面的地址,以及当前页面的请求方法,重新发送请求,同时抛弃当前页面

服务器和浏览器的约定:发送 GET 请求,不附带请求体,该默认行为逐步造成 GETPOST 的差异

  1. 浏览器在发送 GET 请求时,不会附带请求体
  2. GET 请求的传递信息量有限(URL 长度有限),适合传递少量数据;POST 请求的传递信息量没有限制,适合传输大量数据
  3. GET 请求只能传递 ASCII 数据,若遇到非 ASCII 数据则需进行编码;POST 请求没有限制
  4. 大部分 GET 请求传递的数据都附带在 path 参数中,能够通过分享地址完整重现页面,但同时暴露了数据,若有敏感数据传递,则不该使用 GET 请求,至少不该放到 path
  5. POST 不会被保存到浏览器的历史记录中,历史记录的本质是复现请求,所以点开浏览器的历史记录都是通过 GET 方式进行请求
  6. 刷新页面时,若当前的页面是通过 POST 请求得到的,则浏览器会提示用户是否重新提交;若是 GET 请求得到的页面则没有提示

1693327097360.png

自动解析响应的能力

  • 浏览器不仅能发送请求,还能针对服务器的各种响应结果做出不同的自动处理

常见的响应处理

  • 识别响应码:浏览器能够自动识别响应码,当出现特殊响应码时浏览器会自动完成处理,如 301、302

  • 自动分析响应头的 Content-Type,并进行不同处理

    • text/plain 普通的纯文本,浏览器通常会将响应体原封不动的显示到页面上
    • text/html html 文档,浏览器通常会将响应体作为页面进行渲染
    • text/javascriptapplication/javascript JavaScript 代码,浏览器通常会使用 JavaScript 执行引擎进行解析执行
    • text/css css 代码,浏览器会将它视为样式
    • image/jpeg 浏览器会将它视为 jpg 图片
    • application/octet-stream 二进制数据,会触发浏览器下载功能
    • attachment 附件,会触发下载功能,该值应放到 Content-Disposition

Ajax的由来

  • 浏览器本身就具备网络通信的能力,但在早期浏览器并没有把这个能力开放给 JavaScript
  • 最早是微软在 IE 浏览器中把这一能力向 JavaScript 开放,让 JavaScript 可以在代码中实现发送请求,且不会刷新页面,这项技术在 2005 年被正式命名为 AJAX(Asynchronous JavaScript And XML>

AJAX 是指在 web 应用程序中异步向服务器发送请求,实现方式有两种

功能点XHRFetch
基本的请求能力
基本的获取响应能力
监控请求进度
监控响应进度
Service Worker 中是否可用
控制 cookie 的携带
控制重定向
请求取消
自定义 referrer
API 风格EventPromise
活跃度停止更新不断更新

XHR进度监控

  • 首先准备一个上传文件的控件,这里以上传图片为例
 /* 控制预览图和进度的显示与隐藏 */
 .upload.select .upload-select {
   display: block;
 }
 .upload.select .preview {
   display: none;
 }
 ​
 .upload.progress .upload-progress {
   display: block;
 }
 ​
 .upload.result .upload-result {
   display: block;
 }
 <div class="upload select">
   <!-- 文件上传控件 -->
   <div class="upload-select">
     <input type="file" />
   </div>
   
   <!-- 展示进度 -->
   <div class="upload-progress" style="--percent: 0">
     <div class="progress-bar"></div>
   </div>
   
   <!-- 展示预览的图片 -->
   <img alt="" class="preview" />
 </div>

1693410810766.png

  • 点击后获取图片文件,并展示预览图和上传进度
 function showArea(areaName) {
   $('.upload').className = `upload ${areaName}`;
 }
 ​
 // 监听change事件,选中文件发生变化
 $('.upload-select input').onchange = (e) => {
   // 获取图片文件对象
   const file = e.target.files[0];
   // 使用图片文件读取器展示文件预览图
   const reader = new FileReader();
   // 将图片文件数据读取成base64
   reader.readAsDataURL(file);
   // 监听图片文件读取完成事件
   reader.onload = (e) => {
     // 读取完成
     $('.preview').src = e.target.result;
      展示需要上传的预览图            
     showArea('progress');
     // 上传文件
     uploadFile(file);
   };
 };

send.gif

  • 上传到服务器,并实时更改上传进度
 function uploadFile(file) {
   // 设置当前上传进度
   setProgress(0);
   // 创建xhr实例
   const xhr = new XMLHttpRequest();
   // 打开一个请求
   xhr.open('POST', 'http://localhost:9527/upload/single');
   // 生成 multipart/form-data 格式的请求体
   const form = new FormData();
   // 构建一条请求体(其中file是文件对象)
   form.append('avatar', file);
   // 监听请求进度
   xhr.upload.onprogress = (e) => {
     console.log(e.loaded, e.total);
     // 计算请求进度,loaded是当前已经上传部分,total是总大小
     const percent = Math.floor((e.loaded / e.total) * 100);
     // 设置进度
     setProgress(percent);
   };
   // 监听上传完成
   xhr.onload = () => {
     showArea('result');
   };
   // 发送请求
   xhr.send(form);
 }
 ​
 // 设置进度的函数
 function setProgress(value) {
   $('.upload-progress').style.setProperty('--percent', value);
 }

send_load.gif