首页 > 其他分享 >go http路由处理流程

go http路由处理流程

时间:2022-12-17 18:36:20浏览次数:35  
标签:http Handler mux handler ServeMux func go 路由

(1)type Handler

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

该接口用于开发者能够实现自己的Handler,只要实现ServeHTTP(ResponseWriter, *Request)方法即可

实现了ServeHTTP方法的结构都能够称之为handler对象,ServeMux会使用handler(如它的函数func (*ServeMux) Handle)并调用其ServeHTTP方法处理请求并返回响应。 

(2)type HandlerFunc

type HandlerFunc func(ResponseWriter, *Request)

HandlerFunc 实现了ServeHTTP(w http.ResponseWrite, r *http.Request),因此可以将我们自己实现

func(w http.ResponseWrite, r *http.Request)类型的函数转化为实现了ServeHTTP的类型函数。

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

而ServeHTTP其实是调用f本身。

(3)type ServeMux--多路复用器

type ServeMux struct {
    mu    sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
    m     map[string]muxEntry //路由规则,一个string对应一个mux实体,这里的string就是注册的路由
    hosts bool // whether any patterns contain hostnames
}
type muxEntry struct{
    explicit bool //是否精确匹配
    h          Handler //这个路由表达式对应哪个handler
}

ServeMux为什么被称为多路复用器,其实是一个分发器,ServeMux也实现了ServeHTTP,只不过它的作用是找到收到的请求对应的处理函数。

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

举个例子:

请求来了-->被ServeMux收到-->ServeMux调用它实现的ServeHTTP函数来找到这个请求对应的处理函数(也就是在ServeMux的结构体成员map[string]muxEntry中找到-->然后调用这个处理函数。

func NewServeMux

func NewServeMux() *ServeMux  //NewServeMux创建并返回一个新的*ServeMux
var DefaultServeMux = NewServeMux()  //DefaultServeMux是用于Serve的默认ServeMux。

func (*ServeMux) Handle

(mux *ServeMux) Handle(pattern string, handler Handler)
Handle注册HTTP处理器handler和对应的模式pattern。如果该模式已经注册有一个处理器,Handle会panic。

func (*ServeMux) HandleFunc

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
//HandleFunc注册一个处理器函数handler和对应的模式pattern,其实至始至终就调用了上面的Handle
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

既然说完了ServeMux的注册路由函数,就先举个例子:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/hello", Hello)
    server := &http.Server{
        Addr:    "127.0.0.1:8080",
        Handler: mux,
    }
    server.ListenAndServe()
}

func Hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello ", r.URL.Path)
}

请求(Path:"/hello")-->mux在自己的map中找Path对应的Handler(mux调用HandelFunc()注册到map中)

-->找到后调用。

注意上面调用的mux.HandleFunc是由mux调用的,而平时我们使用的是

 http.HandleFunc("/", sayhelloName) 
 http.Handle("/", sayhelloName) 

http在包中声明并定义了一个ServeMux,名字叫DefaultServeMux,然后又声明了HandleFunc和Handle函数。

func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

func ListenAndServe

func ListenAndServe(addr string, handler Handler) error

该代码的执行过程是:

1.首先调用http.HandleFunc时:

  • 调用了DefaultServeMux的HandleFunc
  • 然后调用了DefaultServeMux的Handle
  • 接着就是往DefaultServeMux的map[string]muxEntry中添加请求URL对应的路由规则,并且路由规则中存储对应的handler处理器

2.其次就是调用http.ListenAndServe(":12345", nil):

  • 首先实例化Server
  • 然后调用Server.ListenAndServe()
  • 再调用net.Listen("tcp", addr)监听端口启动一个for循环,在循环体中Accept请求
  • 然后对每个请求都实例化一个Conn,即srv.newConn(rw);并开启一个goroutine为这个请求进行服务go c.serve()
  • 读取每个请求的内容 w, err := c.readRequest()
  • 然后判断handler是否为空,如果没有设置handler(即第二个参数为nil时),handler就设置为DefaultServeMux
  • 然后要选择DefaultServeMux中合适的handler,即要判断是否有路由能够满足这个request(循环遍历ServerMux的muxEntry)。如果有路由满足,则调用该handler的ServeHTTP;如果没有路由满足,则调用NotFoundHandler的ServeHTTP

 

func (*ServeMux) Handler

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)

Handler根据r.Method、r.Host和r.URL.Path等数据,返回将用于处理该请求的HTTP处理器。它总是返回一个非nil的处理器。如果路径不是它的规范格式,将返回内建的用于重定向到等价的规范路径的处理器。

Handler也会返回匹配该请求的的已注册模式;在内建重定向处理器的情况下,pattern会在重定向后进行匹配。如果没有已注册模式可以应用于该请求,本方法将返回一个内建的"404 page not found"处理器和一个空字符串模式。

 其源代码为:

复制代码
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

    // CONNECT requests are not canonicalized.
    if r.Method == "CONNECT" {
        // If r.URL.Path is /tree and its handler is not registered,
        // the /tree -> /tree/ redirect applies to CONNECT requests
        // but the path canonicalization does not.
        if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
            return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
        }

        return mux.handler(r.Host, r.URL.Path)
    }

    // All other requests have any port stripped and path cleaned
    // before passing to mux.handler.
    host := stripHostPort(r.Host)
    path := cleanPath(r.URL.Path)

    // If the given path is /tree and its handler is not registered,
    // redirect for /tree/.
    if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
        return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
    }

    if path != r.URL.Path {
        _, pattern = mux.handler(host, path)
        url := *r.URL
        url.Path = path
        return RedirectHandler(url.String(), StatusMovedPermanently), pattern
    }

    return mux.handler(host, r.URL.Path)
}

// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
    mux.mu.RLock()
    defer mux.mu.RUnlock()

    // Host-specific pattern takes precedence over generic ones
    if mux.hosts {
        h, pattern = mux.match(host + path)
    }
    if h == nil {
        h, pattern = mux.match(path)
    }
    if h == nil {
        h, pattern = NotFoundHandler(), ""
    }
    return
}

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    // Check for exact match first.
    v, ok := mux.m[path] //匹配路由规则
    if ok {
        return v.h, v.pattern
    }

    // Check for longest valid match.  mux.es contains all patterns
    // that end in / sorted from longest to shortest.
    for _, e := range mux.es {
        if strings.HasPrefix(path, e.pattern) {
            return e.h, e.pattern
        }
    }
    return nil, ""
}
复制代码

那么当路由器ServeMux里面存储好了相应的路由规则m后,具体的请求又是怎么分发的呢:

  • 当路由器ServeMux接收到请求request后就会调用
handler, _ : = mux.Handler(request)
handler.ServeHTTP(w, request)
  • 由上面源码中可见调用mux.Handler(request)的代码中又调用了mux.handler(host, r.URL.Path),他就是使用用户请求的URL和路由器中的路由规则map相匹配,匹配成功后返回map中的handler值
  • 然后再调用该handler的ServeHTTP即可

 


5)Server

type Server

复制代码
type Server struct {
    Addr           string        // 监听的TCP地址,如果为空字符串会使用":http"
    Handler        Handler       // 调用的处理器,如为nil会调用http.DefaultServeMux
    ReadTimeout    time.Duration // 请求的读取操作在超时前的最大持续时间
    WriteTimeout   time.Duration // 回复的写入操作在超时前的最大持续时间
    MaxHeaderBytes int           // 请求的头域最大长度,如为0则用DefaultMaxHeaderBytes
    TLSConfig      *tls.Config   // 可选的TLS配置,用于ListenAndServeTLS方法
    // TLSNextProto(可选地)指定一个函数来在一个NPN型协议升级出现时接管TLS连接的所有权。
    // 映射的键为商谈的协议名;映射的值为函数,该函数的Handler参数应处理HTTP请求,
    // 并且初始化Handler.ServeHTTP的*Request参数的TLS和RemoteAddr字段(如果未设置)。
    // 连接在函数返回时会自动关闭。
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
    // ConnState字段指定一个可选的回调函数,该函数会在一个与客户端的连接改变状态时被调用。
    // 参见ConnState类型和相关常数获取细节。
    ConnState func(net.Conn, ConnState)
    // ErrorLog指定一个可选的日志记录器,用于记录接收连接时的错误和处理器不正常的行为。
    // 如果本字段为nil,日志会通过log包的标准日志记录器写入os.Stderr。
    ErrorLog *log.Logger
    // 内含隐藏或非导出字段
}
复制代码

Server类型定义了运行HTTP服务端的参数。Server的零值是合法的配置。

func (*Server) Serve

func (srv *Server) Serve(l net.Listener) error

Serve会接手监听器l收到的每一个连接,并为每一个连接创建一个新的服务go程。该go程会读取请求,然后调用srv.Handler回复请求。

func (*Server) ListenAndServe

func (srv *Server) ListenAndServe() error

ListenAndServe监听srv.Addr指定的TCP地址,并且会调用Serve方法接收到的连接。如果srv.Addr为空字符串,会使用":http"。

func (*Server) ListenAndServeTLS

func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error

ListenAndServeTLS监听srv.Addr确定的TCP地址,并且会调用Serve方法处理接收到的连接。必须提供证书文件和对应的私钥文件。如果证书是由权威机构签发的,certFile参数必须是顺序串联的服务端证书和CA证书。如果srv.Addr为空字符串,会使用":https"。

举例:

复制代码
package main 
import(
    "fmt"
    "net/http"
    "time"
)

func sayhelloName(w http.ResponseWriter, req *http.Request){
    fmt.Fprintf(w, "hello web server") //将字符串写入到w,即在客户端输出
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", sayhelloName) //设置访问的路由
    // err := http.ListenAndServe(":9090", nil) //设置监听的端口
    // if err != nil {
    //     log.Fatal("ListenAndServe : ", err)
    // }
    //如果使用的是Server,则等价于:
    server := &http.Server{
        Addr: ":8000",
        ReadTimeout: 60 * time.Second,
        WriteTimeout: 60 * time.Second,
        Handler: mux,
    }
    server.ListenAndServe()
}
复制代码

返回:

func (*Server) SetKeepAlivesEnabled

func (s *Server) SetKeepAlivesEnabled(v bool)

SetKeepAlivesEnabled控制是否允许HTTP闲置连接重用(keep-alive)功能。默认该功能总是被启用的。只有资源非常紧张的环境或者服务端在关闭进程中时,才应该关闭该功能。

标签:http,Handler,mux,handler,ServeMux,func,go,路由
From: https://www.cnblogs.com/dadishi/p/16989314.html

相关文章

  • 与Google一起构建芯片
    Google 介绍,这是 Google 与 SkyWater Technology 晶圆代工厂、IC 设计业者 Efabless 合作,提供完全开源的工艺设计套件 (PDK) 与工具,任何开发人员都能设计芯片。......
  • Gorm源码学习-创建行记录
    1.前言Gorm源码学习系列Gorm源码学习-数据库连接此文是Gorm源码学习系列的第二篇,主要梳理下通过Gorm创建表的流程。 2.创建行记录代码示例gorm提供了以下几个接......
  • Http 请求头与响应头 全部字段
    Http请求头:Accept 浏览器通过这个头,告诉服务器它所支持的数据类型Accept-Charset浏览器通过这个头,告诉服务器它采用的字符集Accept-Encoding浏览器通过这个头,告诉服......
  • 三步快速搭建Typora图床(SM.MS+PicGo)
    三步快速搭建Typora图床(基于SM.MS+PicGo)前言在有些同学使用Typora的过程中,会发现Typora不像Word一样,在文档脱离本机后依然正常显示图片,自己的tyopora文件在发给别人就......
  • laravel代码优化,使用路由中间件来处理数据返回和端口请求速率
    2022年12月17日14:47:22之前代码一直是使用trait来处理返回,但是如果遇到不熟悉代码系统设计的人就麻烦了,就想着能不能使用路由中间件来处理所有问题traitResponseTrait......
  • vue2 路由24 声明式导航 编程式导航 路由守卫
    用户点击了页面的路由链接,会导致hash值发生变化,路由监听到hash值的链接发生的变化,会把对应的组件渲染到当前的页面中   安装:直接安装router的话会安装最新版的,最新......
  • golang的标准包---strconv
    字符串和基本数据类型之间转换这里的基本数据类型包括:布尔、整型(包括有/无符号、二进制、八进制、十进制和十六进制)和浮点型等。包转换错误处理介绍具体的转换之前,先看看......
  • golang的标准库---bytes
    bytes—byteslice便利操作该包定义了一些操作byteslice的便利操作。因为字符串可以表示为[]byte,因此,bytes包定义的函数、方法等和strings包很类似,所以讲解时会和......
  • 2022.12.17 - vue Router4 关于嵌套路由配置无匹配页面导致失效问题
    constroutes=[ { path:'', name:'home', component:()=>import(/*webpackChunkName:"home"*/'../pages/index'), children:[ { path:'', ......
  • 谷歌上做SEO价钱大概多少,Google优化怎么收费?
    很多做外贸的老板一直头疼,自己组建团队去做优化跟外包其实在成本上有巨大的差距,所以目前谷歌seo代运营很火,关键是你找谁做,市场有没有性价比高的SEO服务?收费如何?答案是:非标......