GO语言启动web服务的实现方式

来源:互联网 发布:如何投资美股 知乎 编辑:程序博客网 时间:2024/05/14 17:27

1.首先看一个简单的web服务

package mainimport (    "io"    "net/http"    "log")   // hello world, the web serverfunc 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 mainimport (    "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功能,不过也可以作为一个实现路由的例子。

0 0
原创粉丝点击