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