美文网首页程序员GoGolang
golang 网络编程(5)中间件

golang 网络编程(5)中间件

作者: zidea | 来源:发表于2019-04-22 06:51 被阅读22次
    golang_real.jpg

    当构建 web 应用程序,可能对所有的请求会共享一些功能。例如我们每一个请求都会写入日志。

    • 打印 http 请求或返回的日志
    • 压缩 http 请求的返回值
    • 将 http 请求头保持一致
    • 实现安全以及权限验证


      Middleware-web.jpg

    在 go 语言 net/http 标准库中提供了中间件类似的函数 StripPrefix 和 TimeoutHandler。
    如何自己编写一个中间件呢?答案是

    • 这个函数接收一个 http.Handler 作为参数,目的是可使用其他中间件或应用处理器,调用其他中间件或应用的方法就是调用其 HttpServe 的方法。
    • 并且将 http.Handler 作为返回值,这样其他中间件可以使用 http.Handler 作为输入的参数,达到链式调用。
    func middlewareHandler(next http.Handler) http.Handler {
     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     // 在执行应用处理器前编译中间件的逻辑
     next.ServeHTTP(w, r)
     // 在执行应用处理器后将会执行的编译中间件的逻辑
     })
    }
    

    从上面示例我们可以看出 go 语言是支持高阶函数,通过向下传递 http.Handler 来实现中间件。

    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("Executing index handler")
        fmt.Fprintf(w,"welcome!")
    }
    
    func about(w http.ResponseWriter, r *http.Request){
        log.Println("Executing about handler")
        fmt.Fprintf(w,"Go Middleware")
    }
    
    func iconHandler(w http.ResponseWriter, r *http.Request){
    
    }
    
    func main()  {
    
        http.HandleFunc("/favicon.ico", iconHandler)
    
        indexHandler := http.HandlerFunc(index)
        aboutHandler := http.HandlerFunc(about)
    
        http.Handle("/",logginHandler(indexHandler))
        http.Handle("/about",logginHandler(aboutHandler))
    
        server :=  &http.Server{
            Addr:":8080",
        }
    
        log.Println("Listening...")
        server.ListenAndServe()
    }
    
    • 上面的代码关键是 logginHandler 这个日志处理器,作为中间件会拦截请求,进行处理也就是输出日志,通过next.ServeHTTP(w,r)然后将请求向下进行传递
    • 这里创建两个简单路由处理器indexabout。都是向客户端输出不同的文本。
    • http.Handle("/",logginHandler(indexHandler)) 通过 logginHandler 函数进行包裹,从容我们日志中间件可以拦截到发起的请求进行一些业务逻辑,业务逻辑可以位于next.ServeHTTP(w,r)之前或之后。通过下面的输出我们可以清晰可见代码执行的顺序。
    Listening...
     Started GET /
     Executing index handler
     Completed / in 41.129µs
     Started GET /
     Executing index handler
     Completed / in 50.475µs
     Started GET /about
     Executing about handler
     Completed /about in 49.483µs
    

    许多书都提供代码示例,个人建议是不要 copy 过来运行看效果,自己手 coding 就可以发现一些问题,同时可以思考为什么要这样编写代码。

    通过上面示例我们了解如何写一个简单中间件,这个个人看来和 nodejs 实现中间件没有什么差别。实际开发中我们往往会有多个中间件来执行业务,那么这些中间件执行顺序也是我们值得考虑的问题。

    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("MiddlewareFirst - after Handler")
        })
    }
    
    func middlewareSecond(next http.Handler) http.Handler{
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
            log.Println("MiddlewareSecond - before Handler")
            if r.URL.Path == "/message"{
                if r.URL.Query().Get("password") == "123"{
                    log.Println("Authorized to system...")
                    next.ServeHTTP(w,r)
                }else{
                    log.Println("Failed to authorize to the system")
                    return
                }
            }else{
                next.ServeHTTP(w,r)
            }
            log.Println("MiddlewareScond - after Handler")      
        })
    }
    
    func index(w http.ResponseWriter, r *http.Request){
        log.Println("Executed index Handler")
        fmt.Fprintf(w,"welcome")
    }
    
    func message(w http.ResponseWriter, r *http.Request){
        log.Println("Executed message Handler")
        fmt.Fprintf(w,"message...")
    }
    
    func iconHandler(w http.ResponseWriter, r *http.Request){
    
    }
    
    func main() {
        http.HandleFunc("/favicon",iconHandler)
        indexHandler := http.HandlerFunc(index)
        messageHandler := http.HandlerFunc(message)
    
        http.Handle("/",middlewareFirst(middlewareSecond(indexHandler)))
        http.Handle("/message",middlewareFirst(middlewareSecond(messageHandler)))
    
        server  := &http.Server{
            Addr:":8080",
        }
    
        log.Println("Listen...")
    
        server.ListenAndServe()
    
    }
    

    在上面代码中其实也没有什么特别之处,就是我们创建了两个middlewareFirstmiddlewareSecond两个中间件并且有一定先后顺序,然后在 middlewareSecond中写了一个对访问权限校验的逻辑。大家可以尝试地去运行一下。

    th-7.jpeg

    相关文章

      网友评论

        本文标题:golang 网络编程(5)中间件

        本文链接:https://www.haomeiwen.com/subject/vjnjgqtx.html