Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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.go +92

@@ -0,0 +1,92 @@
1 package web
2
3 // Byte ranges for a download.
4 //
5 // A blob is streamed from git, which cannot seek, so a range is served by
6 // reading and discarding the bytes before it and copying only the requested
7 // part. The win is what does not cross the network: a player asking for the
8 // first few KiB of a 100 MiB asset costs one copy of the skipped bytes on the
9 // server, not 100 MiB of transfer to the client.
10
11 import (
12 "strconv"
13 "strings"
14 )
15
16 // byteRange is one satisfiable range of a representation.
17 type byteRange struct {
18 start int64
19 length int64
20 }
21
22 // parseByteRange reads a Range header against a representation of size bytes.
23 //
24 // It answers one of three ways: a range to serve (partial), a range that cannot
25 // be satisfied (unsatisfiable), or neither — no header, an unparsable one, or
26 // more than one range. The last group means "serve the whole file", which
27 // RFC 9110 allows; answering multiple ranges with a single full response keeps
28 // the reader honest rather than silently dropping the ones it did not implement.
29 func parseByteRange(header string, size int64) (br byteRange, partial, unsatisfiable bool) {
30 header = strings.TrimSpace(header)
31 const prefix = "bytes="
32 if !strings.HasPrefix(header, prefix) {
33 return byteRange{}, false, false
34 }
35 spec := strings.TrimSpace(strings.TrimPrefix(header, prefix))
36 if spec == "" || strings.ContainsRune(spec, ',') {
37 return byteRange{}, false, false
38 }
39 first, last, ok := strings.Cut(spec, "-")
40 if !ok {
41 return byteRange{}, false, false
42 }
43 first, last = strings.TrimSpace(first), strings.TrimSpace(last)
44
45 if first == "" {
46 // A suffix range: the last n bytes.
47 n, ok := parseRangeInt(last)
48 if !ok {
49 return byteRange{}, false, false
50 }
51 if n == 0 || size == 0 {
52 return byteRange{}, false, true
53 }
54 if n > size {
55 n = size
56 }
57 return byteRange{start: size - n, length: n}, true, false
58 }
59 start, ok := parseRangeInt(first)
60 if !ok {
61 return byteRange{}, false, false
62 }
63 if start >= size {
64 return byteRange{}, false, true
65 }
66 if last == "" {
67 return byteRange{start: start, length: size - start}, true, false
68 }
69 end, ok := parseRangeInt(last)
70 if !ok || end < start {
71 return byteRange{}, false, false
72 }
73 if end >= size {
74 end = size - 1
75 }
76 return byteRange{start: start, length: end - start + 1}, true, false
77 }
78
79 // parseRangeInt reads the digits of one range bound. A sign, a space, or an
80 // overflow is not a bound, so the header is ignored rather than guessed at.
81 func parseRangeInt(s string) (int64, bool) {
82 if s == "" {
83 return 0, false
84 }
85 for _, r := range s {
86 if r < '0' || r > '9' {
87 return 0, false
88 }
89 }
90 n, err := strconv.ParseInt(s, 10, 64)
91 return n, err == nil
92 }