mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Looking in history to learn why this struct existed, shows that this type was mostly the result of tech-debt accumulating over time; - originally ([moby@1aa7f13]) most of the request handling was internal; the [`call()` function][1] would make a request, read the `response.Body`, and return it as a `[]byte` (or an error if one happened). - some features needed the statuscode, so [moby@a4bcf7e] added an extra output variable to return the `response.StatusCode`. - some new features required streaming, so [moby@fdd8d4b] changed the function to return the `response.Body` as a `io.ReadCloser`, instead of a `[]byte`. - some features needed access to the content-type header, so a new `clientRequest` method was introduced in [moby@6b2eeaf] to read the `Content-Type` header from `response.Headers` and return it as a string. - of course, `Content-Type` may not be the only header needed, so [moby@0cdc3b7] changed the signature to return `response.Headers` as a whole as a `http.Header` - things became a bit unwieldy now, with the function having four (4) output variables, so [moby@126529c] chose to refactor this code, introducing a `serverResponse` struct to wrap them all, not realizing that all these values were effectively deconstructed from the `url.Response`, so now re-assembling them into our own "URL response", only preserving a subset of the information available. - now that we had a custom struct, it was possible to add more information to it without changing the signature. When there was a need to know the URL of the request that initiated the response, [moby@27ef09a] introduced a `reqURL` field to hold the `request.URL` which notably also is available in `response.Request.URL`. In short; - The original implementation tried to (pre-maturely) abstract the underlying response to provide a simplified interface. - While initially not needed, abstracting caused relevant information from the response (and request) to be unavailable to callers. - As a result, we ended up in a situation where we are deconstructing the original `url.Response`, only to re-assemble it into our own, custom struct (`serverResponsee`) with only a subset of the information preserved. This patch removes the `serverResponse` struct, instead returning the `url.Response` as-is, so that all information is preserved, allowing callers to use the information they need. There is one follow-up change to consider; commit [moby@589df17] introduced a `ensureReaderClosed` utility. Before that commit, the response body would be closed in a more idiomatic way through a [`defer serverResp.body.Close()`][2]. A later change in [docker/engine-api@5dd6452] added an optimization to that utility, draining the response to allow connections to be reused. While skipping that utility (and not draining the response) would not be a critical issue, it may be easy to overlook that utility, and to close the response body in the "idiomatic" way, resulting in a possible performance regression. We need to check if that optimization is still relevant or if later changes in Go itself already take care of this; we should also look if context cancellation is handled correctly for these. If it's still relevant, we could - Wrap the the `url.Response` in a custom struct ("drainCloser") to provide a `Close()` function handling the draining and closing; this would re- introduce a custom type to be returned, so perhaps not what we want. - Wrap the `url.Response.Body` in the response returned (so, calling) `response.Body.Close()` would call the wrapped closer. - Change the signature of `Client.sendRequest()` (and related) to return a `close()` func to handle this; doing so would more strongly encourage callers to close the response body. [1]:1aa7f1392d/commands.go (L1008-L1027)[2]:589df17a1a/api/client/ps.go (L84-L89)[moby@1aa7f13]:1aa7f1392d[moby@a4bcf7e]:a4bcf7e1ac[moby@fdd8d4b]:fdd8d4b7d9[moby@6b2eeaf]:6b2eeaf896[moby@0cdc3b7]:0cdc3b7539[moby@126529c]:126529c6d0[moby@27ef09a]:27ef09a46f[moby@589df17]:589df17a1a[docker/engine-api@5dd6452]:5dd6452d4dSigned-off-by: Sebastiaan van Stijn <github@gone.nl>
105 lines
3.4 KiB
Go
105 lines
3.4 KiB
Go
package client // import "github.com/docker/docker/client"
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
|
)
|
|
|
|
// ContainerStatPath returns stat information about a path inside the container filesystem.
|
|
func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (container.PathStat, error) {
|
|
containerID, err := trimID("container", containerID)
|
|
if err != nil {
|
|
return container.PathStat{}, err
|
|
}
|
|
|
|
query := url.Values{}
|
|
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
|
|
|
|
resp, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil)
|
|
defer ensureReaderClosed(resp)
|
|
if err != nil {
|
|
return container.PathStat{}, err
|
|
}
|
|
return getContainerPathStatFromHeader(resp.Header)
|
|
}
|
|
|
|
// CopyToContainer copies content into the container filesystem.
|
|
// Note that `content` must be a Reader for a TAR archive
|
|
func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options container.CopyToContainerOptions) error {
|
|
containerID, err := trimID("container", containerID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
query := url.Values{}
|
|
query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API.
|
|
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
|
|
if !options.AllowOverwriteDirWithFile {
|
|
query.Set("noOverwriteDirNonDir", "true")
|
|
}
|
|
|
|
if options.CopyUIDGID {
|
|
query.Set("copyUIDGID", "true")
|
|
}
|
|
|
|
response, err := cli.putRaw(ctx, "/containers/"+containerID+"/archive", query, content, nil)
|
|
defer ensureReaderClosed(response)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CopyFromContainer gets the content from the container and returns it as a Reader
|
|
// for a TAR archive to manipulate it in the host. It's up to the caller to close the reader.
|
|
func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, container.PathStat, error) {
|
|
containerID, err := trimID("container", containerID)
|
|
if err != nil {
|
|
return nil, container.PathStat{}, err
|
|
}
|
|
|
|
query := make(url.Values, 1)
|
|
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
|
|
|
|
resp, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil)
|
|
if err != nil {
|
|
return nil, container.PathStat{}, err
|
|
}
|
|
|
|
// In order to get the copy behavior right, we need to know information
|
|
// about both the source and the destination. The response headers include
|
|
// stat info about the source that we can use in deciding exactly how to
|
|
// copy it locally. Along with the stat info about the local destination,
|
|
// we have everything we need to handle the multiple possibilities there
|
|
// can be when copying a file/dir from one location to another file/dir.
|
|
stat, err := getContainerPathStatFromHeader(resp.Header)
|
|
if err != nil {
|
|
return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err)
|
|
}
|
|
return resp.Body, stat, err
|
|
}
|
|
|
|
func getContainerPathStatFromHeader(header http.Header) (container.PathStat, error) {
|
|
var stat container.PathStat
|
|
|
|
encodedStat := header.Get("X-Docker-Container-Path-Stat")
|
|
statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
|
|
|
|
err := json.NewDecoder(statDecoder).Decode(&stat)
|
|
if err != nil {
|
|
err = fmt.Errorf("unable to decode container path stat header: %s", err)
|
|
}
|
|
|
|
return stat, err
|
|
}
|