GO语言web开发的几种Mux库阅读

2024-02-03 02:58

本文主要是介绍GO语言web开发的几种Mux库阅读,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

DefaultServeMux阅读

可以查看到ServeMux结构的定义如下:

type ServeMux struct {mu    sync.RWMutexm     map[string]muxEntryhosts bool 
}type muxEntry struct {explicit boolh        Handlerpattern  string
}

ServeMux结构中最重要的字段为map[string]muxEntry类型的m,这是一个map,key为表示url模式的string,value则是muxEntry结构。可以看到muxEntry结构中存储了表示具体的url模式的string和handler,以及是否explicit的bool类型。由于ServeMux也实现了ServeHTTP函数,其也可以看成是一个handler接口,与之前ServeHTTP函数处理http请求并产生http响应不同,ServeMux的ServeHTTP方法是用来找到路由对应的handler的。

ServeMux的任务是将用户请求中 path 映射到 Handler,由于其可能会被并发访问,因此其还有一个子段mu作为sync.RWMutex类型,也就是读写互斥锁,其保证不同go程对该线程不安全变量的互斥访问。

可以查看到ServeMux的Match函数如下:

// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
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, ""
}

可以看到,其首先检查mux.m中是否含有path对应的精确匹配,如果有的话就直接返回对应的handler和pattern。否则则遍历所有的patterns,由于patterns首先按照长度从大到小排序,因此其会找到具有最长匹配前缀的pattern并将其和对应的handler返回。这相比过去版本的match有了优化,可以更快地找到第一个具有最长匹配的pattern。

而Handle函数可以用于注册路由,可以看到其实现如下:

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {mux.mu.Lock()defer mux.mu.Unlock()if pattern == "" {panic("http: invalid pattern")}if handler == nil {panic("http: nil handler")}if _, exist := mux.m[pattern]; exist {panic("http: multiple registrations for " + pattern)}if mux.m == nil {mux.m = make(map[string]muxEntry)}e := muxEntry{h: handler, pattern: pattern}mux.m[pattern] = eif pattern[len(pattern)-1] == '/' {mux.es = appendSorted(mux.es, e)}if pattern[0] != '/' {mux.hosts = true}
}

正如之前所述,由于ServeMux不是线程安全的,因此Handle函数首先将ServerMux加上互斥锁,并在函数处理完后解开互斥锁。之后其检查pattern和handler是否是合法且是没有注册过的,如果是的话则将其加入到mux.m这个map中(如果还没有申请mux.m就创建)。之后按照长度的顺序将其插入到es中,使得插入后es仍然有序。

然后可以查看到handler方法:

// 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 onesif mux.hosts {h, pattern = mux.match(host + path)}if h == nil {h, pattern = mux.match(path)}if h == nil {h, pattern = NotFoundHandler(), ""}return
}

同样,其首先上互斥锁,然后调用match检查是否存在host+path路径,如果有就返回对应的h和pattern,否则再调用match检查是否存在path路径,如果有也返回对应的h和pattern。否则就进入NotFoundHandler的处理。

// NotFoundHandler returns a simple request handler
// that replies to each request with a ``404 page not found'' reply.
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }

NotFoundHandler如上吗,将NotFound作为参数,使用HandlerFunc函数处理,也就是处理了404错误的情况。

经过上面的分析,可以看到DefaultServeMux实现了对用户请求url进行路由的功能。可以看到,DefaultServeMux支持url的精确匹配和最长前缀匹配。但由于路由的时候还需要一条条检查所有项目来匹配,因此在路由条目比较多的时候可能还是比较慢。为了加快速度,可以像其他一些后端路由一样采取基于Trie前缀树(用/分隔)等层次匹配的算法,可以使得最长查找次数为树的高度。此外,DefaultServeMux也不支持Method,header,host等信息匹配以及正则表达式的url匹配。

gorilla/mux阅读

进一步对比阅读gorilla/mux代码。可以看到gorilla/mux比DefaultServeMux实现了更强大的功能,包括正则表达式匹配以及Method,header,host等信息的匹配,利用gorilla/mux可以更方便地开发server程序。

可以看到其定义Route如下,Route结构存储了匹配请求的handler和name等结构其还有着map[string]类型指针,指向全局索引所有命名Routes的结构。

// Route stores information to match a request and build URLs.
type Route struct {// Request handler for the route.handler http.Handler// If true, this route never matches: it is only used to build URLs.buildOnly bool// The name used to build URLs.name string// Error resulted from building a route.err error// "global" reference to all named routesnamedRoutes map[string]*Route// config possibly passed in from `Router`routeConf
}

Router的代码则如下:

type Router struct {// Configurable Handler to be used when no route matches.NotFoundHandler http.Handler// Configurable Handler to be used when the request method does not match the route.MethodNotAllowedHandler http.Handler// Routes to be matched, in order.routes []*Route// Routes by name for URL building.namedRoutes map[string]*Route// If true, do not clear the request context after handling the request.//// Deprecated: No effect, since the context is stored on the request itself.KeepContext bool// Slice of middlewares to be called after a match is foundmiddlewares []middleware// configuration shared with `Route`routeConf
}

可以看到,其包括特定的NotFoundHandler 和MethodNotAllowedHandler 字段来处理页面没有找到,和方法不允许的通常情况,这两个handler也是http.handler类型。而其存储路由条目则是用一个[]*Route存储,同时还有一个map[string]*Route类型根据名称索引到对应路由。

其Match函数与net/http包中的不同,其首先遍历所有routes,然后调用route.Match看能否匹配,如果可以匹配就检查如果在没有错误的情况下,添加各个中间件作为回调函数链。这样可以支持服务端通过中间件实现更强大的路由功能。之后其检查方法如果不匹配的话,就用MethodNotAllowedHandler进行处理,最后检查如果是没有找到页面的话,就用NotFoundHandler 进行处理。

func (r *Router) Match(req *http.Request, match *RouteMatch) bool {for _, route := range r.routes {if route.Match(req, match) {// Build middleware chain if no error was foundif match.MatchErr == nil {for i := len(r.middlewares) - 1; i >= 0; i-- {match.Handler = r.middlewares[i].Middleware(match.Handler)}}return true}}if match.MatchErr == ErrMethodMismatch {if r.MethodNotAllowedHandler != nil {match.Handler = r.MethodNotAllowedHandlerreturn true}return false}// Closest match for a router (includes sub-routers)if r.NotFoundHandler != nil {match.Handler = r.NotFoundHandlermatch.MatchErr = ErrNotFoundreturn true}match.MatchErr = ErrNotFoundreturn false
}

进一步查看route.Match函数的代码如下:

// Match matches the route against the request.
func (r *Route) Match(req *http.Request, match *RouteMatch) bool {if r.buildOnly || r.err != nil {return false}var matchErr error// Match everything.for _, m := range r.matchers {if matched := m.Match(req, match); !matched {if _, ok := m.(methodMatcher); ok {matchErr = ErrMethodMismatchcontinue}// Ignore ErrNotFound errors. These errors arise from match call// to Subrouters.//// This prevents subsequent matching subrouters from failing to// run middleware. If not ignored, the middleware would see a// non-nil MatchErr and be skipped, even when there was a// matching route.if match.MatchErr == ErrNotFound {match.MatchErr = nil}matchErr = nilreturn false}}if matchErr != nil {match.MatchErr = matchErrreturn false}if match.MatchErr == ErrMethodMismatch && r.handler != nil {// We found a route which matches request method, clear MatchErrmatch.MatchErr = nil// Then override the mis-matched handlermatch.Handler = r.handler}// Yay, we have a match. Let's collect some info about it.if match.Route == nil {match.Route = r}if match.Handler == nil {match.Handler = r.handler}if match.Vars == nil {match.Vars = make(map[string]string)}// Set variables.r.regexp.setMatch(req, match, r)return true
}

可以看到,其首先检查buildOnly和err,在buildOnly为假和err为nil的情况下,遍历r.matchers ,调用m.Match检查是否方法匹配,如果发生错误就进行错误处理,直到找到匹配的match。之后就收集相关信息,设定对应的Route和handler,以及参数Vars,之后通过正则表达式的setMatch匹配相应正则表达式中的参数。

然后可以查看Vars函数如下,其取出http请求相关联的变量的信息。该函数从上下文中获得对应的varsKey,将存放该请求对应的变量值集合的string映射到string的map类型返回。

func Vars(r *http.Request) map[string]string {if rv := r.Context().Value(varsKey); rv != nil {return rv.(map[string]string)}return nil
}

还可以查看handler的实现如下,HandlerFunc调用Route的Handler函数字段对http.HandlerFunc(f)返回的带handler的Route进行处理。而Handler函数则设置了Route的handler并将其返回。

func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {return r.Handler(http.HandlerFunc(f))
}
// Handler sets a handler for the route.
func (r *Route) Handler(handler http.Handler) *Route {if r.err == nil {r.handler = handler}return r
}

查看Router的ServeHTTP函数如下,其处理http的请求,并在http.ResponseWriter类型参数中写入响应报文。其首先在r.skipClean为假的情况下获得请求的url路径,之后在r.useEncodedPath为真的情况下调用EscapedPath()对路径进行处理。之后其清理路径到规范的形式并重定向。之后就检查是否有match和请求相匹配,匹配的话就设定handler为match的Handler字段,以及设置请求包括match的参数,路由等。之后看是否需要用到methodNotAllowedHandler和NotFoundHandler,最终让handler处理请求。

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {if !r.skipClean {path := req.URL.Pathif r.useEncodedPath {path = req.URL.EscapedPath()}// Clean path to canonical form and redirect.if p := cleanPath(path); p != path {// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.// This matches with fix in go 1.2 r.c. 4 for same problem.  Go Issue:// http://code.google.com/p/go/issues/detail?id=5252url := *req.URLurl.Path = pp = url.String()w.Header().Set("Location", p)w.WriteHeader(http.StatusMovedPermanently)return}}var match RouteMatchvar handler http.Handlerif r.Match(req, &match) {handler = match.Handlerreq = requestWithVars(req, match.Vars)req = requestWithRoute(req, match.Route)}if handler == nil && match.MatchErr == ErrMethodMismatch {handler = methodNotAllowedHandler()}if handler == nil {handler = http.NotFoundHandler()}handler.ServeHTTP(w, req)
}

这篇关于GO语言web开发的几种Mux库阅读的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/672791

相关文章

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

go中的时间处理过程

《go中的时间处理过程》:本文主要介绍go中的时间处理过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 获取当前时间2 获取当前时间戳3 获取当前时间的字符串格式4 相互转化4.1 时间戳转时间字符串 (int64 > string)4.2 时间字符串转时间

Go语言中make和new的区别及说明

《Go语言中make和new的区别及说明》:本文主要介绍Go语言中make和new的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 概述2 new 函数2.1 功能2.2 语法2.3 初始化案例3 make 函数3.1 功能3.2 语法3.3 初始化

Go语言中nil判断的注意事项(最新推荐)

《Go语言中nil判断的注意事项(最新推荐)》本文给大家介绍Go语言中nil判断的注意事项,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.接口变量的特殊行为2.nil的合法类型3.nil值的实用行为4.自定义类型与nil5.反射判断nil6.函数返回的

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

在Golang中实现定时任务的几种高效方法

《在Golang中实现定时任务的几种高效方法》本文将详细介绍在Golang中实现定时任务的几种高效方法,包括time包中的Ticker和Timer、第三方库cron的使用,以及基于channel和go... 目录背景介绍目的和范围预期读者文档结构概述术语表核心概念与联系故事引入核心概念解释核心概念之间的关系

SpringBoot开发中十大常见陷阱深度解析与避坑指南

《SpringBoot开发中十大常见陷阱深度解析与避坑指南》在SpringBoot的开发过程中,即使是经验丰富的开发者也难免会遇到各种棘手的问题,本文将针对SpringBoot开发中十大常见的“坑... 目录引言一、配置总出错?是不是同时用了.properties和.yml?二、换个位置配置就失效?搞清楚加

Go语言代码格式化的技巧分享

《Go语言代码格式化的技巧分享》在Go语言的开发过程中,代码格式化是一个看似细微却至关重要的环节,良好的代码格式化不仅能提升代码的可读性,还能促进团队协作,减少因代码风格差异引发的问题,Go在代码格式... 目录一、Go 语言代码格式化的重要性二、Go 语言代码格式化工具:gofmt 与 go fmt(一)

如何使用Maven创建web目录结构

《如何使用Maven创建web目录结构》:本文主要介绍如何使用Maven创建web目录结构的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录创建web工程第一步第二步第三步第四步第五步第六步第七步总结创建web工程第一步js通过Maven骨架创pytho

Java Web实现类似Excel表格锁定功能实战教程

《JavaWeb实现类似Excel表格锁定功能实战教程》本文将详细介绍通过创建特定div元素并利用CSS布局和JavaScript事件监听来实现类似Excel的锁定行和列效果的方法,感兴趣的朋友跟随... 目录1. 模拟Excel表格锁定功能2. 创建3个div元素实现表格锁定2.1 div元素布局设计2.