Iris 這個在 go 語言上地表最快的網頁框架-iris的middleware

middleware

在上篇文章介紹routing時有提到Party時有傳入一個handler不知道讀者們有沒有注意到,所以今天來介紹那個middleware

middleware

其實middleware在很多網頁程式裡都有,不過在介紹iris的middleware之前還是介紹一下什麼是middleware,所以接下來的內容就先介紹什麼是middleware,再來再開始介紹在iris怎麼撰寫middleware。

甚麼是middleware

什麼是middleware呢,這裡還是先看看middleware wiki的解釋吧,對於筆者的解釋與其類似,簡單來說明就是在網頁程式的生命週期中切入一段服務,藉此來處理所有的請求,接下來就開始跟大家介紹如何撰寫iris的middleware。

irir如何撰寫middleware

這邊利用iris-go custom-middleware來說明如何撰寫middleware,請大家先看看以下例子

func Logger() iris.Handler {
    return func(ctx iris.Context) {
        t := time.Now()

        // Set a shared variable between handlers
        ctx.Values().Set("framework", "iris")

        // before request

        ctx.Next()

        // after request
        latency := time.Since(t)
        log.Print(latency)

        // access the status we are sending
        status := ctx.GetStatusCode()
        log.Println(status)
    }
}

func main() {
    app := iris.New()
    app.Use(Logger())

    app.Get("/test", func(ctx iris.Context) {
        // retrieve a value set by the middleware.
        framework := ctx.Values().GetString("framework")

        // it would print: "iris"
        log.Println(framework)
    })

    app.Listen(":8080")
}

簡單講如何撰寫一個自訂義的middleware就是返還一個handdler,撰寫middleware的要點主要是區分處理請求之前跟處理完請求的部分,看到上面的例子就知道其中的區隔是一個ctx.Next(),呼叫這個方法之前是處理請求之前,呼叫完就是處理請求之後。
然而如何分享變數在handler之間呢,就是透過ctx.Values().Set(key string, value object)ctx.Values().GetString(key string) string之類的方法。

如何使用middleware

上面例子說明如何撰寫middleware,也提到一個如何使用middleware的方法,簡單來說就是透過app.Use(h handler)來使用middleware,不過使用的方式不只這樣,還有下列幾種方法

  • Party.UseError
  • Party.Use
  • Party.UseOnce
  • Application.UseGlobal
  • Party.Done
  • Application.DoneGlobal
  • Party.UseRouter
  • Application.WrapRouter

至於其中的差別可以看看iris-go middleware的內容,首先先看看他的例子

package main

import (
    "net/http"

    "github.com/kataras/iris/v12"
)

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

    app.WrapRouter(routerWrapper)
    app.UseRouter(routerMiddleware)
    app.UseGlobal(globalMiddleware)
    app.Use(useMiddleware)
    app.UseError(errorMiddleware)
    // app.Done(done)
    // app.DoneGlobal(doneGlobal)

    // Adding a OnErrorCode(iris.StatusNotFound) causes `.UseGlobal`
    // to be fired on 404 pages without this,
    // only `UseError` will be called, and thus should
    // be used for error pages.
    app.OnErrorCode(iris.StatusNotFound, notFoundHandler)

    app.Get("/", mainHandler)

    app.Listen(":8080")
}

func mainHandler(ctx iris.Context) {
    ctx.WriteString("Main Handler")
}

func notFoundHandler(ctx iris.Context) {
    ctx.WriteString("404 Error Handler")
}

上面是主程式以及綁定middleware的資訊,接下來看看期middleware的內容

func routerWrapper(w http.ResponseWriter, r *http.Request,
    router http.HandlerFunc) {

    if r.URL.Path == "/" {
        w.Write([]byte("#1 .WrapRouter\n"))
        /* Note for new Gophers:
            If we Write anything here on an error resource in the raw
            `net/http` wrapper like this one, then the response writer will
            automatically send a `200` OK status code (when we first write).
            Any error handler executed after this will not fire as expected.
            Also, when `w.WriteHeader` is called you can NOT change the
            status code later on.

            In Iris Handlers, if you write before the status code has been
            set, then it will also automatically send the 200 OK status
            code which then cannot be changed later. However, if we call
            `ctx.StatusCode` inside an Iris Handler without writing any
            content, then we can change the status code later on. When you
            need to change that behaviour, you must start the handler with
            a `ctx.Record` call.
        */
    }

    // Continue by executing the Iris Router and let it do its job.
    router(w, r)
}


func routerMiddleware(ctx iris.Context) {
    if ctx.Path() == "/" {
        ctx.WriteString("#2 .UseRouter\n")
    // The same caveat described in routerWrapper applies here as well.
    }

    ctx.Next()
}

func globalMiddleware(ctx iris.Context) {
    ctx.WriteString("#3 .UseGlobal\n")
    ctx.Next()
}

func useMiddleware(ctx iris.Context) {
    ctx.WriteString("#4 .Use\n")
    ctx.Next()
}

func errorMiddleware(ctx iris.Context) {
    ctx.WriteString("#3 .UseError\n")
    ctx.Next()
}

這裡的middleware都只是寫log而已,然後執行這個程式會有甚麼結果呢,請大家看看以下的內容

#1 .WrapRouter
#2 .UseRouter
#3 .UseGlobal
#4 .Use
Main Handler

由上面例子可以知道,註冊middleware的執行順序是如上面顯示的順序WrapRouter最先被執行、Use最後,接下來才執行綁定的handler,除此之外如果觸發404會有下列的輸出

#3 .UseGlobal
#3 .UseError
404 Error Handler

不過這裡有一個部分需要注意的,就是如果OnErrorCode沒有綁定到的狀態被觸發,UseGlobal也不會被觸發,例如觸發的不是404而是400則會輸出的log如下

#3 .UseError
Not Found

修改middleware

最後跟大家說明如何修改原來的middleware,簡單說就是像接水管一樣,再接原來的middleware之前先接另外一個middleware去判斷後即可,大家可以看看下列例子

package main

import (
    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/middleware/basicauth"
)

func main() {
    users := map[string]string{"username":"password"}
    auth := basicauth.New(basicauth.Default(users))

    app.UseRouter(skipStaticSubdomain(auth)) // <--

    // [...]
    app.Listen(":80")
}

func skipStaticSubdomain(handler iris.Handler) iris.Handler {
    return func(ctx iris.Context) {
        if ctx.Subdomain() == "static." {
            // continue to the next or main handler and exit.
            ctx.Next()
            return
        }

        handler(ctx)   
    }
}

除了上述的例子之外,另外可以把conditionhandler分開來寫,透過iris.NewConditionalHandler(filter Filter, handlers ...Handler) Handler來改寫middleware,詳見下列例子

app.UseRouter(iris.NewConditionalHandler(isNotStaticSubdomain, auth))

func isNotStaticSubdomain(ctx iris.Context) bool {
    return ctx.Subdomain() != "static."
}

結論

本篇介紹什麼是middleware,並且介紹如何在iris撰寫middleware,希望大家對於iris的middleware有初步的概念。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *