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

从一个需求提及

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

1、经过Ajax请求获取

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

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

转为ArrayBuffer类型

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

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

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

转为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进行获取,接着往下看:安全

2、经过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能够得到精选前端进阶电子书,感谢~

图片描述

相关文章
相关标签/搜索