创建一个http服务

先创建一个基础的http服务,代码如下:

  1. package main
  2. import (
  3. "net/http"
  4. )
  5. func main() {
  6. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  7. w.Write([]byte("Hello World!\n"))
  8. })
  9. http.ListenAndServe(":8080", nil)
  10. }

如何创建一个中间件

中间件的定义形式为

  1. func (http.Handler) http.Hander

传入一个**http.Handler** 返回一个 **http.Hanlder**
来创建一个中间件

  1. func TestMiddleware(next http.Handler) http.Handler {
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3. log.Println("use middleware")
  4. next.ServeHTTP(w, r)
  5. })
  6. }

修改服务代码,使其支持中间件

  1. package main
  2. import (
  3. "log"
  4. "net/http"
  5. )
  6. func main() {
  7. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  8. w.Write([]byte("Hello World!\n"))
  9. })
  10. // http默认使用的http.DefaultServeMux,如果使用自定义的handler,这里改成自定义handler
  11. handler := TestMiddleware(http.DefaultServeMux)
  12. // 这里需要修改为添加了中间件之后的handler
  13. http.ListenAndServe(":8080", handler)
  14. }

访问服务,测试是否使用了中间件
image.png
请求的时候,触发了中间件日志。

如何在中间件中访问或操作Request

中间件为洋葱模型,所以在操作Request时,需要在next.ServeHTTP调用之前进行操作。

1. 如何获取请求头信息

获取请求头信息可以直接通过header来获取,修改中间件,获取请求头信息

  1. func TestMiddleware(next http.Handler) http.Handler {
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3. log.Println("use middleware")
  4. // 获取请求头信息
  5. header := r.Header.Values("Accept")
  6. log.Println("Accept:", header)
  7. next.ServeHTTP(w, r)
  8. })
  9. }

image.png

2. 如何添加或修改请求头信息

  1. package main
  2. import (
  3. "log"
  4. "net/http"
  5. )
  6. func main() {
  7. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  8. // 在代码中获取请求头
  9. log.Println("Code Get Accept:", r.Header.Values("Accept"))
  10. w.Write([]byte("Hello World!\n"))
  11. })
  12. handler := TestMiddleware(http.DefaultServeMux)
  13. http.ListenAndServe(":8080", handler)
  14. }
  15. func TestMiddleware(next http.Handler) http.Handler {
  16. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  17. log.Println("use middleware")
  18. // 在中间件中获取请求头信息
  19. header := r.Header.Values("Accept")
  20. log.Println("Middleware GET Accept:", header)
  21. // 在中间件中设置请求头
  22. r.Header.Set("Accept", "application/json")
  23. next.ServeHTTP(w, r)
  24. })
  25. }

image.png

可以看出,设置请求头之后,在code处获取的请求头信息发生了变化。

3. 如何获取请求body

示例一: 直接使用Request.Boby

  1. package main
  2. import (
  3. "bytes"
  4. "io"
  5. "log"
  6. "net/http"
  7. )
  8. func main() {
  9. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  10. defer r.Body.Close()
  11. b := new(bytes.Buffer)
  12. io.Copy(b, r.Body)
  13. log.Println("Code Get Body:", b.String())
  14. w.Write([]byte("Hello World!\n"))
  15. })
  16. handler := TestMiddleware(http.DefaultServeMux)
  17. http.ListenAndServe(":8080", handler)
  18. }
  19. func TestMiddleware(next http.Handler) http.Handler {
  20. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  21. log.Println("use middleware")
  22. b := new(bytes.Buffer)
  23. io.Copy(b, r.Body)
  24. defer r.Body.Close()
  25. log.Println("Middleware Get Body: ", b.String())
  26. next.ServeHTTP(w, r)
  27. })
  28. }

image.png
通过请求发现,在中间件中读入body之后,会导致后面的handler无法使用body中的数据,因为已经被读完了,后面继续读取的时候,就读取不到数据了,因此在使用完request.Body后需要记得给body赋值。

  1. func TestMiddleware(next http.Handler) http.Handler {
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3. log.Println("use middleware")
  4. b := new(bytes.Buffer)
  5. io.Copy(b, r.Body)
  6. defer r.Body.Close()
  7. log.Println("Middleware Get Body: ", b.String())
  8. // 将读取到的body内容重新赋值给body
  9. r.Body = io.NopCloser(b)
  10. next.ServeHTTP(w, r)
  11. })
  12. }

4. 如何修改请求body

修改body内容只需要在读取body内容后,将修改的body赋值给request.Body即可。

如何在中间件中访问或操作Response

在上诉代码中,如果没有设置响应头,我们调用发现响应头如下:
image.png
系统默认设置了Date,Content-Length和Content-Type头信息,下面我们通过中间件修改Content-Type

0. 测试下Response在Write之后,能不能进行修改

  1. func TestMiddleware(next http.Handler) http.Handler {
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3. next.ServeHTTP(w, r)
  4. // 在ServeHTTP()之后添加响应头信息
  5. w.Header().Add("Content-Type", "application/json")
  6. })
  7. }

image.png
结果发现不能修改响应头信息。
阅读源码发现:
http.response中,当调用Write()方法之后,会调用WriteHeader() 方法,如果调用的该方法,后面继续设置Header的时候,都只会操作 response.Header中的内容,实际返回的头信息在response.cw.Header中。
所以我们需要在WriteHeader()调用前进行操作。

1. 在Response中新增头信息

修改中间件代码

  1. func TestMiddleware(next http.Handler) http.Handler {
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3. w.Header().Set("Content-Type", "application/json")
  4. next.ServeHTTP(w, r)
  5. })
  6. }

中间件在调用 next.ServeHTTP 之前设置响应头信息,这样在响应时才是我们设置的响应头信息
image.png

2. 修改Response中的body和statusCode

Response头信息在next.ServeHTTP() 调用前就可以进行设置,但是ResponseBody的数据,必须要在ServeHttp()调用之后才会写入,并且ResponseWriter也没有提供读取响应体的接口,所以这里需要采用自定义响应结构体的方法来修改Response中的bodystatusCode等数据。

自定义一个ResponseWriter

  1. // CustomResponseWriter 自定义的一个ResponseWriter
  2. type CustomResponseWriter struct {
  3. http.ResponseWriter
  4. }
  5. func (w *CustomResponseWriter) Write(b []byte) (int, error) {
  6. // 接管body的写入,在这里可以修改响应的内容
  7. // 在Hello World!\n后面添加一个 smile
  8. b = append(b, []byte("😁")...)
  9. return w.ResponseWriter.Write(b)
  10. }

修改中间件,使用自定义ResponseWriter

  1. func TestMiddleware(next http.Handler) http.Handler {
  2. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  3. next.ServeHTTP(&CustomResponseWriter{
  4. ResponseWriter: w,
  5. }, r)
  6. })
  7. }

查看效果

image.png

在某些非Restful接口中,经常会统一返回200的状态码,根据自带的code来判断处理结果,那么如何修改http返回的状态码呢?

如果需要修改Response中的状态码,则需要自定义WriterHeader()方法。修改上面自定义ResponseWriter:

  1. func (w *CustomResponseWriter) WriterHeader(statusCode int) {
  2. // 把状态码放入自定义Code中,在需要的时候访问
  3. w.Code = statusCode
  4. // http响应码固定成200
  5. w.ResponseWriter.WriteHeader(http.StatusOK)
  6. }

现在将状态码固定成了200,修改handler中的内容,写入500的响应码

  1. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  2. w.WriteHeader(http.StatusInternalServerError)
  3. })

image.png
可以看到,响应的结果还是200

多中间件使用