2.2.7 Transport 说明
本节简要介绍 Client-go 源码中 Transport 包的实现和底层原理。
1. Transport 功能说明
Transport 提 供 认 证 授 权 安 全 的 传 输 控 制 协 议(TCP,Transmission Control Protocol)连接,基于 SPDY 协议支持 HTTP 流(Stream)传输机制。Transport 包是通过自定义 Transport 对 http.Client 的定制化封装实现的,在 Client-go 源码中作为工具包为 RESTClient 封装 HTTP 客户端请求。在实际开发过程中,一般只需要调用 Client-go中提供的 RESTClient、DiscoveryClient、ClientSet、DynamicClient 即可。
2. Transport 的内部实现
Transport 包主要是对 http.RoundTripper 的封装实现,为了更好地理解本节内容,首先介绍 http.RoundTripper 的概念和实现,然后介绍 Transport 是怎样封装 http.RoundTripper的。最后简要介绍 Client-go 包中底层的 RESTClient 是如何使用 Transport 的。
(1) http.RoundTripper
http.RoundTripper 能够执行单个 HTTP 事务,获取给定请求的响应。实现 http.Round Tripper 接口的代码通常需要在多个 Goroutine 中并发执行,因此,必须确保实现代码的线程安全性。http.RoundTripper 是在 Go 语言 HTTP 包中定义的接口,接口定义见代码清单 2-49。
代码清单 2-49
type RoundTripper interface { RoundTrip(*Request) (*Response, error) }
从上述代码中可以看出,http.RoundTripper 接口很简单,只定义了一个名为 RoundTrip 的方法。RoundTrip() 方法用于执行一个独立的 HTTP 事务,接收传入的 *Request 请求值作为参数并返回对应的 *Response 响应值,以及一个 error 值。任何实现了 RoundTrip() 方法的类型都实现了 http.RoundTripper 接口。代码清单 2-50 简单展示了 http.RoundTripper 接口的实现。
代码清单 2-50
type testTransport struct { agent string originalTransport http.RoundTripper } func (c *testTransport) RoundTrip(r *http.Request) (*http.Response, error) { if len(r.Header.Get("User-Agent")) != 0 { return c.originalTransport.RoundTrip(r) } r = utilnet.CloneRequest(r) r.Header.Set("User-Agent", c.agent) resp, err := c.originalTransport.RoundTrip(r) if err != nil { return nil, err } return resp, nil }
上述代码定义了一个 testTransport 结构体类型,通过实现该类型的 RoundTrip 方法实现了该类型的 http.RoundTripper 接口。该接口的功能是在 HTTP 的请求头中添加 User-Agent 参数。通过以上 http.RoundTripper 接口的简单实现,我们了解了 http.RoundTripper 接口是如何封装 HTTP 请求的。Transport 包中以同样的方式实现了多种封装类型。下面具体介绍 Transport 包中 http.RoundTripper 的封装。
(2) Transport 包中对 http.RoundTripper 的封装
Transport 包中定义了 New 函数,该函数通过传入的 Config 参数创建 http.RoundTripper,具体见代码清单 2-51。
代码清单 2-51
func New(config *Config) (http.RoundTripper, error) { // 设置 Transport 的安全级别 if config.Transport != nil && (config.HasCA() || config.HasCertAuth() || config.HasCertCallback() || config.TLS.Insecure) { return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed") } var ( rt http.RoundTripper err error ) if config.Transport != nil { rt = config.Transport } else { rt, err = tlsCache.get(config) if err != nil { return nil, err } } return HTTPWrappersForConfig(config, rt) }
在 New 函数中,首先通过 config 对象的方法获取认证信息,如果认证信息是非安全设置的,则返回 nil;如果配置中满足安全设置,则 New 函数会读取配置中的 Transport 信息;如果 Transport 为空,New 函数就从缓存中读取缓存的 Transport,如果缓存中没有Transport,那么需要初始化一个默认的 Transport。最后调用 HTTPWrappersForConfig函数,该函数将 Config 作为参数对 Transport 进行进一步的封装。HTTPWrappersForConfig 函数见代码清单 2-52。
代码清单 2-52
func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTripper, error) { if config.WrapTransport != nil { rt = config.WrapTransport(rt) } rt = DebugWrappers(rt) // Set authentication wrappers switch { case config.HasBasicAuth() && config.HasTokenAuth(): return nil, fmt.Errorf("username/password or bearer token may be set, but not both") case config.HasTokenAuth(): var err error rt, err = NewBearerAuthWithRefreshRoundTripper(config.BearerToken, config. BearerTokenFile, rt) if err != nil { return nil, err } case config.HasBasicAuth(): rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt) } if len(config.UserAgent) > 0 { rt = NewUserAgentRoundTripper(config.UserAgent, rt) } if len(config.Impersonate.UserName) > 0 || len(config.Impersonate.Groups) > 0 || len(config.Impersonate.Extra) > 0 { rt = NewImpersonatingRoundTripper(config.Impersonate, rt) } return rt, nil }
HTTPWrappersForConfig 函 数 可 以 根 据 不 同 配 置 的 认 证 信 息 创 建 不 同 的 http.Round‐ Tripper。由以上代码可知该函数共创建了 4 种不同类型的 http.RoundTripper。
① NewBearerAuthWithRefreshRoundTripper 创建了 BearerAuthWithRefreshRoundTripper 类型对象,实现了 http.RoundTripper 接口,并将提供的 Bearer 令牌添加到请求中,如果 tokenFile 是非空的,则定期读取 tokenFile,最后一次成功读取的内容作为 Bearer 令牌;如果 tokenFile 是非空的,而 Bearer 是空的,tokenFile会被立即读取,以填充初始的 Bearer 令牌。
② NewBasicAuthRoundTripper 创建了 basicAuthRoundTripper 类型对象,它将基本 auth 授权应用到请求中,通过用户名和密码授权实现 http.RoundTripper 接口。
③ NewUserAgentRoundTripper 创建了 userAgentRoundTripper 类型对象,它向请求添加 User-Agent 请求头,实现了 http.RoundTripper 接口。
④ NewImpersonatingRoundTripper 创建了 ImpersonatingRoundTripper 类型对象,向请求添加一个 Act-As 请求头,实现了 http.RoundTripper 接口。
以上 4 种不同类型的 http.RoundTripper 实现方式与 2.2.6 节的案例实现是同一种方式,这里不再赘述。除了以上 4 种不同类型的 http.RoundTripper 对 http.RoundTripper 接口的实现,Transport 中还包括 authProxyRoundTripper、debuggingRoundTripper 等其他类型,具体可在 /client-go/transport/round_trippers.go 文件中查看。
(3) Transport 的使用案例
Client-go 源码 Rest 包中通过 RESTClientFor 返回一个 RESTClient 对象,RESTClient对 Kubernetes APIServer 的 RESTful API 的访问进行了封装抽象。RESTClientFor 的实现见代码清单 2-53。
代码清单 2-53
func RESTClientFor(config *Config) (*RESTClient, error) { ... transport, err := TransportFor(config) if err != nil { return nil, err } var httpClient *http.Client if transport != http.DefaultTransport { httpClient = &http.Client{Transport: transport} if config.Timeout > 0 { httpClient.Timeout = config.Timeout } } ... restClient, err := NewRESTClient(baseURL, versionedAPIPath, clientContent, rateLimiter, httpClient) if err == nil && config.WarningHandler != nil { restClient.warningHandler = config.WarningHandler } return restClient, err } func TransportFor(config *Config) (http.RoundTripper, error) { cfg, err := config.TransportConfig() if err != nil { return nil, err } return transport.New(cfg) }
以上代码是通过函数 RESTClientFor 传入客户端配置参数 Config 来创建 RESTClient的,函数通过调用 TransportFor 函数创建了一个 Transport,在上述代码的最后一行,可 以 看 出 该 函 数 是 通 过 调 用 Transport 包 中 的 New 函 数 创 建 了 http.RoundTripper。
TransportFor 函数通过将客户端 Config 配置转化为 Transport 包中的 Config 类型并调用New 函数创建 http.Transport。通过 New 函数实现了底层 HTTP 不同请求的封装,实现了 HTTP 客户端的安全连接。