| /* |
| * 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.http; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Predicates.and; |
| import static com.google.common.base.Predicates.equalTo; |
| import static com.google.common.base.Predicates.in; |
| import static com.google.common.base.Predicates.not; |
| import static com.google.common.base.Predicates.notNull; |
| import static com.google.common.base.Throwables.getCausalChain; |
| import static com.google.common.base.Throwables.propagate; |
| import static com.google.common.collect.Iterables.filter; |
| import static com.google.common.collect.Iterables.get; |
| import static com.google.common.collect.Iterables.size; |
| import static com.google.common.collect.Multimaps.filterKeys; |
| import static com.google.common.io.BaseEncoding.base64; |
| import static com.google.common.io.ByteStreams.toByteArray; |
| import static com.google.common.net.HttpHeaders.CACHE_CONTROL; |
| import static com.google.common.net.HttpHeaders.CONTENT_DISPOSITION; |
| import static com.google.common.net.HttpHeaders.CONTENT_ENCODING; |
| import static com.google.common.net.HttpHeaders.CONTENT_LANGUAGE; |
| import static com.google.common.net.HttpHeaders.CONTENT_LENGTH; |
| import static com.google.common.net.HttpHeaders.CONTENT_MD5; |
| import static com.google.common.net.HttpHeaders.CONTENT_TYPE; |
| import static com.google.common.net.HttpHeaders.EXPIRES; |
| import static org.jclouds.util.Closeables2.closeQuietly; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.annotation.Annotation; |
| import java.util.Collection; |
| import java.util.Map.Entry; |
| |
| import jakarta.inject.Named; |
| import jakarta.inject.Singleton; |
| import jakarta.ws.rs.HttpMethod; |
| |
| import org.jclouds.Constants; |
| import org.jclouds.io.ContentMetadata; |
| import org.jclouds.io.MutableContentMetadata; |
| import org.jclouds.io.Payload; |
| import org.jclouds.io.PayloadEnclosing; |
| import org.jclouds.io.Payloads; |
| import org.jclouds.logging.Logger; |
| import org.jclouds.logging.internal.Wire; |
| |
| import com.google.common.base.Optional; |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.ImmutableMultimap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Multimap; |
| import com.google.common.collect.ImmutableSet.Builder; |
| import com.google.common.reflect.Invokable; |
| import com.google.inject.Inject; |
| |
| @Singleton |
| public class HttpUtils { |
| |
| @Inject(optional = true) |
| @Named(Constants.PROPERTY_RELAX_HOSTNAME) |
| private boolean relaxHostname = false; |
| |
| private final int globalMaxConnections; |
| private final int globalMaxConnectionsPerHost; |
| private final int connectionTimeout; |
| private final int soTimeout; |
| |
| @Inject(optional = true) |
| @Named(Constants.PROPERTY_TRUST_ALL_CERTS) |
| private boolean trustAllCerts; |
| |
| @Inject |
| public HttpUtils(@Named(Constants.PROPERTY_CONNECTION_TIMEOUT) int connectionTimeout, |
| @Named(Constants.PROPERTY_SO_TIMEOUT) int soTimeout, |
| @Named(Constants.PROPERTY_MAX_CONNECTIONS_PER_CONTEXT) int globalMaxConnections, |
| @Named(Constants.PROPERTY_MAX_CONNECTIONS_PER_HOST) int globalMaxConnectionsPerHost) { |
| this.soTimeout = soTimeout; |
| this.connectionTimeout = connectionTimeout; |
| this.globalMaxConnections = globalMaxConnections; |
| this.globalMaxConnectionsPerHost = globalMaxConnectionsPerHost; |
| } |
| |
| public int getSocketOpenTimeout() { |
| return soTimeout; |
| } |
| |
| public int getConnectionTimeout() { |
| return connectionTimeout; |
| } |
| |
| public boolean relaxHostname() { |
| return relaxHostname; |
| } |
| |
| public boolean trustAllCerts() { |
| return trustAllCerts; |
| } |
| |
| public int getMaxConnections() { |
| return globalMaxConnections; |
| } |
| |
| public int getMaxConnectionsPerHost() { |
| return globalMaxConnectionsPerHost; |
| } |
| |
| public static byte[] toByteArrayOrNull(PayloadEnclosing response) { |
| if (response.getPayload() != null) { |
| InputStream input = null; |
| try { |
| input = response.getPayload().openStream(); |
| return toByteArray(input); |
| } catch (IOException e) { |
| propagate(e); |
| } finally { |
| closeQuietly(input); |
| } |
| } |
| return null; |
| } |
| |
| public static Optional<String> tryFindHttpMethod(Invokable<?, ?> method) { |
| Builder<String> methodsBuilder = ImmutableSet.builder(); |
| for (Annotation annotation : method.getAnnotations()) { |
| HttpMethod http = annotation.annotationType().getAnnotation(HttpMethod.class); |
| if (http != null) |
| methodsBuilder.add(http.value()); |
| } |
| Collection<String> methods = methodsBuilder.build(); |
| switch (methods.size()) { |
| case 0: |
| return Optional.absent(); |
| case 1: |
| return Optional.of(get(methods, 0)); |
| default: |
| throw new IllegalStateException("You must specify at most one HttpMethod annotation on: " + method); |
| } |
| } |
| |
| /** |
| * Content stream may need to be read. However, we should always close the http stream. |
| * |
| * @throws IOException |
| */ |
| public static byte[] closeClientButKeepContentStream(PayloadEnclosing response) { |
| byte[] returnVal = toByteArrayOrNull(response); |
| if (returnVal != null && !response.getPayload().isRepeatable()) { |
| Payload newPayload = Payloads.newByteArrayPayload(returnVal); |
| MutableContentMetadata fromMd = response.getPayload().getContentMetadata(); |
| MutableContentMetadata toMd = newPayload.getContentMetadata(); |
| copy(fromMd, toMd); |
| response.setPayload(newPayload); |
| } |
| return returnVal; |
| } |
| |
| public static void copy(ContentMetadata fromMd, MutableContentMetadata toMd) { |
| toMd.setCacheControl(fromMd.getCacheControl()); |
| toMd.setContentLength(fromMd.getContentLength()); |
| toMd.setContentMD5(fromMd.getContentMD5()); |
| toMd.setContentType(fromMd.getContentType()); |
| toMd.setContentDisposition(fromMd.getContentDisposition()); |
| toMd.setContentEncoding(fromMd.getContentEncoding()); |
| toMd.setContentLanguage(fromMd.getContentLanguage()); |
| toMd.setExpires(fromMd.getExpires()); |
| } |
| |
| public void logRequest(Logger logger, HttpRequest request, String prefix) { |
| if (logger.isDebugEnabled()) { |
| logger.debug("%s %s", prefix, request.getRequestLine().toString()); |
| logMessage(logger, request, prefix); |
| } |
| } |
| |
| private void logMessage(Logger logger, HttpMessage message, String prefix) { |
| for (Entry<String, String> header : message.getHeaders().entries()) { |
| if (header.getKey() != null) |
| logger.debug("%s %s: %s", prefix, header.getKey(), header.getValue()); |
| } |
| if (message.getPayload() != null) { |
| if (message.getPayload().getContentMetadata().getCacheControl() != null) |
| logger.debug("%s %s: %s", prefix, CACHE_CONTROL, message.getPayload().getContentMetadata().getCacheControl()); |
| if (message.getPayload().getContentMetadata().getContentType() != null) |
| logger.debug("%s %s: %s", prefix, CONTENT_TYPE, message.getPayload().getContentMetadata().getContentType()); |
| if (message.getPayload().getContentMetadata().getContentLength() != null) |
| logger.debug("%s %s: %s", prefix, CONTENT_LENGTH, message.getPayload().getContentMetadata() |
| .getContentLength()); |
| byte[] md5 = message.getPayload().getContentMetadata().getContentMD5(); |
| if (md5 != null) |
| logger.debug("%s %s: %s", prefix, CONTENT_MD5, base64().encode(md5)); |
| if (message.getPayload().getContentMetadata().getContentDisposition() != null) |
| logger.debug("%s %s: %s", prefix, CONTENT_DISPOSITION, message.getPayload().getContentMetadata() |
| .getContentDisposition()); |
| if (message.getPayload().getContentMetadata().getContentEncoding() != null) |
| logger.debug("%s %s: %s", prefix, CONTENT_ENCODING, message.getPayload().getContentMetadata() |
| .getContentEncoding()); |
| if (message.getPayload().getContentMetadata().getContentLanguage() != null) |
| logger.debug("%s %s: %s", prefix, CONTENT_LANGUAGE, message.getPayload().getContentMetadata() |
| .getContentLanguage()); |
| if (message.getPayload().getContentMetadata().getExpires() != null) |
| logger.debug("%s %s: %s", prefix, EXPIRES, message.getPayload().getContentMetadata().getExpires()); |
| } |
| } |
| |
| public void logResponse(Logger logger, HttpResponse response, String prefix) { |
| if (logger.isDebugEnabled()) { |
| logger.debug("%s %s", prefix, response.getStatusLine().toString()); |
| logMessage(logger, response, prefix); |
| } |
| } |
| |
| public void checkRequestHasRequiredProperties(HttpRequest message) { |
| checkArgument( |
| message.getPayload() == null || message.getFirstHeaderOrNull(CACHE_CONTROL) == null, |
| "configuration error please use request.getPayload().getContentMetadata().setCacheControl(value) as opposed to adding a cache control header: %s", |
| message); |
| checkArgument( |
| message.getPayload() == null || message.getFirstHeaderOrNull(CONTENT_TYPE) == null, |
| "configuration error please use request.getPayload().getContentMetadata().setContentType(value) as opposed to adding a content type header: %s", |
| message); |
| checkArgument( |
| message.getPayload() == null || message.getFirstHeaderOrNull(CONTENT_LENGTH) == null, |
| "configuration error please use request.getPayload().getContentMetadata().setContentLength(value) as opposed to adding a content length header: %s", |
| message); |
| checkArgument( |
| message.getPayload() == null || message.getPayload().getContentMetadata().getContentLength() != null |
| || "chunked".equalsIgnoreCase(message.getFirstHeaderOrNull("Transfer-Encoding")), |
| "either chunked encoding must be set on the http request or contentlength set on the payload: %s", message); |
| checkArgument( |
| message.getPayload() == null || message.getFirstHeaderOrNull("Content-MD5") == null, |
| "configuration error please use request.getPayload().getContentMetadata().setContentMD5(value) as opposed to adding a content md5 header: %s", |
| message); |
| checkArgument( |
| message.getPayload() == null || message.getFirstHeaderOrNull("Content-Disposition") == null, |
| "configuration error please use request.getPayload().getContentMetadata().setContentDisposition(value) as opposed to adding a content disposition header: %s", |
| message); |
| checkArgument( |
| message.getPayload() == null || message.getFirstHeaderOrNull(CONTENT_ENCODING) == null, |
| "configuration error please use request.getPayload().getContentMetadata().setContentEncoding(value) as opposed to adding a content encoding header: %s", |
| message); |
| checkArgument( |
| message.getPayload() == null || message.getFirstHeaderOrNull(CONTENT_LANGUAGE) == null, |
| "configuration error please use request.getPayload().getContentMetadata().setContentLanguage(value) as opposed to adding a content language header: %s", |
| message); |
| checkArgument( |
| message.getPayload() == null || message.getFirstHeaderOrNull(EXPIRES) == null, |
| "configuration error please use request.getPayload().getContentMetadata().setExpires(value) as opposed to adding an expires header: %s", |
| message); |
| } |
| |
| public static void releasePayload(HttpMessage from) { |
| if (from.getPayload() != null) |
| from.getPayload().release(); |
| } |
| |
| public static String nullToEmpty(byte[] md5) { |
| return md5 != null ? base64().encode(md5) : ""; |
| } |
| |
| public static String nullOrZeroToEmpty(Long contentLength) { |
| return contentLength != null && contentLength > 0 ? contentLength.toString() : ""; |
| } |
| |
| public static String nullToEmpty(Collection<String> collection) { |
| return (collection == null || collection.isEmpty()) ? "" : collection.iterator().next(); |
| } |
| |
| public static Long attemptToParseSizeAndRangeFromHeaders(HttpMessage from) throws HttpException { |
| String contentRange = from.getFirstHeaderOrNull("Content-Range"); |
| if (contentRange == null && from.getPayload() != null) { |
| return from.getPayload().getContentMetadata().getContentLength(); |
| } else if (contentRange != null) { |
| return Long.parseLong(contentRange.substring(contentRange.lastIndexOf('/') + 1)); |
| } |
| return null; |
| } |
| |
| public static void checkRequestHasContentLengthOrChunkedEncoding(HttpMessage request, String message) { |
| boolean chunked = "chunked".equals(request.getFirstHeaderOrNull("Transfer-Encoding")); |
| checkArgument(request.getPayload() == null || chunked |
| || request.getPayload().getContentMetadata().getContentLength() != null, message); |
| } |
| |
| public static void wirePayloadIfEnabled(Wire wire, HttpMessage request) { |
| if (request.getPayload() != null && wire.enabled()) { |
| wire.output(request); |
| checkRequestHasContentLengthOrChunkedEncoding(request, |
| "After wiring, the request has neither chunked encoding nor content length: " + request); |
| } |
| } |
| |
| public static <T> T returnValueOnCodeOrNull(Throwable from, T value, Predicate<Integer> codePredicate) { |
| Iterable<HttpResponseException> throwables = filter(getCausalChain(from), HttpResponseException.class); |
| if (size(throwables) >= 1 && get(throwables, 0).getResponse() != null |
| && codePredicate.apply(get(throwables, 0).getResponse().getStatusCode())) { |
| return value; |
| } |
| return null; |
| } |
| |
| public static Multimap<String, String> filterOutContentHeaders(Multimap<String, String> headers) { |
| // http message usually comes in as a null key header, let's filter it out. |
| return ImmutableMultimap.copyOf(filterKeys(headers, and(notNull(), not(in(ContentMetadata.HTTP_HEADERS))))); |
| } |
| |
| public static boolean contains404(Throwable t) { |
| return returnValueOnCodeOrNull(t, true, equalTo(404)) != null; |
| } |
| } |