各位老铁们,大家好,今天由我来为大家分享国外服务器网站源码分享,以及国外服务器网站链接的相关问题知识,希望对大家有所帮助。如果可以帮助到大家,还望关注收藏下本站,您的支持是我们最大的动力,谢谢大家了哈,下面我们开始吧!
前言
本文目标:
从路由注册到监听本地端口后请求路由的一系列动作的分析,基本上仅限于net/httpserver.go这个包文件的路由相关部分解读
写作目的:
在使用原生库进行web开发的时候,很多初学者很容易被mux.Handle()/mux.HandleFunc()/mux.Handler()/Handlerfunc/Handler/Handle()/Handlefunc()/handler给唬住,本身几个名称就相近,首字母有时候大写有时候小写,有时候是handle,有时候是handler,看起来相似但是类型和作用却完全不同。因为命名相似容易混淆,因此其真实含义也不容易搞清楚,对于开发者来说也不容易记忆。有些命名甚至看不出来这个函数到底是干什么用的,有些属于设计库的时候的历史遗留问题,使得理解http库变得更加困难。
很多网上的教程只是讲了某些东西是什么,用来干什么的,而没有讲为什么是这样的,为什么要这样设计,这样设计有什么好处。更重要的是有些教程已经老了,2018年到现在已经两年了,很多函数都经过优化重写了,至少server.go中的很多函数变化都很大,2018年的教程很多已经过时了,可能2022年又需要重新写一篇http库的解读。不过有些东西是不变的,很多设计思想都是共通的,而这些思想才是初学者应该掌握的。事实上死记硬背掌握handle的四种写法对开发没有任何帮助,如果不深入理解以后还会经常性的把文档翻来翻去而一头雾水。
Go的有些设计哲学很有趣,不是简简单单几万字的篇幅就可以讲明白的。
官网示例
在官网示例中,使用go搭建一个稳定的高并发web服务器仅需要短短几行:
http.Handle(&34;,fooHandler)\n\nhttp.HandleFunc(&34;,func(whttp.ResponseWriter,r*http.Request){\nfmt.Fprintf(w,&34;,html.EscapeString(r.URL.Path))\n})\n\nlog.Fatal(http.ListenAndServe(&34;,nil))\n复制代码
当然对于大部分开发来说,这几行代码已经足够在生产环境中使用了,但是如果对比较底层的一些原理不解,那么还需要继续深究。
一个go服务器正常运行起来的步骤大致有:注册函数、监听端口,接受请求,处理请求,提供服务,关闭链接
0.注册函数:首先往路由表中注册对应的路由规则
1.监听端口:创建listensocket,循环监听
2.接受请求:接受请求,创建网络链接conn对象,开启一个协程处理该链接(估计多路复用复用在这里了)每服务一个新的链接,在conn.connect()中就会调用serveHTTP来处理请求
3.处理请求:读取请求参数构造Request对象,根据请求路径在map路由表中查找对应的Handler。然后把请求分配给处理函数
4.提供服务:处理函数根据请求的参数等信息做处理,返回不同的信息
5.关闭链接:应用层处理完请求后关闭链接
前置知识
Go基础语法、web基础、*压缩字典树
源码分析范围/大纲
主要分析net/http库中的server.go文件,但是篇幅有限重点分析(使用mux.XX()简化代替ServeMux.XX()):
1.ServeMux结构体及其方法:mux.NewServeMux(),mux.Handle(),mux.HandleFunc(),mux.Handler()/mux.handler(),mux.ServeHTTP()
2.HandlerFunc结构体及其实现方法:HandlerFunc.ServeHTTP()
3.Handler接口类型
4.函数Handle()和函数HandleFunc()
路由部分就这么点东西
ServeMux
ServeMux是一个结构体
ServeMux定义
ServeMux是一个HTTP请求多路复用器。它根据注册模式列表(路由表)将每个传入请求的URL匹配,并为与URL最匹配的模式调用处理程序(handler)。
typeServeMuxstruct{\n//containsfilteredorunexportedfields\n}\n复制代码
结构体内黑盒,包含已过滤和未导出的字段,其实就是不想让你知道里面的构造,事实上的构造如下:
typeServeMuxstruct{\nmusync.RWMutex//读写互斥锁\nmmap[string]muxEntry//路由表\nes[]muxEntry//有序数组,从最长到最短排序\nhostsbool//whetheranypatternscontainhostnames\n}\n复制代码
ServeMux结构体本质上是由mu读写互斥锁、m路由表、es数组(很多老教程都没有这个更新字段)和hosts布尔值组成
其中:
1.mu是读写互斥锁,详情见设计思想
2.m是路由表,路由表本质上就是一个map[string]muxEntry变量,键是路径字符串(由method和传入参数拼接字符串组成),值是对应的处理结构体muxEntry
3.es是一个有序数组,由长到短维护所有后缀为/的路由地址,至于为什么要这样设计,见设计思想
4.布尔类型的hosts属性标记路由中是否带有主机名,若hosts值为true,则路由的起始不能为/
m路由表中的muxEntry的结构体如下:
typemuxEntrystruct{\nhHandler//处理程序\npatternstring//路由路径\n}\n复制代码
muxEntry本质上是由Handler类和路由路径字符串组成,其中:
1.h是Handler类型,Handler类型要求实现接口中的ServeHTTP方法
2.pattern实际上和路由表中的key相同
默认多路复用器
在net/http包中规定了默认的多路复用器,如果不自己手写指定则使用默认mux:
//DefaultServeMuxisthedefaultServeMuxusedbyServe.\nvarDefaultServeMux=&defaultServeMux\n\nvardefaultServeMuxServeMux\n复制代码
这里为什么可以在声明前使用变量?DaveCheney告诉我包级别的变量与声明顺序无关,还告诉我这种问题以后去slack上自己问:sweat_smile:,编译器做初始化工作的时候会首先初始化包级别的变量,因此无论声明在哪里都可以使用。
ServeMux方法
公有方法
mux.NewServeMux()
//NewServeMuxallocatesandreturnsanewServeMux.\nfuncNewServeMux()*ServeMux{returnnew(ServeMux)}\n复制代码
新建并返回一个ServeMux结构体,为结构体内字段分配空间。值得注意的是,初始化并返回的结构体字段hosts默认值为false
mux.Handler()
对于给定的请求,mux.Handler()总是返回非空Handler来使用。如果方法是CONNECT则见私有方法mux.redirectToPathSlash()
mux.Handler()调用私有方法mux.handler(),在mux.handler()内部调用了mux.match()方法来返回匹配pattern的handler
func(mux*ServeMux)Handler(r*Request)(hHandler,patternstring){\n\n//CONNECTrequestsarenotcanonicalized.\nifr.Method==&34;{\n//Ifr.URL.Pathis/treeanditshandlerisnotregistered,\n//the/tree->/tree/redirectappliestoCONNECTrequests\n//butthepathcanonicalizationdoesnot.\nifu,ok:=mux.redirectToPathSlash(r.URL.Host,r.URL.Path,r.URL);ok{\nreturnRedirectHandler(u.String(),StatusMovedPermanently),u.Path\n}\n\nreturnmux.handler(r.Host,r.URL.Path)\n}\n\n//Allotherrequestshaveanyportstrippedandpathcleaned\n//beforepassingtomux.handler.\nhost:=stripHostPort(r.Host)\npath:=cleanPath(r.URL.Path)\n\n//Ifthegivenpathis/treeanditshandlerisnotregistered,\n//redirectfor/tree/.\nifu,ok:=mux.redirectToPathSlash(host,path,r.URL);ok{\nreturnRedirectHandler(u.String(),StatusMovedPermanently),u.Path\n}\n\nifpath!=r.URL.Path{\n_,pattern=mux.handler(host,path)\nurl:=*r.URL\nurl.Path=path\nreturnRedirectHandler(url.String(),StatusMovedPermanently),pattern\n}\n\nreturnmux.handler(host,r.URL.Path)\n}\n复制代码
对于r.host和r.URL.Path进行了简单处理,简要说明一下两个函数cleanPath()和stripHostPort()分别做了什么工作:
cleanPath()
1.处理无效路由
2.对于斜杠的处理,代替无效的多个斜杠
3.移除所有的.替换为等效path
简单来说就是对路径进行处理为等效最短路径,使之可以在后续查找路由表的过程中可以查找到相应键值对。
//cleanPathreturnsthecanonicalpathforp,eliminating.and..elements.\nfunccleanPath(pstring)string{\nifp==&34;{\nreturn&34;\n}\nifp[0]!=&39;{\np=&34;+p\n}\nnp:=path.Clean(p)\n//path.Cleanremovestrailingslashexceptforroot;\n//putthetrailingslashbackifnecessary.\nifp[len(p)-1]==&39;&&np!=&34;{\n//Fastpathforcommoncaseofpbeingthestringwewant:\niflen(p)==len(np)+1&&strings.HasPrefix(p,np){\nnp=p\n}else{\nnp+=&34;\n}\n}\nreturnnp\n}\n复制代码
stripHostPort()
就是对host格式的规范
//stripHostPortreturnshwithoutanytrailing&34;.\nfuncstripHostPort(hstring)string{\n//Ifnoportonhost,returnunchanged\nifstrings.IndexByte(h,&39;)==-1{\nreturnh\n}\nhost,_,err:=net.SplitHostPort(h)\niferr!=nil{\nreturnh//onerror,returnunchanged\n}\nreturnhost\n}\n复制代码
mux.ServeHTTP()
mux.ServeHTTP()给最能匹配请求URL的handler发出请求,最后调用实现ServeHTTP()方法的Handler类型的Handler.ServeHTTP()来处理请求
有点绕,总之HTTP的请求首先由mux.ServeHTTP()进行处理,在该函数内部调用了mux.Handler()(见私有方法mux.handler())来选择处理程序handler(在这个过程中调用了mux.handler()/mux.RedirectHandler()来查找路由表,最后在mux.handler()的内部调用了mux.match()来最后对路由进行匹配查找返回Handler类型的h)
//ServeHTTPdispatchestherequesttothehandlerwhose\n//patternmostcloselymatchestherequestURL.\nfunc(mux*ServeMux)ServeHTTP(wResponseWriter,r*Request){\nifr.RequestURI==&34;{\nifr.ProtoAtLeast(1,1){\nw.Header().Set(&34;,&34;)\n}\nw.WriteHeader(StatusBadRequest)\nreturn\n}\nh,_:=mux.Handler(r)\nh.ServeHTTP(w,r)\n}\n复制代码
mux.Handle()
在注册路由/添加路由阶段,注册函数mux.Handle()负责将处理程序和路径注册到路由表中,本质上是一个写表的过程
//Handleregistersthehandlerforthegivenpattern.\n//Ifahandleralreadyexistsforpattern,Handlepanics.\nfunc(mux*ServeMux)Handle(patternstring,handlerHandler){\nmux.mu.Lock()\ndefermux.mu.Unlock()\n\nifpattern==&34;{\npanic(&34;)\n}\nifhandler==nil{\npanic(&34;)\n}\nif_,exist:=mux.m[pattern];exist{\npanic(&34;+pattern)\n}\n\nifmux.m==nil{\nmux.m=make(map[string]muxEntry)\n}\ne:=muxEntry{h:handler,pattern:pattern}\nmux.m[pattern]=e\nifpattern[len(pattern)-1]==&39;{\nmux.es=appendSorted(mux.es,e)\n}\n\nifpattern[0]!=&39;{\nmux.hosts=true\n}\n}\n复制代码
对于路由在表中重复会引发panic,对于后缀为slash/的路径,按照长度大小写入mux.es中,之前分析结构体mux时也提到过这一点。
简单看一下其实现的排序函数:
funcappendSorted(es[]muxEntry,emuxEntry)[]muxEntry{\nn:=len(es)\ni:=sort.Search(n,func(iint)bool{\nreturnlen(es[i].pattern)<len(e.pattern)\n})\nifi==n{\nreturnappend(es,e)\n}\n//wenowknowthatipointsatwherewewanttoinsert\nes=append(es,muxEntry{})//trytogrowthesliceinplace,anyentryworks.\ncopy(es[i+1:],es[i:])//Moveshorterentriesdown\nes[i]=e\nreturnes\n}\n复制代码
mux.HandleFunc()
mux.HandleFunc()是我认为最重要的一个方法,同样是将handler注册到路由表中,我们应该对比mux.HandleFunc()和mux.Handle()的区别,其实从函数体来看mux.HandleFunc()算是对mux.Handle()函数的一个再封装,调用了HandlerFunc()这个适配器函数,本质上是将一个普通函数作为HTTP请求handler的语法糖,我们不再需要实现ServeHTTP()方法,取而代之的是传入的普通函数只要为func(ResponseWriter,*Request)类型的,就可以进行函数的路由,基本上一行代码就可以搞定,这也是为什么在官网示例中我们可以轻而易举的构建简单的web程序的原因。在官网示例的HandleFunc()函数中调用了默认复用器的DefaultServeMux.HandleFunc()方法,开发者只需要自己定义普通函数即可:
//HandleFuncregistersthehandlerfunctionforthegivenpattern.\nfunc(mux*ServeMux)HandleFunc(patternstring,handlerfunc(ResponseWriter,*Request)){\nifhandler==nil{\npanic(&34;)\n}\nmux.Handle(pattern,HandlerFunc(handler))\n}\n复制代码
私有方法
mux.match()
当调用mux.Handler()返回Handler类时在mux.handler()内部会调用mux.match()函数,本质上可以看作是路由查找的过程(Handle()是路由注册的过程)
//Findahandleronahandlermapgivenapathstring.\n//Most-specific(longest)patternwins.\nfunc(mux*ServeMux)match(pathstring)(hHandler,patternstring){\n//Checkforexactmatchfirst.\nv,ok:=mux.m[path]\nifok{\nreturnv.h,v.pattern\n}\n\n//Checkforlongestvalidmatch.mux.escontainsallpatterns\n//thatendin/sortedfromlongesttoshortest.\nfor_,e:=rangemux.es{\nifstrings.HasPrefix(path,e.pattern){\nreturne.h,e.pattern\n}\n}\nreturnnil,&34;\n}\n复制代码
在进行匹配的过程中:
1.首先在路由表中进行精确匹配,匹配到muxEntry后返回
2.如果在路由表中没有查询到,则在有序数组es中进行匹配,从strings.HasPrefix()可以看出,本质上这是一种模糊匹配,只匹配了相应的前缀,就认定匹配成功
3.如果相应前缀无法查询,则认为匹配失败,返回nilhandler
总结匹配规则一句话描述是:Longerpatternstakeprecedenceovershorterones,长字符串模式优先级大于短字符串模式,优先匹配长字符串
mux.shouldRedirectRLocked()
mux.shouldRedirectRLocked()方法的作用较为简单,判断是否需要对像&34;这种路由的重定向(在ServeMux中对于&34;会自动重定向到&34;,除非路由表中已有&34;,此过程在mux.Handler()中调用mux.redirectToPathSlash()完成)
1.判断路由表中是否存在host+path或者path的组合,如果存在则不需要重定向
2.如果path为空字符串,则不需要重定向
3.如果当前路由表中存在path+“/”,则需要重定向(例如在注册时将&34;注册到表中,则对于&34;的路由重定向到了&34;,)
func(mux*ServeMux)shouldRedirectRLocked(host,pathstring)bool{\np:=[]string{path,host+path}\n\nfor_,c:=rangep{\nif_,exist:=mux.m[c];exist{\nreturnfalse\n}\n}\n\nn:=len(path)\nifn==0{\nreturnfalse\n}\nfor_,c:=rangep{\nif_,exist:=mux.m[c+&34;];exist{\nreturnpath[n-1]!=&39;\n}\n}\n\nreturnfalse\n}\n复制代码
mux.redirectToPathSlash()
mux.redirectToPathSlash()函数确定是否需要在其路径后附加&34;,一旦判断需要添加&34;则返回新的url和被重定向后的handler:
func(mux*ServeMux)redirectToPathSlash(host,pathstring,u*url.URL)(*url.URL,bool){\nmux.mu.RLock()\nshouldRedirect:=mux.shouldRedirectRLocked(host,path)\nmux.mu.RUnlock()\nif!shouldRedirect{\nreturnu,false\n}\npath=path+&34;\nu=&url.URL{Path:path,RawQuery:u.RawQuery}\nreturnu,true\n}\n复制代码
判断应该重定向后,返回结尾带有/的路径
mux.Handler()中,如果Http.Method为CONNECT,则会返回RedirectHandler(也是Handler类型的一种)写入StatusMovedPermanently(见status.go中的定义),调用RedirectHandler.ServeHTTP()来对HTTP请求进行处理
ifu,ok:=mux.redirectToPathSlash(r.URL.Host,r.URL.Path,r.URL);ok{\nreturnRedirectHandler(u.String(),StatusMovedPermanently),u.Path\n}\n\nreturnmux.handler(r.Host,r.URL.Path)\n复制代码
mux.handler()
mux.handler()函数是mux.Handler()的一种实现
//handleristhemainimplementationofHandler.\n//Thepathisknowntobeincanonicalform,exceptforCONNECTmethods.\nfunc(mux*ServeMux)handler(host,pathstring)(hHandler,patternstring){\nmux.mu.RLock()\ndefermux.mu.RUnlock()\n\n//Host-specificpatterntakesprecedenceovergenericones\nifmux.hosts{\nh,pattern=mux.match(host+path)\n}\nifh==nil{\nh,pattern=mux.match(path)\n}\nifh==nil{\nh,pattern=NotFoundHandler(),&34;\n}\nreturn\n}\n复制代码
进行两种匹配后都没有找到相应的handler后,返回NotFoundHandler()
总结
Go其实支持外部实现的路由器ListenAndServe的第二个参数就是用以配置外部路由器的,它是一个Handler接口,即外部路由器只要实现了Handler接口就可以,我们可以在自己实现的路由器的ServeHTTP里面实现自定义路由功能
HandleFunc()
HandleFunc()是函数
HandleFunc定义
对于给定的模式字符串,HandleFunc将handler函数注册到相应的路由上。换句话说,当对不同的url路径请求时,给出不同的处理逻辑,而HandleFunc可以实现这种将处理逻辑和url绑定的关系。
函数定义:
funcHandleFunc(patternstring,handlerfunc(ResponseWriter,*Request)){\nDefaultServeMux.HandleFunc(pattern,handler)\n}\n复制代码
第一个参数是字符串,第二个参数是handler,HandleFunc处理匹配到的url路径请求。
HandleFunc()本质上调用了默认复用器的mux.HandleFunc()
例子:
packagemain\n\nimport(\n&34;\n&34;\n&34;\n)\n\nfuncmain(){\nh1:=func(whttp.ResponseWriter,_*http.Request){\nio.WriteString(w,&1!\\n&34;HellofromaHandleFunc34;)\n}\n\nhttp.HandleFunc(&34;,h1)\nhttp.HandleFunc(&34;,h2)\n\nlog.Fatal(http.ListenAndServe(&34;,nil))\n}\n复制代码
HandleFunc优势
HandleFunc函数的存在使得我们可以直接将一个func(ResponseWriter,*Request)类型的函数作为handler,而不再需要实现Handler这个接口和自定义一个实现ServeHTTP函数的类型了,HandleFunc可以非常简便的为url注册路径。
Handle()
Handle()是函数
Handle()定义
和HandleFunc类似,本质上也是调用了默认mux的mux.Handle()方法
函数定义:
//Handleregistersthehandlerforthegivenpattern\n//intheDefaultServeMux.\n//ThedocumentationforServeMuxexplainshowpatternsarematched.\nfuncHandle(patternstring,handlerHandler){DefaultServeMux.Handle(pattern,handler)}\n复制代码
第一个参数是待匹配的字符串,第二个参数是Handler类型的handler,和上面例子的handler不同,需要自己实现一个新的类,并且实现类中的方法ServeHTTP
例子:
packagemain\n\nimport(\n&34;\n&34;\n&34;\n&34;\n)\n\ntypecountHandlerstruct{\nmusync.Mutex//guardsn\nnint\n}\n\nfunc(h*countHandler)ServeHTTP(whttp.ResponseWriter,r*http.Request){\nh.mu.Lock()\ndeferh.mu.Unlock()\nh.n++\nfmt.Fprintf(w,&34;,h.n)\n}\n\nfuncmain(){\nhttp.Handle(&34;,new(countHandler))\nlog.Fatal(http.ListenAndServe(&34;,nil))\n}\n复制代码
Handler
Handler是接口
Handler定义
在gopackage的官网文档中是这样定义Handler的“Handler响应HTTP请求”,理解成中文就是“处理程序”。
Handler是一个接口,定义在net/http原生包中:
typeHandlerinterface{\nServeHTTP(ResponseWriter,*Request)\n}\n复制代码
对于任何实现了ServeHTTP方法的都属于Handler。
Handler性质
1.Handler只能用来读取request的body,而不能修改请求
2.先读取body,然后再写入resp
3.事实上在真正的开发过程中,我们不太会经常使用Handler,因为net/http给我们提供了更方便的HandleFunc()函数,而这个函数可以让我们直接将一个函数作为handler,在这里handler是函数类型而非此Handle接口,这种实现较实现ServeHTTP()来说更加方便。
Handler用于处理请求并给予响应,更严格地说,用来读取请求体、并将请求对应的响应字段(responesheader)写入ResponseWriter中,然后返回
HandlerFunc
http还提供了HandlerFunc类,和HandleFunc()函数仅有一字之差
HandlerFunc本质是一个适配器函数,这个类在内部实现了ServeHTTP()函数,因此这个类本质上是一个Handler类型
HandlerFunc()定义
HandlerFunc(f)是调用f函数的处理程序
函数原型:
typeHandlerFuncfunc(ResponseWriter,*Request)\n复制代码
源码
//调用默认ServerMux的HandleFunc方法\nfuncHandleFunc(patternstring,handlerfunc(ResponseWriter,*Request)){\nDefaultServeMux.HandleFunc(pattern,handler)\n}\n//把方法handler转换成HandlerFunc类型,即实现了Handler接口;再执行Handle方法\nfunc(mux*ServeMux)HandleFunc(patternstring,handlerfunc(ResponseWriter,*Request)){\nmux.Handle(pattern,HandlerFunc(handler))\n}\n\n//路由器注册一个handler给指定的parttern\nfunc(mux*ServeMux)Handle(patternstring,handlerHandler){\n….\n}\n复制代码
执行HandleFunc()其实就是为某一规则的请求注册处理器。
Handler/HandlerFunc区别
Handler是一个接口
HandlerFunc是Handler类型,是一个适配器函数
Handle()/HandleFunc()区别
HandleFunc()和Handle()都是调用DefaultServeMux对应的方法,而DefaultServeMux是ServeMux的实例,ServeMux实现了Handler接口。事实上,HandleFunc()最终还是会调用Handle方法,之前也提到过了:
func(mux*ServeMux)HandleFunc(patternstring,handlerfunc(ResponseWriter,*Request)){\n…\nmux.Handle(pattern,HandlerFunc(handler))\n}\n复制代码
路由注册过程
路由注册过程是由mux结构体内的两个方法mux.Handle()和mux.HandleFunc()实现的
mux.Handle()
之前大致了解了函数体,路由注册过程主要由ServeMux结构体内的Handle()方法实现的,如果该路由已经存在在路由表内,则会引发Panic
ServeMux结构体内的Handle()方法大致上干了这样几件事:
1.检查路由合法性,若不合法则给出报错信息,报错信息multipleregistrationsfor/xxx就属于路由重复注册而引发的问题
2.若多路复用器中没有路由表,则创建路由表
3.若路由合法则创建路由条目muxEntry添加至路由表中
4.若路由最后一个字符带有&34;,则按照自定义排序函数appendSorted()的规则定位索引、插入并排序(尽管我对此函数的执行效率充满疑惑),返回排序后的数组。
5.若路由的首字母不为&34;,则包含主机名
mux.HandleFunc()
func(mux*ServeMux)HandleFunc(patternstring,handlerfunc(ResponseWriter,*Request)){\nifhandler==nil{\npanic(&34;)\n}\nmux.Handle(pattern,HandlerFunc(handler))\n}\n复制代码
Helpfulbehavior
在之前版本的server.go中,注册函数mux.Handle是存在一些辅助行为的,当你将路由路径设置为/tree/时,Helpfulbehavior会隐式永久的帮你将/tree注册到注册表中,当然也可以显式指定路由进行覆盖,在对/tree/进行访问时,/tree的handler会自动将请求重定向到/tree/:
状态代码:301/MovedPermanently\n复制代码
在现在server.go中,ServeMux结构体内维护了一个es类型的数组,就是从长到短记录最后一个字母是&39;路由字符串的
在使用mux.match()对路由path进行匹配的时候(详情见“路由查找过程”),首先查找路由表,当路由表中不存在该路由时,遍历es数组,匹配其最大长度:
//Findahandleronahandlermapgivenapathstring.\n//Most-specific(longest)patternwins.\nfunc(mux*ServeMux)match(pathstring)(hHandler,patternstring){\n//Checkforexactmatchfirst.\nv,ok:=mux.m[path]\nifok{\nreturnv.h,v.pattern\n}\n\n//Checkforlongestvalidmatch.mux.escontainsallpatterns\n//thatendin/sortedfromlongesttoshortest.\nfor_,e:=rangemux.es{\nifstrings.HasPrefix(path,e.pattern){\nreturne.h,e.pattern\n}\n}\nreturnnil,&34;\n}\n复制代码
服务请求过程
参考官网的文档,我们可以使用golang原生的库net/http来实现一个简单的web路由示例:
//serve.go\npackagemain\n\nimport(\n&34;\n&34;\n)\n\nfuncmain(){\nhttp.HandleFunc(&34;,HelloWorld)\nhttp.ListenAndServe(&34;,nil)\n}\n\nfuncHelloWorld(whttp.ResponseWriter,r*http.Request){\nfmt.Fprint(w,&34;)\n}\n复制代码
其实主函数中只有两个方法,HandleFunc()和ListenAndServe(),整个请求响应的执行流程如下:
注册路由过程:首先调用HandleFunc():调用了默认复用器DefaultServeMux的HandleFunc(),调用了DefaultServeMux的Handle,往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则。实例化server并调用server.ListenAndServe(),调用net.Listen(&34;,addr)监听端口。启动一个for循环,在循环体中Accept请求对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务goc.serve()读取每个请求的内容w,err:=c.readRequest()进入serveHandler.ServeHTTP若有自己实现的mux则使用自己的mux。判断handler是否为空,如果没有设置handler(此例子中为nilhandler),handler就设置为DefaultServeMux调用handler的ServeHttp,进入到DefaultServeMux.ServeHttp根据request选择匹配handler,并且进入到这个handler的ServeHTTP
对于每一个HTTP请求,服务端都会起一个协程来进行服务,这部分不在本文讨论范围。对于官网示例:
http.ListenAndServe(&34;,nil)\n复制代码
传入handler是nil,则使用默认复用器DefaultServeMux,调用HandleFunc时,就是向默认复用器注册了handler
//ServeHTTPdispatchestherequesttothehandlerwhose\n//patternmostcloselymatchestherequestURL.\nfunc(mux*ServeMux)ServeHTTP(wResponseWriter,r*Request){\nifr.RequestURI==&34;{\nifr.ProtoAtLeast(1,1){\nw.Header().Set(&34;,&34;)\n}\nw.WriteHeader(StatusBadRequest)\nreturn\n}\nh,_:=mux.Handler(r)\nh.ServeHTTP(w,r)\n}\n复制代码
鉴于本文篇幅太小,有机会再分析一下server.go中server.Serve()函数
路由查找过程
如何保证确保访问&39;/path/subpath&39;/path/subpath&39;/path/&39;,是因为在路由查找过程中的查找规则(之前同样提到过):
mux.ServerHTTP()->mux.Handler()->mux.handler()->mux.match()
得到了处理请求的handler,再调用h.ServeHTTP(w,r),去执行相应的handler方法:
typeHandlerFuncfunc(ResponseWriter,*Request)\n\n//ServeHTTPcallsf(w,r).\nfunc(fHandlerFunc)ServeHTTP(wResponseWriter,r*Request){\nf(w,r)\n}\n复制代码
f(w,r)就实现了handler的执行
设计思想
mu读写锁:请求设计并发处理,到底哪些地方用到了并发?
没时间写了
hasPrefix()可以做到精准匹配么?
可以,但没必要。因为需要对于模糊路由进行匹配,如果实现上只有路由表m,则对于访问URL的人来说极不友好,无法做到有效的辅助提示
为什么需要单独维护一个后缀为/的数组es?
1.同上,为了模糊匹配
2.最重要的是,方便插入排序,尽管时间复杂度没有那么乐观
总结
本文仅仅只是对http原生库的一小部分进行了解读,对于生产来说其实并没有特别大的帮助,但是掌握原生库的一些设计思想和设计模式对于理解其他框架是一定有很大帮助的,毕竟原生库的作者都是真正的大神。
很多舍本逐末的程序员只注重框架的学习而不注重这种基础,那和培训班出来的又有什么区别呢?还是应该真正理解一下原生库,毕竟后人开发的第三方还是借鉴了这些设计哲学的。
未来展望
一
因为原生库自带的默认多路请求路由器功能有限,而催生了很多路由框架,例如比较有名的框架httprouter,这篇文章比较详细的讲解了httprouter框架,感兴趣的可以先看看和原生库有什么不同,未来如果有时间,也会更新对这些框架的学习。
对于目前已经存在的web路由框架,已经有人比较过这些框架的优劣了,看这篇文章可以提前预习一下:
超全的GoHttp路由框架性能比较
二
很早就想系统的讲解一下智能爬虫和爬虫框架了,因为爬虫和http这个库关系非常大。基本上star较多的框架我也都多多少少使用过,部分源码也阅读过,当然因为框架本身的复杂性,手撕源码会更加困难和耗费时间。之前也陆陆续续的简单介绍过colly等常用爬虫框架,未来会更新更多的关于爬虫框架的源码解读。爬虫本身的技术含量并不高,但是理解这个框架为什么好、哪里好,这个技术含量就高很多了,毕竟还是要和其他爬虫框架对比一下才知道各自的优劣。
但是受限于每个开发者自身的水平,这些框架或多或少都有这那的问题,它们并没有github主页上宣传的那么“万能”,所以在这种比较底层的地方,掌握和使用的区别就很大了,大多数人只会使用而没有掌握。
当然如果熟悉http库你就会发现,使用原生函数来写爬虫一样非常流畅。
我坚持认为:爬虫使用框架是最后的妥协。
三
Go的有些设计哲学很有趣,不是简简单单几万字的篇幅就可以讲明白的
我不想把自己标榜成Go布道者或者卷入语言圣战,Gopher应当是有趣的地鼠:
作者:Wzy_CC链接:https://juejin.im/post/6865634592016039949
文章分享结束,国外服务器网站源码分享和国外服务器网站链接的答案你都知道了吗?欢迎再次光临本站哦!
