Post

net/http study[1]-basic usage

net/http study[1]-basic usage

Enhancement in go1.22

  1. routing

Advanced routing

  1. Support method based routing
    • Before
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
        router.HandleFunc("/todos", FindByID)
      
        func FindByID(w http.ResponseWriter, r *http.Request) {
        if r.Method == http.MethodGet {
                //handlre get
        }else if r.Method == http.MethodPost {
                    //handle post
        }else{
            //handle other method
            }    
        }
              
      
    • After
      1
      2
      
        router.Handle("GET /todos", handler1)
        router.Handle("POST /todos", handler2)
      

      If a path no explicit method defined, it will handle any method that have not been defined explicitly.

      1
      2
      
        router.Handle("PUT /todos", handler1)
        router.Handle("/todos", handler2)
      

      The handler2 will handle the request of GET POST DELETE PUT /todos

  2. Support host based routing
  3. Support wide card routing (path parameter)
    example:
    /{message}
    /products/{slug}
    /{id}/elements
    Unvalid example:
    /product_{id}
    /articles/{slug}.html

    r.PathValue("id") to get the value of the path parameter

    • basic usage

      curl "http://localhost:8080/todos/123?p=1"

      1
      2
      3
      4
      5
      
        router.HandleFunc("GET /todos/{id}", func(w http.ResponseWriter, r *http.Request) {
            id := r.PathValue("id")
            p := r.URL.Query().Get("p")
            w.Write([]byte("get a todo by id " + id + " " + p))
        })
      
    • multiple wildcards /chats/{id}/message/{index}
    • Matching remainder
      The last wildcard in a pattern can optionally match all remaining path segments by having its name end in ...
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
        mux.HandleFunc("/tree/{steps...}", handler)
      
        urls := []string{
            "/tree/1",        //match
            "/tree/1/2",      //match
            "/tree/1/2/test", //match
            "/tree/",         //miss
            "/tree",          //miss
            "/none",          //miss
        }
      
      
    • Pattern with trailing slash If a routing pattern ends in a trailing slash, that will result in an anonymous ...

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      
        mux.HandleFunc("/tree/", handler)
      
        urls := []string{
            "/tree/1",        //match
            "/tree/1/2",      //match
            "/tree/1/2/test", //match
            "/tree/",         //match
            "/tree",          //miss
            "/none",          //miss
        }
        //Note that we can’t retrieve the steps using r.PathValue, so we use r.URL.Path instead.
        func handler(w http.ResponseWriter, r *http.Request) {
            fmt.Printf("URL Path received: %s\n", r.URL.Path)
        }
      
    • Match end of URL {$}
      1
      2
      3
      4
      5
      6
      7
      8
      
       mux.HandleFunc("/tree/{$}", handler)
      
       urls := []string{
           "/tree/",     //match
           "/tree",      //miss
           "/tree/1",    //miss
           "/none",       //miss
       }
      
  4. Conflicting paths & precedence

    • most specific wins
      1
      2
      
        router.Handle("/items/{id}", handler1)
        router.Handle("/items/latest", handler2)
      

      the request /items/latest will be handled by handler2

    • conflict detection
      1
      2
      
        router.Handle("GET /todos/{id}", handler1)
        router.Handle("GET /{resource}/123", handler2)
      

      will panic at runtime in compile time,and error as below:

      1
      2
      3
      4
      5
      
        panic: pattern "GET /{resources}/123" (registered at /Users/winter_wang/go1.22_projects/demo/main.go:34) conflicts with pattern "GET /todos/{id}" (registered at /Users/winter_wang/go1.22_projects/demo/main.go:18):
        GET /{resources}/123 and GET /todos/{id} both match some paths, like "/todos/123".
        But neither is more specific than the other.
        GET /{resources}/123 matches "/resources/123", but GET /todos/{id} doesn't.
        GET /todos/{id} matches "/todos/id", but GET /{resources}/123 doesn't.
      

Middleware

Here is a simple example to implement middlewares using ntt/http package.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type Middleware func(handler http.Handler) http.Handler

func CreateStack(xs ...Middleware) Middleware {
	return func(next http.Handler) http.Handler {
		for i := len(xs) - 1; i >= 0; i-- {
			x := xs[i]
			next = x(next)
		}
		return next
	}
}

func LoggerMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		println("before")
		next.ServeHTTP(w, r)
		println("after")
	})
}

func AuthMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Println("Auth: checking authorization")
		if r.Header.Get("Authorization") == "" {
			http.Error(w, "Forbidden", http.StatusForbidden)
			return
		}
		next.ServeHTTP(w, r)
	})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func main() {
	router := http.NewServeMux()
	router.HandleFunc("POST /todos", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("create a todo"))
	})

	router.HandleFunc("GET /todos/{id}", func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		p := r.URL.Query().Get("p")
		w.Write([]byte("get a todo by id " + id + " " + p))

	})

	router.HandleFunc("PATCH /todos/{id}", func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		w.Write([]byte("update a todo by id " + id))
	})

	router.HandleFunc("DELETE /todos/{id}", func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		w.Write([]byte("delete a todo by id " + id))
	})

	stack := CreateStack(LoggerMiddleware, AuthMiddleware)

	ere := http.ListenAndServe("localhost:8090", stack(router))
	if ere != nil {
		panic(ere)
	}
}   

Group Routing and subrouting

http.StripPrefix to create a group routing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
	router := http.NewServeMux()
	g1 := http.NewServeMux()

	router.HandleFunc("POST /todos", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("create a todo"))
	})
	router.HandleFunc("GET /todos/{id}", func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		p := r.URL.Query().Get("p")
		w.Write([]byte("get a todo by id " + id + " " + p))

	})
	g1.Handle("/v1/", http.StripPrefix("/v1", router))

	stack := CreateStack(LoggerMiddleware)

	ere := http.ListenAndServe("localhost:8090", stack(g1))
	if ere != nil {
		panic(ere)
	}
}

Pass through context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const TokenKey = "Authorization"

func AuthMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Println("Auth: checking authorization")
		if r.Header.Get("Authorization") == "" {
			http.Error(w, "Forbidden", http.StatusForbidden)
			return
		}
		ctx := context.WithValue(r.Context(), TokenKey, r.Header.Get("Authorization"))
		req := r.WithContext(ctx)
		next.ServeHTTP(w, req)
	})
}

Reference

https://www.willem.dev/articles/url-path-parameters-in-routes/
https://www.youtube.com/watch?v=H7tbjKFSg58

This post is licensed under CC BY 4.0 by the author.