github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/client/transport/http_reader.go (about)

     1  package transport
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"os"
    10  )
    11  
    12  // ReadSeekCloser combines io.ReadSeeker with io.Closer.
    13  type ReadSeekCloser interface {
    14  	io.ReadSeeker
    15  	io.Closer
    16  }
    17  
    18  // NewHTTPReadSeeker handles reading from an HTTP endpoint using a GET
    19  // request. When seeking and starting a read from a non-zero offset
    20  // the a "Range" header will be added which sets the offset.
    21  // TODO(dmcgowan): Move this into a separate utility package
    22  func NewHTTPReadSeeker(client *http.Client, url string, errorHandler func(*http.Response) error) ReadSeekCloser {
    23  	return &httpReadSeeker{
    24  		client:       client,
    25  		url:          url,
    26  		errorHandler: errorHandler,
    27  	}
    28  }
    29  
    30  type httpReadSeeker struct {
    31  	client *http.Client
    32  	url    string
    33  
    34  	// errorHandler creates an error from an unsuccessful HTTP response.
    35  	// This allows the error to be created with the HTTP response body
    36  	// without leaking the body through a returned error.
    37  	errorHandler func(*http.Response) error
    38  
    39  	size int64
    40  
    41  	// rc is the remote read closer.
    42  	rc io.ReadCloser
    43  	// brd is a buffer for internal buffered io.
    44  	brd *bufio.Reader
    45  	// readerOffset tracks the offset as of the last read.
    46  	readerOffset int64
    47  	// seekOffset allows Seek to override the offset. Seek changes
    48  	// seekOffset instead of changing readOffset directly so that
    49  	// connection resets can be delayed and possibly avoided if the
    50  	// seek is undone (i.e. seeking to the end and then back to the
    51  	// beginning).
    52  	seekOffset int64
    53  	err        error
    54  }
    55  
    56  func (hrs *httpReadSeeker) Read(p []byte) (n int, err error) {
    57  	if hrs.err != nil {
    58  		return 0, hrs.err
    59  	}
    60  
    61  	// If we seeked to a different position, we need to reset the
    62  	// connection. This logic is here instead of Seek so that if
    63  	// a seek is undone before the next read, the connection doesn't
    64  	// need to be closed and reopened. A common example of this is
    65  	// seeking to the end to determine the length, and then seeking
    66  	// back to the original position.
    67  	if hrs.readerOffset != hrs.seekOffset {
    68  		hrs.reset()
    69  	}
    70  
    71  	hrs.readerOffset = hrs.seekOffset
    72  
    73  	rd, err := hrs.reader()
    74  	if err != nil {
    75  		return 0, err
    76  	}
    77  
    78  	n, err = rd.Read(p)
    79  	hrs.seekOffset += int64(n)
    80  	hrs.readerOffset += int64(n)
    81  
    82  	// Simulate io.EOF error if we reach filesize.
    83  	if err == nil && hrs.size >= 0 && hrs.readerOffset >= hrs.size {
    84  		err = io.EOF
    85  	}
    86  
    87  	return n, err
    88  }
    89  
    90  func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
    91  	if hrs.err != nil {
    92  		return 0, hrs.err
    93  	}
    94  
    95  	_, err := hrs.reader()
    96  	if err != nil {
    97  		return 0, err
    98  	}
    99  
   100  	newOffset := hrs.seekOffset
   101  
   102  	switch whence {
   103  	case os.SEEK_CUR:
   104  		newOffset += int64(offset)
   105  	case os.SEEK_END:
   106  		if hrs.size < 0 {
   107  			return 0, errors.New("content length not known")
   108  		}
   109  		newOffset = hrs.size + int64(offset)
   110  	case os.SEEK_SET:
   111  		newOffset = int64(offset)
   112  	}
   113  
   114  	if newOffset < 0 {
   115  		err = errors.New("cannot seek to negative position")
   116  	} else {
   117  		hrs.seekOffset = newOffset
   118  	}
   119  
   120  	return hrs.seekOffset, err
   121  }
   122  
   123  func (hrs *httpReadSeeker) Close() error {
   124  	if hrs.err != nil {
   125  		return hrs.err
   126  	}
   127  
   128  	// close and release reader chain
   129  	if hrs.rc != nil {
   130  		hrs.rc.Close()
   131  	}
   132  
   133  	hrs.rc = nil
   134  	hrs.brd = nil
   135  
   136  	hrs.err = errors.New("httpLayer: closed")
   137  
   138  	return nil
   139  }
   140  
   141  func (hrs *httpReadSeeker) reset() {
   142  	if hrs.err != nil {
   143  		return
   144  	}
   145  	if hrs.rc != nil {
   146  		hrs.rc.Close()
   147  		hrs.rc = nil
   148  	}
   149  }
   150  
   151  func (hrs *httpReadSeeker) reader() (io.Reader, error) {
   152  	if hrs.err != nil {
   153  		return nil, hrs.err
   154  	}
   155  
   156  	if hrs.rc != nil {
   157  		return hrs.brd, nil
   158  	}
   159  
   160  	req, err := http.NewRequest("GET", hrs.url, nil)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	if hrs.readerOffset > 0 {
   166  		// TODO(stevvooe): Get this working correctly.
   167  
   168  		// If we are at different offset, issue a range request from there.
   169  		req.Header.Add("Range", "1-")
   170  		// TODO: get context in here
   171  		// context.GetLogger(hrs.context).Infof("Range: %s", req.Header.Get("Range"))
   172  	}
   173  
   174  	resp, err := hrs.client.Do(req)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	// Normally would use client.SuccessStatus, but that would be a cyclic
   180  	// import
   181  	if resp.StatusCode >= 200 && resp.StatusCode <= 399 {
   182  		hrs.rc = resp.Body
   183  		if resp.StatusCode == http.StatusOK {
   184  			hrs.size = resp.ContentLength
   185  		} else {
   186  			hrs.size = -1
   187  		}
   188  	} else {
   189  		defer resp.Body.Close()
   190  		if hrs.errorHandler != nil {
   191  			return nil, hrs.errorHandler(resp)
   192  		}
   193  		return nil, fmt.Errorf("unexpected status resolving reader: %v", resp.Status)
   194  	}
   195  
   196  	if hrs.brd == nil {
   197  		hrs.brd = bufio.NewReader(hrs.rc)
   198  	} else {
   199  		hrs.brd.Reset(hrs.rc)
   200  	}
   201  
   202  	return hrs.brd, nil
   203  }