从一个需求说起

这标题起得有点标题党,实际情况是我最近在做一个公司内部工具时,遇到了这么一个需求,给定一个静态资源站点上某张图片的url(比如https://a.xxxcdn.com/demo.jpg),如何获取其存储大小并计算出加载该资源的平均网速呢?注意,是存储大小,而不是图片的宽高尺寸大小。接下来就是我在实现这个需求中总结的一些方式。

一、通过Ajax请求获取

这种方式涉及到XMLHttpRequest的一个属性:responseType,这是属于XMLHttpRequest Level2中的标准属性。
responseType有几种类型:textblobarraybufferdocument。默认是使用text类型,也就是我们可以从返回结果中的responseText取到服务端返回的主体数据。而如果你设置为其他类型(比如blob),会发现responseText无法正常获取了,如下图:

既然XMLHttpRequest对象可以帮我们把返回结果进行转换,那我们就可以借助它来实现这个需求了,先看看转为arraybuffer会返回什么结果:

转为ArrayBuffer类型

从上图可以看到,通过XMLHttpRequest实例对象的response字段,我们可以取到一个ArrayBuffer类型的对象,它是用来表示通用的、固定长度的原始二进制数据缓冲区。它暴露出的byteLength,代表了数组的字节大小,也就是B(1KB = 1024B),从而可知该文件的存储大小为14896B

如果仔细观察,会发现byteLength与上图打印出来的[[Int8Array]][[Uint8Array]]的长度是一致的,这里稍微解释一下它的原理:

首先,我们可以使用Uint8Array(request.response),将这个ArrayBuffer对象转换为Uint8Array类型数组。那么,为何两者的长度值是一样的呢?因为ArrayBuffer的byteLength表示的是其字节大小,众所周知一个字节(1B)是由8个bit组合的,比如01010101。而Uint8Array数组中的每一个值,都代表8个二进制位转换为十进制后的值,所以ArrayBuffer中有多少个字节,那么对应的Uint8Array就有多少个值。Int8Array也同理,区别在于Int8Array是有符号整数,Uint8Array是无符号的。

转为Blob类型

如果将responseType设置为blob,那么可想而知XMLHttpRequest对象会将结果转为Blob类型,如下图:

通过它的size,我们也可以正确获取到该文件的字节大小。并且,我们应该知道,Blob和ArrayBuffer之间是可以通过HTML5的FileReader互相转化的,有兴趣的读者可以通过这篇文章进行了解。

小结

至此,这个需求已经可以实现了,但是,通过Ajax的手段去请求一个文件,还得服务器的CORS配置允许跨域,但一般服务器不会设置Access-Control-Allow-Origin*,否则随意哪个域名都可以请求它的资源了。于是,我们可以联想到,通过img发起的请求不就可以支持跨域吗?但可惜,img的onload事件的回调参数中不会为我们提供图片文件的大小信息,它只会提供图片元素本身的一些信息,比如宽、高等HTMLImageElement中的属性。

后来,经过stackoverflow、MDN搜寻一番之后,发现了一个更值得推荐的做法,那就是:img加载图片配合Performance API进行获取,接着往下看:

二、通过Performance API

通常,我们可以使用performance的timing来测量一个页面的各项性能指标,比如DNS查询时间、HTTP连接时间、首字节时间、可交互式时间等等,但除此之外,performance还提供了一项能力,可以让我们探测某个被加载的资源的各项指标:

通过MDN文档,我们可以了解到,Performace API在浏览器进行资源加载时,会自动生成每个资源的PerformanceEntry对象,自动生成的PerformanceEntry对象,其entryType一般有三种:resource、navigation和paint。

而对于图片、css、脚本等资源文件,其entryType是resource,与之相对应的是扩展了PerformanceEntry的PerformanceResourceTiming接口,其中的属性提供有关获取资源大小的数据以及初始化时获取的资源类型。比如,一个图片资源对应的PerformanceResourceTiming对象中,会包含以下属性(部分):

可以看到,最后的三个属性,都可以表示该资源的大小,我们选取encodedBodySize来表示作为该资源的存储大小,因为该值与浏览器Network面板中显示的该资源大小是一致的。让我们通过代码实践一下。

我们使用img加载一个资源文件,并在其onload回调中使用performance的API来获取它的PerformanceResourceTiming:

var img = new Image();
var resource = 'https://a.xx.com/xxx.png';
img.src = resource;
img.onload = function() {
console.log(performance.getEntriesByName(resource));
}

结果如下:

跟预期的不同,三个可以标识资源大小的属性,都返回了0,而当我换了其他服务器上的另一张图片时,三个属性却返回了预期的值:

为何有些行得通有些却不行呢?经过仔细对比,我发现了它们之间的区别,凡是可以被检测出大小的资源,在Response Header中都有一个字段:timing-allow-origin: *。那么,timing-allow-origin的作用是啥呢?这里我给出MDN文档的解释:

响应头Timing-Allow-Origin用于指定特定站点,以允许其访问Resource Timing API提供的相关信息,否则这些信息会由于跨源限制将被报告为零

原来,只有设置了该响应头的资源,才能被指定域名的脚本进行performance的相关检测。

小结

到这里我们已经可以对这两种方法进行比对了:

两种方法都可以顺利获取到资源准确的存储字节大小,但前者通过ajax进行请求,需要服务器配合设置CORS的Access-Control-Allow-Origin,但对于一个服务器来讲,出于安全考虑,将这个字段设置为*是不太可能的。

然而,第二种方法也需要服务器配合设置timing-allow-origin字段,但区别在于,这个字段的设置只用于performance的检测,几乎不需要付出安全成本。不过,在谷歌上我也找到了一个利用 timing-allow-origin: * 来检测接口返回时间从而推断接口状态的漏洞,所以,最好的方式就是,只对专门放置资源文件的服务器设置该响应头,或者在主服务器中,针对资源文件的请求加入该响应头,就可以避免这种漏洞了。

并且,同理可得,对于其他资源,比如字体、样式文件、脚本文件等,也可以通过以上方式进行存储大小的检测。

引申(凑字数)——关于图片的跨域能力

众所周知,img和script都是支持跨域访问的,这是一般浏览器都会提供的能力,但大家有没有想过,图片发出的请求,和XMLHttpRequest发出的ajax请求,其实都是正常的http请求,为啥img和script就可以绕过同源策略呢?我们可以观察一下两个跨域的资源请求的请求头。

一个是用img发出的请求:

一个是XMLHttpRequest的get请求:

这里有两个差别,一个是Accept,一个是Origin,Accept只是告诉服务器客户端可以接受的返回内容类型,而Origin,才是决定是否触发浏览器同源策略的关键。浏览器接收到请求的响应后,通过判断响应头中是否有Access-Control-Allow-Origin字段并验证它与当前请求的Origin是否匹配,来决定是否让用户读取到返回值,如果没有Access-Control-Allow-Origin字段,那么我们会看到一个很常见的浏览器报错:

反之,像img发起的请求,没有携带Origin字段,那么对于这个请求,浏览器就会忽略这层判断,自然就绕过了同源策略的限制。

ps:欢迎关注微信公众号——前端漫游指南,会定期发布优质原创文章和译文,关注公众号福利:回复666可以获得精选前端进阶电子书,感谢~

面试问题之——给你图片的url,你能知道它所占的字节空间吗?的更多相关文章

  1. 微信分享功能引入页面-控制分享时候调用的标题、图片、url和微信按钮隐藏显示控制

    1.设置分享调用的标题.图片.url预览. 2.控制右上角三个点按钮的隐藏显示(和底部工具栏的显示隐藏--未测试). 3.判断网页是否在微信中被调用. <!doctype html> &l ...

  2. 使用SQL命令批量替换WordPress站点中图片的URL链接地址

    本文由荒原之梦原创,原文链接:http://zhaokaifeng.com/?p=689 前言: 本文记录了使用SQL命令批量替换URL的方法以及除了替换URL之外,网站更换图片URL地址所必须的其他 ...

  3. Js/使用js来改变图片的url

    1.使用js的方式来改变url地址: $('#a1').attr("src","test1.jpg");这种方式来改变图片的url地址: 而不是采用$('#a1 ...

  4. Redis 存储图片 [base64/url/path]vs[object]

    一.base64图片编解码 基本流程:从网络获取下载一张图片.然后base64编码,再base64解码,存到本地E盘根文件夹下. import java.awt.image.BufferedImage ...

  5. 如何把上传图片时候的文件对象转换为图片的url !

    getObjectURL(file) { var url = null; if (window.createObjectURL != undefined) { url = window.createO ...

  6. 后端返回图片的url,将其转成base64,再次进行上传

      //将图片变成base64再上传(主要是转化来自客户端的图片)  getUrlBase64=(url, ext)=> {     var canvas = document.createEl ...

  7. 微博获取原图时重定向到图片的url

    微博获取原图时重定向到图片的url,所以获取的是乱码 jsoup默认是执行重定向的. //根据Url获取页面对应的Document public static Document getDoc1(Str ...

  8. Web前端学习(4):显示图片、url与文件路径

    本章主旨 介绍<img>标签及其基本属性:介绍URL和文件路径 在上一章中,我简单地介绍了HTML的一些基本标签及基本属性,例如,我们用<p>标签来标记文本段落,用<h1 ...

  9. java根据图片的url地址下载图片到本地

    package com.daojia.haobo.aicircle.util; import sun.misc.BASE64Encoder; import java.io.*; import java ...

随机推荐

  1. 「CSP-S模拟赛」2019第三场

    目录 T1 「POI2007」山峰和山谷 Ridges and Valleys 题目 考场思路(几近正解) 正解 T2 「JOI 2013 Final」 现代豪宅 题目 考场思路(正解) T3 「SC ...

  2. 解决ubuntu和win10双系统时间不一致

    1.在ubuntu下安装ntpdate sudo apt install ntpdate 2.设置同步windows时间 sudo ntpdate time.windows.com 3.把时间更新到硬 ...

  3. jvm字节码助记符

    反编译指令 javap -c xxxx.class JVM参数设置 -xx:+<option>                  开启option -xx: -<option> ...

  4. 台式机windows10 进入安全模式

    按住shift键不松,在登录界面点击重启,即可进入安全模式!!!!

  5. java多线程信息共享

    上篇文章知识介绍了多线程的创建和启动问题,各个子线程和子线程或者说子线程和main线程没有信息的交流,这篇文章主要探讨线程之间信息共享以及交换问题.这篇文章主要以一个卖票例子来展开. 继承Thread ...

  6. opencv:绘制图像直方图

    #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace st ...

  7. 关于XMlHttpRequest对象

    //创建XMLHttpRequest对象的三种方法 1 var xhr = createXMLHttpRequest(); function createXMLHttpRequest(){ try{ ...

  8. cordova将vue项目打包成apk

    1,若vue项目不在cordova项目里,直接把它复制进来,避免改动代码的麻烦 2,直接按照以下链接进行操作即可 链接:https://www.cnblogs.com/qirui/p/8421372. ...

  9. Laravel Vuejs 实战:开发知乎 (6)发布问题

    1.view部分: 安装一个扩展包:Laravel-UEditor composer require "overtrue/laravel-ueditor:~1.0" 配置 添加下面 ...

  10. 【PAT甲级】1077 Kuchiguse (20 分)(cin.ignore()吃掉输入n以后的回车接着用getine(cin,s[i])输入N行字符串)

    题意: 输入一个正整数N(<=100),接着输入N行字符串.输出N行字符串的最长公共后缀,否则输出nai. AAAAAccepted code: #include<bits/stdc++. ...