码迷,mamicode.com
首页 > 其他好文 > 详细

docker的cli的call执行流程

时间:2015-09-07 13:08:20      阅读:332      评论:0      收藏:0      [点我收藏+]

标签:

平常我们输入一个命令,比如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包的执行流程过大和复杂,本篇文章就不概述了。

docker的cli的call执行流程

标签:

原文地址:http://my.oschina.net/yang1992/blog/502230

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!