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 语言中的select语句详解及工作原理

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

C语言函数递归实际应用举例详解

《C语言函数递归实际应用举例详解》程序调用自身的编程技巧称为递归,递归做为一种算法在程序设计语言中广泛应用,:本文主要介绍C语言函数递归实际应用举例的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录前言一、递归的概念与思想二、递归的限制条件 三、递归的实际应用举例(一)求 n 的阶乘(二)顺序打印

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

CSS去除a标签的下划线的几种方法

《CSS去除a标签的下划线的几种方法》本文给大家分享在CSS中,去除a标签(超链接)的下划线的几种方法,本文给大家介绍的非常详细,感兴趣的朋友一起看看吧... 在 css 中,去除a标签(超链接)的下划线主要有以下几种方法:使用text-decoration属性通用选择器设置:使用a标签选择器,将tex

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

JSON Web Token在登陆中的使用过程

《JSONWebToken在登陆中的使用过程》:本文主要介绍JSONWebToken在登陆中的使用过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录JWT 介绍微服务架构中的 JWT 使用结合微服务网关的 JWT 验证1. 用户登录,生成 JWT2. 自定义过滤