1.首先看一个简单的web服务
package main
import (
"io"
"net/http"
"log"
)
// hello world, the web server
func HelloServer(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello, world!\n")
}
func main() {
http.HandleFunc("/hello", HelloServer)
err := http.ListenAndServe(":12345", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
以上代码只依赖go的标准包,没有依赖任何第三方包.
在浏览器中输入\”http://localhost:12345/hello\”就可以看到“Hello world!”.
我们看到上面的代码,要编写一个web服务器很简单,只要调用http包的两个函数就可以
了.
2.分析如何启动这一个服务有两个方面,一个是脱离语言的Web的工作方式,另一个是Go语言的底层实现;Web的工作方式这里不进行讨论,这里讨论Go的实现。
Go的http包的执行流程:
(1) 创建Listen Socket, 监听指定的端口, 等待客户端请求到来。
(2) Listen Socket接受客户端的请求, 得到Client Socket, 接下来通过Client Socket与客户端通信。
(3) 处理客户端的请求, 首先从Client Socket读取HTTP请求的协议头, 如果是POST方法,还可能要读取客户端提交的数据,然后交给相应的handler处理请求,handler处理完毕准备好客户端需要的数据,通过Client Socket写给客户端。
这整个的过程里面我们只要了解清楚下面三个问题,也就知道Go是如何让Web运行起来了
(1)如何监听端口?
(2)如何接收客户端请求?
(3)如何分配handler?
其实这三个功能在http.ListenAndServe(“:12345”, nil)这一个函数中就实现了,通过查看Go的源码,这个函数实现如下:
// ListenAndServe listens on the TCP network address addr
// and then calls Serve with handler to handle requests
// on incoming connections. Handler is typically nil,
// in which case the DefaultServeMux is used.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
生成了一个Server对象,接着调用Server对象的ListenAndServe()方法
// 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)})
}
调用了net.Listen(“tcp”,addr),也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。(TCP协议我基本不知道,而且似乎不知道对本问题也没有什么影响)
再看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
c, err := srv.newConn(rw)
if err != nil {
continue
}
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve()
}
}
该代码启动一个for的死循环,首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:go c.serve()。这个就是高并发的体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。for循环中的其他代码主要是实现了一旦接收请求出错,隔一段时间之后继续进入循环,不会因为一次出错导致程序退出。
那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request:c.readRequest(),然后获取相应的handler:handler := c.server.Handler,也就是我们刚才在调用函数ListenAndServe时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认handler=DefaultServeMux,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数。那么这个我们有设置过吗?有,我们调用的代码里面第一句调用了http.HandleFunc(“/”, HelloServer),这个作用就是注册了请求/的路由规则,当请求url为”/”,路由就会转到函数HelloServer,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用HelloServer本身,最后通过写入response的信息反馈到客户端。注意,这只是一个路由器,用来分配handler,下面我们来看这个路由器是如何实现的。
3.路由器的实现
在这个例子中,http.HandleFunc(“/hello”, HelloServer) 这行代码注册了指定路径下的处理函数。
type 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
}
// Objects implementing the Handler interface can be
// registered to serve a particular path or subtree
// in the HTTP server.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished
// and that the HTTP server can move on to the next request on
// the connection.
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and hangs up the connection.
//
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
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)
}
map[string]muxEntry,保存了路径名对应的方法,一个路径对应一个方法;方法实体muxEntry中最关键的方法属性Handler,
下面是Handler的定义,其中有一个方法:ServeHTTP(ResponseWriter,*Request)。这个方法就是我们传入的sayhello方法,这个方法是我们真正要做的业务逻辑方法,他的参数只有两个,一个是用户的请求信息,一个用来写入我们处理完业务逻辑之后的结果。通过func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request),我们可以看到ServeMux也实现了Handler接口,但ServeMux是一个特殊的Handler接口,他并不处理实际的业务逻辑,而是在他内部存储的Handler字典中根据路由信息找到合适的Handler来处理。到这里就解决了我们之前提的问题:c.serve()就是调用了ServeMux的ServeHTTP方法找到我们的HelloServer方法并执行。
需要说明的是,Go语言的默认路由是DefaultServeMux,当我们传递的是nil时,就会使用这个路由规则。这里有人会好奇,这个函数的第二个参数应当是一个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)
}
// 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)
}
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
该方法调用了ServeMux(DefaultServeMux就是ServeMux的一个实例)的HandleFunc方法,这个方法做了一个强制类型转换:HandlerFunc(handler),把我们传入的HelloServer方法转换成HandlerFunc(注意HandleFunc和HandlerFunc不同,后者比前者多了一个r字母)。HandlerFunc实现了Handler接口,就可以被ServeMux找到并调用它的ServeHTTP方法,实际就是调用了HelloServer方法。
从结果来说,这个设计让我们实现一个Web服务器只需要一行代码,十分方便。但实现方式的确有点绕人。
4.自定义路由
回到最开始的ListenAndServe()函数,这里的第二个参数是可以用来配置外部路由的,只要这个外部路由实现了Handler接口。
下面是一个《Go_web编程》里面的例子:
package main
import (
"fmt"
"net/http"
)
type MyMux struct{}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
hello(w, r)
return
}
http.NotFound(w, r)
return
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func main() {
mux := &MyMux{}
http.ListenAndServe(":3030", mux)
}
在这个路由中,你访问这个站点的所有路径都会调用hello函数,也就是写死了,没有实现分配handler功能,不过也可以作为一个实现路由的例子。
有疑问加站长微信联系(非本文作者)