Go语言使用net/http包编写Web 服务器

网友投稿 485 2022-11-13

Go语言使用net/http包编写Web 服务器

Go语言使用net/http包编写Web 服务器

func main() { sayhelloName) // 设置访问的路由 err := nil) // 设置监听的端口 if err != nil { log.Fatal("ListenAndServe: ", err) }}

上面这个代码,我们 build 之后,然后执行 web.exe, 这个时候其实已经在 9090 端口监听 链接请求了。在浏览器输入 Hello astaxie!。可以换一个地址试试:Listen Socket, 监听指定的端口,等待客户端请求到来。Listen Socket 接受客户端的请求,得到 Client Socket, 接下来通过 Client Socket 与客户端通信。处理客户端的请求,首先从 Client Socket 读取 HTTP 请求的协议头,如果是 POST 方法,还可能要读取客户端提交的数据,然后交给相应的 handler 处理请求,handler 处理完毕准备好客户端需要的数据,通过 Client Socket 写给客户端。

如何监听端口?如何接收客户端请求?如何分配 handler?

Go 是通过一个函数 ListenAndServe 来处理这些事情的,这个底层其实这样处理的:初始化一个 server 对象,然后调用了 net.Listen(“tcp”, addr),也就是底层用 TCP 协议搭建了一个服务,然后监控我们设置的端口。下面代码来自 Go 的 包的源码,通过下面的代码我们可以看到整个的 处理过程:

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() // 调用Listener接口中的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 } log.Printf("Accept error: %v; retrying in %v", e, tempDelay) time.Sleep(tempDelay) continue } return e } tempDelay = 0 c, err := srv.newConn(rw) // 创建一个 Conn if err != nil { continue } go c.serve() // 单独开了一个 goroutine,把这个请求的数据当做参数扔给这个 conn 去服务 }}

监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了 srv.Serve(net.Listener) 函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个 for{},首先通过 Listener 接收请求,其次创建一个 Conn,最后单独开了一个 goroutine,把这个请求的数据当做参数扔给这个 conn 去服务:go c.serve()。这个就是高并发体现了,用户的每一次请求都是在一个新的 goroutine 去服务,相互不影响。

那么如何具体分配到相应的函数来处理请求呢?conn 首先会解析 request:c.readRequest(), 然后获取相应的 handler:handler := c.server.Handler,也就是我们刚才在调用函数 ListenAndServe 时候的第二个参数,我们前面例子传递的是 nil,也就是为空,那么默认获取 handler = DefaultServeMux, 那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配 url 跳转到其相应的 handle 函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了 sayhelloName) 嘛。这个作用就是注册了请求 / 的路由规则,当请求 uri 为 “/”,路由就会转到函数 sayhelloName,DefaultServeMux 会调用 ServeHTTP 方法,这个方法内部其实就是调用 sayhelloName 本身,最后通过写入 response 的信息反馈到客户端。

详细的整个流程如下图所示:

路由器ServeMux

type ServeMux struct { mu sync.RWMutex // 锁,由于请求涉及到并发处理,因此这里需要一个锁机制 m map[string]muxEntry // 路由规则,一个 string 对应一个 mux 实体,这里的 string 就是注册的路由表达式 hosts bool // 是否在任意的规则中带有 host 信息}type muxEntry struct { explicit bool // 是否精确匹配 h Handler // 这个路由表达式对应哪个 handler pattern string // 匹配字符串}

接着看一下 Handler 的定义

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)}

Handler 是一个接口,但是前一小节中的 sayhelloName 函数并没有实现 ServeHTTP 这个接口,为什么能添加呢?原来在 包里面还定义了一个类型 HandlerFunc, 我们定义的函数 sayhelloName 就是这个 HandlerFunc 调用之后的结果,这个类型默认就实现了 ServeHTTP 这个接口,即我们调用了 HandlerFunc (f), 强制类型转换 f 成为 HandlerFunc 类型,这样 f 就拥有了 ServeHTTP 方法。

路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?请看下面的代码,默认的路由器实现了 ServeHTTP:

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { w.Header().Set("Connection", "close") w.WriteHeader(StatusBadRequest) return } h, _ := mux.Handler(r) h.ServeHTTP(w, r)}

如上所示路由器接收到请求之后,如果是 * 那么关闭链接,不然调用 mux.Handler® 返回对应设置路由的处理 Handler,然后执行 h.ServeHTTP(w, r)。也就是调用对应路由的 handler 的 ServerHTTP 接口,那么 mux.Handler ® 怎么处理的呢?

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { if r.Method != "CONNECT" { if p := cleanPath(r.URL.Path); p != r.URL.Path { _, pattern = mux.handler(r.Host, p) return RedirectHandler(p, StatusMovedPermanently), pattern } } return mux.handler(r.Host, r.URL.Path)}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}

原来他是根据用户请求的 URL 和路由器里面存储的 map 去匹配的,当匹配到之后返回存储的 handler,调用这个 handler 的 ServeHTTP 接口就可以执行到相应的函数了。

通过上面这个介绍,我们了解了整个路由过程,Go 其实支持外部实现的路由器,ListenAndServe的第二个参数就是用以配置外部路由器的,它是一个 Handler 接口,即外部路由器只要实现了 Handler 接口就可以,我们可以在自己实现的路由器的 ServeHTTP 里面实现自定义路由功能。 如下代码所示,我们自己实现了一个简易的路由器

package mainimport ( "fmt" "net/MyMux struct {}func (p *MyMux) ServeHTTP(w r *{ if r.URL.Path == "/" { sayhelloName(w, r) return } r) return}func sayhelloName(w r *{ fmt.Fprintf(w, "Hello myroute!")}func main() { mux := &MyMux{} mux)}

Go 代码的执行流程

通过对 包的分析之后,现在让我们来梳理一下整个的代码执行过程。 首先调用 Http.HandleFunc,按顺序做了几件事: 1 调用了 DefaultServeMux 的 HandleFunc 2 调用了 DefaultServeMux 的 Handle 3 往 DefaultServeMux 的 map [string] muxEntry 中增加对应的 handler 和路由规则

其次调用 (":9090", nil),按顺序做了几件事情: 1 实例化 Server 2 调用 Server 的 ListenAndServe () 3 调用 net.Listen (“tcp”, addr) 监听端口 4 启动一个 for 循环,在循环体中 Accept 请求 5 对每个请求实例化一个 Conn,并且开启一个 goroutine 为这个请求进行服务 go c.serve () 6 读取每个请求的内容 w, err := c.readRequest () 7 判断 handler 是否为空,如果没有设置 handler(这个例子就没有设置 handler),handler 就设置为 DefaultServeMux 8 调用 handler 的 ServeHttp 9 在这个例子中,下面就进入到 DefaultServeMux.ServeHttp 10 根据 request 选择 handler,并且进入到这个 handler 的 ServeHTTP mux.handler®.ServeHTTP(w, r) 11 选择 handler: A 判断是否有路由能满足这个 request(循环遍历 ServeMux 的 muxEntry) B 如果有路由满足,调用这个路由 handler 的 ServeHTTP C 如果没有路由满足,调用 NotFoundHandler 的 ServeHTTP

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:基于Consumer接口、Predicate接口初使用
下一篇:Barman备份恢复迁移——Before you start
相关文章

 发表评论

暂时没有评论,来抢沙发吧~