Go搭建简单服务器及http包源码分析
来源:互联网 发布:js onload 编辑:程序博客网 时间:2024/06/05 14:43
Go搭建web服务器
使用go语言搭建一个简单的web服务器是非常方便的,一个简单的例子如下:
// main.gopackage mainimport ( "fmt" "log" "net/http")func sayhelloName(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello!") // 这个写入到w的是输出到客户端的}func main() { http.HandleFunc("/", sayhelloName) // 设置访问的路由 port := ":9090" // 设置监听的端口 err := http.ListenAndServe(port, nil) // 开始监听 if err != nil { log.Fatal("ListenAndServe: ", err) // 输出错误日志 } fmt.Printf("Server listen at: %s", port)}
运行服务器:
$ go run main.go
运行后,服务器已经在9090端口开始监听http请求了
使用curl工具访问,并查看详细情况:
* Trying ::1...* TCP_NODELAY set* Connected to localhost (::1) port 9090 (#0)> GET / HTTP/1.1> Host: localhost:9090> User-Agent: curl/7.52.1> Accept: */*> < HTTP/1.1 200 OK< Date: Wed, 15 Nov 2017 08:32:16 GMT< Content-Length: 6< Content-Type: text/plain; charset=utf-8< * Curl_http_done: called premature == 0* Connection #0 to host localhost left intactHello!
可以看到,想要实现一个简单的web服务器,仅仅需要调用net/http包中的两个函数HandleFunc()
和ListenAndServe()
。
http包源码分析
下面通过阅读http包源码,并结合上面的例子,详细分析一下,只通过调用HandleFunc()
和ListenAndServe()
两个函数,是怎样使一个web服务器运行起来的。
HandleFunc() HandleFunc()
用于注册路由。在上面的的例子中,即指定:当路径为”/”时,使用sayhelloName()
来处理。
// server.go line 2313// 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)}
源码中调用了DefaulServeMux的HandleFunc,那么再看看DefaultServeMux是什么:
// server.go line 2116// DefaultServeMux is the default ServeMux used by Serve.var DefaultServeMux = &defaultServeMuxvar defaultServeMux ServeMux
定义了ServeMux如下:
// server.go line 2101type ServeMux struct { mu sync.RWMutex m map[string]muxEntry hosts bool // whether any patterns contain hostnames}type muxEntry struct { explicit bool h Handler pattern string}
ServeMux是一个http request的多路复用器(即相当于路由),它根据request的URL,与已经注册的所有patterns相匹配,并根据匹配结果,调用对应的Handler。ServeMux中利用map[string]muxEntry来进行匹配。对于每一个已经注册的pattern(string类型),都能找到一个对应的muxEntry,也就能找到相应的Handler。
再来看一下Handler:
// server.go line 82type Handler interface { ServeHTTP(ResponseWriter, *Request)}
Handler是一个接口类型,所有实现了ServeHTTP的类型都可以作为一个Handler。
上文提到HandleFunc()在注册路由时是调用了DefaulServeMux的HandleFunc(),阅读了ServeMux的源码后,现在来看一下HandleFunc()是什么:
// server.go line 2300// HandleFunc registers the handler function for the given pattern.func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler))}
回顾上文,例子中的sayHelloName()似乎没有实现ServeHTTP,却也能实现接口?这是因为,看以上这部分代码,在将函数传递到下一层时,使用了一次强制类型转换。由于sayHelloName()与HandlerFunc()有相同的参数,因此完成了转换,也就实现了接口。
HandlerFunc()相关源码如下:
// server.go line 1910// 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 that calls f.type HandlerFunc func(ResponseWriter, *Request)// ServeHTTP calls f(w, r).func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r)}
HandleFunc把上一层的两个参数继续传递到mux.Handle()。
//server.go line 2257// 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 mux.m == nil { mux.m = make(map[string]muxEntry) } mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern} if pattern[0] != '/' { mux.hosts = true } ... }}
由于代码较长,我们在这里只看一下最基本的功能部分,省略了一些异常处理,重定向等相关的代码。可以看到,这里注册路由的功能就真正实现了,在map中加入了对应的(patterm, MuxEntry)项,且MuxEntry中存储了对应的pattern和Handler。
以上就是路由注册的过程,也就是HandleFunc()的实现。
假设map中注册好了路由规则后,下面看一下接受到请求后,Handler是如何匹配的:
前面我们提到了这个需要被实现的函数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)}
简单的处理之后,将request传递到mux.Handler():
// server.go line 2203func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { // CONNECT requests are not canonicalized. if r.Method == "CONNECT" { return mux.handler(r.Host, r.URL.Path) } // All other requests have any port stripped and path cleaned // before passing to mux.handler. host := stripHostPort(r.Host) path := cleanPath(r.URL.Path) if path != r.URL.Path { _, pattern = mux.handler(host, path) url := *r.URL url.Path = path return RedirectHandler(url.String(), StatusMovedPermanently), pattern } return mux.handler(host, r.URL.Path)}
处理参数后,继续传递到mux.Hanler(),但这时参数内容已经改变:
// server.go line 2226unc (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}
这里,就真正完成了patter和handler的匹配。可以看到,这就是在我们上文分析的ServeMux中的map来进行匹配的。
ListenAndServe():
完成了路由相关规则的处理后,开始监听端口。
// server.go line 2880func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe()}
ListenAndServe()创建了一个server,并调用它的ListenAndServe()。Server结构体由于代码太长,我们只分析server.IstenAndServe():
// server.go line 2627func (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)})}
可以看到,在ListenAndServe()中,在传输层发起了tcp的监听。
再看看server.Serve():
// server.go line 2678func (srv *Server) Serve(l net.Listener) error { defer l.Close() ... for { rw, e := l.Accept() if e != nil { ... } tempDelay = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return go c.serve(ctx) }}
同样,这里只保留了核心代码,把有关的异常处理,时延等相关代码都省略。可以看到,在一个for循环中,客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息。使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。
至此,例子中的web服务器,基本的代码运行流程就已经分析完了。
- Go搭建简单服务器及http包源码分析
- go语言 通过http包搭建简单web服务器 对http包源码的略微分析
- Go源码分析 net/http包分析:追溯到socket
- Go利用net/http包搭建Web服务器
- go HTTP Client大量长连接保持(自定义client设置及源码简单分析)
- 快速搭建HTTP服务器 go http server
- Go Web编程:http包分析
- Go网络编程之net/http包执行流程源码分析
- Go语言 简单的http服务器示例
- 用go使用简单的http服务器
- Mongoose源码分析之--简单的服务器搭建(C语言)
- Mongoose源码分析之--简单的服务器搭建(C语言)
- go搭建一个简单web服务器
- 搭建一个简单的Go Web服务器
- MQTT入门级服务器搭建及简单通信源码
- CSAPP Tiny web 服务器源码分析及搭建运行
- 用Go语言写一个简单的HTTP服务器,及静态文件服务器
- 搭建简单的http服务器
- HDU 2688
- jvm学习记录--08 Class文件结构
- Cisco链接聚合EtherChannel(PAgP、LACP)详解
- 迁移学习之caffe训练的层设置
- 2017.11.15笔记
- Go搭建简单服务器及http包源码分析
- 全景VR酷炫演示
- GreenDao的使用简介
- 基于selenium-java封装chrome、firefox、phantomjs实现爬虫
- C++编译后报Error spawning 'rc.exe'.错误
- matplotlib如何不显示图片只保存图片
- Github学习(1)——Git的安装与配置
- 设计模式六大原则之----依赖倒置原则
- svn提交403forbidden