深入理解Golang之httprver的实现

更新时间:2023-05-31 21:09:17 阅读: 评论:0

深⼊理解Golang之httprver的实现
前⾔
对于Golang来说,实现⼀个简单的http rver⾮常容易,只需要短短⼏⾏代码。同时有了协程的加持,Go实现的http rver能够取得⾮常优秀的性能。这篇⽂章将会对go标准库net/http实现http服务的原理进⾏较为深⼊的探究,以此来学习了解⽹络编程的常见范式以及设计思路。
HTTP服务
基于HTTP构建的⽹络应⽤包括两个端,即客户端( Client )和服务端( Server )。两个端的交互⾏为包括从客户端发出request、服务端接受request进⾏处理并返回respon以及客户端处理respon。所以http服务器的⼯作就在于如何接受来⾃客户端的request,并向客户端返回respon。
典型的http服务端的处理流程可以⽤下图表⽰:
服务器在接收到请求时,⾸先会进⼊路由( router ),这是⼀个Multiplexer,路由的⼯作在于为这个request找到对应的处理器( handler ),处理器对request进⾏处理,并构建respon。Golang实现的http rver同样遵循这样的处理流程。
我们先看看Golang如何实现⼀个简单的http rver:
package main
import (
"fmt"
"net/http"
)
func indexHandler(w http.ResponWriter, r *http.Request) {
fmt.Fprintf(w, "hello world")
}
func main() {
http.HandleFunc("/", indexHandler)
http.ListenAndServe(":8000", nil)
}
运⾏代码之后,在浏览器中打开localhost:8000就可以看到hello world。这段代码先利⽤http.HandleFunc在根路由/上注册了⼀个indexHandler , 然后利⽤http.ListenAndServe开启监听。当有请求过来时,则根据路由执⾏对应的handler函数。
我们再来看⼀下另外⼀种常见的http rver实现⽅式:
package main
import (
"fmt"
"net/http"
客人英文)
type indexHandler struct {
content string
}
func (ih *indexHandler) ServeHTTP(w http.ResponWriter, r *http.Request) {
fmt.Fprintf(w, ih.content)
}
func main() {
http.Handle("/", &indexHandler{content: "hello world!"})
http.ListenAndServe(":8001", nil)
}
Go实现的http服务步骤⾮常简单,⾸先注册路由,然后创建服务并开启监听即可。下⽂我们将从注册路由、开启服务、处理请求这⼏个步骤了解Golang如何实现http服务。
注册路由
http.HandleFunc和http.Handle都是⽤于注册路由,可以发现两者的区别在于第⼆个参数,前者是⼀个具有func(w
http.ResponWriter, r *http.Requests)签名的函数,⽽后者是⼀个结构体,该结构体实现了func(w http.ResponWriter, r *http.Requests)签名的⽅法。
http.HandleFunc和http.Handle的源码如下:
func HandleFunc(pattern string, handler func(ResponWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponWriter, *Request)) {
if handler == nil {bingo儿歌
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(ResponWriter, *Request)
}
Handler接⼝中声明了名为ServeHTTP的函数签名,也就是说任何结构只要实现了这个ServeHTTP⽅法,那么这个结构体就是⼀个Handler对象。其实go的http服务都是基于Handler进⾏处理,⽽Handler对象的ServeHTTP⽅法也正是⽤以处理request并构建respon的核⼼逻辑所在。
回到上⾯的HandleFunc函数,注意⼀下这⾏代码:
mux.Handle(pattern, HandlerFunc(handler))
可能有⼈认为HandlerFunc是⼀个函数,包装了传⼊的handler函数,返回了⼀个Handler对象。然⽽这⾥HandlerFunc实际上是将handler函数做了⼀个类型转换,看⼀下HandlerFunc的定义:
type HandlerFunc func(ResponWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponWriter, r *Request) {
f(w, r)
}
HandlerFunc是⼀个类型,只不过表⽰的是⼀个具有func(ResponWriter, *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 ResponWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "clo")
精度英文}
w.WriteHeader(StatusBadRequest)
return
三夫之对
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
也就是说ServeMux结构体也是Handler对象,只不过ServeMux的ServeHTTP⽅法不是⽤来处理具体的request和构建respon,⽽是⽤来确定路由注册的handler。
注册路由
搞明⽩Handler和ServeMux之后,我们再回到之前的代码:
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.ResponWriter, r *http.Request) {
fmt.Fprintf(w, "hello world")
}
func htmlHandler(w http.ResponWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
html := `<!doctype html>
demonstrates<META http-equiv="Content-Type" content="text/html" chart="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, ur-scalable=0;" />
</head>
<body>
<div id="app">Welcome!</div>
</body>
</html>`
fmt.Fprintf(w, html)
}
func main() {
demand的用法mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(indexHandler))
mux.HandleFunc("/welcome", htmlHandler)说服性演讲
you make me wanna歌词
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 {
rver := &Server{Addr: addr, Handler: handler}
return rver.ListenAndServe()
}
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClod
}
satioaddr := 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  // accesd atomically.
inShutdown  int32  // accesd atomically (non-zero means we're in Shutdown)
nextProtoOnce  sync.Once // guards tupHTTP2_* init
nextProtoErr  error  // result of http2.ConfigureServer if ud
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 {
...
baCtx := context.Background() // ba is always background, per Issue 16220
ctx := context.WithValue(baCtx, ServerContextKey, srv)
for {
rw, e := l.Accept() // 等待新的连接建⽴
...
c := wConn(rw)
c.tState(c.rwc, StateNew) // before Serve can return
go c.rve(ctx) // 创建新的协程处理请求
}
}
这⾥隐去了⼀些细节,以便了解Serve⽅法的主要逻辑。⾸先创建⼀个上下⽂对象,然后调⽤Listener的Accept()等待新的连接建⽴;⼀旦有新的连接建⽴,则调⽤Server的newConn()创建新的连接对象,并将连接的状态标志为StateNew,然后开启⼀个新的goroutine处理连接请求。
处理连接
我们继续探索conn的rve()⽅法,这个⽅法同样很长,我们同样只看关键逻辑。坚持⼀下,马上就要看见⼤海了。
func (c *conn) rve(ctx context.Context) {
...
for {
w, err := c.readRequest(ctx)
ain != c.rver.initialReadLimitSize() {
// If we read any bytes off the wire, we're active.
c.tState(c.rwc, StateActive)
}
...
// HTTP cannot have multiple simultaneous active requests.[*]
// Until the rver 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 respons need to be rialized.
// But we're not going to implement HTTP pipelining becau it
// was never deployed in the wild and the answer is HTTP/2.
xdc是什么意思
rverHandler{c.rver}.ServeHTTP(w, w.req)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
if !w.shouldReuConnection() {
questBodyLimitHit || w.clodRequestBodyEarly() {

本文发布于:2023-05-31 21:09:17,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/78/822488.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:路由   处理   函数   结构   实现   创建   对象   服务
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图