01 net/http原理和使用

官方样例

// 创建一个Foo路由和处理函数
http.Handle("/foo", fooHandler)

// 创建一个bar路由和处理函数
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})

// 监听8080端口
log.Fatal(http.ListenAndServe(":8080", nil))

阅读代码库流程

阅读顺序,库函数 > 结构定义 > 结构函数,首先搞清楚库要提供什么功能(对外函数), 为了提供这些功能要把库分为几个核心模块(结构), 最后每个模块该提供什么能力(结构函数)。

库函数

go doc net/http | grep “^func” 查看对外函数:

func CanonicalHeaderKey(s string) string
func DetectContentType(data []byte) string
func Error(w ResponseWriter, error string, code int)
func Get(url string) (resp *Response, err error)
func Handle(pattern string, handler Handler)
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
func Head(url string) (resp *Response, err error)
func ListenAndServe(addr string, handler Handler) error
func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser
func NewRequest(method, url string, body io.Reader) (*Request, error)
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error)
func NotFound(w ResponseWriter, r *Request)
func ParseHTTPVersion(vers string) (major, minor int, ok bool)
func ParseTime(text string) (t time.Time, err error)
func Post(url, contentType string, body io.Reader) (resp *Response, err error)
func PostForm(url string, data url.Values) (resp *Response, err error)
func ProxyFromEnvironment(req *Request) (*url.URL, error)
func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error)
func ReadRequest(b *bufio.Reader) (*Request, error)
func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)
func Redirect(w ResponseWriter, r *Request, url string, code int)
func Serve(l net.Listener, handler Handler) error
func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, ...)
func ServeFile(w ResponseWriter, r *Request, name string)
func ServeTLS(l net.Listener, handler Handler, certFile, keyFile string) error
func SetCookie(w ResponseWriter, cookie *Cookie)
func StatusText(code int) string

大致分为三类:

  • 为服务端提供创建HTTP服务,名字中包含Serve,如Server、ServeFile、ListenAndServe
  • 为客户端提供调用HTTP服务,以http的method同名,如Get、Post、Head
  • 提供中转代理,如ProxyURL、ProxyFromEnvironment

结构定义

go doc net/http | grep “^type” | grep struct

type Client struct{ ... }
type Cookie struct{ ... }
type ProtocolError struct{ ... }
type PushOptions struct{ ... }
type Request struct{ ... } 
type Response struct{ ... }
type ServeMux struct{ ... }
type Server struct{ ... }
type Transport struct{ ... }
  • Client 负责构建 HTTP 客户端
  • Server 负责构建 HTTP 服务端
  • ServerMux 负责 HTTP 服务端路由
  • Transport、Request、Response、Cookie 负责客户端和服务端传输对应的不同模块

构建的 HTTP 服务端除了提供真实服务之外,也能提供代理中转服务,它们分别由 Client 和 Server 两个数据结构负责。除了这两个最重要的数据结构之外,HTTP 协议的每个部分,比如请求、返回、传输设置等都有具体的数据结构负责。

结构函数

跟着 http.ListenAndServe 这个函数来理一下 net/http 创建服务的主流程逻辑
第一层,http.ListenAndServe 本质是通过创建一个 Server 数据结构,调用 server.ListenAndServe 对外提供服务。这一层完全是比较简单的封装,目的将内部结构体的方法对外暴露成库函数,增加易用性。

第二层, 此ListenAndServe函数先定义了监听信息net.Listen, 然后调用Serve函数(很合乎ListenAndServe这个函数名,内部就是listen+serve)。

第三层, Serve 函数中,用了一个 for 循环,通过 l.Accept不断接收从客户端传进来的请求连接。当接收到了一个新的请求连接的时候,通过 srv.NewConn创建了一个连接结构(http.conn)作为c,并创建一个 Goroutine 为这个请求连接对应服务(c.serve)。

从第四层开始,后面就是单个连接的服务逻辑了。

第四层,c.serve函数先判断本次 HTTP 请求是否需要升级为 HTTPs,接着创建读文本的 reader 和写文本的 buffer,再进一步读取本次请求数据,然后第五层调用最关键的方法 serverHandler{c.server}.ServeHTTP(w, w.req) ,来处理这次请求。

这个关键方法是为了实现自定义的路由和业务逻辑,调用写法是比较有意思的:

serverHandler{c.server}.ServeHTTP(w, w.req)

serverHandler 结构体,是标准库封装的,代表“请求对应的处理逻辑”,它只包含了一个指向总入口服务 server 的指针。

这个结构将总入口的服务结构 Server 和每个连接的处理逻辑巧妙联系在一起了,你可以看接着的第六层逻辑(意思就是每个连接c内部有个字段server,指向了总的入口结构体):

// serverHandler 结构代表请求对应的处理逻辑
type serverHandler struct {
  srv *Server
}

// 具体处理逻辑的处理函数
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
  handler := sh.srv.Handler
  if handler == nil {
    handler = DefaultServeMux
  }
  ...
  handler.ServeHTTP(rw, req)
}

如果入口服务 server 结构已经设置了 Handler,就调用这个 Handler 来处理此次请求,反之则使用库自带的 DefaultServerMux。

那么 DefaultServeMux 是怎么寻找 Handler 的呢,这就是思维导图的最后一部分第七层

DefaultServeMux的ServeHTTP(rw, req)方法,就是根据req的url中的path,找到注册时候的路由,而注册时候的http.Handle(“foo”, fooHandler)和http.HandleFunc(“bar”, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, “Hello, %q”, html.EscapeString(r.URL.Path)) }),作用就是写入DefaultServeMux中的map结构,用于后续的寻找。

各层的关键结论:

  • 第一层,标准库创建 HTTP 服务是通过创建一个 Server 数据结构完成的;
  • 第二层,Server 数据结构在 for 循环中不断监听每一个连接;
  • 第三层,每个连接默认开启一个 Goroutine 为其服务;
  • 第四、五层,serverHandler 结构代表请求对应的处理逻辑,并且通过这个结构进行具体业务逻辑处理;
  • 第六层,Server 数据结构如果没有设置处理函数 Handler,默认使用 DefaultServerMux 处理请求;
  • 第七层,DefaultServerMux 是使用 map 结构来存储和查找路由规则。

创建框架的server结构

通过 go doc net/http.Server 我们可以看到 Server 的结构:

type Server struct {
    // 请求监听地址
  Addr string
    // 请求核心处理函数
  Handler Handler 
  ...
}

其中最核心的是 Handler 这个字段,从主流程中我们知道(第六层关键结论),当 Handler 这个字段设置为空的时候,它会默认使用 DefaultServerMux 这个路由器来填充这个值,但是我们一般都会使用自己定义的路由来替换这个默认路由。

所以在框架代码中,我们要创建一个自己的核心路由结构,实现 Handler。

创建framework目录,以下内容写入core.go:

package framework

import "net/http"

type Core struct {

}

func NewCore() *Core {
	return &Core{}
}

func (c *Core) ServeHTTP(w http.ResponseWriter, r *http.Request) {

}

在framework目录之外,创建main.go:

package main

import (
	"net/http"
	"webframework/framework"
)


func main() {
	server := http.Server{
		Handler: framework.NewCore(),
		Addr:    ":8080",
	}
	server.ListenAndServe()
}

我们通过自己创建Server数据结构,并且在数据结构中创建了自定义的 Handler(Core 数据结构)和监听地址,实现了一个 HTTP 服务。这个服务的具体业务逻辑都集中在我们自定义的 Core 结构中,后续我们要做的事情就是不断丰富这个 Core 数据结构的功能逻辑。

补充

net/http中Request结构体

Request结构体可以表示server端收到的请求,或者客户端主动发出的请求。

  • 写服务端代码时,ServeHTTP方法时接收的request参数
  • 写客户端代码时,发送方法的request参数

type Request struct {
	Method string
	
	// 定义了被请求的URI(对于服务端请求)或者要访问的URL(对于客户端请求)
	// URI不包含域名,URL包含域名
	// 
	// 对于服务端请求,此URL字段是http请求行Request-Line中的URI解析而来,
	// Request-Line存储在RequestURI字段中。
	// 对于大多数请求,除了Path和RawQuery字段外,其他字段都为空
	
	// 对于客户端请求,此URL的Host字段表明要连接的服务器地址,而此结构体的
	// Host字段可选择性地定义了要发送地http header值
	URL *url.URL
    
    // 从PATCH, POST 或者PUT请求的body中解析的参数
    PostForm url.Values
    ...
}

type URL struct {
	Scheme      string
	Opaque      string    // encoded opaque data
	User        *Userinfo // username and password information
	Host        string    // host or host:port
	Path        string    // path (relative paths may omit leading slash)
	RawPath     string    // encoded path hint (see EscapedPath method)
	ForceQuery  bool      // append a query ('?') even if RawQuery is empty
	RawQuery    string    // encoded query values, without '?'
	Fragment    string    // fragment for references, without '#'
	RawFragment string    // encoded fragment hint (see EscapedFragment method)
}

type Values map[string][]string

// 解析RUL中的参数,返回{key: value_list}这样的map
func (u *URL) Query() Values {
	v, _ := ParseQuery(u.RawQuery)
	return v
}

可以看到,对于服务端而言,Request.URL代表请求行中的URI录像,用Query方法返回URI的键值对(URI中key可以写多次,每个key最终对应的value是一个列表形式, 不过一般URL中不会传重复的key)。
比如 http://192.168.1.241:8080/login?user=111&passwd=abc&user=222
因此框架中解析命令行参数如下:

// 获取所有参数
func (ctx *Context) QueryAll() map[string][]string {
	if ctx.request != nil {
		return map[string][]string(ctx.request.URL.Query())
	}
	return map[string][]string{}
}

// 获取string类型的参数,默认值为“”
func (ctx *Context) QueryString(key string, def string) string {
	params := ctx.QueryAll()
	if vals, ok := params[key]; ok {
		len := len(vals)
		if len > 0 {
			return vals[len-1]  // 取列表中最后一项
		}
	}
	return def
}

// 获取int类型的key,默认值为0
func (ctx *Context) QueryInt(key string, def int) int {
	params := ctx.QueryAll()
	if vals, ok := params[key]; ok {
		len := len(vals)
		if len > 0 {
			intval, err := strconv.Atoi(vals[len-1])
			if err != nil {
				return def
			}
			return intval
		}
	}
	return def
}

测试样例:

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		fmt.Println(request.URL)

		fmt.Println("url params: ")
		url_params := request.URL.Query()
		for key, list := range url_params {
			fmt.Printf("%v: %v\n", key, list)
		}

		fmt.Println("body params: ")
		request.ParseForm()
		body_params := request.PostForm
		for key, list := range body_params {
			fmt.Printf("%v: %v\n", key, list)
		}
		fmt.Println()

	})

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

【小结】:

  1. 每个http请求会开一个goroutine进行处理,调用注册时server中handler路由的ServerHTTP方法
  2. 由于多线程并发调用ServerHTTP方法,因此如果是自己写逻辑要注意并发问题。官方样例中只是从map中找处理函数,map并发读不会出现问题。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值