Signed-off-by: Olivier Gambier <olivier@docker.com>
This commit is contained in:
Olivier Gambier 2016-03-22 10:42:33 -07:00
parent 59401e277b
commit d1444b56e9
141 changed files with 19483 additions and 4205 deletions

View file

@ -9,21 +9,13 @@ package googleapi
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"regexp"
"strconv"
"strings"
"sync"
"time"
"golang.org/x/net/context"
"google.golang.org/api/googleapi/internal/uritemplates"
)
@ -42,17 +34,28 @@ type SizeReaderAt interface {
Size() int64
}
// ServerResponse is embedded in each Do response and
// provides the HTTP status code and header sent by the server.
type ServerResponse struct {
// HTTPStatusCode is the server's response status code.
// When using a resource method's Do call, this will always be in the 2xx range.
HTTPStatusCode int
// Header contains the response header fields from the server.
Header http.Header
}
const (
Version = "0.5"
// statusResumeIncomplete is the code returned by the Google uploader when the transfer is not yet complete.
statusResumeIncomplete = 308
// UserAgent is the header string used to identify this package.
UserAgent = "google-api-go-client/" + Version
// uploadPause determines the delay between failed upload attempts
uploadPause = 1 * time.Second
// The default chunk size to use for resumable uplods if not specified by the user.
DefaultUploadChunkSize = 8 * 1024 * 1024
// The minimum chunk size that can be used for resumable uploads. All
// user-specified chunk sizes must be multiple of this value.
MinUploadChunkSize = 256 * 1024
)
// Error contains an error response from the server.
@ -65,6 +68,8 @@ type Error struct {
// Body is the raw response returned by the server.
// It is often but not always JSON, depending on how the request fails.
Body string
// Header contains the response header fields from the server.
Header http.Header
Errors []ErrorItem
}
@ -122,6 +127,34 @@ func CheckResponse(res *http.Response) error {
return jerr.Error
}
}
return &Error{
Code: res.StatusCode,
Body: string(slurp),
Header: res.Header,
}
}
// IsNotModified reports whether err is the result of the
// server replying with http.StatusNotModified.
// Such error values are sometimes returned by "Do" methods
// on calls when If-None-Match is used.
func IsNotModified(err error) bool {
if err == nil {
return false
}
ae, ok := err.(*Error)
return ok && ae.Code == http.StatusNotModified
}
// CheckMediaResponse returns an error (of type *Error) if the response
// status code is not 2xx. Unlike CheckResponse it does not assume the
// body is a JSON error document.
func CheckMediaResponse(res *http.Response) error {
if res.StatusCode >= 200 && res.StatusCode <= 299 {
return nil
}
slurp, _ := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
res.Body.Close()
return &Error{
Code: res.StatusCode,
Body: string(slurp),
@ -148,52 +181,6 @@ func (wrap MarshalStyle) JSONReader(v interface{}) (io.Reader, error) {
return buf, nil
}
func getMediaType(media io.Reader) (io.Reader, string) {
if typer, ok := media.(ContentTyper); ok {
return media, typer.ContentType()
}
pr, pw := io.Pipe()
typ := "application/octet-stream"
buf, err := ioutil.ReadAll(io.LimitReader(media, 512))
if err != nil {
pw.CloseWithError(fmt.Errorf("error reading media: %v", err))
return pr, typ
}
typ = http.DetectContentType(buf)
mr := io.MultiReader(bytes.NewReader(buf), media)
go func() {
_, err = io.Copy(pw, mr)
if err != nil {
pw.CloseWithError(fmt.Errorf("error reading media: %v", err))
return
}
pw.Close()
}()
return pr, typ
}
// DetectMediaType detects and returns the content type of the provided media.
// If the type can not be determined, "application/octet-stream" is returned.
func DetectMediaType(media io.ReaderAt) string {
if typer, ok := media.(ContentTyper); ok {
return typer.ContentType()
}
typ := "application/octet-stream"
buf := make([]byte, 1024)
n, err := media.ReadAt(buf, 0)
buf = buf[:n]
if err == nil || err == io.EOF {
typ = http.DetectContentType(buf)
}
return typ
}
type Lengther interface {
Len() int
}
// endingWithErrorReader from r until it returns an error. If the
// final error from r is io.EOF and e is non-nil, e is used instead.
type endingWithErrorReader struct {
@ -209,12 +196,6 @@ func (er endingWithErrorReader) Read(p []byte) (n int, err error) {
return
}
func typeHeader(contentType string) textproto.MIMEHeader {
h := make(textproto.MIMEHeader)
h.Set("Content-Type", contentType)
return h
}
// countingWriter counts the number of bytes it receives to write, but
// discards them.
type countingWriter struct {
@ -226,203 +207,65 @@ func (w countingWriter) Write(p []byte) (int, error) {
return len(p), nil
}
// ConditionallyIncludeMedia does nothing if media is nil.
//
// bodyp is an in/out parameter. It should initially point to the
// reader of the application/json (or whatever) payload to send in the
// API request. It's updated to point to the multipart body reader.
//
// ctypep is an in/out parameter. It should initially point to the
// content type of the bodyp, usually "application/json". It's updated
// to the "multipart/related" content type, with random boundary.
//
// The return value is the content-length of the entire multpart body.
func ConditionallyIncludeMedia(media io.Reader, bodyp *io.Reader, ctypep *string) (cancel func(), ok bool) {
if media == nil {
return
}
// Get the media type, which might return a different reader instance.
var mediaType string
media, mediaType = getMediaType(media)
body, bodyType := *bodyp, *ctypep
pr, pw := io.Pipe()
mpw := multipart.NewWriter(pw)
*bodyp = pr
*ctypep = "multipart/related; boundary=" + mpw.Boundary()
go func() {
w, err := mpw.CreatePart(typeHeader(bodyType))
if err != nil {
mpw.Close()
pw.CloseWithError(fmt.Errorf("googleapi: body CreatePart failed: %v", err))
return
}
_, err = io.Copy(w, body)
if err != nil {
mpw.Close()
pw.CloseWithError(fmt.Errorf("googleapi: body Copy failed: %v", err))
return
}
w, err = mpw.CreatePart(typeHeader(mediaType))
if err != nil {
mpw.Close()
pw.CloseWithError(fmt.Errorf("googleapi: media CreatePart failed: %v", err))
return
}
_, err = io.Copy(w, media)
if err != nil {
mpw.Close()
pw.CloseWithError(fmt.Errorf("googleapi: media Copy failed: %v", err))
return
}
mpw.Close()
pw.Close()
}()
cancel = func() { pw.CloseWithError(errAborted) }
return cancel, true
}
var errAborted = errors.New("googleapi: upload aborted")
// ProgressUpdater is a function that is called upon every progress update of a resumable upload.
// This is the only part of a resumable upload (from googleapi) that is usable by the developer.
// The remaining usable pieces of resumable uploads is exposed in each auto-generated API.
type ProgressUpdater func(current, total int64)
// ResumableUpload is used by the generated APIs to provide resumable uploads.
type MediaOption interface {
setOptions(o *MediaOptions)
}
type contentTypeOption string
func (ct contentTypeOption) setOptions(o *MediaOptions) {
o.ContentType = string(ct)
if o.ContentType == "" {
o.ForceEmptyContentType = true
}
}
// ContentType returns a MediaOption which sets the Content-Type header for media uploads.
// If ctype is empty, the Content-Type header will be omitted.
func ContentType(ctype string) MediaOption {
return contentTypeOption(ctype)
}
type chunkSizeOption int
func (cs chunkSizeOption) setOptions(o *MediaOptions) {
size := int(cs)
if size%MinUploadChunkSize != 0 {
size += MinUploadChunkSize - (size % MinUploadChunkSize)
}
o.ChunkSize = size
}
// ChunkSize returns a MediaOption which sets the chunk size for media uploads.
// size will be rounded up to the nearest multiple of 256K.
// Media which contains fewer than size bytes will be uploaded in a single request.
// Media which contains size bytes or more will be uploaded in separate chunks.
// If size is zero, media will be uploaded in a single request.
func ChunkSize(size int) MediaOption {
return chunkSizeOption(size)
}
// MediaOptions stores options for customizing media upload. It is not used by developers directly.
type MediaOptions struct {
ContentType string
ForceEmptyContentType bool
ChunkSize int
}
// ProcessMediaOptions stores options from opts in a MediaOptions.
// It is not used by developers directly.
type ResumableUpload struct {
Client *http.Client
// URI is the resumable resource destination provided by the server after specifying "&uploadType=resumable".
URI string
UserAgent string // User-Agent for header of the request
// Media is the object being uploaded.
Media io.ReaderAt
// MediaType defines the media type, e.g. "image/jpeg".
MediaType string
// ContentLength is the full size of the object being uploaded.
ContentLength int64
mu sync.Mutex // guards progress
progress int64 // number of bytes uploaded so far
started bool // whether the upload has been started
// Callback is an optional function that will be called upon every progress update.
Callback ProgressUpdater
}
var (
// rangeRE matches the transfer status response from the server. $1 is the last byte index uploaded.
rangeRE = regexp.MustCompile(`^bytes=0\-(\d+)$`)
// chunkSize is the size of the chunks created during a resumable upload and should be a power of two.
// 1<<18 is the minimum size supported by the Google uploader, and there is no maximum.
chunkSize int64 = 1 << 18
)
// Progress returns the number of bytes uploaded at this point.
func (rx *ResumableUpload) Progress() int64 {
rx.mu.Lock()
defer rx.mu.Unlock()
return rx.progress
}
func (rx *ResumableUpload) transferStatus() (int64, *http.Response, error) {
req, _ := http.NewRequest("POST", rx.URI, nil)
req.ContentLength = 0
req.Header.Set("User-Agent", rx.UserAgent)
req.Header.Set("Content-Range", fmt.Sprintf("bytes */%v", rx.ContentLength))
res, err := rx.Client.Do(req)
if err != nil || res.StatusCode != statusResumeIncomplete {
return 0, res, err
func ProcessMediaOptions(opts []MediaOption) *MediaOptions {
mo := &MediaOptions{ChunkSize: DefaultUploadChunkSize}
for _, o := range opts {
o.setOptions(mo)
}
var start int64
if m := rangeRE.FindStringSubmatch(res.Header.Get("Range")); len(m) == 2 {
start, err = strconv.ParseInt(m[1], 10, 64)
if err != nil {
return 0, nil, fmt.Errorf("unable to parse range size %v", m[1])
}
start += 1 // Start at the next byte
}
return start, res, nil
}
type chunk struct {
body io.Reader
size int64
err error
}
func (rx *ResumableUpload) transferChunks(ctx context.Context) (*http.Response, error) {
var start int64
var err error
res := &http.Response{}
if rx.started {
start, res, err = rx.transferStatus()
if err != nil || res.StatusCode != statusResumeIncomplete {
return res, err
}
}
rx.started = true
for {
select { // Check for cancellation
case <-ctx.Done():
res.StatusCode = http.StatusRequestTimeout
return res, ctx.Err()
default:
}
reqSize := rx.ContentLength - start
if reqSize > chunkSize {
reqSize = chunkSize
}
r := io.NewSectionReader(rx.Media, start, reqSize)
req, _ := http.NewRequest("POST", rx.URI, r)
req.ContentLength = reqSize
req.Header.Set("Content-Range", fmt.Sprintf("bytes %v-%v/%v", start, start+reqSize-1, rx.ContentLength))
req.Header.Set("Content-Type", rx.MediaType)
req.Header.Set("User-Agent", rx.UserAgent)
res, err = rx.Client.Do(req)
start += reqSize
if err == nil && (res.StatusCode == statusResumeIncomplete || res.StatusCode == http.StatusOK) {
rx.mu.Lock()
rx.progress = start // keep track of number of bytes sent so far
rx.mu.Unlock()
if rx.Callback != nil {
rx.Callback(start, rx.ContentLength)
}
}
if err != nil || res.StatusCode != statusResumeIncomplete {
break
}
}
return res, err
}
var sleep = time.Sleep // override in unit tests
// Upload starts the process of a resumable upload with a cancellable context.
// It retries indefinitely (with a pause of uploadPause between attempts) until cancelled.
// It is called from the auto-generated API code and is not visible to the user.
// rx is private to the auto-generated API code.
func (rx *ResumableUpload) Upload(ctx context.Context) (*http.Response, error) {
var res *http.Response
var err error
for {
res, err = rx.transferChunks(ctx)
if err != nil || res.StatusCode == http.StatusCreated || res.StatusCode == http.StatusOK {
return res, err
}
select { // Check for cancellation
case <-ctx.Done():
res.StatusCode = http.StatusRequestTimeout
return res, ctx.Err()
default:
}
sleep(uploadPause)
}
return res, err
return mo
}
func ResolveRelative(basestr, relstr string) string {
@ -549,3 +392,33 @@ func CombineFields(s []Field) string {
}
return strings.Join(r, ",")
}
// A CallOption is an optional argument to an API call.
// It should be treated as an opaque value by users of Google APIs.
//
// A CallOption is something that configures an API call in a way that is
// not specific to that API; for instance, controlling the quota user for
// an API call is common across many APIs, and is thus a CallOption.
type CallOption interface {
Get() (key, value string)
}
// QuotaUser returns a CallOption that will set the quota user for a call.
// The quota user can be used by server-side applications to control accounting.
// It can be an arbitrary string up to 40 characters, and will override UserIP
// if both are provided.
func QuotaUser(u string) CallOption { return quotaUser(u) }
type quotaUser string
func (q quotaUser) Get() (string, string) { return "quotaUser", string(q) }
// UserIP returns a CallOption that will set the "userIp" parameter of a call.
// This should be the IP address of the originating request.
func UserIP(ip string) CallOption { return userIP(ip) }
type userIP string
func (i userIP) Get() (string, string) { return "userIp", string(i) }
// TODO: Fields too