API attach: return vnd.docker.multiplexed-stream header

The attach API used to always return the Content-Type
`vnd.docker.raw-stream`, however docker api v1.42 added the
`vnd.docker.multiplexed-stream` type when no tty was used.

Follow suit and return the same header for docker api v1.42 and libpod
v4.7.0. This technically allows clients to make a small optimization as
they no longer need to inspect the container to see if they get a raw or
multiplexed stream.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger
2023-08-24 16:11:39 +02:00
parent 243f365aa4
commit 7c9c969815
5 changed files with 37 additions and 6 deletions

View File

@ -614,7 +614,7 @@ func (r *ConmonOCIRuntime) HTTPAttach(ctr *Container, req *http.Request, w http.
hijackDone <- true
writeHijackHeader(req, httpBuf)
writeHijackHeader(req, httpBuf, isTerminal)
// Force a flush after the header is written.
if err := httpBuf.Flush(); err != nil {

View File

@ -569,7 +569,7 @@ func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.Resp
hijackDone <- true
// Write a header to let the client know what happened
writeHijackHeader(r, httpBuf)
writeHijackHeader(r, httpBuf, isTerminal)
// Force a flush after the header is written.
if err := httpBuf.Flush(); err != nil {

View File

@ -15,6 +15,7 @@ import (
"github.com/containers/common/libnetwork/types"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/api/handlers/utils/apiutil"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/sirupsen/logrus"
@ -182,22 +183,36 @@ func makeHTTPAttachHeader(stream byte, length uint32) []byte {
// writeHijackHeader writes a header appropriate for the type of HTTP Hijack
// that occurred in a hijacked HTTP connection used for attach.
func writeHijackHeader(r *http.Request, conn io.Writer) {
func writeHijackHeader(r *http.Request, conn io.Writer, tty bool) {
// AttachHeader is the literal header sent for upgraded/hijacked connections for
// attach, sourced from Docker at:
// https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go
// Using literally to ensure compatibility with existing clients.
// New docker API uses a different header for the non tty case.
// Lets do the same for libpod. Only do this for the new api versions to not break older clients.
header := "application/vnd.docker.raw-stream"
if !tty {
version := "4.7.0"
if !apiutil.IsLibpodRequest(r) {
version = "1.42.0" // docker only used two digest "1.42" but our semver lib needs the extra .0 to work
}
if _, err := apiutil.SupportedVersion(r, ">= "+version); err == nil {
header = "application/vnd.docker.multiplexed-stream"
}
}
c := r.Header.Get("Connection")
proto := r.Header.Get("Upgrade")
if len(proto) == 0 || !strings.EqualFold(c, "Upgrade") {
// OK - can't upgrade if not requested or protocol is not specified
fmt.Fprintf(conn,
"HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
"HTTP/1.1 200 OK\r\nContent-Type: %s\r\n\r\n", header)
} else {
// Upgraded
fmt.Fprintf(conn,
"HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: %s\r\n\r\n",
proto)
"HTTP/1.1 101 UPGRADED\r\nContent-Type: %s\r\nConnection: Upgrade\r\nUpgrade: %s\r\n\r\n",
proto, header)
}
}

View File

@ -1376,6 +1376,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
//
// When the TTY setting is disabled for the container,
// the HTTP Content-Type header is set to application/vnd.docker.multiplexed-stream
// (starting with v4.7.0, previously application/vnd.docker.raw-stream was always used)
// and the stream over the hijacked connected is multiplexed to separate out
// `stdout` and `stderr`. The stream consists of a series of frames, each
// containing a header and a payload.

View File

@ -30,6 +30,21 @@ podman run --rm -d --replace --name foo $IMAGE sh -c "echo $mytext;sleep 42"
# Looks like it is missing the required 0 bytes from the message, why?
t POST "containers/foo/attach?logs=true&stream=false" 200 \
$'\001\031'$mytext
# check old docker header
response_headers=$(cat "$WORKDIR/curl.headers.out")
like "$response_headers" ".*Content-Type: application/vnd\.docker\.raw-stream.*" "vnd.docker.raw-stream docker v1.40"
# check new vnd.docker.multiplexed-stream header
t POST "/v1.42/containers/foo/attach?logs=true&stream=false" 200
response_headers=$(cat "$WORKDIR/curl.headers.out")
like "$response_headers" ".*Content-Type: application/vnd\.docker\.multiplexed-stream.*" "vnd.docker.multiplexed-stream docker v1.42"
t POST "/v4.6.0/libpod/containers/foo/attach?logs=true&stream=false" 200
response_headers=$(cat "$WORKDIR/curl.headers.out")
like "$response_headers" ".*Content-Type: application/vnd\.docker\.raw-stream.*" "vnd.docker.raw-stream libpod v4.6.0"
t POST "/v4.7.0/libpod/containers/foo/attach?logs=true&stream=false" 200
response_headers=$(cat "$WORKDIR/curl.headers.out")
like "$response_headers" ".*Content-Type: application/vnd\.docker\.multiplexed-stream.*" "vnd.docker.multiplexed-stream libpod v4.7.0"
t POST "containers/foo/kill" 204
podman run --replace --name=foo -v /tmp:/tmp $IMAGE true