标签:
平常我们输入一个命令,比如docker info,就是使用cli向daemon发送一次http请求,返回一些容器相关的参数
输入docker info的时候,如果daemon不是在后台运行,会有 INFO[0007] GET /v1.19/info 输出,GET相当于HTTP中的GET/POST中的method GET, v1.19相当于版本号,info就是命令的name咯
接下来我们分析源码:
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil) //attach命令请求
//一次cli.call的调用执行
serverResp, err := cli.call("GET", "/info", nil, nil) //info 命令请求
接下来我们分析一下cli.call的执行流程:
//对应attach的请求,method="GET" path="/containers/"+cmd.Arg(0)+"/json" data=nil headers=nil
func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (*serverResponse, error) {
params, err := cli.encodeData(data) //数据编码,data类型为interface{},可以兼容所有类型的data
if err != nil {
sr := &serverResponse{
body: nil,
header: nil,
statusCode: -1,
}
return sr, nil
}
//设置http的headers
if data != nil {
if headers == nil {
headers = make(map[string][]string)
}
headers["Content-Type"] = []string{"application/json"}
}
//接下来就是它
serverResp, err := cli.clientRequest(method, path, params, headers)
return serverResp, err
}
再来看一看 cli.clientRequest执行
func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) {
//daemon的返回数据的struct为serverResp
serverResp := &serverResponse{
body: nil,
statusCode: -1,
}
//这是期待POST和PUT方法,貌似是写xx
expectedPayload := (method == "POST" || method == "PUT")
if expectedPayload && in == nil {
in = bytes.NewReader([]byte{})
}
//这里是重点,这个是用golang默认的http包返回一个req对象
req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), in)
if err != nil {
return serverResp, err
}
// Add CLI Config‘s HTTP Headers BEFORE we set the Docker headers
// then the user can‘t change OUR headers
//这个cli初始化的时候设置的一些数据,比如下面的cli.addr,cli.scheme都是初始化时候设置的
for k, v := range cli.configFile.HTTPHeaders {
req.Header.Set(k, v)
}
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
req.URL.Host = cli.addr
req.URL.Scheme = cli.scheme
if headers != nil {
for k, v := range headers {
req.Header[k] = v
}
}
if expectedPayload && req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "text/plain")
}
//执行http的请求就在这里这行咯,cli.HTTpClient返回&http.Client{Transport: cli.transport}
//cli.transport也是cli初始化的时候设置的,调用golang的http包向server做一次请求
resp, err := cli.HTTPClient().Do(req)
if resp != nil {
serverResp.statusCode = resp.StatusCode
}
//接下来就是打杂的返回值错误和http错误码的判断
if err != nil {
if types.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
return serverResp, errConnectionFailed
}
if cli.tlsConfig == nil && strings.Contains(err.Error(), "malformed HTTP response") {
return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
}
if cli.tlsConfig != nil && strings.Contains(err.Error(), "remote error: bad certificate") {
return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
}
return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
}
if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return serverResp, err
}
if len(body) == 0 {
return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
}
return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
}
serverResp.body = resp.Body
serverResp.header = resp.Header
return serverResp, nil
}
梳理一下,其实就是两步很重要,一步是 http.NewRequest,一步是 cli.HTTPClient().Do(req)
记得还有其中一堆参数实在cli初始化的时候建立的,来看看cli初始化的代码
func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli {
cli := &DockerCli{
in: in,
out: out,
err: err,
keyFile: clientFlags.Common.TrustKey,
} //输入,输出,错误的IO,以及tls的keyfile
cli.init = func() error {
clientFlags.PostParse()
hosts := clientFlags.Common.Hosts
switch len(hosts) {
case 0:
defaultHost := os.Getenv("DOCKER_HOST")
if defaultHost == "" {
defaultHost = opts.DefaultHost
}
//看看这个DefaultHost是多少
//DefaultHTTPHost = "127.0.0.1" DefaultHTTPPort = 2375
// DefaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
//其实本地使用的是DefaultUnixSocket = "/var/run/docker.sock"
defaultHost, err := opts.ValidateHost(defaultHost)
if err != nil {
return err
}
hosts = []string{defaultHost}
case 1:
// only accept one host to talk to
default:
return errors.New("Please specify only one -H")
}
protoAddrParts := strings.SplitN(hosts[0], "://", 2)
cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1]
//协议地址在这里初始化,proto="tcp/http/..." addr="127.0.0.1/...."
if cli.proto == "tcp" {
// error is checked in pkg/parsers already
parsed, _ := url.Parse("tcp://" + cli.addr)
cli.addr = parsed.Host
cli.basePath = parsed.Path
}
if clientFlags.Common.TLSOptions != nil {
cli.scheme = "https"
var e error
cli.tlsConfig, e = tlsconfig.Client(*clientFlags.Common.TLSOptions)
if e != nil {
return e
}
} else {
cli.scheme = "http"
}
if cli.in != nil {
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
}
if cli.out != nil {
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
}
// The transport is created here for reuse during the client session.
cli.transport = &http.Transport{
TLSClientConfig: cli.tlsConfig,
}
//这个很关键cli.transport的设置
sockets.ConfigureTCPTransport(cli.transport, cli.proto, cli.addr)
configFile, e := cliconfig.Load(cliconfig.ConfigDir())
if e != nil {
fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
}
cli.configFile = configFile
return nil
}
return cli
}
最后再来看看clientFlags是怎么来的吧?
在docker/docker/client.go里面初始化的
var clientFlags = &cli.ClientFlags{FlagSet: new(flag.FlagSet), Common: commonFlags}
//------------------------------------------------------------------------------------------------------
//学以致用,做一个自己版本的cli去请求docker的daemon的
package main
import (
_"bytes"
"fmt"
"io/ioutil"
"net/http"
"os"
"runtime"
"time"
"net"
)
func main(){
httppost()
}
func ConfigureTCPTransport(tr *http.Transport, proto, addr string) {
timeout := 32 * time.Second
if proto == "unix" {
// No need for compression in local communications.
tr.DisableCompression = true
tr.Dial = func(_, _ string) (net.Conn, error) {
return net.DialTimeout(proto, addr, timeout)
}
} else {
tr.Proxy = http.ProxyFromEnvironment
tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
}
}
func httppost() {
tr := &http.Transport{}
ConfigureTCPTransport(tr,"unix", "/var/run/docker.sock")
client := &http.Client{Transport:tr}
req, err := http.NewRequest("GET", "/v1.20/info", nil) //v1.20这个是我自己的docker api version
if err != nil {
fmt.Println(111111111111111111)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "Docker-Client/"+"1.8.0"+" ("+runtime.GOOS+")")
//1.8.0也是我自己的docker version的版本
req.URL.Host = "/var/run/docker.sock"
req.URL.Scheme = "http"
resp, err := client.Do(req)
fmt.Printf("%v ---- %v ---------- %v\n ",resp,err,os.Getenv("DOCKER_HOST"))
if err ==nil {
defer resp.Body.Close()
}
//defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
// handle error
}
fmt.Println(string(body))
}
总结来说,docker的这个请求还是用golang里面的http包,这个http包的执行流程过大和复杂,本篇文章就不概述了。
标签:
原文地址:http://my.oschina.net/yang1992/blog/502230