Go搭建靜態頁面server筆記

go是一門簡潔強大的語言,簡單體驗以後以爲對於網絡和命令行的支持也很是棒,本文介紹一下go實現靜態服務器的大體流程。html

基礎實現

最近接手了gobyexample的翻譯工做,將項目重構後須要本地的測試環境。
因爲想要頁面的url顯示爲「https://gobyexample.xgwang.me/hello-world」這種結尾不帶「/」的形式,子頁面沒有帶上html,而且有圖片資源所以須要一個static server。git

根據golang wiki,實現這個簡單server只須要...一行代碼:github

package main

import "net/http"

func main() {
    panic(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))
}

加入log後稍微改寫一下,放在咱們項目的tools目錄下:golang

package main

import (
    "log"
    "net/http"
)

func main() {
    // Simple static webserver:
    port := ":8080"
    log.Printf("Serving at: http://localhost%s\n", port)
    err := http.ListenAndServe(port, http.FileServer(http.Dir("public")))
    if err != nil {
        log.Fatal("ListenAndServe fail:", err)
    }
}

再來一個可執行的tools/serve文件web

#!/bin/bash
exec go run tools/serve.go

ok如今只須要tools/serve就能夠啓動這個服務器了。bash

404

一切看起來很正常,但若是咱們訪問一下不存在的某個頁面,404.html並不會被serve,這是由於go提供的FileServer並不知道咱們自定義的404頁面。
因此咱們須要將http.FileServer改成一個自定義的Handler服務器

寫go的時候體驗特別好的一點就是go官方團隊提供了很opinionated的convention,好比go-get,go-fmt等。
在咱們輸入http.FileServer時會自動在imports中添加相應的庫,跳轉到源碼後看到了這個函數的實現:網絡

type fileHandler struct {
    root FileSystem
}

// FileServer returns a handler that serves HTTP requests
// with the contents of the file system rooted at root.
//
// To use the operating system's file system implementation,
// use http.Dir:
//
//     http.Handle("/", http.FileServer(http.Dir("/tmp")))
//
// As a special case, the returned file server redirects any request
// ending in "/index.html" to the same path, without the final
// "index.html".
func FileServer(root FileSystem) Handler {
    return &fileHandler{root}
}

func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
    upath := r.URL.Path
    if !strings.HasPrefix(upath, "/") {
        upath = "/" + upath
        r.URL.Path = upath
    }
    serveFile(w, r, f.root, path.Clean(upath), true)
}

因而咱們知道了這裏的函數須要返回的Handler有一個ServeHTTP方法。可是這裏的serveFile並不能直接由http.serveFile調用:go規定一個package內小寫字母開頭的均爲私有,不能被外部package訪問。函數

可是沒有關係,咱們能夠在fileHandler上再包裝一層代理,在執行完咱們判斷文件存在的邏輯後執行原先全部fileHandler.ServeHTTP的內容,修改後的代碼以下:工具

type fileHandler struct {
    root http.FileSystem
    h    http.Handler
}

func fileServer(root http.FileSystem, h http.Handler) http.Handler {
    return &fileHandler{root, h}
}

func (f *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    path := r.URL.Path
    if _, err := os.Stat("public/" + path); os.IsNotExist(err) {
        http.ServeFile(w, r, "public/404.html")
        return
    }
    f.h.ServeHTTP(w, r)
}

func main() {
    // Simple static webserver:
    port := ":8080"
    log.Printf("Serving at: http://localhost%s\n", port)
    fs := http.Dir("public")
    http.Handle("/", fileServer(&fs, http.FileServer(&fs)))
    err := http.ListenAndServe(port, nil)
    if err != nil {
        log.Fatal("ListenAndServe fail:", err)
    }
}

在傳入FileSystem的時候傳入指針也避免建立,頗有C的感受。

小細節

基本功能都已經實現,但做爲一個命令行工具,但願再進行一些完善。

首先咱們須要支持傳參,go對於命令行參數的支持很是棒,只要引入builtin的flag包以後,咱們加入

port := flag.String("port", ":8080", "localhost port to serve")
path := flag.String("path", "public", "public files path")
flag.Parse()

就能夠獲得*string類型的命令行參數,而且天生支持默認值和描述,測試一下go run tools/serve.go -h,能夠獲得:

Usage of /var/folders/sd/cwk5fwtd4ms5vflhq5_0_5rr0000gn/T/go-build178666598/command-line-arguments/_obj/exe/serve:
  -path string
        public files path (default "public")
  -port string
        localhost port to serve (default ":8080")

準備serve文件以前,再輸出一下帶有格式的信息加粗一下咱們傳入的參數:

log.Printf("Serving \x1b[1m%s\x1b[0m at: http://localhost\x1b[1m%s\x1b[0m\n", *path, *port)

這裏\x1b[0m表明「All attributes off(color at startup)」,\x1b[1m表明「Bold on(enable foreground intensity)」。

總結

go做爲靜態語言擁有能夠與動態語言媲美的靈活性,有完整易用的工具鏈和豐富的標準庫,是2017年增加最快的語言,簡單的同時很是強大。
但願有更多的人能夠一塊兒學習go,我正在完善Go By Example的翻譯,歡迎閱讀以及貢獻PR!

閱讀原文

相關文章
相關標籤/搜索