背景 如果您向客户提供银行服务,则可能必须与第三方(例如其他银行和支付提供商)集成。为了保证高级别的安全性,参与方通常要求: 对通信通道进行加密 限制目的地和来源 对于Web服务: 可通过使用具有传输层安全性(TLS)或虚拟专用网络(VPN)连接的HTTPS来解决加密问题。 目的地限制是由防火墙和负载平衡器通过端口和数据包筛选来强制实施的。 来源限制是由静态IP地址(例如,亚马逊网络服务(AWS)提供的弹性IP(EIP)或Google Cloud Platform(GCP)提供的静态外部IP地址)实施的。如果您的服务已经在这些云提供商的范围内运行,则只需将集成服务连接到这些静态IP。 但是,如果您的服务托管在平台即服务(PaaS)提供程序(例如Heroku)上,则必须考虑其他集成工作,以满足起源限制。这是HTTPS代理用来提供封装的。 HTTP/S 代理是什么 代理是一种服务器或应用程序,可充当从客户端到目的地请求的中介。HTTP/S代理是支持HTTP请求转发(forwarding)和HTTP隧道(tunneling)的代理。它通常在开放系统互连(OSI)第7层上运行,这与通常在OSI第3层上运行的网络地址转换(NAT)应用程序区分开来。像HTTP隧道这种代理,有很多备用名称,例如网关(gateway)或透明代理(transparent proxy)。要代理HTTP/S请求,可以在两种类型之间进行广泛选择:转发代理(Forwarding Proxy)和反向代理(Reverse Proxy)。 转发代理 转发代理可以有两种变体。 复制 一种变体称为复制(replication),其中代理端终止传入的客户端请求,创建自己的到目的地的传出请求,然后以目的地回复来响应客户端。这适用于HTTP,但不适用于HTTPS,因为它会附带一个重要的副作用,即能够检查(和修改)客户端的请求,这会破坏TLS完整性。 隧道 另一个变种称为隧道(tunneling),其中代理端通过HTTP CONNECT方法使用HTTP隧道功能。这样,代理端就接受了一个初始CONNECT请求,该请求将整个URL当作HOST值,而不仅仅是主机地址。然后,代理端打开到目的地的TCP连接,并透明地将原始通信从客户端转发到目的地。这允许代理端仅检查和评估来自客户端的初始CONNECT请求,例如授权。但是,任何进一步的通信都不会被拦截,因此代理端无法读取TLS通信。这允许初始的连接代理端的CONNECT方法是HTTP或HTTPS,然后是通过代理端的HTTPS到目的地。 HTTP会话示例 > CONNECT example.host.com:443 HTTP/1.1 > Host: example.host.com:443 > Proxy-Authorization: Basic base64-encoded-proxy-credentials > Proxy-Connection: Keep-Alive < HTTP/1.1 200 OK > GET /foo/bar?baz#qux HTTP/1.1 > Host: example.host.com > Authorization: Basic base64-encoded-destination-credentials < HTTP/1.1 200 OK < Connection: close 在这两种变体中,客户端都会直接影响目的地的选择,因为涉及两个不同的URL:代理端的URL和目的地的URL。 反向代理 在计算机网络中,反向代理是一种代理服务器,它代表一个客户端从一个或多个服务器检索资源。然后,将这些资源返回给客户端,就好像它们源自Web服务器本身一样。 反向代理接受来自客户端的传入请求,并根据该请求将它们路由到特定的目的地。目的地选择的区分因素可能是请求的主机,路径,查询参数,任何标头,甚至是请求体的有效负载。无论如何,反向代理都会拦截并终止HTTP和HTTPS连接,并向目的地创建新请求。因此,在使用反向代理时,客户端不会直接影响目的地选择。 小结 现在我们知道了不同的HTTP/S代理类型和变体,可以评估我们的要求并选择适当的代理实现:如果希望完全控制目的地的选择,那么选择转发代理,并且要保证高度的隐私和完整性,因此选择HTTP隧道。 示例 Golang的net/http 包中有客户端-服务器通信所需的大多数实现。您需要做的就是了解功能,并在需要时使用它。 转发代理示例 生成证书 #!/usr/bin/env bash case `uname -s` in Linux*) sslConfig=/etc/ssl/openssl.cnf;; Darwin*) sslConfig=/System/Library/OpenSSL/openssl.cnf;; esac openssl req \ -newkey rsa:2048 \ -x509 \ -nodes \ -keyout server.key \ -new \ -out server.pem \ -subj /CN=localhost \ -reqexts SAN \ -extensions SAN \ -config <(cat $sslConfig \ <(printf '[SAN]\nsubjectAltName=DNS:localhost')) \ -sha256 \ -days 3650 代理程序 package main import ( "crypto/tls" "flag" "io" "log" "net" "net/http" "time" ) func handleTunneling(w http.ResponseWriter, r *http.Request) { dest_conn, err := net.DialTimeout("tcp", r.Host, 10*time.Second) if err != nil { http.Error(w, err.Error(), http.StatusServiceUnavailable) return } w.WriteHeader(http.StatusOK) hijacker, ok := w.(http.Hijacker) if !ok { http.Error(w, "Hijacking not supported", http.StatusInternalServerError) return } client_conn, _, err := hijacker.Hijack() if err != nil { http.Error(w, err.Error(), http.StatusServiceUnavailable) } go transfer(dest_conn, client_conn) go transfer(client_conn, dest_conn) } func transfer(destination io.WriteCloser, source io.ReadCloser) { defer destination.Close() defer source.Close() io.Copy(destination, source) } func handleHTTP(w http.ResponseWriter, req *http.Request) { resp, err := http.DefaultTransport.RoundTrip(req) if err != nil { http.Error(w, err.Error(), http.StatusServiceUnavailable) return } defer resp.Body.Close() copyHeader(w.Header(), resp.Header) w.WriteHeader(resp.StatusCode) io.Copy(w, resp.Body) } func copyHeader(dst, src http.Header) { for k, vv := range src { for _, v := range vv { dst.Add(k, v) } } } func main() { var pemPath string flag.StringVar(&pemPath, "pem", "server.pem", "path to pem file") var keyPath string flag.StringVar(&keyPath, "key", "server.key", "path to key file") var proto string flag.StringVar(&proto, "proto", "https", "Proxy protocol (http or https)") flag.Parse() if proto != "http" && proto != "https" { log.Fatal("Protocol must be either http or https") } server := &http.Server{ Addr: ":8888", Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodConnect { handleTunneling(w, r) } else { handleHTTP(w, r) } }), // Disable HTTP/2. TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), } if proto == "http" { log.Fatal(server.ListenAndServe()) } else { log.Fatal(server.ListenAndServeTLS(pemPath, keyPath)) } } // 升级版 package main import ( "crypto/tls" "fmt" "net/http" "net/http/httputil" "net/url" ) func main() { u, err := url.Parse("https://localhost:8888") if err != nil { panic(err) } tr := &http.Transport{ Proxy: http.ProxyURL(u), // Disable HTTP/2. TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), } client := &http.Client{Transport: tr} resp, err := client.Get("https://google.com") if err != nil { panic(err) } defer resp.Body.Close() dump, err := httputil.DumpResponse(resp, true) if err != nil { panic(err) } fmt.Printf("%q", dump) } 测试 Chrome --proxy-server=https://localhost:8888 curl -Lv --proxy https://localhost:8888 --proxy-cacert server.pem https://google.com 反向代理示例一 已经有singleHostReverseProxy函数,在httputil包中。 func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy 我们只需要发送一个URL到返回reverseProxy的函数即可。 func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) 然后,如果我们调用ServeHTTP函数,它将http请求发送到适当的服务。 用例 假设有一个可通过某个端口的http访问的服务,但是不希望公开该服务,或者想要添加一些自定义规则,或者想要在界面上进行一些性能分析,则可以使用反向代理。 在此示例中,我们将通过将所有Web请求从一台服务器(运行在任意的9090端口上)转发到某处的另一台服务器来演示反向代理的作用,例如 <http://127.0.0.1:8080> : 反向代理golang服务器将在9090端口上运行。 对服务器的所有请求将透明地转发到目标Web服务器,并将响应发送到第一台服务器。 在转发请求之前,将捕获请求正文。借助ioutil包的ReadAll和NopCloser函数,该函数有助于在不修改请求缓冲区的情况下复制请求正文。 还捕获了原始Web服务器为每个路径提供请求所花费的时间。 路由流量 创建一个称为Prox的简单结构,它将处理反向代理的业务逻辑。 type Prox struct { target *url.URL proxy *httputil.ReverseProxy } func NewProxy(target string) *Prox { url, _ := url.Parse(target) return &Prox{ target: url, proxy: httputil.NewSingleHostReverseProxy(url) } } 如您所见,没有太多代码。我们只需要发送目标url,NewProxy函数将返回Prox结构对象。 func (p *Prox) handle(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-GoProxy", "GoProxy") p.proxy.Transport = &myTransport{} p.proxy.ServeHTTP(w, r) } 由于我们要捕获请求/响应主体或为每个请求添加一些自定义规则,因此我们不得不更改传输接口。因此,我们为Transport创建了自己的RoundTrip函数并将其分配给我们之前创建的proxy.Transport。 type myTransport struct { } func (t *myTransport) RoundTrip(request *http.Request) (*http.Response, error) { buf, _ := ioutil.ReadAll(request.Body) rdr1 := ioutil.NopCloser(bytes.NewBuffer(buf)) rdr2 := ioutil.NopCloser(bytes.NewBuffer(buf)) fmt.Println("Request body : ", rdr1) request.Body = rdr2 response, err := http.DefaultTransport.RoundTrip(request) if err != nil { print("\n\ncame in error resp here", err) return nil, err //Server is not reachable. Server not working } body, err := httputil.DumpResponse(response, true) if err != nil { print("\n\nerror in dumb response") // copying the response body did not work return nil, err } log.Println("Response Body : ", string(body)) return response, err } 借助Go的time包,我们可以测量函数所花费的时间,在这种情况下,我们可以测量原始服务器的响应时间。我们修改了RoundTrip函数,以测量每个路径所花费的时间。我们添加了代码,以便对于特定路径,我们可以得到进行http调用的次数,所有调用花费的时间,平均时间等。 var globalMap = make(map[string]Montioringpath) func (t *myTransport) RoundTrip(request *http.Request) (*http.Response, error) { start := time.Now() response, err := http.DefaultTransport.RoundTrip(request) if err != nil { print("\n\ncame in error resp here", err) return nil, err //Server is not reachable. Server not working } elapsed := time.Since(start) key := request.Method + "-" + request.URL.Path // for example for POST Method with /path1 as url path key=POST-/path1 if val, ok := globalMap[key]; ok { val.Count = val.Count + 1 val.Duration += elapsed.Nanoseconds() val.AverageTime = val.Duration / val.Count globalMap[key] = val //do something here } else { var m Montioringpath m.Path = request.URL.Path m.Count = 1 m.Duration = elapsed.Nanoseconds() m.AverageTime = m.Duration / m.Count globalMap[key] = m } b, err := json.MarshalIndent(globalMap, "", " ") if err != nil { fmt.Println("error:", err) } body, err := httputil.DumpResponse(response, true) if err != nil { print("\n\nerror in dumb response") // copying the response body did not work return nil, err } log.Println("Response Body : ", string(body)) log.Println("Response Time:", elapsed.Nanoseconds()) } Go的Flag 包使得在运行程序时接受命令行参数。我们可以通过命令行参数设置http端口(将在其中运行代理服务器的位置)和重定向url(需要将http请求路由到的位置)。 func main() { const ( defaultPort = ":9090" defaultPortUsage = "default server port, ':9090'" defaultTarget = "http://127.0.0.1:8080" defaultTargetUsage = "default redirect url, 'http://127.0.0.1:8080'" ) // flags port = flag.String("port", defaultPort, defaultPortUsage) redirecturl = flag.String("url", defaultTarget, defaultTargetUsage) flag.Parse() fmt.Println("server will run on :", *port) fmt.Println("redirecting to :", *redirecturl) // proxy proxy := NewProxy(*redirecturl) http.HandleFunc("/proxyServer", ProxyServer) // server redirection http.HandleFunc("/", proxy.handle) log.Fatal(http.ListenAndServe(":"+*port, nil)) } 果在运行程序时未设置参数,则默认端口设置为9090,并且请求将路由到http://127.0.0.1:8080。 反向代理示例二 func main() { //New functionality written in Go http.HandleFunc("/new", func(w http.ResponseWriter, r *http.Request){ fmt.Fprint(w, "New function") }) //gospot.mchampaneri.in ===> localhost:8085 u1, _ := url.Parse("http://localhost:8085/") http.Handle("gospot.mchampaneri.in/", httputil.NewSingleHostReverseProxy(u1)) // www.mchampaneri.in ===> localhost:8081 u2, _ := url.Parse("http://localhost:8081/") http.Handle("www.mchampaneri.in/", httputil.NewSingleHostReverseProxy(u2)) // Start the server http.ListenAndServe(":80", nil) }
原文链接 对于Golang来说,实现一个简单的http server非常容易,只需要短短几行代码。同时有了协程的加持,Go实现的http server能够取得非常优秀的性能。 HTTP服务器 基于HTTP构建的网络应用包括两个端,即客户端(Client)和服务端(Server)。 两个端的交互行为包括从客户端发出request、服务端接受request进行处理并返回response以及客户端处理response。 所以http服务器的工作就在于如何接受来自客户端的request,并向客户端返回response。 服务器在接收到请求时,首先会进入路由(router),这是一个Multiplexer,路由的工作在于为这个request找到对应的处理器(handler),处理器对request进行处理,并构建response。Golang实现的http server同样遵循这样的处理流程。 // 实现一:先利用http.HandleFunc在根路由/上注册了一个indexHandler, // 然后利用http.ListenAndServe开启监听。当有请求过来时,则根据路由执行对应的handler函数。 package main import ( "fmt" "net/http" ) func indexHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello world") } func main() { http.HandleFunc("/", indexHandler) http.ListenAndServe(":8000", nil) } // 实现二:创建结构体实现handler接口 package main import ( "fmt" "net/http" ) type indexHandler struct { content string } func (ih *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, ih.content) } func main() { http.Handle("/", &indexHandler{content: "hello world!"}) http.ListenAndServe(":8001", nil) } Go实现的http服务步骤非常简单,首先注册路由,然后创建服务并开启监听即可。 注册路由 http.HandleFunc和http.Handle都是用于注册路由,可以发现两者的区别在于第二个参数: 前者是一个具有func(w http.ResponseWriter, r *http.Requests)签名的函数, 而后者是一个结构体,该结构体实现了func(w http.ResponseWriter, r *http.Requests)签名的方法。 http.HandleFunc和http.Handle的源码如下: func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) } // 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)) } func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) } 可以看到这两个函数最终都由DefaultServeMux调用Handle方法来完成路由的注册。这里我们遇到两种类型的对象:ServeMux和Handler,我们先说Handler。 Handler Handler是一个接口: type Handler interface { ServeHTTP(ResponseWriter, *Request) } Handler接口中声明了名为ServeHTTP的函数签名,也就是说任何结构只要实现了这个ServeHTTP方法,那么这个结构体就是一个Handler对象。其实go的http服务都是基于Handler进行处理,而Handler对象的ServeHTTP方法也正是用以处理request并构建response的核心逻辑所在。 HandleFunc HandlerFunc实际上是将handler函数做了一个类型转换: type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } HandlerFunc是一个类型,只不过表示的是一个具有func(ResponseWriter, *Request)签名的函数类型,并且这种类型实现了ServeHTTP方法(在ServeHTTP方法中又调用了自身),也就是说这个类型的函数其实就是一个Handler类型的对象。 利用这种类型转换,我们可以将一个handler函数转换为一个Handler对象,而不需要定义一个结构体,再让这个结构实现ServeHTTP方法。 ServeMux Golang中的路由(即Multiplexer)基于ServeMux结构,先看一下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中的字段m,这是一个map,key是路由表达式,value是一个muxEntry结构,muxEntry结构体存储了对应的路由表达式和handler。 值得注意的是,ServeMux也实现了ServeHTTP方法: 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) } 也就是说ServeMux结构体也是Handler对象,只不过ServeMux的ServeHTTP方法不是用来处理具体的request和构建response,而是用来确定路由注册的handler。 注册路由 DefaultServeMux.Handle(pattern, handler) 这里的DefaultServeMux表示一个默认的Multiplexer,当我们没有创建自定义的Multiplexer,则会自动使用一个默认的Multiplexer。 然后再看一下ServeMux的Handle方法具体做了什么: func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() if pattern == "" { panic("http: invalid pattern") } if handler == nil { panic("http: nil handler") } if _, exist := mux.m[pattern]; exist { panic("http: multiple registrations for " + pattern) } if mux.m == nil { mux.m = make(map[string]muxEntry) } // 利用当前的路由和handler创建muxEntry对象 e := muxEntry{h: handler, pattern: pattern} // 向ServeMux的map[string]muxEntry增加新的路由匹配规则 mux.m[pattern] = e // 如果路由表达式以'/'结尾,则将对应的muxEntry对象加入到[]muxEntry中,按照路由表达式长度排序 if pattern[len(pattern)-1] == '/' { mux.es = appendSorted(mux.es, e) } if pattern[0] != '/' { mux.hosts = true } } Handle方法主要做了两件事情: 一个就是向ServeMux的map[string]muxEntry增加给定的路由匹配规则; 然后如果路由表达式以'/'结尾,则将对应的muxEntry对象加入到[]muxEntry中,按照路由表达式长度排序。 自定义ServeMux 我们也可以创建自定义的ServeMux取代默认的DefaultServeMux: package main import ( "fmt" "net/http" ) func indexHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello world") } func htmlHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") html := `<!doctype html> <META http-equiv="Content-Type" content="text/html" charset="utf-8"> <html lang="zh-CN"> <head> <title>Golang</title> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;" /> </head> <body> <div id="app">Welcome!</div> </body> </html>` fmt.Fprintf(w, html) } func main() { mux := http.NewServeMux() mux.Handle("/", http.HandlerFunc(indexHandler)) mux.HandleFunc("/welcome", htmlHandler) http.ListenAndServe(":8001", mux) } NewServeMux()可以创建一个ServeMux实例,之前提到ServeMux也实现了ServeHTTP方法,因此mux也是一个Handler对象。对于ListenAndServe()方法,如果传入的handler参数是自定义ServeMux实例mux,那么Server实例接收到的路由对象将不再是DefaultServeMux而是mux。 开启服务 首先从http.ListenAndServe这个方法开始: func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() } func (srv *Server) ListenAndServe() error { if srv.shuttingDown() { return ErrServerClosed } addr := srv.Addr if addr == "" { addr = ":http" } ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) } 这里先创建了一个Server对象,传入了地址和handler参数,然后调用Server对象ListenAndServe()方法。 看一下Server这个结构体,Server结构体中字段比较多,可以先大致了解一下: type Server struct { Addr string // TCP address to listen on, ":http" if empty Handler Handler // handler to invoke, http.DefaultServeMux if nil TLSConfig *tls.Config ReadTimeout time.Duration ReadHeaderTimeout time.Duration WriteTimeout time.Duration IdleTimeout time.Duration MaxHeaderBytes int TLSNextProto map[string]func(*Server, *tls.Conn, Handler) ConnState func(net.Conn, ConnState) ErrorLog *log.Logger disableKeepAlives int32 // accessed atomically. inShutdown int32 // accessed atomically (non-zero means we're in Shutdown) nextProtoOnce sync.Once // guards setupHTTP2_* init nextProtoErr error // result of http2.ConfigureServer if used mu sync.Mutex listeners map[*net.Listener]struct{} activeConn map[*conn]struct{} doneChan chan struct{} onShutdown []func() } 在Server的ListenAndServe方法中,会初始化监听地址Addr,同时调用Listen方法设置监听。最后将监听的TCP对象传入Serve方法: func (srv *Server) Serve(l net.Listener) error { ... baseCtx := context.Background() // base is always background, per Issue 16220 ctx := context.WithValue(baseCtx, ServerContextKey, srv) for { rw, e := l.Accept() // 等待新的连接建立 ... c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve(ctx) // 创建新的协程处理请求 } } 这里隐去了一些细节,以便了解Serve方法的主要逻辑。首先创建一个上下文对象,然后调用Listener的Accept()等待新的连接建立;一旦有新的连接建立,则调用Server的newConn()创建新的连接对象,并将连接的状态标志为StateNew,然后开启一个新的goroutine处理连接请求。 处理连接 我们继续探索conn的serve()方法,这个方法同样很长,我们同样只看关键逻辑。 func (c *conn) serve(ctx context.Context) { ... for { 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) } ... // HTTP cannot have multiple simultaneous active requests.[*] // Until the server replies to this request, it can't read another, // so we might as well run the handler in this goroutine. // [*] Not strictly true: HTTP pipelining. We could let them all process // in parallel even if their responses need to be serialized. // But we're not going to implement HTTP pipelining because it // was never deployed in the wild and the answer is HTTP/2. serverHandler{c.server}.ServeHTTP(w, w.req) w.cancelCtx() if c.hijacked() { return } w.finishRequest() if !w.shouldReuseConnection() { if w.requestBodyLimitHit || w.closedRequestBodyEarly() { c.closeWriteAndWait() } return } c.setState(c.rwc, StateIdle) // 请求处理结束后,将连接状态置为空闲 c.curReq.Store((*response)(nil))// 将当前请求置为空 ... } } 当一个连接建立之后,该连接中所有的请求都将在这个协程中进行处理,直到连接被关闭。在serve()方法中会循环调用readRequest()方法读取下一个请求进行处理,其中最关键的逻辑就是一行代码: serverHandler{c.server}.ServeHTTP(w, w.req) 进一步解释serverHandler: type serverHandler struct { srv *Server } 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) } 在serverHandler的ServeHTTP()方法里的sh.srv.Handler其实就是我们最初在http.ListenAndServe()中传入的Handler对象,也就是我们自定义的ServeMux对象。如果该Handler对象为nil,则会使用默认的DefaultServeMux。最后调用ServeMux的ServeHTTP()方法匹配当前路由对应的handler方法。 后面的逻辑就相对简单清晰了,主要在于调用ServeMux的match方法匹配到对应的已注册的路由表达式和handler。 // ServeHTTP dispatches the request to the handler whose // pattern most closely matches the request URL. 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) } 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 } // Find a handler on a handler map given a path string. // Most-specific (longest) pattern wins. 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, "" } 在match方法里我们看到之前提到的mux的m字段(类型为map[string]muxEntry)和es(类型为[]muxEntry)。这个方法里首先会利用进行精确匹配,在map[string]muxEntry中查找是否有对应的路由规则存在;如果没有匹配的路由规则,则会利用es进行近似匹配。 之前提到在注册路由时会把以'/'结尾的路由(可称为节点路由)加入到es字段的[]muxEntry中。对于类似/path1/path2/path3这样的路由,如果不能找到精确匹配的路由规则,那么则会去匹配和当前路由最接近的已注册的父节点路由,所以如果路由/path1/path2/已注册,那么该路由会被匹配,否则继续匹配下一个父节点路由,直到根路由/。 由于[]muxEntry中的muxEntry按照路由表达式从长到短排序,所以进行近似匹配时匹配到的节点路由一定是已注册父节点路由中最相近的。 至此,Go实现的http server的大致原理介绍完毕! 总结 Golang通过ServeMux定义了一个多路器来管理路由,并通过Handler接口定义了路由处理函数的统一规范,即Handler都须实现ServeHTTP方法;同时Handler接口提供了强大的扩展性,方便开发者通过Handler接口实现各种中间件。相信大家阅读下来也能感受到Handler对象在server服务的实现中真的无处不在。理解了server实现的基本原理,大家就可以在此基础上阅读一些第三方的http server框架,以及编写特定功能的中间件。