如何编写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去编写一个中间件。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
上一篇: 拍摄技巧介绍:男人拍照如何摆姿势更爷们
下一篇: 如何拍出漂亮的光绘作品?