Go Web 框架必備的 ServeMux 路由
【導讀】本文介紹了 ServeMux 路由的內部實現。
功能簡介
根據 Golang 文檔 (https://golang.org/pkg/net/http/#ServeMux) 中的介紹,ServeMux 是一個 HTTP 請求多路複用器 (HTTP Request multiplexer
)。
具體來說,它主要有以下兩個功能:
-
路由功能,將請求的 URL 與一組已經註冊的路由模式 (
pattern
) 進行匹配,並選擇一個最接近模式,調用其處理函數。 -
它會清理請求 URL 與
Host
請求頭,清除端口號,並對包含..
和重複/
的 URL 進行處理。
註冊名字固定的路徑
ServeMux 註冊路由模式的方式有兩種,註冊名字固定的路徑
與註冊根路徑子樹
。
註冊名字固定的路徑
就是指定一個固定的 URL 和請求進行精確匹配。需要注意的是 /
路由模式會匹配所有未被其他路由模式匹配的請求。
例如下面的代碼,請求/a
會匹配/a
路由模式,請求/abc
會匹配/
路由模式。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/a", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "/a", request.URL.EscapedPath())
})
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "default", request.URL.EscapedPath())
})
log.Fatalln(http.ListenAndServe(":8080", nil))
}
>>> curl 'localhost:8080/a'
/a /a
>>> curl 'localhost:8080/abc'
default /abc
註冊根路徑子樹
註冊根路徑子樹
就是註冊一個子路徑,所有以這個子路徑開頭的請求都會轉發到對應的 handler 中。注意子路徑__必須__以 /
結尾。
例如下面的代碼中,所有以 /user/
開頭的請求,都會被匹配。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/user/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "user", request.URL.EscapedPath())
})
log.Fatalln(http.ListenAndServe(":8080", nil))
}
>>> curl '0.0.0.0:8080/user/1'
user /user/1
>>> curl '0.0.0.0:8080/user/2'
user /user/2
最長匹配
註冊根路徑子樹
是符合最長路徑匹配的原則的,例如我們註冊了兩個子路徑,/image/gif/
和/image/
,URL 爲/image/gif/
的請求會優先匹配第一個路由模式。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// 注意我把 /image/ 寫在了前面
http.HandleFunc("/image/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "image", request.URL.EscapedPath())
})
http.HandleFunc("/image/gif/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "gif", request.URL.EscapedPath())
})
log.Fatalln(http.ListenAndServe(":8080", nil))
}
>>> curl '0.0.0.0:8080/image/gif/'
gif /image/gif/
APPEND_SLASH
APPEND_SLASH
是 Django 中的一個配置,是指如果我們註冊了一個路由模式/something/
,且APPEND_SLASH=True
的話,那麼 URL 爲 /something
的請求會自動重定向爲/something/
。
註冊根路徑子樹
默認也是有這種功能的,請看下面的代碼:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/image/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "image", request.URL.EscapedPath())
})
log.Fatalln(http.ListenAndServe(":8080", nil))
}
>>> curl -v '0.0.0.0:8080/image'
> GET /image HTTP/1.1
> Host: 0.0.0.0:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Content-Type: text/html; charset=utf-8
< Location: /image/
< Date: Sat, 30 Nov 2019 11:30:14 GMT
< Content-Length: 42
<
<a href="/image/">Moved Permanently</a>.
我們註冊了子路徑/image/
,服務器會自動將/image
請求重定向爲/image/
。
如果我們不想讓服務器自動重定向的話,只需要添加一個/image
模式就好了。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/image/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "sub image", request.URL.EscapedPath())
})
http.HandleFunc("/image", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "image", request.URL.EscapedPath())
})
log.Fatalln(http.ListenAndServe(":8080", nil))
}
>>> curl '0.0.0.0:8080/image'
image /image
>>> curl '0.0.0.0:8080/image/'
sub image /image/
>>> curl '0.0.0.0:8080/image/gif'
sub image /image/gif
使用域名匹配
ServeMux 還支持根據主機名精確匹配,沒有指定主機名的路徑/
路由模式將會是所有未匹配請求的缺省值。
下面的代碼中,主機名爲 localhost
請求將會默認匹配localhost/
路由模式,其他主機名的請求會匹配/
路由模式。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("localhost/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "localhost", request.URL.EscapedPath())
})
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "default", request.URL.EscapedPath())
})
log.Fatalln(http.ListenAndServe(":8080", nil))
}
>>> curl -v '127.0.0.1:8080/'
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sat, 30 Nov 2019 11:39:04 GMT
< Content-Length: 10
< Content-Type: text/plain; charset=utf-8
<
default /
>>> curl -v '0.0.0.0:8080/'
> GET / HTTP/1.1
> Host: 0.0.0.0:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sat, 30 Nov 2019 11:39:14 GMT
< Content-Length: 10
< Content-Type: text/plain; charset=utf-8
<
default /
>>> curl -v 'localhost:8080/'
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sat, 30 Nov 2019 11:39:19 GMT
< Content-Length: 12
< Content-Type: text/plain; charset=utf-8
<
localhost /
>>> curl -v 'localhost:8080/abc'
> GET /abc HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sat, 30 Nov 2019 12:01:47 GMT
< Content-Length: 15
< Content-Type: text/plain; charset=utf-8
<
localhost /abc
處理冗餘的 URL
-
針對 URL 中包含
..
的請求,ServeMux 會對其 Path 進行整理,並匹配到合適的路由模式上。 -
針對 URL 中包含重複
/
的請求,ServeMux 會對其進行重定向。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/image/png/1", func(writer http.ResponseWriter, request *http.Request) {
fmt.Fprintln(writer, "default", request.URL.EscapedPath())
})
log.Fatalln(http.ListenAndServe(":8080", nil))
}
>>> curl -v 'localhost:8080/image/gif/../png/1'
> GET /image/png/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sat, 30 Nov 2019 11:42:00 GMT
< Content-Length: 23
< Content-Type: text/plain; charset=utf-8
<
localhost /image/png/1
>>> curl -v 'localhost:8080/image/gif/../png//1'
> GET /image/png//1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Content-Type: text/html; charset=utf-8
< Location: /image/png/1
< Date: Sat, 30 Nov 2019 11:42:14 GMT
< Content-Length: 47
<
<a href="/image/png/1">Moved Permanently</a>.
>>> curl -v 'localhost:8080/image/png//1'
> GET /image/png//1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Content-Type: text/html; charset=utf-8
< Location: /image/png/1
< Date: Sat, 30 Nov 2019 11:45:18 GMT
< Content-Length: 47
<
<a href="/image/png/1">Moved Permanently</a>.
轉自:
bwangel.me/2019/11/30/intro-servemux/
Go 開發大全
參與維護一個非常全面的 Go 開源技術資源庫。日常分享 Go, 雲原生、k8s、Docker 和微服務方面的技術文章和行業動態。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/s2mHPSkUoxA6DOmaEU9Y6A