大家有没有写过中间件呢,它是怎么写的呢?今天给大家分享一下使用 Go,如何编写中间件,供大家参考学习。
一开始,我们认为编写中间件似乎很容易,但是我们实际编写的时候也会遇到各种各样的坑。让我们来看看一些例子。
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 去编写一个中间件。如果有什么问题或是建议,可以通过公众号下方的留言与我们沟通。
本文转载自公众号 360 云计算(ID:hulktalk)。
原文链接:
评论