如何编写Go语言中间件的实例教程

 更新时间:2018年04月05日 08:45:11   作者:HULK一线技术杂谈  
不知道大家有没有写过中间件呢,它是怎么写的呢?下面这篇文中就来给大家分享一下使用Go,如何编写中间件,文中通过示例代码介绍的非常详细,供大家参考学习,下面随着小编来一起学习学习吧。

引言

web开发的背景下,“中间件”通常意思是“包装原始应用并添加一些额外的功能的应用的一部分”。这个概念似乎总是不被人理解,但是我认为中间件非常棒。

首先,一个好的中间件有一个责任就是可插拔并且自足。这就意味着你可以在接口级别嵌入你的中间件他就能直接运行。它不会影响你编码方式,不是框架,仅仅是你请求处理里面的一层而已。完全没必要重写你的代码,如果你想使用中间件的一个功能,你就帮他插入到那里,如果不想使用了,就可以直接移除。

纵观Go语言,中间件是非常普遍的,即使在标准库中。虽然开始的时候不会那么明显,在标准库net/http中的函数StripText或者TimeoutHandler就是我们要定义和的中间件的样子,处理请求和相应的时候他们包装你的handler,并处理一些额外的步骤。

一开始,我们认为编写中间件似乎很容易,但是我们实际编写的时候也会遇到各种各样的坑。让我们来看看一些例子。

1、读取请求

在我们的示例中,所有的中间件都将接受http。处理程序作为一个参数,并返回一个http.Handler。这使得人们很容易就能把中间产品串起来。我们所有的中间产品的基本模式是这样的:

func X(h http.Handler) http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 // Something here...
 h.ServeHTTP(w, r)
 })
}

我们想要将所有的请求重定向到一个斜杠——比方说/message/,到它们的非斜杠等效,比如/message。我们可以这样写:

func TrailingSlashRedirect(h http.Handler) http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 if r.URL.Path != "/" && r.URL.Path[len(r.URL.Path)-1] == '/' {
 http.Redirect(w, r, r.URL.Path[:len(r.URL.Path)-1], http.StatusMovedPermanently)
 return
 }
 h.ServeHTTP(w, r)
 })
}

有没有很简单。

2、修改请求

比方说,我们想要向请求添加一个标题,或者修改它。http.Handler文档中指明:

除了读取主体之外,处理程序不应该修改所提供的请求。

Go标准库复制http.Request。请求对象在将其传递到响应链之前,我们也应该这样做。假设我们想在每个请求上设置Request-Id头,以便内部跟踪。创建*Request的浅副本,并在代理之前修改标题。

func RequestID(h http.Handler) http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 r2 := new(http.Request)
 *r2 = *r
 r2.Header.Set("X-Request-Id", uuid.NewV4().String())
 h.ServeHTTP(w, r2)
 })
}

3、编写响应头

如果你想设置响应头,你可以只写它们,然后代理请求。

func Server(h http.Handler, servername string) http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 w.Header().Set("Server", servername)
 h.ServeHTTP(w, r)
 })
}

上面的问题是,如果内部处理器也设置了服务器头,那么你的头将被覆盖。如果不想公开内部软件的服务器头,或者如果想在发送响应给客户端之前去掉头部,这可能会带来问题。

要做到这一点,我们必须自己实现ResponseWriter接口。大多数时候,我们只会代理到底层的ResponseWriter,但是如果用户试图写一个响应,我们就会潜入并添加我们的标题。

type serverWriter struct {
 w http.ResponseWriter
 name string
 wroteHeaders bool
}

func (s *serverWriter) Header() http.Header {
 return s.w.Header()
}

func (s *serverWriter) WriteHeader(code int) http.Header {
 if s.wroteHeader == false {
 s.w.Header().Set("Server", s.name)
 s.wroteHeader = true
 }
 s.w.WriteHeader(code)
}

func (s *serverWriter) Write(b []byte) (int, error) {
 if s.wroteHeader == false {
 // We hit this case if user never calls WriteHeader (default 200)
 s.w.Header().Set("Server", s.name)
 s.wroteHeader = true
 } return s.w.Write(b)
}

要在我们的中间件中使用它,我们会写:

func Server(h http.Handler, servername string) http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 sw := &serverWriter{
 w: w,
 name: servername,
 }
 h.ServeHTTP(sw, r)
 })
}

问题

如果用户从不调用Write或WriteHeader呢?例如,有一个200状态并且是空body,或者对选项请求的响应——我们的拦截函数都不会运行。因此,我们应该在ServeHTTP调用之后再添加校验。

func Server(h http.Handler, servername string) http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 sw := &serverWriter{
 w: w,
 name: servername,
 }
 h.ServeHTTP(sw, r)
 if sw.wroteHeaders == false {
 s.w.Header().Set("Server", s.name)
 s.wroteHeader = true
 }
 })
}

其他ResponseWriter接口

ResponseWriter接口只需要有三种方法。但在实践中,它也可以对其他接口作出响应,例如http.Pusher。你的中间件可能会意外地禁用HTTP/2支持,这是不好的。

// Push implements the http.Pusher interface.
func (s *serverWriter) Push(target string, opts *http.PushOptions) error {
 if pusher, ok := s.w.(http.Pusher); ok {
 return pusher.Push(target, opts)
 }
 return http.ErrNotSupported
}

// Flush implements the http.Flusher interface.
func (s *serverWriter) Flush() {
 f, ok := s.w.(http.Flusher)
 if ok {
 f.Flush()
 }
}

总结

通过以上的学习,不知道大家对Go编写中间件有没有一个完整的认识。大家也可以尝试着用Go去编写一个中间件。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

相关文章

  • Go语言中定时任务库Cron使用方法介绍

    Go语言中定时任务库Cron使用方法介绍

    cron的意思计划任务,说白了就是定时任务。我和系统约个时间,你在几点几分几秒或者每隔几分钟跑一个任务(job),今天通过本文给大家介绍下Go语言中定时任务库Cron使用方法,感兴趣的朋友一起看看吧
    2022-03-03
  • Golang使用panic控制程序错误流程

    Golang使用panic控制程序错误流程

    这篇文章主要介绍了Golang使用panic控制程序错误流程,Golang panic异常处理机制中的一种流程控制方式,用于中断程序流程并触发异常处理
    2023-04-04
  • gin自定义中间件解决requestBody不可重复读问题(最新推荐)

    gin自定义中间件解决requestBody不可重复读问题(最新推荐)

    这篇文章主要介绍了gin自定义中间件解决requestBody不可重复读问题,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-04-04
  • 详解Golang中gcache模块的基本使用

    详解Golang中gcache模块的基本使用

    这篇文章主要通过结合商业项目的使用场景,为大家介绍了gcache的基本使用、缓存控制以及淘汰策略。使用gcache做缓存处理,简单方便易上手
    2022-11-11
  • Golang泛型的使用方法详解

    Golang泛型的使用方法详解

    这篇文章主要介绍了Golang中泛型的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • Golang安装和使用protocol-buffer流程介绍

    Golang安装和使用protocol-buffer流程介绍

    这篇文章主要介绍了Golang安装和使用protocol-buffer过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-09-09
  • Go语言中错误处理的方式总结

    Go语言中错误处理的方式总结

    这篇文章会结合 errors 中的函数,来讨论一下 Go 中常见的 error 使用方式,文中的示例代码讲解详细,具有一定的学习价值,需要的可以了解一下
    2023-07-07
  • Golang使用协程实现批量获取数据

    Golang使用协程实现批量获取数据

    服务端经常需要返回一个列表,里面包含很多用户数据,常规做法当然是遍历然后读缓存。使用Go语言后,可以并发获取,极大提升效率,本文就来聊聊具体的实现方法,希望对大家有所帮助
    2023-02-02
  • 浅析Golang中float64的精度问题

    浅析Golang中float64的精度问题

    这篇文章主要来和大家一起探讨一下Golang中关于float64的精度问题,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以了解下
    2023-08-08
  • Go语言服务器开发实现最简单HTTP的GET与POST接口

    Go语言服务器开发实现最简单HTTP的GET与POST接口

    这篇文章主要介绍了Go语言服务器开发实现最简单HTTP的GET与POST接口,实例分析了Go语言http包的使用技巧,需要的朋友可以参考下
    2015-02-02

最新评论