前言
Web框架是Web开发中不可或缺的组件。它们的主要目标是抽象出HTTP请求和响应的细节,使开发人员可以更专注于业务逻辑的实现。在本篇文章中,我们将使用Go语言实现一个简单的Web框架,类似于Gin框架。
功能
我们的Web框架需要实现以下功能:
- 路由:处理HTTP请求的路由,并支持路径参数和通配符。
- 上下文:封装HTTP请求和响应,并提供访问请求参数的方法。
- 中间件:在请求处理之前或之后运行的函数。
- HTTP请求和响应:支持GET、POST等HTTP方法。
实现
HandlerFuncContext
type HandlerFunc func(Context)
Contexthttp.ResponseWriterhttp.Request
type Context struct {
Response http.ResponseWriter
Request *http.Request
Params map[string]string
}
Params/users/:idc.Params["id"]id
RouteRouter
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})
}
当HTTP请求到达时,需要遍历所有的路由,并找到匹配的路由。如果找到了一个匹配的路由,我们就调用它的处理函数,并且如果有中间件,需要遍历所有中间件执行中间件处理逻辑。否则,我们返回HTTP 404错误。
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)
}
}
matchPathContext
MiddlewareFunc
type MiddlewareFunc func(handler HandlerFunc) HandlerFunc
RouterUse
func (r *Router) Use(middleware MiddlewareFunc) {
r.middlewares = append(r.middlewares, middleware)
}
Handle
GET
func (r *Router) GET(path string, handler HandlerFunc) {
r.Handle("GET", path, handler)
}
现在,我们已经完成了一个简单的Web框架的实现。下面是完整的代码:
完整的代码
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)
}
在上面的代码中,我们添加了一个中间件函数记录请求耗时,它用于记录每个HTTP请求的执行。我们还添加了几个路由,以演示路径参数和通配符的用法。
运行结果
$ 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
总结
在本文中,我们使用Go语言实现了一个简单的Web框架。我们实现了路由、上下文、中间件、HTTP请求和响应等功能。还演示了如何使用路径参数和通配符来匹配不同的路径。这个Web框架虽然比不上流行的框架,但它可以作为学习Web框架实现的好起点。
欢迎关注,学习不迷路