当前位置: 首页 > news >正文

建设部注册中心网站网站开发中的文档

建设部注册中心网站,网站开发中的文档,百度新闻发布,高端网站设计公司名单文章目录 模块化路由前缀树路由 前情提示: 【Golang学习笔记】从零开始搭建一个Web框架(一)-CSDN博客 模块化路由 路由在kilon.go文件中导致路由和引擎交织在一起,如果要实现路由功能的拓展增强,那将会非常麻烦&…

文章目录

    • 模块化路由
    • 前缀树路由

前情提示:

【Golang学习笔记】从零开始搭建一个Web框架(一)-CSDN博客

模块化路由

路由在kilon.go文件中导致路由和引擎交织在一起,如果要实现路由功能的拓展增强,那将会非常麻烦,这无疑降低了代码的可读性和可维护性。现在的工作是将路由从引擎里剥离出来,引擎中仅对路由进行包装。

新建文件router.go,当前目录结构为:

myframe/├── kilon/│   ├── context.go│   ├── go.mod      [1]│   ├── kilon.go│   ├── router.go├── go.mod          [2]├── main.go

在router中添加下面内容:

package kilonimport ("net/http"
)type router struct {Handlers map[string]HandlerFunc
}
// 创建router对象
func newRouter() *router {return &router{make(map[string]HandlerFunc)}
}
// 剥离路由注册的具体实现
func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {key := method + "-" + patternr.Handlers[key] = handler
}
// 剥离SeverHTTP中路由处理的具体实现
func (r *router) handle(ctx *Context) {key := ctx.Method + "-" + ctx.Pathif handler, ok := r.Handlers[key]; ok {handler(ctx)} else {ctx.String(http.StatusNotFound, "404 NOT FOUND: %s\n", ctx.Path)}
}

修改kilon.go文件:

package kilonimport ("net/http"
)type HandlerFunc func(*Context)type Origin struct {router *router // 修改路由
}func New() *Origin {return &Origin{router: newRouter()} // 修改构造函数
}func (origin *Origin) addRoute(method string, pattern string, handler HandlerFunc) {origin.router.addRoute(method, pattern, handler) // 修改调用
}func (origin *Origin) GET(pattern string, hander HandlerFunc) {origin.addRoute("GET", pattern, hander) 
}func (origin *Origin) POST(pattern string, hander HandlerFunc) {origin.addRoute("POST", pattern, hander) 
}func (origin *Origin) ServeHTTP(w http.ResponseWriter, req *http.Request) {ctx := newContext(w, req)origin.router.handle(ctx) // 调用router.go中的处理方法
}func (origin *Origin) Run(addr string) (err error) {return http.ListenAndServe(addr, origin)
}

至此,实现了路由的模块化,后续路由功能的增强将不会改动kilon.go文件。

前缀树路由

目前的路由表使用map存储键值对,索引非常高效,但是有一个弊端,键值对的存储的方式,只能用来索引静态路由而无法实现动态路由。在实际的应用中,可能需要使用正则表达式或者其他匹配规则来实现更复杂的路由匹配,而 map 无法提供这种功能。接下来,将使用前缀树(Tire树)实现动态路由,主要实现两个功能:

  • 参数匹配:。例如 /p/:name/doc,可以匹配 /p/zhangsan/doc/p/lisi/doc
  • 通配*(仅允许最后一个有"*"号)。例如 /static/*filepath,可以匹配/static/fav.ico/static/js/jQuery.js

新建文件trie.go,当前文件目录结构为:

myframe/├── kilon/│   ├── context.go│   ├── go.mod      [1]│   ├── kilon.go│   ├── router.go│   ├── tire.go├── go.mod          [2]├── main.go

在trie.go中创建前缀树的节点:

type node struct {patten   string  // 待匹配路由part     string  // 路由当前部分children []*node // 孩子节点isWild   bool    // 是否为模糊搜索,当含有":"和通配符"*"时为true
}

当注册路由"/p/:name/doc"、“/p/:name/png”、“/p/:lang/doc”、"/p/:lang/png"后,树中内容如下:

在这里插入图片描述

可以看到,pattern只有在插入最后一个子节点后才会设置,这是为了在查询路由信息时可以根据 pattern==""来判断改路由是否注册。isWaild的作用在于当part不匹配时,如果isWaild为true可以继续搜索,这样就实现了模糊匹配。

先实现路由注册时的前缀树插入逻辑:

func (n *node) insert(pattern string, parts[]string, index int)

pattern是注册路由地址,parts是解析pattern后的字符串数组(使用方法strings.Split(pattern, "/")进行解析)如"/p/:name/doc"对应 [“p”,“:name”,“doc”],parts[index]是当前需要插入的part。可以通过index判断是否退出。(疑问:如果只用Split解析那pattren="/"的时候不就无法注册了吗?答:开始时树的根节点的part为空,不会匹配,“p"一定会插入到根节点的子节点切片中。而当pattern为”/“时解析字符串切片为空,进入根节点的时候len(parts) = index = 0,会将根节点的pattern设置为”/“,也可以实现路由”/"的注册。)

代码如下:

func (n *node) insert(pattern string, parts[]string, index int){// 进来的时候说明 n.part = parts[index-1] 即最后一个 part 则直接设置 pattenif len(parts) == index {n.patten = patternreturn}// 还需匹配 part// 先在 n.children 切片中匹配 partpart := parts[index]child :=  n.matchChild(part)// 如果没有找到,则构建一个 child 并插入 n.children 切片中if child == nil {child = &node{part: part,// 含有":"或者通配符"*"时为 trueisWild: part[0] ==':' || part[0] == '*',}// 插入 n.children 切片n.children = append(n.children, child)}// 递归插入child.insert(pattern, parts, index + 1)
}
// 查找匹配 child
func (n *node) matchChild(part string) *node {// 遍历 n.children 查找 part 相同的 childfor _, child := range n.children {// 如果找到匹配返回 child, 当 isWild 为 true 时视为匹配实现模糊搜索if child.part == part || child.isWild == true {return child}}	// 没找到返回nilreturn nil
}

接下来实现接受请求时查询路由信息时的前缀树搜索逻辑:

func (n *node) search(parts []string, index int) *node

parts是路由地址的解析数组,index指向当前part索引

代码如下:

// 搜索
func (n *node) search(parts []string, index int) *node {// 如果匹配将节点返回if len(parts) == index || strings.HasPrefix(n.part, "*") {if n.pattern == "" {return nil}return n}part := parts[index]// 获取匹配的所有孩子节点nodes := n.matchChildren(part)// 递归搜索匹配的child节点for _, child := range nodes {result := child.search(parts, index+1)if result != nil {return result}}return nil
}
// 查找匹配的孩子节点,由于有":"和"*",所以可能会有多个匹配,因此返回一个节点切片
func (n *node) matchChildren(part string) []*node {nodes := make([]*node, 0)for _, child := range n.children {if child.part == part || child.isWild == true {nodes = append(nodes, child) // 将符合的孩子节点添入返回切片}}return nodes
}

至此trie.go暂时写完,现在在路由中进行应用,回到router.go文件。为了区分不同的方法如GET和POST,为每一个Method建立一颗前缀树,并以键值对的形式存储在一个map中:map[Method] = tire。修改router结构体与构造方法:

type router struct {roots     map[string]*node       // 前缀树mapHandlers map[string]HandlerFunc // 将pattern作为key获取/注册方法
}
func newRouter() *router {return &router{make(map[string]*node),make(map[string]HandlerFunc),}
}

将pattern插入前缀树之前,要先解析成字符串切片,现在需要实现一个解析函数。

func parsePattern(pattern string) []string {temp := strings.Split(pattern, "/")parts := make([]string, 0)for _, item := range temp {if item != ""{parts = append(parts, item)if item[0] == '*' {break}}	}return parts
}

修改注册路由的逻辑:

func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {parts := parsePattern(pattern) // 解析patternkey := method + "-" + patternif _, ok := r.roots[key]; !ok {r.roots[method] = &node{} // 如果没有则创建一个节点}r.roots[method].insert(pattern, parts, 0) // 前缀树插入patternr.Handlers[key] = handler			     // 注册方法
}

当接受请求时,需要对请求中携带的路由信息解析,并获取匹配的节点以及":“,”*"匹配到的参数,现在需要写一个路由获取方法:

func (r *router) getRoute(method string, path string) (*node, map[string]string) {searchParts := parsePattern(path) // 解析路由信息params := make(map[string]string) // 参数字典root, ok := r.roots[method]if !ok {return nil, nil}// 搜索匹配节点n := root.search(searchParts, 0)if n!= nil {parts := parsePattern(n.pattern) // 解析pattern// 寻找'*'和':',找到对应的参数。for index, part := range parts {if part[0] == ':' {params[part[1:]] = searchParts[index]}if part[0] == '*' && len(part) >1 {// 将'*'后切片内容拼接成路径params[part[1:]] = strings.Join(searchParts[index:],"/")break // 仅允许一个通配符'*'}return n, params}}return nil, nil
}

路径中的参数应该交给上下文对象让用户便捷获取。在Context结构体中添加Params属性,并包装获取方法:

type Context struct {Writer     http.ResponseWriterReq        *http.RequestPath       stringMethod     stringParams     map[string]string // 路由参数属性StatusCode int
}
// 获取路径参数
func (c *Context) Param(key string) string {value := c.Params[key]return value
}

在router.go中的handle中应用路由获取方法,并将路径参数提交给上下文对象。

func (r *router) handle(ctx *Context) {n, params := r.getRoute(ctx.Method, ctx.Path) // 获取路由节点及参数字典ctx.Params = paramsif n != nil {key := ctx.Method + "-" + n.pattern // key为n的patternr.Handlers[key](ctx) // 调用注册函数} else {ctx.String(http.StatusNotFound, "404 NOT FOUND: %s\n", ctx.Path)}
}

现在router.go内容为:

package kilonimport ("net/http""strings"
)type router struct {roots    map[string]*nodeHandlers map[string]HandlerFunc
}func newRouter() *router {return &router{make(map[string]*node),make(map[string]HandlerFunc),}
}func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {parts := parsePattern(pattern)key := method + "-" + pattern_, ok := r.roots[method]if !ok {r.roots[method] = &node{}}r.roots[method].insert(pattern, parts, 0)r.Handlers[key] = handler
}func (r *router) handle(ctx *Context) {n, params := r.getRoute(ctx.Method, ctx.Path)ctx.Params = paramsif n != nil {key := ctx.Method + "-" + n.patternr.Handlers[key](ctx)} else {ctx.String(http.StatusNotFound, "404 NOT FOUND: %s\n", ctx.Path)}
}func parsePattern(pattern string) []string {temp := strings.Split(pattern, "/")parts := make([]string, 0)for _, item := range temp {if item != "" {parts = append(parts, item)if item[0] == '*' {break}}}return parts
}func (r *router) getRoute(method string, path string) (*node, map[string]string) {searchParts := parsePattern(path)params := make(map[string]string)root, ok := r.roots[method]if !ok {return nil, nil}n := root.search(searchParts, 0)if n != nil {parts := parsePattern(n.pattern)for index, part := range parts {if part[0] == ':' {params[part[1:]] = searchParts[index]}if part[0] == '*' && len(part) > 1 {params[part[1:]] = strings.Join(searchParts[index:], "/")break}}return n, params}return nil, nil
}

在main.go测试一下:

package mainimport ("kilon""net/http"
)func main() {r := kilon.New()r.GET("/hello", func(ctx *kilon.Context) {ctx.JSON(http.StatusOK, kilon.H{"message": "Hello World",})})r.GET("/hello/:username", func(ctx *kilon.Context) {ctx.JSON(http.StatusOK, kilon.H{"message": ctx.Param("username"),})})r.GET("/hello/:username/*filename", func(ctx *kilon.Context) {ctx.JSON(http.StatusOK, kilon.H{"username": ctx.Param("username"),"filename": ctx.Param("filename"),})})r.Run(":8080")
}

分别访问下面地址,都可以看到响应信息

127.0.0.1:8080/hello

127.0.0.1:8080/hello/zhangsan

127.0.0.1:8080/hello/zhangsan/photo.png

http://www.yayakq.cn/news/394485/

相关文章:

  • 网站制作超链接怎么做工作室注册流程
  • 南屏网站建设做网站公司介绍ppt
  • 如何查公司网站开发时间建立企业网站费用
  • 做室内设计的网站有哪些内容网络营销首先要进行
  • 定制网站建设开发查注册公司什么网站
  • 普宁旅游网站设计方案自己在线制作logo免费模版
  • 上海哪家网站建设好交友app搭建
  • 外贸在哪些网站做建立网站可以赚钱吗
  • 怎么进入企业的网站成都网站制作收费
  • 网站开发者取色工具专业柳州网站建设多少钱
  • 广州网站建设哪里好快速开发平台社区
  • 贵阳网站建设网站制作58同城网站建设
  • 建立旅游公司网站多钱wordpress 原理
  • seo优化培训公司海阳seo排名
  • 刚开始做网站布局很吃力 怎么办wordpress右下角广告代码
  • 淘客单网站永兴县网站建设公司
  • 网站建设案例模板下载网页页脚模板
  • 网站底部怎么做需要放些什么网站策划报告怎么写
  • 黑山网站建设做网站的公司哪家有名
  • 宜春的网站建设公司深圳施工图制作
  • 文化公司网站建设策划书怎样做网络推广效果好
  • wordpress 3.9中文版网络推广优化服务
  • 贵州网站建设价格会计培训班要多少钱
  • 济南兼职做网站外贸退税流程及方法
  • 河南网站开发培训价格wordpress系统升级的风险
  • 好的免费个人网站太原的网站搭建公司
  • 武昌做网站公司建设部网站政策法规
  • 深圳网站建设有免费的吗网站源码大全最新
  • wordpress 网站的占有2020十大热点事件
  • 合肥市建设网站市场信息价茶叶公司网站的建设