http包详解

http包的作用及使用

go的http包是go的web编程的核心内容,go的web框架本质上都是基于http提供的组件进行再度封装。我们来看一下http基本的使用:

复制代码
func main() {
	http.Handle("/get", GetVal())
	http.Handle("/hello", Hello())
	http.Handle("/demo", http.HandlerFunc(Demo))
	if err := http.ListenAndServe("0.0.0.0:9191", nil); err != nil {
		fmt.Println("err: %v", err)
	}
}

func GetVal() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		val := "get\n"
		fmt.Fprintf(w, val)

	}
}

func Hello() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		val := "hello\n"
		w.Write([]byte(val))
	}
}

func Demo(w http.ResponseWriter, r *http.Request) {
	val := "get\n"
	fmt.Fprintf(w, val)
}

代码非常简单,就是为路由注册一个handler来处理请求并写入响应,我们来探究一下它的内部是如何实现的

源码分析

http包下的重要数据结构

ServerMux

复制代码
type ServeMux struct {
	mu       sync.RWMutex //保证读写路由表的并发安全
	m         map[string]muxEntry 
}

它是http包中的路由器组件,存储路由及handler的信息,能够通过路由规则快速匹配到对应的handler(高版本go使用的前缀树方式,低版本使用map的方式).

muxEntry

复制代码
type muxEntry struct {
	explict  bool
	handler Handler
}

Handler

复制代码
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

请求处理的业务逻辑函数,由用户自己定义,通过ServeHttp方法进行处理

HandlerFunc

复制代码
type HandlerFunc func(ResponseWriter, *Request)

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

它完全是为了方便用户使用,通过定义函数的方法替代定义结构体来注册handler

http包的工作流程------使用默认路由

注册路由

复制代码
func Handle(pattern string, handler Handler) {
	DefaultServeMux.register(pattern, handler)
}

//DefaultServeMux
func (mux *ServeMux) register(pattern string, handler Handler) {
	if err := mux.registerErr(pattern, handler); err != nil {
		panic(err)
	}
}

func (mux *ServeMux) registerErr(patstr string, handler Handler) error {
	if patstr == "" {
		return errors.New("http: invalid pattern")
	}
	if handler == nil {
		return errors.New("http: nil handler")
	}
	if f, ok := handler.(HandlerFunc); ok && f == nil {
		return errors.New("http: nil handler")
	}

	pat, err := parsePattern(patstr)
	if err != nil {
		return fmt.Errorf("parsing %q: %w", patstr, err)
	}

	// Get the caller's location, for better conflict error messages.
	// Skip register and whatever calls it.
	_, file, line, ok := runtime.Caller(3)
	if !ok {
		pat.loc = "unknown location"
	} else {
		pat.loc = fmt.Sprintf("%s:%d", file, line)
	}

	mux.mu.Lock()
	defer mux.mu.Unlock()
	// Check for conflict.
	if err := mux.index.possiblyConflictingPatterns(pat, func(pat2 *pattern) error {
		if pat.conflictsWith(pat2) {
			d := describeConflict(pat, pat2)
			return fmt.Errorf("pattern %q (registered at %s) conflicts with pattern %q (registered at %s):\n%s",
				pat, pat.loc, pat2, pat2.loc, d)
		}
		return nil
	}); err != nil {
		return err
	}
	mux.tree.addPattern(pat, handler)
	mux.index.addPattern(pat)
	mux.patterns = append(mux.patterns, pat)
	return nil
}

简单来说,但直接执行http.Handler方法注册路由时,就是将pattern及handler挂载到默认的ServeMux上。

DefaultServerMux会在挂载之前执行一系列的校验操作,并为了优化路由匹配性能引入一些复杂的数据结构和操作

server监听

入口-绑定port、监听请求

复制代码
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)
}

循环阻塞、等待请求、协程处理

简化版代码

复制代码
func (srv *Server) Serve(l net.Listener) error {
	baseCtx := context.Background()
	if srv.BaseContext != nil {
		baseCtx = srv.BaseContext(origListener)
		if baseCtx == nil {
			panic("BaseContext returned a nil context")
		}
	}

	var tempDelay time.Duration // how long to sleep on accept failure
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		rw, err := l.Accept()
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew, runHooks) // before Serve can return
		go c.serve(connCtx)
	}
}

server会启动一个协程不断接收新来的请求,并新开一个协程处理请求来提高go的并发性和性能

请求处理逻辑

复制代码
func (c *conn) serve(ctx context.Context) {
	//根据不同的配置往ctx注入信息
	// 针对不同的err信息进行处理
	//for循环不断读取conn的信息------针对长链接
	for {
		w, err := c.readRequest(ctx)
		//处理w和err,可能会推出循环
		
		serverHandler{c.server}.ServeHTTP(w, w.req) //处理请求
		//判断是否服用连接,不复用则退出循环
	}
}
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为空则使用我们之前说的defaultServerMux,否则使用我们自己的路由器处理请求

相关推荐
purrrew2 小时前
【Java ee初阶】HTTP(2)
网络·网络协议·http
火星数据-Tina2 小时前
从HTTP轮询到WebSocket:如何让体育API性能提升100倍?
websocket·网络协议·http
hgdlip5 小时前
怎么快速换电脑浏览器的ip:方法与注意事项
网络·网络协议·tcp/ip·电脑
Think Spatial 空间思维7 小时前
【HTTPS基础概念与原理】TLS握手过程详解
数据库·网络协议·https
2501_915909068 小时前
开发日常中的抓包工具经验谈:Charles 抓包工具与其它选项对比
websocket·网络协议·tcp/ip·http·网络安全·https·udp
利刃大大11 小时前
【网络编程】十、详解 UDP 协议
网络·网络协议·udp
LaoZhangGong12311 小时前
W5500使用ioLibrary库创建TCP客户端
网络·经验分享·stm32·网络协议·tcp/ip
北极象12 小时前
Go语言处理HTTP下载中EOFFailed
开发语言·http·golang
天天爱吃肉821813 小时前
车载以太网驱动智能化:域控架构设计与开发实践
java·运维·网络协议·微服务
IP管家15 小时前
企业级IP代理解决方案:负载均衡与API接口集成实践
服务器·网络·数据库·网络协议·tcp/ip·容器·负载均衡