def receive_upload_chunk(self, gcs_url, request): """Receive a new upload chunk. :param gcs_url: str the base URL for the service. :param request: flask.Request the original http request. :return: the HTTP response. """ upload_id = request.args.get('upload_id') if upload_id is None: raise error_response.ErrorResponse( 'Missing upload_id in resumable_upload_chunk', status_code=400) upload = self.resumable_uploads.get(upload_id) if upload is None: raise error_response.ErrorResponse( 'Cannot find resumable upload %s' % upload_id, status_code=404) # Be gracious in what you accept, if the Content-Range header is not # set we assume it is a good header and it is the end of the file. next_byte = upload['next_byte'] begin = next_byte end = next_byte + len(request.data) total = end final_chunk = False content_range = request.headers.get('content-range') if content_range is not None: if content_range.startswith('bytes */*'): # This is just a query to resume an upload, if it is done, return # an empty response = flask.make_response('') if next_byte > 1: response.headers['Range'] = 'bytes=0-%d' % (next_byte - 1) response.status_code = 308 return response match = re.match('bytes \*/(\\*|[0-9]+)', content_range) if match: if match.group(1) == '*': total = 0 else: total = int(match.group(1)) final_chunk = True else: match = re.match('bytes ([0-9]+)-([0-9]+)/(\\*|[0-9]+)', content_range) if not match: raise error_response.ErrorResponse( 'Invalid Content-Range in upload %s' % content_range, status_code=400) begin = int(match.group(1)) end = int(match.group(2)) if match.group(3) == '*': total = 0 else: total = int(match.group(3)) final_chunk = True if begin != next_byte: raise error_response.ErrorResponse( 'Mismatched data range, expected data at %d, got %d' % ( next_byte, begin), status_code=400) if len(request.data) != end - begin + 1: raise error_response.ErrorResponse( 'Mismatched data range (%d) vs. content-length (%d)' % ( end - begin + 1, len(request.data)), status_code=400) upload['media'] = upload.get('media', '') + request.data next_byte = len(upload.get('media', '')) upload['next_byte'] = next_byte response_payload = '' if final_chunk and next_byte >= total: upload['done'] = True object_name = upload.get('object_name') object_path, blob = testbench_utils.get_object( self.name, object_name, gcs_object.GcsObject(self.name, object_name)) # Release a few resources to control memory usage. original_metadata = upload.pop('metadata', None) media = upload.pop('media', None) blob.check_preconditions_by_value( upload.get('ifGenerationMatch'), upload.get('ifGenerationNotMatch'), upload.get('ifMetagenerationMatch'), upload.get('ifMetagenerationNotMatch') ) if upload.pop('instructions', None) == 'inject-upload-data-error': media = testbench_utils.corrupt_media(media) revision = blob.insert_resumable( gcs_url, request, media, original_metadata) response_payload = testbench_utils.filter_fields_from_response( upload.get('fields'), revision.metadata) testbench_utils.insert_object(object_path, blob) response = flask.make_response(response_payload) if next_byte == 0: response.headers['Range'] = 'bytes=0-0' else: response.headers['Range'] = 'bytes=0-%d' % (next_byte - 1) if upload.get('done', False): response.status_code = 200 else: response.status_code = 308 return response
def receive_upload_chunk(self, gcs_url, request): """Receive a new upload chunk. :param gcs_url: str the base URL for the service. :param request: flask.Request the original http request. :return: the HTTP response. """ upload_id = request.args.get("upload_id") if upload_id is None: raise error_response.ErrorResponse( "Missing upload_id in resumable_upload_chunk", status_code=400) upload = self.resumable_uploads.get(upload_id) if upload is None: raise error_response.ErrorResponse( "Cannot find resumable upload %s" % upload_id, status_code=404) # Be gracious in what you accept, if the Content-Range header is not # set we assume it is a good header and it is the end of the file. next_byte = upload["next_byte"] begin = next_byte end = next_byte + len(request.data) total = end final_chunk = False content_range = request.headers.get("content-range") if content_range is not None: if content_range.startswith("bytes */*"): # This is just a query to resume an upload, if it is done, return # the completed upload payload and an empty range header. response = flask.make_response(upload.get("payload", "")) if next_byte > 1 and not upload["done"]: response.headers["Range"] = "bytes=0-%d" % (next_byte - 1) response.status_code = 200 if upload["done"] else 308 return response match = re.match("bytes \*/(\\*|[0-9]+)", content_range) if match: if match.group(1) == "*": total = 0 else: total = int(match.group(1)) final_chunk = True else: match = re.match("bytes ([0-9]+)-([0-9]+)/(\\*|[0-9]+)", content_range) if not match: raise error_response.ErrorResponse( "Invalid Content-Range in upload %s" % content_range, status_code=400, ) begin = int(match.group(1)) end = int(match.group(2)) if match.group(3) == "*": total = 0 else: total = int(match.group(3)) final_chunk = True if begin != next_byte: raise error_response.ErrorResponse( "Mismatched data range, expected data at %d, got %d" % (next_byte, begin), status_code=400, ) if len(request.data) != end - begin + 1: raise error_response.ErrorResponse( "Mismatched data range (%d) vs. content-length (%d)" % (end - begin + 1, len(request.data)), status_code=400, ) upload["media"] = upload.get("media", b"") + request.data next_byte = len(upload.get("media", "")) upload["next_byte"] = next_byte response_payload = "" if final_chunk and next_byte >= total: upload["done"] = True object_name = upload.get("object_name") object_path, blob = testbench_utils.get_object( self.name, object_name, gcs_object.GcsObject(self.name, object_name)) # Release a few resources to control memory usage. original_metadata = upload.pop("metadata", None) media = upload.pop("media", None) blob.check_preconditions_by_value( upload.get("ifGenerationMatch"), upload.get("ifGenerationNotMatch"), upload.get("ifMetagenerationMatch"), upload.get("ifMetagenerationNotMatch"), ) if upload.pop("instructions", None) == "inject-upload-data-error": media = testbench_utils.corrupt_media(media) revision = blob.insert_resumable(gcs_url, request, media, original_metadata) response_payload = testbench_utils.filter_fields_from_response( upload.get("fields"), revision.metadata) upload["payload"] = response_payload testbench_utils.insert_object(object_path, blob) response = flask.make_response(response_payload) if next_byte == 0: response.headers["Range"] = "bytes=0-0" else: response.headers["Range"] = "bytes=0-%d" % (next_byte - 1) if upload.get("done", False): response.status_code = 200 else: response.status_code = 308 return response