All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.services.s3.internal.S3ErrorResponseHandler Maven / Gradle / Ivy

/*
 * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
package com.amazonaws.services.s3.internal;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.http.HttpMethodName;
import com.amazonaws.http.HttpResponse;
import com.amazonaws.http.HttpResponseHandler;
import com.amazonaws.services.s3.Headers;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.amazonaws.util.IOUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import static com.amazonaws.util.StringUtils.UTF8;

/**
 * Response handler for S3 error responses. S3 error responses are different
 * from other AWS error responses in a few ways. Most error responses will
 * contain an XML body, but not all (ex: error responses to HEAD requests will
 * not), so this error handler has to account for that. The actual XML error
 * response body is slightly different than other services like SimpleDB or EC2
 * and some information isn't explicitly represented in the XML error response
 * body (ex: error type/fault information) so it has to be inferred from other
 * parts of the error response.
 */
public class S3ErrorResponseHandler implements
        HttpResponseHandler {
    /** Shared logger for profiling information */
    private static final Log log = LogFactory
            .getLog(S3ErrorResponseHandler.class);

    /** Shared factory for creating XML event readers */
    private static final XMLInputFactory xmlInputFactory = XMLInputFactory
            .newInstance();

    private static enum S3ErrorTags {
        Error, Message, Code, RequestId, HostId
    };

    @Override
    public AmazonServiceException handle(HttpResponse httpResponse)
            throws XMLStreamException {
        final AmazonServiceException exception = createException(httpResponse);
        exception.setHttpHeaders(httpResponse.getHeaders());
        return exception;
    }

    private AmazonServiceException createException(HttpResponse httpResponse) throws
                                                                              XMLStreamException {
        final InputStream is = httpResponse.getContent();
        String xmlContent = null;
        /*
         * We don't always get an error response body back from S3. When we send
         * a HEAD request, we don't receive a body, so we'll have to just return
         * what we can.
         */
        if (is == null
                || httpResponse.getRequest().getHttpMethod() == HttpMethodName.HEAD) {
            return createExceptionFromHeaders(httpResponse, null);
        }

        String content = null;
        try {
            content = IOUtils.toString(is);
        } catch (IOException ioe) {
            if (log.isDebugEnabled())
                log.debug("Failed in parsing the error response : ", ioe);
            return createExceptionFromHeaders(httpResponse, null);
        }

        /*
         * XMLInputFactory is not thread safe and hence it is synchronized.
         * Reference :
         * http://itdoc.hitachi.co.jp/manuals/3020/30203Y2210e/EY220140.HTM
         */
        XMLStreamReader reader;
        synchronized (xmlInputFactory) {
            reader = xmlInputFactory
                    .createXMLStreamReader(new ByteArrayInputStream(content
                            .getBytes(UTF8)));
        }

        try {
            /*
             * target depth is to determine if the XML Error response from the
             * server has any element inside  tag have child tags.
             * Parsing such tags is not supported now. target depth is
             * incremented for every start tag and decremented after every end
             * tag is encountered.
             */
            int targetDepth = 0;
            final AmazonS3ExceptionBuilder exceptionBuilder = new AmazonS3ExceptionBuilder();
            exceptionBuilder.setErrorResponseXml(content);
            exceptionBuilder.setStatusCode(httpResponse.getStatusCode());
            exceptionBuilder.setCloudFrontId(httpResponse.getHeaders().get(Headers.CLOUD_FRONT_ID));

            boolean hasErrorTagVisited = false;
            while (reader.hasNext()) {
                int event = reader.next();

                switch (event) {
                case XMLStreamConstants.START_ELEMENT:
                    targetDepth++;
                    String tagName = reader.getLocalName();
                    if (targetDepth == 1
                            && !S3ErrorTags.Error.toString().equals(tagName))
                        return createExceptionFromHeaders(httpResponse,
                                "Unable to parse error response. Error XML Not in proper format."
                                        + content);
                    if (S3ErrorTags.Error.toString().equals(tagName)) {
                        hasErrorTagVisited = true;
                    }
                    continue;
                case XMLStreamConstants.CHARACTERS:
                    xmlContent = reader.getText();
                    if (xmlContent != null)
                        xmlContent = xmlContent.trim();
                    continue;
                case XMLStreamConstants.END_ELEMENT:
                    tagName = reader.getLocalName();
                    targetDepth--;
                    if (!(hasErrorTagVisited) || targetDepth > 1) {
                        return createExceptionFromHeaders(httpResponse,
                                "Unable to parse error response. Error XML Not in proper format."
                                        + content);
                    }
                    if (S3ErrorTags.Message.toString().equals(tagName)) {
                        exceptionBuilder.setErrorMessage(xmlContent);
                    } else if (S3ErrorTags.Code.toString().equals(tagName)) {
                        exceptionBuilder.setErrorCode(xmlContent);
                    } else if (S3ErrorTags.RequestId.toString().equals(tagName)) {
                        exceptionBuilder.setRequestId(xmlContent);
                    } else if (S3ErrorTags.HostId.toString().equals(tagName)) {
                        exceptionBuilder.setExtendedRequestId(xmlContent);
                    } else {
                        exceptionBuilder.addAdditionalDetail(tagName, xmlContent);
                    }
                    continue;
                case XMLStreamConstants.END_DOCUMENT:
                    return exceptionBuilder.build();
                }
            }
        } catch (Exception e) {
            if (log.isDebugEnabled())
                log.debug("Failed in parsing the error response : " + content,
                        e);
        }
        return createExceptionFromHeaders(httpResponse, content);
    }

    private AmazonS3Exception createExceptionFromHeaders(
            HttpResponse errorResponse, String errorResponseXml) {
        final Map headers = errorResponse.getHeaders();
        final int statusCode = errorResponse.getStatusCode();
        final AmazonS3ExceptionBuilder exceptionBuilder = new AmazonS3ExceptionBuilder();
        exceptionBuilder.setErrorMessage(errorResponse.getStatusText());
        exceptionBuilder.setErrorResponseXml(errorResponseXml);
        exceptionBuilder.setStatusCode(statusCode);
        exceptionBuilder
                .setExtendedRequestId(headers.get(Headers.EXTENDED_REQUEST_ID));
        exceptionBuilder.setRequestId(headers.get(Headers.REQUEST_ID));
        exceptionBuilder.setCloudFrontId(headers.get(Headers.CLOUD_FRONT_ID));
        exceptionBuilder
                .setErrorCode(statusCode + " " + errorResponse.getStatusText());
        exceptionBuilder.addAdditionalDetail(Headers.S3_BUCKET_REGION,
                errorResponse.getHeaders().get(Headers.S3_BUCKET_REGION));
        return exceptionBuilder.build();
    }

    /**
     * Since this response handler completely consumes all the data from the
     * underlying HTTP connection during the handle method, we don't need to
     * keep the HTTP connection open.
     *
     * @see com.amazonaws.http.HttpResponseHandler#needsConnectionLeftOpen()
     */
    public boolean needsConnectionLeftOpen() {
        return false;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy