十分钟学会用Go编写Web中间件

本文首发于公众号,关注文末公众号回复gohttp03 获取文章所用完整源代码。shell

中间件(一般)是一小段代码,它们接受一个请求,对其进行处理,每一个中间件只处理一件事情,完成后将其传递给另外一个中间件或最终处理程序,这样就作到了程序的解耦。若是没有中间件那么咱们必须在最终的处理程序中来完成这些处理操做,这无疑会形成处理程序的臃肿和代码复用率不高的问题。中间件的一些常见用例是请求日志记录,Header操纵、HTTP请求认证和ResponseWriter劫持等等。编程

画外音:上面这段描述中间件的文字,跟我两年前在Laravel源码解析之中间件写的几乎同样(其实这图也是从那里拿过来的)。再次说明作开发时间长了之后掌握一些编程的思想有时候比掌握一门编程语言更重要,这不我们就又用Go来写中间件了。浏览器

建立中间件

接下来咱们用Go建立中间件,中间件只将http.HandlerFunc做为其参数,在中间件里将其包装并返回新的http.HandlerFunc供服务器服务复用器调用。这里咱们建立一个新的类型Middleware,这会让最后一块儿链式调用多个中间件变的更简单。bash

type Middleware func(http.HandlerFunc) http.HandlerFunc 复制代码

下面的中间件通用代码模板让咱们平时编写中间件变得更容易。服务器

中间件代码模板

中间件是使用装饰器模式实现的,下面的中间件通用代码模板让咱们平时编写中间件变得更容易,咱们在本身写中间件的时候只须要往样板里填充须要的代码逻辑便可。app

func createNewMiddleware() Middleware {
    // 建立一个新的中间件
    middleware := func(next http.HandlerFunc) http.HandlerFunc {
        // 建立一个新的handler包裹next
        handler := func(w http.ResponseWriter, r *http.Request) {

            // 中间件的处理逻辑
						......
            // 调用下一个中间件或者最终的handler处理程序
            next(w, r)
        }

        // 返回新建的包装handler
        return handler
    }

    // 返回新建的中间件
    return middleware
}
复制代码

使用中间件

咱们建立两个中间件,一个用于记录程序执行的时长,另一个用于验证请求用的是不是指定的HTTP Method,建立完后再用定义的Chain函数把http.HandlerFunc和应用在其上的中间件链起来,中间件会按添加顺序依次执行,最后执行处处理函数。完整的代码以下:编程语言

package main

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

type Middleware func(http.HandlerFunc) http.HandlerFunc // 记录每一个URL请求的执行时长 func Logging() Middleware {

    // 建立中间件
    return func(f http.HandlerFunc) http.HandlerFunc {

        // 建立一个新的handler包装http.HandlerFunc
        return func(w http.ResponseWriter, r *http.Request) {

            // 中间件的处理逻辑
            start := time.Now()
            defer func() { log.Println(r.URL.Path, time.Since(start)) }()

            // 调用下一个中间件或者最终的handler处理程序
            f(w, r)
        }
    }
}

// 验证请求用的是不是指定的HTTP Method,不是则返回 400 Bad Request
func Method(m string) Middleware {

    return func(f http.HandlerFunc) http.HandlerFunc {

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

            if r.Method != m {
                http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
                return
            }

            f(w, r)
        }
    }
}

// 把应用到http.HandlerFunc处理器的中间件
// 按照前后顺序和处理器自己链起来供http.HandleFunc调用
func Chain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
    for _, m := range middlewares {
        f = m(f)
    }
    return f
}

// 最终的处理请求的http.HandlerFunc 
func Hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello world")
}

func main() {
    http.HandleFunc("/", Chain(Hello, Method("GET"), Logging()))
    http.ListenAndServe(":8080", nil)
}
复制代码

运行程序后会打开浏览器访问http://localhost:8080会有以下输出:函数

2020/02/07 21:07:52 / 359.503µs
2020/02/07 21:09:17 / 34.727µs
复制代码

到这里怎么用Go编写和使用中间件就讲完,也就十分钟吧。不过这里更多的是探究实现原理,那么在生产环境怎么本身使用编写的这些中间件呢,咱们接着往下看。学习

使用gorilla/mux应用中间件

上面咱们探讨了如何建立中间件,可是使用上每次用Chain函数连接多个中间件和处理程序仍是有些不方便,并且在上一篇文章中咱们已经开始使用gorilla/mux提供的Router做为路由器了。好在gorrila.mux支持向路由器添加中间件,若是发现匹配项,则按照添加中间件的顺序执行中间件,包括其子路由器也支持添加中间件。ui

gorrila.mux路由器使用Use方法为路由器添加中间件,Use方法的定义以下:

func (r *Router) Use(mwf ...MiddlewareFunc) {
	for _, fn := range mwf {
		r.middlewares = append(r.middlewares, fn)
	}
}
复制代码

它能够接受多个mux.MiddlewareFunc类型的参数,mux.MiddlewareFunc的类型声明为:

type MiddlewareFunc func(http.Handler) http.Handler
复制代码

跟咱们上面定义的Middleware类型很像也是一个函数类型,不过函数的参数和返回值都是http.Handler接口,在《深刻学习用 Go 编写 HTTP 服务器》中咱们详细讲过http.Handler它 是net/http中定义的接口用来表示处理 HTTP 请求的对象,其对象必须实现ServeHTTP方法。咱们把上面说的中间件模板稍微更改下就能建立符合gorrila.mux要求的中间件:

func CreateMuxMiddleware() mux.MiddlewareFunc {

	// 建立中间件
	return func(f http.Handler) http.Handler {

		// 建立一个新的handler包装http.HandlerFunc
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

			// 中间件的处理逻辑
			......

			// 调用下一个中间件或者最终的handler处理程序
			f.ServeHTTP(w, r)
		})
	}
}
复制代码

接下来,咱们把上面自定义的两个中间件进行改造,而后应用到咱们一直在使用的http_demo项目上,为了便于管理在项目中新建middleware目录,两个中间件分别放在log.gohttp_method.go

//middleware/log.go
func Logging() mux.MiddlewareFunc {

	// 建立中间件
	return func(f http.Handler) http.Handler {

		// 建立一个新的handler包装http.HandlerFunc
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

			// 中间件的处理逻辑
			start := time.Now()
			defer func() { log.Println(r.URL.Path, time.Since(start)) }()

			// 调用下一个中间件或者最终的handler处理程序
			f.ServeHTTP(w, r)
		})
	}
}

// middleware/http_demo.go
func Method(m string) mux.MiddlewareFunc {

	return func(f http.Handler) http.Handler {

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

			if r.Method != m {
				http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
				return
			}

			f.ServeHTTP(w, r)
		})
	}
}
复制代码

而后在咱们的路由器中进行引用:

func RegisterRoutes(r *mux.Router) {
	r.Use(middleware.Logging())// 全局应用
	indexRouter := r.PathPrefix("/index").Subrouter()
	indexRouter.Handle("/", &handler.HelloHandler{})

	userRouter := r.PathPrefix("/user").Subrouter()
	userRouter.HandleFunc("/names/{name}/countries/{country}", handler.ShowVisitorInfo)
	userRouter.Use(middleware.Method("GET"))//给子路由器应用
}
复制代码

再次编译启动运行程序后访问

http://localhost:8080/user/names/James/countries/NewZealand
复制代码

从控制台里能够看到,记录了这个请求的处理时长:

2020/02/08 09:29:50 Starting HTTP server...
2020/02/08 09:55:20 /user/names/James/countries/NewZealan 51.157µs

复制代码

到这里咱们探究完了编写Web中间件的过程和原理,在实际开发中只须要根据本身的需求按照咱们给的中间件代码模板编写中间件便可,在编写中间件的时候也要注意他们的职责范围,不要全部逻辑都往里放。

前文回顾:

深刻学习用 Go 编写 HTTP 服务器

使用gorilla/mux加强Go HTTP服务器的路由能力

在公众号里关键字回复gohttp03能够拿到本篇文章中完整的源代码,喜欢个人文章帮忙转发点赞。

tWbHIMFsM3.png
相关文章
相关标签/搜索