go-hep.org/x/hep@v0.38.1/groot/internal/httpio/reader.go (about) 1 // Copyright ©2022 The go-hep Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package httpio 6 7 import ( 8 "context" 9 "fmt" 10 "io" 11 "net/http" 12 "strconv" 13 "sync" 14 ) 15 16 // Reader presents an HTTP resource as an io.Reader and io.ReaderAt. 17 type Reader struct { 18 cli *http.Client 19 req *http.Request 20 ctx context.Context 21 cancel context.CancelFunc 22 23 pool sync.Pool 24 25 r *io.SectionReader 26 len int64 27 etag string 28 } 29 30 // Open returns a Reader from the provided URL. 31 func Open(uri string, opts ...Option) (r *Reader, err error) { 32 cfg := newConfig() 33 for _, opt := range opts { 34 err := opt(cfg) 35 if err != nil { 36 return nil, fmt.Errorf("httpio: could not open %q: %w", uri, err) 37 } 38 } 39 40 r = &Reader{ 41 cli: cfg.cli, 42 } 43 r.ctx, r.cancel = context.WithCancel(cfg.ctx) 44 45 req, err := http.NewRequestWithContext(r.ctx, http.MethodGet, uri, nil) 46 if err != nil { 47 r.cancel() 48 return nil, fmt.Errorf("httpio: could not create HTTP request: %w", err) 49 } 50 if cfg.auth.usr != "" || cfg.auth.pwd != "" { 51 req.SetBasicAuth(cfg.auth.usr, cfg.auth.pwd) 52 } 53 r.req = req.Clone(r.ctx) 54 55 hdr, err := r.cli.Head(r.req.URL.String()) 56 if err != nil { 57 r.cancel() 58 return nil, fmt.Errorf("httpio: could not send HEAD request: %w", err) 59 } 60 defer hdr.Body.Close() 61 _, _ = io.Copy(io.Discard, hdr.Body) 62 63 if hdr.StatusCode != http.StatusOK { 64 return nil, fmt.Errorf("httpio: invalid HEAD response code=%v", hdr.StatusCode) 65 } 66 67 if hdr.Header.Get("accept-ranges") != "bytes" { 68 return nil, fmt.Errorf("httpio: invalid HEAD response: %w", errAcceptRange) 69 } 70 71 r.len = hdr.ContentLength 72 r.etag = hdr.Header.Get("Etag") 73 r.r = io.NewSectionReader(r, 0, r.len) 74 75 r.req.Header.Set("Range", "") 76 r.pool = sync.Pool{ 77 New: func() any { 78 return r.req.Clone(r.ctx) 79 }, 80 } 81 82 return r, nil 83 } 84 85 // Size returns the number of bytes available for reading via ReadAt. 86 func (r *Reader) Size() int64 { 87 return r.len 88 } 89 90 // Name returns the name of the file as presented to Open. 91 func (r *Reader) Name() string { 92 return r.req.URL.String() 93 } 94 95 // Close implements the io.Closer interface. 96 func (r *Reader) Close() error { 97 r.cancel() 98 r.cli = nil 99 r.req = nil 100 return nil 101 } 102 103 // Read implements the io.Reader interface. 104 func (r *Reader) Read(p []byte) (int, error) { 105 return r.r.Read(p) 106 } 107 108 // Seek implements the io.Seeker interface. 109 func (r *Reader) Seek(offset int64, whence int) (int64, error) { 110 return r.r.Seek(offset, whence) 111 } 112 113 // ReadAt implements the io.ReaderAt interface. 114 func (r *Reader) ReadAt(p []byte, off int64) (int, error) { 115 if len(p) == 0 { 116 return 0, nil 117 } 118 119 rng := rng(off, off+int64(len(p))-1) 120 req := r.getReq(rng) 121 defer r.pool.Put(req) 122 123 resp, err := r.cli.Do(req) 124 if err != nil { 125 return 0, fmt.Errorf("httpio: could not send GET request: %w", err) 126 } 127 defer resp.Body.Close() 128 129 n, _ := io.ReadFull(resp.Body, p) 130 131 if etag := resp.Header.Get("Etag"); etag != r.etag { 132 return n, fmt.Errorf("httpio: resource changed") 133 } 134 135 switch resp.StatusCode { 136 case http.StatusPartialContent: 137 // ok. 138 case http.StatusRequestedRangeNotSatisfiable: 139 return 0, io.EOF 140 default: 141 return n, fmt.Errorf("httpio: invalid GET response: code=%v", resp.StatusCode) 142 } 143 144 if int64(len(p)) > r.len { 145 return n, io.EOF 146 } 147 148 return n, nil 149 } 150 151 func (r *Reader) getReq(rng string) *http.Request { 152 o := r.pool.Get().(*http.Request) 153 o.Header = r.req.Header.Clone() 154 o.Header["Range"][0] = rng 155 return o 156 } 157 158 func rng(beg, end int64) string { 159 return "bytes=" + strconv.Itoa(int(beg)) + "-" + strconv.Itoa(int(end)) 160 } 161 162 var ( 163 _ io.Reader = (*Reader)(nil) 164 _ io.Seeker = (*Reader)(nil) 165 _ io.ReaderAt = (*Reader)(nil) 166 _ io.Closer = (*Reader)(nil) 167 )