package jenkins.plugins.asynchttpclient; import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.AsyncHttpClientConfig; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Extension; import hudson.ProxyConfiguration; import hudson.model.Describable; import hudson.model.Descriptor; import jenkins.model.Jenkins; import org.apache.commons.lang.StringUtils; import java.util.logging.Logger; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Provides a global shared {@link AsyncHttpClient} instance, for use from the master, * configured with the master's proxy settings. * This shared instance will be gracefully closed when Jenkins is terminating, and will be recycled if the proxy * settings change or if somebody closes it by accident. * The recommendation is not to cache the instance longer than a user's request. * * @since 1.7.8 */ @Extension public class AHC extends Descriptor<AHC> implements Describable<AHC> { /** * Override to enable insecure handling of TLS connections. * @see <a href="https://www.cvedetails.com/cve/CVE-2013-7397/">CVE-2013-7397</a> and * <a href="https://www.cvedetails.com/cve/CVE-2013-7398/">CVE-2013-7398</a> * @since 1.7.24.1 */ @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Allow runtime modification") @Restricted(NoExternalUse.class) // no direct linking against this field please public static boolean acceptAnyCertificate = Boolean.getBoolean(AHC.class.getName() + ".acceptAnyCertificate"); /** * Our logger. */ private Logger logger = Logger.getLogger(AHC.class.getName()); /** * Our instance. */ private AsyncHttpClient instance; /** * A memo instance of the proxy settings. */ private ProxyConfiguration memo; /** * Our constructor. */ @SuppressWarnings("unused") // used by Jenkins public AHC() { super(AHC.class); } /** * Returns the shared {@link AsyncHttpClient} instance. * * @return the shared {@link AsyncHttpClient} instance. * @throws IllegalStateException if executed on a slave JVM. */ public static AsyncHttpClient instance() { Jenkins master = Jenkins.getInstance(); if (master == null) { throw new IllegalStateException("The shared AsyncHttpClient instance is only available on the master"); } return AHC.class.cast(master.getDescriptorOrDie(AHC.class)).getInstance(); } /** * Get the instance, refreshing if needed and creating on demand. * * @return the instance. */ @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "https://github.com/jenkinsci/jenkins/pull/2094") private synchronized AsyncHttpClient getInstance() { if (instance != null) { ProxyConfiguration proxy = Jenkins.getInstance().proxy; if (!equals(proxy, memo)) { logger.fine("Proxy configuration changed, recycling shared AsyncHttpClient instance"); if (instance != null) { instance.close(); } instance = null; memo = proxy; } } if (instance == null || instance.isClosed()) { logger.fine("Starting shared AsyncHttpClient instance"); instance = new AsyncHttpClient( new AsyncHttpClientConfig.Builder() .setProxyServer(AHCUtils.getProxyServer()) .setHostnameVerifier(AHCUtils.getHostnameVerifier()) .setSSLContext(AHCUtils.getSSLContext()) .build()); } return instance; } /** * Compare two {@link ProxyConfiguration} instances. * * @param p1 the first. * @param p2 the second. * @return {@code true} if and only if the two instances are effectively the same. */ private boolean equals(ProxyConfiguration p1, ProxyConfiguration p2) { if (p1 == p2) { return true; } if (p1 == null || p2 == null) { return false; } if (p1.port != p2.port) { return false; } if (!StringUtils.equals(p1.name, p2.name)) { return false; } if (!StringUtils.equals(p1.getUserName(), p2.getUserName())) { return false; } if (!StringUtils.equals(p1.getEncryptedPassword(), p2.getEncryptedPassword())) { return false; } return true; } /** * Shut down the instance if it exists. */ synchronized void shutdown() { if (instance != null) { if (!instance.isClosed()) { logger.fine("Shutting down shared AsyncHttpClient instance"); instance.close(); } instance = null; memo = null; } } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "https://github.com/jenkinsci/jenkins/pull/2094") public AHC getDescriptor() { return (AHC) Jenkins.getInstance().getDescriptor(AHC.class); } /** * {@inheritDoc} */ @Override public String getDisplayName() { return ""; } }