Normally we enter a command, such as Docker info, that uses the CLI to send an HTTP request to daemon, returning some container-related parameters
When entering Docker INFO, if Daemon is not running in the background, there will be info[0007] get/v1.19/info output, and get is equivalent to the method get in Get/post in HTTP, v1.19 equivalent to the version number, info is the name of the command
Next we analyze the source code:
SERVERRESP, err: = Cli.call ("GET", "/containers/" +cmd. ARG (0) + "/json", nil, nil)//attach command request//Once Cli.call call execution Serverresp, err: = Cli.call ("GET", "/info", nil, nil)//info command request
Next we analyze the Cli.call execution process:
corresponding to the attach request, method= "GET" path= "/containers/" +cmd. ARG (0) + "/json" data=nil headers=nilfunc (CLI *DOCKERCLI) call (method, path string, data interface{}, headers map[string][]string) (*serverresponse, error) The {params, err := cli.encodedata (data) //is encoded in interface{} and can be compatible with all types of dataif err != nil {sr := &serverresponse{body: nil,header: nil,statuscode: -1,}return sr, nil} //to set HTTP headersif data != nil {if headers == nil {headers = make (map[string][]string)}headers["Content-type"] = []string{" Application/json "}} //Next is it serverresp, err := Cli.clientrequest (method, path, params, headers) return Serverresp, err}
Take a look at Cli.clientrequest execution
func (CLI&NBSP;*DOCKERCLI) clientrequest (Method, path string, in io. reader, headers map[string][]string) (*serverresponse, error) { // Daemon The return data of the struct is serverrespserverresp := &serverresponse{body: nil,statuscode: -1,} //This is expected post and put method, Seems to be writing xxexpectedpayload := (method == "POST" | | method == "PUT") if expectedpayload && in == nil {in = bytes. Newreader ([]byte{})}//here is the focus, this is to return a Req object req, err := http with the Golang default HTTP packet. 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//some of the data that is set when the CLI is initialized, such as the following cli.addr,cli.scheme are set at initialization time 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.addrreq.url.scheme = cli.schemeif 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")} // The request to execute HTTP is here, CLI. HttpClient returns &HTTP. client{transport: cli.transport} // Cli.transport is also set when the CLI is initialized, calling Golang's HTTP packet to make a request to the server resp, err := cli. HTTPClient (). Do (req) if resp != nil {serverresp.statuscode = resp. statuscode} // The next step is a handyman's return value error and HTTP error code judgment 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. Bodyserverresp.header = resp. Headerreturn serverresp, nil}
Combing, in fact, two steps is very important, one step is HTTP. Newrequest, one step is the CLI. HTTPClient (). Do (req)
Remember that there are a bunch of parameters that were created when the CLI was initialized, and look at the code of the CLI initialization
FUNC&NBSP;NEWDOCKERCLI (In io. Readcloser, out, err io. Writer, clientflags *cli. Clientflags) *dockercli {cli := &dockercli{in: in, out: out,err: err,keyfile: clientflags.common.trustkey,} //input, output, error IO, and TLS keyfilecli.init = func () error { Clientflags.postparse () Hosts := clientflags.common.hostsswitch len (hosts) {case 0: Defaulthost := os. Getenv ("Docker_host") if defaulthost == "" {defaulthost = opts. defaulthost}//See how much this defaulthost is//defaulthttphost = "127.0.0.1" defaulthttpport = 2375 // defaulthost = fmt. Sprintf ("tcp://%s:%d", defaulthttphost, defaulthttpport)//actually local use is 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 todefault:return errors. New ("Please specify only one -h")}protoaddrparts := strings. SPLITN (hosts[0], "://", 2) cli.proto, cli.addr = protoaddrparts[0], protoaddrparts[1]//protocol address is initialized here, proto= "tcp/http/..." addr= "127.0.0.1/...." if cli.proto == "TCP" {// error is checked in pkg/parsers alreadyparsed, _ := url. Parse ("tcp://" + cli.addr) cli.addr = parsed. Hostcli.basepath = parsed. path}if clientflags.common.tlsoptions != nil {cli.scheme = "https" var e errorcli.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,}//This very critical cli.transport setting Sockets.configuretcptransport ( CLI.TRANSPORT,&NBSP;CLI.PROTO,&NBSP;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 &NBSP;=&NBSP;CONFIGFILERETURN&NBSP;NIL}RETURN&NBSP;CLI}
Finally, let's see how Clientflags got here, right?
The var clientflags = &cli initialized in Docker/docker/client.go. Clientflags{flagset:new (flag. Flagset), common:commonflags}
//------------------------------------------------------------------------------------------------------
Apply your own version of the CLI to request Docker's daemon
package mainimport ( _ "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 &Nbsp; }}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 This is my own 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+ ")") &NBSP;&NBsp; //1.8.0 is also my own version of 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))}
In summary, this request for Docker is still using the HTTP package inside the Golang, the HTTP packet execution process is too large and complex, this article is not outlined.
Call execution process for the Docker CLI