net/http study[1]-basic usage
Enhancement in go1.22
- routing
Advanced routing
- 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 ofGET POST DELETE PUT /todos
- Before
- Support host based routing
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 parameterbasic 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 }
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 byhandler2
- 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.
- most specific wins
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