英文网站建设服务合同模板下载,太原做企业网站的,企业网站建设课件,最炫的网站Go http服务器编程
初始
http 是典型的 C/S 架构#xff0c;客户端向服务端发送请求#xff08;request#xff09;#xff0c;服务端做出应答#xff08;response#xff09;。
golang 的标准库 net/http 提供了 http 编程有关的接口#xff0c;封装了内部TCP连接和…Go http服务器编程
初始
http 是典型的 C/S 架构客户端向服务端发送请求request服务端做出应答response。
golang 的标准库 net/http 提供了 http 编程有关的接口封装了内部TCP连接和报文解析的复杂琐碎的细节使用者只需要和 http.request 和 http.ResponseWriter 两个对象交互就行。也就是说我们只要写一个 handler请求会通过参数传递进来而它要做的就是根据请求的数据做处理把结果写到 Response 中。废话不多说来看看 hello world 程序有多简单吧
我们有两种写法现在来看一下这两种写法
type helloHandler struct {
}func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {w.Write([]byte(Hello, world!))
}func main() {http.HandleFunc(/way1, func(writer http.ResponseWriter, request *http.Request) {writer.Write([]byte(Hello, world!))})http.Handle(/way2, helloHandler{})http.ListenAndServe(localhost:8080, nil)
}我们先把注意力聚焦到/way2上先暂时不看/way1。
正如上面程序展示的那样我们只要实现的一个 Handler它的接口原型是也就是说只要实现了 ServeHTTP 方法的对象都可以作为 Handler
type Handler interface {ServeHTTP(ResponseWriter, *Request)
}然后注册到对应的路由路径上就 OK 了。
http.HandleFunc接受两个参数第一个参数是字符串表示的 url 路径第二个参数是该 url 实际的处理对象。
http.ListenAndServe 监听在某个端口启动服务准备接受客户端的请求第二个参数这里设置为 nil这里也不要纠结什么意思后面会有讲解。每次客户端有请求的时候把请求封装成 http.Request调用对应的 handler 的 ServeHTTP 方法然后把操作后的 http.ResponseWriter 解析返回到客户端。
封装
/way2没有什么问题但是有一个不便每次写 Handler 的时候都要定义一个类型然后编写对应的 ServeHTTP 方法这个步骤对于所有 Handler 都是一样的。重复的工作总是可以抽象出来net/http 也正这么做了它提供了 http.HandleFunc 方法允许直接把特定类型的函数作为 handler。于是/way2可以改成/way1的方法。
但是实际上/way1的本质还是/way2这种方法。
我们看一下源码就可以知道了
Handler是一个接口
type Handler interface {ServeHTTP(ResponseWriter, *Request)
}Handler 接口中声明了名为 ServeHTTP 的函数签名也就是说任何结构只要实现了这个 ServeHTTP 方法那么这个结构体就是一个 Handler 对象。其实 go 的 http 服务都是基于 Handler 进行处理而 Handler 对象的 ServeHTTP 方法也正是用以处理 request 并构建 response 的核心逻辑所在。
我们现在回到上面的HandleFunc函数注意一下这个代码
mux.Handle(pattern, HandlerFunc(handler))可能有人认为 HandlerFunc 是一个函数包装了传入的 handler 函数返回了一个 Handler 对象。然而这里 HandlerFunc 实际上是将 handler 函数做了一个类型转换看一下 HandlerFunc 的定义
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 方法。读者可以体会一下这种技巧。
路由
虽然上面的代码已经工作并且能实现很多功能但是实际开发中HTTP 接口会有许多的 URL 和对应的 Handler。这里就要讲 net/http 的另外一个重要的概念ServeMux。Mux 是 multiplexor 的缩写就是多路传输的意思请求传过来根据某种判断分流到后端多个不同的地方。ServeMux 可以注册多了 URL 和 handler 的对应关系并自动把请求转发到对应的 handler 进行处理。我们还是来看例子吧
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(localhost:8080, 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 的处理方法。
URL 分为两种末尾是 /表示一个子树后面可以跟其他子路径 末尾不是 /表示一个叶子固定的路径以/ 结尾的 URL 可以匹配它的任何子路径比如 /images 会匹配 /images/cute-cat.jpg它采用最长匹配原则如果有多个匹配一定采用匹配路径最长的那个进行处理如果没有找到任何匹配项会返回 404 错误ServeMux 也会识别和处理 . 和 ..正确转换成对应的 URL 地址
你可能会有疑问我们之间为什么没有使用 ServeMux 就能实现路径功能那是因为 net/http 在后台默认创建使用了 DefaultServeMux。
深入
Server
首先来看 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 emptyHandler 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.Addrif 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 传递过来的请求为每个请求创建 goroutine 进行后台处理goroutine 会读取请求调用 srv.Handler
func (c *conn) serve() {origConn : c.rwc // copy it before its 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, were active.c.setState(c.rwc, StateActive)}...// HTTP cannot have multiple simultaneous active requests.[*]// Until the server replies to this request, it cant 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.Handlerif 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
我们已经知道ServeMux 会以某种方式保存 URL 和 Handlers 的对应关系下面我们就从代码层面来解开这个秘密
type ServeMux struct {mu sync.RWMutexm map[string]muxEntry // 存放路由信息的字典\(^o^)/hosts bool // whether any patterns contain hostnames
}type muxEntry struct {explicit boolh Handlerpattern 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 : patternif pattern[0] ! / {// In pattern, at least the last character is a /, so// strings.Index cant 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 happenreturn 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 0for k, v : range mux.m {if !pathMatch(k, path) {continue}// 最长匹配的逻辑在这里if h nil || len(k) n {n len(k)h v.hpattern 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 调用对应的方法到这里上面的一切都串起来了