http 请求接收
go 源码中的http包中包含一个Handler接口
type Handler interface{
ServeHTTP(w ResponseWriter, r *Request)
}
使用的时候,只要保证有一个满足Handler接口的方法,然后调用http.ListenAndServe方法即可。
type database map[string]interface{}
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request){
for item, value := range db { ...}
}
func main() {
db := database{ "li ning" : 200, "an ta" : 300 }
http.ListenAndServe("localhost", db)
}
go语言中保留了一个ServerMux, 来提供基本的路由功能。
type database map[string]interface{}
func (db database) list(w http.ResponseWriter, req *http.Request) { ... }
func (db database) price(w http.ResponseWriter, req *http.Request) { ... }
func main() {
db := database{ "li ning" : 200, "an ta" : 300 }
mux := http.NewServerMux()
mux.handle("/list",http.HandlerFunc(db.list))
mux.handle("/price",http.HandlerFunc(db.pirce))
http.ListenAndServe("localhost", mux)
}
其中, http.HandlerFunc是一个包装接口, 使得特定函数类型能实现特定的接口。
package http
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHttp(w http.ResponseWriter, req *http.Request) {
f(w,req)
}
二 gin 框架
gin框架只是对对handler接口进行了包装,从而方便了用户使用,也就意味着gin框架同样调用了go 源码库中提供的http server 。
gin框架主要做了下面的工作:
1 封装路由
2 将请求与响应用Context包装以方便业务代码调用
3 将中间件的调用进行封装
这一部分结合gin框架对请求接收的主流程来分析一下相关功能的实现机制。
gin框架的精华部分:
1 Engine路由树
路由树的概念前面已经介绍过,这里介绍一下请求的处理过程。
2 RouterGroup(路由组的概念,可以自由组会中间件与路由的匹配关系)
使用举例
3 中间件的执行过程
4 利用Context来对请求过程中的上下文信息进行封装
type Context struct {
writermem responseWriter
Request *http.Request
// 传递接口,使用各个处理函数,更加灵活,降低耦合
Writer ResponseWriter
Params Params // 路径当中的参数
handlers HandlersChain // 处理函数数组
index int8 // 目前在运行着第几个处理函数
engine *Engine
Keys map[string]interface{} // 各个中间件添加的key value
Errors errorMsgs
Accepted []string
}
5 获取请求内容
请求内容具有不同的格式类型(请求中的content-type头来判断请求格式),json,xml等等,需要抽象一个接口
type Binding interface {
Name() string
Bind(*http.Request, interface{}) error
}
返回内容的样式也是多种多样,因此也需要抽象出一个接口
type Render interface {
Render(http.ResponseWriter) error
WriteContentType(w http.ResponseWriter)
}
如何使用
// 自动更加请求头选择不同的绑定器对象进行处理
func (c *Context) Bind(obj interface{}) error {
b := binding.Default(c.Request.Method, c.ContentType())
return c.MustBindWith(obj, b)
}
// 绑定失败后,框架会进行统一的处理
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
if err = c.ShouldBindWith(obj, b); err != nil {
c.AbortWithError(400, err).SetType(ErrorTypeBind)
}
return
}
// 用户可以自行选择绑定器,自行对出错处理。自行选择绑定器,这也意味着用户可以自己实现绑定器。
// 例如:嫌弃默认的json处理是用官方的json处理包,嫌弃它慢,可以自己实现Binding接口
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
return b.Bind(c.Request, obj)
}
http 请求发送
// NewClient create a janus client with ClientOption(s) to setup the newed client.
func NewClient(opts ...ClientOption) *JanusClient {
jc := &JanusClient{
cli: &http.Client{**
Transport: NewRoundTripper(),
Timeout: 1 * time.Second,
},
}
return jc
}
// NewRoundTripper create a new RoundTripper to cover the default: http.DefaultTransport.
func NewRoundTripper() http.RoundTripper {
return &http.Transport{
DialContext: DialContext,
MaxIdleConns: 1000,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 10 * time.Second,
DisableKeepAlives: false,
}
}
// DialContext 方法重写了创建链接的过程
func DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
if IsHostPort(addr) || IsDomainName(addr) {
return DefaultDialer.DialContext(ctx, network, addr)
}
name := addr
// rm :80 for http, see transport.go connectMethodForRequest -> canonicalAddr
// To support https or sock5, here need upgrade
if idx := strings.Index(addr, ":"); idx > 0 { // rm :80
name = addr[:idx]
}
// get cluster and rm DefultKeepAliveTimespan
// name is shown as: p.s.m.servie.idc$cluster${for connection reuse}
var cluster string
if idx := strings.Index(name, "&"); idx > 0 {
name = name[:idx]
}
if idx := strings.Index(name, "$"); idx > 0 {
cluster = name[idx+1:]
name = name[:idx]
}
var (
endpoints consul.Endpoints
err error
)
if cluster != "" {
endpoints, err = consul.Lookup(name, consul.WithCluster(cluster))
} else {
endpoints, err = consul.Lookup(name)
}
if err != nil {
return nil, err
}
for tries := 3; tries > 0; tries-- {
conn, err := DefaultDialer.DialContext(ctx, network, endpoints.GetOne().Addr)
if err == nil {
return conn, nil
}
logs.Warn("DialConext error: %s", err.Error())
}
return nil, errors.New("DialContext failed")
}
http.Client.Do(*Http.Request) 执行过程最终会调用了roundTripper接口, 而RoundTripper 是由http.Transport 实现的,重写了Transport 创建链接的方法(即DialContext, 在其中加入了服务发现)