/* * 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.jersey2; import javax.net.ssl.SSLContext; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.core.Feature; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import java.io.FileInputStream; import java.security.KeyStore; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; 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.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 org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.client.JerseyClient; import org.glassfish.jersey.message.GZipEncoder; import static com.netflix.discovery.util.DiscoveryBuildInfo.buildVersion; /** * @author Tomasz Bak */ public class Jersey2ApplicationClientFactory implements TransportClientFactory { public static final String HTTP_X_DISCOVERY_ALLOW_REDIRECT = "X-Discovery-AllowRedirect"; private static final String KEY_STORE_TYPE = "JKS"; private final Client jersey2Client; private final MultivaluedMap<String, Object> additionalHeaders; public Jersey2ApplicationClientFactory(Client jersey2Client, MultivaluedMap<String, Object> additionalHeaders) { this.jersey2Client = jersey2Client; this.additionalHeaders = additionalHeaders; } @Override public EurekaHttpClient newClient(EurekaEndpoint endpoint) { return new Jersey2ApplicationClient(jersey2Client, endpoint.getServiceUrl(), additionalHeaders); } @Override public void shutdown() { jersey2Client.close(); } public static Jersey2ApplicationClientFactory create(EurekaClientConfig clientConfig, Collection<ClientRequestFilter> additionalFilters, InstanceInfo myInstanceInfo, AbstractEurekaIdentity clientIdentity) { Jersey2ApplicationClientFactoryBuilder clientBuilder = newBuilder(); clientBuilder.withAdditionalFilters(additionalFilters); clientBuilder.withMyInstanceInfo(myInstanceInfo); clientBuilder.withUserAgent("Java-EurekaClient"); clientBuilder.withClientConfig(clientConfig); clientBuilder.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 Jersey2ApplicationClientFactoryBuilder newBuilder() { return new Jersey2ApplicationClientFactoryBuilder(); } public static class Jersey2ApplicationClientFactoryBuilder extends EurekaClientFactoryBuilder<Jersey2ApplicationClientFactory, Jersey2ApplicationClientFactoryBuilder> { private List<Feature> features = new ArrayList<>(); private List<ClientRequestFilter> additionalFilters = new ArrayList<>(); public Jersey2ApplicationClientFactoryBuilder withFeature(Feature feature) { features.add(feature); return this; } Jersey2ApplicationClientFactoryBuilder withAdditionalFilters(Collection<ClientRequestFilter> additionalFilters) { if (additionalFilters != null) { this.additionalFilters.addAll(additionalFilters); } return this; } @Override public Jersey2ApplicationClientFactory build() { ClientBuilder clientBuilder = ClientBuilder.newBuilder(); ClientConfig clientConfig = new ClientConfig(); for (ClientRequestFilter filter : additionalFilters) { clientBuilder.register(filter); } for (Feature feature : features) { clientConfig.register(feature); } addProviders(clientConfig); addSSLConfiguration(clientBuilder); addProxyConfiguration(clientConfig); // Common properties to all clients final String fullUserAgentName = (userAgent == null ? clientName : userAgent) + "/v" + buildVersion(); clientBuilder.register(new ClientRequestFilter() { // Can we do it better, without filter? @Override public void filter(ClientRequestContext requestContext) { requestContext.getHeaders().put(HttpHeaders.USER_AGENT, Collections.<Object>singletonList(fullUserAgentName)); } }); clientConfig.property(ClientProperties.FOLLOW_REDIRECTS, allowRedirect); clientConfig.property(ClientProperties.READ_TIMEOUT, readTimeout); clientConfig.property(ClientProperties.CONNECT_TIMEOUT, connectionTimeout); clientBuilder.withConfig(clientConfig); // Add gzip content encoding support clientBuilder.register(new GZipEncoder()); // always enable client identity headers String ip = myInstanceInfo == null ? null : myInstanceInfo.getIPAddr(); final AbstractEurekaIdentity identity = clientIdentity == null ? new EurekaClientIdentity(ip) : clientIdentity; clientBuilder.register(new Jersey2EurekaIdentityHeaderFilter(identity)); JerseyClient jersey2Client = (JerseyClient) clientBuilder.build(); MultivaluedMap<String, Object> additionalHeaders = new MultivaluedHashMap<>(); if (allowRedirect) { additionalHeaders.add(HTTP_X_DISCOVERY_ALLOW_REDIRECT, "true"); } if (EurekaAccept.compact == eurekaAccept) { additionalHeaders.add(EurekaAccept.HTTP_X_EUREKA_ACCEPT, eurekaAccept.name()); } return new Jersey2ApplicationClientFactory(jersey2Client, additionalHeaders); } private void addSSLConfiguration(ClientBuilder clientBuilder) { try { if (systemSSL) { clientBuilder.sslContext(SSLContext.getDefault()); } else if (trustStoreFileName != null) { KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE); FileInputStream fin = new FileInputStream(trustStoreFileName); trustStore.load(fin, trustStorePassword.toCharArray()); clientBuilder.trustStore(trustStore); } } catch (Exception ex) { throw new IllegalArgumentException("Cannot setup SSL for Jersey2 client", ex); } } private void addProxyConfiguration(ClientConfig clientConfig) { if (proxyHost != null) { String proxyAddress = proxyHost; if (proxyPort > 0) { proxyAddress += ':' + proxyPort; } clientConfig.property(ClientProperties.PROXY_URI, proxyAddress); if (proxyUserName != null) { if (proxyPassword == null) { throw new IllegalArgumentException("Proxy user name provided but not password"); } clientConfig.property(ClientProperties.PROXY_USERNAME, proxyUserName); clientConfig.property(ClientProperties.PROXY_PASSWORD, proxyPassword); } } } private void addProviders(ClientConfig clientConfig) { DiscoveryJerseyProvider discoveryJerseyProvider = new DiscoveryJerseyProvider(encoderWrapper, decoderWrapper); clientConfig.register(discoveryJerseyProvider); } } }