Dawn's Blogs

分享技术 记录成长

0%

Go Web编程学习笔记 (1) Web基础和http包

Web 基础

Go 搭建一个 Web 服务器

Go 搭建一个 Web 服务器

使用 http 包可以轻松的搭建一个 Web 服务器:

  • http.HandleFunc 函数:用于绑定路由。
  • http.ListenAndServe 函数:设置监听的端口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"log"
"net/http"
"strings"
)

func sayHelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() // 解析参数
fmt.Println(r.Form) // 这些信息是输出到服务器端的打印信息
fmt.Println("path:", r.URL.Path)
fmt.Println("scheme:", r.URL.Scheme)

for k, v := range r.Form {
fmt.Println("key:", k)
fmt.Println("val:", strings.Join(v, ""))
}

fmt.Fprintf(w, "Hello astaxie!") // 这个写入到 w 的是输出到客户端的
}

func main() {
http.HandleFunc("/", sayHelloName) // 设置路由
err := http.ListenAndServe(":9000", nil) // 设置监听端口
if err != nil {
log.Fatal(err)
}
}

Go 中 Web 的工作方式

以下是服务器端的几个基本概念:

  • Request :用户的请求信息。
  • Response :服务器需要反馈给客户端的信息。
  • Conn :用户每次请求的链接。
  • Handler :处理请求和生成返回信息的处理逻辑。

http 包运行机制

Go 实现 Web 服务的工作模式如下:

  1. 创建 Listen Socket,监听指定的端口,等待客户端请求到来。
  2. Listen Socket 接收客户端的请求,得到 Client Socket,通过Client Socket 与客户端进行通信。
  3. 处理客户端的请求,交给 Handler进行处理,Handler 处理好数据之后通过 Client Socket 返回给客户端。

img

这整个的过程里面我们只要了解清楚下面三个问题,也就知道 Go 是如何让 Web 运行起来了:

  • 如何监听端口?

通过 http.ListenAndServe 函数,可以监听端口并分配handler,底层实现如下:初始化一个 server 对象,然后调用了 net.Listen("tcp", addr),也就是底层用 TCP 协议搭建了一个服务,然后监控我们设置的端口。

  • 如何接收客户端请求?

以下来自 http 包的源码,可以看到整个 http 的处理过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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()
}
}

在监听完端口过后,调用了 srv.Serve(net.Listener) 方法,这个方法就是处理客户端的请求信息。在 Accept 之后,创建一个 Conn,然后开启一个协程来单独处理来自客户端的 http 请求:go c.serve()

  • 如何分配 handler?

Conn 首先会解析 request:c.readRequest(),然后获取相应的 handler:handler := c.server.Handerc.server.Handler 即为 http.ListenAndServe 的第二个参数;若为,则默认获取 handler = DefaultServeMux

DefaultServeMux 实际上是一个路由器,它用来匹配 url 跳转到其相应的 handle 函数,通过 http.HandleFunc 函数设置。DefaultServeMux 会调用 ServeHTTP方法,这个方法内部调用了 http.HandleFunc 函数定义的 handler。

和http包img

http 包

Go 的 http 有两个核心功能:Conn、ServeMux

Conn 的 goroutine

在 http 包中,当与客户端建立 TCP 连接之后,会建立一个协程来单独处理这一次用户的请求:

1
2
3
4
5
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()

ServeMux

c.server.Handler时,则默认获取 handler = DefaultServeMux。这个 DefaullServeMux 是一个路由器,结构如下:

1
2
3
4
5
type ServeMux struct {
mu sync.RWMutex // 锁,由于请求涉及到并发处理,因此这里需要一个锁机制
m map[string]muxEntry // 路由规则,一个 string 对应一个 mux 实体,这里的 string 就是注册的路由表达式
hosts bool // 是否在任意的规则中带有 host 信息
}

muxEntry 的结构如下:

1
2
3
4
5
type muxEntry struct {
explicit bool // 是否精确匹配
h Handler // 这个路由表达式对应哪个 handler
pattern string // 匹配字符串
}

Handler 是一个接口:

1
2
3
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // 路由实现器
}

但是用户自定义的 Handler 并没有实现 ServeHTTP 方法,所以在 http 包里面还定义了一个类型 HandlerFunc,这个类型实现了 ServeHTTP 方法,也就是 Handler 接口。调用了 http.HandlerFunc 函数之后,我们自定义的 Handler 会被强制类型转换为 HandleFunc 类型,这样就有了 ServeHTTP方法:

1
2
3
4
5
6
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?请看下面的代码,默认的路由器实现了 ServeHTTP

1
2
3
4
5
6
7
8
9
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) 返回应设置路由的处理 Handler, 然后执行 h.ServeHTTP(w, r)

mux.Handler(r) 函数实现如下,它根据用户请求的 URL 和路由器里面存储的 map 去匹配的,当匹配到之后返回存储的 handler,调用这个 handler 的 ServeHTTP 接口就可以执行到相应的函数了。:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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)
}

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
}

自定义路由器

ListenAndServe 函数的第二个参数可以用来配置外部路由器,它是一个 Handler 接口,即即外部路由器只要实现了 Handler 接口就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
"log"
"net/http"
)

type myMux struct {
}

func (p myMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
sayHello(w, r)
return
}

http.NotFound(w, r)
return
}

func sayHello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, I am Dawn.")
}

func main() {
mux := myMux{}
err := http.ListenAndServe(":9000", mux)
if err != nil {
log.Fatal(err)
}
}

总结

Go语言中 Web 的工作流程如下:

  • 首先调用 http.HandleFunc 函数:

    1. 调用 DefaultServeMux 的HandleFunc
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 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)
    }

    // HandleFunc registers the handler function for the given pattern.
    func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
    panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
    }
    1. 调用了 DefaultServeMux 的 Handle
    2. 往 DefaultServeMux 的 map[string]muxEntry增加对应条目
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // Handle registers the handler for the given pattern.
    // If a handler already exists for pattern, Handle panics.
    func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    if pattern == "" {
    panic("http: invalid pattern")
    }
    if handler == nil {
    panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
    panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
    mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
    mux.es = appendSorted(mux.es, e)
    }

    if pattern[0] != '/' {
    mux.hosts = true
    }
    }
  • 其次调用 http.ListenAndServe 函数:

    1. 实例化 Server
    2. 调用 Server 的 ListenAndServe
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // ListenAndServe listens on the TCP network address addr and then calls
    // Serve with handler to handle requests on incoming connections.
    // Accepted connections are configured to enable TCP keep-alives.
    //
    // The handler is typically nil, in which case the DefaultServeMux is used.
    //
    // ListenAndServe always returns a non-nil error.
    func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
    }
    1. 调用 net.Listen(“tcp”, addr) 监听端口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // ListenAndServe listens on the TCP network address srv.Addr and then
    // calls Serve to handle requests on incoming connections.
    // Accepted connections are configured to enable TCP keep-alives.
    //
    // If srv.Addr is blank, ":http" is used.
    //
    // ListenAndServe always returns a non-nil error. After Shutdown or Close,
    // the returned error is ErrServerClosed.
    func (srv *Server) ListenAndServe() error {
    if srv.shuttingDown() {
    return ErrServerClosed
    }
    addr := srv.Addr
    if addr == "" {
    addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
    return err
    }
    return srv.Serve(ln)
    }
    1. 启动一个 for 循环,在循环体中 Accept 请求。对于每一个请求实例化一个 Conn,请开启一个 goroutine 运行服务 go c.serve()
    2. 读取每一个请求的内容 w, err := c.readRequest()
    3. 判断 handler 是否为空,如果为空则将 handler 设置为 DefaultServeMux
    4. 调用 handler 的 ServeHTTP,即 DefaultServeMux.ServeHTTP
    5. 根据请求选择 handler,并进入这个handler 的 ServeHTTPmux.handler(r).ServeHTTP(w, r)