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  }