github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/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 // GetClient returns a client used for handling the 30 // requests. It is a good place for e.g. setting up a redirect 31 // handling. 32 GetClient() (*http.Client, error) 33 // GetRequest returns an HTTP request. Is is a good place to 34 // add some headers to a request. 35 GetRequest(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 // GetBodyReader returns a reader used to get contents of the 42 // response body. 43 GetBodyReader(*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 d.ensureSession() 56 client, err := d.Session.GetClient() 57 if err != nil { 58 return err 59 } 60 req, err := d.Session.GetRequest(u) 61 if err != nil { 62 return err 63 } 64 res, err := client.Do(req) 65 if err != nil { 66 return err 67 } 68 defer res.Body.Close() 69 70 if stopNow, err := d.Session.HandleStatus(res); stopNow || err != nil { 71 return err 72 } 73 74 reader, err := d.Session.GetBodyReader(res) 75 if err != nil { 76 return err 77 } 78 if _, err := io.Copy(out, reader); err != nil { 79 return errwrap.Wrap(fmt.Errorf("failed to download %q", u.String()), err) 80 } 81 82 if err := out.Sync(); err != nil { 83 return errwrap.Wrap(fmt.Errorf("failed to sync data from %q to disk", u.String()), err) 84 } 85 86 return nil 87 } 88 89 func (d *downloader) ensureSession() { 90 if isReallyNil(d.Session) { 91 d.Session = &defaultDownloadSession{} 92 } 93 } 94 95 // default DownloadSession is very simple implementation of 96 // downloadSession interface. It returns a default client, a GET 97 // request without additional headers and treats any HTTP status of a 98 // response other than 200 as an error. 99 type defaultDownloadSession struct{} 100 101 func (s *defaultDownloadSession) GetBodyReader(res *http.Response) (io.Reader, error) { 102 return res.Body, nil 103 } 104 105 func (s *defaultDownloadSession) GetClient() (*http.Client, error) { 106 return http.DefaultClient, nil 107 } 108 109 func (s *defaultDownloadSession) GetRequest(u *url.URL) (*http.Request, error) { 110 req := &http.Request{ 111 Method: "GET", 112 URL: u, 113 Proto: "HTTP/1.1", 114 ProtoMajor: 1, 115 ProtoMinor: 1, 116 Header: make(http.Header), 117 Host: u.Host, 118 } 119 return req, nil 120 } 121 122 func (s *defaultDownloadSession) HandleStatus(res *http.Response) (bool, error) { 123 switch res.StatusCode { 124 case http.StatusOK: 125 return false, nil 126 default: 127 return false, fmt.Errorf("bad HTTP status code: %d", res.StatusCode) 128 } 129 }