etcd3 client/client.go设计分析
pkg/mod/github.com/coreos/etcd@v3.3.17+incompatible/client/client.go:176
- Client接口
- etcd3的client接口
- newHTTPClientFactory对象
- Client接口的工厂方法
- 返回httpClient接口Do
- httpClient接口
- client http请求的实现接口
- Do(context.Context, httpAction) (*http.Response, []byte, error)
- httpAction接口
- 封装了http request结构,结构返回http.Request
- HTTPRequest(url.URL) *http.Request
//Client etcd client通信的接口
type Client interface {
Sync(context.Context) error
AutoSync(context.Context, time.Duration) error
Endpoints() []string
SetEndpoints(eps []string) error
GetVersion(ctx context.Context) (*version.Versions, error)
//接口类型,
httpClient
}
//New 面向接口编程,返回接口,对外暴露接口即可
func New(cfg Config) (Client, error) {
//httpClusterClient 实现Client接口,对外调用
c := &httpClusterClient{
//工厂方法设计模式
clientFactory: newHTTPClientFactory(cfg.transport(), cfg.checkRedirect(), cfg.HeaderTimeoutPerRequest),
rand: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
selectionMode: cfg.SelectionMode,
}
if cfg.Username != "" {
c.credentials = &credentials{
username: cfg.Username,
password: cfg.Password,
}
}
if err := c.SetEndpoints(cfg.Endpoints); err != nil {
return nil, err
}
return c, nil
}
//httpClient client真正触发http请求,参数是httpAction,只要实现了httpAction的对象都可以作为参数,也就是说用户外部自定义http请求过程传入即可
type httpClient interface {
Do(context.Context, httpAction) (*http.Response, []byte, error)
}
//newHTTPClientFactory http client的工厂方法模式,可以随时替换,这里返回接口httpClientFactory,httpClientFactory又是函数返回接口httpClient,httpClient会触发http请求Do,实现了链式调用的技巧
func newHTTPClientFactory(tr CancelableTransport, cr CheckRedirectFunc, headerTimeout time.Duration) httpClientFactory {
return func(ep url.URL) httpClient {
return &redirectFollowingHTTPClient{
checkRedirect: cr,
client: &simpleHTTPClient{
transport: tr,
endpoint: ep,
headerTimeout: headerTimeout,
},
}
}
}
type httpClientFactory func(url.URL) httpClient
//httpAction http请求接口,只要实现此接口的都可以http请求
type httpAction interface {
HTTPRequest(url.URL) *http.Request
}
//httpClusterClient 实现Client接口,作为client对象
type httpClusterClient struct {
clientFactory httpClientFactory
...
}
httpClusterClient实现接口
func (c *httpClusterClient) getLeaderEndpoint(ctx context.Context, eps []url.URL) (string, error) {
...
}
func (c *httpClusterClient) parseEndpoints(eps []string) ([]url.URL, error) {
...l
}
func (c *httpClusterClient) SetEndpoints(eps []string) error {
...
}
func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
...
}
func (c *httpClusterClient) Endpoints() []string {
...
}
func (c *httpClusterClient) Sync(ctx context.Context) error {
...
}
func (c *httpClusterClient) AutoSync(ctx context.Context, interval time.Duration) error {
...
}
func (c *httpClusterClient) GetVersion(ctx context.Context) (*version.Versions, error) {
...
}
//simpleHTTPClient 实现httpClient接口,用于触发http请求
type simpleHTTPClient struct {
transport CancelableTransport
endpoint url.URL
headerTimeout time.Duration
}
func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
req := act.HTTPRequest(c.endpoint)
}
这样的设计真的是妙极了!!!
调用逻辑:
client接口–>httpClient接口–>httpAction接口
- client接口:对外暴露Client接口
- httpClient接口:实现真正的http请求接口Do
- httpAction接口:实现构造http request
从上而下调用,层层封装,实现从对外接口到底层http请求,request封装的解耦
1.实例化client (httpClientFactory工厂方法), 先得到实现Client接口的httpClusterClient,进而得到实现Do接口的simpleHTTPClient
func New(cfg Config) (Client, error) {
c := &httpClusterClient{
clientFactory: newHTTPClientFactory(cfg.transport(), cfg.checkRedirect(), cfg.HeaderTimeoutPerRequest),
rand: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
selectionMode: cfg.SelectionMode,
}
...
}
func newHTTPClientFactory(tr CancelableTransport, cr CheckRedirectFunc, headerTimeout time.Duration) httpClientFactory {
return func(ep url.URL) httpClient {
return &redirectFollowingHTTPClient{
checkRedirect: cr,
client: &simpleHTTPClient{
transport: tr,
endpoint: ep,
headerTimeout: headerTimeout,
},
}
}
}
2.实现httpAction,构造http request请求
type authAPIAction struct {
verb string
}
func (l *authAPIAction) HTTPRequest(ep url.URL) *http.Request {
u := v2AuthURL(ep, "enable", "")
req, _ := http.NewRequest(l.verb, u.String(), nil)
return req
}
3.调用Client接口(由于Client内嵌HTTPClient接口, 所以即调用simpleHTTPClient的Do)
func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
req := act.HTTPRequest(c.endpoint)
...
}