GO语言启动web服务的实现方式
2016-08-07 17:28
561 查看
1.首先看一个简单的web服务
以上代码只依赖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的源码,这个函数实现如下:
生成了一个Server对象,接着调用Server对象的ListenAndServe()方法
调用了net.Listen(“tcp”,addr),也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。(TCP协议我基本不知道,而且似乎不知道对本问题也没有什么影响)
再看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) 这行代码注册了指定路径下的处理函数。
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,但我们传递的只是一个普通的函数,又如何能够识别?请看下面的代码:
该方法调用了ServeMux(DefaultServeMux就是ServeMux的一个实例)的HandleFunc方法,这个方法做了一个强制类型转换:HandlerFunc(handler),把我们传入的HelloServer方法转换成HandlerFunc(注意HandleFunc和HandlerFunc不同,后者比前者多了一个r字母)。HandlerFunc实现了Handler接口,就可以被ServeMux找到并调用它的ServeHTTP方法,实际就是调用了HelloServer方法。
从结果来说,这个设计让我们实现一个Web服务器只需要一行代码,十分方便。但实现方式的确有点绕人。
4.自定义路由
回到最开始的ListenAndServe()函数,这里的第二个参数是可以用来配置外部路由的,只要这个外部路由实现了Handler接口。
下面是一个《Go_web编程》里面的例子:
在这个路由中,你访问这个站点的所有路径都会调用hello函数,也就是写死了,没有实现分配handler功能,不过也可以作为一个实现路由的例子。
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功能,不过也可以作为一个实现路由的例子。
相关文章推荐
- 设计模式GOF23——享元模式
- GPG即GNU PrivacyGuard 加密工具PGP(Pretty Good Privacy)
- Sparse Representation---Yall1---Your algorithms for $L1$
- Google Page
- 【深度学习】A neural algorithm of artistic style算法详解
- Golang elasticsearch 对接问题
- UVALive3045 POJ2000 ZOJ2345 Gold Coins
- [Golang] base64加密与解密
- go语言入门(转载自开源社区)
- 关于GOLANG的chan
- goovy初学笔记(一)
- Google Guava官方教程(中文版)
- Django Form之动态数据初始化
- CodeForces 615E——Hexagons(二分,模拟)
- 设计模式GOF23——外观模式
- sublimetext3中pachage control的安装
- Google 最常用的搜索指令
- Django Form源码分析之Field验证逻辑
- google-api-url
- Google 地图API Map()构造器详解