在进行Web应用开发的时候,使用成熟并且复杂的Web应用框架会使开发变得更加迅速和简便,但这意味着开发者必须接受框架自身的一套约定和模式。虽然很多框架都认为自己提供的约定和模式是最佳实践,但是如果开发者没有正确的理解这些最佳实践,那么对最佳实践的应用就可能会发展为货物崇拜编程:开发者如果不了解这些约定和模式的用法,就可能会在不必要甚至有害的情况下盲目的使用它们。—摘自《Go Web编程-黄健宏译》
对Go语言而言,隐藏在框架下的通常是net/http和html/template这两个标准库。

1 net/http标准库的各个组成部分

image.png
net/http标准库的各个组成部分

库中的结构和函数有些支持客户端和服务器中的一个,而有些则同时支持客户端和服务器:

  • Client、Response、Header、Request和Cookie对客户端进行支持;
  • Server、ServerMux、Handler/HandleFunc、ResponseWriter、Header、Request和Cookie则对服务器进行支持。

2 使用Go构建服务器

image.png
通过Go服务器处理请求


3 Go Web服务器

创建HTTP服务器的方式有两种:

  • 方式一:http.ListenAndServe(“”,nil)

直接调用ListenAndServe()并传入【网络地址】和【负责请求的处理器】作为参数即可
【网络地址】如果为空字符串,那么服务器默认使用80端口进行网络链接
【负责请求的处理器】如果为nil,那么使用默认的多路复用器 DefaultServeMux

  1. package main
  2. import (
  3. "net/http"
  4. )
  5. func main() {
  6. err := http.ListenAndServe("", nil)
  7. if err != nil {
  8. return
  9. }
  10. }

创建http服务方式一
方式二:先借助Server结构对服务器进行更详细的配置,然后再调用ListenAndServe()创建服务器

package main

import (
    "net/http"
)

func main() {
    server := http.Server{
        Addr:    "127.0.0.1",
        Handler: nil,
    }
    err := server.ListenAndServe()
    if err != nil {
        return
    }
}

创建http服务方式二
方式一内部调用的是server.ListenAndServe(),综上,以后直接使用第二种方式。
ListenAndServe()源码:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

ListenAndServe源码
DefaultServeMux的本质:

type Server struct{
    ...
    Handler Handler // handler to invoke, http.DefaultServeMux if nil
    ...
}
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
}

server.go中揭示了DefaultServeMux的本质


4 处理器和处理器函数

4.1 处理器

一个处理器就是拥有一个ServerHTTP()方法的接口,并且这个ServerHTTP方法需要接受两个参数:第一个参数是ResponseWriter接口,第二个参数是一个指向Request结构的指针。
也就是说,任何接口只要拥有一个ServerHTTP方法,并且该方法带有以下签名,那么他就是一个处理器:

ServerHttp(http.ResponseWriter,*http.Request)

server.ListenAndServe方法第二个参数是处理器,但是其默认值却是多路复用器DefaultServeMux?
DefaultServeMux的本质可得知,DefaultServeMux是ServeMux结构的一个实例,而ServeMux拥有ServerHTTP方法,并且这个方法的签名与成为处理器所需的签名完全一致。因此DefaultServeMux不仅是一个多路复用器,而且还是一个处理器。

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)
}

ServeMux结构的ServerHTTP方法
DefaultServeMux处理器和其他一般的处理器不同,**DefaultServeMux是一个特殊的处理器,它唯一要做的就是根据请求的URL将请求重定向到不同的处理器。**

4.1.1 自定义处理器

package main

import (
    "fmt"
    "net/http"
)
// 自定义处理器结构体
type MyHandler struct {}
// 要想成为一个处理器,必须实现ServerHTTP方法且参数也需要满足签名要求。
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w,"Hello World!")
}


func main() {

    server := http.Server{
        Addr:    "127.0.0.1:8080",
        Handler: &MyHandler{},
    }
    err := server.ListenAndServe()
    if err != nil {
        return
    }
}

自定义处理器
在浏览器中请求
image.png
发现一个问题,无论请求的url如何变化,都会返回相同的响应
image.png
这也就从侧面说明了多路复用器的作用。
在程序中我们用自定义的Handler: &MyHandler{}处理器取代了默认的多路复用器-DefaultServeMux,这也就意味着多路复用器的功能被替换掉啦,进而使得服务器不会再通过URL匹配来将请求路由至不同的处理器,而是直接使用同一个处理器(Handler: &MyHandler{})来处理所有请求,因此,无论浏览器访问什么地址,服务器返回的都是同样的响应。

4.1.3 使用多个处理器

在实际情况中,通常都是使用多个处理器处理不同的URL。也就是说,我们不再在Server结构的Handle字段中指定处理器,而是让处理器使用默认的多路服务器DefaultServeMux作为处理器,然后通过http.Handle函数将用户自定义的处理器绑定至DefaultServeMux

package main
// 处理hello的处理器结构
type HelloHandler struct {}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w,"Hello!")
}
// 处理world的处理器结构
type WorldHandler struct {}
func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w,"World!")
}

func main() {
    helloHandler := HelloHandler{}
    worldHandler := WorldHandler{}
    server := http.Server{
        Addr:    "127.0.0.1:8080",
        // 处理器置为空,就会使用默认的多路复用器。
        Handler: nil,
    }
    =========http.Handle函数将用户自定义的处理器绑定至DefaultServeMux=========
    http.Handle("/hello",&helloHandler)
    http.Handle("/world",&worldHandler)
    err := server.ListenAndServe()
    if err != nil {
        return
    }
}

http.Handle函数如何将用户自定义的处理器绑定到多路复用处理器上呢?看一下http.Handle的源码:

// Handle registers the handler for the given pattern in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

根据源码可知,调用http.Handle函数其实就是调用DefaultServeMux.Handle(pattern, handler)函数,之所以这样做,就是为了操作便利。
image.png
另外,虽然Handle函数源于http包,但实际上是ServeMux结构的方法,这也是为了方便。

4.2 处理器函数

处理器函数实际上就是与处理器拥有相同行为的函数:这些函数与ServeHTTP方法拥有相同的签名,即处理器函数接收ResponseWriter和指向Request结构的指针作为参数。只不过将处理器函数绑定到多路复用器上时,使用的是http.HandleFunc()方法。

=== hello函数的类型就是HandlerFunc类型 =====
func hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w,"Hello!")
}
=== world函数的类型就是HandlerFunc类型 =====
func world(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w,"World!")
}

func main() {

    server := http.Server{
        Addr:    "127.0.0.1:8080",
        Handler: nil,
    }
    http.HandleFunc("/hello", hello)
    http.HandleFunc("/world", world)
    err := server.ListenAndServe()
    if err != nil {
        return
    }
}

处理器和处理器函数的异同
image.png
HandleFunc函数会将hello处理器函数转换为一个Handler,并将它与DefaultServeMux进行绑定,以此来简化创建并绑定Handler的工作。
也就是说处理器函数只不过是创建处理器的一种简便的方法而已
但是,处理器函数并不能完全替代处理器。因为在某些情况下,代码可能已经包含了某个接口或某种类型,这是我们只需要为它们添加ServeHTTP方法就可以将它们转变为处理器,并且这种转变也有助于构建出更为模块化的Web应用。

4.3 串联多个处理器和处理器函数

【案例】假设我们想要在每个处理器被调用时,在某个地方记录下相应的调用信息。
方式一:在处理器中添加一些额外的代码;
方式二:将这些记录日志的代码重构成一个工具函数,然后让每个处理器都去调用这个工具函数。
反思:
虽然实现上述两种方式并不难,但是引入额外代码的做法会给程序的编写带来麻烦,并导致处理器需要包含于处理请求无关的代码。
横切关注点:例如日志记录、安全检查和错误处理这样的操作都被称为横切关注点。
为了防止代码重复和代码依赖的问题,不希望这些操作和正常代码搅合在一起,为此可以使用串联技术分隔代码中的横切关注点。

4.3.1 串联处理器函数

func hello(w http.ResponseWriter, r *http.Request) {
    _, _ = fmt.Fprintf(w, "Hello!")
}

func log(h http.HandlerFunc) http.HandlerFunc{
    return func(w http.ResponseWriter, r *http.Request) {
        name := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
        fmt.Println("Handler function called - " + name)
        h(w,r)
    }
}

func protect(h http.HandlerFunc) http.HandlerFunc{
    return func(w http.ResponseWriter, r *http.Request) {
        name := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
        fmt.Println("Handler function called - " + name)
        h(w,r)
    }
}

func main() {
    fmt.Printf("%T",hello)
    server := http.Server{
        Addr:    "127.0.0.1:8080",
        Handler: nil, // 为空表示使用多路复用器进行请求分发
    }
    http.HandleFunc("/hello", log(hello))
    // 串联多个处理器函数
    http.HandleFunc("/world",protect(log(hello)))
    err := server.ListenAndServe()
    if err != nil {
        return
    }
}

image.png

4.3.2 串联处理器

func hello(w http.ResponseWriter, r *http.Request) {
    _, _ = fmt.Fprintf(w, "Hello!")
}

func log(h http.HandlerFunc) http.HandlerFunc{
    return func(w http.ResponseWriter, r *http.Request) {
        name := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
        fmt.Println("Handler function called - " + name)
        h(w,r)
    }
}

func protect(h http.HandlerFunc) http.HandlerFunc{
    return func(w http.ResponseWriter, r *http.Request) {
        name := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
        fmt.Println("Handler function called - " + name)
        h(w,r)
    }
}

func main() {
    fmt.Printf("%T",hello)
    server := http.Server{
        Addr:    "127.0.0.1:8080",
        Handler: nil, // 为空表示使用多路复用器进行请求分发
    }
    http.HandleFunc("/hello", log(hello))
    http.HandleFunc("/world",protect(log(hello)))
    err := server.ListenAndServe()
    if err != nil {
        return
    }
}

image.png
串联处理器和处理器函数是一种非常常见的惯用法,很多Web应用框架都使用这一技术。

4.4 使用第三方多路复用器