/* * Copyright 2012 the original author or authors. * * Licensed 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.gradle.internal.resource.transport.http; import org.apache.http.Header; import org.apache.http.HeaderIterator; import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; import org.apache.http.ProtocolVersion; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.client.utils.HttpClientUtils; import org.apache.http.entity.BufferedHttpEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.gradle.api.UncheckedIOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Closeable; import java.io.IOException; import java.util.Locale; /** * Provides some convenience and unified logging. */ public class HttpClientHelper implements Closeable { private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientHelper.class); private CloseableHttpClient client; private final HttpSettings settings; private final HttpContext sharedContext; public HttpClientHelper(HttpSettings settings) { this.settings = settings; if (!settings.getAuthenticationSettings().isEmpty()) { sharedContext = new BasicHttpContext(); } else { sharedContext = null; } } public CloseableHttpResponse performRawHead(String source, boolean revalidate) { return performRequest(new HttpHead(source), revalidate); } public CloseableHttpResponse performHead(String source, boolean revalidate) { return processResponse(source, "HEAD", performRawHead(source, revalidate)); } public CloseableHttpResponse performRawGet(String source, boolean revalidate) { return performRequest(new HttpGet(source), revalidate); } public CloseableHttpResponse performGet(String source, boolean revalidate) { return processResponse(source, "GET", performRawGet(source, revalidate)); } public CloseableHttpResponse performRequest(HttpRequestBase request, boolean revalidate) { String method = request.getMethod(); if (revalidate) { request.addHeader(HttpHeaders.CACHE_CONTROL, "max-age=0"); } CloseableHttpResponse response; try { response = executeGetOrHead(request); } catch (IOException e) { throw new HttpRequestException(String.format("Could not %s '%s'.", method, request.getURI()), e); } return response; } protected CloseableHttpResponse executeGetOrHead(HttpRequestBase method) throws IOException { final CloseableHttpResponse httpResponse = performHttpRequest(method); // Consume content for non-successful, responses. This avoids the connection being left open. if (!wasSuccessful(httpResponse)) { CloseableHttpResponse response = new AutoClosedHttpResponse(httpResponse); HttpClientUtils.closeQuietly(httpResponse); return response; } return httpResponse; } public boolean wasMissing(CloseableHttpResponse response) { int statusCode = response.getStatusLine().getStatusCode(); return statusCode == 404; } public boolean wasSuccessful(CloseableHttpResponse response) { int statusCode = response.getStatusLine().getStatusCode(); return statusCode >= 200 && statusCode < 400; } public CloseableHttpResponse performHttpRequest(HttpRequestBase request) throws IOException { if (sharedContext == null) { // There's no authentication involved, requests can be done concurrently return performHttpRequest(request, new BasicHttpContext()); } // authentication is used, we cannot guarantee thread-safety in this case so requests need // to be done with blocking synchronized (this) { return performHttpRequest(request, sharedContext); } } private CloseableHttpResponse performHttpRequest(HttpRequestBase request, HttpContext httpContext) throws IOException { // Without this, HTTP Client prohibits multiple redirects to the same location within the same context httpContext.removeAttribute(HttpClientContext.REDIRECT_LOCATIONS); LOGGER.debug("Performing HTTP {}: {}", request.getMethod(), request.getURI()); return getClient().execute(request, httpContext); } private CloseableHttpResponse processResponse(String source, String method, CloseableHttpResponse response) { if (wasMissing(response)) { LOGGER.info("Resource missing. [HTTP {}: {}]", method, source); return null; } if (!wasSuccessful(response)) { LOGGER.info("Failed to get resource: {}. [HTTP {}: {}]", method, response.getStatusLine(), source); throw new UncheckedIOException(String.format("Could not %s '%s'. Received status code %s from server: %s", method, source, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase())); } return response; } private synchronized CloseableHttpClient getClient() { if (client == null) { HttpClientBuilder builder = HttpClientBuilder.create(); builder.setRedirectStrategy(new AlwaysRedirectRedirectStrategy()); new HttpClientConfigurer(settings).configure(builder); this.client = builder.build(); } return client; } @Override public synchronized void close() throws IOException { if (client != null) { client.close(); } } private static class AutoClosedHttpResponse implements CloseableHttpResponse { private final HttpEntity entity; private final CloseableHttpResponse httpResponse; public AutoClosedHttpResponse(CloseableHttpResponse httpResponse) throws IOException { this.httpResponse = httpResponse; HttpEntity entity = httpResponse.getEntity(); this.entity = entity != null ? new BufferedHttpEntity(entity) : null; } @Override public void close() throws IOException { } @Override public StatusLine getStatusLine() { return httpResponse.getStatusLine(); } @Override public void setStatusLine(StatusLine statusline) { throw new UnsupportedOperationException(); } @Override public void setStatusLine(ProtocolVersion ver, int code) { throw new UnsupportedOperationException(); } @Override public void setStatusLine(ProtocolVersion ver, int code, String reason) { throw new UnsupportedOperationException(); } @Override public void setStatusCode(int code) throws IllegalStateException { throw new UnsupportedOperationException(); } @Override public void setReasonPhrase(String reason) throws IllegalStateException { throw new UnsupportedOperationException(); } @Override public HttpEntity getEntity() { return entity; } @Override public void setEntity(HttpEntity entity) { throw new UnsupportedOperationException(); } @Override public Locale getLocale() { return httpResponse.getLocale(); } @Override public void setLocale(Locale loc) { throw new UnsupportedOperationException(); } @Override public ProtocolVersion getProtocolVersion() { return httpResponse.getProtocolVersion(); } @Override public boolean containsHeader(String name) { return httpResponse.containsHeader(name); } @Override public Header[] getHeaders(String name) { return httpResponse.getHeaders(name); } @Override public Header getFirstHeader(String name) { return httpResponse.getFirstHeader(name); } @Override public Header getLastHeader(String name) { return httpResponse.getLastHeader(name); } @Override public Header[] getAllHeaders() { return httpResponse.getAllHeaders(); } @Override public void addHeader(Header header) { throw new UnsupportedOperationException(); } @Override public void addHeader(String name, String value) { throw new UnsupportedOperationException(); } @Override public void setHeader(Header header) { throw new UnsupportedOperationException(); } @Override public void setHeader(String name, String value) { throw new UnsupportedOperationException(); } @Override public void setHeaders(Header[] headers) { throw new UnsupportedOperationException(); } @Override public void removeHeader(Header header) { throw new UnsupportedOperationException(); } @Override public void removeHeaders(String name) { throw new UnsupportedOperationException(); } @Override public HeaderIterator headerIterator() { return httpResponse.headerIterator(); } @Override public HeaderIterator headerIterator(String name) { return httpResponse.headerIterator(name); } @Override @SuppressWarnings("deprecation") public org.apache.http.params.HttpParams getParams() { return httpResponse.getParams(); } @Override @SuppressWarnings("deprecation") public void setParams(org.apache.http.params.HttpParams params) { throw new UnsupportedOperationException(); } } }