Negroni是一個非常棒的中間件,尤其是其中間件調用鏈優雅的設計,以及對GO HTTP 原生處理器的兼容。我以前寫過兩篇文章,對Negroni進行了專門的分析,沒有看過的朋友可以在看下。
Go語言經典庫使用分析(五)| Negroni 中間件(一) http://www.flysnow.org/2017/08/20/go-classic-libs-negroni-one.html
Go語言經典庫使用分析(六)| Negroni 中間件(二) http://www.flysnow.org/2017/09/02/go-classic-libs-negroni-two.html
Negroni Static中間件
Negroni設計的時候,內置了3箇中間件,它們分別是Logger、Recovery和Static,用於日誌處理,故障恢復以及靜態文件處理,今天來分析下靜態文件處理的源碼實現。
Negroni的靜態文件處理中間件,主要是爲了把我們電腦(服務器)上的文件託管到Web服務上,可以通過HTTP的方式訪問查看對應的電腦上的文件。如果請求的文件不存在,那麼請求將會轉給下一個中間件;如果文件存在,那麼就會顯示靜態文件的內容,請求到此終止。
使用Negroni實現靜態處理非常簡單,我們看一個示例。
package main import ( "github.com/urfave/negroni" "net/http" ) func main(){ n := negroni.New() n.Use(negroni.NewLogger()) n.Use(negroni.NewStatic(http.Dir("/tmp"))) n.Run(":8080") }
示例中,把/tmp
目錄下的文件,託管在了Web服務器上,可以通過HTTP的方式訪問到。假如/tmp
下有一個1.txt
文件,那麼我們打開瀏覽器,輸入地址http://127.0.0.1:8080/1.txt
即可訪問對應的1.txt
文件的內容。
示例中,我添加了一個n.Use(negroni.NewLogger())
中間件,用於打印訪問日誌,比如請求的URL等。
從示例代碼中,可以看到,Negroni主要通過negroni.NewStatic
函數,生成一個靜態文件處理的中間件。
// NewStatic returns a new instance of Static func NewStatic(directory http.FileSystem) *Static { return &Static{ Dir: directory, Prefix: "", IndexFile: "index.html", } }
該函數接受一個http.FileSystem
類型的參數,用於指定要處理的目錄。從函數的源代碼可以看出,返回的其實是一個*Static
,這個Static
結構體有三個字段。
Negroni Static 結構體分析
type Static struct { // 靜態服務要處理目錄 Dir http.FileSystem // 給這些靜態文件添加的URL前綴,主要用於對處理的靜態文件分類 Prefix string // 索引文件,訪問Dir目錄的時候,顯示這個索引文件的內容 IndexFile string }
第一個字段我們已經在示例中演示了,下面看看另外兩個字段的用法,我通過示例說明,更容易理解。
Negroni Static Prefix 分析
現在我要託管/tmp
目錄下的文件,但是我又想通過http://hostname/tmp/1.txt
這樣的方式進行歸類,讓訪問的人知道,這些文件是在tmp/
下的,這就可以用到Prefix
字段了。我們把原來的示例修改下看看。
func main(){ n := negroni.New() n.Use(negroni.NewLogger()) n.Use(&negroni.Static{ Dir: http.Dir("/tmp"), Prefix: "/tmp", IndexFile: "index.html", }) n.Run(":8080") }
我們不再使用NewStatic
方法,而是直接通過&negroni.Static{}
構建一個靜態文件處理的中間件,在構建的時候,指定Prefix
的值爲/tmp
,現在我們再訪問/tmp
下的1.txt
文件,就只能通過http://127.0.0.1:8080/tmp/1.txt
,而不是原來的http://127.0.0.1:8080/1.txt
,這樣我們通過給URL加前綴,達到文件分類的目的,其實就是增加一個URL path。
本文爲原創文章,轉載註明出處&&,歡迎掃碼關注公衆號
flysnow_org
或者網站http://www.flysnow.org/,&&第一時間看後續精彩文章。覺得好的話,&&順手分享到朋友圈吧,感謝支持。
以上這些是如何實現的呢?我們看下靜態文件處理的源代碼,結合分析。
file := r.URL.Path // if we have a prefix, filter requests by stripping the prefix if s.Prefix != "" { if !strings.HasPrefix(file, s.Prefix) { next(rw, r) return } file = file[len(s.Prefix):] if file != "" && file[0] != '/' { next(rw, r) return } } f, err := s.Dir.Open(file)
以上這段代碼,是通過URL路徑,查找對應的靜態文件的核心代碼。從源代碼中可以看到,對Prefix
不爲空的情況進行了特殊處理,如果Prefix
不爲空,那麼我們就要從URL Path中去掉這個Prefix
,因爲Prefix
是我們自己強加入的,不屬於文件路徑中的部分,所以要剝離掉,這樣纔可以得到要處理文件的真實路徑,也就是源代碼中的file
變量,最後通過s.Dir.Open(file)
打開文件,得到其內容顯示即可。
Negroni Static IndexFile 分析
最後一個字段是IndexFile
,用於指定索引文件。對於我們使用過Apache、Nginx的都比較熟悉,我們指定了index.html
作爲索引文件後,那麼我們再訪問http://127.0.0.1:8080/tmp/
這個目錄,顯示的其實是index.html
文件的內容,因爲我們訪問的其實是http://127.0.0.1:8080/tmp/index.html
,這就是IndexFile
的作用。
Negroni的靜態處理器中間件源代碼中,對於IndexFile
也非常簡潔,容易理解。
// try to serve index file if fi.IsDir() { file = path.Join(file, s.IndexFile) f, err = s.Dir.Open(file) } //省略了無關代碼
這個源代碼處理很簡單,如果是一個目錄的話,就把目錄和IndexFile
拼接成一個新的文件路徑,進行二次打開。
如何在請求頁面上顯示文件內容
在一系列的打開、判斷中,如果最後可以找到正確的文件,拿到內容,那麼就可以把內容展示到瀏覽器的頁面上了。
http.ServeContent(rw, r, file, fi.ModTime(), f)
非常簡潔的一段代碼,即達到了我們的目的,該函數可以把ReadSeeker
中的內容,顯示到請求的頁面上。
func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) { sizeFunc := func() (int64, error) { size, err := content.Seek(0, io.SeekEnd) if err != nil { return 0, errSeeker } _, err = content.Seek(0, io.SeekStart) if err != nil { return 0, errSeeker } return size, nil } serveContent(w, req, name, modtime, sizeFunc, content) }
相比io.Copy
,http.ServeContent
可以自動計算響應的內容長度、可以自動識別內容的MIME類型,還可以處理If-Match,If-Unmodified-Since,If-None-Match,If-Modified-Since和If-Range的要求。
因爲* os.File
實現了io.ReadSeeker
接口,所以我們可以直接使用文件的內容。
小結
好了,到了這裏,我們已經分析完了Negroni中靜態文件處理中間件的實現,看完後相信你也可以寫自己的靜態文件處理服務了,可以自己試試,寫一個和http.FileServer
類似功能的靜態文件處理服務。