| /* |
| * 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; |
| } |
| } |