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