go标准库net/http服务端的实现示例

 更新时间:2024年07月22日 08:32:57   作者:StarSky-yuan  
go的http标准库非常强大,本文主要介绍了go标准库net/http服务端,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1、http简单使用

go的http标准库非常强大,调用了两个函数就能够实现一个简单的http服务:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
func ListenAndServe(addr string, handler Handler) error

handleFunc注册一个路由和相应的处理函数,第一个参数表示注册的路由,第二个参数表示注册路由对应的处理函数;ListenAndServe用来启动http服务并监听,第一个参数是服务器地址,第二个参数表示使用的处理器。

下面是用这两个函数实现的简单的http服务:注册了一个“/”路由的处理函数,并在8080端口启动http服务,ListenAndServe第二个参数为空表示使用标准库默认的处理器,也可使用自定义处理器,传参即可。处理器的概念在下面标准库分析中进行介绍。

import (
    "net/http"
)


func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // TODO
    })


    http.ListenAndServe(":8080", nil)
}

2、http标准库分析

根据上面的两个函数来对http标准库展开分析

2.1、服务端数据结构

首先介绍下这两个函数涉及到的数据类型

(1)服务器对象,其中最核心的是Handler成员,表示整个http服务的路由器,存储路由路径对应到处理函数的映射,可自定义,例如第1小姐中的案例,没有自定义路由器对象,就会使用标准库提供的默认对象DefaultServeMux

type Server struct {
	Addr string // 地址  host:port
	Handler Handler // 处理器对象或路由器
    // ...
}

(2)Handler是一个接口,提供了ServeHTTP方法,用来将路由映射到相应的处理函数上

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

(3)路由器对象ServeMux,用来存储路由到处理函数的映射关系,该对象就是Handler接口的具体实现。

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
}

(4)muxEntry就是一个映射关系单元

type muxEntry struct {
	h       Handler
	pattern string
}

2.2、HandleFunc流程

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    // TODO
})

根据上面的函数来分析标准库的执行流程,首先看HandleFunc相关的实现:使用默认的DefaultServeMux路由器对象,调用ServeMux的HandleFunc,最后路由的注册是在mux.handle中实现,其中mux.Handle(pattern, HandlerFunc(handler))中对处理器做了类型转换,HandlerFunc 类型实现了ServeHTTP方法,所以被该类型转换后的函数都是Handler对象的实例

var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

type HandlerFunc func(ResponseWriter, *Request)

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

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}

func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.handle(pattern, handler)
}

进入到mux.handle中,会创建一个路由单元muxEntry对象,存储相应的路由和处理函数,其中对于根路径的存储需要做出特殊处理,在muxEntry中通过es存储,并按照顺序存储在muxEntry切片中,到此,已经完成了路由注册

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

func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
	n := len(es)
	i := sort.Search(n, func(i int) bool {
		return len(es[i].pattern) < len(e.pattern)
	})
	if i == n {
		return append(es, e)
	}

	es = append(es, muxEntry{}) 
	copy(es[i+1:], es[i:])      
	es[i] = e
	return es
}

2.3、ListenAndServe流程

ListenAndServe先初始化一个Server对象,并绑定地址和路由器,调用Server的ListenAndServe方法,其中net.Listen("tcp", addr)用于创建一个监听套接字并开始监听指定网络地址上的连接,返回一个实现了Listener接口的对象。关键是srv.Serve()

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

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

在srv.Serve(ln)中使用onceCloseListener 对Listener进行封装,防止被多次关闭,context.WithValue用来将srv服务器对象信息存储在context中,并使用for循环轮询等待连接,l.Accept()会阻塞等待,直到连接到达,并执行conn.serve函数。

type onceCloseListener struct {
	net.Listener
	once     sync.Once
	closeErr error
}

type contextKey struct {
	name string
}

ServerContextKey = &contextKey{"http-server"}

func (srv *Server) Serve(l net.Listener) error {
	l = &onceCloseListener{Listener: l}
	defer l.Close()
    // ...
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		rw, err := l.Accept()
        // ...
		connCtx := ctx
        // ...
		c := srv.newConn(rw)
        // ...
		go c.serve(connCtx)
	}
}

其中newConn会将Accept的返回的net.Conn封装成一个conn对象,对每个请求都会创建一个线程来处理,在conn.serve中会针对conn对象创建读写器并将内容置入缓冲区,在for中调用readRequest函数传入上下文,在readRequest中读取请求体req,并返回一个ResponseWriter的接口对象,用于向请求方返回响应,并在调用serverHandler的ServeHTTP方法

type conn struct {
	server *Server
	rwc net.Conn
    // ...
}

func (c *conn) serve(ctx context.Context) {
	c.remoteAddr = c.rwc.RemoteAddr().String()

	ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
	ctx, cancelCtx := context.WithCancel(ctx)
	c.cancelCtx = cancelCtx
	defer cancelCtx()

	c.r = &connReader{conn: c}
	c.bufr = newBufioReader(c.r)
	c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

	for {
		w, _ := c.readRequest(ctx)
		serverHandler{c.server}.ServeHTTP(w, w.req)
		w.finishRequest()
		...
	}
}

serverHandler的ServeHTTP方法用来根据路由分配handler,如果Server的Handler为空就是用默认的DefaultServerMux,对应上了文章一开始调用ListenAndServe的第二个参数,如果为空就使用默认路由器对象,最后调用路由器的ServeHTTP函数。

type serverHandler struct {
	srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler
	if handler == nil {
		handler = DefaultServeMux
	}
	if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {
		handler = globalOptionsHandler{}
	}

	handler.ServeHTTP(rw, req)
}

接下来流程如下,依次调用返回命中的handler,如果没有命中,则采用模糊匹配命中,最后调用handler的ServeHTTP函数,因为注册路由时候的函数在注册时候被强转成HandleFunc函数类型,该类型是实现ServeHTTP方法的,所以执行handler的ServeHTTP方法就是执行注册路由是对应的处理函数。

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}


func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    // ...
    return mux.handler(host, r.URL.Path)
}

func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
	mux.mu.RLock()
	defer mux.mu.RUnlock()

    return mux.match(path)
}

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}

	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil, ""
}

到此这篇关于go标准库net/http服务端的实现示例的文章就介绍到这了,更多相关go net/http服务端内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • GO语言如何手动处理TCP粘包详解

    GO语言如何手动处理TCP粘包详解

    最近在用golang开发人工客服系统的时候碰到了粘包问题,那么什么是粘包呢?下面这篇文章就来给大家介绍了关于GO语言如何手动处理TCP粘包的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴。
    2017-12-12
  • Golang创建第一个web项目(Gin+Gorm)

    Golang创建第一个web项目(Gin+Gorm)

    本文主要介绍了Golang创建第一个web项目(Gin+Gorm),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-06-06
  • 一文带你轻松学会Go语言动态调用函数

    一文带你轻松学会Go语言动态调用函数

    这篇文章主要是带大家学习一下Go语言是如何动态调用函数的,文中的示例代码讲解详细,对我们学习Go语言有一定的帮助,需要的可以参考下
    2022-11-11
  • Go中时间与时区问题的深入讲解

    Go中时间与时区问题的深入讲解

    go语言中如果不设置指定的时区,通过time.Now()获取到的就是本地时区,下面这篇文章主要给大家介绍了关于Go中时间与时区问题的相关资料,需要的朋友可以参考下
    2021-12-12
  • Go语言数据结构之单链表的实例详解

    Go语言数据结构之单链表的实例详解

    链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。本文将通过五个例题带大家深入了解Go语言中单链表的用法,感兴趣的可以了解一下
    2022-08-08
  • HTTP服务压力测试工具及相关术语讲解

    HTTP服务压力测试工具及相关术语讲解

    这篇文章主要为大家介绍了HTTP服务压力测试工具使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04
  • golang高并发的深入理解

    golang高并发的深入理解

    golang从语言级别上对并发提供了支持,而且在启动并发的方式上直接添加了语言级的关键字。下面这篇文章主要给大家介绍了关于golang高并发的相关资料,需要的朋友可以参考下
    2019-03-03
  • Go语言sync包与锁实现限制线程对变量的访问

    Go语言sync包与锁实现限制线程对变量的访问

    本文主要介绍了Go语言sync包与锁实现限制线程对变量的访问,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • Golang实现常见排序算法的示例代码

    Golang实现常见排序算法的示例代码

    现在的面试真的是越来越卷了,算法已经成为了面试过程中必不可少的一个环节,你如果想进稍微好一点的公司,算法是必不可少的一个环节。本文为大家准备了Golang实现常见排序算法的示例代码,需要的可以参考一下
    2022-05-05
  • go local history本地历史恢复代码神器

    go local history本地历史恢复代码神器

    这篇文章主要为大家介绍了go local history本地历史恢复代码神器的使用功能详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01

最新评论