vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Answer byte ranges on raw downloads
26c9be338872126250f581f47c16ae5619f2cd2c · Keenan Nemetz · 9d ago · parent 1dfa8cbd
Answer byte ranges on raw downloads `/r/<name>/raw/<ref>/<path>` streamed the whole blob and ignored `Range`, so a client resuming a download or asking for a few KiB received the whole file. The artifact route already answers `206`; this does the same for repository content. A blob is streamed from `git cat-file`, which cannot seek, so a range reads and discards the bytes before it and copies only the requested part. The win is what does not cross the network: a 64 KiB range of an 8 MiB file is still 8 MiB of reading on the server, but 64 KiB of transfer instead of 8 MiB. - A single `Range` is answered with `206`, `Content-Range`, and only the requested bytes; prefix, open-ended and suffix forms all work. - A range that starts past the end is `416` with `Content-Range: bytes */<size>`. - Multiple ranges, an unknown unit, and a malformed header get the whole file with `200`, which RFC 9110 allows. - The no-range response keeps its bytes and gains `Accept-Ranges: bytes`. - `HEAD` stops after the headers instead of reading the blob. The load harness's `raw-range` scenario now reports `206`: 365.6 req/s at concurrency 4, p50 11 ms, against 265.6 req/s and p50 15 ms when the full file was sent.
server/web/byte_range_test.go +48
| @@ -0,0 +1,48 @@ | |||
| 1 | package web | ||
| 2 | |||
| 3 | import "testing" | ||
| 4 | |||
| 5 | // TestParseByteRange pins the decisions a Range header can ask for: serve a | ||
| 6 | // part, refuse because it starts past the end, or ignore it and serve the whole | ||
| 7 | // file. The last case is the one that must not surprise a client: an | ||
| 8 | // unparsable header is not an error, it is "no range". | ||
| 9 | func TestParseByteRange(t *testing.T) { | ||
| 10 | cases := []struct { | ||
| 11 | name string | ||
| 12 | header string | ||
| 13 | size int64 | ||
| 14 | start, length int64 | ||
| 15 | partial bool | ||
| 16 | unsatisfiable bool | ||
| 17 | }{ | ||
| 18 | {name: "no header", size: 20}, | ||
| 19 | {name: "other unit", header: "items=0-1", size: 20}, | ||
| 20 | {name: "prefix", header: "bytes=0-9", size: 20, start: 0, length: 10, partial: true}, | ||
| 21 | {name: "middle", header: "bytes=5-9", size: 20, start: 5, length: 5, partial: true}, | ||
| 22 | {name: "open ended", header: "bytes=15-", size: 20, start: 15, length: 5, partial: true}, | ||
| 23 | {name: "end past size", header: "bytes=15-99", size: 20, start: 15, length: 5, partial: true}, | ||
| 24 | {name: "whole file", header: "bytes=0-", size: 20, start: 0, length: 20, partial: true}, | ||
| 25 | {name: "suffix", header: "bytes=-5", size: 20, start: 15, length: 5, partial: true}, | ||
| 26 | {name: "suffix larger than the file", header: "bytes=-99", size: 20, start: 0, length: 20, partial: true}, | ||
| 27 | {name: "start past the end", header: "bytes=20-", size: 20, unsatisfiable: true}, | ||
| 28 | {name: "range past the end", header: "bytes=25-30", size: 20, unsatisfiable: true}, | ||
| 29 | {name: "zero suffix", header: "bytes=-0", size: 20, unsatisfiable: true}, | ||
| 30 | {name: "empty file", header: "bytes=0-", size: 0, unsatisfiable: true}, | ||
| 31 | {name: "multiple ranges", header: "bytes=0-1,3-4", size: 20}, | ||
| 32 | {name: "not a number", header: "bytes=abc", size: 20}, | ||
| 33 | {name: "reversed", header: "bytes=5-3", size: 20}, | ||
| 34 | {name: "no dash", header: "bytes=5", size: 20}, | ||
| 35 | } | ||
| 36 | for _, c := range cases { | ||
| 37 | t.Run(c.name, func(t *testing.T) { | ||
| 38 | br, partial, unsatisfiable := parseByteRange(c.header, c.size) | ||
| 39 | if partial != c.partial || unsatisfiable != c.unsatisfiable { | ||
| 40 | t.Fatalf("parseByteRange(%q, %d) = partial %t, unsatisfiable %t; want %t, %t", | ||
| 41 | c.header, c.size, partial, unsatisfiable, c.partial, c.unsatisfiable) | ||
| 42 | } | ||
| 43 | if c.partial && (br.start != c.start || br.length != c.length) { | ||
| 44 | t.Fatalf("range = %+v; want start %d length %d", br, c.start, c.length) | ||
| 45 | } | ||
| 46 | }) | ||
| 47 | } | ||
| 48 | } | ||