张子阳的博客

首页 读书 技术 店铺 关于
张子阳的博客 首页 读书 技术 关于

Go 自定义Url路由器

2019-06-24 张子阳 分类: Go 语言

Go Web编程比较不方便的一点就是URL路由,也叫多路复用器(ServerMux),实现的比较“粗放”。简单来说,就是匹配的规则太广太简单,没有提供通配符、占位符的机制。这篇文章将重写一个简单的URL路由器,使其支持通配符。

为了保留一部分的使用习惯,我们首先了解一下go提供的默认路由器(DefaultServerMux)的规则是什么样的。如下表所示:

模式 说明
/ 匹配所有:/、/a、/a/b、/a/b/c ...
/a 仅匹配/a
/a/ 当没有配置/a时,匹配/a/和/a,并且/a的Path会被重写为/a/;当配置了/a时,则分别进行匹配。
除此以外,还匹配:/a/b、/a/b/c ...

这个匹配规范显然有点宽了,当以“/”收尾时,模式就相当于一个“前缀”了。因此,我们可以自定两个通配符:“+”和“*”。

还是举一些例子看得更清楚一些:

模式 说明
/ 仅匹配 /
/a 仅匹配/a
/a/+ 匹配 /a/b、/a/c ...,不匹配 /a/b/c、/a/b/c/d
/a/+/c 匹配 /a/b/c、/a/d/c
/a/*/c 匹配 /a/b/c、/a/d/e/c、/a/b/d/f/c ...
/a/* 匹配 /a/、/a/b、/a/d/、/a/b/d/f ...

实现这个路由器只需要实现Handler接口,它只定义了一个ServeHTTP方法:

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

下面是实现的主要代码:

package router

import (
    "net/http"
    "strings"
)

// MyRouter 路由
type myRouter struct {
    // 保存用户的 pattern 和 对应的handlerFunc
    handlers map[string]http.HandlerFunc

    // 保存用户pattern,主要是为了按顺序,因为map没有顺序
    patterns []string
}

// DefaultRouter 路由
var DefaultRouter = &myRouter{handlers: map[string]http.HandlerFunc{}, patterns: []string{}}

// ServeHTTP
func (x *myRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    path := r.URL.Path

    for _, k := range x.patterns {
        if match(k, path) {
            x.handlers[k](w, r)
            return
        }
    }

    // 全都不匹配则404
    http.NotFound(w, r)
}

func (x *myRouter) HandleFunc(pattern string, handler http.HandlerFunc) {
    if !checkPattern(pattern) {
        panic("pattern格式有误,检查是否存在下面模式:/+/*、/*/+、/*/*,可使用/*代替")
    }

    if _, ok := x.handlers[pattern]; !ok {
        // 如果不存在,在list中加一个
        x.patterns = append(x.patterns, pattern)
    } else {
        panic("URL模式 " + pattern + " 重复注册了!")
    }

    x.handlers[pattern] = handler
}

// 验证模式和路径是否匹配
func match(pattern string, path string) bool {
    ...
}

// 验证模式,不能出现 /+/* 或者 /*/+ ,或者 /*/*
// 因为它们都等于 /*/
func checkPattern(pattern string) bool {
    ...
}

使用方法如下:

package main

import (
    "fmt"
    "net/http"
    "tracefact/router"
)

func main() {
    fmt.Println("server start.")

    r := router.DefaultRouter

    // 仅匹配 /
    r.HandleFunc("/", getHandler("/"))

    // 仅匹配 /a
    r.HandleFunc("/a", getHandler("/a"))

    // 仅匹配 /b/ 和 /b
    r.HandleFunc("/b/", getHandler("/b/"))

    // 匹配 /a/x/y/b、匹配 /a/x/b ...
    r.HandleFunc("/a/*/b", getHandler("/a/*/b"))

    // 匹配 /c/、/c/x、/c/x/y ...
    r.HandleFunc("/c/*", getHandler("/c/*"))

    http.ListenAndServe(":8081", r)
}

func getHandler(pattern string) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        body := r.URL.Path + " : " + pattern
        fmt.Println(body)
        fmt.Fprintln(w, body)
    }
}

完整代码可以查看这里:https://github.com/tracefact/router

感谢阅读,希望这篇文章能给你带来帮助!