Golang web路由实现方式整理总结

水主沉浮 · · 812 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

一、参考博客文章整理

1.0 链接整理集合

  1. 博客文章代码下载地址
  2. Go的http包详解
  3. Go语言经典库使用分析(七)| 高性能可扩展 HTTP 路由 httprouter
  4. golang自定义路由控制实现(一)
  5. golang自定义路由控制实现(二)-流式注册接口以及支持RESTFUL
  6. Golang学习笔记 - 标准库'net/http'的简析及自制简单路由框架
  7. go web框架的路由分析
  8. golang实现的简单http路由器,用于学习理解http.

1.1 链接文章整理

Go的http包详解

通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。

  • 首先调用Http.HandleFunc

按顺序做了几件事:

  1. 调用了DefaultServeMux的HandleFunc
  2. 调用了DefaultServeMux的Handle
  3. 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则
  • 其次调用http.ListenAndServe(":9090", nil)

按顺序做了几件事情:

  1. 实例化Server
  2. 调用Server的ListenAndServe()
  3. 调用net.Listen("tcp", addr)监听端口
  4. 启动一个for循环,在循环体中Accept请求
  5. 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()
  6. 读取每个请求的内容w, err := c.readRequest()
  7. 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux
  8. 调用handler的ServeHttp
  9. 在这个例子中,下面就进入到DefaultServeMux.ServeHttp
  10. 根据request选择handler,并且进入到这个handler的ServeHTTP

mux.handler(r).ServeHTTP(w, r)

  1. 选择handler:
  • A 判断是否有路由能满足这个request(循环遍历ServeMux的muxEntry)
  • B 如果有路由满足,调用这个路由handler的ServeHTTP
  • C 如果没有路由满足,调用NotFoundHandler的ServeHTTP

二、Golang Web路由组件实现方式

2.0 实现方式分类

  1. 原生方式

(1)调用http.HandleFunc(2)调用http.ListenAndServe(":8080", nil)

  1. 路由封装重写ServeHTTP方式
  • 路由存储格式:

(1)map[string]http.HandlerFunc格式,其中的string由method和传入参数拼接字符串组成(2)map[string]map[string]http.HandlerFunc,,其中一维的键String表示请求method比如post, get 等。二维的键string表示要匹配的URL地址, http.HandlerFunc当然就是处理URL请求的具体方法。

  • 重写步骤:

2.1 原生方式实现

//http.go
package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", SayhelloName)
    http.ListenAndServe(":8080", nil)
}

func SayhelloName(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "原生HTTP路由测试页面")
}

/*
package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", SayhelloName)
    log.Fatal(http.ListenAndServe(":8080", nil))
    //http.ListenAndServe(":8080", nil)
}

func SayhelloName(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "原生HTTP路由测试页面")
}
*/

原生方式主要是2个操作,第1步使用http.HandleFunc函数导入对应的方法,第二步使用http.ListenAndServe监听对应的端口

2.2 路由封装重写ServeHTTP方式实现

第1种方式 不存储对应路由规则、直接在ServeHTTP判断对应的路由规则方式

你可以直接使用&结构体方式new一个空对象,来实现:

//第一种方式
package main

import (
    "fmt"
    "net/http"
)

type MyMux struct {
}

func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path == "/" {
        sayhelloName(w, r)
        return
    }
    http.NotFound(w, r)
    return
}

func sayhelloName(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello MyMuxRoute!")
}

func main() {
    mux := &MyMux{}                   //&结构体方式
    http.ListenAndServe(":8080", mux) //mux是核心操作代码
}

第2种方式 你也可以通过通过一个NewMyMux方法来实现这个功能,然后通过调用当前NewMyMux方法来实现,主流Web框架都使用这种方法实现对应的路由功能,NewMyMux方法代码:

func NewMyMux() *MyMux {
    return new(MyMux)
}

完整代码

//第二种方式
package main

import (
    "fmt"
    "net/http"
)

type MyMux struct {
}

func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path == "/" {
        sayhelloName(w, r)
        return
    }
    http.NotFound(w, r)
    return
}

func sayhelloName(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello MyMuxRoute!")
}

//新建的&结构体方法,框架中十分常用
func NewMyMux() *MyMux {
    return &MyMux{} //等同于  return new(MyMux)
}

func main() {
    mux := NewMyMux()                 //通过调用一个方法来实现
    http.ListenAndServe(":8080", mux) //mux是核心代码
}

2.3 路由封装重写ServeHTTP方式继续封装代码

我们继续封装代码,将NewMyMux方法和type MyMux struct以及ServeHTTP封装到一个Router包里面,其他的还是在main包里面,03http.go详细代码:

//第二种方式代码封装
//03http.go
package main

import (
    "GoHTTP/01httpBase/03httpBase/route" //你复制过去的代码,前面的HTTP/01httpBase/03httpBase/根据需要修改
    "net/http"
)

func main() {
    mux := route.NewMyMux()           //通过调用一个方法来实现
    http.ListenAndServe(":8080", mux) //mux是核心代码
}

封装的路由route.go代码:

//route包函数封装
//route.go
package route

import (
    "fmt"
    "net/http"
)

type MyMux struct {
}

//新建的&结构体方法,框架中十分常用
func NewMyMux() *MyMux {
    return &MyMux{} //等同于  return new(MyMux)
}

func sayhelloName(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello MyMuxRoute!")
}

func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    //下面是路由处理,一般web框架会使用一个封装函数统一处理
    if r.URL.Path == "/" {
        sayhelloName(w, r) //这是所有web框架路由的核心代码
        return
    }
    http.NotFound(w, r)
    return
}

我们增加MyMux结构体来存储http.HandleFunc("/", SayhelloName)和重写NewMyMux方法,增加路由的多样性和灵活性,04http.go代码:

//第二种方式代码封装
//增加NewMyMux结构体属性,方便存储http.HandleFunc("/", SayhelloName)路由规则
//04http.go
package main

import (
    "GoHTTP/01httpBase/03httpBase/route" //你复制过去的代码,前面的HTTP/01httpBase/03httpBase/根据需要修改
    "net/http"
)

func main() {
    mux := route.NewMyMux()           //通过调用一个方法来实现
    http.ListenAndServe(":8080", mux) //mux是核心代码
}

route.go代码:

//route包函数封装
//route.go
package route

import (
    "fmt"
    "net/http"
)

/*【对比前代码】
type MyMux struct {
}
*/

type MyMux struct {
    handlers map[string][]*Handler //用于存储http.HandleFunc("/", SayhelloName) 格式的路由规则
}

type Handler struct {
    path string
    http.HandlerFunc
}

/*【对比前代码】
//新建的&结构体方法,框架中十分常用
func NewMyMux() *MyMux {
    return &MyMux{} //等同于  return new(MyMux)
}
*/

func NewMyMux() *MyMux {
    return &MyMux{make(map[string][]*Handler)}
}

func sayhelloName(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello MyMuxRoute!")
}

func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    //下面是路由处理,一般web框架会使用一个封装函数统一处理
    if r.URL.Path == "/" {
        sayhelloName(w, r) //这是所有web框架路由的核心代码
        return
    }
    http.NotFound(w, r)
    return
}

我们继续封装http.ListenAndServe(":8080", mux)功能,提升代码的简洁性,也方便后期其他封装操作

//第二种方式代码封装
//封装http.ListenAndServe(":8080", mux)功能
//05http.go
package main

import (
    "GoHTTP/01httpBase/05httpBase/route" //你复制过去的代码,前面的HTTP/01httpBase/03httpBase/根据需要修改
    //"fmt"
    //"net/http"
)

func main() {
    mux := route.NewMyMux() //通过调用一个方法来实现
    mux.Listen(":8080")     //http.ListenAndServe(":8080", mux)
}

route.go代码详情:

//route包函数封装
//route.go
package route

import (
    "fmt"
    "log"
    "net/http"
)

type MyMux struct {
    handlers map[string][]*Handler //用于存储http.HandleFunc("/", SayhelloName) 格式的路由规则
}

type Handler struct {
    path string
    http.HandlerFunc
}

//开启http服务
func (m *MyMux) Listen(port string) {
    err := http.ListenAndServe(port, m)
    if err != nil {
        log.Fatal("开启http服务错误!")
    }
}

func NewMyMux() *MyMux {
    return &MyMux{make(map[string][]*Handler)}
}

func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    //下面是路由处理,一般web框架会使用一个封装函数统一处理
    if r.URL.Path == "/" {
        sayhelloName(w, r) //这是所有web框架路由的核心代码
        return
    }
    http.NotFound(w, r)
    return
}

func sayhelloName(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello MyMuxRoute!")
}

我们继续封装存储路由功能和重写ServeHTTP()功能,增加路由多样性和灵活性,方便后期存储Rest格式接口

//第二种方式代码封装
//封装AddRoute添加路由功能、重写ServeHTTP功能
//06http.go
package main

import (
    "GoHTTP/01httpBase/06httpBase/route" //你复制过去的代码,前面的HTTP/01httpBase/03httpBase/根据需要修改
    "fmt"
    "net/http"
)

func main() {
    r := route.NewMyMux() //通过调用一个方法来实现
    r.AddRoute("GET", "/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello Get!")
    })
    //mux.Listen(":8080")
    http.ListenAndServe(":8080", r)
}
//route包函数封装
//route.go
package route

import (
    "fmt"
    "log"
    "net/http"
    "strings"
)

type MyMux struct {
    handlers map[string][]*Handler //用于存储http.HandleFunc("/", SayhelloName) 格式的路由规则
}

type Handler struct {
    path string
    http.HandlerFunc
}

//开启http服务
func (m *MyMux) Listen(port string) {
    err := http.ListenAndServe(port, m)
    if err != nil {
        log.Fatal("开启http服务错误!")
    }
}

func NewMyMux() *MyMux {
    return &MyMux{make(map[string][]*Handler)}
}

//添加路由
func (m *MyMux) AddRoute(mode string, path string, fun http.HandlerFunc) {
    m.add(mode, path, fun)
}

//添加路由
/*mode  Post|Get|Put|Delete
 *path  前缀
 *fun    方法
 */
func (m *MyMux) add(mode, path string, fun http.HandlerFunc) {
    h := &Handler{
        strings.ToLower(path),
        fun}

    //fmt调试代码可以删除
    fmt.Println("h ::", &h)
    fmt.Println("strings.ToLower(path) ::", strings.ToLower(path))
    fmt.Println("strings.ToLower(mode) ::", strings.ToLower(mode))

    //下面是存储路由的核心代码,这里的路由m.handlers存储的格式是Get|Post|Put|Delete:String:http.HandlerFunc
    m.handlers[strings.ToLower(mode)] = append(
        m.handlers[strings.ToLower(mode)],
        h,
    )
    fmt.Println("m.handlers", m.handlers[strings.ToLower(mode)])
}

//优化前代码
/*
func (p *MyMux) ServeHTTP2(w http.ResponseWriter, r *http.Request) {
    //下面是路由处理,一般web框架会使用一个封装函数统一处理
    if r.URL.Path == "/" {
        sayhelloName(w, r) //这是所有web框架路由的核心代码
        return
    }
    http.NotFound(w, r)
    return
}
*/

//进行路由分配

func (m *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    //处理静态文件
    url := strings.Split(strings.TrimLeft(r.URL.Path, "/"), "/")

    //调试代码,可以直接删除
    fmt.Println("r", fmt.Sprintf("%+v", r))
    fmt.Println("w", fmt.Sprintf("%+v", w))

    for _, handler := range m.handlers[strings.ToLower(r.Method)] {
        if handler.path == "/"+strings.ToLower(url[0]) {
            handler.ServeHTTP(w, r) //调用的是func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
            return
        }
    }
    http.NotFound(w, r)
    return
}

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:水主沉浮

查看原文:Golang web路由实现方式整理总结

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

812 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传