因為覺得今年剩下的一週不會再拍什麼照片了,所以就先整理了 2012 年的回顧,和 去年 一樣一個月挑選一張,今年總照片數約是去年的 1.5 倍,不過 12 月的照片超少的,基本上是只有去一次活動,除了 12 月外,其實挑起來都蠻辛苦的...
閱讀「2012」全文Cache Control 與 ETag
俗話說的好,最快的連線就是不要連線,最快的下載就是不要下載,訪客連到網站的網路狀況其實是不容易由網站這邊來控制的,所以要提升網頁的速度,除了提升網路的可達性外,還有一個方法就是 cache,瀏覽器在需要某個檔案的時候,首先它會檢查是否有 cache,有的話會看有沒有過期,過期的話就根據現有資訊去問 server 有沒有新版,如果 server 比對之後發現有新版的,才會把要求的檔案傳給瀏覽器。這一個流程一共有三個判斷點,分別是:
- 是否需要無視 cache,前面沒講到,可能是 cache 設定或是瀏覽器設定
- 有沒有 cache、有沒有過期
- Server 端檔案有沒有更新
Cache 的機制早在 HTTP 1.0 時就有制訂了,不過當時只有 Expires 和 Pragma 這兩個 header,其中一個可以指定 cache 過期的時間,另外一個就只能指定叫瀏覽器 no-cache,到了 HTTP 1.1 之後,改成用 Cache-Control 提供更多功能來控制,支援 HTTP 1.1 的瀏覽器,只要看到 Cache-Control 就會忽略 Expires,除了因為 Cache-Control 的功能比較強大外,單純就過期時間的這點來看,Expires 看的是 ISO Time,會有 server 和 client 之間的時差問題,而 Cache-Control 則是用 max-age 直接說這個 Cache 可以活多久,就沒了時差問題。
Cache-Control 除了 max-age 外還有很多參數可以用,簡單介紹幾個常用的:
- no-store, 完全不存下來,所以完全沒有 cache
- no-cache, 雖然會 cache,但還是會每次都問有沒有新內容,就是三個判斷點的第一個
- private, 限制在只有現在這個使用者可以用,通常用於敏感資料
- public, cache 公開讓不同使用者用,如果是有 HTTP Auth 的網頁,預設會是 private cache
- must-revalidate, 在一些情形下會去檢查內容是否有更新,像是使用者自己重新造訪頁面時,也是第一個判斷點
根據 Cache-Control 的規則,瀏覽器在有需要時會去問 Server 是否有新版本,而這裡根據的資訊就是 Date 和 ETag 兩個資訊。Date 很簡單,就是回 request 的時間,ETag 全名是 Entity Tag,可以想成是該檔案的版本 hash,理想上確實是用 hash 來當 ETag 最合適,不過不可能每次 request 都算 hash,所以 Apache 內建的 ETag 機制是用 inode、檔案大小和最後修改時間來產生的,不過這種方法有個缺點,在 YSlow 的 guide 有提到其中的 inode 在有負載平衡的架構下,不同機器會產生出不一樣的 ETag,結果反而可能會造成不需要重新抓的檔案又下載一次,雖然說 Apache 也是可以指定說不要用 inode 來生 ETag 啦。
個人建議是如果是 CMS 之類的系統,每個節點都可以在變動時重算 hash,然後在 response 的時候加上 ETag header,其他靜態檔案就用 Apache 的 ETag,有負載平衡機制的話就把 inode 的部分拿掉就好了。當然也是可以照 YSlow 的建議就是完全不用 ETag,只看修改時間,當然有個小缺點是,時間單位的最小精度是秒,如果是一秒內內容就會一直變動的話,就不適合使用了,似乎也很少這種需求就是(又要 cache 又要在一秒內數次變動還要能反應)。
瀏覽器如果要問 Server 有沒有新東西的話,就會帶著這兩個資訊一起去問,Date 會變成 If-Modified-Since,字面意思就是從那個時間點以後有更新的話。Etag 則會變成 If-None-Match,字面上意思就是如果和這個不一樣的話。Server 端除非是 Apache 直接 host 的靜態檔案,都要 Server Side 的程式自己來處理,有些 Framework 就有內建支援,像是 Rails。如果要自己實做的話,其實檢查是否有新東西這個動作有分嚴謹 (strong) 和寬鬆 (weak) 兩種驗證方式,其中用更新時間判斷的話,是屬於寬鬆驗證的,因為它的時間精度只有一秒。而 ETag 也不是完全就是嚴謹的驗證方式,其實 ETag 的格式有兩種:
ETag: "1234abcd"
ETag: W/"1234abcd"
第一種是嚴謹的 ETag,第二種就是寬鬆的格式,W 代表的是 weak,如果宣告是寬鬆的話,那代表的意思是檔案內容不完全相同,但是可以互相通用,像是有沒有最小化過的 JS/CSS,更新解析度的圖片或是小修正排版的文章等等都是,不過如果用寬鬆判斷,由於檔案內容可能不相同,所以就無法用區段下載的功能,也就是所謂的續傳功能,通常這會搭配的是 If-Match,確定要抓的檔案是同一份。理想上支援寬鬆驗證的話可以減少更多的實際傳輸,因為一些小修改可以不用更新訪客端的 cache,不過實際上好像沒看到有人實做,而且實做起來也不是很簡單,所以一般看到有用 ETag 的話都是用嚴謹版的。
總之,如果 server 端判斷說沒有新內容的話,那就回個 304 Not Modified 的 header 就可以了,同時還可以趁機更新 cache 的 expire time,這樣就不會內容依然沒更新,但是 cache 過期讓瀏覽器還是一直問你更新了沒。
最前面提到三個判斷點當中的第一二兩個判斷點是用來決定要不要跟 Server 發 request,而不管這邊判斷的依據為何,只要結果是有發 request 的話,都還是會照著標準的流程來看 Server 端檔案是否有更新,不過一些情形下,像是瀏覽器關閉 cache 支援的時候,發出去的 request 不會有 If-Modified-Since 和 If-None-Match,所以這時候一定會把檔案抓一份回來。
最後設定完後,以本 blog 為例,還沒有 cache 時:
有 cache 還沒過期,request 不會發出,速度最快:
Cache 過期去問 server 有沒有更新版時,檔案沒更新所以都是 304 沒抓內容下來:
參考資料:
mod_proxy 的注意事項
最近調整 HTTPS 支援的時候,啟用了 Apache 的 mod_proxy,結果我沒注意到預設的 config 檔案會把 open proxy 開起來,沒兩天就被人 scan 到然後狂打,結果就是上圖那個慘狀,我大概第二天有覺得怪怪的,到第三天才發現問題在哪,可以看到關掉之後流量馬上掉下來,又過了好幾天才比較看不出來,不過看 CPU 和 Disk IO:
到現在都還沒回覆到之前的狀況,尤其是 Disk IO,因為會一直寫 log,到現在還是和當初的 loading 有一段差距,後遺症持續很久,到現在還是一直會被打,整個就是長尾理論(?)的活例子。
在這邊奉勸大家要用 Apache 的 mod_proxy 的時候記得要把 ProxyRequests 設成 Off 啊,預設是 On 什麼的根本是蓄意謀殺啊!
Flickr Share with Template
前幾天在做 srcset 支援 支援時,遇到一個問題是,Flickr 雖然有提供到 2048px 和原始大小的檔案,可是這些高解析度檔案的檔名和 1024px 以下的檔案是不一樣的,所以不能用簡單的加 postfix 來找到位置,所以我每要貼一張圖,就要辛苦的去開 Flickr 的各種 size 來抓他的路徑,然後剪剪貼貼組合出原始碼,這實在太辛苦了,而且手工作業長期下來很容易會出錯,剛好我也對 Flickr 預設的分享用 HTML code 不支援 XHTML 不滿很久了,所以我就決定寫個 Chrome extension 來做這件事。
目前還沒正式公開,沒有上架,專案在 Github 上,現在因為還沒上架,要使用比較麻煩一點,把專案 clone 到本機後,先開啟 Chrome 擴充功能的開發人員模式,然後選 載入未封裝擴充功能,路徑指到專案下的 source 去。
目前樣版語法用 mustache 語法,大概看一下預設的樣版就能懂怎麼用了,不過不是很正式的 parser 來處理,以後可能會換成 hogan.js 引擎,東西現狀其實只是可以用,還沒有寫文件,寫 License...等,先貼上來其實主要目的是,徵求一組可用的 icon,沒有 icon 就上架好像怪怪,有興趣提供的麻煩跟我聯絡,專案本身會是 MIT License,之後還會弄 Firefox 版,大概就這樣,非常感謝。
WebSite Improvent
這篇文章整理一下這次改版大概是做了哪些東西,其實一開始的目標很簡單,就是對以前的樣式不滿很久了,想要一個簡潔,沒有多餘東西的版面,不過實際上讓我動手的原因其實是我開始有 Responsive Web Site 的需求了,我想很多人都知道,現在全世界可以上網的設備,手持裝置的數量已經和電腦不相上下了,以前我想說要看文章應該不會想在手機上看,可是後來我慢慢瞭解,有很多時候可能是點到朋友在 Twitter 上的分享,所以訪客才會用手機打開你的網頁,如果訪客這時候點開的網頁有支援行動裝置的低解析度,那使用者體驗會好很多。總之,我就想要讓我的部落格也有支援就是,一開始只是想要套用 Twitter Bootstrap,不過之後又陸續加上不少東西,整理如下:
Bootstrap
這次使用 Bootstrap 的 CSS Framework 來支援不同解析度的瀏覽介面,套用起來很簡單,預設的樣式就很不錯,像是表單之類的元件我就不用另外費心思來處理,主要是調整行高之類的改善排版,讓中文文章的閱讀更輕鬆些,顏色和背景則是從 日本的傳統色 那裡挑來的,目前是還沒考慮到配色的協調,應該還會再調整。
Web Fonts
用了 Google Web fonts,分別在頁首 logo 和程式碼區塊,範例:
@media (-webkit-min-device-pixel-ratio: 2),
(min--moz-device-pixel-ratio: 2),
(min-resolution: 2dppx)
Logo 部分當然就是要讓它比較不一樣些,所以找了個很特別的 8bit 風格字體,至於程式碼區塊,有一個基本要求是要是等寬字形,不然程式碼每個字元垂直對不齊會超難受的,後來用的是設計給 Android 手機的 Droid Sans,Google Web Fonts 有個好處是完全照標準 CSS 的用法,而且免費。
DevicePixelRatio Support
這也是之前 介紹 過,讓高解析度螢幕的環境可以使用比較大張的圖片,配合 flickr 的命名規則作的,自己用 iPad 看起來是蠻漂亮的啦。
Default sans-serif font
以前有介紹過判斷訪客瀏覽器是否支援字體渲染的 方法,當時我給 Windows 的預設是不支援,不過時代在改變,所以現在預設的字型都是黑體了,如果瀏覽器判斷出來不支援字體渲染,那會改用系統預設字形,通常會是新細明體,會做這樣的改變事我自己有時候會看到切換字型那瞬間的跳動,老實說一直跳是不太舒服啦,所以我想讓看到的人盡量減少,考量到現在數據上訪客用 Windows 7 的使用者已經大於其他所有的 Windows 的使用者的總和,所以我就這樣改了。
Twitter Cards and Open Graph Protocol
接下來的改造則是社群網路的結合,這部分是以前完全沒弄過的部分,主要的原因是太多社群網路了,但是每個社群網路間都有堵 高牆,使用者要跨越這些高牆非常困難,一個只用 Facebook 的人無法和只用 Twitter 的人從社群網路上有太多互動,就像是陷入飛彈發射井一樣。
從網站開發者這端來看,要減少這個社群網路的壟斷,基本上就是增加支援不同的社群網路,不過社群網站太多了,簡單 Google 一下 Social Network Buttons 就可以看到一大堆按鈕,要公平的對待每個社群網路,那就是每個都支援,可以想像要是每篇文章都這麼多按鈕要加,那是會多可怕的畫面,所以現狀雖然很殘酷,不過網際網路目前確實是被社群網路控制著的,最大的就是 Facebook 和 Twitter 兩家。
不過先不看社群網路的壟斷問題,資訊在網路上傳播的方式已經從最早的入口網站、搜尋網站這種使用者主動尋找的模式,經過 RSS 訂閱機制這種半被動方式,到現在透過社群網路分享,使用者只要做這那邊等朋友們分享的資訊,看似被動模式,不過其實是角色之間界線的消失了,在社群網路內的每個人都可能是資訊的提供者、傳播者或接收者。
話題有點扯遠了,總之社群網站礙於使用性我不可能全部支援,所以就挑最大的三家支援,Facebook、Twitter、Google+,而第一步就是 Twitter Cards 和 Open Graph Protocol,其中 Twitter Cards 目前需要申請,而且我自己測試過,個人網站也是會通過的。
Twitter Feed
第二步就是讓新文章能主動發佈到社群網站上,這邊是使用 twitter feed 這個服務,目前由於 Google+ 沒有開放 API,所以只支援 Twitter 和 Facebook,只要給該服務你網站內容的 RSS 並透過社群網站的授權機制授權給它發文,它就會在 RSS 有更新時,自動用你的帳號在你的 timeline 貼連結,不過這個服務其實是 bit.ly 的相關服務,所以貼過去的連結其實是 bit.ly 短網址,好處是會有 bit.ly 的統計資料,而且也不影響 Twitter Card 的功能。
Social Network Buttons
最後一個社群網站相關的修改是加上這三個社群網站的分享按鈕,也是很基本就照文件說明,不過比較麻煩的是按鈕樣式的選擇,本來是想要放最小的,20px 高的那種,可是因為寬度問題,由於都是用 iframe 實做,iframe 裡不同網域的程式碼無法動到 iframe 本身的大小,結果就是會讓這些按鈕後面需要預留空間,當多個按鈕要放一起時,就會出現間距不相同的情形。後來我就改成現在的形式,好處是寬高固定,比較可以控制位置,不過我發現三家社群網路的類似形式的按鈕還是故意有些不相容,眼尖的人可以能會發現他們高度不一樣,分別有 59px、60px、61px,如果把 Pinterest 也算上來,那還有 57px,還真是不知道該說什麼...
HTTPS
最後還有一個比較不重要,純粹是自爽的,就是現在本 blog 支援 HTTPS 連線了,一開始的動機只是因為看到 Chrome 會說網站尚未驗證身份,所以就一不做二不休,弄到有綠色鎖頭出現,不過目前還有一些問題需要處理,所以沒有弄成強制轉成 HTTPS,像是 Twitter Card 白名單好像有分,還有提交給搜尋引擎的 sitemap.xml,還有 canonical url 等都要統一,其實還很多細節要處理就是。
這一兩個月大概就是做了這些改進,斷斷續續的修改,看到什麼就修什麼,其實沒有先列清單,其實還有剛完成的 srcset 支援的東西就是。
id 的由來
這篇文章實在是拖了很久,最近剛好比較有動力,然後這邊又九週年(2003/12/04)了,所以就來說一下啦,取這 id 是在大學入學時期,加入台大卡漫社後需要取個暱稱和 bbs 系統的 id,othree 這個 id 可以說是從三種地方來的元素組合起來:
- 首先是 O 這個字母,其實我是要它的形狀,其實就是圓形,取其無意,我記得這部分曾經有人講對過,可是時間太久遠我忘了是誰XD。
- 小時候台視有撥過一部影集叫「異形終結者」,2005 年 Tom Cruise 有一部片台灣叫「世界大戰」,就是同一部原著小說,原名都叫「War of the Worlds」,總之,3 這個數字對片中的外星人有很特別的意義,手指是三隻,指揮官有三位,機體有三隻腳,其實我一直都對這部影集印象蠻深刻的,而我的 id 的 3 的元素就是從這邊來的。
- 1997 年的最後一部勇者動畫「勇者王」中,主角群的組織叫做 GGG(Gusty Geoid Guard),念法是 three G,其實我本來是要模仿這個念法的,結果我當時記錯了,所以就變成 o three 了 XDrz。
所以最後決定的 id 就是 othree,唸做 O three,翻成中文的 歐三 大概是最多人叫的,也有用過才三(才和日文假名オ很像)的代稱,其實有時候會被寫成 Othree,雖然是遇到名字首字大寫的禮儀,不過我自己用的時候其實只有 OOO 的形式會用大寫。
大概就是這樣了,其實也不是什麼秘密,只是當面被問到的時候要講清楚實在不容易,所以想說整理寫成文章還可以騙文章數,結果又拖很久才真的動手寫,最後就,未來的日子還請大家多多指教。
參考資料:
srcset 支援
之前的 Device Pixel Ratio 支援過後,繼續花了時間研究目前的 responsive image spec,目前大概就是 picture 標籤和 srcset 屬性兩種,好像是會兩種併行,picture 標籤的話看起來就像:
<picture>
<source media="(min-width: 40em)" srcset="big.jpg 1x, big-hd.jpg 2x">
<source srcset="small.jpg 1x, small-hd.jpg 2x">
<img src="fallback.jpg" alt="">
</picture>
至於 srcset 就長的像:
<img src="fallback.jpg" alt="" srcset="small.jpg 640w 1x, small-hd.jpg 640w 2x, med.jpg 1x, med-hd.jpg 2x ">
基本上差異就是,picture 標籤內可以用 media-query,而 srcset 裡面的寬高則是代表最大值,也就是等價於 max-width 和 max-height,而不能設定 min-width 或 min-height,目前這兩份標準都是由 Responsive Images Community Group(RICG) 來制訂,這個 Community Group(CG) 我覺得還蠻特別的,因為他們有自己的 domain,自己的網站,還有 twitter 帳號、Github 帳號,和以往的標準 CG 比起來實在差蠻多,不知道組成成員是哪些,都用些很新世代的服務。
我自己是比較喜歡 srcset 這個作法,簡潔很多,不過相對的也就沒有 picture 那樣結構化,不過兩者都有正確的 fallback 機制,所以不管選那個方法,目前的瀏覽器至少都還是可以顯示圖片。現在由於還沒有任何一個瀏覽器開始支援,所以要能實現 responsive image,要碼就是用 javascript library 來實做,要碼就是在 server 端做這件事,RICG 他們推薦的 polyfill library 是 picturefill 這套,不過我不太喜歡,因為它不是用 picture 標籤,而是自定義了一組用 div 和 data attribute 的寫法:
<div data-picture data-alt="A giant stone face at The Bayon temple in Angkor Thom, Cambodia">
<div data-src="small.jpg"></div>
<div data-src="medium.jpg" data-media="(min-width: 400px)"></div>
<div data-src="large.jpg" data-media="(min-width: 800px)"></div>
<div data-src="extralarge.jpg" data-media="(min-width: 1000px)"></div>
<!-- Fallback content for non-JS browsers. Same img src as the initial, unqualified source element. -->
<noscript>
<img src="external/imgs/small.jpg" alt="A giant stone face at The Bayon temple in Angkor Thom, Cambodia">
</noscript>
</div>
目前 dribbble 是使用這套,另外有一套實做 srcset 的 library 叫 srcset-polyfill,這套就是完全照 spec 草稿下去寫的,不過我自己是沒用過,因為我自己是能少用 polyfill 就少用的那種,剛好我的 blog 架構有多一層 XSLT,所以我就把支援做在那層了。我的作法就是 server 端想辦法判斷應該要給瀏覽器哪張圖片,然後 parse 網頁內容後,對有 srcset 的圖片作處理,把原來的 src 替換成適合的圖片位置,做在 XSLT 端的好處是反正那邊本來就要處理網頁的 DOM tree 了,用專門 XML parser 來做這件事情比較不會發生意外這樣,不過基本上就是個 server 端實做,也沒很特別,倒是判斷依據和規則想比較久。
<a class="thumbnail" href="http://www.flickr.com/photos/othree/7877215448/" title="Flickr 上 othree 的 花蓮">
<img src="//farm9.staticflickr.com/8431/7877215448_1d8dcf278c_b.jpg" width="1024" height="683" alt="花蓮"
srcset="//farm9.staticflickr.com/8431/7877215448_1d8dcf278c.jpg 768w,
//farm9.staticflickr.com/8431/7877215448_1d8dcf278c_b.jpg 768w 2x,
//farm9.staticflickr.com/8431/7877215448_9a51e0f42a_k.jpg 2x" />
</a>
目前我會在 srcset 裡面放三張圖,寬度分別是 500px、1024px 和 2048px 的,500px 那張的主要目標是給手機,1024px 那張是一般電腦,2048px 則是要給螢幕夠大且支援 HiDPI 的設備,像是 MBPR 和 iPad 3,基本上和 Device Pixel Ratio 的方法一樣,第一次來的訪客會寫入 cookie,記錄寬高最短邊的長度,只要最短邊小於 768 就給他最小張的寬 500px 的圖,剩下的情形就看 Dvice Pixel Ratio 決定要給哪張。不過光是這樣的規則其實還有些問題,就是頻寬的問題,像是用手機的人用 3G 網路加上螢幕不大,即使只是第一次來也很不需要抓 1024px 的圖片,所以第一次來的訪客我還會用 Mobile_Detect 來判斷,如果發現是手機的話第一次就會給 500px 的小圖。
目前用 Android Chrome、iPad Safari 還有桌上型電腦測過都有符合預期目標,目前也還沒想到其他改進的方向,可能等市面上的裝置分布有改變的話再調整了吧,不過如果瀏覽器直接支援 srcset 的話,那是最好,我也不需要在修改了:P。
PHP and XSLT 2.0
最近弄 srcset 支援的時候,一度需要 XSLT 2.0 的 xsl:function,所以研究一下要怎樣在 php 下可以使用 XSLT 2.0,PHP 自己的 extension 用的是 libxslt,只支援到 1.0,而且沒有支援 2.0 的計畫,所以就需要找其他的引擎,後來是看上 SAXON 這套 Java 寫的 XSLT Processor,有支援 XSLT 2.0,而且近期還有在更新,主要是由 Saxonica 這間公司維護的,有 open source 的 home edition。
找好引擎後第二步就是要怎樣用 PHP load Java 的程式來用了,本來是想看 PHP/Java Bridge,不過我覺得還要弄 proxy 有些麻煩,幸好有找到 XML_XSLT2Processor 這個專門來把第三方 XSLT 引擎包起來給 PHP 用的專案,用起來很簡單,API 開的和 PHP 自己的版本都一樣,只是產生物件時要跟他說是要用那個引擎,檔案位置在哪而已:
$proc = new XML_XSLT2Processor('SAXON9', './saxon/saxon9he.jar', 'JAVA-CLI');
像這樣,後面的用法就和以前都一樣了,設計的很不錯,等於可以只動兩行就換過去,不過實際上用了之後覺得,速度差太多 >_< ,而且我後來發現本來讓我想要使用 XSLT 2.0 的那個錯誤並不是因為需要 xsl:function,而是我沒把 namespace 搞好就去用 EXSLT 的 tokenize 這個 function,後來根據 stackoverflow 上的 回答 改動之後,發現可以動我就換去 PHP extension 了。
更之前的文章