面试问题之——给你图片的url,你能知道它所占的字节空间吗?
从一个需求说起
这标题起得有点标题党,实际情况是我最近在做一个公司内部工具时,遇到了这么一个需求,给定一个静态资源站点上某张图片的url(比如https://a.xxxcdn.com/demo.jpg),如何获取其存储大小并计算出加载该资源的平均网速呢?注意,是存储大小,而不是图片的宽高尺寸大小。接下来就是我在实现这个需求中总结的一些方式。
一、通过Ajax请求获取
这种方式涉及到XMLHttpRequest的一个属性:responseType,这是属于XMLHttpRequest Level2中的标准属性。
responseType有几种类型:text、blob、arraybuffer、document。默认是使用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,你能知道它所占的字节空间吗?的更多相关文章
- 微信分享功能引入页面-控制分享时候调用的标题、图片、url和微信按钮隐藏显示控制
1.设置分享调用的标题.图片.url预览. 2.控制右上角三个点按钮的隐藏显示(和底部工具栏的显示隐藏--未测试). 3.判断网页是否在微信中被调用. <!doctype html> &l ...
- 使用SQL命令批量替换WordPress站点中图片的URL链接地址
本文由荒原之梦原创,原文链接:http://zhaokaifeng.com/?p=689 前言: 本文记录了使用SQL命令批量替换URL的方法以及除了替换URL之外,网站更换图片URL地址所必须的其他 ...
- Js/使用js来改变图片的url
1.使用js的方式来改变url地址: $('#a1').attr("src","test1.jpg");这种方式来改变图片的url地址: 而不是采用$('#a1 ...
- Redis 存储图片 [base64/url/path]vs[object]
一.base64图片编解码 基本流程:从网络获取下载一张图片.然后base64编码,再base64解码,存到本地E盘根文件夹下. import java.awt.image.BufferedImage ...
- 如何把上传图片时候的文件对象转换为图片的url !
getObjectURL(file) { var url = null; if (window.createObjectURL != undefined) { url = window.createO ...
- 后端返回图片的url,将其转成base64,再次进行上传
//将图片变成base64再上传(主要是转化来自客户端的图片) getUrlBase64=(url, ext)=> { var canvas = document.createEl ...
- 微博获取原图时重定向到图片的url
微博获取原图时重定向到图片的url,所以获取的是乱码 jsoup默认是执行重定向的. //根据Url获取页面对应的Document public static Document getDoc1(Str ...
- Web前端学习(4):显示图片、url与文件路径
本章主旨 介绍<img>标签及其基本属性:介绍URL和文件路径 在上一章中,我简单地介绍了HTML的一些基本标签及基本属性,例如,我们用<p>标签来标记文本段落,用<h1 ...
- java根据图片的url地址下载图片到本地
package com.daojia.haobo.aicircle.util; import sun.misc.BASE64Encoder; import java.io.*; import java ...
随机推荐
- Linux下查看进程和端口信息
1.根据进程名查看进程信息,以查看tomcat进程名为例,查看所对应的进程id为1095(或者使用: ps -aux | grep tomcat 查看占用内存等信息) ps -ef | grep to ...
- Nuxt配置动态路由以及参数校验
动态路由就是带参数的路由.比如我们商品列表里很多商品详细页,这时候就需要动态路由的帮助了. 比如我们新建一个commodity文件夹,新建一个index.vue 文件,然后新建一个_id.vue (以 ...
- 特征值 特征向量 正交分解 PCA
无意间想到的,有时间会补充内容. 还记得学线性代数时计算矩阵的特征值和特征向量,然后这个矩阵就可以用这个特征值和特征向量表示. 这样就可以理解成矩阵其实是多个向量拼在一起的,这样就可以将矩阵和向量建立 ...
- Spring bean继承
Bean 定义继承 bean 定义可以包含很多的配置信息,包括构造函数的参数,属性值,容器的具体信息例如初始化方法,静态工厂方法名,等等. 子 bean 的定义继承父定义的配置数据.子定义可以根据需要 ...
- eureka-获取服务列表(各种状态)
在刚开始做的时候也搜了下搜到的大多是下面的第一种方法,这种方法很简单,但并不是Eureka展示的那个服务列表,他只包括了注册证成功的,或者说eureka中状态为“Up”的实例列表,对于down掉的实例 ...
- 【原】docker基础(一)
1.架构 2.说明 Docker daemon( Docker守护进程):Docker daemon是一个运行在宿主机( DOCKER-HOST)的后台进程.可通过 Docker客户端与之通信. Cl ...
- celery task - 2
# celery task 前言 讨论一个定时任务,一般而言,需要的功能如下: 封装成对象,独立执行: 对象有一些接口,便于了解它的状态: 定时调用: 行为控制,包括重试,成功/失败回调等: 下面分别 ...
- 谈谈一些有趣的CSS题目-- 从倒影说起,谈谈 CSS 继承 inherit
开本系列,讨论一些有趣的 CSS 题目,抛开实用性而言,一些题目为了拓宽一下解决问题的思路,此外,涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题中有你 ...
- java8中的常用日期操作
java8有很多时间上的新api,在操作时间的时候很好用,这儿算是个备忘录吧,(补充中...) 定位某个时间:of方法 LocalDateTime dateTime = LocalDateTime.o ...
- WebRTC的音频编码(转)
一.一个典型的IP通信模型 二.Server2Server技术分类 Server2Server这块也是一个专门的领域,这里只简单分个类. 1.同一国家相同运营商之间: 同一运营商之间也有丢包,在铁通, ...