写点什么

如何编写 Go 中间件

2019 年 11 月 26 日

如何编写Go中间件

大家有没有写过中间件呢,它是怎么写的呢?今天给大家分享一下使用 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)。


原文链接:


https://mp.weixin.qq.com/s/2w5Equc7xw0s_oVxz7PVmQ


2019 年 11 月 26 日 16:23335

评论

发布
暂无评论
发现更多内容

Linux之which命令

入门小站

Linux

【建党纪念日】TcaplusDB为祖国喝彩,向党员致敬!

tcaplus

数据库 TcaplusDB

杀进腾讯你知道多难吗?6个小时,4 轮技术面+1 轮 HR面,面到吐才斩获40k*16 薪offer!

Java架构师迁哥

Java线程映射到操作系统线程原理浅析

CodeWithBuff

Java JVM

整整奋战6个月,入职字节跳动那一天,我泪目了

Java架构师迁哥

为什么AI需要大大大大大模型?

白洞计划

08 | 指针系列(二):记住,指针变量也是变量(2)

Nydia

【Flutter 专题】97 仿网易新闻标签选择器

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 7月日更

啥,Vue又出新单曲?

码生笔谈

Vue 前端

先到先得先学!Spring Cloud Alibaba第三版耀世升级全彩PDF版

Java架构追梦

Java 阿里巴巴 架构 面试 SpringCloud

【架构设计模块一】:微信业务架构图 & 学生管理系统

Ryoma

架构实战营

为什么AI需要大大大大大模型?

脑极体

一文看懂 Context

Rayjun

go

【LeetCode每日一题 Day 4】4. 寻找两个正序数组的中位数

编程熊

算法 LeetCode 面经 程序员、 笔试

Ubuntu 如何强制时间同步

HoneyMoose

external-attacher源码分析(1)-main方法与启动参数分析

良凯尔

Kubernetes 源码分析 Ceph CSI Kubernetes Plugin

Reactor响应式编程,你只差这个!

CodeWithBuff

Java reactor

WebFlux 操作 MySQL 是种什么体验?

Java 白

排序——冒泡排序

若尘

排序 冒泡排序 冒泡

二分查找常见套路与分析

gevin

算法 二分查找

架构实战营模块七作业

竹林七贤

架构实战营模块一作业

Clarke

架构实战营

🐬【MySQL技术导航】「索引技术」体验前所未有的技术探险,看穿索引的本质和技术体系(上篇)

李浩宇/Alex

MySQL 索引结构 7月日更 索引原理

在线HTML转JS/JSON工具

入门小站

工具

七月新开始

IT蜗壳-Tango

7月日更

分层架构:一个经典却得不到优的难题

码猿外

架构 微服务

比收费软件还良心的软件,这回藏不住了 | OBS

彭宏豪95

效率 软件 App 录屏软件

TcaplusDB君 | 行业新闻汇编(7月1日)

tcaplus

数据库 游戏 TcaplusDB

架构实战营模块7作业

Vic

架构实战营

网络攻防学习笔记 Day63

穿过生命散发芬芳

网络攻防 7月日更

如何预防一个服务故障崩掉整个系统?【熔断】

我爱娃哈哈😍

架构设计 熔断 微服务治理

低代码的认知误区与落地实践

低代码的认知误区与落地实践

如何编写Go中间件-InfoQ