Skip to content

Commit 9e13be8

Browse files
fix(api): support multipart range blob pulls (#3995)
* fix(api): support multipart range blob pulls Signed-off-by: Akash Kumar <meakash7902@gmail.com> * fix(api): tighten multipart range response - Drop the redundant deferred closeRangeReaders; the deferred cleanup registered when the slice is created already covers all paths. - Stop copying the request Accept header into each multipart part's Content-Type. Accept can be a list of media ranges (e.g. "application/octet-stream,*/*"), which is not a valid Content-Type and may confuse multipart parsers. RFC 9110 lets us omit it entirely. - Set Docker-Content-Digest on the partial-content response so range pulls expose the same header as a full GET. - Drop the over-broad build tag on routes_internal_test.go; the parser unit test does not need any extension build tags. Signed-off-by: Akash Kumar <meakash7902@gmail.com> --------- Signed-off-by: Akash Kumar <meakash7902@gmail.com>
1 parent 113c481 commit 9e13be8

3 files changed

Lines changed: 379 additions & 74 deletions

File tree

pkg/api/controller_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
goerrors "errors"
1313
"fmt"
1414
"io"
15+
"mime"
16+
"mime/multipart"
1517
"net"
1618
"net/http"
1719
"net/http/httptest"
@@ -11285,6 +11287,54 @@ func TestPullRange(t *testing.T) {
1128511287
So(resp.Body(), ShouldResemble, content[2:4])
1128611288
})
1128711289

11290+
Convey("Get a suffix range of bytes", func() {
11291+
resp, err = resty.R().SetHeader("Range", "bytes=-3").Get(blobLoc)
11292+
So(err, ShouldBeNil)
11293+
So(resp.StatusCode(), ShouldEqual, http.StatusPartialContent)
11294+
So(resp.Header().Get("Content-Length"), ShouldEqual, "3")
11295+
So(resp.Header().Get("Content-Range"), ShouldEqual, "bytes 7-9/10")
11296+
So(resp.Body(), ShouldResemble, content[7:10])
11297+
11298+
resp, err = resty.R().SetHeader("Range", "bytes=-100").Get(blobLoc)
11299+
So(err, ShouldBeNil)
11300+
So(resp.StatusCode(), ShouldEqual, http.StatusPartialContent)
11301+
So(resp.Header().Get("Content-Length"), ShouldEqual, strconv.Itoa(len(content)))
11302+
So(resp.Header().Get("Content-Range"), ShouldEqual, "bytes 0-9/10")
11303+
So(resp.Body(), ShouldResemble, content)
11304+
})
11305+
11306+
Convey("Get multiple ranges of bytes", func() {
11307+
resp, err = resty.R().SetHeader("Range", "bytes=0-1,4-6").Get(blobLoc)
11308+
So(err, ShouldBeNil)
11309+
So(resp.StatusCode(), ShouldEqual, http.StatusPartialContent)
11310+
11311+
contentType, params, err := mime.ParseMediaType(resp.Header().Get("Content-Type"))
11312+
So(err, ShouldBeNil)
11313+
So(contentType, ShouldEqual, "multipart/byteranges")
11314+
So(params["boundary"], ShouldNotBeEmpty)
11315+
11316+
multipartReader := multipart.NewReader(bytes.NewReader(resp.Body()), params["boundary"])
11317+
11318+
part, err := multipartReader.NextPart()
11319+
So(err, ShouldBeNil)
11320+
So(part.Header.Get("Content-Range"), ShouldEqual, "bytes 0-1/10")
11321+
11322+
partBody, err := io.ReadAll(part)
11323+
So(err, ShouldBeNil)
11324+
So(partBody, ShouldResemble, content[0:2])
11325+
11326+
part, err = multipartReader.NextPart()
11327+
So(err, ShouldBeNil)
11328+
So(part.Header.Get("Content-Range"), ShouldEqual, "bytes 4-6/10")
11329+
11330+
partBody, err = io.ReadAll(part)
11331+
So(err, ShouldBeNil)
11332+
So(partBody, ShouldResemble, content[4:7])
11333+
11334+
_, err = multipartReader.NextPart()
11335+
So(err, ShouldEqual, io.EOF)
11336+
})
11337+
1128811338
Convey("Negative cases", func() {
1128911339
resp, err = resty.R().SetHeader("Range", "=0").Get(blobLoc)
1129011340
So(err, ShouldBeNil)
@@ -11353,6 +11403,11 @@ func TestPullRange(t *testing.T) {
1135311403
resp, err = resty.R().SetHeader("Range", "bytes=a-b").Get(blobLoc)
1135411404
So(err, ShouldBeNil)
1135511405
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
11406+
11407+
resp, err = resty.R().SetHeader("Range", "bytes=100-100").Get(blobLoc)
11408+
So(err, ShouldBeNil)
11409+
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
11410+
So(resp.Header().Get("Content-Range"), ShouldEqual, "bytes */10")
1135611411
})
1135711412
})
1135811413
}

0 commit comments

Comments
 (0)