/* * Copyright 2015 Netflix, Inc. * * 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 com.netflix.discovery.shared.transport.jersey; import com.netflix.appinfo.AbstractEurekaIdentity; import com.netflix.appinfo.EurekaAccept; import com.netflix.appinfo.EurekaClientIdentity; import com.netflix.appinfo.InstanceInfo; import com.netflix.discovery.EurekaClientConfig; import com.netflix.discovery.EurekaIdentityHeaderFilter; import com.netflix.discovery.provider.DiscoveryJerseyProvider; import com.netflix.discovery.shared.resolver.EurekaEndpoint; import com.netflix.discovery.shared.transport.EurekaClientFactoryBuilder; import com.netflix.discovery.shared.transport.EurekaHttpClient; import com.netflix.discovery.shared.transport.TransportClientFactory; import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClientImpl.EurekaJerseyClientBuilder; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.filter.ClientFilter; import com.sun.jersey.api.client.filter.GZIPContentEncodingFilter; import com.sun.jersey.client.apache4.ApacheHttpClient4; import com.sun.jersey.client.apache4.config.ApacheHttpClient4Config; import com.sun.jersey.client.apache4.config.DefaultApacheHttpClient4Config; import org.apache.http.client.params.ClientPNames; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.scheme.SchemeSocketFactory; import org.apache.http.conn.ssl.AllowAllHostnameVerifier; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.CoreProtocolPNames; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import static com.netflix.discovery.util.DiscoveryBuildInfo.buildVersion; /** * @author Tomasz Bak */ public class JerseyEurekaHttpClientFactory implements TransportClientFactory { public static final String HTTP_X_DISCOVERY_ALLOW_REDIRECT = "X-Discovery-AllowRedirect"; private final EurekaJerseyClient jerseyClient; private final ApacheHttpClient4 apacheClient; private final ApacheHttpClientConnectionCleaner cleaner; private final Map<String, String> additionalHeaders; /** * @deprecated {@link EurekaJerseyClient} is deprecated and will be removed */ @Deprecated public JerseyEurekaHttpClientFactory(EurekaJerseyClient jerseyClient, boolean allowRedirects) { this( jerseyClient, null, -1, Collections.singletonMap(HTTP_X_DISCOVERY_ALLOW_REDIRECT, allowRedirects ? "true" : "false") ); } @Deprecated public JerseyEurekaHttpClientFactory(EurekaJerseyClient jerseyClient, Map<String, String> additionalHeaders) { this(jerseyClient, null, -1, additionalHeaders); } public JerseyEurekaHttpClientFactory(ApacheHttpClient4 apacheClient, long connectionIdleTimeout, Map<String, String> additionalHeaders) { this(null, apacheClient, connectionIdleTimeout, additionalHeaders); } private JerseyEurekaHttpClientFactory(EurekaJerseyClient jerseyClient, ApacheHttpClient4 apacheClient, long connectionIdleTimeout, Map<String, String> additionalHeaders) { this.jerseyClient = jerseyClient; this.apacheClient = jerseyClient != null ? jerseyClient.getClient() : apacheClient; this.additionalHeaders = additionalHeaders; this.cleaner = new ApacheHttpClientConnectionCleaner(this.apacheClient, connectionIdleTimeout); } @Override public EurekaHttpClient newClient(EurekaEndpoint endpoint) { return new JerseyApplicationClient(apacheClient, endpoint.getServiceUrl(), additionalHeaders); } @Override public void shutdown() { cleaner.shutdown(); if (jerseyClient != null) { jerseyClient.destroyResources(); } else { apacheClient.destroy(); } } public static JerseyEurekaHttpClientFactory create(EurekaClientConfig clientConfig, Collection<ClientFilter> additionalFilters, InstanceInfo myInstanceInfo, AbstractEurekaIdentity clientIdentity) { JerseyEurekaHttpClientFactoryBuilder clientBuilder = newBuilder() .withAdditionalFilters(additionalFilters) .withMyInstanceInfo(myInstanceInfo) .withUserAgent("Java-EurekaClient") .withClientConfig(clientConfig) .withClientIdentity(clientIdentity); if ("true".equals(System.getProperty("com.netflix.eureka.shouldSSLConnectionsUseSystemSocketFactory"))) { clientBuilder.withClientName("DiscoveryClient-HTTPClient-System").withSystemSSLConfiguration(); } else if (clientConfig.getProxyHost() != null && clientConfig.getProxyPort() != null) { clientBuilder.withClientName("Proxy-DiscoveryClient-HTTPClient") .withProxy( clientConfig.getProxyHost(), Integer.parseInt(clientConfig.getProxyPort()), clientConfig.getProxyUserName(), clientConfig.getProxyPassword() ); } else { clientBuilder.withClientName("DiscoveryClient-HTTPClient"); } return clientBuilder.build(); } public static JerseyEurekaHttpClientFactoryBuilder newBuilder() { return new JerseyEurekaHttpClientFactoryBuilder().withExperimental(false); } public static JerseyEurekaHttpClientFactoryBuilder experimentalBuilder() { return new JerseyEurekaHttpClientFactoryBuilder().withExperimental(true); } /** * Currently use EurekaJerseyClientBuilder. Once old transport in DiscoveryClient is removed, incorporate * EurekaJerseyClientBuilder here, and remove it. */ public static class JerseyEurekaHttpClientFactoryBuilder extends EurekaClientFactoryBuilder<JerseyEurekaHttpClientFactory, JerseyEurekaHttpClientFactoryBuilder> { private Collection<ClientFilter> additionalFilters = Collections.emptyList(); private boolean experimental = false; public JerseyEurekaHttpClientFactoryBuilder withAdditionalFilters(Collection<ClientFilter> additionalFilters) { this.additionalFilters = additionalFilters; return this; } public JerseyEurekaHttpClientFactoryBuilder withExperimental(boolean experimental) { this.experimental = experimental; return this; } @Override public JerseyEurekaHttpClientFactory build() { Map<String, String> additionalHeaders = new HashMap<>(); if (allowRedirect) { additionalHeaders.put(HTTP_X_DISCOVERY_ALLOW_REDIRECT, "true"); } if (EurekaAccept.compact == eurekaAccept) { additionalHeaders.put(EurekaAccept.HTTP_X_EUREKA_ACCEPT, eurekaAccept.name()); } if (experimental) { return buildExperimental(additionalHeaders); } return buildLegacy(additionalHeaders, systemSSL); } private JerseyEurekaHttpClientFactory buildLegacy(Map<String, String> additionalHeaders, boolean systemSSL) { EurekaJerseyClientBuilder clientBuilder = new EurekaJerseyClientBuilder() .withClientName(clientName) .withUserAgent("Java-EurekaClient") .withConnectionTimeout(connectionTimeout) .withReadTimeout(readTimeout) .withMaxConnectionsPerHost(maxConnectionsPerHost) .withMaxTotalConnections(maxTotalConnections) .withConnectionIdleTimeout((int) connectionIdleTimeout) .withEncoderWrapper(encoderWrapper) .withDecoderWrapper(decoderWrapper); if (systemSSL) { clientBuilder.withSystemSSLConfiguration(); } EurekaJerseyClient jerseyClient = clientBuilder.build(); ApacheHttpClient4 discoveryApacheClient = jerseyClient.getClient(); addFilters(discoveryApacheClient); return new JerseyEurekaHttpClientFactory(jerseyClient, additionalHeaders); } private JerseyEurekaHttpClientFactory buildExperimental(Map<String, String> additionalHeaders) { ThreadSafeClientConnManager cm = createConnectionManager(); ClientConfig clientConfig = new DefaultApacheHttpClient4Config(); if (proxyHost != null) { addProxyConfiguration(clientConfig); } DiscoveryJerseyProvider discoveryJerseyProvider = new DiscoveryJerseyProvider(encoderWrapper, decoderWrapper); clientConfig.getSingletons().add(discoveryJerseyProvider); // Common properties to all clients cm.setDefaultMaxPerRoute(maxConnectionsPerHost); cm.setMaxTotal(maxTotalConnections); clientConfig.getProperties().put(ApacheHttpClient4Config.PROPERTY_CONNECTION_MANAGER, cm); String fullUserAgentName = (userAgent == null ? clientName : userAgent) + "/v" + buildVersion(); clientConfig.getProperties().put(CoreProtocolPNames.USER_AGENT, fullUserAgentName); // To pin a client to specific server in case redirect happens, we handle redirects directly // (see DiscoveryClient.makeRemoteCall methods). clientConfig.getProperties().put(ClientConfig.PROPERTY_FOLLOW_REDIRECTS, Boolean.FALSE); clientConfig.getProperties().put(ClientPNames.HANDLE_REDIRECTS, Boolean.FALSE); ApacheHttpClient4 apacheClient = ApacheHttpClient4.create(clientConfig); addFilters(apacheClient); return new JerseyEurekaHttpClientFactory(apacheClient, connectionIdleTimeout, additionalHeaders); } /** * Since Jersey 1.19 depends on legacy apache http-client API, we have to as well. */ private ThreadSafeClientConnManager createConnectionManager() { try { ThreadSafeClientConnManager connectionManager; if (sslContext != null) { SchemeSocketFactory socketFactory = new SSLSocketFactory(sslContext, new AllowAllHostnameVerifier()); SchemeRegistry sslSchemeRegistry = new SchemeRegistry(); sslSchemeRegistry.register(new Scheme("https", 443, socketFactory)); connectionManager = new ThreadSafeClientConnManager(sslSchemeRegistry); } else { connectionManager = new ThreadSafeClientConnManager(); } return connectionManager; } catch (Exception e) { throw new IllegalStateException("Cannot initialize Apache connection manager", e); } } private void addProxyConfiguration(ClientConfig clientConfig) { if (proxyUserName != null && proxyPassword != null) { clientConfig.getProperties().put(ApacheHttpClient4Config.PROPERTY_PROXY_USERNAME, proxyUserName); clientConfig.getProperties().put(ApacheHttpClient4Config.PROPERTY_PROXY_PASSWORD, proxyPassword); } else { // Due to bug in apache client, user name/password must always be set. // Otherwise proxy configuration is ignored. clientConfig.getProperties().put(ApacheHttpClient4Config.PROPERTY_PROXY_USERNAME, "guest"); clientConfig.getProperties().put(ApacheHttpClient4Config.PROPERTY_PROXY_PASSWORD, "guest"); } clientConfig.getProperties().put(DefaultApacheHttpClient4Config.PROPERTY_PROXY_URI, "http://" + proxyHost + ':' + proxyPort); } private void addFilters(ApacheHttpClient4 discoveryApacheClient) { // Add gzip content encoding support discoveryApacheClient.addFilter(new GZIPContentEncodingFilter(false)); // always enable client identity headers String ip = myInstanceInfo == null ? null : myInstanceInfo.getIPAddr(); AbstractEurekaIdentity identity = clientIdentity == null ? new EurekaClientIdentity(ip) : clientIdentity; discoveryApacheClient.addFilter(new EurekaIdentityHeaderFilter(identity)); if (additionalFilters != null) { for (ClientFilter filter : additionalFilters) { if (filter != null) { discoveryApacheClient.addFilter(filter); } } } } } }