多读go的源码,能够加深对go言语的明白和认知,本日分享一下http相干的源码局部
在不运用第三方库的情况下,我们能够很轻易的的用go完成一个http效劳,
package main
import (
"fmt"
"net/http"
)
func IndexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world ! ")
}
func main() {
http.HandleFunc("/", IndexHandler)
if err := http.ListenAndServe(":9100", nil); err != nil {
panic(err)
}
}
直接在浏览器里接见9100端口就能够返回 hello world !
go已把一切的细节封装好了,我们只需要本身去写Handler完成就够了。源码简朴来讲做了以下几件事:
- 把我们自界说的Handler要领添加到默许路由
DefaultServeMux
的Map里好比:http.HandleFunc("/", IndexHandler)
(btw: go言语的map黑白线程平安的,能够在http源码里看到官方的处置惩罚方式); - 启动一个tcp效劳监听9100端口,守候http挪用;
- 当监听到有http挪用时,启动一个协程来处置惩罚这个要求,这个是go的http效劳快的一个重要原因,把要求内容转换成http.Request, 把以后衔接封装http.RespnseWriter;
- 默许路由
DefaultServeMux
依据request的path找到相应的Handler,把 request和 responseWriter传给Handler 举行营业逻辑处置惩罚,response相应信息write给客户端;
ServeMux & Handler
http 包的默许路由 DefaultServeMux
是 ServeMux
构造休的实例http.HandleFunc("/", IndexHandler)
的挪用,会把path信息和自界说的要领信息生存到 DefaultServeMux
的 m map[string]muxEntry
变量里
我们看一下ServeMux
的界说:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
ServeMux
中生存了path
和Handler
的对应干系,也是路由干系。
Handler
muxEntry
中的 h Handler
对就的就是我们自界说的Handler要领好比,我们本身例子中的要领 func IndexHandler(w http.ResponseWriter, r *http.Request)
仔细的同砚可能会问 Handler是一个接口,然则我们只是界说了一个要领,这是怎样转换的呢?
接口Halder设置了署名划定规矩,也就是我们自界说的处置惩罚要领
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
go言语中一切的自界说范例都能够完成本身的要领,http包是用一个自界说的func往来来往完成了Handler接口,
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
然后在ServerMux
的要领HandleFunc
处置惩罚的时刻会把 handler func(ResponseWriter, *Request)
转换成 HandlerFunc
, 以下所示:
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
ServerMux
构造中另有一个读写锁 mu sync.RWMutex
mu就是用来处置惩罚多线程下map的平安接见的。
查找&挪用 Handler
获得自界说的handler要领,就是去map中依据path婚配获得Handler
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 ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
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, ""
}
ServeMux
完成了 Handler
接口,也是默许的路由挪用的详细划定规矩完成的处所,他的 ServeHTTP
要领处置惩罚方式就是获得自界说的handler要领,并挪用我们自界说的要领:
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
接口Halder设置了署名划定规矩,也就是我们自界说的处置惩罚要领
好比下面的代码,函数IndexHandler就是我们自界说的要领,返回给客户端要求一个 hello world !
字符串。中心要求是怎样挪用到我们自界说的要领的详细逻辑都是http包供应的,然则一点也不神奇,
http.HandleFunc("/", IndexHandler)
// IndexHandler 我们本身界说的Handler要领
func IndexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world ! ")
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
//
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
http ListenAndServe
说完 ServeMux
是怎样连系 Handler
接口,来完成路由和挪用后,就要说一下,http效劳是怎样获得客户端传入的信息,封装requet和rresponse的。
在启动顺序的时刻http.ListenAndServe
, 有两个参数,第一个是指写端口号,第二个是处置惩罚逻辑,若是我们没有给定处置惩罚逻辑,会运用默许的处置惩罚DefaultServeMux
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
ListenAndServe
要领翻开tcp端口举行监听,然后把Listener
传给srv.Serve
要领
func (srv *Server) ListenAndServe() error {
// 省略局部代码 ...
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
详细要说一下 Service
要领,这个要领中,对监听tcp要求,然后把要求的客户端衔接举行封装,
func (srv *Server) Serve(l net.Listener) error {
// 省略局部代码 ...
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, e := l.Accept()
// 省略局部代码 ...
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}
把客户端的要求封装成一个Conn,然后启动一个协程go c.serve(ctx)来处置惩罚这个衔接要求,这就是http包快的一个重要原因
,每个衔接就是一个协程。客户端能够先和效劳器举行衔接,然后应用这个conn来屡次发送http要求,如许,就能够削减每次的举行衔接而提高一些速率。像一些rpc里就是应用这点去完成的双向的stream流,好比我之前的帖子go微效劳框架go-micro深度进修(五) stream 挪用历程详解,他就是竖立一个tcp衔接,然后基于这个conn,发送多个request,返回屡次respose数据。
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
// 省略局部代码 ...
// 轮回读取要求 ...
for {
// 读取要求数据,封装response
w, err := c.readRequest(ctx)
if c.r.remain != c.server.initialReadLimitSize() {
// If we read any bytes off the wire, we're active.
c.setState(c.rwc, StateActive)
}
// 省略局部代码 ...
// 路由挪用自界说的要领,把封装好的responseWrite和 request传到要领内
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
// 省略局部代码 ...
}
}
Comment here is closed