开发 web 程序服务 之 源码分析
文章目录
- 开发 web 程序服务 之 源码分析
- 前言
- http 包源码
- 路由部分
- 监听和服务部分
- mux 库源码
- 源码分析
- 创建路由
- 路由匹配
- 总结
开发 web 程序服务 之 源码分析
前言
本文的内容
介绍 http 包,解释一些关键功能的实现
选择 mux 库,通过源码分析、解释它是如何实现扩展原理的,包括一些 golang 程序的设计技巧
http 包源码
路由部分
最主要的函数是
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {mux.Handle(pattern, HandlerFunc(handler))
}
该函数主要实现了将传入的处理相应函数与相应的路径进行匹配
HandleFunc 在 DefaultServeMux 中注册给定模式的处理程序函数。ServeMux 的文档解释了如何匹配模式。
Handle()
函数中进行了路由的匹配,并存放了路由的匹配规则
type ServeMux struct {mu sync.RWMutex //锁机制,因为请求会涉及到并发处理m map[string]muxEntry //路由规则,使用map将string对应mux实体,这里的string是注册的路由表达式hosts bool //是否在任意的规则中带有host信息
}
type muxEntry struct {explicit bool //是否精确匹配h Handler //这个路由表达式对应的处理响应函数pattern string //匹配字符串
}
根据HandleFunc()
中的代码,执行 Handle()
,这个函数对传入的路径进行解析,然后向ServeMux
中添加路由规则
对应的路由规则在函数 match()
中,该函数实现了解释了匹配最长的最佳匹配
func (mux *ServeMux) match(path string) (h Handler, pattern string) {var n = 0for k, v := range mux.m {if !pathMatch(k, path) {continue}//如果匹配到了一个规则,还会继续匹配并且判断path的长度是否最长if h == nil || len(k) > n {n = len(k)h = v.hpattern = v.pattern}}return
}
监听和服务部分
该部分主要的函数为:
func ListenAndServe(addr string, handler Handler) error
func ListenAndServe(addr string, handler Handler) error {server := &Server{Addr: addr, Handler: handler}return server.ListenAndServe()
}
该函数首先对实例化 Serve,然后调用了函数 Server.ListenAndServe()
func (srv *Server) ListenAndServe() error {addr := srv.Addrif addr == "" {addr = ":http"}ln, err := net.Listen("tcp", addr) if err != nil {return err}return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
该函数首先调用了监听端口,然后调用了 srv.Serve
for {rw, e := l.Accept()if e != nil {select {case <-srv.getDoneChan():return ErrServerCloseddefault:}if ne, ok := e.(net.Error); ok && ne.Temporary() {if tempDelay == 0 {tempDelay = 5 * time.Millisecond} else {tempDelay *= 2}if max := 1 * time.Second; tempDelay > max {tempDelay = max}srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)time.Sleep(tempDelay)continue}return e}tempDelay = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew) // before Serve can returngo c.serve(ctx)}
该函数只要是一个 for 循环,然后再循环体中接受请求,对于每个请求,都进行实例化,并且开启一个 go 程来进行服务。体现了高并发和高性能
客户端请求进行服务的时候会调用 ServeHttp()
:
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {handler := sh.srv.Handlerif handler == nil {handler = DefaultServeMux}if req.RequestURI == "*" && req.Method == "OPTIONS" {handler = globalOptionsHandler{}}handler.ServeHTTP(rw, req)
}
上面函数的 handler 是一个接口,如一开始的web服务器代码中,虽然我们并没有实现ServeHTTP()
,但是在http包里面还定义了一个类型HandlerFunc
,这个类型默认就实现了ServeHTTP()
,在调用http.HandleFunc()
的时候已经将自定义的handler处理函数强制转为HandlerFunc
类型
mux 库源码
源码分析
最开始会有 NewRouter()
函数,它实例化了Router
,并且将 KeepContext
设置为false:
// NewRouter returns a new router instance.
func NewRouter() *Router {return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
}
然后进行查看 Route
结构体中的成员变量:
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// Parent route, if this is a subrouter.parent parentRoute// Routes to be matched, in order.routes []*Route// Routes by name for URL building.namedRoutes map[string]*Route// See Router.StrictSlash(). This defines the flag for new routes.strictSlash bool// See Router.SkipClean(). This defines the flag for new routes.skipClean bool// If true, do not clear the request context after handling the request.// This has no effect when go1.7+ is used, since the context is stored// on the request itself.KeepContext bool// see Router.UseEncodedPath(). This defines a flag for all routes.useEncodedPath bool// Slice of middlewares to be called after a match is foundmiddlewares []middleware
}
创建路由
然后调用 Path()
函数,该函数用 NewRoute()
创建了一个新路由,并且调用新路由对象的 Path()
,也可以直接使用 HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
func (r *Router) Path(tpl string) *Route {return r.NewRoute().Path(tpl)
}func (r *Router) NewRoute() *Route {route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}r.routes = append(r.routes, route)return route
}func (r *Route) Path(tpl string) *Route {r.err = r.addRegexpMatcher(tpl, false, false, false)return r
}func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,*http.Request)) *Route {return r.NewRoute().Path(path).HandlerFunc(f)
}
在 Path()
中调用的 addRegexpMatcher()
函数,实现了根据传入的tpl创建正则表达式的匹配以及调用 newRouteRegexp()
方法。newRouteRegexp()
解析一个路由模板并返回一个 routeRegexp
,用于匹配主机、路径或查询字符串
// addRegexpMatcher adds a host or path matcher and builder to a route.
func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{strictSlash: r.strictSlash,useEncodedPath: r.useEncodedPath,})r.addMatcher(rr)return nil
}func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) {idxs, errBraces := braceIndices(tpl)if errBraces != nil {return nil, errBraces}template := tpldefaultPattern := "[^/]+"if typ == regexpTypeQuery {defaultPattern = ".*"} else if typ == regexpTypeHost {defaultPattern = "[^.]+"}if typ != regexpTypePath {options.strictSlash = false}endSlash := falseif options.strictSlash && strings.HasSuffix(tpl, "/") {tpl = tpl[:len(tpl)-1]endSlash = true}varsN := make([]string, len(idxs)/2)varsR := make([]*regexp.Regexp, len(idxs)/2)pattern := bytes.NewBufferString("")pattern.WriteByte('^')reverse := bytes.NewBufferString("")var end intvar err errorfor i := 0; i < len(idxs); i += 2 {raw := tpl[end:idxs[i]]end = idxs[i+1]parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)name := parts[0]patt := defaultPatternif len(parts) == 2 {patt = parts[1]}if name == "" || patt == "" {return nil, fmt.Errorf("mux: missing name or pattern in %q",tpl[idxs[i]:end])}fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)fmt.Fprintf(reverse, "%s%%s", raw)varsN[i/2] = namevarsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))if err != nil {return nil, err}}raw := tpl[end:]pattern.WriteString(regexp.QuoteMeta(raw))if options.strictSlash {pattern.WriteString("[/]?")}if typ == regexpTypeQuery {if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {pattern.WriteString(defaultPattern)}}if typ != regexpTypePrefix {pattern.WriteByte('$')}reverse.WriteString(raw)if endSlash {reverse.WriteByte('/')}reg, errCompile := regexp.Compile(pattern.String())if errCompile != nil {return nil, errCompile}if reg.NumSubexp() != len(idxs)/2 {panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")}return &routeRegexp{template: template,regexpType: typ,options: options,regexp: reg,reverse: reverse.String(),varsN: varsN,varsR: varsR,}, nil
}
路由匹配
HandlerFunc()
函数完成了给定特定的路由匹配一个处理相应的函数,并且调用了 Handler()
函数,将处理相应函数传给 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
}
该包定义了自己的 ServeHTTP()
函数。且该 ServeHTTP()
函数调用 Match()
函数对请求进行匹配:
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 {url := *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 = setVars(req, match.Vars)req = setCurrentRoute(req, match.Route)}if handler == nil && match.MatchErr == ErrMethodMismatch {handler = methodNotAllowedHandler()}if handler == nil {handler = http.NotFoundHandler()}if !r.KeepContext {defer contextClear(req)}handler.ServeHTTP(w, req)
}
mux.Vars()
函数可以取出 http.Request
所有相关联的变量的信息:
func setVars(r *http.Request, val interface{}) {//设置参数时候,val实际上时一个map[string][string],存放该请求对应的变量值集合context.Set(r, varsKey, val)
}func Vars(r *http.Request) map[string]string {if rv := context.Get(r, varsKey); rv != nil {return rv.(map[string]string)}return nil
}
总结
Golang
通过一个 ServeMux
实现了的 multiplexer
路由多路复用器来管理路由。同时提供一个 Handler
接口提供 ServeHTTP
用来实现 handler
处理其函数,后者可以处理实际 request
并构造 response
。ServeMux
和 handler
处理器函数的连接桥梁就是 Handler
接口。ServeMux
的 ServeHTTP
方法实现了寻找注册路由的 handler
的函数,并调用该 handler
的 ServeHTTP
方法。ServeHTTP
方法就是真正处理请求和构造响应的地方。
源码的阅读可以提高自身的代码逻辑组织能力,能够写出更加优秀的代码。
开发 web 程序服务 之 源码分析相关推荐
- 抖音seo搜索排名,源码开发部署/seo排名系统源码分析。
前言:抖音seo搜索排名,源码开发部署/seo排名系统源码分析.抖音seo源码搭建部分代码 抖音seo是什么?其实seo它是搜索引擎优化,不仅这样,抖音里也有搜索引擎优化,抖音seo其实就是优化抖音的 ...
- Dubbo 服务订阅源码分析
Dubbo 服务引用的时机有两个: 第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务 第二个是在 ReferenceBean 对 ...
- 几款小众web指纹识别工具源码分析
公粽号:黒掌 一个专注于分享网络安全.黑客圈热点.黑客工具技术区博主! Webfinger 简介 这是一款很小巧的工具,由Python2编写,使用Fofa的指纹库 Github地址:https://g ...
- Eureka服务注册源码分析
本文来说下Eureka服务注册源码 文章目录 Eureka-Client注册服务 啥时候会注册 定时器注册 自动注册 DiscoveryClient.register() Eureka-Server接 ...
- 深入 Eureka 服务注册 源码分析(二)
说一下自己对Eureka注册的理解 Eureka注册的流程是 1.客户端在初始化或者在注册信息发生变化的时候,发送注册信息.其实在初始化的时候也是通过改变注册信息的. 客户端会开启一个定时任务,每40 ...
- Dubbo服务注册源码分析
本代码版本基于Dubbo2.7.8版本进行源码分析 注册概览 扫描所有@DubboService注解, 加载配置文件, 装载注解中的所有属性, 把每个服务都封装成一个ServiceBean, 注入到S ...
- 交易系统开发(十三)——QuickFIX源码分析
一.QuickFIX源码目录 QuickFIX主要目录如下: doc:QuickFIX说明和简要HTML文件. example:QuickFIX示例程序. spec:存放FIX数据字典. UnitTe ...
- 基于后端开发Redisson实现分布式锁源码分析解读
一.分布式锁的概念和使用场景 分布式锁是控制分布式系统之间同步访问共享资源的一种方式. 在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问 ...
- Android 蓝牙开发(十)A2DP源码分析
转载请注明出处:http://blog.csdn.net/vnanyesheshou/article/details/71811288 本文已授权微信公众号 fanfan程序媛 独家发布 扫一扫文章底 ...
最新文章
- PHP的静态变量介绍
- MIT警告深度学习正在逼近计算极限,网友:放缓不失为一件好事
- redis中的发布订阅
- linux2.6.37内核接两个硬盘导致读写效率变低的问题
- Makefile常用信息查询页
- why get_expanded_entityset is not called but works in Ke's laptop
- MATLAB错误:‘conv2’
- 如何升级浏览器_手把手教你申请IOS14 Beta升级方法
- WPF应用程序内嵌网页
- 移动Web体验月报(6月):MIP 核心代码升级,增加基于 Vue 开发能力
- SQL2005 游标学习
- Linux 快捷键总结
- 7 vsphere 分配许可_vCenter server 5.5中添加ESXi5.5主机并分配许可密钥
- 【使用指南】ComponentOne Enterprise .NET开发控件集
- 快递单号查询api接口对接
- jsp左侧菜单栏_HTML页面左侧菜单栏切换实现右侧主体内容改变
- 新研究评估Masimo Patient SafetyNet™对普通病房护理工作流程的效用
- SOFA Weekly | SOFAJRaft 发布、SOFAJRaft 源码解析文章合集
- java 抽奖系统_【小型系统】抽奖系统-使用Java Swing完成
- VS Code 2022路线图:大量Spring Boot优化提上日程