github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/rkt/image/downloader.go (about) 1 // Copyright 2015 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package image 16 17 import ( 18 "fmt" 19 "io" 20 "net/http" 21 "net/url" 22 23 "github.com/hashicorp/errwrap" 24 ) 25 26 // downloadSession is an interface used by downloader for controlling 27 // the downloading process. 28 type downloadSession interface { 29 // Client returns a client used for handling the 30 // requests. It is a good place for e.g. setting up a redirect 31 // handling. 32 Client() (*http.Client, error) 33 // Request returns an HTTP request. It is a good place to 34 // add some headers to a request. 35 Request(u *url.URL) (*http.Request, error) 36 // HandleStatus is mostly used to check if the response has 37 // the required HTTP status. When this function returns either 38 // an error or a false value, then the downloader will skip 39 // getting contents of the response body. 40 HandleStatus(res *http.Response) (bool, error) 41 // BodyReader returns a reader used to get contents of the 42 // response body. 43 BodyReader(*http.Response) (io.Reader, error) 44 } 45 46 // downloader has a rather obvious purpose - it downloads stuff. 47 type downloader struct { 48 // Session controls the download process 49 Session downloadSession 50 } 51 52 // Download tries to fetch the passed URL and write the contents into 53 // a given writeSyncer instance. 54 func (d *downloader) Download(u *url.URL, out writeSyncer) error { 55 client, err := d.Session.Client() 56 if err != nil { 57 return err 58 } 59 req, err := d.Session.Request(u) 60 if err != nil { 61 return err 62 } 63 res, err := client.Do(req) 64 if err != nil { 65 return err 66 } 67 defer res.Body.Close() 68 69 if stopNow, err := d.Session.HandleStatus(res); stopNow || err != nil { 70 return err 71 } 72 73 reader, err := d.Session.BodyReader(res) 74 if err != nil { 75 return err 76 } 77 if _, err := io.Copy(out, reader); err != nil { 78 return errwrap.Wrap(fmt.Errorf("failed to download %q", u.String()), err) 79 } 80 81 if err := out.Sync(); err != nil { 82 return errwrap.Wrap(fmt.Errorf("failed to sync data from %q to disk", u.String()), err) 83 } 84 85 return nil 86 } 87 88 // default DownloadSession is very simple implementation of 89 // downloadSession interface. It returns a default client, a GET 90 // request without additional headers and treats any HTTP status of a 91 // response other than 200 as an error. 92 type defaultDownloadSession struct{} 93 94 func (s *defaultDownloadSession) BodyReader(res *http.Response) (io.Reader, error) { 95 return res.Body, nil 96 } 97 98 func (s *defaultDownloadSession) Client() (*http.Client, error) { 99 return http.DefaultClient, nil 100 } 101 102 func (s *defaultDownloadSession) Request(u *url.URL) (*http.Request, error) { 103 req := &http.Request{ 104 Method: "GET", 105 URL: u, 106 Proto: "HTTP/1.1", 107 ProtoMajor: 1, 108 ProtoMinor: 1, 109 Header: make(http.Header), 110 Host: u.Host, 111 } 112 return req, nil 113 } 114 115 func (s *defaultDownloadSession) HandleStatus(res *http.Response) (bool, error) { 116 switch res.StatusCode { 117 case http.StatusOK: 118 return false, nil 119 default: 120 return false, fmt.Errorf("bad HTTP status code: %d", res.StatusCode) 121 } 122 }