web based
用 http 包建立一个简单的 http 服务器
go 能直接监听端口,不需要像 php 语言那样需要 nginx 或 apache 服务器。并且由于 go 语言的特性,下面这个 web server 已经支持并发了
package main
import (
"fmt"
"log"
"net/http"
"strings"
)
func done(w http.ResponseWriter, r *http.Request) {
r.ParseForm() //解析参数,默认是不解析的
fmt.Println(r.Form) //在服务端输出一些访问者的信息
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
fmt.Println(r.Form["url_long"])
//获取 url 的参数和值
for k, v := range r.Form {
fmt.Println("[key:", k, " value:", strings.Join(v, ""))
}
//写入 w 是输出到客户端的信息
fmt.Fprintf(w, "hello world")
}
func main() {
http.HandleFunc("/", done) //设置访问的路由
err := http.ListenAndServe(":8890", nil) //设置监听的端口
if err != nil {
log.Fatal("ListenAndServer: ", err)
}
}
分析 http 包运行机制
http 执行流程
- 创建 Listen Socket,监听指定的端口,等待客户端请求
结合前面的代码,显然监听端口是在 http.ListenAndServer 中完成的。其底层实现是:初始化一个 server 对象,然后调用 net.Listen(“tcp”, addr),即底层用 tcp 协议搭建了一个服务,然后监控设置的端口 - Listen Socket 接受客户端的请求,得到 Client Socket,接下来通过 Client Socket 和客户端通信
继 1 中监控端口之后,调用 srv.Server(net.Listener)//实现见下面代码。该函数中先通过 Listener 接收请求信息(Listen Socket 部分),然后创建一个 Conn,接着单独开一个 goroutine,go conn.server(即 Client Socket),把接收的请求数据扔给这个 conn 去服务。即用户的每个请求都是在一个新的 goroutine 中处理的,互相不影响。可以支持并发 - 处理客户端请求,首先从 Client Socket 读取请求数据,然后交给对应的 handle 处理,handle 处理完毕准备后客户端需要的数据,通过 Client socket 写给客户端
conn 首先会解析 request: c.readRequest,然后获取 handle: handle := c.server.Handler,即 http.ListenAndServer 的第二个参数(我们前面代码中传的是 nil),若为空则取默认值 DefultServeMux,即 http.HandleFunc(“/”, sayhalloName)。DefultServeMux 会调用 ServerHTTP 方法,该方法内部会调用当前路由对应的逻辑方法,这里是 sayhalloName,最后通过写入 response 的信息反馈到客户端
http 包执行流程图
server.server 代码
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
}
log.Printf("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
}
go c.serve()
}
}
路由的具体实现
ServeMux
type ServeMux struct {
mu sync.RWMutex //锁,由于请求涉及到并发处理,go 里面的 map 是非线程安全的,因此这里需要一个锁机制
m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
hosts bool // 是否在任意的规则中带有host信息
}
muxEntry
type muxEntry struct {
explicit bool // 是否精确匹配
h Handler // 这个路由表达式对应哪个handler
pattern string //匹配字符串
}
Handler
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // 路由实现器
}
前面的 web server 代码中 http.HandleFunc(“/”, sayHelloName) 语句的最终目标就是将 sayHelloName 和 “/” 这条路由映射规则存到 ServerMux 中的 muxEntry 对象中。这里存在一个问题,sayHelloName 没有实现 Handle 接口,怎么能存到 muxEntry 中?实际上 http 包中的 HandleFunc 类型实现了 Handle 接口(实现 ServerHttp 方法)。所以在调用 HandleFunc 方法后先转换成了 HandleFunc 类型,这样就拥有了 ServerHttp 方法
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
分发请求
默认的路由器实现了 ServeHTTP
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
w.Header().Set("Connection", "close")
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
mux.Handler(r)
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method != "CONNECT" {
if p := cleanPath(r.URL.Path); p != r.URL.Path {
_, pattern = mux.handler(r.Host, p)
return RedirectHandler(p, StatusMovedPermanently), pattern
}
}
return mux.handler(r.Host, r.URL.Path)
}
mux.handler(r.Host, r.URL.Path)
func (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
}
实现一个简易的路由器
go 支持外部路由,http.ListenAndServer 的第二个参数,它是一个 Handler 接口,我们可以自定义一个类型,给它实现 Handler 接口,在 Handler 接口中实现自定义路由功能
package main
import (
"fmt"
"log"
"net/http"
)
type MyMux struct {
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
done(w, r)
return
}
http.NotFound(w, r)
return
}
func done(w http.ResponseWriter, r *http.Request) {
//写入 w 是输出到客户端的信息
fmt.Fprintf(w, "hello world")
}
func main() {
mux := &MyMux{}
err := http.ListenAndServe(":8890", mux) //设置监听的端口和路由
if err != nil {
log.Fatal("ListenAndServer: ", err)
}
}
总结 go web server 执行流程
- 首先调用 Http.HandleFunc
a.调用了 DefaultServeMux(ServerMux 的一个实例) 的 HandleFunc
b.调用了 DefaultServeMux 的 Handle
c.往 DefaultServeMux 的 map[string]muxEntry 中增加对应的 handler 和路由规则 - 其次调用http.ListenAndServe(“:9090”, nil)
a.实例化 Server
b.调用 Server 的 ListenAndServe()
c.调用 net.Listen(“tcp”, addr) 监听端口
d.启动一个 for 循环,在循环体中 Accept 请求
e.对每个请求实例化一个 Conn,并且开启一个 goroutine 为这个请求进行服务 go c.serve()
f.读取每个请求的内容 w, err := c.readRequest()
g.判断 handler 是否为空,如果没有设置 handler(这个例子就没有设置 handler),handler 就设置为 DefaultServeMux
h.调用 handler 的 ServeHttp
i.在这个例子中,下面就进入到 DefaultServeMux.ServeHttp
j.根据 request 中的路径选择 handler,并且进入到这个 handler 的 ServeHTTP // 这里的 handler 和 g 中的 handler 不是指同一个东西,g 中的 handler 对应的是 server 结构里面的 handler 成员,这里的 handler 指的是 muxEntry 对象中的 handler 成员,里面存储的逻辑函数。因为他们都实现了 handler 接口,此处要注意区分
k.调用对应的逻辑函数