不久前我發布了nosurf,這是Go語言的一個CSRF跨站請求偽造(Cross-Site Request Forgery)中間件。編寫一個看起來簡單並且小巧的包就足以讓你愛上Go處理HTTP的方式。然而,這卻取決於我們擁抱標准的HTTP設施或者是粉碎它,犧牲可組性和模塊化。
與此同時,在Go語言中,唯一需要的接口自2009年就一直在發展中,盡管它從那時起經歷了許多嚴重的改變,但是在2011年末,Go 1.0發布前的幾個月,它已經變得非常穩定。
當然,我說的是mightyhttp.Handler。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
為了能夠處理HTTP請求,你的類型僅僅需要實現這個方法就可以了。這個方法從給定的*Request讀取請求信息,並且將響應信息寫在給定的ResponseWriter中。看起來很簡單,是吧?
然而,在此基礎之上的建立抽象時,有些東西弄錯了。舉個例子,Mango,被它的作者描述為“一個模塊化的Go語言Web應用程序框架,靈感來自Rack和PEP333”。
這是一個Mango應用程序的樣子:
func Hello(env mango.Env) (mango.Status, mango.Headers, mango.Body) {
return 200, mango.Headers{}, mango.Body("Hello World!")
}
看起來簡單,簡潔,並且非常類似於WSGI或者Rack,對不對?除了一件事情。在使用動態/鴨式類型時,你可以在任何一個body上用上迭代,這裡的mango.Body只是一個簡單的字符串。從本質上講,這樣就丟掉了做任何形式的流式響應的能力。即便是暴露一個ResponseWriter,任何寫入它的東西都將與返回的值沖突,因為它們只在函數結束時返回,在已經調用了ResponseWriter之後。
這不好。你是否需要基於現有的net/http的另外一個接口,只是你的口味問題,但即使你這樣做了,它也不應該拿掉其它的功能。一個接口,在其上的編寫代碼感覺更棒,但是它帶走了重要的功能,顯然就遜色了。
現在流行的一個“微型”Web框架web.go使用了一種簡單,但更好的方法。它的處理程序使用一個指向web.Contextas的指針作為可選的第一個參數。
type Context struct {
Request *http.Request
Params map[string]string
Server *Server
http.ResponseWriter
}
// ...
func hello(ctx *web.Context, val string) string {
return "hello " + val
}
web.Context不再采取標准的HTTP處理程序結構。相反,Request參數可作為結構成員和Context,實現ResponseWriter所需要的方法,故而就讓它自身就嵌入了原來的ResponseWriter。你從函數返回字符串(如果有的話)只是簡單的追加到了response上。
這是一個很好的設計選擇,我認為它能迎合Go的理念。盡管你得到一個不錯的更高級別的API,但你不必犧牲對請求處理的底層控制。
Go的HTTP庫基礎設施,盡管增長迅速,卻仍然有一些空白需要填補。但是我們需要注意的最後一件事是碎片與惱人的不兼容性,這是由於糟糕的設計和實際上帶走許多功能的抽象所導致。依我之見,擁抱和支持標准的Go HTTP設施就是,最直接地擁有功能化和模塊化的第三方HTTP工具。