前言
Web框架是Web開(kāi)發(fā)中不可或缺的組件卖子。它們的主要目標(biāo)是抽象出HTTP請(qǐng)求和響應(yīng)的細(xì)節(jié),使開(kāi)發(fā)人員可以更專注于業(yè)務(wù)邏輯的實(shí)現(xiàn)刑峡。在本篇文章中洋闽,我們將使用Go語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Web框架,類似于Gin框架突梦。
功能
我們的Web框架需要實(shí)現(xiàn)以下功能:
- 路由:處理HTTP請(qǐng)求的路由诫舅,并支持路徑參數(shù)和通配符。
- 上下文:封裝HTTP請(qǐng)求和響應(yīng)宫患,并提供訪問(wèn)請(qǐng)求參數(shù)的方法刊懈。
- 中間件:在請(qǐng)求處理之前或之后運(yùn)行的函數(shù)。
- HTTP請(qǐng)求和響應(yīng):支持GET、POST等HTTP方法虚汛。
實(shí)現(xiàn)
首先匾浪,我們需要定義一個(gè)HandlerFunc
類型,表示處理HTTP請(qǐng)求的函數(shù)卷哩。這個(gè)函數(shù)需要接受一個(gè)Context
類型的參數(shù)蛋辈,用于訪問(wèn)請(qǐng)求和響應(yīng)。
type HandlerFunc func(Context)
接下來(lái)将谊,我們需要定義一個(gè)Context
類型冷溶,封裝HTTP請(qǐng)求和響應(yīng),并提供訪問(wèn)請(qǐng)求參數(shù)的方法尊浓。我們可以使用Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的http.ResponseWriter
和http.Request
類型分別表示響應(yīng)和請(qǐng)求挂洛。
type Context struct {
Response http.ResponseWriter
Request *http.Request
Params map[string]string
}
Params
字段用于存儲(chǔ)路徑參數(shù)。例如眠砾,如果路由路徑為/users/:id
虏劲,則可以使用c.Params["id"]
訪問(wèn)路徑參數(shù)id
的值。
現(xiàn)在褒颈,我們可以開(kāi)始實(shí)現(xiàn)路由柒巫。我們需要定義一個(gè)Route
類型,表示一個(gè)路由谷丸,包含HTTP方法堡掏、路徑和處理函數(shù)。我們還需要一個(gè)Router
類型刨疼,表示整個(gè)應(yīng)用程序的路由器泉唁。它應(yīng)該包含所有的路由,包含需要的中間件揩慕,并能夠處理HTTP請(qǐng)求亭畜。
type Route struct {
method string
path string
handler HandlerFunc
}
type Router struct {
routes []*Route
middlewares []MiddlewareFunc
}
我們可以使用Handle
方法將路由添加到路由器中。
func (r *Router) Handle(method, path string, handler HandlerFunc) {
r.routes = append(r.routes, &Route{method, path, handler})
}
當(dāng)HTTP請(qǐng)求到達(dá)時(shí)迎卤,需要遍歷所有的路由拴鸵,并找到匹配的路由。如果找到了一個(gè)匹配的路由蜗搔,我們就調(diào)用它的處理函數(shù)劲藐,并且如果有中間件,需要遍歷所有中間件執(zhí)行中間件處理邏輯樟凄。否則聘芜,我們返回HTTP 404錯(cuò)誤。
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var match *Route
params := make(map[string]string)
for _, route := range r.routes {
if req.Method == route.method {
if ok, p := matchPath(route.path, req.URL.Path); ok {
match = route
params = p
break
}
}
}
if match != nil {
handler := match.handler
for i := len(r.middlewares) - 1; i >= 0; i-- {
handler = r.middlewares[i](handler)
}
handler(Context{w, req, params})
} else {
http.NotFound(w, req)
}
}
在上面的代碼中缝龄,我們使用了matchPath
函數(shù)來(lái)比較HTTP請(qǐng)求的路徑和路由的路徑汰现,以確定是否匹配挂谍。這個(gè)函數(shù)還會(huì)返回路徑參數(shù)的值,以便我們可以在Context
中訪問(wèn)它們服鹅。
現(xiàn)在,我們可以實(shí)現(xiàn)中間件百新。中間件是在請(qǐng)求處理前或處理后運(yùn)行的函數(shù)企软,它們可以修改請(qǐng)求或響應(yīng),或執(zhí)行其他任務(wù)饭望。我們可以定義一個(gè)MiddlewareFunc
類型仗哨,表示中間件函數(shù)。
type MiddlewareFunc func(handler HandlerFunc) HandlerFunc
接下來(lái)铅辞,我們可以在Router
中添加一個(gè)Use
方法厌漂,用于注冊(cè)中間件。這個(gè)方法會(huì)往路由中添加一個(gè)中間件斟珊,后面處理函數(shù)時(shí)候會(huì)要遍歷使用苇倡。
func (r *Router) Use(middleware MiddlewareFunc) {
r.middlewares = append(r.middlewares, middleware)
}
最后,我們需要添加HTTP方法的支持囤踩。我們可以為每個(gè)HTTP方法定義一個(gè)快捷方法旨椒,它們分別調(diào)用Handle
方法并傳遞正確的HTTP方法和路徑。
例如堵漱,對(duì)于GET方法综慎,我們可以定義一個(gè)GET
方法,如下所示:
func (r *Router) GET(path string, handler HandlerFunc) {
r.Handle("GET", path, handler)
}
現(xiàn)在勤庐,我們已經(jīng)完成了一個(gè)簡(jiǎn)單的Web框架的實(shí)現(xiàn)示惊。下面是完整的代碼:
完整的代碼
package main
import (
"fmt"
"net/http"
"strings"
"time"
)
type HandlerFunc func(Context)
type Context struct {
Response http.ResponseWriter
Request *http.Request
Params map[string]string
}
type Route struct {
method string
path string
handler HandlerFunc
}
type Router struct {
routes []*Route
middlewares []MiddlewareFunc
}
type MiddlewareFunc func(handler HandlerFunc) HandlerFunc
func NewRouter() *Router {
return &Router{}
}
func (r *Router) Handle(method, path string, handler HandlerFunc) {
r.routes = append(r.routes, &Route{method, path, handler})
}
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var match *Route
params := make(map[string]string)
for _, route := range r.routes {
if req.Method == route.method {
if ok, p := matchPath(route.path, req.URL.Path); ok {
match = route
params = p
break
}
}
}
if match != nil {
handler := match.handler
for i := len(r.middlewares) - 1; i >= 0; i-- {
handler = r.middlewares[i](handler)
}
handler(Context{w, req, params})
} else {
http.NotFound(w, req)
}
}
func (r *Router) Use(middleware MiddlewareFunc) {
r.middlewares = append(r.middlewares, middleware)
}
func (r *Router) GET(path string, handler HandlerFunc) {
r.Handle("GET", path, handler)
}
func (r *Router) POST(path string, handler HandlerFunc) {
r.Handle("POST", path, handler)
}
func (r *Router) PUT(path string, handler HandlerFunc) {
r.Handle("PUT", path, handler)
}
func (r *Router) DELETE(path string, handler HandlerFunc) {
r.Handle("DELETE", path, handler)
}
func matchPath(path, pattern string) (bool, map[string]string) {
parts1 := strings.Split(path, "/")
parts2 := strings.Split(pattern, "/")
if len(parts1) != len(parts2) {
return false, nil
}
params := make(map[string]string)
for i, part := range parts1 {
if part != parts2[i] {
if strings.HasPrefix(part, ":") {
params[part[1:]] = parts2[i]
} else if strings.HasPrefix(part, "*") {
params[part[1:]] = strings.Join(parts2[i:], "/")
break
} else {
return false, nil
}
}
}
return true, params
}
使用案例
func main() {
router := NewRouter()
router.Use(func(handler HandlerFunc) HandlerFunc {
return func(ctx Context) {
start := time.Now()
handler(ctx)
fmt.Printf("%s cost %s\n", ctx.Request.RequestURI, time.Now().Sub(start))
}
})
router.GET("/", func(c Context) {
fmt.Fprintf(c.Response, "歡迎使用我的web框架!")
})
router.GET("/users/:id", func(c Context) {
fmt.Fprintf(c.Response, "User ID: %s", c.Params["id"])
})
router.GET("/users/:id/friends", func(c Context) {
fmt.Fprintf(c.Response, "User ID: %s, list all friends.", c.Params["id"])
})
router.GET("/*path", func(c Context) {
fmt.Fprintf(c.Response, "User path: %s", c.Params["path"])
})
http.ListenAndServe(":8080", router)
}
在上面的代碼中,我們添加了一個(gè)中間件函數(shù)記錄請(qǐng)求耗時(shí)愉镰,它用于記錄每個(gè)HTTP請(qǐng)求的執(zhí)行米罚。我們還添加了幾個(gè)路由,以演示路徑參數(shù)和通配符的用法丈探。
運(yùn)行結(jié)果
$ curl localhost:8080/users
User path: users
$ curl localhost:8080/users/1001
User ID: 1001
$ curl localhost:8080/users/1001/friends
User ID: 1001, list all friends.
$ curl localhost:8080/users/1001/friends/xxx
404 page not found
$ curl localhost:8080/xxxx
User path: xxxx
---------------------------------------------------
/users cost 51.917μs
/users/1001 cost 2.75μs
/users/1001/friends cost 5.833μs
/xxxx cost 6.875μs
總結(jié)
在本文中阔拳,我們使用Go語(yǔ)言實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的Web框架。我們實(shí)現(xiàn)了路由类嗤、上下文糊肠、中間件、HTTP請(qǐng)求和響應(yīng)等功能遗锣。還演示了如何使用路徑參數(shù)和通配符來(lái)匹配不同的路徑货裹。這個(gè)Web框架雖然比不上流行的框架,但它可以作為學(xué)習(xí)Web框架實(shí)現(xiàn)的好起點(diǎn)精偿。
歡迎關(guān)注弧圆,學(xué)習(xí)不迷路