如何编写Go中间件

mob604756f04b77 · · 659 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

如何编写Go中间件

开发组 360云计算

女主宣言

大家有没有写过中间件呢,它是怎么写的呢?今天给大家分享一下使用Go,如何编写中间件,供大家参考学习。
PS:丰富的一线技术、多元化的表现形式,尽在“HULK一线技术杂谈”,点关注哦!

引言

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

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去编写一个中间件。如果有什么问题或是建议,可以通过公众号下方的留言与我们沟通。


有疑问加站长微信联系(非本文作者)

本文来自:51CTO博客

感谢作者:mob604756f04b77

查看原文:如何编写Go中间件

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

659 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传