This page looks best with JavaScript enabled

Building Web Server with Go - Part 6

 ·  ☕ 4 min read
This is part 6 of 09 part series on building web server with go.
Checkout https://www.gophersumit.com/series/web-server/ for more.

middleware

Middleware is used to provide a processing pipeline for web servers. Middleware is way of providing shared processing functionality for our web handlers. Whenever there is incoming request to our web server, we may want to log it, check if user is authorized etc. While sending response back to client, we may want to perform compressions, set some custom headers etc. These are functionalities that is shared by most handlers or all the handlers. These shared functionalities can be abstracted as middleware.

Middleware operates as chain of calls to ServerHTTP.

Title: Middleware Processing Pipeline - Flow
Client->M1: Client Request
M1-->>M1: Authenticate User
M1->Client: Unauthorized
M1->M2: Next Handler
M2-->>M2: Authorize
M2-->>M1: Unauthorized
M1-->>Client: Unauthorized
M2->M3: Next Handler
M3-->>M3: Log Request
M3->Handler: Next Handler
Handler-->>Handler: Processing
Handler->M3: Response 
M3 -->>M3: Log Response
M3->M2: Response 
M2->M1: Response 
M1->Client: Response 

multiplexer

If you recall from first post from this series, we have Handler type defined in net/http library as:

1
2
3
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

We have also used http.DefaultServeMux() to create a new multiplexer for request handling. This multiplexer is ServeMux type defined as

1
2
3
4
5
6
type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry // slice of entries sorted from longest to shortest.
	hosts bool       // whether any patterns contain hostnames
}

If you dig further in source code for net/http, you will find that ServeMux type implements Handler interface.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	if r.RequestURI == "*" {
		if r.ProtoAtLeast(1, 1) {
			w.Header().Set("Connection", "close")
		}
		w.WriteHeader(StatusBadRequest)
		return
	}
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, r)
}
The multiplexer which we are using is a middleware.

building custom middleware

Let’s build a simple middleware and plug it in processing pipeline.

For our function to be able to work as valid middleware, it should be pluggable in processing chain. To satisfy this requirement, our function should accept http.Handler type and it should also return http.Handler type.

1
2
3
4
5
6
7
8
func customMiddleware(hand http.Handler) http.Handler {
	myFunc := func(w http.ResponseWriter, r *http.Request) {
		// middleware processing
		hand.ServeHTTP(w, r)
	}

	return http.HandlerFunc(myFunc)
}

Things to note about this middleware:

  1. myFunc is an anonymous function declared in middleware. It closes over hand and will have access to hand since Go supports Closure.
  2. We are again using http.HandlerFunc adaptor function to convert myFunc to http.Handler type which is returned.
  3. Inside of our anonymous function before call to hand.ServeHTTP(), we add our middleware logic.

Lets spin a simple web server to set some custom header in every incoming http request.

  1. For our middleware to be utilized, we can use Server struct as below. customMiddleware will receive the request, it will perform our custom middleware logic and send the request to default server mux that we are creating.
1
2
3
4
server := http.Server{
      Addr:    ":3000",
      Handler: customMiddleware(mux),
  }
  1. In our custom middleware, we are setting request header using :
1
w.Header().Set("X-Correlation-Id", "Guid")

Complete working middleware code :

app.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import "net/http"

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", sendResponse)
  server := http.Server{
      Addr:    ":3000",
      Handler: customMiddleware(mux),
  }
  server.ListenAndServe()
}

handler.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
  "fmt"
  "net/http"
)

func sendResponse(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte("default page"))
}

func customMiddleware(hand http.Handler) http.Handler {
  handlerFunc := func(w http.ResponseWriter, r *http.Request) {
      fmt.Printf("received new request...")
      w.Header().Set("X-Correlation-Id", "Guid")
      hand.ServeHTTP(w, r)
  }
  return http.HandlerFunc(handlerFunc)
}

curl

1
2
3
4
5
6
$ curl -i http://localhost:3000
  HTTP/1.1 200 OK
  X-Correlation-Id: Guid
  Date: Sun, 09 Feb 2020 14:33:19 GMT
  Content-Length: 12
  Content-Type: text/plain; charset=utf-8

Sumit
WRITTEN BY
Sumit
Gopher


What's on this Page