文章目录

  • 开发 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 并构造 responseServeMuxhandler 处理器函数的连接桥梁就是 Handler 接口。ServeMuxServeHTTP 方法实现了寻找注册路由的 handler 的函数,并调用该 handlerServeHTTP 方法。ServeHTTP 方法就是真正处理请求和构造响应的地方。

源码的阅读可以提高自身的代码逻辑组织能力,能够写出更加优秀的代码。

开发 web 程序服务 之 源码分析相关推荐

  1. 抖音seo搜索排名,源码开发部署/seo排名系统源码分析。

    前言:抖音seo搜索排名,源码开发部署/seo排名系统源码分析.抖音seo源码搭建部分代码 抖音seo是什么?其实seo它是搜索引擎优化,不仅这样,抖音里也有搜索引擎优化,抖音seo其实就是优化抖音的 ...

  2. Dubbo 服务订阅源码分析

    Dubbo 服务引用的时机有两个: 第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务 第二个是在 ReferenceBean 对 ...

  3. 几款小众web指纹识别工具源码分析

    公粽号:黒掌 一个专注于分享网络安全.黑客圈热点.黑客工具技术区博主! Webfinger 简介 这是一款很小巧的工具,由Python2编写,使用Fofa的指纹库 Github地址:https://g ...

  4. Eureka服务注册源码分析

    本文来说下Eureka服务注册源码 文章目录 Eureka-Client注册服务 啥时候会注册 定时器注册 自动注册 DiscoveryClient.register() Eureka-Server接 ...

  5. 深入 Eureka 服务注册 源码分析(二)

    说一下自己对Eureka注册的理解 Eureka注册的流程是 1.客户端在初始化或者在注册信息发生变化的时候,发送注册信息.其实在初始化的时候也是通过改变注册信息的. 客户端会开启一个定时任务,每40 ...

  6. Dubbo服务注册源码分析

    本代码版本基于Dubbo2.7.8版本进行源码分析 注册概览 扫描所有@DubboService注解, 加载配置文件, 装载注解中的所有属性, 把每个服务都封装成一个ServiceBean, 注入到S ...

  7. 交易系统开发(十三)——QuickFIX源码分析

    一.QuickFIX源码目录 QuickFIX主要目录如下: doc:QuickFIX说明和简要HTML文件. example:QuickFIX示例程序. spec:存放FIX数据字典. UnitTe ...

  8. 基于后端开发Redisson实现分布式锁源码分析解读

    一.分布式锁的概念和使用场景 分布式锁是控制分布式系统之间同步访问共享资源的一种方式. 在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问 ...

  9. Android 蓝牙开发(十)A2DP源码分析

    转载请注明出处:http://blog.csdn.net/vnanyesheshou/article/details/71811288 本文已授权微信公众号 fanfan程序媛 独家发布 扫一扫文章底 ...

最新文章

  1. PHP的静态变量介绍
  2. MIT警告深度学习正在逼近计算极限,网友:放缓不失为一件好事
  3. redis中的发布订阅
  4. linux2.6.37内核接两个硬盘导致读写效率变低的问题
  5. Makefile常用信息查询页
  6. why get_expanded_entityset is not called but works in Ke's laptop
  7. MATLAB错误:‘conv2’
  8. 如何升级浏览器_手把手教你申请IOS14 Beta升级方法
  9. WPF应用程序内嵌网页
  10. 移动Web体验月报(6月):MIP 核心代码升级,增加基于 Vue 开发能力
  11. SQL2005 游标学习
  12. Linux 快捷键总结
  13. 7 vsphere 分配许可_vCenter server 5.5中添加ESXi5.5主机并分配许可密钥
  14. 【使用指南】ComponentOne Enterprise .NET开发控件集
  15. 快递单号查询api接口对接
  16. jsp左侧菜单栏_HTML页面左侧菜单栏切换实现右侧主体内容改变
  17. 新研究评估Masimo Patient SafetyNet™对普通病房护理工作流程的效用
  18. SOFA Weekly | SOFAJRaft 发布、SOFAJRaft 源码解析文章合集
  19. java 抽奖系统_【小型系统】抽奖系统-使用Java Swing完成
  20. VS Code 2022路线图:大量Spring Boot优化提上日程

热门文章

  1. 段码液晶屏可以修复吗?
  2. SCP-Firmware 功能介绍
  3. 优秀项目管理者的必修课程2(项目规划)-吴永达原创文章
  4. 那一抹秋色!漂亮的秋天风景壁纸【组图】
  5. poi导出word表格、图片、多段等处理
  6. 金橙子通过注册:年营收2亿净利5263万 拟募资4亿
  7. sql-server简单查询
  8. 门控循环单元-GRU单元(Gated Recurrent Unit)
  9. Spring Boot: Spring Boot 整合 RabbitMQ
  10. 贝肯斯坦界(Bekenstein bound)