围绕Handler接口的方法ServeHTTP,可以轻松的写出go中的中间件

suoniao 2021-01-20
需要:0索币

 Golang标准库http包提供了基础的http服务,这个服务又基于Handler接口和ServeMux结构的叫做Mutilpexer。实际上,go的作者设计Handler这样的接口,不仅提供了默认的ServeMux对象,开发者也可以自定义ServeMux对象。

    本质上ServeMux只是一个路由管理器,而它本身也实现了Handler接口的ServeHTTP方法。因此围绕Handler接口的方法ServeHTTP,可以轻松的写出go中的中间件。

自定义的Handler

    标准库http提供了Handler接口,用于开发者实现自己的handler。只要实现接口的ServeHTTP方法即可。

  1. type textHandler struct {
  2. responseText string
  3. }
  4. func (th *textHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
  5. fmt.Fprintf(w, th.responseText)
  6. }
  7. type indexHandler struct {}
  8. func (ih *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  9. w.Header().Set("Content-Type", "text/html")
  10. html := `<doctype html>
  11. <html>
  12. <head>
  13. <title>Hello World</title>
  14. </head>
  15. <body>
  16. <p>
  17. <a href="/welcome">Welcome</a> | <a href="/message">Message</a>
  18. </p>
  19. </body>
  20. </html>`
  21. fmt.Fprintln(w, html)
  22. }
  23. func main() {
  24. mux := http.NewServeMux()
  25. mux.Handle("/", &indexHandler{})
  26. thWelcome := &textHandler{"TextHandler !"}
  27. mux.Handle("/text",thWelcome)
  28. http.ListenAndServe(":8000", mux)
  29. }

   上面自定义了两个handler结构,都实现了ServeHTTP方法。我们知道,NewServeMux可以创建一个ServeMux实例,ServeMux同时也实现了ServeHTTP方法,因此代码中的mux也是一种handler。把它当作参数传给http.ListenAndServe方法,后者会把mux传给Server实例。因为指定了handler,因此整个http服务就不再是DefaultServeMux,而是mux,无论是在注册路由还是提供请求服务的时候。

 

   有一点值得注意,这里并没有使用HandleFunc注册路由,而是直接使用了mux注册路由。当没有指定mux的时候,系统需要创建一个默认的defaultServeMux,此时我们已经有了mux,因此不需要http.HandleFunc方法了,直接使用mux的Handle方法注册即可。

   此外,Handle第二个参数是一个Handler(处理器),并不是HandleFunc的一个handler函数,其原因是mux.Handle本质上就需要绑定url的pattern模式和handler(处理器)即可。既然indexHandler是handle(处理器),当然就能作为参数,一切请求的处理过程,都交给实现的接口方法ServeHTTP就行了。
  

创建Handler处理器

  1. func text(w http.ResponseWriter, r *http.Request){
  2. fmt.Fprintln(w, "hello world")
  3. }
  4. func index(w http.ResponseWriter, r *http.Request) {
  5. w.Header().Set("Content-Type", "text/html")
  6. html := `<doctype html>
  7. <html>
  8. <head>
  9. <title>Hello World</title>
  10. </head>
  11. <body>
  12. <p>
  13. <a href="/welcome">Welcome</a> | <a href="/message">Message</a>
  14. </p>
  15. </body>
  16. </html>`
  17. fmt.Fprintln(w, html)
  18. }
  19. func main() {
  20. mux := http.NewServeMux()
  21. mux.Handle("/", http.HandlerFunc(index))
  22. mux.HandleFunc("/text", text)
  23. http.ListenAndServe(":8000", mux)
  24. }

   代码中使用了http.HandlerFunc方法直接将一个handler函数转变成实现了handler(处理器)。
 

 

使用默认的DefaultServeMux

  1. func main() {
  2. http.Handle("/", http.HandlerFunc(index))
  3. http.HandleFunc("/text", text)
  4. http.ListenAndServe(":8000", nil)
  5. }

   当代码中不显示的创建serveMux对象,http包就默认创建一个DefaultServeMux对象用来做路由管理器mutilplexer。
 

 

自定义Server

   默认的DefaultServeMux创建的判断来自server对象,如果server对象不提供handler,才会使用默认的serveMux对象。既然ServeMux可以自定义,那么Server对象一样可以。

   使用http.Server即可创建自定义的server对象:

  1. func main(){
  2. http.HandleFunc("/", index)
  3. server := &http.Server{
  4. Addr: ":8000",
  5. ReadTimeout: 60 * time.Second,
  6. WriteTimeout: 60 * time.Second,
  7. }
  8. server.ListenAndServe()
  9. }

   自定义的serverMux对象也可以传到server对象中。

  1. func main() {
  2. mux := http.NewServeMux()
  3. mux.HandleFunc("/", index)
  4. server := &http.Server{
  5. Addr: ":8000",
  6. ReadTimeout: 60 * time.Second,
  7. WriteTimeout: 60 * time.Second,
  8. Handler: mux,
  9. }
  10. server.ListenAndServe()
  11. }

   可见go中的路由和处理函数之间关系非常密切,同时又很灵活。

 

中间件Middleware

   中间件就是连接上下级不同功能的函数或软件,通常进行一些包裹函数的行为,为被包裹函数提供一些功能或行为。

   go的http中间件很简单,只要实现一个函数签名为func(http.Handler) http.Handler的函数即可。http.Handler是一个接口,接口方法我们熟悉的为serveHTTP。返回也是一个handler。因为go中的函数也可以当作变量传递或者返回,只要这个函数是一个handler即可,即实现或被handlerFunc包裹成handler处理器。

  1. func middlewareHandler(next http.Handler) http.Handler{
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
  3. // 执行handler之前的逻辑
  4. next.ServeHTTP(w, r)
  5. // 执行完毕handler后的逻辑
  6. })
  7. }


   go的实现实例:

  1. func loggingHandler(next http.Handler) http.Handler {
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3. start := time.Now()
  4. log.Printf("Started %s %s", r.Method, r.URL.Path)
  5. next.ServeHTTP(w, r)
  6. log.Printf("Comleted %s in %v", r.URL.Path, time.Since(start))
  7. })
  8. }
  9. func main() {
  10. http.Handle("/", loggingHandler(http.HandlerFunc(index)))
  11. http.ListenAndServe(":8000", nil)
  12. }

   既然中间件是一种函数,并且签名都是一样,那么很容易就联想到函数一层包一层的中间件。再添加一个函数,然后修改main函数:

  1. func hook(next http.Handler) http.Handler{
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3. log.Println("before hook")
  4. next.ServeHTTP(w, r)
  5. log.Println("after hook")
  6. })
  7. }
  8. func main() {
  9. http.Handle("/", hook(loggingHandler(http.HandlerFunc(index))))
  10. http.ListenAndServe(":8000", nil)
  11. }

   函数调用形成一条链,可以在这条链上做很多事情。
 

参考:

https://www.yuque.com/docs/share/c03cf792-227a-4e7a-850b-4789e479361d

回帖
  • 消灭零回复
相关主题
golang实现内存池 go语言字节池byte pool实现代码 0
golang网络编程之基于TCP协议实现长连接 golang心跳检测 0
golang类型断言type的使用 0
golang依赖包管理 mod使用教程 0
golang表单验证库validator 0
如何使用Go语言实现一个简单的异步任务框架呢?生产者消费者模型 0
golang 网络编程设置keepAlive空闲多长时间开始探测、 探测总次数 0
golang网络编程之TCP编程详解 0
go语言利用ioutil.ReadAll读取TCP socket所有数据 0
golang利用io.copy和bytes.Buffer读取TCP socket所有数据 0
golang读取所有socket数据的方式一 0
golang实现http中间件 gin框架中间件实现原理分析 0
围绕Handler接口的方法ServeHTTP,可以轻松的写出go中的中间件 0
golang语言错误处理方式check函数,把错误转化为panic 0
golang cannot find module providing package 0
golang语言错误处理errors包使用详解 0
golang基于通道channel实现一个通用连接池 pool 0
golang数据类型之map结构详解 0
golang socket关闭读导致 wsarecv: An existing connection was forcibly closed by the remote host 0
windows配置golang的环境变量 gopath配置学习go语言第一天 0
相关主题
QListWidget滚动的时候显示不完整 滚动条模式导致的哦 0
Qt设置顶层面板背景透明Qt::WA_TranslucentBackground 隐藏边框Qt::FramelessWindowHint 0
QMenu和QMenuBar样式表大全 qss 0
QT定时器startTimer和timerEvent事件 每隔interval 毫秒就会启动一次 0
C语言内存分配函数malloc和calloc的区别 0
QCheckBox的QSS样式表总结 0
Qt通过qRegisterMetaType注册自定义数据类型 0
QListwidget触发2次itemClicked事件 0
打印机USB驱动开发之实现打印服务器 0
Qt利用QLabel组件来显示图片 0
TableView自定义代理QStyledItemDelegate实现ComboBox 0
Qt利用QGraphicsView类实现图片放大缩小平移显示 0
Qt实现非阻塞延迟方法sleep 0
海康相机SDK的C++对应的接口 0
Qt实现webdav客户端功能支持https协议的webdav客户端 0
CHKDSK解决 移动硬盘只能看见盘符其它信息都看不见另外双击也打不开 0
gogs一直报errror:dial tcp xxx.xxx.xxx.xxx 宿主机的ip 0
索鸟快传2.1.2发布 0
索鸟快传2.1.1发布 0
Qt操作windows注册表的方法 bat从注册表中将键值删除 0