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

相关文章

Python开发文字版随机事件游戏的项目实例

《Python开发文字版随机事件游戏的项目实例》随机事件游戏是一种通过生成不可预测的事件来增强游戏体验的类型,在这篇博文中,我们将使用Python开发一款文字版随机事件游戏,通过这个项目,读者不仅能够... 目录项目概述2.1 游戏概念2.2 游戏特色2.3 目标玩家群体技术选择与环境准备3.1 开发环境3

C语言中位操作的实际应用举例

《C语言中位操作的实际应用举例》:本文主要介绍C语言中位操作的实际应用,总结了位操作的使用场景,并指出了需要注意的问题,如可读性、平台依赖性和溢出风险,文中通过代码介绍的非常详细,需要的朋友可以参... 目录1. 嵌入式系统与硬件寄存器操作2. 网络协议解析3. 图像处理与颜色编码4. 高效处理布尔标志集合

Go语言开发实现查询IP信息的MCP服务器

《Go语言开发实现查询IP信息的MCP服务器》随着MCP的快速普及和广泛应用,MCP服务器也层出不穷,本文将详细介绍如何在Go语言中使用go-mcp库来开发一个查询IP信息的MCP... 目录前言mcp-ip-geo 服务器目录结构说明查询 IP 信息功能实现工具实现工具管理查询单个 IP 信息工具的实现服

JAVA保证HashMap线程安全的几种方式

《JAVA保证HashMap线程安全的几种方式》HashMap是线程不安全的,这意味着如果多个线程并发地访问和修改同一个HashMap实例,可能会导致数据不一致和其他线程安全问题,本文主要介绍了JAV... 目录1. 使用 Collections.synchronizedMap2. 使用 Concurren

C 语言中enum枚举的定义和使用小结

《C语言中enum枚举的定义和使用小结》在C语言里,enum(枚举)是一种用户自定义的数据类型,它能够让你创建一组具名的整数常量,下面我会从定义、使用、特性等方面详细介绍enum,感兴趣的朋友一起看... 目录1、引言2、基本定义3、定义枚举变量4、自定义枚举常量的值5、枚举与switch语句结合使用6、枚

go 指针接收者和值接收者的区别小结

《go指针接收者和值接收者的区别小结》在Go语言中,值接收者和指针接收者是方法定义中的两种接收者类型,本文主要介绍了go指针接收者和值接收者的区别小结,文中通过示例代码介绍的非常详细,需要的朋友们下... 目录go 指针接收者和值接收者的区别易错点辨析go 指针接收者和值接收者的区别指针接收者和值接收者的

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

Go 语言中的select语句详解及工作原理

《Go语言中的select语句详解及工作原理》在Go语言中,select语句是用于处理多个通道(channel)操作的一种控制结构,它类似于switch语句,本文给大家介绍Go语言中的select语... 目录Go 语言中的 select 是做什么的基本功能语法工作原理示例示例 1:监听多个通道示例 2:带