blob: b00c3ff8b2f470df17fbf201eb00673566e373f3 [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.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;
}
}