Go Fiber 框架系列教程 02:詳解相關 API 的使用

大家好,我是 polarisxu。

該系列第一篇文章發出後,大家褒貶不一,很正常。選什麼,不選什麼,大家自己評估,沒有什麼是最好的。我這個系列,更多隻是讓大家對 Fiber 有些瞭解,說不定正好合你胃口呢?

前面對 Fiber 有了大概的印象。今天着重較深入探討 Fiber 相關功能。

先從 fiber.New 函數配置開始。

01 配置

大部分 Go 框架,獲得實例的函數是不支持配置的,比如 Gin、Echo 等。但 Fiber 框架的 New 函數支持傳遞配置:

// New creates a new Fiber named instance.
//  app := fiber.New()
// You can pass optional configuration options by passing a Config struct:
//  app := fiber.New(fiber.Config{
//      Prefork: true,
//      ServerHeader: "Fiber",
//  })
func New(config ...Config) *App

一般情況,使用默認配置即可(即不手動傳遞配置),但有必要了解下,通過配置,我們能幹些什麼。

比如,我們希望響應頭中,Server 用自定義的。

config := fiber.Config{
  ServerHeader: "Go Fiber Framework",
}
app := fiber.New(config)

響應頭類似這樣:

Content-Length: 12
Content-Type: text/plain; charset=utf-8
Date: Mon, 20 Sep 2021 14:58:45 GMT
Server: Go Fiber Framework

實際上,在前文模板引擎使用的 Views 就是一個配置項。

目前配置 29 項之多,有不少是關於 HTTP 的配置。所有的配置和說明可以在文檔找到:https://docs.gofiber.io/api/fiber#config。建議掃一遍,有一個印象,方便將來有需求時知道在這裏找。

02 路由

標準庫 net/http 的路由比較簡單,這大概也是有各種路由庫(框架)的原因之一。

最簡單的路由莫過於直接匹配,如:

// 請求匹配到 /about
app.Get("/about", func(c *fiber.Ctx) error {
  return c.SendString("about")
})

命名路由(也叫參數路由)是一個強大框架必須的,即提供佔位符。比如:

app.Get("/hello/:username", func(c *fiber.Ctx) error {
  str := fmt.Sprintf("Hello, %s", c.Params("username"))
  return c.SendString(str)
})

這個路由就可以匹配任意的以 /hello/ 開頭的請求,比如:/hello/polarisxu,最後會輸出:Hello, polarixu

不過,如果請求的剛好是 /hello/ 呢?Fiber 會返回 404,報路由找不到。如果你希望這時候把 username 當空處理,而不是返回 404,可以在 :username 後加一個 ?

app.Get("/hello/:username?", func(c *fiber.Ctx) error {
  str := fmt.Sprintf("Hello, %s", c.Params("username"))
  return c.SendString(str)
})

此外,還有 +* 進行通配,區別在於 + 要求至少要有一個,而 * 可以沒有。通過 c.Params("+")c.Params("*") 獲得對於的值。

此外,Fiber 還支持有 -. 的複雜路由,例如:

// http://localhost:3000/flights/LAX-SFO
app.Get("/flights/:from-:to", func(c *fiber.Ctx) error {
    fmt.Fprintf(c, "%s-%s\n", c.Params("from"), c.Params("to"))
    return nil // LAX-SFO
})

注意,如果路由中需要包含特殊字符,比如 :,需要進行轉義。

因爲 Fiber 的目標之一是成爲 Go 最快、最清晰的 Web 框架,因此對於更復雜的路由,比如正則表達式,Fiber 不會支持。

Fiber 還提供了方法,返回所有註冊的路由信息:

var handler = func(c *fiber.Ctx) error { return nil }

func main() {
    app := fiber.New()

    app.Get("/john/:age", handler)
    app.Post("/register", handler)

    data, _ := json.MarshalIndent(app.Stack()"""  ")
    fmt.Println(string(data))

    app.Listen(":3000")
}

返回結果如下:

[
  [
    {
      "method""GET",
      "path""/john/:age",
      "params"[
        "age"
      ]
    }
  ],
  [
    {
      "method""HEAD",
      "path""/john/:age",
      "params"[
        "age"
      ]
    }
  ],
  [
    {
      "method""POST",
      "path""/register",
      "params": null
    }
  ]
]

可以輔助排查路由問題。

03 Static

上文介紹了服務靜態資源的 Static 方法,這裏詳細解釋下。

Static 方法可以多個。默認情況下,如果目錄下有 index.html 文件,對目錄的訪問會以該文件作爲響應。

app.Static("/static/""./public")

以上代碼用於項目根目錄下 public 目錄的文件和文件夾。

此外,Static 方法有第三個可選參數,以便對 Static 行爲進行微調,這可以通過 fiber.Static 結構體控制。

// Static defines configuration options when defining static assets.
type Static struct {
    // When set to true, the server tries minimizing CPU usage by caching compressed files.
    // This works differently than the github.com/gofiber/compression middleware.
    // Optional. Default value false
    Compress bool `json:"compress"`

    // When set to true, enables byte range requests.
    // Optional. Default value false
    ByteRange bool `json:"byte_range"`

    // When set to true, enables directory browsing.
    // Optional. Default value false.
    Browse bool `json:"browse"`

    // The name of the index file for serving a directory.
    // Optional. Default value "index.html".
    Index string `json:"index"`

    // Expiration duration for inactive file handlers.
    // Use a negative time.Duration to disable it.
    //
    // Optional. Default value 10 * time.Second.
    CacheDuration time.Duration `json:"cache_duration"`

    // The value for the Cache-Control HTTP-header
    // that is set on the file response. MaxAge is defined in seconds.
    //
    // Optional. Default value 0.
    MaxAge int `json:"max_age"`

    // Next defines a function to skip this middleware when returned true.
    //
    // Optional. Default: nil
    Next func(c *Ctx) bool
}

上文說,默認情況下,對目錄訪問的索引文件是 index.html,通過 Index 可以改變該行爲。如果想要啓用目錄瀏覽功能,可以設置 Browse 爲 true。

04 路由處理器

在前面提到,Fiber 有對應的方法支持所有 HTTP Method。除此之外,還有兩個特殊的方法:Add 和 All。

Add 方法是所有 HTTP Method 對應方法的底層實現,比如 Get 方法:

func (app *App) Get(path string, handlers ...Handler) Router {
 return app.Add(MethodHead, path, handlers...).Add(MethodGet, path, handlers...)
}

它底層調用了 Add 方法,做了兩次綁定,分別是 HEAD 和 GET,也就是說,對於 Get 方法,支持 HTTP GET 和 HEAD。

我之前寫過一篇文章:網友很強大,發現了 Go 併發下載的 Bug。Echo 框架,對於 Get 方法,只是 HTTP GET,不支持 HEAD 請求。目前看,Fiber 的做法更合理。如果你真的只需要 GET,可以通過 Add 方法實現。

而 All 方法表示支持任意 HTTP Method。

05 Mount 和 Group

Mount 方法可以將一個 Fiber 實例掛載到另一個實例。

func main() {
    micro := fiber.New()
    micro.Get("/doe", func(c *fiber.Ctx) error {
        return c.SendStatus(fiber.StatusOK)
    })

    app := fiber.New()
    app.Mount("/john", micro) // GET /john/doe -> 200 OK

    log.Fatal(app.Listen(":3000"))
}

Group 是路由分組功能,框架基本會支持該特性,對於 API 版本控制很有用。

func main() {
  app := fiber.New()

  api := app.Group("/api", handler)  // /api

  v1 := api.Group("/v1", handler)   // /api/v1
  v1.Get("/list", handler)          // /api/v1/list
  v1.Get("/user", handler)          // /api/v1/user

  v2 := api.Group("/v2", handler)   // /api/v2
  v2.Get("/list", handler)          // /api/v2/list
  v2.Get("/user", handler)          // /api/v2/user

  log.Fatal(app.Listen(":3000"))
}

06 fiber.Ctx 的方法

此外,就是 handler 中的參數 fiber.Ctx,這是一個結構體,包含了衆多的方法(不少都是方便開發的方法),在使用時查閱 API 文檔,或訪問 https://docs.gofiber.io/api/ctx 瀏覽。

這裏介紹幾個其他框架可能沒有的方法。

// BodyParser binds the request body to a struct.
// It supports decoding the following content types based on the Content-Type header:
// application/json, application/xml, application/x-www-form-urlencoded, multipart/form-data
// If none of the content types above are matched, it will return a ErrUnprocessableEntity error
func (c *Ctx) BodyParser(out interface{}) error

該方法將請求綁定到結構體。(響應的也有 QueryParser 方法,主要處理查詢字符串到結構體的綁定)

看一個例子:

type Person struct {
  Name string `json:"name" xml:"name" form:"name"`
  Pass string `json:"pass" xml:"pass" form:"pass"`
}

app.Post("/login", func(ctx *fiber.Ctx) error {
  p := new(Person)

  if err := ctx.BodyParser(p); err != nil {
   return err
  }

  log.Println(p.Name) // john
  log.Println(p.Pass) // doe

  return ctx.SendString("Success")
})

// 運行下面的命令進行測試

// curl -X POST -H "Content-Type: application/json" --data "{\"name\":\"john\",\"pass\":\"doe\"}" localhost:3000/login

// curl -X POST -H "Content-Type: application/xml" --data "<login><name>john</name><pass>doe</pass></login>" localhost:3000/login

// curl -X POST -H "Content-Type: application/x-www-form-urlencoded" --data " localhost:3000/login

// curl -X POST -F name=john -F pass=doe http://localhost:3000/login

// curl -X POST "http://localhost:3000/login?

關於獲取參數,包括路由參數、查詢參數、表單參數,Fiber 都非常友好的提供了可選的默認值形式,也就是說,當沒有傳遞對應值時,我們可以給一個默認值,比如:

// 10 是可選的。以下代碼表示,當 page 參數沒有傳遞,page=10
page := ctx.Query("page", 10)

默認值模式(可選參數)在 Fiber 中有大量使用,這能極大爲使用者帶來方便。

此外,路由參數還有 ParamsInt 方法,用來獲取 int 類型的路由參數。

07 小結

通過本文對 Fiber 內置功能的介紹,我的感受是,Fiber 爲開發者提供了很多便利。如果你沒有用過其他框架,可能沒有那麼大的感受。後續文章考慮出一個不同框架相關寫法的對比。

下篇文章介紹 Fiber 的中間件~

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/u0_wBhucMMzJYOwrZkV9IA