這是一個Web Server的時代,apache2與nginx共舞,在追求極致性能的路上,沒有最高,只有更高。但這又是一個追求個性化的時代,有些Web Server并沒有去擠“Performance提升”這一獨木橋,而是有著自己的定位,Caddy就是這樣一個開源Web Server。
Caddy的作者Matt Holt在caddy官網以及FAQ中對caddy的目標闡釋如下: 其他Web Server為Web而設計,Caddy為human設計。功能定位上,與經常充當最前端反向代理的nginx不同,caddy致力于成為一個易用的靜態 文件Web Server。可以看出Caddy主打易用性,使用配置簡單。并且得益于Go的跨平臺特性,caddy很容易的支持了三大主流平臺:Windows、 Linux、Mac。在Caddy開發者文檔中,我們可以看到caddy還可以在Android(linux arm)上運行。caddy目前版本為0.7.1,還不穩定,且后續版本可能變化較大,甚至與前期版本不兼容,因此作者目前不推薦caddy在生產環境被 重度使用。
關注caddy,是因為caddy填補了go在通用web server這塊的空白(也許有其他,但我還不知道),同時Web server in go也“響應”了近期Golang去C化的趨勢(Go 1.5中C is gone!),即便caddy作者提到caddy的目標并非如nginx那樣。但未來誰知道呢?一旦Go性能足夠高時,一旦caddy足夠穩定時,自然而 然的就會有人將其用在某些應用的生產環境中替代nginx或apache2了。一套全Go的系統,在部署、運維方面也是有優勢的。
一、安裝和運行caddy
和諸多go應用一樣,我們可以直接從caddy的github.com releases頁中找到最新發布版(目前是0.7.1)的二進制包。這里使用的是caddy_darwin_amd64.zip。
下載解壓后,進入目錄,直接執行./caddy即可將caddy運行起來。
$caddy
0.0.0.0:2015
在瀏覽器里訪問localhost:2015,頁面上沒有預期顯示的類似"caddy works!”之類的默認Welcome頁面,而是“404 Not Found"。雖然這說明caddy已經work了,但沒有一個default welcome page畢竟對于caddy beginer來說并不友好。這里已經向作者提了一個sugguestion issue。
二、caddy原理
Go的net/http標準庫已經提供了http server的實現,大多數場合這個http server都能滿足你的需要,無論是功能還是性能。Caddy實質上也是一個Go web app,它也import net/http,嵌入*http.Server,并通過handler的ServeHTTP方法為每個請求提供服務。caddy使用 http.FileServer作為處理 靜態文件的基礎。caddy的誘人之處在于其middleware,將諸多middleware串成一個middleware chain以提供了靈活的web服務。另外caddy中的middleware還可以獨立于caddy之外使用。
caddy從當前目錄的Caddyfile(默認)文件中讀取配置,當然你也可以通過-conf指定配置文件路徑。Caddyfile的配置格式 的確非常easy,這也符合caddy的目標。
Caddyfile總是以站點的Addr開始的。
單一站點的Caddyfile樣例如下:
//Caddyfilelocalhost:2015gziplog ./2015.log
Caddy也支持配置多個站點,類似virtualhost的 配置(80端口多路復用):
//Caddyfilefoo.com:80 {log ./foo.loggzip}bar.com:80 {log ./bar.loggzip}
為了實現風格上的統一,單一站點也最好配置為如下這種格式(代碼內部稱之為 Server Block):
localhost:2015 {gziplog ./2015.log}
這樣Caddyfile的配置文件模板樣式類似于下面這樣:
host1:port {middleware1middleware2 {… …}… …}host2:port {middleware1middleware2 {… …}… …}… …
關于middleware,在caddy文檔中有較為詳細的說明和例子。對于caddy這樣一個年輕的開源項目而言,其文檔還算是相對較全的,雖 然現在還不能和nginx、 apache比。
caddy中的middleware就是一個實現了middleware.Handler接口的struct,例如gzip這個 middleware:
// middleware.gotype Middleware func(Handler) Handlertype Handler interface {ServeHTTP(http.ResponseWriter, *http.Request) (int, error)}// gzip/gzip.gotype Gzip struct {Next middleware.Handler}func (g Gzip) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {return g.Next.ServeHTTP(w, r)}…. …gz := gzipResponseWriter{Writer: gzipWriter, ResponseWriter: w}// Any response in forward middleware will now be compressedstatus, err := g.Next.ServeHTTP(gz, r)… …}
middleware.Handler的函數原型與http.Handler的不同,不能直接作為http.Server的Handler使用。caddy使用了下面這個idiomatic go pattern:
type appHandler func(http.ResponseWriter, *http.Request) (int, error)
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {if status, err := fn(w, r); err != nil {http.Error(w, err.Error(), status)}}
當然這個pattern有很多變種,但思路大致類似。一個middleware chain大致就是handler1(handler2(handler3))的調用傳遞。
前面說過caddy是基于http.FileServer的靜態文件Web Server,FileServer總會作為middleware chain的最后一環,如果沒有配置任何middleware,那你的server就是一個靜態文件server。
三、caddy典型應用
【靜態文件Server】
caddy的最基礎應用實際就是一個靜態文件Server,底層由http.FileServer承載,當然caddy封裝了http.FileServer,做了一些攔截處理,最后將w, r傳遞給http.ServeContent去處理文件數據。
第一次執行./caddy,實際上就啟動了一個靜態文件Server。但這個server不默認支持你navigate directory。如果你知道website root目錄(如果沒有指定root,則caddy執行的當前路徑會作為website的root路徑)下的文件名,比如foo.txt,你可以在瀏覽器 中輸入:localhost:2015/foo.txt,caddy會執行正確的服務,瀏覽器也會顯示foo.txt的全文。
對于靜態文件Server,caddy支持在website的root路徑下首先查找是否有如下四個文件:
//caddy/middleware/browse/browse.govar IndexPages = []string{"index.html","index.htm","default.html","default.htm",}
如果查到有其中一個,則優先返回這個文件內容,這就是靜態站點的首頁。
如果要支持目錄文件列表瀏覽,則需要為website配置browse middleware,這樣對于無index file的目錄,我們可以看到目錄文件列表。
localhost:2015 {browse}
【反向代理】
caddy支持基本的反向代理功能。反向代理配置通過proxy middleware實現。
localhost:2015 {log ./2015.logproxy /foo localhost:9001proxy /bar localhost:9002}
當你訪問localhost:2015/foo時,實際上訪問的是9001端口的服務程序;
當你訪問localhost:2015/bar時,實際上訪問的是9002端口的服務程序。
【負載均衡】
Caddy支持負載均衡配置,并支持三種負載均衡算法:random(隨機)、least_conn(最少連接)以及round_robin(輪詢調度)。
負載均衡同樣是通過proxy middleware實現的。
localhost:2015 {log ./2015.logproxy / localhost:9001 localhost:9003 {policy round_robin}proxy /bar localhost:9002 localhost:9004 {policy least_conn}}
【支持fastcgi代理】
caddy同樣支持fastcgi代理,可以將請求通過fastcgi接口發送給后端的實現fastcgi的server。我們以一個"hello world"的php server為例。
mac os上自帶了php-fpm,一個實現了fastcgi的php cgi進程管理器。caddy將請求轉發給php-fpm監聽的端口,后者會啟動php-cgi解釋器,解釋index.php,并將結果返回給caddy。
mac os上的php-fpm默認沒有隨機啟動。我們需要簡單配置一下:
$mkdir phptest
$mkdir -p phptest/etc
$mkdir -p phptest/log
$cd phptest
$sudo cp /private/etc/php-fpm.conf.default ./etc
$cd ./etc
$sudo chown tony php-fpm.conf.default
$mv php-fpm.conf.default php-fpm.conf
編輯php-fpm.conf,保證下面兩項是非注釋狀態的:
error_log = log/php-fpm.log
listen = 127.0.0.1:9000
我們通過network socket進行fastcgi通信。
回到phptest目錄下,執行:
php-fpm -p ~/test/go/caddy/phptest
執行后,php-fpm就會轉入后臺執行了。
接下來我們來配置Caddyfile:
localhost:2015 {fastcgi / 127.0.0.1:9000 phplog ./2015.log}
這里配置的含義是:將全部請求轉發到9000端口,這里的php是一個preset(預配置集合),相當于:
ext .phpsplit .phpindex index.php
我們在phptest目錄下創建一個index.php文件,內容如下:
<?php echo "Hello World/n"; ?>
好了,現在啟動caddy,并使用瀏覽器訪問localhost:2015試試。你會看到"Hello World"呈現在瀏覽器中。
【git push發布】
對于一些靜態站點,caddy支持git directive,實現在server啟動以及運行時定期git pull你的項目庫,將最新更新pull到server上。
caddy文檔中給出兩個例子:
第一個是一個php站點,定期pull項目庫,實現server更新:
git git@github.com:user/myphpsite {key /home/user/.ssh/id_rsa}fastcgi / 127.0.0.1:9000 php
第二個是一個hugo支撐的靜態站點,每次pull后,執行hugo命令生成新的靜態頁面:
git github.com/user/site {path ../then hugo –destination=/home/user/hugosite/public}
注意:git directive并非middleware,而是一個單獨的goroutine實現的。
四、小結
caddy的功能不局限于上面的幾個例子,上面只是幾個最為常見的場景而已。caddy目前還很年輕,應用不多,但知名golang網站 gopheracademy.com(GopherCon組織方)是由Caddy support的。caddy還在積極進化,有興趣的Gopher可持續關注。
新聞熱點
疑難解答
圖片精選