blob: 997709c2ece17ee247b22a4e83c802e01ce94a9d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.jclouds.digitalocean.http;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.io.Payloads.newInputStreamPayload;
import static org.jclouds.util.Closeables2.closeQuietly;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URI;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.jclouds.digitalocean.domain.BaseResponse;
import org.jclouds.digitalocean.domain.BaseResponse.Status;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.IOExceptionRetryHandler;
import org.jclouds.http.functions.ParseJson;
import org.jclouds.http.handlers.DelegatingErrorHandler;
import org.jclouds.http.handlers.DelegatingRetryHandler;
import org.jclouds.http.internal.HttpWire;
import org.jclouds.http.internal.JavaUrlHttpCommandExecutorService;
import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.Payload;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.io.ByteStreams;
/**
* Custom implementation of the HTTP driver to read the response body in order to get the real response status.
* <p>
* The DigitalOcean API always return 200 codes even if a request failed due to some internal error, but populates an
* <code>ERROR</code> string in the response payload.
* <p>
* This class will read the body of the response and populate a 500 status code if an error is found.
*/
@Singleton
public class ResponseStatusFromPayloadHttpCommandExecutorService extends JavaUrlHttpCommandExecutorService {
public static final String ACCESS_DENIED = "Access Denied";
public static final String NOT_FOUND = "Not Found";
private final ParseJson<BaseResponse> errorParser;
@Inject
ResponseStatusFromPayloadHttpCommandExecutorService(HttpUtils utils, ContentMetadataCodec contentMetadataCodec,
DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler,
DelegatingErrorHandler errorHandler, HttpWire wire, @Named("untrusted") HostnameVerifier verifier,
@Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider, Function<URI, Proxy> proxyForURI,
ParseJson<BaseResponse> errorParser) throws SecurityException, NoSuchFieldException {
super(utils, contentMetadataCodec, retryHandler, ioRetryHandler, errorHandler, wire, verifier,
untrustedSSLContextProvider, proxyForURI);
this.errorParser = checkNotNull(errorParser, "errorParser cannot be null");
}
@Override
protected HttpResponse invoke(HttpURLConnection connection) throws IOException, InterruptedException {
HttpResponse original = super.invoke(connection);
HttpResponse.Builder<?> response = original.toBuilder();
if (hasPayload(original)) {
// As we need to read the response body to determine if there are errors, but we may need to process the body
// again later in the response parsers if everything is OK, we buffer the body into an InputStream we can reset
InputStream in = null;
InputStream originalInputStream = original.getPayload().openStream();
if (originalInputStream instanceof ByteArrayInputStream) {
in = originalInputStream;
} else {
try {
in = new ByteArrayInputStream(ByteStreams.toByteArray(originalInputStream));
} finally {
closeQuietly(originalInputStream);
}
}
// Process the payload and look for errors
BaseResponse responseContent = errorParser.apply(in);
if (responseContent != null && responseContent.getStatus() == Status.ERROR) {
// Yes, this is ugly, but the DigitalOcean API sometimes sets the status code to 200 for these errors and
// the only way to know what happened is parsing the error message
String message = responseContent.getMessage();
if (ACCESS_DENIED.equals(message)) {
response.statusCode(401);
} else if (NOT_FOUND.equals(message)) {
response.statusCode(404);
} else {
response.statusCode(500);
}
response.message(responseContent.getDetails());
}
// Reset the input stream and set the payload, so it can be read again
// by the response and error parsers
in.reset();
Payload payload = newInputStreamPayload(in);
contentMetadataCodec.fromHeaders(payload.getContentMetadata(), original.getHeaders());
response.payload(payload);
}
return response.build();
}
private static boolean hasPayload(final HttpResponse response) {
return response.getPayload() != null && response.getPayload().getRawContent() != null;
}
}