首先来看一个用net/http包写的web服务器:两个函数实现http服务器
package main
import (
"fmt"
"net/http"
"strings"
"log"
)
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)
fmt.Println(r.Form["url_long"])
for k, v := range r.Form {
fmt.Println("key:", k)
fmt.Println("val:", strings.Join(v, ""))
}
fmt.Fprintf(w, "Hello world!") //这个写入到w的是输出到客户端的
}
func main() {
http.HandleFunc("/", sayhelloName) //设置访问的路由
err := http.ListenAndServe(":9090", nil) //设置监听的端口
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
函数1:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
//HandleFunc注册一个处理器函数handler和对应的模式pattern(注册到DefaultServeMux)。ServeMux的文档解释了模式的匹配机制。
var DefaultServeMux = NewServeMux()
//DefaultServeMux是用于Serve的默认ServeMux。
func NewServeMux() *ServeMux
//NewServeMux创建并返回一个新的*ServeMux
函数2:
func ListenAndServe(addr string, handler Handler) error
//ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。
//handler参数一般会设为nil,此时会使用DefaultServeMux(默认的路由器)。
--------------------------------------第一个函数解析-------------------------------------------------------------
1、HTTP包默认的路由器:DefaultServeMux
type ServeMux struct { //ServeMux就是路由器类型
mu sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
hosts bool // 是否在任意的规则中带有host信息
}
//ServeMux类型是HTTP请求的多路转接器。
//它会将每一个接收的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。
type muxEntry struct {
explicit bool // 是否精确匹配
h Handler // 这个路由表达式对应哪个handler
pattern string //匹配字符串
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // 路由器实现
}
//实现了Handler接口的对象可以注册到HTTP服务端,为特定的路径及其子树提供服务。
//ServeHTTP应该将回复的头域和数据写入ResponseWriter接口然后返回。返回标志着该请求已经结束,
//HTTP服务端可以转移向该连接上的下一个请求。
路由器:就是访问特定URI的时候,执行该URI指定的函数(controller)。
上面讲到HandleFunc函数的作用是把一个模式(URI)和对应的处理函数(controller)注册到HTTP包默认的路由器-DefaultServeMux中。那么这个注册的过程是怎么进行的呢?
注册“模式-处理函数”对到HTTP默认路由器中:
注册“模式-处理函数”对到路由器中,代码中就是把模式字符串如"/",和函数名写入到ServeMux的实例中-DefaultServeMux。也就是写到map[string]muxEntry这个map里面。模式字符串对应string,而处理函数对应muxEntry结构体的Handler类型。从上面的几个struct和interface中可以看出,如果要把我们自己写的函数写入到muxEntry的h中,函数必须要实现Handler这个接口。然而,从最上面的HTTP服务器代码中看到sayHelloName这个函数并没有实现Handler这个结构啊,这是怎么实现的呢?
调用逻辑①
这里就涉及到上面讲到的HandleFunc这个函数了:
// 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)
}
为什么sayhelloName函数并没有实现ServeHTTP这个接口,却能添加到默认路由器DefaultServeMux里面呢?从上面HandleFunc函数可以看出,当我们把自定义的处理函数如sayhelloName作为参数传递的时候,go语言上会有一个函数类型的转换!
调用逻辑②
HandleFunc这个函数调用了 (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request) 这个方法:
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
这里面就有 HandlerFunc(handler) 这个类型转换的过程。下面来看一下HandlerFunc这个类型:
// 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 that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
所以,我们写的sayHelloName这个处理函数就强制转换成了HandlerFunc类型,而HandlerFunc类型实现了ServeHTTP这个方法,即HandlerFunc实现了type Handler interface这个接口。故而sayHelloName实现了type Handler interface接口。即我们可以把我们定义的“模式-处理函数”对写入到默认路由器DefaultServeMux中。
调用逻辑③
回到上面提到的(ServeMux) HandleFunc(pattern, handler) 这个方法,这个方法里面调用了(mux *ServeMux) Handle(pattern string, handler Handler) 这个方法:
// 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就是上面HandleFunc传入的DefaultServeMux,默认路由器
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 { //说明map[string]muxEntry 这个不存在
mux.m = make(map[string]muxEntry)
}
mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
if pattern[0] != '/' {
mux.hosts = true
}
}
上面这个函数就是“URI-处理函数”的注册过程。
--------------------------------------第一个函数解析完成--------------------------------------------------------
--------------------------------------第二个函数解析-------------------------------------------------------------
第二个函数:http.ListenAndServe(":9090", nil)
func ListenAndServe(addr string, handler Handler) error
//ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。
//handler参数一般会设为nil,此时会使用DefaultServeMux。
函数体:
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
先实例化一个Server对象,Server类型定义了运行HTTP服务端的参数,Server的零值是合法的配置。Server其实是type Server struct,即一个结构体,具体字段如下:
type Server struct {
Addr string // 监听的TCP地址,如果为空字符串会使用":http"
Handler Handler // 调用的处理器,如为nil会调用http.DefaultServeMux
ReadTimeout time.Duration // 请求的读取操作在超时前的最大持续时间
WriteTimeout time.Duration // 回复的写入操作在超时前的最大持续时间
MaxHeaderBytes int // 请求的头域最大长度,如为0则用DefaultMaxHeaderBytes
TLSConfig *tls.Config // 可选的TLS配置,用于ListenAndServeTLS方法
// TLSNextProto(可选地)指定一个函数来在一个NPN型协议升级出现时接管TLS连接的所有权。
// 映射的键为商谈的协议名;映射的值为函数,该函数的Handler参数应处理HTTP请求,
// 并且初始化Handler.ServeHTTP的*Request参数的TLS和RemoteAddr字段(如果未设置)。
// 连接在函数返回时会自动关闭。
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
// ConnState字段指定一个可选的回调函数,该函数会在一个与客户端的连接改变状态时被调用。
// 参见ConnState类型和相关常数获取细节。
ConnState func(net.Conn, ConnState)
// ErrorLog指定一个可选的日志记录器,用于记录接收连接时的错误和处理器不正常的行为。
// 如果本字段为nil,日志会通过log包的标准日志记录器写入os.Stderr。
ErrorLog *log.Logger
// 内含隐藏或非导出字段
}
然后调用 (Server) ListenAndServe()方法进行监听:
// 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.
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函数(dial.go): —— 监听端口
func Listen(net, laddr string) (Listener, error)
返回在一个本地网络地址laddr上监听的Listener。网络类型参数net必须是面向流的网络:
"tcp"、"tcp4"、"tcp6"、"unix"或"unixpacket"。参见Dial函数获取laddr的语法。
func Listen(network, address string) (Listener, error) {
addrs, err := DefaultResolver.resolveAddrList(context.Background(), "listen", network, address, nil)
if err != nil {
return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
}
var l Listener
switch la := addrs.first(isIPv4).(type) {
case *TCPAddr:
l, err = ListenTCP(network, la)
case *UnixAddr:
l, err = ListenUnix(network, la)
default:
return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
}
if err != nil {
return nil, err // l is non-nil interface containing nil pointer
}
return l, nil
}
函数 srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}):——接收客户端请求
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() //通过Listener接收请求
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) //创建一个Conn。Conn是net包里面的一个接口 type Conn interface。Conn接口代表通用的面向流的网络连接。多个线程可能会同时调用同一个Conn的方法。
if err != nil {
continue
}
go c.serve() //Go语言高并发的体现
}
}
具体底层的net.Listen和srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})就不再解析了,具体可以看源码。
--------------------------------------第二个函数解析完成--------------------------------------------------------
总结:利用net/http包建立一个HTTP服务器
①首先要定义一个路由器,这里可以自定一个路由器(如:mux := http.NewServeMux()),也可以使用http包默认的DefaultServeMux路由器。
②然后我们把要注册的“模式(URI)-处理函数”对添加到mux中。模式好办,就是一个string类型的值。对于处理函数,因为ServeMux路由器类型中map[string]muxEntry类型,里面muxEntry类型,的处理函数是Handler类型,而Handler类型是接口类型,如下:
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // 路由器实现
}
所以我们的处理函数必须要实现Handler这个接口,也就是实现ServeHTTP这个方法。最后通过
func (mux *ServeMux) Handle(pattern string, handler Handler)
这个方法把“模式-处理函数”对添加到自定义/http包默认路由器中。
③最后一步是调用http.ListenAndServe(":9090",mux)函数,对指定的addr进行监听就可以了。这个函数第二个参数选取的规则是:如果是自定义路由,传入自定路由;如果是http包默认路由,传入nil值即可。
附录:
一个使用自定义路由的HTTP服务器:
package main
import (
"net/http"
"log"
"io"
)
type myHandler struct{}
func (*myHandler) ServeHTTP(w http.ResponseWriter,r *http.Request){
io.WriteString(w,"URL:"+r.URL.String()+"Method: "+r.Method)
}
func main() {
mux:= http.NewServeMux()
mux.Handle("/", &myHandler{}) //为什么要传指针过去呢?因为是指针实现了这个接口
err:=http.ListenAndServe(":9090",mux)
if err!=nil {
log.Fatal(err)
}
}
用http包实现http服务器,使用高层封装:第一个版本
*******************************************************************
package main
import (
"net/http"
"io"
"log"
)
func main(){
//设置路由
http.HandleFunc("/", sayHello) //第二个参数指定了传入函数的格式
err:=http.ListenAndServe(":8081",nil)//使用http包默认的路由器DefaultServeMux时,传入nil即可。
if err!=nil{
log.Fatal(err)
}
}
func sayHello(w http.ResponseWriter, r *http.Request){ //这样才能注册进去路由
io.WriteString(w, "hello world, this is version 1")
//只要实现了writer接口就可以这么传。
}
注释:http.ResponseWriter对Writer接口的实现。http包server.go文件
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
}
func (w *response) Write(data []byte) (n int, err error) {
return w.write(len(data), data, "")
}
*******************************************************************
用http包实现http服务器,使用中层封装:第二个版本
*********************************************************************
package main
import (
"net/http"
"io"
"log"
"os"
)
//对比第一版本:传入的handler是nil,接下来我们要把nil换掉,换成我们自己实现的handler
//其实并不是一个handler,真实是ServerMux(路由类型),首先需要实现一个mux
func main(){
mux:=http.NewServeMux() //实例化一个mux,返回一个mux
//设置handler操作,不可以用HandlerFunc这个函数,这个函数用的是默认的路由器DefaultServeMux
//这里用的是默认的的handler进行注册,要自己实现handler,注册到mux中
//
mux.Handle("/",&myHandler{})
mux.HandleFunc("/hello", sayHello)
//实现文件服务器-简易静态文件实现
wd,err:=os.Getwd() //Getwd返回一个对应当前工作目录的根路径
if err!=nil{
log.Fatal(err)
}
mux.Handle("/static/",http.StripPrefix("/static/",
http.FileServer(http.Dir(wd))))
err =http.ListenAndServe(":9090",mux) //使用自定义的路由器mux时,用http包的ListenAndServe函数,此时要传入mux
if err!=nil{
log.Fatal(err)
}
}
type myHandler struct {}
func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
io.WriteString(w, "URL:"+r.URL.String())
}
func sayHello(w http.ResponseWriter, r * http.Request){
io.WriteString(w, "hello world, this is version 2")
}
********************************************************************
从这里就可以看出来,我们还需要一个mux来控制我们的访问!这个函数用了两个路由器,一个DefaultServeMux和一个mux。
为什么这里没有ListenAndServe设置nil也可以访问到默认路由DefaultServeMux设置的URL呢?
答:注意这一句mux.HandleFunc("/hello", sayHello),这里是调用的HandleFunc这个方法,不是用的HandleFunc函数;所以这里也是操作的mux这个自定义的路由器!
用http包实现http服务器,使用底层封装:第三个版本
*********************************************************************
package main
import (
"net/http"
"time"
"io"
"log"
)
//通过map保存注册的handler
//然后通过底层的serveHTTP进行转发,这是效率最高的,因为没有进任何封装
//参考beego的那些路由
var mux map[string]func(http.ResponseWriter, *http.Request)
func main(){
server := http.Server{
Addr: ":8080",
Handler: &myHandler{},
ReadHeaderTimeout: 5*time.Second,
}
mux = make(map[string]func(http.ResponseWriter, *http.Request))
mux["/hello"]=sayHello
mux["/bye"]=sayBye
err:=server.ListenAndServe() //使用自定义的map来实现路由时,使用ListenAndServe方法,上面用的是ListenAndServe函数。
if err!=nil{
log.Fatal(err)
}
}
type myHandler struct{}
func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
if h,ok := mux[r.URL.String()] ; ok {
h(w,r) //根据URL从map中取出函数名,然后调用。
return
}
io.WriteString(w, "Version 3 "+"URL: "+r.URL.String())
}
func sayHello(w http.ResponseWriter, r *http.Request){
io.WriteString(w, "Version 3 "+"Hello ")
}
func sayBye(w http.ResponseWriter, r *http.Request){
io.WriteString(w, "Version 3 "+"Bye")
}
********************************************************************************
这个程序只是用了ListenAndServe方法,把前面两个版本的http.ListenAndServe函数中的封装解开,直接设置server这个结构体。
而上面那两个版本,是先设置一个路由器(默认的或者是自定义的),然后把路由器传到ListenAndServe函数,ListenAndServe函数再通过传入的路由器调用ListenAndServe方法,其实底层是一样的。