SoFunction
Updated on 2025-03-04

How to write a sample tutorial for Go language middleware

introduction

In the context of web development, "middleware" usually means "a part of the application that wraps the original application and adds some extra functionality". This concept always seems to be unintelligible, but I think the middleware is great.

First of all, a good middleware has a responsibility to pluggable and self-sufficient. This means that you can embed your middleware at the interface level and it can run it directly. It will not affect your encoding method, it is not a framework, it is just a layer inside your request processing. There is no need to rewrite your code. If you want to use a function of the middleware, you can insert it there. If you don’t want to use it, you can directly remove it.

Looking at Go, middleware is very common, even in the standard library. Although it won't be so obvious at the beginning, the functions StripText or TimeoutHandler in the standard library net/http are what we want to define and middleware, and they wrap your handler when processing requests and corresponding times, and handle some extra steps.

At the beginning, we thought it seemed easy to write middleware, but we also encountered various pitfalls when we actually wrote it. Let's take a look at some examples.

1. Read request

In our example, all middleware will accept http. The handler takes as an argument and returns one. This makes it easy for people to string together intermediate products. The basic model of all our intermediate products is as follows:

func X(h )  {
 return (func(w , r *) {
 // Something here...
 (w, r)
 })
}

We want to redirect all requests to a slash—say /message/, to their non-slash equivalents, such as /message. We can write this way:

func TrailingSlashRedirect(h )  {
 return (func(w , r *) {
 if  != "/" && [len()-1] == '/' {
 (w, r, [:len()-1], )
 return
 }
 (w, r)
 })
}

Is it easy?

2. Modify the request

Let's say we want to add a title to the request, or modify it. The documentation states:

The handler should not modify the provided request except to read the body.

Go standard library copy. We should do the same before passing it to the response chain. Suppose we want to set the Request-Id header on each request for internal tracking. Create a shallow copy of *Request and modify the title before the proxy.

func RequestID(h )  {
 return (func(w , r *) {
 r2 := new()
 *r2 = *r
 ("X-Request-Id", uuid.NewV4().String())
 (w, r2)
 })
}

3. Write response header

If you want to set response headers, you can just write them and then proxy the request.

func Server(h , servername string)  {
 return (func(w , r *) {
 ().Set("Server", servername)
 (w, r)
 })
}

The problem above is that if the internal processor also sets the server header, then your header will be overwritten. This can cause problems if you don't want to expose the server header of the internal software, or if you want to remove the header before sending the response to the client.

To do this, we must implement the ResponseWriter interface ourselves. Most of the time, we only proxy to the underlying ResponseWriter, but if the user tries to write a response, we sneak in and add our title.

type serverWriter struct {
 w 
 name string
 wroteHeaders bool
}

func (s *serverWriter) Header()  {
 return ()
}

func (s *serverWriter) WriteHeader(code int)  {
 if  == false {
 ().Set("Server", )
  = true
 }
 (code)
}

func (s *serverWriter) Write(b []byte) (int, error) {
 if  == false {
 // We hit this case if user never calls WriteHeader (default 200)
 ().Set("Server", )
  = true
 } return (b)
}

To use it in our middleware we will write:

func Server(h , servername string)  {
 return (func(w , r *) {
 sw := &serverWriter{
 w: w,
 name: servername,
 }
 (sw, r)
 })
}

question

What if the user never calls Write or WriteHeader? For example, there is a 200 state and is an empty body, or a response to an option request - our intercept function will not run. Therefore, we should add verification after the ServeHTTP call.

func Server(h , servername string)  {
 return (func(w , r *) {
 sw := &serverWriter{
 w: w,
 name: servername,
 }
 (sw, r)
 if  == false {
 ().Set("Server", )
  = true
 }
 })
}

Other ResponseWriter interfaces

The ResponseWriter interface only requires three methods. But in practice, it can also respond to other interfaces, for example. Your middleware may accidentally disable HTTP/2 support, which is bad.

// Push implements the  interface.
func (s *serverWriter) Push(target string, opts *) error {
 if pusher, ok := .(); ok {
 return (target, opts)
 }
 return 
}

// Flush implements the  interface.
func (s *serverWriter) Flush() {
 f, ok := .()
 if ok {
 ()
 }
}

Summarize

Through the above learning, I wonder if you have a complete understanding of Go writing middleware. You can also try to write a middleware using Go.

Okay, the above is the entire content of this article. I hope that the content of this article has a certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.