go生态下的http请求接收与发送流程

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

参考:gin框架请求接收流程学习

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, 在其中加入了服务发现)

参考: go http client 中transport 解析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值