net/http源码分析之Go原生是如何搭建web程序

一、Go原生代码库实现一个简单的web程序

首先来浏览一下以下使用Go内置的net/http包实现的一个简单的web实例:

示例代码:

package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    //r.URL.Path是请求URL的路径部分
    //其尾随的[1:]意思是“创建Path中第一个字符到结尾的子片段”(这将从路径名中删除前导“ /”)。
    //将format字符串写入w
	fmt.Fprintf(w, "Hello world, I love %s!", r.URL.Path[1:])
}

func main() {
    //设置路由
	http.HandleFunc("/", handler)
	//设置监听的端口
	http.ListenAndServe(":8080", nil)
}

运行上面程序,打开http://localhost:8080/coding可以见到如下效果:
在这里插入图片描述
先来大概解释一下这个web程序是如何运行的:

  1. 首先我们启动程序,main函数以调用http.HandleFunc开始,它告诉http包使用handler处理所有对web根目录("/")的请求。
  2. 然后调用http.ListenAndServe,指定监听8080端口。(暂时不必担心它的第二个参数nil。)此函数将阻塞,直到程序终止。
  3. 所以当在浏览器输入http://localhost:8080/coding时,程序收到此请求并将其路由给给handler函数处理。
  4. 通过变量r获得请求信息里的请求路径,获取到路径名后将去掉第一个字符的字符串写入w输出到客户端

handler函数:

  • handler的函数签名是func(ResponseWriter, *Request),它以http.ResponseWriter和http.Request作为参数。
  • 一个http.ResponseWriter的值w,组装了HTTP服务器的响应;通过写入它,我们将数据发送到HTTP客户端。
  • http.Request是一个表示客户端的HTTP请求的数据结构

二、细说Go如何使Web工作

其实Go使得Web工作主要就是两步:

  1. 先通过HandleFunc函数在DefaultServeMux中为给定的模式(pattern)注册处理函数。
  2. 然后通过ListenAndServe监听TCP端口8080,然后调用Serve和handler来处理传入连接的请求。handler通常为nil,在这种情况下使用DefaultServeMux中匹配的handler。

下面通过剖析源码来了解Go是如何实现上面的两步

1、HandleFunc函数为DefaultServeMux注册handler

DefaultServeMux&ServeMux
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
}
...
type muxEntry struct {
	h       Handler
	pattern string
}
...
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

由net/http.server.go里的源码可知,DefaultServeMux是一个指向ServeMux的指针变量。
ServeMux是一个HTTP请求多路复用器。它将每个传入请求的URL与一组已注册的模式(ServerMux中的m)进行匹配,并调用与URL最匹配的模式的处理程序。

HandleFunc函数:

HandleFunc在DefaultServeMux中为给定的模式(pattern)注册处理函数(handler)。

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    //为给定的模式(pattern)注册处理函数(handler)。
	DefaultServeMux.HandleFunc(pattern, handler)
}
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
...
type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}
...
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	//Handle方法为pattern注册Handler
	mux.Handle(pattern, HandlerFunc(handler))
}
  • 由net/http.server.go里的源码可知HandlerFunc是一个实现了Handler接口的类型
  • 而ServeMux的Handle方法是为pattern注册Handler。
ServeMux的Handler方法
func (mux *ServeMux) Handle(pattern string, handler Handler) {
	...
	if _, exist := mux.m[pattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}

	if mux.m == nil {
		mux.m = make(map[string]muxEntry)
	}
	//为pattern注册Handler
	e := muxEntry{h: handler, pattern: pattern}
	mux.m[pattern] = e
	...
}

2、ListenAndServe监听端口、处理接收的请求

ListenAndServe监听TCP的地址addr,然后调用Serve和handler来处理传入连接的请求。处理程序通常为nil,在这种情况下使用DefaultServeMux。

http.ListenAndServe
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
  • 利用Server的ListenAndServe监听端口和处理请求
Server的ListenAndServe方法
func (srv *Server) ListenAndServe() error {
	...
	addr := srv.Addr
	...
	ln, err := net.Listen("tcp", addr)
	...
	return srv.Serve(ln)
}
  • 通过net.Listen监听tcp端口,返回侦听器ln,然后调用Server的Serve方法接收侦听器接收的请求并处理。
Server的Serve方法

在侦听器l上接收传入的连接,为每个连接创建一个新的goroutine。这些goroutines读取请求,处理请求。

func (srv *Server) Serve(l net.Listener) error {
	...
	baseCtx := context.Background()
	...
	//ServerContextKey是一个全局变量,它等于&contextKey{"http-server"}
	ctx := context.WithValue(baseCtx, ServerContextKey, srv) 
	for {
		rw, err := l.Accept()
		...
		connCtx := ctx
		...
		c := srv.newConn(rw)
		...
		go c.serve(connCtx)
	}
}
conn的serve方法
func (c *conn) serve(ctx context.Context) {
	...
	for {
		w, err := c.readRequest(ctx)
		...
		req := w.req
		...
	
		//重点方法
		serverHandler{c.server}.ServeHTTP(w, w.req)
		...
	}
}
serverHandler结构体
type serverHandler struct {
	srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler
	if handler == nil {
		handler = DefaultServeMux //handler为nil则使用默认的DefaultServeMux
	}
	...
	handler.ServeHTTP(rw, req)
}

然后调用ServeMux的ServeHTTP方法,方法内部会根据其匹配模式返回符合请求URL的handler,然后调用handler的实现的ServeHTTP接口的方法处理请求

本文参考了
go官方文档给出的Writing Web Applications

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值