github.com/nalind/docker@v1.5.0/pkg/httputils/resumablerequestreader.go (about)

     1  package httputils
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"time"
     8  
     9  	log "github.com/Sirupsen/logrus"
    10  )
    11  
    12  type resumableRequestReader struct {
    13  	client          *http.Client
    14  	request         *http.Request
    15  	lastRange       int64
    16  	totalSize       int64
    17  	currentResponse *http.Response
    18  	failures        uint32
    19  	maxFailures     uint32
    20  }
    21  
    22  // ResumableRequestReader makes it possible to resume reading a request's body transparently
    23  // maxfail is the number of times we retry to make requests again (not resumes)
    24  // totalsize is the total length of the body; auto detect if not provided
    25  func ResumableRequestReader(c *http.Client, r *http.Request, maxfail uint32, totalsize int64) io.ReadCloser {
    26  	return &resumableRequestReader{client: c, request: r, maxFailures: maxfail, totalSize: totalsize}
    27  }
    28  
    29  func ResumableRequestReaderWithInitialResponse(c *http.Client, r *http.Request, maxfail uint32, totalsize int64, initialResponse *http.Response) io.ReadCloser {
    30  	return &resumableRequestReader{client: c, request: r, maxFailures: maxfail, totalSize: totalsize, currentResponse: initialResponse}
    31  }
    32  
    33  func (r *resumableRequestReader) Read(p []byte) (n int, err error) {
    34  	if r.client == nil || r.request == nil {
    35  		return 0, fmt.Errorf("client and request can't be nil\n")
    36  	}
    37  	isFreshRequest := false
    38  	if r.lastRange != 0 && r.currentResponse == nil {
    39  		readRange := fmt.Sprintf("bytes=%d-%d", r.lastRange, r.totalSize)
    40  		r.request.Header.Set("Range", readRange)
    41  		time.Sleep(5 * time.Second)
    42  	}
    43  	if r.currentResponse == nil {
    44  		r.currentResponse, err = r.client.Do(r.request)
    45  		isFreshRequest = true
    46  	}
    47  	if err != nil && r.failures+1 != r.maxFailures {
    48  		r.cleanUpResponse()
    49  		r.failures++
    50  		time.Sleep(5 * time.Duration(r.failures) * time.Second)
    51  		return 0, nil
    52  	} else if err != nil {
    53  		r.cleanUpResponse()
    54  		return 0, err
    55  	}
    56  	if r.currentResponse.StatusCode == 416 && r.lastRange == r.totalSize && r.currentResponse.ContentLength == 0 {
    57  		r.cleanUpResponse()
    58  		return 0, io.EOF
    59  	} else if r.currentResponse.StatusCode != 206 && r.lastRange != 0 && isFreshRequest {
    60  		r.cleanUpResponse()
    61  		return 0, fmt.Errorf("the server doesn't support byte ranges")
    62  	}
    63  	if r.totalSize == 0 {
    64  		r.totalSize = r.currentResponse.ContentLength
    65  	} else if r.totalSize <= 0 {
    66  		r.cleanUpResponse()
    67  		return 0, fmt.Errorf("failed to auto detect content length")
    68  	}
    69  	n, err = r.currentResponse.Body.Read(p)
    70  	r.lastRange += int64(n)
    71  	if err != nil {
    72  		r.cleanUpResponse()
    73  	}
    74  	if err != nil && err != io.EOF {
    75  		log.Infof("encountered error during pull and clearing it before resume: %s", err)
    76  		err = nil
    77  	}
    78  	return n, err
    79  }
    80  
    81  func (r *resumableRequestReader) Close() error {
    82  	r.cleanUpResponse()
    83  	r.client = nil
    84  	r.request = nil
    85  	return nil
    86  }
    87  
    88  func (r *resumableRequestReader) cleanUpResponse() {
    89  	if r.currentResponse != nil {
    90  		r.currentResponse.Body.Close()
    91  		r.currentResponse = nil
    92  	}
    93  }