fix(gateway): better seeking/sized
1. Require files to have known sizes. We can add support for unknown sizes _later_ but we can't use ServeContent for those files. 2. Replace the `sizeReadSeeker` with a `lazySeeker`. This one makes no assumptions about how it's used so we're less likely to run into weird bugs.
Steven Allen committed
Sep 26, 2019 at 13:26 UTC
3859f08bf79c9a68933c9d398f834c5c32462664
2 files changed
+69
-19
core/corehttp/gateway_handler.go
+9
-19
@@ -372,29 +372,19 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request
372
}
373
}
374
375
-type sizeReadSeeker interface {
375
+type sized interface {
376
Size() (int64, error)
377
-
378
- io.ReadSeeker
379
-}
380
-
381
-type sizeSeeker struct {
382
- sizeReadSeeker
377
}
378
385
-func (s *sizeSeeker) Seek(offset int64, whence int) (int64, error) {
386
- if whence == io.SeekEnd && offset == 0 {
387
- return s.Size()
379
+func (i *gatewayHandler) serveFile(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, file files.File) {
380
+ size, err := file.Size()
381
+ if err != nil {
382
+ http.Error(w, "cannot serve files with unknown sizes", http.StatusBadGateway)
383
+ return
384
}
389
-
390
- return s.sizeReadSeeker.Seek(offset, whence)
391
-}
392
-
393
-func (i *gatewayHandler) serveFile(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker) {
394
- if sp, ok := content.(sizeReadSeeker); ok {
395
- content = &sizeSeeker{
396
- sizeReadSeeker: sp,
397
- }
385
+ content := &lazySeeker{
386
+ size: size,
387
+ reader: file,
388
}
389
390
ctype := mime.TypeByExtension(gopath.Ext(name))
core/corehttp/lazyseek.go
new
+60
@@ -0,0 +1,60 @@
1
+package corehttp
2
+
3
+import (
4
+ "fmt"
5
+ "io"
6
+)
7
+
8
+// The HTTP server uses seek to determine the file size. Actually _seeking_ can
9
+// be slow so we wrap the seeker in a _lazy_ seeker.
10
+type lazySeeker struct {
11
+ reader io.ReadSeeker
12
+
13
+ size int64
14
+ offset int64
15
+ realOffset int64
16
+}
17
+
18
+func (s *lazySeeker) Seek(offset int64, whence int) (int64, error) {
19
+ switch whence {
20
+ case io.SeekEnd:
21
+ return s.Seek(s.size+offset, io.SeekStart)
22
+ case io.SeekCurrent:
23
+ return s.Seek(s.offset+offset, io.SeekStart)
24
+ case io.SeekStart:
25
+ if offset < 0 {
26
+ return s.offset, fmt.Errorf("invalid seek offset")
27
+ }
28
+ s.offset = offset
29
+ return s.offset, nil
30
+ default:
31
+ return s.offset, fmt.Errorf("invalid whence: %d", whence)
32
+ }
33
+}
34
+
35
+func (s *lazySeeker) Read(b []byte) (int, error) {
36
+ // If we're past the end, EOF.
37
+ if s.offset >= s.size {
38
+ return 0, io.EOF
39
+ }
40
+
41
+ // actually seek
42
+ for s.offset != s.realOffset {
43
+ off, err := s.reader.Seek(s.offset, io.SeekStart)
44
+ if err != nil {
45
+ return 9, err
46
+ }
47
+ s.realOffset = off
48
+ }
49
+ off, err := s.reader.Read(b)
50
+ s.realOffset += int64(off)
51
+ s.offset += int64(off)
52
+ return off, err
53
+}
54
+
55
+func (s *lazySeeker) Close() error {
56
+ if closer, ok := s.reader.(io.Closer); ok {
57
+ return closer.Close()
58
+ }
59
+ return nil
60
+}