2015年8月10日 星期一

HTTP 通訊簡介

從網址列按下 enter 後,到網頁畫面顯示出來,這中間發生了哪些事?

前言

我一直很喜歡的一個面試題,我決定今天把它拿出來告訴大家,歡迎參考,條件是如果有什麼有趣的回饋請一定要讓我知道!:P
每次面試工程師 (敝公司會接觸到的是 web 前後端以及 mobile app 工程師) 的時候,我幾乎都會問這個問題:「從網址列按下 enter 後,到網頁畫面顯示出來,這中間發生了哪些事?」這是一個好像很空泛卻又很實際的問題,我則是認為這個問題可以問出受試者對哪個領域較熟悉。
舉例來說,如果對方說了「瀏覽器會從 server 讀網頁跟 CSS 還有 JavaScript 回來,然後根據 HTML 產生 DOM,並根據 CSS 裡面描述的規則,對 DOM 中的元素進行排版,並且執行 JavaScript」這樣的話,那你可以猜測他應該比較熟悉前端。如果對方說的是「瀏覽器會送出 request 到 server,server 收到之後會先執行指定的 controller 裡面的 method,然後去存取資料庫把資料撈出來並顯示 view 回傳給瀏覽器」,那這個人應該曾經用某個後端的 framework 寫過網頁。又或是「server 會執行指定的 php 檔案,然後把執行結果顯示在瀏覽器上」,他可能是個比較資淺的 php 工程師 (因為講得很粗略)。我碰過一個有趣的例子,他跟我說的是「瀏覽器會先去找 DNS,解出主機的 IP...」,那表示他至少知道 DNS 這個東西XD。
總之,這個問題其實相當開放的,牽扯到的東西也滿多的,下面就來講講我會怎麼對付這個問題。

OSI 網路七層架構

現在的網路世界,可以被分成很多層,每一層各自有他的功能。參考維基百科上的 OSI 頁面,看出可分為 physical, data link, network, transport, session, presentation, application 等 layer。由於這個問題屬於較上層相關的問題,所以通常會傾向從 application layer 的角度回答,也就是關於 HTTP protocol 本身,以及處理 HTTP protocol 的應用程式等等。不過,由於網路本身也是很重要的,所以如果能夠從 SSL, TCP/IP, DNS, 防火牆等等角度去敘述,也會是個有趣的答案。

網路方面的回答

HTTP 一般走的是 TCP 的 port 80,HTTPS 一般是走 TCP port 443,有些 web server 可能會因為特殊原因開在 8000 或是 8080 等等,都滿常見的。不過其實走哪個 port 都可以,只要是 TCP 就好。(唔,其實我不知道 HTTP 能不能建構於 UDP 之上,我想技術上是可行的,但是有沒有大家共通的實作就是個問題了,所以這邊不討論)
如果提到了 TCP,那就可以進一步討論到 TCP 的 3-way handshaking,至少一般會了解到 SYN, SYN/ACK, ACK 這個過程。至於關閉連線的 RST...我承認我忘了 (這篇文章是在搭車的時候抽時間寫的,沒有網路就不查資料了XD),大致上是 RST, ACK + RST, ACK 之類的的吧.......
再底層有 IP 嘛,那就可以討論到像是 DNS 的解析 (DNS 用什麼 protocol 傳送資料呢?:P),拿到 IP 之後,資料要怎麼送出去?路由 (routing) 可能經過什麼地方?再更進一步,IP 的封包有沒有可能偽造來源?

關於 server 端的回答

由於應用層有很多部分可以討論,所以分開來講。server 上應該會有個 web server 去 listen port 80 (又跟 TCP 扯上關係了!),這個 server 可能是 apache、nginx、IIS 或是 lighttpd 之類的 general 的 web server,又或者是你的應用程式直接就自己 listen 80 port,比方說 rails 的 dev server: webrick,或是 thin, unicorn、php 的 php-fpm 等等。

Web Server

web server 是負責接受 http request 的。在 web server 的後面,需要有人負責處理 http request。有可能你的 web server 本身就具備處理 request 的能力,比方說裝了 mod_php 的 apache,或是有 passenger 的 nginx 等等。又或者 web server 只作為一個 reverse proxy,將 http request 轉送給知道怎麼處理的 server。轉送的方式可能透過某個 port,又或是某個 socket file (如果知道這個,應該是有管過或設定過 server 的人)。

Application Server

那麼 http request 要怎麼處理呢?由於 http request 就只是一串文字,包含了 header 跟 body (或說 content、payload,我不確定有沒有正式正確名稱),一般是將它解析、轉換成特殊格式,好讓後面的應用程式方便處理。這邊就會有些被定義好的界面,像是以前常聽到的 CGI、FastCGI、uWSGI、Rack...等等,而我們一般寫的 web 應用程式就是接上這些界面去實作,而不用自己 parse 純文字的 http protocol。
進入真正的 user 寫的 application 這邊,以我自身的經驗又可根據是否使用 framework 分成兩類。

不使用 framwork 的 application

不用 framework 的經典例子大概就是 php 了。PHP 因為以前 mod_php 安裝很方便 (至少我是這樣覺得啦),所以到現在依然很紅。幾乎所有 php 入門書都教你寫單純的 php 去產生網頁,簡單來說就是....連到哪個路徑就執行哪個檔案裡的程式碼,然後把印出來的結果傳回給 client 端。寫單純 php 的人,大概都用過 $GET, $POST 這些 global array,這些 array 的值就是 application server (或是 web server 的某個 module,如 mod_php) 幫你設定好,傳進來給你的 application 用的。最後印出來的資料 (比方說透過 echo "<p>這是一個 <marquee>php 網頁</marquee></p>" 印出來的) 也會交由他們負責回傳給 client。
(碎碎念:就是因為這些東西包裝得太好了,才會一堆人連 server-side 跟 client-side 的程式執行順序都搞不清楚...)

使用 framwork 的 application

用 framework 的話,狀況就複雜多了,程式的執行順序要看 framework 怎麼設計。以我接觸過的 framework 而言,會有一個單一進入點,request 進來之後,會先進入一個 router,不是網路上的路由器那個 router,是 URI router。URI router 的用途是根據 request 的 http method、URI (簡單說就是整串網址) (通常是這兩個) 決定要執行哪一段程式。以 (Web-)MVC framework 來說,就是某個特定的 controller 的某個特定的 method。或是像有些 framwork 的設計,讓你直接把 method、URI 跟 controller 寫在一起 (像是 Sinatra, Flask),也是還不賴這樣。又或是像 ASP.net WebForm 的設計,會直接讀取到路徑對應的 .aspx 檔案,然後把它 render 出來,並把程式部分的那些 handler 用奇妙的方式塞進 render 出來的 html 裡面...我是一直覺得這樣很亂我都被搞混啦XD
題外話:其實雖然現在公司寫 rails,我也不是完全清楚 rails 完整的執行流程細節...比方說 app server (unicorn) 進到 rails 的進入點是哪邊 (我只知道 config.ru 是 rack 的 initialize),他又是根據什麼去找到 routes.rb 這些的...
進到 controller 以後,就會....執行那個 method XD。不過如果是 rails developer 的話,可能會提到會先執行 ApplicationController 裡面寫的某些東西,或是會用到 before_action 之類的。當然 controller 中會去存取 model,然後 render view 之類的,這些就是各 framework 的設計不同的地方了...

Application Server 跟 Application 綁在一起的狀況

其實 application server 也不一定會跟 application 分開,比方說有些人可能喜歡自己處理 raw HTTP request,自己用 C 語言寫個程式,開了 socket 收文字資訊自己 parse...又或是像 node 世界,有些簡單的小應用就自己開 socket 自己處理資訊這樣 (但我不會寫 node,如果有誤解的話歡迎指教)。

關於 client 端的回答

Server 端講完了,來講講我比較不熟的 client 端 (又稱為前端、瀏覽器端)。前端嘛...就把 HTML 建成 DOM,然後 parse 完 CSS 把它裡面的規則拿來排版,最後再執行 JavaScript。(完)
....開玩笑的,不過這邊我比較組織不起來,可能有點雜亂。一開始瀏覽器會把 user 輸入的資訊 (網址、表單內容) 組合成 HTTP request,這個 request 是一個單純的文字串流,然後就透過 TCP 送出去了...經過 server 端的一番大戰之後,瀏覽器會收到一個回應 (http response),通常內容會是一份 html 資料啦,這時候瀏覽器就會開始讀取、解析這個 html。
HTML 中會有些 tag 參考到其他資料嘛,比方說像是 link (通常用來讀 css)、script (通常用來參考外部 JavaScript。VBScript?可以吃嗎?)、img (通常用來放圖片),當瀏覽器解析到這些外部資源的時候,就會再開一個連線,去讀取外部資源 (簡單來說意思是,下載那些檔案)。當瀏覽器讀到 js 檔案,或是一段 js 程式碼的時候,會停下解析 html 的工作,去執行 js。
瀏覽器會在解析的時候就執行 js 的這個事實滿重要的,因為很容易害人踩到雷XD。如果你把 js 放在 html 的 head 中,當你執行到這段 js 的時候,html 的 body 根本還沒有讀到,也就是說 DOM 根本還沒建立。如果在 js 中企圖去操作 DOM (比方說,想用 jQuery 找出某個 DOM element 並修改它),那會連找都找不到...這也就是為什麼很多 jQuery 範例都會要你用 $(function(){ /*...*/ }) 把要做的事情包裝起來。
再講個題外話,如果瀏覽器是透過 js 送出 request 的話,它應該是透過 XHR (XmlHttpRequest) 物件去送的,也就是俗稱的 AJAX request。這個狀況下,server 回的就可能不是 html 了。一般狀況可能會拿到一個 json 格式的資料,又或者是 xml 格式的資料,當然也有拿到 html 片段的 (像是 ASP.Net 的 UpdatePanel,就是個會去拿 HTML 片段回來顯示的玩意兒)。在處理上的話,就不會把焦點放在 rendering,而會放在 json/xml 格式的解析,還有針對 load 回來的資料的處理了。
對了,關於瀏覽器這邊,由於瀏覽器發展也是滿快的,我所理解的知識說不定已經過時了,也歡迎各方大德多多指教。<( )>

關於 HTTP protocol 本身的回答

上面其實提到好幾次會扯到 HTTP protocol 的東西,像是 request/response,或是像資料格式之類的。有些人也許會注意到:既然瀏覽器送出資料,可能會拿到各種不同格式的資訊,那瀏覽器又是怎麼知道資料是什麼格式的呢?用「副檔名」嗎?
其實 HTTP 的 request/response 並不單純只是資料,而是還有一些 metadata 的,也就是 http 的 header (跟 html 的 <head> 不一樣噢!)。其實應該要用範例講比較清楚...可是我現在懶得貼範例 (攤)。
先來解釋 HTTP request header:關於 request 的 metadata 無非就是要取得什麼資料,主機名稱為何,接受什麼格式、編碼、語言的回應,還有接不接受回傳資料先壓縮過之類的。另外,如果有給 payload (最常見就是 POST 的時候),那還會有個欄位用來描述 payload 的長度 (byte 數)。
至於 response header,首先最重要的就是狀態碼。有在上網的人大概比較常聽到「404 Not Found」吧,那個 404 其實就是狀態碼。除此之外常見的還有像是「200 OK」、「302 Found」、「500 Internal Server Error」、「403 Forbidden」 、「400 Bad Request」之類的...反正很多啦XD 簡單分類:2xx 表示請求成功,3xx 表示重新導向、4xx 表示 (server 認為) client 端有錯誤,5xx 表示 server 自己有錯誤。除了狀態碼以外,header 剩下的部分差不多就是回應 request 裡面的要求了,比方說 Content-Type、Content-Encoding 之類的,還有壓縮與否之類的資訊。
對 http request/response 有興趣的話,只要打開瀏覽器的開發者工具 (Chrome, Firefox, Safari, IE 都有內建),選擇觀看網路流量,就可以看到 request/response 的內容了。

結語

其實寫了這麼亂一篇,還是覺得不夠全面,畢竟一開始我提出來的問題就是個開放式的問題...回頭看看這篇文章,發現如果是另一個人來看我的文章,也會抓出我對於哪些方面的技術接觸較多,哪些比較少,還有哪些地方其實我一竅不通XD
期許這篇文章可以對一些朋友有點幫助,也希望各路高手如果抓到什麼問題,或是覺得哪邊值得討論,都可以提出來。雖然我很懶 (XD 要不是搭車沒有網路用,我可能一輩子不會把這篇文章寫出來),但我還是會儘量把上面寫的東西修得更完整的啦....

2013年9月27日 星期五

好用的 mockup、wireframe 工具:Balsamiq Mockups

對於一個美術白癡來說,要設計一個網站的外觀,實在有點困難。有時候要把心中構想先畫出來給人看,都覺得力不從心,所以只好直接寫一個網頁出來,調 CSS 調老半天,只為了把心中的畫面呈現給人「看看再說」。上面所說的作法,做出來的東西比較類似於 prototype,也就是已經包含了部分 CSS/JavaScript 的完整網頁,也許缺的只是真正會 work 的功能還有各種比較細節的調整而已。但萬一這樣做出來的東西不合人家(也許是主管、客戶,甚至是自己)的胃口,那花的這些時間就算沒有全部白費,也浪費掉不少了。

或許有人會說,「我寫 prototype 超快我超強!」但你再強你有人家設計師用 photoshop 隨便畫畫強嗎?雖然人家畫出來的就是一張圖,不會動也不能點,但是省時省力,又能把想表現的重點突顯出來,用來作為前期的討論跟比較相當適合!可能講成這樣還是有人鐵齒...那試想一個狀況:客戶看了你的 prototype 之後,跟你講了一堆元件說要換位置,下面這個按鈕移到上面去(立刻修改HTML),這個選單要大一點(趕快調整 font-size, width, height,啊破版了再改一下另外那個...),這些東西的間距要小一點,整個寬度要一樣(調 margin 調老半天).....總而言之,對於外觀的要求,在圖片上討論常常比在程式碼中討論來得容易,所以也才有所謂的 wireframe 與 mockup 的出現。

這邊不深究 wireframe 與 mockup 兩者的差別,下面通通用 mockup 來表示。Mockup 是一種用來呈現成品大致外觀的工具,他不能拿來使用,但是可以讓人容易想像成品的呈現方式 (人類果然是視覺的動物...)。以前看過一些人是用 photoshop 之類的影像處理軟體在畫 mockup,之後才去作切版套版...但對於一個美術白癡而言實在太困難了,小弟實在是 photoshop 苦手orz。好在有人推薦了這套工具:Balsamiq Mockups,他是一個很好用的畫 mockup 工具,內建數十種常用元件,可以用來設計網站、視窗程式或是手機介面。下面就直接來介紹一下。

下載跟安裝都很簡單,不多說了,Google 一下或是點上面的連結就可以下載....我是在 Mac 上安裝的,但他也有 Windows 跟 Linux 版,甚至還有 Google Drive 的 plugin (!)

這是開啟後的畫面,可以看到左下角指出我只有 7 天的試用期,購買的話則是美金 $79,其實對我來說有點貴XD 但如果是工作所需的話,其實這個價倒不會有什麼問題...

只要花幾分鐘,從上面拉一些元件下來,打幾個字(不打也沒關係,他都有預設的文字),就可以快速「畫」出一個網站...我實在很喜歡這個 browser window,讓我畫出來的東西突然加了20分....XD
覺得中間的框框實在不太好看...所以就塞了點假文章進去。(我用了一個英文的假文產生器,如果想寫中文的可以參考看看和多的中文假文產生器) 嗯....看起來還算有模有樣呢~
裡面的元件實在太多了,每次都要用選的很麻煩,如果知道大概名字的話,可以透過左上角的 Quick Add 直接搜尋加入
元件中文字的設定也很簡單,常常都是簡單打個字他就會幫你做出想要的東西了,前提當然是元件要用對...每個元件預設都有範例可以依循,所以不用擔心不會打,基本的用法都已經在範例中了!
每個元件也都有相關的一些外觀設定可以用,可以隨意玩玩
例如這個圖片元件(Image),預設是一個 placeholder 的角色,但也可以讓你放入真正的圖片在你的 mockup 中
還可以剪裁圖片成你要的樣子
放在畫面中大概就長這樣.........XD
另外 Balsamiq Mockups 也提供 iPhone 的框框,最有趣的是還有 iPhone 4 跟 iPhone 5 可以選XDDDD,當然也有不同方向提供你作設計。
既然有手機元件可以玩,那就自以為很潮的來作個手機版網頁...把旁邊的主要元素複製一份移到手機框框裡面,調整大小之後,就完成了基本的元素!其實就像 RWD 一樣,都是一樣的元素,只是套用了不同 CSS 一樣...然後接著在右上角加個會跳出選單的小 icon,噢,Balsamiq Mockups 提供的 icon 還滿多的,還不賴!
於是乎....就完成了一個看起來好像有模有樣的「網站雛形」。如果要拿來做文件,或是提供給一般人看,其實這樣就可以假裝很威了,哈哈!這些東西做起來花不到 20 分鐘呢~其中還包含慢慢瀏覽有哪些元件可以用的時間。如果用 photoshop 來畫,以我的能力可能要花一整個早上吧....@@
再來是關於中文的問題,以前的版本會無法顯示中文,所以上面的 demo 才會全部都用英文來當範例,結果沒想到,現在的版本已經把中文的問題解決了XD 所以不需要「Use System Font」才能使用中文囉!雖然這字體實在不像英文版這麼可愛....但至少能用啦XD
最後,總不能畫完東西只給自己看吧XD Balsamiq Mockups 提供 export 的功能,可以輸出成 PNG 圖檔或是 PDF 檔,當然也可以放進系統剪貼簿或是直接列印出來。
這樣的工具也許有些人(可能是專業設計師或 photoshop 強者)認為沒什麼,但每樣工具都有他不同的目標。Balsamiq Mockups 並沒有辦法用來作各種不同的繪圖,但是他專注於繪製 mockup 這件事上,所以可以讓這件事變得很簡單,門檻很低,特別是對小弟這種美術白癡來說相當受用!不管是網站設計公司、SOHO或是要設計教材之類的用途,都相當實用。推薦~ :P

2013年4月7日 星期日

在 Windows 上安裝 Redmine 2.3.0 的筆記

Redmine 是一套很有名的專案管理系統,建構於 Ruby on Rails 之上。網路上搜尋「Redmine」都可以找到很多相關資料,這邊就不贅述功能。

由於 Ruby/Rails 在 MacOSX 跟 Linux 相對比較友善,所以要找 Redmine on Windows 的安裝教學實在不太好找,中文資料更是寥寥可數。加上 Rails 進步迅速,很多 07~09 年的資料其實很可能都已經過時,所以乾脆自己研究完寫一篇XD

不過由於現在實在沒什麼空寫網誌,所以簡單的把方法跟過程記錄一下....

這邊主要是根據 http://www.redmine.org/projects/redmine/wiki/RedmineInstall 上面的說明,而 Redmine 安裝相依於特定的 rails 與 ruby 版本,可以參考前面這個連結裡面的表格。而我這邊以 Ruby 1.9.3 + Rails 3.2.13 + Redmine 2.3.0 為目標。

以下為安裝步驟:(注意!安裝過程中的路徑最好都不要有中文,如果出現奇怪的問題,請先嘗試把目錄或檔案放到沒有中文路徑的位置。目前我已經碰到某些 gem file 放在中文路徑會沒辦法安裝)

  1. 下載 Ruby installer 與 Development Kit (這邊我都選 32-bit 版本)
    http://rubyinstaller.org/downloads
  2. 下載 Redmine (點第一段的 RubyForge 連結)
    http://www.redmine.org/projects/redmine/wiki/Download
  3. 安裝 Ruby
    雙擊 ruby installer 並一直 Next,注意中途應選擇「Add Ruby executables to your PATH」
  4. 安裝 Development Kit
    將 Development Kit 解壓縮到 C:\Ruby193\DevKit,並打開命令提示字元執行以下指令
    ruby C:\Ruby193\DevKit\dk.rb init
    ruby C:\Ruby193\DevKit\dk.rb install
    
  5. 解壓縮 Redmine 至特定目錄,在此以「$REDMINE」表示
  6. 進入 $REDMINE\config\ 目錄中,複製 database.yml.example 至 database.yml
  7. 根據自己的環境,設定 database.yml。
    在此我打算使用 SQLite3 作為 production (雖然官方不建議),所以只需要很簡單的3行
    production:
      adapter: sqlite3
      database: db/redmine.sqlite3
    
  8. 安裝相依 gems
    cd $REDMINE
    gem install bundler
    bundle install --without development test rmagick # rmagick 還需要額外安裝其他東西,乾脆就先不用了
    
  9. 設定 Redmine
    set RAILS_ENV=production
    rake generate_secret_token
    rake db:migrate
    rake redmine:load_default_data
    ruby script/rails server webrick -e production
    
  10. 到這裡,Redmine server 應該已經 run 起來了,連上 http://localhost:3000/ 看看吧!
通常應該這樣就結束了,但是發現某些文章提到用 mongrel,他似乎也是個 ruby 寫的 web server 吧....查了一下資料發現,有個更新的東西叫做 thin,根據 thin 的說法,它用了 mongrel parser....然後效能比 mongrel 好.....哎呀反正我也不太懂,反正試試看就對了XD
安裝 thin 很簡單:
gem install thin
執行則是
thin start
結果竟然跑不起來?找了一些資料才發現,原來要加上 Gemflie.local 才行....反正結論是,應該這樣安裝
  1. 先編輯 $REDMINE\Gemfile.local
    gem 'thin'
    
  2. 執行以下指令
    bundle install
    thin start
    
最後來個番外篇...如果想把相關檔案全都抓下來,拿到一台沒有網路的電腦安裝,應該怎麼做呢?可以透過 bundle 幫我們做這件事!
在 $REDMINE 下,執行 bundle package,就可以在 $REDMINE\vender\cache\ 裡面找到目前配置下所需的所有 gems,但有趣的是,bundler 自己不在裡面.....XD 要找到 bundler 的 gem 的話,可以去 C:\Ruby193\lib\ruby\gems\1.9.1\cache\ 裡面撈...找到 bundle 與 bundler 這兩個 gem,直接複製即可。(別問我,我不曉得這兩個分別是幹嘛的....)
另外,我們還得偷帶走 $REDMINE\Gemfile.lock 這個檔案,這樣 bundle 才能在沒有網路連線的情況下,了解 gems 之間的相依性。既然都拿 Gemfile.lock 了,那剛剛安裝 thin 用的 Gemfile.local 也順便帶走吧!所以,想要離線安裝,先準備好以下這些檔案
  • Ruby Installer
  • Development Kit
  • Redmine
  • 挖出來的 gems (包含 bundler, bundle, 以及 bundle package 產生的那些)
  • Gemfile.local 與 Gemfile.lock
然後根據以下步驟安裝
  1. 進行上面一般安裝步驟的第 3~7 步
  2. 安裝 gems (假設我把檔案放在 C:\Ruby193\gems.local)
    cd C:\Ruby193\gems.local
    gem install -l *
    
  3. 把 Gemfile.local 與 Gemfile.lock 丟回 $REDMINE
  4. 執行以下指令
    cd $REDMINE
    bundle install --without development test rmagick --local
    
  5. 進行一般安裝步驟第 9 步
  6. 完成!
希望上面的筆記也可以對其他人有幫助.....

好啦我知道這篇文章排版很糟糕...但我實在沒空調整了orz 再抱怨一次 Blogger 的寫文章介面實在不太好用 :(

2012年9月24日 星期一

修改 Debian 或 Ubuntu 預設 editor

前幾天突然想到一個問題....由於 Ubuntu 預設 editor 是 nano,這對於一個 vim user 來說實在是不太舒服啊XD 所以稍微找了一下,找到一個很簡便的作法,可以用來修改預設 editor,方式如下:


$ sudo update-alternatives --config editor    
[sudo] password for crboy: 
There are 4 choices for the alternative editor (providing /usr/bin/editor).

  Selection    Path                Priority   Status
------------------------------------------------------------
* 0            /bin/nano            40        auto mode
  1            /bin/ed             -100       manual mode
  2            /bin/nano            40        manual mode
  3            /usr/bin/vim.basic   30        manual mode
  4            /usr/bin/vim.tiny    10        manual mode

Press enter to keep the current choice[*], or type selection number: 3
update-alternatives: using /usr/bin/vim.basic to provide /usr/bin/editor (editor) in manual mode.
$ 

其實重點就是那個 sudo update-alternatives --config editor 而已啦XD 後面照著指示操作就好了....:)

2012年9月21日 星期五

PHP 自動發信程式

之前為了發 COSCUP 的通知信,參考網路上的範例用 PHP 寫了個小程式。我把範例程式放在這個 gist 上:https://gist.github.com/3135053

其實明顯看得出來,程式沒什麼架構,純粹只是為了可以 work 而寫的拋棄式程式。不過丟出來一方面可以當自己的筆記,以後要找比較好找,另一方面,也許另一個人想要寫類似的小玩具的時候就可以參考了 :P

以下是一些簡單的介紹。

我使用這個程式的狀況是,我需要寄一封信給很多人,而我又希望每個人收到的信,收件者只有自己,而且信件開頭還會寫上他的名字,這樣比較貼心。所以沒辦法用「寄給自己 and BCC」的方法達成,只好透過程式完成。

這個小程式實際上是透過一套稱為 PHPMailer 的 library 做真正的寄信動作,所以困難的部分都被它做走了!我們只要把該設定的東西設定一下就差不多。要設定的項目大概有這些:

  • SMTP server 資訊 (主機、port、認證方式...等)
  • 登入資訊 (我使用 Gmail 寄信,所以需要登入)
  • 寄件者與收件者資訊
  • 信件資訊 (標題與內文)
在我這邊,每封信不同的地方只有收件者與內文,所以我會每次重新產生並設定,其餘部分都在最前面寫死。

收件者與內文的部分,我是先將收件者名單從 Google Spreadsheets 中匯出成 .tsv 檔 (因為比較容易 parse),再直接讀入。這個寫在 function parse_name_list_tsv($filename) 中。然後用一個自己刻的簡單 template tool (function fill_template($content, $data), 才 4 行XD) 去加上 user-specified 的資料,最後送信收工!

一些有趣的小細節:
  • 忘記為什麼了,我一開始跑起來會出現跟時區有關的錯誤,所以必須加上 date_default_timezone_set('Asia/Taipei'); 來避免這個問題
  • 因為我的 Google Account 有開啟兩階段驗證 (2-Step Verification),而我們這種小工具當然不可能支援,所以要為我們的程式特別創造一組應用程式密碼,才能正常使用喔!
  • 一開始不小心抓到舊版的 PHPMailer,它有個 bug 就是,CC 的人會變成 BCC (或是反過來...我忘了),所以我直接從它的 source code 裡找到有問題的那段修掉了 (open source rocks!),不過還是不太確定是不是我使用錯誤。結果後來發現有新版 PHPMailer,就特別關注了一下這個問題,發現已經被修掉了XD 修的方法一樣....哈!
  • 因為 PHPMailer 新增收件人的 API 是 AddAddress(),所以要注意,要更改收件人的話必須先執行 ClearAddresses(),否則收件人清單會一直長....(好險我在正式寄信前的測試就發現這個問題...orz)
大概...差不多是這樣吧!突然有點佩服自己,一點小東西也可以囉嗦這麼多,哈哈....