全栈编程

Balance $ 2,317
Item Sold 1230
文章作者: 全栈编程@luboke.com
版权声明: 本文章为全栈编程go语言体系课视频教程配套电子书,版权归 全栈编程@luboke.com所有,欢迎免费学习,转载必须注明出处!但禁止任何商业用途,否则将受到法律制裁!

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

Go 提供了一系列用于创建 Web 服务器的标准库,而且通过 Go 创建一个服务器的 步骤非常简单,只要通过 net/http 包调用 ListenAndServe 函数并传入网络地址以及负责处理请求的处理器( handler )作为参数就可以了。

如果网络地址参数为空字符串,那 么服务器默认使用 80 端口进行网络连接;如果处理器参数为 nil,那么服务器将使用默认的多路复用器 DefaultServeMux

 

Conn的goroutine(这部分内容放在go语言并发编程当中,感兴趣的同学可先学习)

与我们一般编写的http服务器不同, Go为了实现高并发和高性能, 使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。

Go在等待客户端请求里面是这样写的:

c, err := srv.newConn(rw)
if err != nil {
	continue
}
go c.serve()

这里我们可以看到客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到对应的handler,该handler中便可以读取到相应的header信息,这样保证了每个请求的独立性。

ServeMux的自定义

我们前面小节讲述conn.server的时候,其实内部是调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢?

它的结构如下:

type ServeMux struct {
	mu sync.RWMutex   //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
	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)  // 路由实现器
}

Handler是一个接口,但是前一小节中的sayhelloName函数并没有实现ServeHTTP这个接口,为什么能添加呢?原来在http包里面还定义了一个类型HandlerFunc,我们定义的函数sayhelloName就是这个HandlerFunc调用之后的结果,这个类型默认就实现了ServeHTTP这个接口,即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。

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)返回对应设置路由的处理Handler,然后执行h.ServeHTTP(w, r)

也就是调用对应路由的handler的ServerHTTP接口,那么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)
}

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其实支持外部实现的路由器 ListenAndServe的第二个参数就是用以配置外部路由器的,它是一个Handler接口,即外部路由器只要实现了Handler接口就可以,我们可以在自己实现的路由器的ServeHTTP里面实现自定义路由功能。

如下代码所示,我们自己实现了一个简易的路由器

package main

import (
	"fmt"
	"net/http"
)

type MyMux struct {
}

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

func sayhelloName(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello myroute!")
}

func main() {
	mux := &MyMux{}
	http.ListenAndServe(":9090", mux)
}

Go 提供了一系列用于创建 Web 服务器的标准库,而且通过 Go 创建一个服务器的 步骤非常简单,只要通过 net/http 包调用 ListenAndServe 函数并传入网络地址以及负责处理请求的处理器( handler )作为参数就可以了。

如果网络地址参数为空字符串,那 么服务器默认使用 80 端口进行网络连接;如果处理器参数为 nil,那么服务器将使用默认的多路复用器 DefaultServeMux,当然,我们也可以通过调用 NewServeMux 函数创建一个多路复用器。多路复用器接收到用户的请求之后根据请求的 URL 来判断使用哪 个处理器来处理请求,找到后就会重定向到对应的处理器来处理请求

使用默认的多路复用器(DefaultServeMux)

package main

import (
	"fmt"
	"net/http"
)

type DefineServerMux struct{}

func (dsm *DefineServerMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "创建自定义的多路复用处理器defineServerMux")
}

func ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "HandleFunc将一个普通的函数ServeHTTP包装成Handle")
}

func main() {

	defineServerMux := DefineServerMux{}

	http.Handle("/defineServerMux", &defineServerMux)
	http.HandleFunc("/",ServeHTTP)

	//这里我们看到了,第二个参数要求传递的是一个handle,但是为什么handle设置为nil
	http.ListenAndServe(":8080", nil)

}
 

使用自己创建的多路复用器

在创建服务器时,我们还可以通过 NewServeMux 方法创建一个多路复用器

func NewServeMux() *ServeMux

NewServeMux创建并返回一个新的*ServeMux

package main

import (
	"fmt"
	"net/http"
)

type DefineServerMux struct{}

func (dsm *DefineServerMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "创建自定义的多路复用处理器defineServerMux")
}

func main() {

	defineServerMux := DefineServerMux{}

	http.Handle("/defineServerMux", &defineServerMux)
	http.ListenAndServe(":8080", nil)

}

结构体 ServeMux

type ServeMux struct {
    // 内含隐藏或非导出字段
}

ServeMux类型是HTTP请求的多路转接器。它会将每一个接收的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。

结构体 ServeMux 的相关方法

func (*ServeMux) Handle

func (mux *ServeMux) Handle(pattern string, handler Handler)

Handle注册HTTP处理器handler和对应的模式pattern。如果该模式已经注册有一个处理器,Handle会panic。

 

func (*ServeMux) HandleFunc

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))

HandleFunc注册一个处理器函数handler和对应的模式pattern。

func (*ServeMux) Handler

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)

Handler根据r.Method、r.Host和r.URL.Path等数据,返回将用于处理该请求的HTTP处理器。它总是返回一个非nil的处理器。如果路径不是它的规范格式,将返回内建的用于重定向到等价的规范路径的处理器。

Handler也会返回匹配该请求的的已注册模式;在内建重定向处理器的情况下,pattern会在重定向后进行匹配。如果没有已注册模式可以应用于该请求,本方法将返回一个内建的"404 page not found"处理器和一个空字符串模式。

func (*ServeMux) ServeHTTP

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

ServeHTTP将请求派遣到与请求的URL最匹配的模式对应的处理器。

使用实现ServerHTTP的类型来自定义的处理器处理请求

package main

import (
	"fmt"
	"net/http"
)

type MyHandlers struct{}

func (m *MyHandlers) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "通过自己创建的处理器处理请求!")
}

func main() {

	myHandler := MyHandlers{}

	http.Handle("/myHandler", &myHandler)
	http.ListenAndServe(":8080", nil)

}

通过 Server 结构对服务器进行更详细的配置

Server结构体也定义了实现http的相关方法

package main

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

type MyDefinitionMux struct{}

func (m *MyDefinitionMux)sayhelloName(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello 我是波哥,go语言体系课@luboke.com!") // 这个写入到 w 的是输出到客户端的
}

func (h *MyDefinitionMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "测试通过 Server 结构详细配置服务器")
	//h.sayhelloName(w,r)

}

func main() {
	myDefinitionMux := MyDefinitionMux{}
	//对net/http包中 Server 结构体设置
	server := http.Server{
		Addr: ":8080",
		//自定义handle
		Handler: &myDefinitionMux,
		ReadTimeout: 2 * time.Second,
	}
	server.ListenAndServe()
}

 

文章作者: 全栈编程@luboke.com
版权声明: 本文章为全栈编程go语言体系课视频教程配套电子书,版权归 全栈编程@luboke.com所有,欢迎免费学习,转载必须注明出处!但禁止任何商业用途,否则将受到法律制裁!
copyright © 2020 全栈编程@luboke.com