golang构建http服务
下面实现一个最简单的http服务
packagemain import( "fmt" "net/http" ) funcIndexHandler(whttp.ResponseWriter,r*http.Request){ fmt.Fprintln(w,"helloworld") } funcmain(){ http.HandleFunc("/",IndexHandler) http.ListenAndServe("127.0.0.0:8000",nil) }
上面只使用了go的内置包net/http
HTTP
除去细节,理解HTTP构建的网络应用只要关注两个端--客户端(client)和服务端(server),两个端的交互来自client的request,以及server端的response。所谓的http服务器,主要在于如何接受client的request,并向client返回response。
接收request的过程中,最重要的莫过于路由(router),即实现一个Multiplexer器。Go中既可以使用内置的mutilplexer--DefaultServeMux,也可以自定义。Multiplexer路由的目的就是为了找到处理器函数(hander),后者将对request进行处理,同时构建response。
简单总结就是这个流程:
client`->`Request`->`Multiplexer(router)`->`handler`->`Response`->`client
因此,理解go中的http服务,最重要的就是要理解Multiplexer和hander,Golang中的Multiplexer基于ServerMux结构,同时也实现了Handler接口
handler
函数:具有func(whttp.ResponseWriter,r*http.Requests)签名的函数handler
处理器(函数):经过HanderFunc结构包装的handler函数,它实现了ServeHTTP接口方法的函数。调用handler处理器的ServeHTTP方法时,即调用handler函数本身。handler
对象:实现了Hander接口ServeHTTP方法的结构。
handler函数和handler对象的差别在于,一个是函数,另一个是结构体,他们都有实现了serverHTTP方法,很多情况下,他们是类似的。
Golang的http处理流程可以用下面一张图表示,后面内容是针对图进行说明:
Http包的三个关键类型
- Handler接口
- ServeMux接口
- HandlerFunc适配器
- Server
Handler
Golang没有继承,类多态的方法可以通过接口实现。所谓接口则是定义声明了函数签名,任何结构只要实现了与接口函数签名相同的方法,就等同于实现了接口。go的http服务都是基于handler进行处理的。
//AHandlerrespondstoanHTTPrequest. // //ServeHTTPshouldwritereplyheadersanddatatotheResponseWriter //andthenreturn.Returningsignalsthattherequestisfinished;it //isnotvalidtousetheResponseWriterorreadfromthe //Request.Bodyafterorconcurrentlywiththecompletionofthe //ServeHTTPcall. // //DependingontheHTTPclientsoftware,HTTPprotocolversion,and //anyintermediariesbetweentheclientandtheGoserver,itmaynot //bepossibletoreadfromtheRequest.Bodyafterwritingtothe //ResponseWriter.CautioushandlersshouldreadtheRequest.Body //first,andthenreply. // //Exceptforreadingthebody,handlersshouldnotmodifythe //providedRequest. // //IfServeHTTPpanics,theserver(thecallerofServeHTTP)assumes //thattheeffectofthepanicwasisolatedtotheactiverequest. //Itrecoversthepanic,logsastacktracetotheservererrorlog, //andeitherclosesthenetworkconnectionorsendsanHTTP/2 //RST_STREAM,dependingontheHTTPprotocol.Toabortahandlerso //theclientseesaninterruptedresponsebuttheserverdoesn'tlog //anerror,panicwiththevalueErrAbortHandler. typeHandlerinterface{ ServeHTTP(ResponseWriter,*Request)//路由具体实现 }
任何结构体,只要实现了ServeHTTP方法,这个结构就可以称之为handler对象。ServeMux会使用handler并调用其ServeHTTP方法处理请求并返回响应。 所有请求的处理器、路由ServeMux都满足该接口。
ServeMux
了解了Handler之后,再看ServeMux。 HTTP请求的多路转接器(路由),它负责将每一个接收到的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。它内部用一个map来保存所有处理器Handler。 ServeMux源码很简单:
typeServeMuxstruct{ musync.RWMutex//锁,由于请求涉及到并发处理,因此这里需要一个锁机制 mmap[string]muxEntry//路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式 hostsbool//是否在任意的规则中带有host信息 }
typemuxEntrystruct{ hHandler//这个路由表达式对应哪个handler patternstring//匹配字符串 }
ServeMux结构中最重要的字段为m,这是一个map,key是一些url模式,value是一个muxEntry结构,后者里定义存储了具体的url模式和handler。
当然,所谓的ServeMux也实现了ServeHTTP接口,也算是一个handler,不过ServeMux的ServeHTTP方法不是用来处理request和respone,而是用来找到路由注册的handler。
- http包有一个包级别变量DefaultServeMux,表示默认路由:varDefaultServeMux=NewServeMux(),使用包级别的http.Handle()、http.HandleFunc()方法注册处理器时都是注册到该路由中。
//NewServeMuxallocatesandreturnsanewServeMux. funcNewServeMux()*ServeMux{returnnew(ServeMux)} //DefaultServeMuxisthedefaultServeMuxusedbyServe. varDefaultServeMux=&defaultServeMux vardefaultServeMuxServeMux
- ServeMux结构体有ServeHTTP()方法(满足Handler接口),主要用于间接调用它所保存的muxEntry中保存的Handler处理器的ServeHTTP()方法。
HandlerFunc适配器
//TheHandlerFunctypeisanadaptertoallowtheuseof //ordinaryfunctionsasHTTPhandlers.Iffisafunction //withtheappropriatesignature,HandlerFunc(f)isa //Handlerthatcallsf. typeHandlerFuncfunc(ResponseWriter,*Request) //ServeHTTPcallsf(w,r). func(fHandlerFunc)ServeHTTP(wResponseWriter,r*Request){ f(w,r) }
- 自行定义的处理函数转换为Handler类型就是HandlerFunc调用之后的结果,这个类型默认就实现了ServeHTTP这个接口,即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。
Server
除了ServeMux和Handler,还有一个结构Server需要了解。从http.ListenAndServe的源码可以看出,它创建了一个server对象,并调用server对象的ListenAndServe方法:
funcListenAndServe(addrstring,handlerHandler)error{ server:=&Server{Addr:addr,Handler:handler} returnserver.ListenAndServe() }
查看server的结构如下:
typeServerstruct{ Addrstring HandlerHandler ReadTimeouttime.Duration WriteTimeouttime.Duration TLSConfig*tls.Config MaxHeaderBytesint TLSNextProtomap[string]func(*Server,*tls.Conn,Handler) ConnStatefunc(net.Conn,ConnState) ErrorLog*log.Logger disableKeepAlivesint32nextProtoOncesync.Once nextProtoErrerror }
server结构存储了服务器处理请求常见的字段。其中Handler字段也保留Hander接口。如果Server接口没有提供Handler结构对象,那么会使用DefaultServeMux做Multiplexer。
创建HTTP服务
创建一个http服务,大致需要经历两个过程,首先需要注册路由,即提供url模式和handler函数的映射,其次就是实例化一个server对象,并开启对客户端的监听。
http.HandleFunc("/",indexHandler) http.ListenAndServe("127.0.0.1:8000",nil)
或者
server:=&Server{Addr:addr,Handler:handler} server.ListenAndServe()
http注册路由
​net/http包暴露的注册路由的api很简单,http.HandleFunc选取了DefaultServeMux作为multiplexer:
funcHandleFunc(patternstring,handlerfunc(ResponseWriter,*Request)){ DefaultServeMux.HandleFunc(pattern,handler) }
​实际上,DefaultServeMux是ServeMux的一个实例。当然http包也提供了NewServeMux方法创建一个ServeMux实例,默认则创建一个DefaultServeMux:
//NewServeMuxallocatesandreturnsanewServeMux. funcNewServeMux()*ServeMux{ returnnew(ServeMux) } //DefaultServeMuxisthedefaultServeMuxusedbyServe. varDefaultServeMux=&defaultServeMux vardefaultServeMuxServeMux
注意,go创建实例的过程中,也可以使用指针方式,即
typeServerstruct{} server:=Server{}
和下面的一样都可以创建Server的实例
varDefaultServerServer varserver=&DefalutServer
因此DefaultServeMux的HandleFunc(pattern,handler)方法实际是定义在ServeMux下的:
func(mux*ServeMux)HandleFunc(patternstring,handlerfunc(ResponseWriter,*Request)){ mux.Handle(pattern,HandlerFunc(handler)) }
上述代码中,HandlerFunc是一个函数类型。同时实现了Handler接口的ServeHTTP方法。使用HandlerFunc类型包装一下路由定义的indexHandler函数,其目的就是为了让这个函数也实现ServeHTTP方法,即转变成一个handler处理器(函数)。
typeHandlerFuncfunc(ResponseWriter,*Request) func(fHandlerFunc)ServeHTTP(wResponseWriter,r*Request){ f(w,r) }
一旦这样做了,就意味着我们的indexHandler函数也有了ServeHTTP方法。此外,ServeMux的Handle方法,将会对pattern和handler函数做一个map映射:
func(mux*ServeMux)Handle(patternstring,handlerHandler){ mux.mu.Lock() defermux.mu.Unlock() ifpattern==""{ panic("http:invalidpattern"+pattern) } ifhandler==nil{ panic("http:nilhandler") } ifmux.m[pattern].explicit{ panic("http:multipleregistrationsfor"+pattern) } ifmux.m==nil{ mux.m=make(map[string]muxEntry) } mux.m[pattern]=muxEntry{explicit:true,h:handler,pattern:pattern} ifpattern[0]!='/'{ mux.hosts=true } n:=len(pattern) ifn>0&&pattern[n-1]=='/'&&!mux.m[pattern[0:n-1]].explicit{ path:=pattern ifpattern[0]!='/'{ path=pattern[strings.Index(pattern,"/"):] } url:=&url.URL{Path:path} mux.m[pattern[0:n-1]]=muxEntry{h:RedirectHandler(url.String(),StatusMovedPermanently),pattern:pattern} } }
由此可见,Handle函数的主要目的在于把handler和pattern模式绑定到map[string]muxEntry的map上,其中muxEntry保存了更多pattern和handler的信息。Server的m字段就是map[string]muxEntry这样一个map。
此时,pattern和handler的路由注册完成。
开启监听
注册好路由之后,启动web服务还需要开启服务器监听。http的ListenAndServer方法中可以看到创建了一个Server对象,并调用了Server对象的同名方法:
funcListenAndServe(addrstring,handlerHandler)error{ server:=&Server{Addr:addr,Handler:handler} returnserver.ListenAndServe() } func(srvServer)ListenAndServe()error{ addr:=srv.Addr ifaddr==""{ addr=":http" } ln,err:=net.Listen("tcp",addr) iferr!=nil{ returnerr } returnsrv.Serve(tcpKeepAliveListener{ln.(net.TCPListener)}) }
Server的ListenAndServer方法中,会初始化监听地址Addr,同时调用Listen方法设置监听。最后将监听的TCP对象传入Serve方法:
func(srv*Server)Serve(lnet.Listener)error{ deferl.Close() ... baseCtx:=context.Background() ctx:=context.WithValue(baseCtx,ServerContextKey,srv) ctx=context.WithValue(ctx,LocalAddrContextKey,l.Addr()) for{ rw,e:=l.Accept() ... c:=srv.newConn(rw) c.setState(c.rwc,StateNew)//beforeServecanreturn goc.serve(ctx) } }
处理请求
监听开启之后,一旦客户端请求到底,go就开启一个协程处理请求,主要逻辑都在server方法之中。
serve方法比较长,其主要职能就是,创建一个上下文对象,然后调用Listener的Accept方法用来获取连接数据并使用newConn方法创建连接对象。最后使用goroutein协程的方式处理连接请求。因此每一个连接都开启了一个协程,请求的上下文都不同,同时又保证了go的高并发。serve也是一个长长的方法:
func(c*conn)serve(ctxcontext.Context){ c.remoteAddr=c.rwc.RemoteAddr().String() deferfunc(){ iferr:=recover();err!=nil{ constsize=64<<10 buf:=make([]byte,size) buf=buf[:runtime.Stack(buf,false)] c.server.logf("http:panicserving%v:%v\n%s",c.remoteAddr,err,buf) } if!c.hijacked(){ c.close() c.setState(c.rwc,StateClosed) } }() ... for{ w,err:=c.readRequest(ctx) ifc.r.remain!=c.server.initialReadLimitSize(){ //Ifwereadanybytesoffthewire,we'reactive. c.setState(c.rwc,StateActive) } ... } ... serverHandler{c.server}.ServeHTTP(w,w.req) w.cancelCtx() ifc.hijacked(){ return } w.finishRequest() if!w.shouldReuseConnection(){ ifw.requestBodyLimitHit||w.closedRequestBodyEarly(){ c.closeWriteAndWait() } return } c.setState(c.rwc,StateIdle) } }
尽管serve很长,里面的结构和逻辑还是很清晰的,使用defer定义了函数退出时,连接关闭相关的处理。然后就是读取连接的网络数据,并处理读取完毕时候的状态。接下来就是调用serverHandler{c.server}.ServeHTTP(w,w.req)方法处理请求了。最后就是请求处理完毕的逻辑。serverHandler是一个重要的结构,它仅有一个字段,即Server结构,同时它也实现了Hander接口方法ServeHTTP,同时它也实现了Handler接口方法ServeHTTP,并在该接口方法中做了一个重要的事情,初始化multiplexer路由多路复用器。如果server对象没有制定Handler,则使用默认的DefaultServeMux作为路由Multiplexer。并调用初始化Handler的ServeHTTP方法。
typeserverHandlerstruct{ srv*Server } func(shserverHandler)ServeHTTP(rwResponseWriter,reqRequest){ handler:=sh.srv.Handler ifhandler==nil{ handler=DefaultServeMux } ifreq.RequestURI==""&&req.Method=="OPTIONS"{ handler=globalOptionsHandler{} } handler.ServeHTTP(rw,req) }
这里DefaultServeMux的ServeHTTP方法其实也是定义在ServeMux结构中的,相关代码如下:
func(mux*ServeMux)(wResponseWriter,rRequest){ ifr.RequestURI==""{ ifr.ProtoAtLeast(1,1){ w.Header().Set("Connection","close") } w.WriteHeader(StatusBadRequest) return } h,_:=mux.Handler(r) h.ServeHTTP(w,r) } func(mux*ServeMux)Handler(r*Request)(hHandler,patternstring){ ifr.Method!="CONNECT"{ ifp:=cleanPath(r.URL.Path);p!=r.URL.Path{ _,pattern=mux.handler(r.Host,p) url:=*r.URL url.Path=p returnRedirectHandler(url.String(),StatusMovedPermanently),pattern } } returnmux.handler(r.Host,r.URL.Path) } func(mux*ServeMux)handler(host,pathstring)(hHandler,patternstring){ mux.mu.RLock() defermux.mu.RUnlock() //Host-specificpatterntakesprecedenceovergenericones ifmux.hosts{ h,pattern=mux.match(host+path) } ifh==nil{ h,pattern=mux.match(path) } ifh==nil{ h,pattern=NotFoundHandler(),"" } return } func(mux*ServeMux)match(pathstring)(hHandler,patternstring){ varn=0 fork,v:=rangemux.m{ if!pathMatch(k,path){ continue } ifh==nil||len(k)>n{ n=len(k) h=v.h pattern=v.pattern } } return }
mux的ServeHTTP方法通过调用其Handler方法寻找注册到路由上的handler函数,并调用该函数的ServeHTTP方法。
mux的Handler方法对URL简单的处理,然后调用handler方法,后者会创建一个锁,同时调用match方法返回一个handler和pattern。
在match方法中,mux的m字段是map[string]muxEntry,后者存储了pattern和handler处理器函数,因此通过迭代m寻找出注册路由的pattern模式与实际url匹配的handler函数并返回。
返回的结构一直传递到mux的ServeHTTP方法,接下来调用handler函数的ServeHTTP方法,即IndexHandler函数,然后把reponse写到http.RequestWirter对象返回给客户端。
上述函数运行结束即serverHandler{c.server}.ServeHTTP(w,w.req)运行结束。接下来就是对请求处理完毕之后上希望和连接断开的相关逻辑。
至此,Golang中一个完整的http服务介绍完毕,包括注册路由,开启监听,处理连接,路由处理函数。
最后总结一下:
GOHttp执行流程
首先调用Http.HandleFunc
按顺序做了几件事:
- 调用了DefaultServeMux的HandleFunc
- 调用了DefaultServeMux的Handle
- 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则
其次调用http.ListenAndServe(":9090",nil)-nil使用默认路由器 按顺序做了几件事情:
- 实例化Server
- 调用Server的ListenAndServe()
- 调用net.Listen("tcp",addr)监听端口
- 启动一个for循环,在循环体中Accept请求
- 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务goc.serve()
- 读取每个请求的内容w,err:=c.readRequest()
- 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux
- 调用handler的ServeHttp
- 在这个例子中,下面就进入到DefaultServeMux.ServeHttp
- 根据request选择handler,并且进入到这个handler的ServeHTTPmux.handler(r).ServeHTTP(w,r) 11选择handler:
A判断是否有路由能满足这个request(循环遍历ServeMux的muxEntry) B如果有路由满足,调用这个路由handler的ServeHTTP C如果没有路由满足,调用NotFoundHandler的ServeHTTP
原文链接:https://www.jianshu.com/p/4e8cdf3b2f88
原文链接: