面試問題之——給你圖片的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能夠得到精選前端進階電子書,感謝~

圖片描述

相關文章
相關標籤/搜索