Merge component 'engine' from git@github.com:moby/moby master
This commit is contained in:
@@ -142,7 +142,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Get the "docker-py" source so we can run their integration tests
|
||||
ENV DOCKER_PY_COMMIT 5e28dcaace5f7b70cbe44c313b7a3b288fa38916
|
||||
ENV DOCKER_PY_COMMIT 8b246db271a85d6541dc458838627e89c683e42f
|
||||
# To run integration tests docker-pycreds is required.
|
||||
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
||||
&& cd /docker-py \
|
||||
|
||||
@@ -106,7 +106,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Get the "docker-py" source so we can run their integration tests
|
||||
ENV DOCKER_PY_COMMIT 5e28dcaace5f7b70cbe44c313b7a3b288fa38916
|
||||
ENV DOCKER_PY_COMMIT 8b246db271a85d6541dc458838627e89c683e42f
|
||||
# To run integration tests docker-pycreds is required.
|
||||
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
||||
&& cd /docker-py \
|
||||
|
||||
@@ -104,7 +104,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Get the "docker-py" source so we can run their integration tests
|
||||
ENV DOCKER_PY_COMMIT 5e28dcaace5f7b70cbe44c313b7a3b288fa38916
|
||||
ENV DOCKER_PY_COMMIT 8b246db271a85d6541dc458838627e89c683e42f
|
||||
# To run integration tests docker-pycreds is required.
|
||||
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
||||
&& cd /docker-py \
|
||||
|
||||
@@ -102,7 +102,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Get the "docker-py" source so we can run their integration tests
|
||||
ENV DOCKER_PY_COMMIT 5e28dcaace5f7b70cbe44c313b7a3b288fa38916
|
||||
ENV DOCKER_PY_COMMIT 8b246db271a85d6541dc458838627e89c683e42f
|
||||
# To run integration tests docker-pycreds is required.
|
||||
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
||||
&& cd /docker-py \
|
||||
|
||||
@@ -96,7 +96,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Get the "docker-py" source so we can run their integration tests
|
||||
ENV DOCKER_PY_COMMIT 5e28dcaace5f7b70cbe44c313b7a3b288fa38916
|
||||
ENV DOCKER_PY_COMMIT 8b246db271a85d6541dc458838627e89c683e42f
|
||||
# To run integration tests docker-pycreds is required.
|
||||
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
||||
&& cd /docker-py \
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package container // import "github.com/docker/docker/api/server/router/container"
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
@@ -9,6 +11,7 @@ import (
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
gddohttputil "github.com/golang/gddo/httputil"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
@@ -81,6 +84,29 @@ func (s *containerRouter) headContainersArchive(ctx context.Context, w http.Resp
|
||||
return setContainerPathStatHeader(stat, w.Header())
|
||||
}
|
||||
|
||||
func writeCompressedResponse(w http.ResponseWriter, r *http.Request, body io.Reader) error {
|
||||
var cw io.Writer
|
||||
switch gddohttputil.NegotiateContentEncoding(r, []string{"gzip", "deflate"}) {
|
||||
case "gzip":
|
||||
gw := gzip.NewWriter(w)
|
||||
defer gw.Close()
|
||||
cw = gw
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
case "deflate":
|
||||
fw, err := flate.NewWriter(w, flate.DefaultCompression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fw.Close()
|
||||
cw = fw
|
||||
w.Header().Set("Content-Encoding", "deflate")
|
||||
default:
|
||||
cw = w
|
||||
}
|
||||
_, err := io.Copy(cw, body)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *containerRouter) getContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
v, err := httputils.ArchiveFormValues(r, vars)
|
||||
if err != nil {
|
||||
@@ -98,9 +124,7 @@ func (s *containerRouter) getContainersArchive(ctx context.Context, w http.Respo
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/x-tar")
|
||||
_, err = io.Copy(w, tarArchive)
|
||||
|
||||
return err
|
||||
return writeCompressedResponse(w, r, tarArchive)
|
||||
}
|
||||
|
||||
func (s *containerRouter) putContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestDiffFilenameShownInOutput(t *testing.T) {
|
||||
// a "Files/" prefix.
|
||||
lookingFor := containertypes.ContainerChangeResponseItem{Kind: archive.ChangeAdd, Path: "/foo/bar"}
|
||||
if testEnv.OSType == "windows" {
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(60*time.Second))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(60*time.Second))
|
||||
lookingFor = containertypes.ContainerChangeResponseItem{Kind: archive.ChangeModify, Path: "Files/foo/bar"}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestInspectCpusetInConfigPre120(t *testing.T) {
|
||||
c.HostConfig.Resources.CpusetCpus = "0"
|
||||
},
|
||||
)
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, name, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, name, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
_, body, err := client.ContainerInspectWithRaw(ctx, name, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -23,11 +23,11 @@ func TestKillContainerInvalidSignal(t *testing.T) {
|
||||
|
||||
err := client.ContainerKill(ctx, id, "0")
|
||||
require.EqualError(t, err, "Error response from daemon: Invalid signal: 0")
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
err = client.ContainerKill(ctx, id, "SIG42")
|
||||
require.EqualError(t, err, "Error response from daemon: Invalid signal: SIG42")
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
}
|
||||
|
||||
func TestKillContainer(t *testing.T) {
|
||||
@@ -64,7 +64,7 @@ func TestKillContainer(t *testing.T) {
|
||||
err := client.ContainerKill(ctx, id, tc.signal)
|
||||
require.NoError(t, err)
|
||||
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, id, tc.status), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, id, tc.status), poll.WithDelay(100*time.Millisecond))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ func TestKillWithStopSignalAndRestartPolicies(t *testing.T) {
|
||||
err := client.ContainerKill(ctx, id, "TERM")
|
||||
require.NoError(t, err)
|
||||
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, id, tc.status), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, id, tc.status), poll.WithDelay(100*time.Millisecond))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -141,11 +141,11 @@ func TestKillDifferentUserContainer(t *testing.T) {
|
||||
id := container.Run(t, ctx, client, func(c *container.TestContainerConfig) {
|
||||
c.Config.User = "daemon"
|
||||
})
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
err := client.ContainerKill(ctx, id, "SIGKILL")
|
||||
require.NoError(t, err)
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, id, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, id, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
}
|
||||
|
||||
func TestInspectOomKilledTrue(t *testing.T) {
|
||||
@@ -160,7 +160,7 @@ func TestInspectOomKilledTrue(t *testing.T) {
|
||||
c.HostConfig.Resources.Memory = 32 * 1024 * 1024
|
||||
})
|
||||
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
inspect, err := client.ContainerInspect(ctx, cID)
|
||||
require.NoError(t, err)
|
||||
@@ -177,7 +177,7 @@ func TestInspectOomKilledFalse(t *testing.T) {
|
||||
name := "testoomkilled"
|
||||
cID := container.Run(t, ctx, client, container.WithName(name), container.WithCmd("sh", "-c", "echo hello world"))
|
||||
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
inspect, err := client.ContainerInspect(ctx, cID)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestLinksEtcHostsContentMatch(t *testing.T) {
|
||||
|
||||
cID := container.Run(t, ctx, client, container.WithCmd("cat", "/etc/hosts"), container.WithNetworkMode("host"))
|
||||
|
||||
poll.WaitOn(t, containerIsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
body, err := client.ContainerLogs(ctx, cID, types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
|
||||
@@ -69,7 +69,7 @@ func TestNetworkLoopbackNat(t *testing.T) {
|
||||
|
||||
cID := container.Run(t, ctx, client, container.WithCmd("sh", "-c", fmt.Sprintf("stty raw && nc -w 5 %s 8080", endpoint.String())), container.WithTty(true), container.WithNetworkMode("container:server"))
|
||||
|
||||
poll.WaitOn(t, containerIsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
body, err := client.ContainerLogs(ctx, cID, types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
@@ -98,7 +98,7 @@ func startServerContainer(t *testing.T, msg string, port int) string {
|
||||
}
|
||||
})
|
||||
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
return cID
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestPause(t *testing.T) {
|
||||
|
||||
name := "testeventpause"
|
||||
cID := container.Run(t, ctx, client, container.WithName(name))
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
since := request.DaemonUnixTime(ctx, t, client, testEnv)
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestPauseFailsOnWindowsServerContainers(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
cID := container.Run(t, ctx, client)
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
err := client.ContainerPause(ctx, cID)
|
||||
testutil.ErrorContains(t, err, "cannot pause Windows Server Containers")
|
||||
@@ -73,7 +73,7 @@ func TestPauseStopPausedContainer(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
cID := container.Run(t, ctx, client)
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
err := client.ContainerPause(ctx, cID)
|
||||
require.NoError(t, err)
|
||||
@@ -81,7 +81,7 @@ func TestPauseStopPausedContainer(t *testing.T) {
|
||||
err = client.ContainerStop(ctx, cID, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
poll.WaitOn(t, containerIsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond))
|
||||
}
|
||||
|
||||
func getEventActions(t *testing.T, messages <-chan events.Message, errs <-chan error) []string {
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestRenameStoppedContainer(t *testing.T) {
|
||||
|
||||
oldName := "first_name"
|
||||
cID := container.Run(t, ctx, client, container.WithName(oldName), container.WithCmd("sh"))
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
inspect, err := client.ContainerInspect(ctx, cID)
|
||||
require.NoError(t, err)
|
||||
@@ -73,7 +73,7 @@ func TestRenameRunningContainerAndReuse(t *testing.T) {
|
||||
|
||||
oldName := "first_name"
|
||||
cID := container.Run(t, ctx, client, container.WithName(oldName))
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
newName := "new_name" + stringid.GenerateNonCryptoID()
|
||||
err := client.ContainerRename(ctx, oldName, newName)
|
||||
@@ -87,7 +87,7 @@ func TestRenameRunningContainerAndReuse(t *testing.T) {
|
||||
testutil.ErrorContains(t, err, "No such container: "+oldName)
|
||||
|
||||
cID = container.Run(t, ctx, client, container.WithName(oldName))
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
inspect, err = client.ContainerInspect(ctx, cID)
|
||||
require.NoError(t, err)
|
||||
@@ -101,7 +101,7 @@ func TestRenameInvalidName(t *testing.T) {
|
||||
|
||||
oldName := "first_name"
|
||||
cID := container.Run(t, ctx, client, container.WithName(oldName))
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
err := client.ContainerRename(ctx, oldName, "new:invalid")
|
||||
testutil.ErrorContains(t, err, "Invalid container name")
|
||||
@@ -136,7 +136,7 @@ func TestRenameAnonymousContainer(t *testing.T) {
|
||||
err = client.ContainerStart(ctx, "container1", types.ContainerStartOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
count := "-c"
|
||||
if testEnv.OSType == "windows" {
|
||||
@@ -148,7 +148,7 @@ func TestRenameAnonymousContainer(t *testing.T) {
|
||||
}
|
||||
c.HostConfig.NetworkMode = "network1"
|
||||
}, container.WithCmd("ping", count, "1", "container1"))
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
inspect, err := client.ContainerInspect(ctx, cID)
|
||||
require.NoError(t, err)
|
||||
@@ -162,7 +162,7 @@ func TestRenameContainerWithSameName(t *testing.T) {
|
||||
client := request.NewAPIClient(t)
|
||||
|
||||
cID := container.Run(t, ctx, client, container.WithName("old"))
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
err := client.ContainerRename(ctx, "old", "old")
|
||||
testutil.ErrorContains(t, err, "Renaming a container with the same name")
|
||||
err = client.ContainerRename(ctx, cID, "old")
|
||||
@@ -182,10 +182,10 @@ func TestRenameContainerWithLinkedContainer(t *testing.T) {
|
||||
client := request.NewAPIClient(t)
|
||||
|
||||
db1ID := container.Run(t, ctx, client, container.WithName("db1"))
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, db1ID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, db1ID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
app1ID := container.Run(t, ctx, client, container.WithName("app1"), container.WithLinks("db1:/mysql"))
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, app1ID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, app1ID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
err := client.ContainerRename(ctx, "app1", "app2")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -23,7 +23,7 @@ func TestResize(t *testing.T) {
|
||||
|
||||
cID := container.Run(t, ctx, client)
|
||||
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
err := client.ContainerResize(ctx, cID, types.ResizeOptions{
|
||||
Height: 40,
|
||||
@@ -39,7 +39,7 @@ func TestResizeWithInvalidSize(t *testing.T) {
|
||||
|
||||
cID := container.Run(t, ctx, client)
|
||||
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
endpoint := "/containers/" + cID + "/resize?h=foo&w=bar"
|
||||
res, _, err := req.Post(endpoint)
|
||||
@@ -54,7 +54,7 @@ func TestResizeWhenContainerNotStarted(t *testing.T) {
|
||||
|
||||
cID := container.Run(t, ctx, client, container.WithCmd("echo"))
|
||||
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
err := client.ContainerResize(ctx, cID, types.ResizeOptions{
|
||||
Height: 40,
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestStats(t *testing.T) {
|
||||
|
||||
cID := container.Run(t, ctx, client)
|
||||
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
resp, err := client.ContainerStats(ctx, cID, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/integration/internal/container"
|
||||
"github.com/docker/docker/integration/internal/request"
|
||||
"github.com/gotestyourself/gotestyourself/icmd"
|
||||
@@ -30,7 +29,7 @@ func TestStopContainerWithRestartPolicyAlways(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, name, "running", "restarting"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, name, "running", "restarting"), poll.WithDelay(100*time.Millisecond))
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
@@ -39,7 +38,7 @@ func TestStopContainerWithRestartPolicyAlways(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
poll.WaitOn(t, containerIsStopped(ctx, client, name), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsStopped(ctx, client, name), poll.WithDelay(100*time.Millisecond))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +51,7 @@ func TestDeleteDevicemapper(t *testing.T) {
|
||||
|
||||
id := container.Run(t, ctx, client, container.WithName("foo"), container.WithCmd("echo"))
|
||||
|
||||
poll.WaitOn(t, containerIsStopped(ctx, client, id), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsStopped(ctx, client, id), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
inspect, err := client.ContainerInspect(ctx, id)
|
||||
require.NoError(t, err)
|
||||
@@ -70,33 +69,3 @@ func TestDeleteDevicemapper(t *testing.T) {
|
||||
err = client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func containerIsStopped(ctx context.Context, client client.APIClient, containerID string) func(log poll.LogT) poll.Result {
|
||||
return func(log poll.LogT) poll.Result {
|
||||
inspect, err := client.ContainerInspect(ctx, containerID)
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
return poll.Error(err)
|
||||
case !inspect.State.Running:
|
||||
return poll.Success()
|
||||
default:
|
||||
return poll.Continue("waiting for container to be stopped")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func containerIsInState(ctx context.Context, client client.APIClient, containerID string, state ...string) func(log poll.LogT) poll.Result {
|
||||
return func(log poll.LogT) poll.Result {
|
||||
inspect, err := client.ContainerInspect(ctx, containerID)
|
||||
if err != nil {
|
||||
return poll.Error(err)
|
||||
}
|
||||
for _, v := range state {
|
||||
if inspect.State.Status == v {
|
||||
return poll.Success()
|
||||
}
|
||||
}
|
||||
return poll.Continue("waiting for container to be running, currently %s", inspect.State.Status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func TestUpdateMemory(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
|
||||
|
||||
const (
|
||||
setMemory int64 = 314572800
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestUpdateRestartPolicy(t *testing.T) {
|
||||
timeout = 180 * time.Second
|
||||
}
|
||||
|
||||
poll.WaitOn(t, containerIsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(timeout))
|
||||
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(timeout))
|
||||
|
||||
inspect, err := client.ContainerInspect(ctx, cID)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/gotestyourself/gotestyourself/poll"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// IsStopped verifies the container is in stopped state.
|
||||
func IsStopped(ctx context.Context, client client.APIClient, containerID string) func(log poll.LogT) poll.Result {
|
||||
return func(log poll.LogT) poll.Result {
|
||||
inspect, err := client.ContainerInspect(ctx, containerID)
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
return poll.Error(err)
|
||||
case !inspect.State.Running:
|
||||
return poll.Success()
|
||||
default:
|
||||
return poll.Continue("waiting for container to be stopped")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsInState verifies the container is in one of the specified state, e.g., "running", "exited", etc.
|
||||
func IsInState(ctx context.Context, client client.APIClient, containerID string, state ...string) func(log poll.LogT) poll.Result {
|
||||
return func(log poll.LogT) poll.Result {
|
||||
inspect, err := client.ContainerInspect(ctx, containerID)
|
||||
if err != nil {
|
||||
return poll.Error(err)
|
||||
}
|
||||
for _, v := range state {
|
||||
if inspect.State.Status == v {
|
||||
return poll.Success()
|
||||
}
|
||||
}
|
||||
return poll.Continue("waiting for container to be one of (%s), currently %s", strings.Join(state, ", "), inspect.State.Status)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ github.com/Microsoft/go-winio v0.4.6
|
||||
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
||||
github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
|
||||
github.com/golang/gddo 9b12a26f3fbd7397dee4e20939ddca719d840d2a
|
||||
github.com/gorilla/context v1.1
|
||||
github.com/gorilla/mux v1.1
|
||||
github.com/Microsoft/opengcs v0.3.6
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2013 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
This project is the source for http://godoc.org/
|
||||
|
||||
[](http://godoc.org/github.com/golang/gddo)
|
||||
[](https://travis-ci.org/golang/gddo)
|
||||
|
||||
The code in this project is designed to be used by godoc.org. Send mail to
|
||||
golang-dev@googlegroups.com if you want to discuss other uses of the code.
|
||||
|
||||
## Feedback
|
||||
|
||||
Send ideas and questions to golang-dev@googlegroups.com. Request features and
|
||||
report bugs using the [GitHub Issue
|
||||
Tracker](https://github.com/golang/gddo/issues/new).
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions to this project are welcome, though please [file an
|
||||
issue](https://github.com/golang/gddo/issues/new). before starting work on
|
||||
anything major.
|
||||
|
||||
**We do not accept GitHub pull requests**
|
||||
|
||||
Please refer to the [Contribution
|
||||
Guidelines](https://golang.org/doc/contribute.html) on how to submit changes.
|
||||
|
||||
We use https://go-review.googlesource.com to review change submissions.
|
||||
|
||||
## Getting the Source
|
||||
|
||||
To get started contributing to this project, clone the repository from its
|
||||
canonical location
|
||||
|
||||
```
|
||||
git clone https://go.googlesource.com/gddo $GOPATH/src/github.com/golang/gddo
|
||||
```
|
||||
|
||||
Information on how to set up a local environment is available at
|
||||
https://github.com/golang/gddo/wiki/Development-Environment-Setup.
|
||||
|
||||
## More Documentation
|
||||
|
||||
More documentation about this project is available on the
|
||||
[wiki](https://github.com/golang/gddo/wiki).
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type busterWriter struct {
|
||||
headerMap http.Header
|
||||
status int
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (bw *busterWriter) Header() http.Header {
|
||||
return bw.headerMap
|
||||
}
|
||||
|
||||
func (bw *busterWriter) WriteHeader(status int) {
|
||||
bw.status = status
|
||||
}
|
||||
|
||||
// CacheBusters maintains a cache of cache busting tokens for static resources served by Handler.
|
||||
type CacheBusters struct {
|
||||
Handler http.Handler
|
||||
|
||||
mu sync.Mutex
|
||||
tokens map[string]string
|
||||
}
|
||||
|
||||
func sanitizeTokenRune(r rune) rune {
|
||||
if r <= ' ' || r >= 127 {
|
||||
return -1
|
||||
}
|
||||
// Convert percent encoding reserved characters to '-'.
|
||||
if strings.ContainsRune("!#$&'()*+,/:;=?@[]", r) {
|
||||
return '-'
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Get returns the cache busting token for path. If the token is not already
|
||||
// cached, Get issues a HEAD request on handler and uses the response ETag and
|
||||
// Last-Modified headers to compute a token.
|
||||
func (cb *CacheBusters) Get(path string) string {
|
||||
cb.mu.Lock()
|
||||
if cb.tokens == nil {
|
||||
cb.tokens = make(map[string]string)
|
||||
}
|
||||
token, ok := cb.tokens[path]
|
||||
cb.mu.Unlock()
|
||||
if ok {
|
||||
return token
|
||||
}
|
||||
|
||||
w := busterWriter{
|
||||
Writer: ioutil.Discard,
|
||||
headerMap: make(http.Header),
|
||||
}
|
||||
r := &http.Request{URL: &url.URL{Path: path}, Method: "HEAD"}
|
||||
cb.Handler.ServeHTTP(&w, r)
|
||||
|
||||
if w.status == 200 {
|
||||
token = w.headerMap.Get("Etag")
|
||||
if token == "" {
|
||||
token = w.headerMap.Get("Last-Modified")
|
||||
}
|
||||
token = strings.Trim(token, `" `)
|
||||
token = strings.Map(sanitizeTokenRune, token)
|
||||
}
|
||||
|
||||
cb.mu.Lock()
|
||||
cb.tokens[path] = token
|
||||
cb.mu.Unlock()
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
// AppendQueryParam appends the token as a query parameter to path.
|
||||
func (cb *CacheBusters) AppendQueryParam(path string, name string) string {
|
||||
token := cb.Get(path)
|
||||
if token == "" {
|
||||
return path
|
||||
}
|
||||
return path + "?" + name + "=" + token
|
||||
}
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
// Package header provides functions for parsing HTTP headers.
|
||||
package header
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Octet types from RFC 2616.
|
||||
var octetTypes [256]octetType
|
||||
|
||||
type octetType byte
|
||||
|
||||
const (
|
||||
isToken octetType = 1 << iota
|
||||
isSpace
|
||||
)
|
||||
|
||||
func init() {
|
||||
// OCTET = <any 8-bit sequence of data>
|
||||
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
||||
// CR = <US-ASCII CR, carriage return (13)>
|
||||
// LF = <US-ASCII LF, linefeed (10)>
|
||||
// SP = <US-ASCII SP, space (32)>
|
||||
// HT = <US-ASCII HT, horizontal-tab (9)>
|
||||
// <"> = <US-ASCII double-quote mark (34)>
|
||||
// CRLF = CR LF
|
||||
// LWS = [CRLF] 1*( SP | HT )
|
||||
// TEXT = <any OCTET except CTLs, but including LWS>
|
||||
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
||||
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
||||
// token = 1*<any CHAR except CTLs or separators>
|
||||
// qdtext = <any TEXT except <">>
|
||||
|
||||
for c := 0; c < 256; c++ {
|
||||
var t octetType
|
||||
isCtl := c <= 31 || c == 127
|
||||
isChar := 0 <= c && c <= 127
|
||||
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
||||
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
||||
t |= isSpace
|
||||
}
|
||||
if isChar && !isCtl && !isSeparator {
|
||||
t |= isToken
|
||||
}
|
||||
octetTypes[c] = t
|
||||
}
|
||||
}
|
||||
|
||||
// Copy returns a shallow copy of the header.
|
||||
func Copy(header http.Header) http.Header {
|
||||
h := make(http.Header)
|
||||
for k, vs := range header {
|
||||
h[k] = vs
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
var timeLayouts = []string{"Mon, 02 Jan 2006 15:04:05 GMT", time.RFC850, time.ANSIC}
|
||||
|
||||
// ParseTime parses the header as time. The zero value is returned if the
|
||||
// header is not present or there is an error parsing the
|
||||
// header.
|
||||
func ParseTime(header http.Header, key string) time.Time {
|
||||
if s := header.Get(key); s != "" {
|
||||
for _, layout := range timeLayouts {
|
||||
if t, err := time.Parse(layout, s); err == nil {
|
||||
return t.UTC()
|
||||
}
|
||||
}
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// ParseList parses a comma separated list of values. Commas are ignored in
|
||||
// quoted strings. Quoted values are not unescaped or unquoted. Whitespace is
|
||||
// trimmed.
|
||||
func ParseList(header http.Header, key string) []string {
|
||||
var result []string
|
||||
for _, s := range header[http.CanonicalHeaderKey(key)] {
|
||||
begin := 0
|
||||
end := 0
|
||||
escape := false
|
||||
quote := false
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case escape:
|
||||
escape = false
|
||||
end = i + 1
|
||||
case quote:
|
||||
switch b {
|
||||
case '\\':
|
||||
escape = true
|
||||
case '"':
|
||||
quote = false
|
||||
}
|
||||
end = i + 1
|
||||
case b == '"':
|
||||
quote = true
|
||||
end = i + 1
|
||||
case octetTypes[b]&isSpace != 0:
|
||||
if begin == end {
|
||||
begin = i + 1
|
||||
end = begin
|
||||
}
|
||||
case b == ',':
|
||||
if begin < end {
|
||||
result = append(result, s[begin:end])
|
||||
}
|
||||
begin = i + 1
|
||||
end = begin
|
||||
default:
|
||||
end = i + 1
|
||||
}
|
||||
}
|
||||
if begin < end {
|
||||
result = append(result, s[begin:end])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ParseValueAndParams parses a comma separated list of values with optional
|
||||
// semicolon separated name-value pairs. Content-Type and Content-Disposition
|
||||
// headers are in this format.
|
||||
func ParseValueAndParams(header http.Header, key string) (value string, params map[string]string) {
|
||||
params = make(map[string]string)
|
||||
s := header.Get(key)
|
||||
value, s = expectTokenSlash(s)
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
value = strings.ToLower(value)
|
||||
s = skipSpace(s)
|
||||
for strings.HasPrefix(s, ";") {
|
||||
var pkey string
|
||||
pkey, s = expectToken(skipSpace(s[1:]))
|
||||
if pkey == "" {
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(s, "=") {
|
||||
return
|
||||
}
|
||||
var pvalue string
|
||||
pvalue, s = expectTokenOrQuoted(s[1:])
|
||||
if pvalue == "" {
|
||||
return
|
||||
}
|
||||
pkey = strings.ToLower(pkey)
|
||||
params[pkey] = pvalue
|
||||
s = skipSpace(s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AcceptSpec describes an Accept* header.
|
||||
type AcceptSpec struct {
|
||||
Value string
|
||||
Q float64
|
||||
}
|
||||
|
||||
// ParseAccept parses Accept* headers.
|
||||
func ParseAccept(header http.Header, key string) (specs []AcceptSpec) {
|
||||
loop:
|
||||
for _, s := range header[key] {
|
||||
for {
|
||||
var spec AcceptSpec
|
||||
spec.Value, s = expectTokenSlash(s)
|
||||
if spec.Value == "" {
|
||||
continue loop
|
||||
}
|
||||
spec.Q = 1.0
|
||||
s = skipSpace(s)
|
||||
if strings.HasPrefix(s, ";") {
|
||||
s = skipSpace(s[1:])
|
||||
if !strings.HasPrefix(s, "q=") {
|
||||
continue loop
|
||||
}
|
||||
spec.Q, s = expectQuality(s[2:])
|
||||
if spec.Q < 0.0 {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
specs = append(specs, spec)
|
||||
s = skipSpace(s)
|
||||
if !strings.HasPrefix(s, ",") {
|
||||
continue loop
|
||||
}
|
||||
s = skipSpace(s[1:])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func skipSpace(s string) (rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isSpace == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[i:]
|
||||
}
|
||||
|
||||
func expectToken(s string) (token, rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isToken == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[:i], s[i:]
|
||||
}
|
||||
|
||||
func expectTokenSlash(s string) (token, rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
b := s[i]
|
||||
if (octetTypes[b]&isToken == 0) && b != '/' {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[:i], s[i:]
|
||||
}
|
||||
|
||||
func expectQuality(s string) (q float64, rest string) {
|
||||
switch {
|
||||
case len(s) == 0:
|
||||
return -1, ""
|
||||
case s[0] == '0':
|
||||
q = 0
|
||||
case s[0] == '1':
|
||||
q = 1
|
||||
default:
|
||||
return -1, ""
|
||||
}
|
||||
s = s[1:]
|
||||
if !strings.HasPrefix(s, ".") {
|
||||
return q, s
|
||||
}
|
||||
s = s[1:]
|
||||
i := 0
|
||||
n := 0
|
||||
d := 1
|
||||
for ; i < len(s); i++ {
|
||||
b := s[i]
|
||||
if b < '0' || b > '9' {
|
||||
break
|
||||
}
|
||||
n = n*10 + int(b) - '0'
|
||||
d *= 10
|
||||
}
|
||||
return q + float64(n)/float64(d), s[i:]
|
||||
}
|
||||
|
||||
func expectTokenOrQuoted(s string) (value string, rest string) {
|
||||
if !strings.HasPrefix(s, "\"") {
|
||||
return expectToken(s)
|
||||
}
|
||||
s = s[1:]
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '"':
|
||||
return s[:i], s[i+1:]
|
||||
case '\\':
|
||||
p := make([]byte, len(s)-1)
|
||||
j := copy(p, s[:i])
|
||||
escape := true
|
||||
for i = i + 1; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case escape:
|
||||
escape = false
|
||||
p[j] = b
|
||||
j++
|
||||
case b == '\\':
|
||||
escape = true
|
||||
case b == '"':
|
||||
return string(p[:j]), s[i+1:]
|
||||
default:
|
||||
p[j] = b
|
||||
j++
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
// Package httputil is a toolkit for the Go net/http package.
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// StripPort removes the port specification from an address.
|
||||
func StripPort(s string) string {
|
||||
if h, _, err := net.SplitHostPort(s); err == nil {
|
||||
s = h
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Error defines a type for a function that accepts a ResponseWriter for
|
||||
// a Request with the HTTP status code and error.
|
||||
type Error func(w http.ResponseWriter, r *http.Request, status int, err error)
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"github.com/golang/gddo/httputil/header"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NegotiateContentEncoding returns the best offered content encoding for the
|
||||
// request's Accept-Encoding header. If two offers match with equal weight and
|
||||
// then the offer earlier in the list is preferred. If no offers are
|
||||
// acceptable, then "" is returned.
|
||||
func NegotiateContentEncoding(r *http.Request, offers []string) string {
|
||||
bestOffer := "identity"
|
||||
bestQ := -1.0
|
||||
specs := header.ParseAccept(r.Header, "Accept-Encoding")
|
||||
for _, offer := range offers {
|
||||
for _, spec := range specs {
|
||||
if spec.Q > bestQ &&
|
||||
(spec.Value == "*" || spec.Value == offer) {
|
||||
bestQ = spec.Q
|
||||
bestOffer = offer
|
||||
}
|
||||
}
|
||||
}
|
||||
if bestQ == 0 {
|
||||
bestOffer = ""
|
||||
}
|
||||
return bestOffer
|
||||
}
|
||||
|
||||
// NegotiateContentType returns the best offered content type for the request's
|
||||
// Accept header. If two offers match with equal weight, then the more specific
|
||||
// offer is preferred. For example, text/* trumps */*. If two offers match
|
||||
// with equal weight and specificity, then the offer earlier in the list is
|
||||
// preferred. If no offers match, then defaultOffer is returned.
|
||||
func NegotiateContentType(r *http.Request, offers []string, defaultOffer string) string {
|
||||
bestOffer := defaultOffer
|
||||
bestQ := -1.0
|
||||
bestWild := 3
|
||||
specs := header.ParseAccept(r.Header, "Accept")
|
||||
for _, offer := range offers {
|
||||
for _, spec := range specs {
|
||||
switch {
|
||||
case spec.Q == 0.0:
|
||||
// ignore
|
||||
case spec.Q < bestQ:
|
||||
// better match found
|
||||
case spec.Value == "*/*":
|
||||
if spec.Q > bestQ || bestWild > 2 {
|
||||
bestQ = spec.Q
|
||||
bestWild = 2
|
||||
bestOffer = offer
|
||||
}
|
||||
case strings.HasSuffix(spec.Value, "/*"):
|
||||
if strings.HasPrefix(offer, spec.Value[:len(spec.Value)-1]) &&
|
||||
(spec.Q > bestQ || bestWild > 1) {
|
||||
bestQ = spec.Q
|
||||
bestWild = 1
|
||||
bestOffer = offer
|
||||
}
|
||||
default:
|
||||
if spec.Value == offer &&
|
||||
(spec.Q > bestQ || bestWild > 0) {
|
||||
bestQ = spec.Q
|
||||
bestWild = 0
|
||||
bestOffer = offer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestOffer
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ResponseBuffer is the current response being composed by its owner.
|
||||
// It implements http.ResponseWriter and io.WriterTo.
|
||||
type ResponseBuffer struct {
|
||||
buf bytes.Buffer
|
||||
status int
|
||||
header http.Header
|
||||
}
|
||||
|
||||
// Write implements the http.ResponseWriter interface.
|
||||
func (rb *ResponseBuffer) Write(p []byte) (int, error) {
|
||||
return rb.buf.Write(p)
|
||||
}
|
||||
|
||||
// WriteHeader implements the http.ResponseWriter interface.
|
||||
func (rb *ResponseBuffer) WriteHeader(status int) {
|
||||
rb.status = status
|
||||
}
|
||||
|
||||
// Header implements the http.ResponseWriter interface.
|
||||
func (rb *ResponseBuffer) Header() http.Header {
|
||||
if rb.header == nil {
|
||||
rb.header = make(http.Header)
|
||||
}
|
||||
return rb.header
|
||||
}
|
||||
|
||||
// WriteTo implements the io.WriterTo interface.
|
||||
func (rb *ResponseBuffer) WriteTo(w http.ResponseWriter) error {
|
||||
for k, v := range rb.header {
|
||||
w.Header()[k] = v
|
||||
}
|
||||
if rb.buf.Len() > 0 {
|
||||
w.Header().Set("Content-Length", strconv.Itoa(rb.buf.Len()))
|
||||
}
|
||||
if rb.status != 0 {
|
||||
w.WriteHeader(rb.status)
|
||||
}
|
||||
if rb.buf.Len() > 0 {
|
||||
if _, err := w.Write(rb.buf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/golang/gddo/httputil/header"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StaticServer serves static files.
|
||||
type StaticServer struct {
|
||||
// Dir specifies the location of the directory containing the files to serve.
|
||||
Dir string
|
||||
|
||||
// MaxAge specifies the maximum age for the cache control and expiration
|
||||
// headers.
|
||||
MaxAge time.Duration
|
||||
|
||||
// Error specifies the function used to generate error responses. If Error
|
||||
// is nil, then http.Error is used to generate error responses.
|
||||
Error Error
|
||||
|
||||
// MIMETypes is a map from file extensions to MIME types.
|
||||
MIMETypes map[string]string
|
||||
|
||||
mu sync.Mutex
|
||||
etags map[string]string
|
||||
}
|
||||
|
||||
func (ss *StaticServer) resolve(fname string) string {
|
||||
if path.IsAbs(fname) {
|
||||
panic("Absolute path not allowed when creating a StaticServer handler")
|
||||
}
|
||||
dir := ss.Dir
|
||||
if dir == "" {
|
||||
dir = "."
|
||||
}
|
||||
fname = filepath.FromSlash(fname)
|
||||
return filepath.Join(dir, fname)
|
||||
}
|
||||
|
||||
func (ss *StaticServer) mimeType(fname string) string {
|
||||
ext := path.Ext(fname)
|
||||
var mimeType string
|
||||
if ss.MIMETypes != nil {
|
||||
mimeType = ss.MIMETypes[ext]
|
||||
}
|
||||
if mimeType == "" {
|
||||
mimeType = mime.TypeByExtension(ext)
|
||||
}
|
||||
if mimeType == "" {
|
||||
mimeType = "application/octet-stream"
|
||||
}
|
||||
return mimeType
|
||||
}
|
||||
|
||||
func (ss *StaticServer) openFile(fname string) (io.ReadCloser, int64, string, error) {
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return nil, 0, "", err
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, 0, "", err
|
||||
}
|
||||
const modeType = os.ModeDir | os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice
|
||||
if fi.Mode()&modeType != 0 {
|
||||
f.Close()
|
||||
return nil, 0, "", errors.New("not a regular file")
|
||||
}
|
||||
return f, fi.Size(), ss.mimeType(fname), nil
|
||||
}
|
||||
|
||||
// FileHandler returns a handler that serves a single file. The file is
|
||||
// specified by a slash separated path relative to the static server's Dir
|
||||
// field.
|
||||
func (ss *StaticServer) FileHandler(fileName string) http.Handler {
|
||||
id := fileName
|
||||
fileName = ss.resolve(fileName)
|
||||
return &staticHandler{
|
||||
ss: ss,
|
||||
id: func(_ string) string { return id },
|
||||
open: func(_ string) (io.ReadCloser, int64, string, error) { return ss.openFile(fileName) },
|
||||
}
|
||||
}
|
||||
|
||||
// DirectoryHandler returns a handler that serves files from a directory tree.
|
||||
// The directory is specified by a slash separated path relative to the static
|
||||
// server's Dir field.
|
||||
func (ss *StaticServer) DirectoryHandler(prefix, dirName string) http.Handler {
|
||||
if !strings.HasSuffix(prefix, "/") {
|
||||
prefix += "/"
|
||||
}
|
||||
idBase := dirName
|
||||
dirName = ss.resolve(dirName)
|
||||
return &staticHandler{
|
||||
ss: ss,
|
||||
id: func(p string) string {
|
||||
if !strings.HasPrefix(p, prefix) {
|
||||
return "."
|
||||
}
|
||||
return path.Join(idBase, p[len(prefix):])
|
||||
},
|
||||
open: func(p string) (io.ReadCloser, int64, string, error) {
|
||||
if !strings.HasPrefix(p, prefix) {
|
||||
return nil, 0, "", errors.New("request url does not match directory prefix")
|
||||
}
|
||||
p = p[len(prefix):]
|
||||
return ss.openFile(filepath.Join(dirName, filepath.FromSlash(p)))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// FilesHandler returns a handler that serves the concatentation of the
|
||||
// specified files. The files are specified by slash separated paths relative
|
||||
// to the static server's Dir field.
|
||||
func (ss *StaticServer) FilesHandler(fileNames ...string) http.Handler {
|
||||
|
||||
// todo: cache concatenated files on disk and serve from there.
|
||||
|
||||
mimeType := ss.mimeType(fileNames[0])
|
||||
var buf []byte
|
||||
var openErr error
|
||||
|
||||
for _, fileName := range fileNames {
|
||||
p, err := ioutil.ReadFile(ss.resolve(fileName))
|
||||
if err != nil {
|
||||
openErr = err
|
||||
buf = nil
|
||||
break
|
||||
}
|
||||
buf = append(buf, p...)
|
||||
}
|
||||
|
||||
id := strings.Join(fileNames, " ")
|
||||
|
||||
return &staticHandler{
|
||||
ss: ss,
|
||||
id: func(_ string) string { return id },
|
||||
open: func(p string) (io.ReadCloser, int64, string, error) {
|
||||
return ioutil.NopCloser(bytes.NewReader(buf)), int64(len(buf)), mimeType, openErr
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type staticHandler struct {
|
||||
id func(fname string) string
|
||||
open func(p string) (io.ReadCloser, int64, string, error)
|
||||
ss *StaticServer
|
||||
}
|
||||
|
||||
func (h *staticHandler) error(w http.ResponseWriter, r *http.Request, status int, err error) {
|
||||
http.Error(w, http.StatusText(status), status)
|
||||
}
|
||||
|
||||
func (h *staticHandler) etag(p string) (string, error) {
|
||||
id := h.id(p)
|
||||
|
||||
h.ss.mu.Lock()
|
||||
if h.ss.etags == nil {
|
||||
h.ss.etags = make(map[string]string)
|
||||
}
|
||||
etag := h.ss.etags[id]
|
||||
h.ss.mu.Unlock()
|
||||
|
||||
if etag != "" {
|
||||
return etag, nil
|
||||
}
|
||||
|
||||
// todo: if a concurrent goroutine is calculating the hash, then wait for
|
||||
// it instead of computing it again here.
|
||||
|
||||
rc, _, _, err := h.open(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer rc.Close()
|
||||
|
||||
w := sha1.New()
|
||||
_, err = io.Copy(w, rc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
etag = fmt.Sprintf(`"%x"`, w.Sum(nil))
|
||||
|
||||
h.ss.mu.Lock()
|
||||
h.ss.etags[id] = etag
|
||||
h.ss.mu.Unlock()
|
||||
|
||||
return etag, nil
|
||||
}
|
||||
|
||||
func (h *staticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
p := path.Clean(r.URL.Path)
|
||||
if p != r.URL.Path {
|
||||
http.Redirect(w, r, p, 301)
|
||||
return
|
||||
}
|
||||
|
||||
etag, err := h.etag(p)
|
||||
if err != nil {
|
||||
h.error(w, r, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
maxAge := h.ss.MaxAge
|
||||
if maxAge == 0 {
|
||||
maxAge = 24 * time.Hour
|
||||
}
|
||||
if r.FormValue("v") != "" {
|
||||
maxAge = 365 * 24 * time.Hour
|
||||
}
|
||||
|
||||
cacheControl := fmt.Sprintf("public, max-age=%d", maxAge/time.Second)
|
||||
|
||||
for _, e := range header.ParseList(r.Header, "If-None-Match") {
|
||||
if e == etag {
|
||||
w.Header().Set("Cache-Control", cacheControl)
|
||||
w.Header().Set("Etag", etag)
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rc, cl, ct, err := h.open(p)
|
||||
if err != nil {
|
||||
h.error(w, r, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
w.Header().Set("Cache-Control", cacheControl)
|
||||
w.Header().Set("Etag", etag)
|
||||
if ct != "" {
|
||||
w.Header().Set("Content-Type", ct)
|
||||
}
|
||||
if cl != 0 {
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(cl, 10))
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if r.Method != "HEAD" {
|
||||
io.Copy(w, rc)
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
// This file implements a http.RoundTripper that authenticates
|
||||
// requests issued against api.github.com endpoint.
|
||||
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// AuthTransport is an implementation of http.RoundTripper that authenticates
|
||||
// with the GitHub API.
|
||||
//
|
||||
// When both a token and client credentials are set, the latter is preferred.
|
||||
type AuthTransport struct {
|
||||
UserAgent string
|
||||
GithubToken string
|
||||
GithubClientID string
|
||||
GithubClientSecret string
|
||||
Base http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip implements the http.RoundTripper interface.
|
||||
func (t *AuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
var reqCopy *http.Request
|
||||
if t.UserAgent != "" {
|
||||
reqCopy = copyRequest(req)
|
||||
reqCopy.Header.Set("User-Agent", t.UserAgent)
|
||||
}
|
||||
if req.URL.Host == "api.github.com" && req.URL.Scheme == "https" {
|
||||
switch {
|
||||
case t.GithubClientID != "" && t.GithubClientSecret != "":
|
||||
if reqCopy == nil {
|
||||
reqCopy = copyRequest(req)
|
||||
}
|
||||
if reqCopy.URL.RawQuery == "" {
|
||||
reqCopy.URL.RawQuery = "client_id=" + t.GithubClientID + "&client_secret=" + t.GithubClientSecret
|
||||
} else {
|
||||
reqCopy.URL.RawQuery += "&client_id=" + t.GithubClientID + "&client_secret=" + t.GithubClientSecret
|
||||
}
|
||||
case t.GithubToken != "":
|
||||
if reqCopy == nil {
|
||||
reqCopy = copyRequest(req)
|
||||
}
|
||||
reqCopy.Header.Set("Authorization", "token "+t.GithubToken)
|
||||
}
|
||||
}
|
||||
if reqCopy != nil {
|
||||
return t.base().RoundTrip(reqCopy)
|
||||
}
|
||||
return t.base().RoundTrip(req)
|
||||
}
|
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
func (t *AuthTransport) CancelRequest(req *http.Request) {
|
||||
type canceler interface {
|
||||
CancelRequest(req *http.Request)
|
||||
}
|
||||
if cr, ok := t.base().(canceler); ok {
|
||||
cr.CancelRequest(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *AuthTransport) base() http.RoundTripper {
|
||||
if t.Base != nil {
|
||||
return t.Base
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
func copyRequest(req *http.Request) *http.Request {
|
||||
req2 := new(http.Request)
|
||||
*req2 = *req
|
||||
req2.URL = new(url.URL)
|
||||
*req2.URL = *req.URL
|
||||
req2.Header = make(http.Header, len(req.Header))
|
||||
for k, s := range req.Header {
|
||||
req2.Header[k] = append([]string(nil), s...)
|
||||
}
|
||||
return req2
|
||||
}
|
||||
Reference in New Issue
Block a user