服务计算学习之路-开发 web 服务程序

开发 web 服务程序

简介

开发简单 web 服务程序 cloudgo,了解 web 服务器工作原理。

开发环境

  • CentOS7
  • go 1.9.4 linux/amd64

Go的http包

使用http包编写的简单web服务器

下面是一个简单的web服务器,实现在客户端访问http://127.0.0.1:9090/的时候响应内容为Hello World!

package main

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

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World!"))
    })                                       //设置访问的路由
    err := http.ListenAndServe(":9090", nil) //设置监听的端口
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

从上面的代码可以看到,要编写一个Web服务器很简单,首先调用http.HandleFunc()设置路由和响应处理函数,调用http.ListenAndServe()去监听端口,等待客户端访问即可。那http包又为我们做了什么呢,接下来我将分析一下http包的代码执行流程。

http包有关路由部分

根据上面代码,首先是调用了http.HandleFunc(),它的定义如下,实现了将传入的处理响应函数与对应的path进行匹配。

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	mux.Handle(pattern, HandlerFunc(handler))
}

所以在Handle()函数中默认的路由是怎样匹配的呢,先看下面的两个struct,它们存放了默认的路由规则

type ServeMux struct {
	mu sync.RWMutex   //锁机制,因为请求会涉及到并发处理
	m  map[string]muxEntry  //路由规则,使用map将string对应mux实体,这里的string是注册的路由表达式
	hosts bool        //是否在任意的规则中带有host信息
}
type muxEntry struct {
	explicit bool     //是否精确匹配
	h        Handler  //这个路由表达式对应的处理响应函数
	pattern  string   //匹配字符串
}

根据http.HandleFunc()中的代码,执行了mux.Handle(),这个函数对传入的path进行解析,然后向ServeMux中添加路由规则

// 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 " + pattern)
	}
	if handler == nil {
		panic("http: nil handler")
	}
	if mux.m[pattern].explicit {
		panic("http: multiple registrations for " + pattern)
	}

	if mux.m == nil {
		mux.m = make(map[string]muxEntry)
	}
    //增加一个新的匹配规则
    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
     
    //根据path的第一个字母判断是否有host
    if pattern[0] != '/' {
        mux.hosts = true
    }
 
	// Helpful behavior:
	// If pattern is /tree/, insert an implicit permanent redirect for /tree.
	// It can be overridden by an explicit registration.
	n := len(pattern)
	if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
		// If pattern contains a host name, strip it and use remaining
		// path for redirect.
		path := pattern
		if pattern[0] != '/' {
			// In pattern, at least the last character is a '/', so
			// strings.Index can't be -1.
			path = pattern[strings.Index(pattern, "/"):]
		}
		url := &url.URL{Path: path}
		mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
	}
}

既然添加了路由规则,那么如果客户端进行访问,是怎样查找到对应的路由规则呢,对过程mux.ServerHTTP->mux.Handler->mux.handler->mux.match进行追踪,找到了路由匹配函数match()。这个函数的实现解释了为什么会匹配最长的最佳匹配,比如传入/user/hh,不是先匹配/user/,而是匹配了/user/hh。

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    var n = 0
    for k, v := range mux.m {
        if !pathMatch(k, path) {
            continue
        }
        //如果匹配到了一个规则,还会继续匹配并且判断path的长度是否最长
        if h == nil || len(k) > n {
            n = len(k)
            h = v.h
            pattern = v.pattern
        }
    }
    return
}

http包有关监听与服务部分

接下来就是执行http.ListenAndServe(),可以发现这个函数首先实例化了Server,接着调用了Server.ListenAndServe()

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

ListenAndServe()函数调用了net.Listen("tcp",addr)监听端口,接着调用了srv.Serve()

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

Server()函数启动一个for循环,然后在循环体中Accept请求,对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()。使用goroutines来处理Conn的读写事件,这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件,这样使Go实现了高并发和高效能。

	for {
		rw, e := l.Accept()
		if e != nil {
			select {
			case <-srv.getDoneChan():
				return ErrServerClosed
			default:
			}
			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
				}
				srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return e
		}
		tempDelay = 0
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(ctx)
	}

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

在为客户端请求进行服务的c.serve()中,会读取每个请求的内容w, err := c.readRequest(),并且判断handler是否为空,如果没有设置handler则为DefaultServeMux,然后调用handler的ServeHttp()

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    //这里handler为简单web服务器代码中http.ListenAndServe中的第二个参数
    handler := sh.srv.Handler
    if handler == nil {
        //如果handler为空则使用DefaultServeMux进行处理
        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    //如果需要使用自定义的mux,就需要实现ServeHTTP方法也就是Handler接口
    handler.ServeHTTP(rw, req)
}

这里需要注意的是Handler是一个接口,如一开始的web服务器代码中,虽然我们并没有实现ServeHTTP(),但是在http包里面还定义了一个类型HandlerFunc,这个类型默认就实现了ServeHTTP(),在调用http.HandleFunc()的时候已经将自定义的handler处理函数强制转为HandlerFunc类型

type HandlerFunc func(ResponseWriter, *Request)

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

所以以上就是http包的整个的代码执行过程,未完待续…

猜你喜欢

转载自blog.csdn.net/C486C/article/details/84101740