一、基础 HTTP 服务器
任何 Web 框架的核心都是基于 Go 标准库的 net/http 包。我们从最简单的 HTTP 服务器开始:
// main.go
package main
import (
    "fmt"
    "net/http"
)
func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, MiniWeb!")
    })
    http.ListenAndServe(":8080", nil)
}二、实现路由系统
1. 定义路由结构
// router.go
package miniweb
import "net/http"
type Router struct {
    routes map[string]map[string]http.HandlerFunc // method -> path -> handler
}
func NewRouter() *Router {
    return &Router{
        routes: make(map[string]map[string]http.HandlerFunc),
    }
}
// 添加路由
func (r *Router) AddRoute(method, path string, handler http.HandlerFunc) {
    if r.routes[method] == nil {
        r.routes[method] = make(map[string]http.HandlerFunc)
    }
    r.routes[method][path] = handler
}
// 实现 http.Handler 接口
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    method := req.Method
    path := req.URL.Path
    if handlers, ok := r.routes[method]; ok {
        if handler, ok := handlers[path]; ok {
            handler(w, req)
            return
        }
    }
    // 未找到路由
    http.NotFound(w, req)
}2. 使用路由
// main.go
package main
import (
    "miniweb"
    "net/http"
)
func main() {
    router := miniweb.NewRouter()
    router.AddRoute("GET", "/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello from MiniWeb!"))
    })
    
    http.ListenAndServe(":8080", router)
}三、实现上下文(Context)
封装请求和响应,方便传递数据:
// context.go
package miniweb
import (
    "encoding/json"
    "net/http"
)
type Context struct {
    Writer  http.ResponseWriter
    Request *http.Request
    Params  map[string]string // 用于存储路由参数(后续扩展)
}
// 返回 JSON 数据
func (c *Context) JSON(code int, obj interface{}) {
    c.Writer.Header().Set("Content-Type", "application/json")
    c.Writer.WriteHeader(code)
    json.NewEncoder(c.Writer).Encode(obj)
}
// 返回字符串
func (c *Context) String(code int, format string, values ...interface{}) {
    c.Writer.Header().Set("Content-Type", "text/plain")
    c.Writer.WriteHeader(code)
    c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
}四、改进路由支持中间件
1. 定义中间件类型
// router.go
type HandlerFunc func(*Context)
// 中间件链
type Middleware func(HandlerFunc) HandlerFunc
type Router struct {
    routes      map[string]map[string]HandlerFunc
    middlewares []Middleware
}
// 注册中间件
func (r *Router) Use(middleware Middleware) {
    r.middlewares = append(r.middlewares, middleware)
}
// 创建处理链
func (r *Router) createHandler(handler HandlerFunc) HandlerFunc {
    for i := len(r.middlewares) - 1; i >= 0; i-- {
        handler = r.middlewares[i](handler)
    }
    return handler
}2. 中间件示例
// 日志中间件
func Logger() Middleware {
    return func(next HandlerFunc) HandlerFunc {
        return func(c *Context) {
            start := time.Now()
            next(c)
            log.Printf("Request handled in %v", time.Since(start))
        }
    }
}
// 使用中间件
router.Use(Logger())五、支持动态路由(参数匹配)
使用 Trie 树或正则表达式实现高效路由匹配,这里简化实现:
// router.go
func (r *Router) AddRoute(method, path string, handler HandlerFunc) {
    // 简单实现参数匹配(例如 /user/:id)
    if strings.Contains(path, ":") {
        // 转换为正则表达式并存储
        // 这里需要更复杂的实现(留给读者扩展)
    }
    // ...原有逻辑
}六、实现模板渲染
// context.go
func (c *Context) HTML(code int, tmpl string, data interface{}) {
    c.Writer.Header().Set("Content-Type", "text/html")
    c.Writer.WriteHeader(code)
    
    t, err := template.ParseFiles(tmpl)
    if err != nil {
        c.String(500, "Template Error: %v", err)
        return
    }
    t.Execute(c.Writer, data)
}七、静态文件服务
// router.go
func (r *Router) Static(prefix, dir string) {
    fs := http.FileServer(http.Dir(dir))
    handler := http.StripPrefix(prefix, fs)
    
    r.AddRoute("GET", prefix+"/*", func(c *Context) {
        handler.ServeHTTP(c.Writer, c.Request)
    })
}八、最终使用示例
package main
import (
    "miniweb"
    "net/http"
)
func main() {
    r := miniweb.NewRouter()
    
    // 注册中间件
    r.Use(miniweb.Logger())
    
    // 静态文件
    r.Static("/static", "./static")
    
    // 路由
    r.AddRoute("GET", "/", func(c *miniweb.Context) {
        c.String(200, "Welcome to MiniWeb!")
    })
    
    r.AddRoute("GET", "/user/:id", func(c *miniweb.Context) {
        c.JSON(200, map[string]string{"id": c.Params["id"]})
    })
    
    http.ListenAndServe(":8080", r)
}九、关键优化方向
- 路由匹配算法:使用 Trie 树或 Radix 树提高性能
- Context 池:使用 sync.Pool重用 Context 对象
- 参数绑定:实现自动的 JSON/Form 数据绑定
- 错误处理:统一错误处理中间件
- 测试覆盖:添加单元测试和基准测试
十、 路由设计
1. 设计路由树节点
首先,我们需要定义路由树的节点结构。每个节点需要包含以下信息:
- path:当前节点的路径片段。
- children:子节点列表。
- handlers:与路径关联的处理函数。
- wildChild:是否包含通配符子节点。
- nType:节点类型(静态、参数、通配符)。
type nodeType uint8
const (
    static nodeType = iota // 静态路由
    param                 // 参数路由,如 :id
    catchAll              // 通配符路由,如 *
)
type node struct {
    path      string         // 当前节点的路径片段
    children  []*node        // 子节点
    handlers  HandlersChain  // 处理函数链
    wildChild bool           // 是否包含通配符子节点
    nType     nodeType       // 节点类型
    indices   string         // 子节点索引(用于快速查找)
}2. 插入路由
插入路由的逻辑是前缀树的核心。我们需要处理以下情况:
- 静态路由(如 /user)。
- 参数路由(如 /user/:id)。
- 通配符路由(如 /static/*filepath)。
以下是插入路由的代码实现:
func (n *node) addRoute(path string, handlers HandlersChain) {
    fullPath := path
    n.priority++
    // 空树,直接插入
    if len(n.path) == 0 && len(n.children) == 0 {
        n.insertChild(path, fullPath, handlers)
        n.nType = static
        return
    }
walk:
    for {
        // 找到最长公共前缀
        i := longestCommonPrefix(path, n.path)
        // 分裂节点
        if i < len(n.path) {
            child := &node{
                path:      n.path[i:],
                wildChild: n.wildChild,
                nType:     static,
                indices:   n.indices,
                children:  n.children,
                handlers:  n.handlers,
            }
            n.children = []*node{child}
            n.indices = string(n.path[i])
            n.path = path[:i]
            n.handlers = nil
            n.wildChild = false
        }
        // 处理剩余路径
        if i < len(path) {
            path = path[i:]
            // 参数路由(如 :id)
            if path[0] == ':' {
                n.insertParam(path, fullPath, handlers)
                return
            }
            // 通配符路由(如 *)
            if path[0] == '*' {
                n.insertCatchAll(path, fullPath, handlers)
                return
            }
            // 静态路由
            c := path[0]
            for i, max := 0, len(n.indices); i < max; i++ {
                if c == n.indices[i] {
                    n = n.children[i]
                    continue walk
                }
            }
            // 插入新节点
            n.indices += string(c)
            child := &node{
                path: path,
            }
            n.children = append(n.children, child)
            n = child
            n.insertChild(path, fullPath, handlers)
            return
        }
        // 注册处理函数
        if n.handlers != nil {
            panic("handlers already registered for path: " + fullPath)
        }
        n.handlers = handlers
        return
    }
}3. 查找路由
查找路由的逻辑与插入类似,需要根据路径片段匹配节点,并处理参数和通配符。
func (n *node) getRoute(path string) (handlers HandlersChain, params map[string]string) {
walk:
    for {
        // 匹配路径
        if len(path) > len(n.path) {
            if path[:len(n.path)] == n.path {
                path = path[len(n.path):]
                // 参数路由
                if n.nType == param {
                    end := 0
                    for end < len(path) && path[end] != '/' {
                        end++
                    }
                    if params == nil {
                        params = make(map[string]string)
                    }
                    params[n.path[1:]] = path[:end]
                    path = path[end:]
                }
                // 通配符路由
                if n.nType == catchAll {
                    if params == nil {
                        params = make(map[string]string)
                    }
                    params[n.path[1:]] = path
                    return n.handlers, params
                }
                // 静态路由
                c := path[0]
                for i, max := 0, len(n.indices); i < max; i++ {
                    if c == n.indices[i] {
                        n = n.children[i]
                        continue walk
                    }
                }
            }
        } else if path == n.path {
            return n.handlers, params
        }
        return nil, nil
    }
}4. 辅助函数
计算最长公共前缀
func longestCommonPrefix(a, b string) int {
    i := 0
    for i < len(a) && i < len(b) && a[i] == b[i] {
        i++
    }
    return i
}插入参数路由
func (n *node) insertParam(path, fullPath string, handlers HandlersChain) {
    end := 1
    for end < len(path) && path[end] != '/' {
        end++
    }
    child := &node{
        path:     path[:end],
        nType:    param,
        fullPath: fullPath,
    }
    n.children = append(n.children, child)
    n.wildChild = true
    n = child
    if end < len(path) {
        path = path[end:]
        n.insertChild(path, fullPath, handlers)
    } else {
        n.handlers = handlers
    }
}插入通配符路由
func (n *node) insertCatchAll(path, fullPath string, handlers HandlersChain) {
    child := &node{
        path:     path,
        nType:    catchAll,
        handlers: handlers,
        fullPath: fullPath,
    }
    n.children = append(n.children, child)
    n.wildChild = true
}5. 测试路由树
以下是一个简单的测试示例:
func main() {
    root := &node{}
    root.addRoute("/user/:id", HandlersChain{func(c *Context) {
        fmt.Println("User ID:", c.Params["id"])
    }})
    root.addRoute("/static/*filepath", HandlersChain{func(c *Context) {
        fmt.Println("Filepath:", c.Params["filepath"])
    }})
    handlers, params := root.getRoute("/user/123")
    if handlers != nil {
        c := &Context{Params: params}
        handlers[0](c)
    }
    handlers, params = root.getRoute("/static/css/style.css")
    if handlers != nil {
        c := &Context{Params: params}
        handlers[0](c)
    }
}6. 性能优化
- 索引优化:使用 indices字段快速查找子节点。
- 优先级排序:根据节点的优先级调整匹配顺序。
- 减少内存分配:预分配内存,避免频繁分配。










