2017年6月23日 星期五

聖靈的管制

一位姊妹在訓練中說了這樣的見證:
  我是在禱告聚會中清楚得救的,得救時覺得很喜樂。得救後受人逼迫,外面雖苦內心卻覺喜樂。我也學習神醫的功課,多次因著體弱求主,主就作了我的能力,使我能衝得過去,也能多聚會。以後主要我在廚房工作,但我覺得在屬靈方面落後,盼望能像其他姊妹,在屬靈方面事奉主。我本來不喜出外看望姊妹,後因主能力的扶持,就出去看望,自己也的確得益處。我本來覺得自己一無用處,沒有什麼可以交出去。以後覺得有用也好,無用也好,至少我是個屬主之人,無論如何必定要交出去。
倪弟兄的評語
  在你的見證中,我們知道你屬靈的生活很簡單。當你願交出來,願把自己擺在主手中時,主就要引領你過去。但還必須加強一點,才能更有用。人要在神面前作一個有用處的人,不一定是要在屬靈的環境中,更重要的是在於接受神的限制,接受聖靈的管治。我們在屬靈追求的同時,絕不該放棄神所給的限制。許多人以為要得屬靈的祝福,一定要在聚會中,要聽道等;但是有的時候,主卻是在所安排的環境中,賜大的祝福。人以為多聚會、多聽道,在屬靈的環境中,就能多認識神,卻不知在不自由的環境中,更是認識神的機會。人若棄絕神的限制,就不能成為有用的人。
  人在神的手中能夠有用,是在於他能接受環境上的管治。聖靈是借環境來管治人、成全人。人不可只是一直的求直接受恩的機會,卻不接受聖靈的管治。環境的限制,反而是受恩的機會。在廚房服事,為人理家,都有聖靈的管治。許多人都要揀選光明榮耀的路,而不願服在神大能的手下。許多時候,順服神大能手下的環境管治,比直接所受的恩典更多。對青年人來說,這個功課是難學的。他們在追求屬靈的事上,還是充滿了自己,在屬靈的道路上,不能好好的接受聖靈在環境中所安排的管治。但在屬靈的路上,沒有一個機會,像聖靈所給我們安排的環境更好。
  聖靈在人裡面的工作,必須有一個外面環境的安排配合,才能使人學到功課。在裡面的是聖靈的工作,在外面環境的安排,也是聖靈的工作。我們不能因為不喜歡環境的安排,而將環境的安排,從基督徒的生活中除掉。其實信徒沒有環境,只有聖靈的管治。我們在人手下作事,沒有偶然的事,一切所發生的都是聖靈的管治;聖靈是按照我們的要求,在外面給我們安排環境。我們要知道聖靈在裡面的工作,一定要借聖靈外面安排的環境來完成。只有聖靈在我們裡面運行,才知道我們需要何種環境,然後才給我們安排與我們最有益的環境。聖靈安排的環境,我們無法預先知道,所以我們不能照自己的意思揀選道路。聖靈既在裡面工作,也在外面安排環境,所以基督徒必須要順服聖靈在裡面的工作,也要順服聖靈在外面安排的環境。
  我們的姊妹,幾年來,或在廚房的環境,或在看家的環境,學習屬靈的功課,這個路是對的。若求脫離廚房的責任,去作傳道,恐怕還不如在廚房裡服事主。神如果要把我擺在廚房裡,就連聚會也比不上下廚房。每個人所要受屬靈的訓練不同,有的需要損失,有的需要病痛,有的需要麻煩,有的在家中發生兇惡的事,或其他的事,這都是聖靈的管治。我們個個都需要神的限制,藉著靈來管治我們,才造成我們在神面前的用處。我們在這些管治的環境中,都需要學習得著益處。我們總是願把環境推出去,但我們應該學習服在主大能的權下,我們不要稱『環境』這個辭,我們要稱『神的限制,』更好是稱『聖靈的管治。』弟兄姊妹,不要學背叛,要學習順服,時候沒有到,不要求脫離環境,不要求自由。好像在不自由的環境中,人就不能事奉神,但事實上這乃是事奉神的預備時期。
  等到我們在一個管治中的功課學好了,神就要先叫我們裡面得釋放,然後外面環境自然改變了。所以我們裡面若沒有得著釋放,總不要爭執;如果你和環境有爭執,就是和神有爭執了。我們要在主所安排的環境中順服神。許多時候,環境是主所給的鎖練,這些苦難、壓力等管治,都是主用來鍛練我們的。不要不平、埋怨、或反抗,要接受主所給的管治。當神把我們擺在不能直接受恩時,千萬不要爭執、不平、或埋怨。爭執、不平、和埋怨害了許多人。如同一個器皿,放在管治的環境中,乃是神琢磨的時刻。一有不平,就有裂痕了;若口中說出來,而埋怨神,就是器皿破裂的時候,這器皿就沒有用了。弟兄姊妹,要知道,順服聖靈管治所得的好處,是遠勝過你所能知道的。有幾乎過半數的人,花了半生的工夫與環境爭執,這是何等可惜的事。一個人與主沒有意見時,他所遇見的事,都會成為屬靈的祝福。一個不認識主作為的人,會咒詛自己的生日;但一個認識主作為的人,在每日所遇見的事上,都能讚美是主分給的祝福。
  我們必須學習,使我們裡面的世界,與外面的世界一致。在所遭遇的一切事上,我們都要能感謝和讚美。聖經說,凡事都要謝恩。因為是主所量給的環境安排,為要使我們得著最大的訓練,所以我們能凡事謝恩。雖說是因著抗戰多年,我們彼此無法見面,沒有交通,但這也不能說不是在主的主宰手中,所給我們聖靈的管治。主的靈在我們裡面作工,主的靈也在我們外面作工。前者得不到,後者應該得著。這一次使我大失所望的,乃是看見有的弟兄姊妹都沒有進步。我們若肯在聖靈管治的環境中,順服並學功課,我們就會得著光,得著豐盛的生命,不然,我們乃是貧窮的。
  神的兒子應當看見,在你四圍環繞你的人、事、物,都有聖靈所安排之管治的環境。我再重複的說,在這些環境裡,不可有不平、埋怨、和反抗的態度。要認識一切神所安排的,都是好的。我們都要低頭,在灰塵中敬拜神說,神阿,感謝你,你為我所安排的環境,都使我蒙了恩典。我們若不站在神這一邊,我們會得不著什麼,也會成為無用的人。我們受恩的機會有兩種,一是直接從職事得著,一是從聖靈管治的環境得著。對於後者,我們若反抗,就要失去得恩的機會;若順服,就要蒙恩。

2017年6月20日 星期二

循序漸進理解 HTTP Cache 機制




循序漸進理解 HTTP Cache 機制


前言

前陣子在研究跟 HTTP Cache 有關的一些東西,看得眼花撩亂,不同的 Header 愈看愈混亂,像是PragmaCache-ControlEtagLast-ModifiedExpires 等等。找了許多參考資料閱讀之後才有了比較深刻的理解,想說若是從一個比較不同的角度來理解 Cache,說不定會比較容易了解這些 Header 到底在做什麼。
在之前查的資料裡面,很多篇都是逐一解釋各個 Header 的作用以及參數,而我認為其實參數講多了容易造成初學者混淆,想說怎麼有這麼多奇怪的參數,而且每一個看起來都很像。所以這篇文章嘗試一步一步藉由不同的問題來引導出各個 Header 使用的場景以及出現的目的。還有,因為這篇是給初學者看的,所以不會講到所有的參數。
其實關於 Cache 這一部分,很多網路資源的說法都不太一樣,如果碰到有疑義的地方我會盡量以 RFC 上面寫的標準為主。如果有錯誤的話還麻煩不吝指正,感謝。

為什麼需要 Cache

多問為什麼是個好習慣,在你用一個東西之前,必須知道你為什麼要用它。於是,我們需要問自己一個問題:為什麼需要 Cache?
很簡單,因為節省流量嘛,也節省時間,或是更宏觀地來說,減少資源的損耗。
舉例來說,今天電商網站的首頁可能會有很多商品,如果你今天每一個訪客到首頁你都去資料庫重新抓一次所有的資料,那對資料庫的負擔會非常非常大。
可是呢,其實首頁的這些資訊基本上短期之內是不會變的,一個商品的價格不可能上一秒是一千元,下一秒就變成兩千元。所以這些不常變動的資料就很適合儲存起來,也就是我們說的 Cache,台灣翻譯叫做快取,中國翻譯叫做緩存。其實仔細想想,這兩個翻譯根本就是 Cache 的兩面:快速存取跟緩慢儲存(這邊的緩慢指的是可以存很久的意思)。
上面這個例子可以把首頁的那些資訊在撈出來一次之後就存在某個地方,例如說 Redis,其實就是以一個簡單的 Key Value Pair 的形式存進去即可,接著只要是用到這些資訊的時候,都可以用極快的速度撈出來,而不是再到資料庫裡面重新算一次。
上面講的是 Server side 的 Cache,藉由把 Database 的資料撈出來之後存到別的地方達成。但 Server side 的 Cache 並不是我們今天的重點,有興趣的讀者們可以參考我之前寫過的:資料庫的好夥伴:Redis
今天的重點是 Server 跟瀏覽器之間的 Cache 機制。
例如說電商網站的商品圖好了。如果沒有 Cache 的話,那首頁出現的上百張商品圖,只要網頁被瀏覽幾次,就會被下載幾次,這個流量是非常驚人的。所以我們必須讓瀏覽器可以把這些圖片給 Cache 起來。這樣只有第一次瀏覽這個網頁的時候需要重新下載,第二次瀏覽的時候,圖片就可以直接從瀏覽器的快取裡面去抓,不用再跟 Server 拿資料了。

Expires

要達成上述的功能,可以在 HTTP Response Header 裡面加上一個Expires的字段,裡面就是這個 Cache 到期的時間,例如說:
1
Expires: Wed, 21 Oct 2017 07:28:00 GMT
瀏覽器收到這個 Response 之後就會把這個資源給快取起來,當下一次使用者再度造訪這個頁面或是要求這個圖片的資源的時候,瀏覽器會檢視「現在的時間」是否有超過這個 Expires。如果沒有超過的話,那瀏覽器「不會發送任何 Request」,而是直接從電腦裡面已經存好的 Cache 拿資料。
若是打開 Chrome dev tool,就會看到它寫著:「Status code 200 (from disk cache)」,代表這個 Request 其實沒有發出去,Response 是直接從 disk cache 裡面拿的。


可是這樣其實會碰到一個問題,因為瀏覽器檢視這個 Expires 的時間是用「電腦本身的時間」,那如果我喜歡活在未來,把電腦的時間改成 2100 年,會怎樣呢?
瀏覽器就會覺得所有的 Cache 都是過期的,就會重新發送 Request。

Cache-Control 與 max-age

Expires 其實是 HTTP 1.0 就存在的 Header,而為了解決上面 Expires 會碰到的問題,HTTP 1.1 有一個新的 header 出現了,叫做:Cache-Control。(註:Cache-Control 是 HTTP 1.1 出現的 Header,但其實不單單只是解決這個問題,還解決許多 HTTP 1.0 沒辦法處理的快取相關問題)
其中一種用法是:Cache-Control: max-age=30,就代表這個 Response 的過期時間是 30 秒。假設使用者在收到這個 Response 之後的第 10 秒重新整理,那就會出現上面那樣被瀏覽器 Cache 住的現象。
但假如使用者是過 60 秒之後才重新整理,瀏覽器就會發送新的 Request。


仔細觀察 Google Logo 檔案的 Response header,你會發現它的max-age設定成31536000秒,也就是 365 天的意思。只要你在一年之內造訪這個網站,都不會對 Google logo 這張圖片送出 Request,而是會直接使用瀏覽器已經快取住的 Response,也就是這邊寫的Status code 200 (from memory cache)
現在就碰到一個問題了,既然Expiresmax-age都可以決定一個 Response 是否過期,那如果兩個同時出現,瀏覽器要看哪一個呢?
根據RFC2616的定義:
If a response includes both an Expires header and a max-age directive, the max-age directive overrides the Expires header, even if the Expires header is more restrictive
max-age會蓋過Expires。因此現在的快取儘管兩個都會放,但其實真正會用到的是max-age

過期了,然後呢?

上面這兩個 Header 都是在關注一個 Response 的「新鮮度(freshness)」,如果 Response 夠新鮮的話(也就是還沒超過Expire或者是在max-age規定的期限裡面),就直接從快取裡面拿資料。如果過期了、不新鮮了,就發送 Request 去跟 Server 拿新的資料。
但是這邊要特別注意一點:「過期了不代表不能用」
這是什麼意思呢?剛剛有提到說 Google 的 Logo 快取時間是一年,一年之後瀏覽器就會重新發出 Request 對吧?可是很有可能 Google 的 Logo 在一年之後也不會換,代表其實瀏覽器快取起來的圖片還是可以用的。
如果是這種情況,那 Server 就不必返回新的圖片,只要跟瀏覽器說:「你快取的圖片可以繼續用一年喔」就可以了。

Last-Modified 與 If-Modified-Since

想要做到上面的功能,必須要 Server 跟 Client 兩邊互相配合才行。其中一種做法就是使用 HTTP 1.0 就有的:Last-ModifiedIf-Modified-Since的搭配使用。
在 Server 傳送 Response 的時候,可以多加一個Last-Modified的 Header,表示這個檔案上一次更改是什麼時候。而當快取過期,瀏覽器下次再發送 Request 的時候,就可以利用這個資訊,改用If-Modified-Since來跟 Server 指定拿取:某個時間點以後有更改的資料。
直接舉一個例子吧,假設我要求 Google 首頁的圖片檔案,收到了這樣的 Response(為了方便閱讀,日期的格式有更改過,實際上的內容不會是這樣):
1
2
Last-Modified: 2017-01-01 13:00:00
Cache-Control: max-age=31536000
瀏覽器收到之後就會把這張圖片存進快取,並且標明這個檔案的最後更新時間是:2017-01-01 13:00:00,過期時間是一年後。
如果在半年後我重新請求這張圖片,瀏覽器就會跟我說:「你不用重新請求喔,這一份檔案的過期時間是一年,現在才過了半年。你要資料是吧?我這邊就有囉!」,於是就不會發送任何 Request,而是直接從瀏覽器那邊獲得資料。
接著我在過了一年之後再請求一次這張圖片,瀏覽器就會說:「嗯嗯,我這邊的快取的確過期了,我幫你去 Server 問一下檔案從2017-01-01 13:00:00以後有沒有更新」,會發送出下面這樣的 Request:
1
2
GET /logo.png
If-Modified-Since: 2017-01-01 13:00:00
假設檔案確實更新了,那瀏覽器就會收到一份新的檔案。如果新的檔案一樣有那些 Cache 的 Header,就一樣會快取起來,跟上面的流程都一樣。那假設檔案沒有更新呢?
假設沒有更新的話,Server 就會回一個Status code: 304 (Not Modified),代表你可以繼續沿用快取的這份檔案。


Etag 與 If-None-Match

雖然上面的這個方法看起來已經很好了,但還是有一個小問題。
上面講的是檔案有沒有被「編輯」過,但其實這個編輯時間就是你電腦上檔案的編輯時間。若是你打開檔案什麼都不做,然後存檔,這個編輯時間也會被更新。可是儘管編輯時間不一樣,檔案的內容還是一模一樣的。
比起編輯時間,若是能用「檔案內容更動與否」來當作是否要更新快取的條件,那是再好不過了。
Etag這個 Header 就是這樣的一個東西。你可以把 Etag 想成是這份檔案內容的 hash 值(但其實不是,但原理類似就是了,總之就是一樣的內容會產生一樣的 hash,不一樣的會產生不一樣的 hash)。
在 Response 裡面 Server 會帶上這個檔案的 Etag,等快取過期之後,瀏覽器就可以拿這個 Etag 去問說檔案是不是有被更動過。
EtagIf-None-Match也是搭配使用的一對,就像Last-ModifiedIf-Modified-Since一樣。
Server 在回傳 Response 的時候帶上Etag表示這個檔案獨有的 hash,快取過期後瀏覽器發送If-None-Match詢問 Server 是否有新的資料(不符合這個Etag的資料),有的話就回傳新的,沒有的話就只要回傳 304 就好了。
流程可以參考 Google 網站上的下圖:

中場休息

讓我們來總結一下到目前為止學到的東西:
  1. ExpiresCache-Control: max-age決定這份快取的「新鮮度」,也就是什麼時候「過期」。在過期之前,瀏覽器「不會」發送出任何 Request
  2. 當快取過期之後,可以用If-Modified-Since或是If-None-Match詢問 Server 有沒有新的資源,如果有的話就回傳新的,沒有的話就回傳 Status code 304,代表快取裡面的資源還能繼續沿用。
有了這幾個 Header 之後,世界看似美好,好像所有的問題都解決了一樣。
是的,我說「好像」,代表其實還有一些問題存在。

不要快取怎麼辦?

有一些頁面可能會不想要任何的快取,例如說含有一些機密資料的頁面,就不希望任何的東西被保留在 Client 端。
還記得我們一開始有提過Cache-Control這個 Header 其實解決了更多問題嗎?除了可以指定max-age以外,可以直接使用:Cache-Control: no-store,代表說:「我就是不要任何快取」。
因此每一次請求都必定會到達 Server 去要求新的資料,不會有任何資訊被快取住。
(註:HTTP 1.0 裡面有一個Pragma的 Header,使用方法只有一種,就是:Pragma: no-cache,有網路上的資料說它就是不要任何快取的意思,但根據RFC7232的說法,這個用法應該跟Cache-Control: no-cache一樣,而不是Cache-Control: no-store,這兩個的差異等等會提到)

首頁的快取策略

剛剛上面提到的都是一些靜態資源例如說圖片,特性就是會有好一陣子不會變動,因此可以放心地使用max-age
但現在我們考慮到另外一種狀況,那就是網站首頁。
網站首頁雖然也不常會變動,但我們希望只要一變動,使用者就能夠馬上看到變化。那要怎麼辦呢?設max-age嗎?也是可以,例如說Cache-Control: max-age=30,只要過 30 秒就能讓快取過期,去跟 Server 拿新的資料。
但如果我們想要更即時呢?只要一變動,使用者就能夠馬上看到變化。你可能會說:「那我們可以不要快取就好啦,每次都抓取新的頁面」。可是如果這個首頁有一個禮拜都沒有變,其實使用快取會是比較好的辦法,節省很多流量。
因此我們的目的是:「把頁面快取起來,但只要首頁一變動,就能夠立刻看到新的頁面」
這個怎麼做到呢?第一招,你可以用Cache-Control: max-age=0,這就代表說這個 Response 0 秒之後就會過期,意思是瀏覽器一接收到,就會標示為過期。這樣當使用者再次造訪頁面,就會去 Server 詢問有沒有新的資料,再搭配上Etag來使用,就可以保證只會下載到最新的 Response。
例如說第一個 Response 可能是這樣:
1
2
Cache-Control: max-age=0
Etag: 1234
我重新整理一次,瀏覽器發出這樣的 Request:
1
If-None-Match: 1234
如果檔案沒有變動,Server 就會回傳:304 Modified,有變動的話就會回傳新的檔案並且更新Etag。如果是使用這種方式,其實就是「每一次造訪頁面都會發送一個 Request 去確認有沒有新的檔案,有的話就下載更新,沒有的話沿用快取裡的」。
除了上面這招max-age=0,其實有一個已經規範好的策略叫做:Cache-Control: no-cacheno-cache並不是「完全不使用快取的意思」,而是跟我們上面的行為一樣。每次都會發送 Request 去確認是否有新的檔案。
(註:其實這兩種還是有很細微的差別,可參考What’s the difference between Cache-Control: max-age=0 and no-cache?
如果要「完全不使用快取」,是Cache-Control: no-store。這邊不要搞混了。
為了怕大家搞混,我再講一次這兩個的差異:
假設 A 網站是使用Cache-Control: no-store,B 網站是使用Cache-Control: no-cache
當每一次重新造訪同樣一個頁面的時候,無論 A 網站有沒有更新,A 網站都會傳來「整份新的檔案」,假設index.html有 100 kb 好了,造訪了十次,累積的流量就是 1000kb。
B 網站的話,我們假設前九次網站都沒有更新,一直到第十次才更新。所以前九次 Server 只會回傳 Status code 304,這個封包大小我們姑且算作 1kb 好了。第十次因為有新的檔案,會是 100kb。那十次加起來的流量就是 9 + 100 = 109 kb
可以發現 A 跟 B 達成的效果一樣,那就是「只要網站更新,使用者就能立即看到結果」,但是 B 的流量遠低於 A,因為有善用快取策略。只要每一次 Request 都先確認網站有沒有更新即可,不用每一次都抓完整的檔案下來。
這就是no-storeno-cache的差異,永遠不用快取跟永遠檢查快取。

最後一個問題

現在 Web App 當道,許多網站都是採用 SPA 的架構搭配 Webpack 打包。前端只需要引入一個 JavaScript 的檔案,Render 就交給 JavaScript 來做就好。
這類型的網站,HTML 可能長得像這樣:
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' href='style.css'></link>
<script src='script.js'></script>
</head>
<body>
<!-- body 為空,所有內容都交給 js 去 render -->
</body>
</html>
當 JavaScript 載入之後,利用 JavaScript 把頁面渲染出來。
面對這種情境,我們就會希望這個檔案能夠跟上面的首頁檔案一樣,「只要檔案更新,使用者能夠立即看到新的結果」,因此我們可以用Cache-Control: no-cache來達成這個目標。
可是呢,還記得剛說過no-cache其實就是每一次訪問頁面,都去 Server 問說有沒有新的結果。意思就是無論如何,都會發出 Request。
有沒有可能,連 Request 都不發呢?
意思就是:「只要檔案不更新,瀏覽器就不會發 Request,直接沿用快取裡的即可。只要檔案一更新,瀏覽器就要立即抓取新的檔案」
前者其實就是我們一開始講的max-age在做的事,但max-age沒辦法做到判斷「檔案不更新」這件事情。
所以其實這個目標,沒辦法單靠上面我們介紹的這些瀏覽器的快取機制來達成,需要 Server 那邊一起配合才行。其實說穿了,就是把 Etag 的機制自己實作在檔案裡面。
什麼意思呢?我們直接來看一個範例,我們把index.html改成這樣:
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' href='style.css'></link>
<script src='script-qd3j2orjoa.js'></script>
</head>
<body>
<!-- body 為空,所有內容都交給 js 去 render -->
</body>
</html>
注意到 JavaScript 的檔名變成:script-qd3j2orjoa.js,後面其實就跟 Etag 一樣,都是代表這個檔案的 hash 值。然後我們把這個檔案的快取策略設成:Cache-Control: max-age=31536000
這樣子這個檔案就會被快取住一年。一年之內都不會對這個 URL 發送新的 Request。
那如果我們要更新的話怎麼辦呢?我們不要更新這個檔案,直接更新index.html,換一個 JavaScript 檔案:
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' href='style.css'></link>
<script src='script-8953jief32.js'></script>
</head>
<body>
<!-- body 為空,所有內容都交給 js 去 render -->
</body>
</html>
因為index.html的快取策略是no-cache,所以每一次訪問這個頁面,都會去看index.html是否更新。
以現在這個例子來說,它的確更新了,因此新的這份就會傳回給瀏覽器。而瀏覽器發現有新的 JavaScript 檔案就會去下載並且快取起來。
藉由把 Etag 的機制實作在index.html裡面,我們就達成了我們的目標:「只要檔案不更新,瀏覽器就不會發 Request,直接沿用快取裡的即可。只要檔案一更新,瀏覽器就要立即抓取新的檔案」
原理就是針對不同的檔案採用不同的快取策略,並且直接用「更換 JavaScript 檔案」的方式強制瀏覽器重新下載。
這邊也可以參考 Google 提供的圖片:

總結

之所以快取的機制會有點小複雜,是因為分成不同的部分,每一個相關的 Header 其實都是在負責不同的部分。例如說Expiresmax-age是在負責看這個快取是不是「新鮮」,Last-ModifiedIf-Modified-SinceEtagIf-None-Match是負責詢問這個快取能不能「繼續使用」,而no-cacheno-store則是代表到底要不要使用快取,以及應該如何使用。
這篇文章其實只講到快取機制的一半,沒有提到的部分大致上都跟 shared cache 以及 proxy server 有關,有其他的值是在決定快取能不能被存在 proxy server 上?或者是驗證能否繼續使用的時候應該要跟原 server 驗證,還是跟 proxy server 驗證也可以。有興趣想要知道更多的讀者們可以參考底下的參考資料。
最後,希望這篇文章能讓初學者更理解 HTTP 的快取機制。

參考資料

  1. 彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法
  2. 浅谈浏览器http的缓存机制
  3. 使用 HTTP 缓存:Etag, Last-Modified 与 Cache-Control
  4. 【Web缓存机制系列】1 – Web缓存的作用与类型
  5. HTTP缓存控制小结
  6. MDN - Cache-Control
  7. rfc2616
  8. Google Web Fundamentals
  9. HTTP 1.0 spec

2017年6月18日 星期日

結婚證書正本驗證

主要有三個步驟:
1 去戶政申請英文版的結婚證書
只要帶身分證及印章

2 拿去法院公證
身份證
護照
英文版結婚證書


3 去德國在台協會做正本驗證

只需攜帶正本,若請人代辦則需要帶身份證

2017年6月8日 星期四

protobuf

比起 JSON 更方便、更快速、更簡短的 Protobuf 格式

Protocol Buffers 是由 Google 所推出的一格式(後台真硬),你可以把它想像成是 XML 或 JSON 格式,但是更小、更快,而且更簡潔。這能夠幫你節省網路與硬體資源,且你只需要定義一次資料結構,接著就會自動生成符合你程式語言的檔案,讓你能夠直接在你的程式上使用。
而且有趣的是一但你定義了資料結構,你就能在多個語言上使用,像是 C++C#GoJavaPythonJavaScript⋯等,如此一來就不用像 JSON 那樣到不同語言時還要重新定義資料結構。

結構就是文件

首先在傳遞資料前,我們需要定義資料結構。這些檔案以 .proto 作為後輟,有趣的是這些檔案本身就是結構定義檔,所以就不用額外撰寫 API 文件,因為 .proto 檔本身就是一種「文件」。
// 撰寫格式是 Proto v3。
syntax = "proto3";

// User 帶有使用者資料,如帳號、密碼。
message User {  
    string username = 1;
    string password = 2;
}

// Post 帶有文章資料。
message Post {  
    int64  id      = 1;
    string content = 2;
}
在 Protocol Buffers 中會需要定義資料型態,這讓你明白你應該要傳入什麼類型的資料,這些資料型態可以在這個頁面找到。

後面的數字是什麼?

你可能會好奇為什麼我們需要在每個欄位後面標註數字,實際上這是 Protocol Buffers 編碼與解碼所會用到的編號,這令你能夠移除其中一個欄位而不打亂整個資料結構的編碼與解碼(除非你更改了數字編號)。

與 JSON 比較?

與 JSON 比較,Protocol Buffers 有這些優點。
message User {  
    string username = 1;
    string password = 2;
}
  • 資料輕量化:資料非常輕量,省去了不必要的 { 或 : 累贅。
  • 混淆性:在一般人眼中無法輕易地猜測出資料結構為何,因為有經過編碼。
  • 效能高:處理速度很快。
  • 極具方便性:結構就是你的資料模型,你能夠直接在程式中使用這些結構,而不用建立新的物件來接納、映射(Mapping)這些資料。
  • 清晰明瞭、無需文件.proto 檔案本身就是你的文件,不需要額外撰寫 API 或結構文件來告訴別人你接受怎樣的資料。
在行動裝置的應用程式、伺服器與伺服器之間的通訊,或是單頁應用程式都可以用上 Protocol Buffers。

什麼時候應該繼續用 JSON?

當然也不是一味地拋棄 JSON,在有些時候 Protocol Buffers 仍然沒有 JSON 要來地方便。
{
    "username": "Yami Odymel",
    "password": "test"
}
  • 當你希望資料是人類可解讀的時候。
  • 你不打算直接把接收到的資料拿來處理,你希望從中拿取部分資料作為處理。
  • 你希望在純文字、終端機的情況下就能夠與伺服器溝通。
  • 不想經過任何特殊處理,想直接在瀏覽器中解讀。
在傳統網站、小型網站中仍可使用 JSON 作為資料傳遞的格式,如果此時用上 Protocol Buffers 可能會花費過多的時間與開發成本。

傳遞的內容看起來怎樣?

下面這張圖解釋了 Protocol Buffers 傳遞的資料格式。
以最下面的範例結果作為例子,這是 Protocol Buffers 傳遞的內容。Protocol Buffers 位元陣列:
[8 185 96 18 11 89 97 109 105 32 79 100 121 109 101 108 26 4 116 101 115 116]
Protocol Buffers 位元陣列轉為字串:
�`
  Yami Odymeltest
解譯成人類可讀:
12345   Yami Odymel   test  
比起 JSON 的 { 與 : 要來得更加簡潔,並且傳輸時的容量又更小了。

1. 安裝 Protocol Buffers 生成工具

首先需要安裝 protoc,這是用來將 .proto 檔案轉化為程式的工具。先到 Protobuf 的 GitHub 釋出頁面
滾到最下面,下載符合自己系統的已編譯版本,如此一來就不用再手動編譯。
下載之後解壓縮,然後將 bin/protoc 檔案丟至系統路徑 $PATH,這樣我們就能夠在終端機執行

2. 安裝 Golang 擴充插件

先下載並安裝golang  https://golang.org/dl/
接下來的教學會以 Golang 為主,其他語言可能要自行參考這裡,首先以 go get 取得 protoc-gen-go
$ go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
$ go get -u github.com/golang/protobuf/proto
$ go get -u github.com/golang/protobuf/protoc-gen-go
這是 protoc 的擴充插件,能夠將程式轉化成 Golang 語言。
接著進到 $GOPATH/bin 中會找到我們剛才下載的 protoc-gen-go 檔案,同樣地,將他丟進系統路徑 $PATH,如此一來才能在稍後於終端機內執行。

3. 轉化 Proto 文件

接著我們要將 .proto 轉化成 Golang 程式才能夠在 Golang 中使用,現在新增下列文件,並且將其命名為 example.proto
// 撰寫格式是 Proto v3。
syntax = "proto3";  
// 生成的程式在 Golang 中將會屬於 `protobuf` 套件。
package protobuf;

// User 帶有使用者資料,如帳號、密碼。
message User {  
    int64  id       = 1;
    string username = 2;
    string password = 3;
}
#可能要將中文註解拿掉才可以正常編輯
然後透過下列指令將 .proto 轉化成 Golang 程式。
$ protoc --go_out=. *.proto
然後我們就能得到一個屬於 protobuf 套件的 example.pb.go 檔案,內容大略如下。
現在我們就可以在 Golang 中開始使用這個結構。

4. 解碼與編碼

記得將我們剛才的 example.pb.go 放至 /protobuf 資料夾內(自行手動新增),因為那個檔案屬於 protobuf 套件。
現在我們要透過這些結構來解碼、與編碼資料(注意:下列範例為了簡潔,都忽略了錯誤處理,這是很不好的行為)。
package main

import (  
    "fmt"

    "./protobuf"
    "github.com/golang/protobuf/proto"
)

func main() {  
    // 建立一個 User 格式,並在其中放入資料。
    data := protobuf.User{
        Id:       12345,
        Username: "Yami Odymel",
        Password: "test",
    }

    // 將資料編碼成 Protocol Buffer 格式(請注意是傳入 Pointer)。
    dataBuffer, _ := proto.Marshal(&data)

    // 將已經編碼的資料解碼成 protobuf.User 格式。
    var user protobuf.User
    proto.Unmarshal(dataBuffer, &user)

    // 輸出解碼結果。
    fmt.Println(user.Id, " ", user.Username, " ", user.Password)
}
透過下列指令執行。
$ go run main.go
接著我們就能夠得到這樣的結果。
12345   Yami Odymel   test  
上面這個結果是由資料編碼成 Protocol Buffers 然後解碼所得到的。