Go HTTP Middleware学习1

HTTP 中间件

HTTP中间件,在现实项目开发中简化了很多工作,Go开发社区还没有很大的兴趣吸收成熟的web开发框架.而是更加愿意使用标准库的基础模块,再搭配一下第三方库比如Gorilla mux. 使用中间件可以实现很多共同的操作,比如安全验证,HTTP请求,登录响应,压缩响应头,缓存等,而且这些中间件可以应用于很多应用或者跨应用的handlers.

当开发web应用,可能需要共享一些或者全部的请求的handler操作.比如:想打印全部的请求,每个请求都进行登录逻辑的验证.通常情况下可能用到很特别的方式去解决这样的需求.使用HTTP middleware,可以很好的实现这样的功共享能.

middleware是一段可插入式,独立的代码片段.可以用于在HTTP请求的handlers的共享操作.middleware组件可以插入程序和请求handler的其他层共同协作,在HTTP handler前或者后都可以.比如,为了打印全部的网络请求,可以写一个打印的中间件,可以插入到每个handler中.并不会影响原来的功能模块,因为这是一段独立的代码片段.可以随时添加或者移除.

一些常用中间件的例子

  • 打印HTTP请求和相应
  • 压缩HTTP响应
  • 写共同的响应头
  • 创建数据库的session
  • 授权验证和安全验证

写一个简单的HTTP中间件

Go的net/http包 提供了StripPrefix和TimeoutHandler,这样的函数,和中间件非常的类似:包裹了请求的handlers和提供了额外的实现.StripPrefix和TimeoutHandler都需要一个http.Handler作为参数,和其他的参数一起,最后返回一个http.Handler,这样很容易的将一些平常的操作和逻辑包裹进来.StripPrefix需要一个string参数和一个http.Handler.返回一个hander,过滤掉URL中的没有string参数的请求.

// middleware
package main

import (
    "net/http"
)

func main() {
    fs := http.FileServer(http.Dir("public"))
    http.Handle("/public/", http.StripPrefix("/public/", fs))
    http.ListenAndServe(":9090", nil)
}

/*如果我们在浏览器输入:localhost:9090是无法访问的,输入:localhost:9090/public/才能看到对应的文件夹内容*/

// middleware
package main

import (
    "net/http"
)

func main() {
    fs := http.FileServer(http.Dir("public"))
    http.ListenAndServe(":9090", fs)
}
/*如果我们在浏览器输入:localhost:9090就能直接访问*/

StripPrefix函数包裹了http.FileServer的handler,并提供了另外的操作,执行给定的handler对象.

写一个HTTP Middleware的步骤

1.写一个方法,参数其一是一个http.Handler,会在中间件内调用这个参数的ServeHTTP方法.
2.返回一个handler.

middleware的模板

func middlewareHandler(next http.Handler) http.Handler{
    returen http.HandlerFunc(func(w http.ResponseWriter,r *http.Request){
        //中间件的逻辑在这里实现,在执行传递进来的handler之前
        
        next.ServeHTTP(w,r)
        
        //在handler执行之后的中间件逻辑
    })
}

  • 实现一个登陆的中间件
// middleware
package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

func logginHandler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("Started %s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
        log.Printf("Completed %s in %v", r.URL.Path, time.Since(start))
    })
}

func index(w http.ResponseWriter, r *http.Request) {
    log.Println("Execute index handler")
    fmt.Fprintf(w, "Welcome!")
}

func about(w http.ResponseWriter, r *http.Request) {
    log.Println("Execute about handler")
    fmt.Fprintf(w, "About,Go Lang!")
}

func icoHandler(w http.ResponseWriter, r *http.Request) {

}

func main() {
    http.HandleFunc("/favicon.icon", icoHandler)
    indexHandler := http.HandlerFunc(index)
    aboutHandler := http.HandlerFunc(about)

    http.Handle("/", logginHandler(indexHandler))
    http.Handle("/about", logginHandler(aboutHandler))

    server := &http.Server{
        Addr: ":9090",
    }
    log.Println("Listening...")
    server.ListenAndServe()

}

//访问相应的路径,控制台输出类似结果
/*
2016/07/29 08:41:27 Listening...
2016/07/29 08:41:52 Started GET /
2016/07/29 08:41:52 Execute index handler
2016/07/29 08:41:52 Completed / in 33.676µs
2016/07/29 08:42:24 Started GET /about
2016/07/29 08:42:24 Execute about handler
2016/07/29 08:42:24 Completed /about in 22.953µs
*/

在这个例子,logginHandler作为了一个HTTP中间件,装置在”/”和”/about”的路由,中间件可以实现在多个handler共享操作.在logginHandler中间件,登陆信息在传递进来的handler前后都打印了登陆的方法和URL路径,还有执行的时间.执行传递进来的handler是在这一句代码:next.ServeHTTP(w,r)

  • 控制中间件的流程
    HTTP中间件需要一个http.Handler作为参数,并且返回一个http.Handler,所以很轻松的把中间件的handler链接起来,最后调用传递进来的handler.理解中间件的handler的流程非常重要,特别是把他们链接起来的时候.

例子

// middleware
package main

import (
    "fmt"
    "log"
    "net/http"
)

func middlewareFirst(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("MiddlewareFirst - Before Handler")
        next.ServeHTTP(w, r)
        log.Println("MiddlerwareFirst - After Handler")

    })
}

func middlerwareSecond(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("MiddlerwareSecnd - Before Handler")
        if r.URL.Path == "/message" {
            if r.URL.Query().Get("password") == "123456" {
                log.Println("Authorized to system")
                next.ServeHTTP(w, r)
            } else {
                log.Println("Failed to authorize to the system")
            }
        } else {
            next.ServeHTTP(w, r)
        }

        log.Println("Middleware - After Handler")
    })
}

func index(w http.ResponseWriter, r *http.Request) {
    log.Println("Execute index Handler")
    fmt.Fprintf(w, "Welcome!")

}

func message(w http.ResponseWriter, r *http.Request) {
    log.Println("Execute message Handler")
    fmt.Fprintf(w, "Message Go!")
}

func main() {
    http.Handle("/", middlewareFirst(middlerwareSecond(http.HandlerFunc(index))))
    http.Handle("/message", middlewareFirst(middlerwareSecond(http.HandlerFunc(message))))
    server := &http.Server{
        Addr: ":9090",
    }

    log.Println("Listeing...")
    server.ListenAndServe()
}

/*
访问相应的路径,控制台打印:
2016/07/29 09:14:15 Listeing...
2016/07/29 09:14:21 MiddlewareFirst - Before Handler
2016/07/29 09:14:21 MiddlerwareSecnd - Before Handler
2016/07/29 09:14:21 Failed to authorize to the system
2016/07/29 09:14:21 Middleware - After Handler
2016/07/29 09:14:21 MiddlerwareFirst - After Handler
2016/07/29 09:14:26 MiddlewareFirst - Before Handler
2016/07/29 09:14:26 MiddlerwareSecnd - Before Handler
2016/07/29 09:14:26 Execute index Handler
2016/07/29 09:14:26 Middleware - After Handler
2016/07/29 09:14:26 MiddlerwareFirst - After Handler

*/

观察打印信息可以很好的理解中间件的handler的流程,在这里MiddlewareFirst和middlewareSecond还handler内调用,可以将此应用到程序的handler.在middlewareSecond方法,会验证密码.上面的流程如下:

  1. 控制流先到:middlewareFirst函数.

  2. 在打印消息之后,调用next.ServeHTTP方法,进入到MiddlewareSecond方法.

  3. 在middlewareSecond方法执行打印信息之后,next.ServeHTTP方法被调用.
    (1)如果路径是”/”,会调用index handler,不需要验证授权.
    (2)如果路径是”/message”,就会验证参数的密码,控制流会返回到middlewareFirst方法.如果验证通过,就会执行传递进来的handler

  4. 如果执行了传递进来的handler(验证通过),就会执行next.ServeHTTP方法

  5. 最后还回到middlewareFirst

    原文作者:小Q逛逛
    原文地址: https://www.jianshu.com/p/423869b83124
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞