Fix resumable upload protocol. Reviewed in http://codereview.appspot.com/5572075/
diff --git a/apiclient/discovery.py b/apiclient/discovery.py
index a2c81c6..a0ca930 100644
--- a/apiclient/discovery.py
+++ b/apiclient/discovery.py
@@ -461,7 +461,7 @@
multipart_boundary = ''
if media_filename:
- # Convert a simple filename into a MediaUpload object.
+ # Ensure we end up with a valid MediaUpload object.
if isinstance(media_filename, basestring):
(media_mime_type, encoding) = mimetypes.guess_type(media_filename)
if media_mime_type is None:
@@ -474,9 +474,6 @@
else:
raise TypeError('media_filename must be str or MediaUpload.')
- if media_upload.resumable():
- resumable = media_upload
-
# Check the maxSize
if maxSize > 0 and media_upload.size() > maxSize:
raise MediaUploadSizeError("Media larger than: %s" % maxSize)
@@ -488,69 +485,39 @@
expanded_url = uritemplate.expand(mediaPathUrl, params)
url = urlparse.urljoin(self._baseUrl, expanded_url + query)
- if body is None:
- # This is a simple media upload
- headers['content-type'] = media_upload.mimetype()
- expanded_url = uritemplate.expand(mediaResumablePathUrl, params)
- if not media_upload.resumable():
- body = media_upload.getbytes(0, media_upload.size())
+ if media_upload.resumable():
+ # This is all we need to do for resumable, if the body exists it gets
+ # sent in the first request, otherwise an empty body is sent.
+ resumable = media_upload
else:
- # This is a multipart/related upload.
- msgRoot = MIMEMultipart('related')
- # msgRoot should not write out it's own headers
- setattr(msgRoot, '_write_headers', lambda self: None)
-
- # attach the body as one part
- msg = MIMENonMultipart(*headers['content-type'].split('/'))
- msg.set_payload(body)
- msgRoot.attach(msg)
-
- # attach the media as the second part
- msg = MIMENonMultipart(*media_upload.mimetype().split('/'))
- msg['Content-Transfer-Encoding'] = 'binary'
-
- if media_upload.resumable():
- # This is a multipart resumable upload, where a multipart payload
- # looks like this:
- #
- # --===============1678050750164843052==
- # Content-Type: application/json
- # MIME-Version: 1.0
- #
- # {'foo': 'bar'}
- # --===============1678050750164843052==
- # Content-Type: image/png
- # MIME-Version: 1.0
- # Content-Transfer-Encoding: binary
- #
- # <BINARY STUFF>
- # --===============1678050750164843052==--
- #
- # In the case of resumable multipart media uploads, the <BINARY
- # STUFF> is large and will be spread across multiple PUTs. What we
- # do here is compose the multipart message with a random payload in
- # place of <BINARY STUFF> and then split the resulting content into
- # two pieces, text before <BINARY STUFF> and text after <BINARY
- # STUFF>. The text after <BINARY STUFF> is the multipart boundary.
- # In apiclient.http the HttpRequest will send the text before
- # <BINARY STUFF>, then send the actual binary media in chunks, and
- # then will send the multipart delimeter.
-
- payload = hex(random.getrandbits(300))
- msg.set_payload(payload)
- msgRoot.attach(msg)
- body = msgRoot.as_string()
- body, _ = body.split(payload)
- resumable = media_upload
+ # A non-resumable upload
+ if body is None:
+ # This is a simple media upload
+ headers['content-type'] = media_upload.mimetype()
+ body = media_upload.getbytes(0, media_upload.size())
else:
+ # This is a multipart/related upload.
+ msgRoot = MIMEMultipart('related')
+ # msgRoot should not write out it's own headers
+ setattr(msgRoot, '_write_headers', lambda self: None)
+
+ # attach the body as one part
+ msg = MIMENonMultipart(*headers['content-type'].split('/'))
+ msg.set_payload(body)
+ msgRoot.attach(msg)
+
+ # attach the media as the second part
+ msg = MIMENonMultipart(*media_upload.mimetype().split('/'))
+ msg['Content-Transfer-Encoding'] = 'binary'
+
payload = media_upload.getbytes(0, media_upload.size())
msg.set_payload(payload)
msgRoot.attach(msg)
body = msgRoot.as_string()
- multipart_boundary = msgRoot.get_boundary()
- headers['content-type'] = ('multipart/related; '
- 'boundary="%s"') % multipart_boundary
+ multipart_boundary = msgRoot.get_boundary()
+ headers['content-type'] = ('multipart/related; '
+ 'boundary="%s"') % multipart_boundary
logging.info('URL being requested: %s' % url)
return self._requestBuilder(self._http,