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

Desire121 · · 4306 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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功能,不过也可以作为一个实现路由的例子。


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:Desire121

查看原文:GO语言启动web服务的实现方式

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

4306 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传