from botocore.handlers import ( ETree, XMLParseError, _get_cross_region_presigned_url, _get_presigned_url_source_and_destination_regions, logger, ) async def check_for_200_error(response, **kwargs): # From: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html # There are two opportunities for a copy request to return an error. One # can occur when Amazon S3 receives the copy request and the other can # occur while Amazon S3 is copying the files. If the error occurs before # the copy operation starts, you receive a standard Amazon S3 error. If the # error occurs during the copy operation, the error response is embedded in # the 200 OK response. This means that a 200 OK response can contain either # a success or an error. Make sure to design your application to parse the # contents of the response and handle it appropriately. # # So this handler checks for this case. Even though the server sends a # 200 response, conceptually this should be handled exactly like a # 500 response (with respect to raising exceptions, retries, etc.) # We're connected *before* all the other retry logic handlers, so as long # as we switch the error code to 500, we'll retry the error as expected. if response is None: # A None response can happen if an exception is raised while # trying to retrieve the response. See Endpoint._get_response(). return http_response, parsed = response if await _looks_like_special_case_error(http_response): logger.debug( "Error found for response with 200 status code, " "errors: %s, changing status code to " "500.", parsed, ) http_response.status_code = 500 async def _looks_like_special_case_error(http_response): if http_response.status_code == 200: try: parser = ETree.XMLParser( target=ETree.TreeBuilder(), encoding='utf-8' ) parser.feed(await http_response.content) root = parser.close() except XMLParseError: # In cases of network disruptions, we may end up with a partial # streamed response from S3. We need to treat these cases as # 500 Service Errors and try again. return True if root.tag == 'Error': return True return False async def inject_presigned_url_ec2(params, request_signer, model, **kwargs): # The customer can still provide this, so we should pass if they do. if 'PresignedUrl' in params['body']: return src, dest = _get_presigned_url_source_and_destination_regions( request_signer, params['body'] ) url = await _get_cross_region_presigned_url( request_signer, params, model, src, dest ) params['body']['PresignedUrl'] = url # EC2 Requires that the destination region be sent over the wire in # addition to the source region. params['body']['DestinationRegion'] = dest async def inject_presigned_url_rds(params, request_signer, model, **kwargs): # SourceRegion is not required for RDS operations, so it's possible that # it isn't set. In that case it's probably a local copy so we don't need # to do anything else. if 'SourceRegion' not in params['body']: return src, dest = _get_presigned_url_source_and_destination_regions( request_signer, params['body'] ) # Since SourceRegion isn't actually modeled for RDS, it needs to be # removed from the request params before we send the actual request. del params['body']['SourceRegion'] if 'PreSignedUrl' in params['body']: return url = await _get_cross_region_presigned_url( request_signer, params, model, src, dest ) params['body']['PreSignedUrl'] = url async def parse_get_bucket_location(parsed, http_response, **kwargs): # s3.GetBucketLocation cannot be modeled properly. To # account for this we just manually parse the XML document. # The "parsed" passed in only has the ResponseMetadata # filled out. This handler will fill in the LocationConstraint # value. if http_response.raw is None: return response_body = await http_response.content parser = ETree.XMLParser(target=ETree.TreeBuilder(), encoding='utf-8') parser.feed(response_body) root = parser.close() region = root.text parsed['LocationConstraint'] = region