2018-02-05 16:05:59 -05:00
package client // import "github.com/docker/docker/client"
2016-09-06 14:46:37 -04:00
import (
"bytes"
2018-04-19 18:30:59 -04:00
"context"
2016-09-06 14:46:37 -04:00
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
2016-09-21 17:04:44 -04:00
"os"
2016-09-06 14:46:37 -04:00
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
2019-02-09 09:53:29 -05:00
"github.com/docker/docker/errdefs"
2016-09-08 18:37:45 -04:00
"github.com/pkg/errors"
2016-09-06 14:46:37 -04:00
)
// serverResponse is a wrapper for http API responses.
type serverResponse struct {
body io . ReadCloser
header http . Header
statusCode int
2017-06-26 12:06:34 -04:00
reqURL * url . URL
2016-09-06 14:46:37 -04:00
}
// head sends an http request to the docker API using the method HEAD.
func ( cli * Client ) head ( ctx context . Context , path string , query url . Values , headers map [ string ] [ ] string ) ( serverResponse , error ) {
2019-10-12 14:41:14 -04:00
return cli . sendRequest ( ctx , http . MethodHead , path , query , nil , headers )
2016-09-06 14:46:37 -04:00
}
2016-12-01 15:18:02 -05:00
// get sends an http request to the docker API using the method GET with a specific Go context.
2016-09-06 14:46:37 -04:00
func ( cli * Client ) get ( ctx context . Context , path string , query url . Values , headers map [ string ] [ ] string ) ( serverResponse , error ) {
2019-10-12 14:41:14 -04:00
return cli . sendRequest ( ctx , http . MethodGet , path , query , nil , headers )
2016-09-06 14:46:37 -04:00
}
2016-12-01 15:18:02 -05:00
// post sends an http request to the docker API using the method POST with a specific Go context.
2016-09-06 14:46:37 -04:00
func ( cli * Client ) post ( ctx context . Context , path string , query url . Values , obj interface { } , headers map [ string ] [ ] string ) ( serverResponse , error ) {
2016-10-31 12:39:38 -04:00
body , headers , err := encodeBody ( obj , headers )
if err != nil {
return serverResponse { } , err
}
2019-10-12 14:41:14 -04:00
return cli . sendRequest ( ctx , http . MethodPost , path , query , body , headers )
2016-09-06 14:46:37 -04:00
}
func ( cli * Client ) postRaw ( ctx context . Context , path string , query url . Values , body io . Reader , headers map [ string ] [ ] string ) ( serverResponse , error ) {
2019-10-12 14:41:14 -04:00
return cli . sendRequest ( ctx , http . MethodPost , path , query , body , headers )
2016-09-06 14:46:37 -04:00
}
2016-12-01 15:18:02 -05:00
// putRaw sends an http request to the docker API using the method PUT.
2016-09-06 14:46:37 -04:00
func ( cli * Client ) putRaw ( ctx context . Context , path string , query url . Values , body io . Reader , headers map [ string ] [ ] string ) ( serverResponse , error ) {
2019-10-12 14:41:14 -04:00
return cli . sendRequest ( ctx , http . MethodPut , path , query , body , headers )
2016-09-06 14:46:37 -04:00
}
// delete sends an http request to the docker API using the method DELETE.
func ( cli * Client ) delete ( ctx context . Context , path string , query url . Values , headers map [ string ] [ ] string ) ( serverResponse , error ) {
2019-10-12 14:41:14 -04:00
return cli . sendRequest ( ctx , http . MethodDelete , path , query , nil , headers )
2016-09-06 14:46:37 -04:00
}
2016-10-31 12:39:38 -04:00
type headers map [ string ] [ ] string
2016-09-06 14:46:37 -04:00
2016-10-31 12:39:38 -04:00
func encodeBody ( obj interface { } , headers headers ) ( io . Reader , headers , error ) {
if obj == nil {
return nil , headers , nil
2016-09-06 14:46:37 -04:00
}
2016-10-31 12:39:38 -04:00
body , err := encodeData ( obj )
if err != nil {
return nil , headers , err
2016-09-06 14:46:37 -04:00
}
2016-10-31 12:39:38 -04:00
if headers == nil {
headers = make ( map [ string ] [ ] string )
}
headers [ "Content-Type" ] = [ ] string { "application/json" }
return body , headers , nil
}
2016-09-06 14:46:37 -04:00
2016-10-31 12:39:38 -04:00
func ( cli * Client ) buildRequest ( method , path string , body io . Reader , headers headers ) ( * http . Request , error ) {
2019-10-12 14:41:14 -04:00
expectedPayload := ( method == http . MethodPost || method == http . MethodPut )
2016-09-06 14:46:37 -04:00
if expectedPayload && body == nil {
body = bytes . NewReader ( [ ] byte { } )
}
2016-10-31 12:39:38 -04:00
req , err := http . NewRequest ( method , path , body )
2016-09-06 14:46:37 -04:00
if err != nil {
2016-10-31 12:39:38 -04:00
return nil , err
2016-09-06 14:46:37 -04:00
}
2016-10-31 12:39:38 -04:00
req = cli . addHeaders ( req , headers )
2016-09-06 14:46:37 -04:00
if cli . proto == "unix" || cli . proto == "npipe" {
// For local communications, it doesn't matter what the host is. We just
// need a valid and meaningful host name. (See #189)
req . Host = "docker"
}
2016-09-08 23:44:25 -04:00
2016-09-06 14:46:37 -04:00
req . URL . Host = cli . addr
2016-10-11 18:53:14 -04:00
req . URL . Scheme = cli . scheme
2016-09-06 14:46:37 -04:00
if expectedPayload && req . Header . Get ( "Content-Type" ) == "" {
req . Header . Set ( "Content-Type" , "text/plain" )
}
2016-10-31 12:39:38 -04:00
return req , nil
}
func ( cli * Client ) sendRequest ( ctx context . Context , method , path string , query url . Values , body io . Reader , headers headers ) ( serverResponse , error ) {
2019-04-08 08:14:07 -04:00
req , err := cli . buildRequest ( method , cli . getAPIPath ( ctx , path , query ) , body , headers )
2016-10-31 12:39:38 -04:00
if err != nil {
return serverResponse { } , err
}
2020-12-10 18:13:37 -05:00
2017-06-26 12:06:34 -04:00
resp , err := cli . doRequest ( ctx , req )
2020-12-10 18:13:37 -05:00
switch {
case errors . Is ( err , context . Canceled ) :
return serverResponse { } , errdefs . Cancelled ( err )
case errors . Is ( err , context . DeadlineExceeded ) :
return serverResponse { } , errdefs . Deadline ( err )
case err == nil :
err = cli . checkResponseErr ( resp )
2017-06-26 12:06:34 -04:00
}
2019-02-09 09:53:29 -05:00
return resp , errdefs . FromStatusCode ( err , resp . statusCode )
2016-10-31 12:39:38 -04:00
}
func ( cli * Client ) doRequest ( ctx context . Context , req * http . Request ) ( serverResponse , error ) {
2017-06-26 12:06:34 -04:00
serverResp := serverResponse { statusCode : - 1 , reqURL : req . URL }
2016-09-06 14:46:37 -04:00
2018-10-16 12:44:53 -04:00
req = req . WithContext ( ctx )
resp , err := cli . client . Do ( req )
2016-09-06 14:46:37 -04:00
if err != nil {
2016-10-11 18:53:14 -04:00
if cli . scheme != "https" && strings . Contains ( err . Error ( ) , "malformed HTTP response" ) {
2016-09-06 14:46:37 -04:00
return serverResp , fmt . Errorf ( "%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?" , err )
}
2016-10-11 18:53:14 -04:00
if cli . scheme == "https" && strings . Contains ( err . Error ( ) , "bad certificate" ) {
2018-12-31 09:33:40 -05:00
return serverResp , errors . Wrap ( err , "The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings" )
2016-09-06 14:46:37 -04:00
}
// Don't decorate context sentinel errors; users may be comparing to
// them directly.
2020-08-03 08:59:22 -04:00
if errors . Is ( err , context . Canceled ) || errors . Is ( err , context . DeadlineExceeded ) {
2016-09-06 14:46:37 -04:00
return serverResp , err
}
2016-09-21 17:04:44 -04:00
if nErr , ok := err . ( * url . Error ) ; ok {
if nErr , ok := nErr . Err . ( * net . OpError ) ; ok {
if os . IsPermission ( nErr . Err ) {
return serverResp , errors . Wrapf ( err , "Got permission denied while trying to connect to the Docker daemon socket at %v" , cli . host )
}
}
}
2016-09-06 14:46:37 -04:00
if err , ok := err . ( net . Error ) ; ok {
if err . Timeout ( ) {
return serverResp , ErrorConnectionFailed ( cli . host )
}
if ! err . Temporary ( ) {
if strings . Contains ( err . Error ( ) , "connection refused" ) || strings . Contains ( err . Error ( ) , "dial unix" ) {
return serverResp , ErrorConnectionFailed ( cli . host )
}
}
}
2016-09-08 18:37:45 -04:00
2016-10-14 13:14:43 -04:00
// Although there's not a strongly typed error for this in go-winio,
// lots of people are using the default configuration for the docker
// daemon on Windows where the daemon is listening on a named pipe
// `//./pipe/docker_engine, and the client must be running elevated.
// Give users a clue rather than the not-overly useful message
2016-11-11 14:51:26 -05:00
// such as `error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.26/info:
2016-10-14 13:14:43 -04:00
// open //./pipe/docker_engine: The system cannot find the file specified.`.
// Note we can't string compare "The system cannot find the file specified" as
// this is localised - for example in French the error would be
// `open //./pipe/docker_engine: Le fichier spécifié est introuvable.`
if strings . Contains ( err . Error ( ) , ` open //./pipe/docker_engine ` ) {
2019-07-22 05:07:50 -04:00
// Checks if client is running with elevated privileges
if f , elevatedErr := os . Open ( "\\\\.\\PHYSICALDRIVE0" ) ; elevatedErr == nil {
err = errors . Wrap ( err , "In the default daemon configuration on Windows, the docker client must be run with elevated privileges to connect." )
} else {
f . Close ( )
err = errors . Wrap ( err , "This error may indicate that the docker daemon is not running." )
}
2016-10-14 13:14:43 -04:00
}
2016-09-08 18:37:45 -04:00
return serverResp , errors . Wrap ( err , "error during connect" )
2016-09-06 14:46:37 -04:00
}
if resp != nil {
serverResp . statusCode = resp . StatusCode
2017-06-26 12:06:34 -04:00
serverResp . body = resp . Body
serverResp . header = resp . Header
2016-09-06 14:46:37 -04:00
}
2017-06-26 12:06:34 -04:00
return serverResp , nil
}
2016-09-06 14:46:37 -04:00
2017-06-26 12:06:34 -04:00
func ( cli * Client ) checkResponseErr ( serverResp serverResponse ) error {
if serverResp . statusCode >= 200 && serverResp . statusCode < 400 {
return nil
}
2016-09-06 14:46:37 -04:00
Add HEAD support for /_ping endpoint
Monitoring systems and load balancers are usually configured to use HEAD
requests for health monitoring. The /_ping endpoint currently does not
support this type of request, which means that those systems have fallback
to GET requests.
This patch adds support for HEAD requests on the /_ping endpoint.
Although optional, this patch also returns `Content-Type` and `Content-Length`
headers in case of a HEAD request; Refering to RFC 7231, section 4.3.2:
The HEAD method is identical to GET except that the server MUST NOT
send a message body in the response (i.e., the response terminates at
the end of the header section). The server SHOULD send the same
header fields in response to a HEAD request as it would have sent if
the request had been a GET, except that the payload header fields
(Section 3.3) MAY be omitted. This method can be used for obtaining
metadata about the selected representation without transferring the
representation data and is often used for testing hypertext links for
validity, accessibility, and recent modification.
A payload within a HEAD request message has no defined semantics;
sending a payload body on a HEAD request might cause some existing
implementations to reject the request.
The response to a HEAD request is cacheable; a cache MAY use it to
satisfy subsequent HEAD requests unless otherwise indicated by the
Cache-Control header field (Section 5.2 of [RFC7234]). A HEAD
response might also have an effect on previously cached responses to
GET; see Section 4.3.5 of [RFC7234].
With this patch applied, either `GET` or `HEAD` requests work; the only
difference is that the body is empty in case of a `HEAD` request;
curl -i --unix-socket /var/run/docker.sock http://localhost/_ping
HTTP/1.1 200 OK
Api-Version: 1.40
Cache-Control: no-cache, no-store, must-revalidate
Docker-Experimental: false
Ostype: linux
Pragma: no-cache
Server: Docker/dev (linux)
Date: Mon, 14 Jan 2019 12:35:16 GMT
Content-Length: 2
Content-Type: text/plain; charset=utf-8
OK
curl --head -i --unix-socket /var/run/docker.sock http://localhost/_ping
HTTP/1.1 200 OK
Api-Version: 1.40
Cache-Control: no-cache, no-store, must-revalidate
Content-Length: 0
Content-Type: text/plain; charset=utf-8
Docker-Experimental: false
Ostype: linux
Pragma: no-cache
Server: Docker/dev (linux)
Date: Mon, 14 Jan 2019 12:34:15 GMT
The client is also updated to use `HEAD` by default, but fallback to `GET`
if the daemon does not support this method.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-01-14 12:08:49 -05:00
var body [ ] byte
var err error
if serverResp . body != nil {
bodyMax := 1 * 1024 * 1024 // 1 MiB
bodyR := & io . LimitedReader {
R : serverResp . body ,
N : int64 ( bodyMax ) ,
}
body , err = ioutil . ReadAll ( bodyR )
if err != nil {
return err
}
if bodyR . N == 0 {
return fmt . Errorf ( "request returned %s with a message (> %d bytes) for API route and version %s, check if the server supports the requested API version" , http . StatusText ( serverResp . statusCode ) , bodyMax , serverResp . reqURL )
}
2018-10-10 12:37:39 -04:00
}
2017-06-26 12:06:34 -04:00
if len ( body ) == 0 {
2017-09-08 12:04:34 -04:00
return fmt . Errorf ( "request returned %s for API route and version %s, check if the server supports the requested API version" , http . StatusText ( serverResp . statusCode ) , serverResp . reqURL )
2017-06-26 12:06:34 -04:00
}
2016-09-06 14:46:37 -04:00
2017-06-26 12:06:34 -04:00
var ct string
if serverResp . header != nil {
ct = serverResp . header . Get ( "Content-Type" )
2016-09-06 14:46:37 -04:00
}
2017-06-26 12:06:34 -04:00
var errorMessage string
if ( cli . version == "" || versions . GreaterThan ( cli . version , "1.23" ) ) && ct == "application/json" {
var errorResponse types . ErrorResponse
if err := json . Unmarshal ( body , & errorResponse ) ; err != nil {
2018-12-31 09:33:40 -05:00
return errors . Wrap ( err , "Error reading JSON" )
2017-06-26 12:06:34 -04:00
}
2018-12-31 09:33:40 -05:00
errorMessage = strings . TrimSpace ( errorResponse . Message )
2017-06-26 12:06:34 -04:00
} else {
2018-12-31 09:33:40 -05:00
errorMessage = strings . TrimSpace ( string ( body ) )
2017-06-26 12:06:34 -04:00
}
2018-12-31 09:33:40 -05:00
return errors . Wrap ( errors . New ( errorMessage ) , "Error response from daemon" )
2016-09-06 14:46:37 -04:00
}
2016-10-31 12:39:38 -04:00
func ( cli * Client ) addHeaders ( req * http . Request , headers headers ) * http . Request {
2016-09-06 14:46:37 -04:00
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
// then the user can't change OUR headers
for k , v := range cli . customHTTPHeaders {
2016-11-02 20:43:32 -04:00
if versions . LessThan ( cli . version , "1.25" ) && k == "User-Agent" {
continue
}
2016-09-06 14:46:37 -04:00
req . Header . Set ( k , v )
}
2021-04-16 10:15:10 -04:00
for k , v := range headers {
req . Header [ k ] = v
2016-09-06 14:46:37 -04:00
}
2016-10-31 12:39:38 -04:00
return req
2016-09-06 14:46:37 -04:00
}
func encodeData ( data interface { } ) ( * bytes . Buffer , error ) {
params := bytes . NewBuffer ( nil )
if data != nil {
if err := json . NewEncoder ( params ) . Encode ( data ) ; err != nil {
return nil , err
}
}
return params , nil
}
func ensureReaderClosed ( response serverResponse ) {
2017-06-26 12:06:34 -04:00
if response . body != nil {
2016-09-06 14:46:37 -04:00
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
2017-06-26 12:06:34 -04:00
io . CopyN ( ioutil . Discard , response . body , 512 )
2016-09-06 14:46:37 -04:00
response . body . Close ( )
}
}