http 是典型的 C/S 架构,客户端向服务端发送请求(request),服务端作出应答(response)。html
golang 的标准库 net/http
提供了 http 编程有关的接口,封装了内部TCP链接和报文解析的复杂琐碎的细节,使用者只须要和 http.request
和 http.ResponseWriter
两个对象交互就行。也就是说,咱们只要写一个 handler,请求会经过参数传递进来,而它要作的就是根据请求的数据作处理,把结果写到 Response 中。废话很少说,来看看 hello world 程序有多简单吧!git
package main import ( "io" "net/http" ) type helloHandler struct{} func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, world!")) } func main() { http.Handle("/", &helloHandler{}) http.ListenAndServe(":12345", nil) }
运行 go run hello_server.go
,咱们的服务器就会监听在本地的 12345
端口,对全部的请求都会返回 hello, world!
:github
正如上面程序展现的那样,咱们只要实现的一个 Handler,它的接口原型是(也就是说只要实现了 ServeHTTP
方法的对象均可以做为 Handler):golang
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
而后,注册到对应的路由路径上就 OK 了。正则表达式
http.HandleFunc
接受两个参数:第一个参数是字符串表示的 url 路径,第二个参数是该 url 实际的处理对象。编程
http.ListenAndServe
监听在某个端口,启动服务,准备接受客户端的请求(第二个参数这里设置为 nil
,这里也不要纠结什么意思,后面会有讲解)。每次客户端有请求的时候,把请求封装成 http.Request
,调用对应的 handler 的 ServeHTTP
方法,而后把操做后的http.ResponseWriter
解析,返回到客户端。json
上面的代码没有什么问题,可是有一个不便:每次写 Handler 的时候,都要定义一个类型,而后编写对应的 ServeHTTP
方法,这个步骤对于全部 Handler 都是同样的。重复的工做老是能够抽象出来,net/http
也正这么作了,它提供了 http.HandleFunc
方法,容许直接把特定类型的函数做为 handler。上面的代码能够改为:后端
package main import ( "io" "net/http" ) func helloHandler(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "hello, world!\n") } func main() { http.HandleFunc("/", helloHandler) http.ListenAndServe(":12345", nil) }
其实,HandleFunc
只是一个适配器,浏览器
// The HandlerFunc type is an adapter to allow the use of // ordinary functions as HTTP handlers. If f is a function // with the appropriate signature, HandlerFunc(f) is a // Handler object that calls f. type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
自动给 f
函数添加了 HandlerFunc
这个壳,最终调用的仍是 ServerHTTP
,只不过会直接使用 f(w, r)
。这样封装的好处是:使用者能够专一于业务逻辑的编写,省去了不少重复的代码处理逻辑。若是只是简单的 Handler,会直接使用函数;若是是须要传递更多信息或者有复杂的操做,会使用上部分的方法。服务器
若是须要咱们本身写的话,是这样的:
package main import ( "io" "net/http" ) func helloHandler(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "hello, world!\n") } func main() { // 经过 HandlerFunc 把函数转换成 Handler 接口的实现对象 hh := http.HandlerFunc(helloHandler) http.Handle("/", hh) http.ListenAndServe(":12345", nil) }
大部分的服务器逻辑都须要使用者编写对应的 Handler,不过有些 Handler 使用频繁,所以 net/http
提供了它们的实现。好比负责文件 hosting 的 FileServer
、负责 404 的NotFoundHandler
和 负责重定向的RedirectHandler
。下面这个简单的例子,把当前目录全部文件 host 到服务端:
package main import ( "net/http" ) func main() { http.ListenAndServe(":12345", http.FileServer(http.Dir("."))) }
强大吧!只要一行逻辑代码就能实现一个简单的静态文件服务器。从这里能够看出一件事:http.ListenAndServe
第二个参数就是一个 Handler 函数(请记住这一点,后面有些内容依赖于这个)。
运行这个程序,在浏览器中打开 http://127.0.0.1:12345
,能够看到全部的文件,点击对应的文件还能看到它的内容。
其余两个 Handler,这里就再也不举例子了,读者能够自行参考文档。
虽然上面的代码已经工做,而且能实现不少功能,可是实际开发中,HTTP 接口会有许多的 URL 和对应的 Handler。这里就要讲 net/http
的另一个重要的概念:ServeMux
。Mux
是 multiplexor
的缩写,就是多路传输的意思(请求传过来,根据某种判断,分流到后端多个不一样的地方)。ServeMux
能够注册多了 URL 和 handler 的对应关系,并自动把请求转发到对应的 handler 进行处理。咱们仍是来看例子吧:
package main import ( "io" "net/http" ) func helloHandler(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "Hello, world!\n") } func echoHandler(w http.ResponseWriter, r *http.Request) { io.WriteString(w, r.URL.Path) } func main() { mux := http.NewServeMux() mux.HandleFunc("/hello", helloHandler) mux.HandleFunc("/", echoHandler) http.ListenAndServe(":12345", mux) }
这个服务器的功能也很简单:若是在请求的 URL 是 /hello
,就返回 hello, world!
;不然就返回 URL 的路径,路径是从请求对象 http.Requests
中提取的。
这段代码和以前的代码有两点区别:
NewServeMux
生成了 ServerMux
结构,URL 和 handler 是经过它注册的http.ListenAndServe
方法第二个参数变成了上面的 mux
变量还记得咱们以前说过,http.ListenAndServe
第二个参数应该是 Handler 类型的变量吗?这里为何能传过来 ServeMux
?嗯,估计你也猜到啦:ServeMux
也是是 Handler
接口的实现,也就是说它实现了 ServeHTTP
方法,咱们来看一下:
type ServeMux struct { // contains filtered or unexported fields } func NewServeMux() *ServeMux func (mux *ServeMux) Handle(pattern string, handler Handler) func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
哈!果真,这里的方法咱们大都很熟悉,除了 Handler()
返回某个请求的 Handler。Handle
和 HandleFunc
这两个方法 net/http
也提供了,后面咱们会说明它们之间的关系。而 ServeHTTP
就是 ServeMux
的核心处理逻辑:根据传递过来的 Request,匹配以前注册的 URL 和处理函数,找到最匹配的项,进行处理。能够说ServeMux
是个特殊的 Handler,它负责路由和调用其余后端 Handler 的处理方法。
关于ServeMux
,有几点要说明:
/
:表示一个子树,后面能够跟其余子路径; 末尾不是 /
,表示一个叶子,固定的路径/
结尾的 URL 能够匹配它的任何子路径,好比 /images
会匹配 /images/cute-cat.jpg
ServeMux
也会识别和处理 .
和 ..
,正确转换成对应的 URL 地址你可能会有疑问?咱们之间为何没有使用 ServeMux
就能实现路径功能?那是由于net/http
在后台默认建立使用了 DefaultServeMux
。
嗯,上面基本覆盖了编写 HTTP 服务端须要的全部内容。这部分就分析一下,它们的源码实现,加深理解,之后遇到疑惑也能经过源码来定位和解决。
首先来看 http.ListenAndServe()
:
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
这个函数其实也是一层封装,建立了 Server
结构,并调用它的 ListenAndServe
方法,那咱们就跟进去看看:
// A Server defines parameters for running an HTTP server. // The zero value for Server is a valid configuration. type Server struct { Addr string // TCP address to listen on, ":http" if empty Handler Handler // handler to invoke, http.DefaultServeMux if nil ...... } // ListenAndServe listens on the TCP network address srv.Addr and then // calls Serve to handle requests on incoming connections. If // srv.Addr is blank, ":http" is used. func (srv *Server) ListenAndServe() error { 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
保存了运行 HTTP 服务须要的参数,调用 net.Listen
监听在对应的 tcp 端口,tcpKeepAliveListener
设置了 TCP 的 KeepAlive
功能,最后调用 srv.Serve()
方法开始真正的循环逻辑。咱们再跟进去看看 Serve
方法:
// Serve accepts incoming connections on the Listener l, creating a // new service goroutine for each. The service goroutines read requests and // then call srv.Handler to reply to them. func (srv *Server) Serve(l net.Listener) error { defer l.Close() var tempDelay time.Duration // how long to sleep on accept failure // 循环逻辑,接受请求并处理 for { // 有新的链接 rw, e := l.Accept() if e != nil { 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 = 0 // 建立 Conn 链接 c, err := srv.newConn(rw) if err != nil { continue } c.setState(c.rwc, StateNew) // before Serve can return // 启动新的 goroutine 进行处理 go c.serve() } }
最上面的注释也说明了这个方法的主要功能:
Listener l
传递过来的请求srv.Handler
func (c *conn) serve() { origConn := c.rwc // copy it before it's set nil on Close or Hijack ... for { w, err := c.readRequest() if c.lr.N != c.server.initialLimitedReaderSize() { // 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. serverHandler{c.server}.ServeHTTP(w, w.req) w.finishRequest() if w.closeAfterReply { if w.requestBodyLimitHit { c.closeWriteAndWait() } break } c.setState(c.rwc, StateIdle) } }
看到上面这段代码 serverHandler{c.server}.ServeHTTP(w, w.req)
这一句了吗?它会调用最先传递给 Server
的 Handler 函数:
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) }
哇!这里看到 DefaultServeMux
了吗?若是没有 handler 为空,就会使用它。handler.ServeHTTP(rw, req)
,Handler 接口都要实现 ServeHTTP
这个方法,由于这里就要被调用啦。
也就是说,不管如何,最终都会用到 ServeMux
,也就是负责 URL 路由的家伙。前面也已经说过,它的 ServeHTTP
方法就是根据请求的路径,把它转交给注册的 handler 进行处理。此次,咱们就在源码层面一探究竟。
咱们已经知道,ServeMux
会以某种方式保存 URL 和 Handlers 的对应关系,下面咱们就从代码层面来解开这个秘密:
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry // 存放路由信息的字典!\(^o^)/ hosts bool // whether any patterns contain hostnames } type muxEntry struct { explicit bool h Handler pattern string }
没错,数据结构也比较直观,和咱们想象的差很少,路由信息保存在字典中,接下来就看看几个重要的操做:路由信息是怎么注册的?ServeHTTP
方法究竟是怎么作的?路由查找过程是怎样的?
// Handle registers the handler for the given pattern. // If a handler already exists for pattern, Handle panics. func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() // 边界状况处理 if pattern == "" { panic("http: invalid pattern " + pattern) } if handler == nil { panic("http: nil handler") } if mux.m[pattern].explicit { panic("http: multiple registrations for " + pattern) } // 建立 `muxEntry` 并添加到路由字典中 mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern} if pattern[0] != '/' { mux.hosts = true } // 这是一个颇有用的小技巧,若是注册了 `/tree/`, `serveMux` 会自动添加一个 `/tree` 的路径并重定向到 `/tree/`。固然这个 `/tree` 路径会被用户显示的路由信息覆盖。 // Helpful behavior: // If pattern is /tree/, insert an implicit permanent redirect for /tree. // It can be overridden by an explicit registration. n := len(pattern) if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit { // If pattern contains a host name, strip it and use remaining // path for redirect. path := pattern if pattern[0] != '/' { // In pattern, at least the last character is a '/', so // strings.Index can't be -1. path = pattern[strings.Index(pattern, "/"):] } mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently), pattern: pattern} } }
路由注册没有什么特殊的地方,很简单,也符合咱们的预期,注意最后一段代码对相似/tree
URL 重定向的处理。
// 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) }
好吧,ServeHTTP
也只是经过 mux.Handler(r)
找到请求对应的 handler,调用它的ServeHTTP
方法,代码比较简单咱们就显示了,它最终会调用 mux.match()
方法,咱们来看一下它的实现:
// Does path match pattern? func pathMatch(pattern, path string) bool { if len(pattern) == 0 { // should not happen return false } n := len(pattern) if pattern[n-1] != '/' { return pattern == path } // 匹配的逻辑很简单,path 前面的字符和 pattern 同样就是匹配 return len(path) >= n && path[0:n] == pattern } // 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) { var n = 0 for k, v := range mux.m { if !pathMatch(k, path) { continue } // 最长匹配的逻辑在这里 if h == nil || len(k) > n { n = len(k) h = v.h pattern = v.pattern } } return }
match
会遍历路由信息字典,找到全部匹配该路径最长的那个。路由部分的代码解释就到这里了,最后回答上面的一个问题:http.HandleFunc
和 ServeMux.HandlerFunc
是什么关系?
// Handle registers the handler for the given pattern // in the DefaultServeMux. // The documentation for ServeMux explains how patterns are matched. func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) } // HandleFunc registers the handler function for the given pattern // in the DefaultServeMux. // The documentation for ServeMux explains how patterns are matched. func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
原来是直接经过 DefaultServeMux
调用对应的方法,到这里上面的一切都串起来了!
最后一部分,要讲讲 Handler 函数接受的两个参数:http.Request
和http.ResponseWriter
。
Request 就是封装好的客户端请求,包括 URL,method,header 等等全部信息,以及一些方便使用的方法:
// A Request represents an HTTP request received by a server // or to be sent by a client. // // The field semantics differ slightly between client and server // usage. In addition to the notes on the fields below, see the // documentation for Request.Write and RoundTripper. type Request struct { // Method specifies the HTTP method (GET, POST, PUT, etc.). // For client requests an empty string means GET. Method string // URL specifies either the URI being requested (for server // requests) or the URL to access (for client requests). // // For server requests the URL is parsed from the URI // supplied on the Request-Line as stored in RequestURI. For // most requests, fields other than Path and RawQuery will be // empty. (See RFC 2616, Section 5.1.2) // // For client requests, the URL's Host specifies the server to // connect to, while the Request's Host field optionally // specifies the Host header value to send in the HTTP // request. URL *url.URL // The protocol version for incoming requests. // Client requests always use HTTP/1.1. Proto string // "HTTP/1.0" ProtoMajor int // 1 ProtoMinor int // 0 // A header maps request lines to their values. // If the header says // // accept-encoding: gzip, deflate // Accept-Language: en-us // Connection: keep-alive // // then // // Header = map[string][]string{ // "Accept-Encoding": {"gzip, deflate"}, // "Accept-Language": {"en-us"}, // "Connection": {"keep-alive"}, // } // // HTTP defines that header names are case-insensitive. // The request parser implements this by canonicalizing the // name, making the first character and any characters // following a hyphen uppercase and the rest lowercase. // // For client requests certain headers are automatically // added and may override values in Header. // // See the documentation for the Request.Write method. Header Header // Body is the request's body. // // For client requests a nil body means the request has no // body, such as a GET request. The HTTP Client's Transport // is responsible for calling the Close method. // // For server requests the Request Body is always non-nil // but will return EOF immediately when no body is present. // The Server will close the request body. The ServeHTTP // Handler does not need to. Body io.ReadCloser // ContentLength records the length of the associated content. // The value -1 indicates that the length is unknown. // Values >= 0 indicate that the given number of bytes may // be read from Body. // For client requests, a value of 0 means unknown if Body is not nil. ContentLength int64 // TransferEncoding lists the transfer encodings from outermost to // innermost. An empty list denotes the "identity" encoding. // TransferEncoding can usually be ignored; chunked encoding is // automatically added and removed as necessary when sending and // receiving requests. TransferEncoding []string // Close indicates whether to close the connection after // replying to this request (for servers) or after sending // the request (for clients). Close bool // For server requests Host specifies the host on which the // URL is sought. Per RFC 2616, this is either the value of // the "Host" header or the host name given in the URL itself. // It may be of the form "host:port". // // For client requests Host optionally overrides the Host // header to send. If empty, the Request.Write method uses // the value of URL.Host. Host string // Form contains the parsed form data, including both the URL // field's query parameters and the POST or PUT form data. // This field is only available after ParseForm is called. // The HTTP client ignores Form and uses Body instead. Form url.Values // PostForm contains the parsed form data from POST or PUT // body parameters. // This field is only available after ParseForm is called. // The HTTP client ignores PostForm and uses Body instead. PostForm url.Values // MultipartForm is the parsed multipart form, including file uploads. // This field is only available after ParseMultipartForm is called. // The HTTP client ignores MultipartForm and uses Body instead. MultipartForm *multipart.Form ... // RemoteAddr allows HTTP servers and other software to record // the network address that sent the request, usually for // logging. This field is not filled in by ReadRequest and // has no defined format. The HTTP server in this package // sets RemoteAddr to an "IP:port" address before invoking a // handler. // This field is ignored by the HTTP client. RemoteAddr string ... }
Handler 须要知道关于请求的任何信息,都要从这个对象中获取,通常不会直接修改这个对象(除非你很是清楚本身在作什么)!
ResponseWriter 是一个接口,定义了三个方法:
Header()
:返回一个 Header 对象,能够经过它的 Set()
方法设置头部,注意最终返回的头部信息可能和你写进去的不彻底相同,由于后续处理还可能修改头部的值(好比设置Content-Length
、Content-type
等操做)Write()
: 写 response 的主体部分,好比 html
或者 json
的内容就是放到这里的WriteHeader()
:设置 status code,若是没有调用这个函数,默认设置为 http.StatusOK
, 就是 200
状态码// A ResponseWriter interface is used by an HTTP handler to // construct an HTTP response. type ResponseWriter interface { // Header returns the header map that will be sent by WriteHeader. // Changing the header after a call to WriteHeader (or Write) has // no effect. Header() Header // Write writes the data to the connection as part of an HTTP reply. // If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK) // before writing the data. If the Header does not contain a // Content-Type line, Write adds a Content-Type set to the result of passing // the initial 512 bytes of written data to DetectContentType. Write([]byte) (int, error) // WriteHeader sends an HTTP response header with status code. // If WriteHeader is not called explicitly, the first call to Write // will trigger an implicit WriteHeader(http.StatusOK). // Thus explicit calls to WriteHeader are mainly used to // send error codes. WriteHeader(int) }
实际上传递给 Handler 的对象是:
// A response represents the server side of an HTTP response. type response struct { conn *conn req *Request // request for this response wroteHeader bool // reply header has been (logically) written wroteContinue bool // 100 Continue response was written w *bufio.Writer // buffers output in chunks to chunkWriter cw chunkWriter sw *switchWriter // of the bufio.Writer, for return to putBufioWriter // handlerHeader is the Header that Handlers get access to, // which may be retained and mutated even after WriteHeader. // handlerHeader is copied into cw.header at WriteHeader // time, and privately mutated thereafter. handlerHeader Header ... status int // status code passed to WriteHeader ... }
它固然实现了上面提到的三个方法,具体代码就不放到这里了,感兴趣的能够本身去看。
虽然 net/http
提供的各类功能已经知足基本需求了,可是不少时候还不够方便,好比:
ServeMux
变量中虽然这些均可以本身手动去码,但实在很不方便。这部分看看有哪些三方的包,都提供了哪些额外的功能。
alice 的功能很简单——把多个 handler 串联起来,有请求过来的时候,逐个经过这个 handler 进行处理。
alice.New(Middleware1, Middleware2, Middleware3).Then(App)
Gorilla 提供了不少网络有关的组件, Mux 就是其中一个,负责 HTTP 的路由功能。这个组件弥补了上面提到的 ServeMux
的一些缺陷,支持的功能有:
SubRouter
能够实现路由信息的传递ServeMux
彻底兼容r := mux.NewRouter() r.HandleFunc("/products/{key}", ProductHandler) r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
httprouter 和 mux
同样,也是扩展了自带 ServeMux
功能的路由库。它的主要特色是速度快、内存使用少、可扩展性高(使用 radix tree 数据结构进行路由匹配,路由项不少的时候速度也很快)。
package main import ( "fmt" "github.com/julienschmidt/httprouter" "net/http" "log" ) func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Fprint(w, "Welcome!\n") } func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) } func main() { router := httprouter.New() router.GET("/", Index) router.GET("/hello/:name", Hello) log.Fatal(http.ListenAndServe(":8080", router)) }
http middleware 库,支持嵌套的中间件,可以和其余路由库兼容。同时它也自带了很多 middleware 可使用,好比Recovery
、Logger
、Static
。
router := mux.NewRouter() router.HandleFunc("/", HomeHandler) n := negroni.New(Middleware1, Middleware2) // Or use a middleware with the Use() function n.Use(Middleware3) // router goes last n.UseHandler(router) http.ListenAndServe(":3001", n)
这篇文章参考了如下资料: