Browse Source

Optionally remove Jersey dependencies from Eureka Client (#1850)

Fixes gh-1849
pull/6/head
Daniel Lavoie 7 years ago committed by Spencer Gibb
parent
commit
348c96bf13
  1. 28
      docs/src/main/asciidoc/spring-cloud-netflix.adoc
  2. 11
      spring-cloud-netflix-eureka-client/pom.xml
  3. 3
      spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/CloudEurekaClient.java
  4. 23
      spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java
  5. 48
      spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/config/DiscoveryClientOptionalArgsConfiguration.java
  6. 40
      spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/EurekaApplications.java
  7. 29
      spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/RestTemplateDiscoveryClientOptionalArgs.java
  8. 227
      spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/RestTemplateEurekaHttpClient.java
  9. 46
      spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/RestTemplateTransportClientFactories.java
  10. 98
      spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/RestTemplateTransportClientFactory.java
  11. 45
      spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/config/JerseyOptionalArgsConfigurationTest.java
  12. 46
      spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/config/RestTemplateOptionalArgsConfigurationTest.java
  13. 117
      spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/http/EurekaServerMockApplication.java
  14. 139
      spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/http/RestTemplateEurekaHttpClientTest.java
  15. 29
      spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/http/RestTemplateTransportClientFactoriesTest.java
  16. 57
      spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/http/RestTemplateTransportClientFactoryTest.java
  17. 9
      spring-cloud-netflix-eureka-client/src/test/resources/application.yml

28
docs/src/main/asciidoc/spring-cloud-netflix.adoc

@ -264,6 +264,34 @@ not be started yet). It is initialized in a `SmartLifecycle` (with @@ -264,6 +264,34 @@ not be started yet). It is initialized in a `SmartLifecycle` (with
another `SmartLifecycle` with higher phase.
====
==== EurekaClient without Jersey
By default, EurekaClient uses Jersey for HTTP communication. If you wish
to avoid dependencies from Jersey, you can exclude it from your dependencies.
Spring Cloud will auto configure a transport client based on Spring
`RestTemplate`.
----
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-apache-client4</artifactId>
</exclusion>
</exclusions>
</dependency>
----
=== Alternatives to the native Netflix EurekaClient
You don't have to use the raw Netflix `EurekaClient` and usually it

11
spring-cloud-netflix-eureka-client/pom.xml

@ -118,11 +118,22 @@ @@ -118,11 +118,22 @@
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>

3
spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/CloudEurekaClient.java

@ -29,6 +29,7 @@ import org.springframework.util.ReflectionUtils; @@ -29,6 +29,7 @@ import org.springframework.util.ReflectionUtils;
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;
import com.netflix.discovery.DiscoveryClient;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.shared.transport.EurekaHttpClient;
@ -55,7 +56,7 @@ public class CloudEurekaClient extends DiscoveryClient { @@ -55,7 +56,7 @@ public class CloudEurekaClient extends DiscoveryClient {
public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config,
DiscoveryClientOptionalArgs args,
AbstractDiscoveryClientOptionalArgs<?> args,
ApplicationEventPublisher publisher) {
super(applicationInfoManager, config, args);
this.applicationInfoManager = applicationInfoManager;

23
spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java

@ -17,6 +17,8 @@ @@ -17,6 +17,8 @@
package org.springframework.cloud.netflix.eureka;
import static org.springframework.cloud.commons.util.IdUtils.getDefaultInstanceId;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@ -47,6 +49,7 @@ import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationP @@ -47,6 +49,7 @@ import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationP
import org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry;
@ -54,6 +57,7 @@ import org.springframework.context.ApplicationContext; @@ -54,6 +57,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertyResolver;
@ -63,9 +67,10 @@ import com.netflix.appinfo.ApplicationInfoManager; @@ -63,9 +67,10 @@ import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.DiscoveryClient.DiscoveryClientOptionalArgs;
import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.EurekaClientConfig;
import static org.springframework.cloud.commons.util.IdUtils.getDefaultInstanceId;
/**
@ -74,10 +79,12 @@ import static org.springframework.cloud.commons.util.IdUtils.getDefaultInstanceI @@ -74,10 +79,12 @@ import static org.springframework.cloud.commons.util.IdUtils.getDefaultInstanceI
* @author Jon Schneider
* @author Matt Jenkins
* @author Ryan Baxter
* @author Daniel Lavoie
*/
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@Import(DiscoveryClientOptionalArgsConfiguration.class)
@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
@ -177,12 +184,6 @@ public class EurekaClientAutoConfiguration { @@ -177,12 +184,6 @@ public class EurekaClientAutoConfiguration {
return new EurekaAutoServiceRegistration(context, registry, registration);
}
@Bean
@ConditionalOnMissingBean(value = DiscoveryClientOptionalArgs.class, search = SearchStrategy.CURRENT)
public MutableDiscoveryClientOptionalArgs discoveryClientOptionalArgs() {
return new MutableDiscoveryClientOptionalArgs();
}
@Configuration
@ConditionalOnMissingRefreshScope
protected static class EurekaClientConfiguration {
@ -190,8 +191,8 @@ public class EurekaClientAutoConfiguration { @@ -190,8 +191,8 @@ public class EurekaClientAutoConfiguration {
@Autowired
private ApplicationContext context;
@Autowired(required = false)
private DiscoveryClientOptionalArgs optionalArgs;
@Autowired
private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
@ -216,8 +217,8 @@ public class EurekaClientAutoConfiguration { @@ -216,8 +217,8 @@ public class EurekaClientAutoConfiguration {
@Autowired
private ApplicationContext context;
@Autowired(required = false)
private DiscoveryClientOptionalArgs optionalArgs;
@Autowired
private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)

48
spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/config/DiscoveryClientOptionalArgsConfiguration.java

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
/*
* Copyright 2017 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.springframework.cloud.netflix.eureka.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.cloud.netflix.eureka.MutableDiscoveryClientOptionalArgs;
import org.springframework.cloud.netflix.eureka.http.RestTemplateDiscoveryClientOptionalArgs;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;
/**
* @author Daniel Lavoie
*/
@Configuration
public class DiscoveryClientOptionalArgsConfiguration {
@Bean
@ConditionalOnMissingClass("com.sun.jersey.api.client.filter.ClientFilter")
@ConditionalOnMissingBean(value = AbstractDiscoveryClientOptionalArgs.class, search = SearchStrategy.CURRENT)
public RestTemplateDiscoveryClientOptionalArgs restTemplateDiscoveryClientOptionalArgs() {
return new RestTemplateDiscoveryClientOptionalArgs();
}
@Bean
@ConditionalOnClass(name = "com.sun.jersey.api.client.filter.ClientFilter")
@ConditionalOnMissingBean(value = AbstractDiscoveryClientOptionalArgs.class, search = SearchStrategy.CURRENT)
public MutableDiscoveryClientOptionalArgs discoveryClientOptionalArgs() {
return new MutableDiscoveryClientOptionalArgs();
}
}

40
spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/EurekaApplications.java

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
/*
* Copyright 2017 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.springframework.cloud.netflix.eureka.http;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
/**
* A simple wrapper class for {@link Applications} that insure proprer Jackson
* serialization through the JsonPropert overwrites.
*
* @author Daniel Lavoie
*/
public class EurekaApplications extends com.netflix.discovery.shared.Applications {
@JsonCreator
public EurekaApplications(@JsonProperty("apps__hashcode") String appsHashCode,
@JsonProperty("versions__delta") Long versionDelta,
@JsonProperty("application") List<Application> registeredApplications) {
super(appsHashCode, versionDelta, registeredApplications);
}
}

29
spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/RestTemplateDiscoveryClientOptionalArgs.java

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
/*
* Copyright 2017 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.springframework.cloud.netflix.eureka.http;
import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;
/**
* @author Daniel Lavoie
*/
public class RestTemplateDiscoveryClientOptionalArgs
extends AbstractDiscoveryClientOptionalArgs<Void> {
public RestTemplateDiscoveryClientOptionalArgs() {
setTransportClientFactories(new RestTemplateTransportClientFactories());
}
}

227
spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/RestTemplateEurekaHttpClient.java

@ -0,0 +1,227 @@ @@ -0,0 +1,227 @@
/*
* Copyright 2017 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.springframework.cloud.netflix.eureka.http;
import static com.netflix.discovery.shared.transport.EurekaHttpResponse.anEurekaHttpResponse;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
import com.netflix.discovery.shared.transport.EurekaHttpClient;
import com.netflix.discovery.shared.transport.EurekaHttpResponse;
import com.netflix.discovery.shared.transport.EurekaHttpResponse.EurekaHttpResponseBuilder;
import com.netflix.discovery.util.StringUtil;
/**
* @author Daniel Lavoie
*/
public class RestTemplateEurekaHttpClient implements EurekaHttpClient {
protected final Log logger = LogFactory.getLog(getClass());
private RestTemplate restTemplate;
private String serviceUrl;
public RestTemplateEurekaHttpClient(RestTemplate restTemplate, String serviceUrl) {
this.restTemplate = restTemplate;
this.serviceUrl = serviceUrl;
}
@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
String urlPath = serviceUrl + "apps/" + info.getAppName();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT_ENCODING, "gzip");
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
ResponseEntity<Void> response = restTemplate.exchange(urlPath, HttpMethod.POST,
new HttpEntity<InstanceInfo>(info, headers), Void.class);
return anEurekaHttpResponse(response.getStatusCodeValue())
.headers(headersOf(response)).build();
}
@Override
public EurekaHttpResponse<Void> cancel(String appName, String id) {
String urlPath = serviceUrl + "apps/" + appName + '/' + id;
ResponseEntity<Void> response = restTemplate.exchange(urlPath, HttpMethod.DELETE,
null, Void.class);
return anEurekaHttpResponse(response.getStatusCodeValue())
.headers(headersOf(response)).build();
}
@Override
public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id,
InstanceInfo info, InstanceStatus overriddenStatus) {
String urlPath = serviceUrl + "apps/" + appName + '/' + id + "?status="
+ info.getStatus().toString() + "&lastDirtyTimestamp="
+ info.getLastDirtyTimestamp().toString() + (overriddenStatus != null
? "&overriddenstatus=" + overriddenStatus.name() : "");
ResponseEntity<InstanceInfo> response = restTemplate.exchange(urlPath,
HttpMethod.PUT, null, InstanceInfo.class);
EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(
response.getStatusCodeValue(), InstanceInfo.class)
.headers(headersOf(response));
if (response.hasBody())
eurekaResponseBuilder.entity(response.getBody());
return eurekaResponseBuilder.build();
}
@Override
public EurekaHttpResponse<Void> statusUpdate(String appName, String id,
InstanceStatus newStatus, InstanceInfo info) {
String urlPath = serviceUrl + "apps/" + appName + '/' + id + "?status="
+ newStatus.name() + "&lastDirtyTimestamp="
+ info.getLastDirtyTimestamp().toString();
ResponseEntity<Void> response = restTemplate.exchange(urlPath, HttpMethod.PUT,
null, Void.class);
return anEurekaHttpResponse(response.getStatusCodeValue())
.headers(headersOf(response)).build();
}
@Override
public EurekaHttpResponse<Void> deleteStatusOverride(String appName, String id,
InstanceInfo info) {
String urlPath = serviceUrl + "apps/" + appName + '/' + id
+ "/status?lastDirtyTimestamp=" + info.getLastDirtyTimestamp().toString();
ResponseEntity<Void> response = restTemplate.exchange(urlPath, HttpMethod.DELETE,
null, Void.class);
return anEurekaHttpResponse(response.getStatusCodeValue())
.headers(headersOf(response)).build();
}
@Override
public EurekaHttpResponse<Applications> getApplications(String... regions) {
return getApplicationsInternal("apps/", regions);
}
private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath,
String[] regions) {
String url = serviceUrl + urlPath;
if (regions != null && regions.length > 0)
urlPath = (urlPath.contains("?") ? "&" : "?") + "regions="
+ StringUtil.join(regions);
ResponseEntity<EurekaApplications> response = restTemplate.exchange(url,
HttpMethod.GET, null, EurekaApplications.class);
return anEurekaHttpResponse(response.getStatusCodeValue(),
response.getStatusCode().value() == HttpStatus.OK.value()
&& response.hasBody() ? (Applications) response.getBody() : null)
.headers(headersOf(response)).build();
}
@Override
public EurekaHttpResponse<Applications> getDelta(String... regions) {
return getApplicationsInternal("apps/delta", regions);
}
@Override
public EurekaHttpResponse<Applications> getVip(String vipAddress, String... regions) {
return getApplicationsInternal("vips/" + vipAddress, regions);
}
@Override
public EurekaHttpResponse<Applications> getSecureVip(String secureVipAddress,
String... regions) {
return getApplicationsInternal("svips/" + secureVipAddress, regions);
}
@Override
public EurekaHttpResponse<Application> getApplication(String appName) {
String urlPath = serviceUrl + "apps/" + appName;
ResponseEntity<Application> response = restTemplate.exchange(urlPath,
HttpMethod.GET, null, Application.class);
Application application = response.getStatusCodeValue() == HttpStatus.OK.value()
&& response.hasBody() ? response.getBody() : null;
return anEurekaHttpResponse(response.getStatusCodeValue(), application)
.headers(headersOf(response)).build();
}
@Override
public EurekaHttpResponse<InstanceInfo> getInstance(String appName, String id) {
return getInstanceInternal("apps/" + appName + '/' + id);
}
@Override
public EurekaHttpResponse<InstanceInfo> getInstance(String id) {
return getInstanceInternal("instances/" + id);
}
private EurekaHttpResponse<InstanceInfo> getInstanceInternal(String urlPath) {
urlPath = serviceUrl + urlPath;
ResponseEntity<InstanceInfo> response = restTemplate.exchange(urlPath,
HttpMethod.GET, null, InstanceInfo.class);
return anEurekaHttpResponse(response.getStatusCodeValue(),
response.getStatusCodeValue() == HttpStatus.OK.value()
&& response.hasBody() ? response.getBody() : null)
.headers(headersOf(response)).build();
}
@Override
public void shutdown() {
// Nothing to do
}
private static Map<String, String> headersOf(ResponseEntity<?> response) {
HttpHeaders httpHeaders = response.getHeaders();
if (httpHeaders == null || httpHeaders.isEmpty()) {
return Collections.emptyMap();
}
Map<String, String> headers = new HashMap<>();
for (Entry<String, List<String>> entry : httpHeaders.entrySet()) {
if (!entry.getValue().isEmpty()) {
headers.put(entry.getKey(), entry.getValue().get(0));
}
}
return headers;
}
}

46
spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/RestTemplateTransportClientFactories.java

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
/*
* Copyright 2017 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.springframework.cloud.netflix.eureka.http;
import java.util.Collection;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.shared.transport.TransportClientFactory;
import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClient;
import com.netflix.discovery.shared.transport.jersey.TransportClientFactories;
/**
* @author Daniel Lavoie
*/
public class RestTemplateTransportClientFactories
implements TransportClientFactories<Void> {
@Override
public TransportClientFactory newTransportClientFactory(
Collection<Void> additionalFilters, EurekaJerseyClient providedJerseyClient) {
throw new UnsupportedOperationException();
}
@Override
public TransportClientFactory newTransportClientFactory(
EurekaClientConfig clientConfig, Collection<Void> additionalFilters,
InstanceInfo myInstanceInfo) {
return new RestTemplateTransportClientFactory();
}
}

98
spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/RestTemplateTransportClientFactory.java

@ -0,0 +1,98 @@ @@ -0,0 +1,98 @@
/*
* Copyright 2017 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.springframework.cloud.netflix.eureka.http;
import java.net.URI;
import java.net.URISyntaxException;
import org.springframework.http.client.support.BasicAuthorizationInterceptor;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.netflix.discovery.shared.resolver.EurekaEndpoint;
import com.netflix.discovery.shared.transport.EurekaHttpClient;
import com.netflix.discovery.shared.transport.TransportClientFactory;
/**
* Provides the custom {@link RestTemplate} required by the
* {@link RestTemplateEurekaHttpClient}. Relies on Jackson for serialization and
* deserialization.
*
* @author Daniel Lavoie
*/
public class RestTemplateTransportClientFactory implements TransportClientFactory {
@Override
public EurekaHttpClient newClient(EurekaEndpoint serviceUrl) {
return new RestTemplateEurekaHttpClient(restTemplate(serviceUrl.getServiceUrl()),
serviceUrl.getServiceUrl());
}
private RestTemplate restTemplate(String serviceUrl) {
RestTemplate restTemplate = new RestTemplate();
try {
URI serviceURI = new URI(serviceUrl);
if (serviceURI.getUserInfo() != null) {
String[] credentials = serviceURI.getUserInfo().split(":");
if (credentials.length == 2) {
restTemplate.getInterceptors().add(new BasicAuthorizationInterceptor(
credentials[0], credentials[1]));
}
}
}
catch (URISyntaxException ignore) {
}
restTemplate.getMessageConverters().add(0, mappingJacksonHttpMessageConverter());
return restTemplate;
}
/**
* Provides the serialization configurations required by the Eureka Server. JSON
* content exchanged with eureka requires a root node matching the entity being
* serialized or deserialized. Achived with
* {@link SerializationFeature.WRAP_ROOT_VALUE} and
* {@link DeserializationFeature.UNWRAP_ROOT_VALUE}.
* {@link PropertyNamingStrategy.SnakeCaseStrategy} is applied to the underlying
* {@link ObjectMapper}.
*
*
* @return
*/
public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(new ObjectMapper()
.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE));
converter.getObjectMapper().configure(SerializationFeature.WRAP_ROOT_VALUE, true);
converter.getObjectMapper().configure(DeserializationFeature.UNWRAP_ROOT_VALUE,
true);
return converter;
}
@Override
public void shutdown() {
}
}

45
spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/config/JerseyOptionalArgsConfigurationTest.java

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
/*
* Copyright 2017 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.springframework.cloud.netflix.eureka.config;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.netflix.eureka.sample.EurekaSampleApplication;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.netflix.discovery.DiscoveryClient.DiscoveryClientOptionalArgs;
/**
* @author Daniel Lavoie
*/
@DirtiesContext
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = EurekaSampleApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class JerseyOptionalArgsConfigurationTest {
@Autowired
private DiscoveryClientOptionalArgs optionalArgs;
@Test
public void contextLoads() {
Assert.assertNotNull(optionalArgs);
}
}

46
spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/config/RestTemplateOptionalArgsConfigurationTest.java

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
/*
* Copyright 2017 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.springframework.cloud.netflix.eureka.config;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.ClassPathExclusions;
import org.springframework.cloud.FilteredClassPathRunner;
import org.springframework.cloud.netflix.eureka.http.RestTemplateDiscoveryClientOptionalArgs;
import org.springframework.cloud.netflix.eureka.sample.EurekaSampleApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* @author Daniel Lavoie
*/
@RunWith(FilteredClassPathRunner.class)
@ClassPathExclusions({ "jersey-client-*", "jersey-core-*", "jersey-apache-client4-*" })
@SpringBootTest(classes = EurekaSampleApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class RestTemplateOptionalArgsConfigurationTest {
@Test
public void contextLoads() {
try (ConfigurableApplicationContext context = new SpringApplicationBuilder()
.web(false).sources(EurekaSampleApplication.class).run()) {
Assert.assertNotNull(
context.getBean(RestTemplateDiscoveryClientOptionalArgs.class));
}
}
}

117
spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/http/EurekaServerMockApplication.java

@ -0,0 +1,117 @@ @@ -0,0 +1,117 @@
/*
* Copyright 2017 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.springframework.cloud.netflix.eureka.http;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
/**
* Mocked Eureka Server
*
* @author Daniel Lavoie
*/
@Configuration
@RestController
@SpringBootApplication
public class EurekaServerMockApplication {
private static final InstanceInfo INFO = new InstanceInfo(null, null, null, null,
null, null, null, null, null, null, null, null, null, 0, null, null, null,
null, null, null, null, 0l, 0l, null, null);
/**
* Simulates Eureka Server own's serialization.
* @return
*/
@Bean
public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {
return new RestTemplateTransportClientFactory()
.mappingJacksonHttpMessageConverter();
}
@ResponseStatus(HttpStatus.OK)
@PostMapping("/apps/{appName}")
public void register(@PathVariable String appName,
@RequestBody InstanceInfo instanceInfo) {
// Nothing to do
}
@ResponseStatus(HttpStatus.OK)
@DeleteMapping("/apps/{appName}/{id}")
public void cancel(@PathVariable String appName, @PathVariable String id) {
}
@ResponseStatus(HttpStatus.OK)
@PutMapping(value = "/apps/{appName}/{id}", params = { "status",
"lastDirtyTimestamp" })
public InstanceInfo sendHeartBeat(@PathVariable String appName,
@PathVariable String id, @RequestParam String status,
@RequestParam String lastDirtyTimestamp,
@RequestParam(required = false) String overriddenstatus) {
return new InstanceInfo(null, null, null, null, null, null, null, null, null,
null, null, null, null, 0, null, null, null, null, null, null, null, 0l,
0l, null, null);
}
@ResponseStatus(HttpStatus.OK)
@PutMapping(value = "/apps/{appName}/{id}/status", params = { "value",
"lastDirtyTimestamp" })
public void statusUpdate(@PathVariable String appName, @PathVariable String id,
@RequestParam String value, @RequestParam String lastDirtyTimestamp) {
}
@ResponseStatus(HttpStatus.OK)
@DeleteMapping(value = "/apps/{appName}/{id}/status", params = "lastDirtyTimestamp")
public void deleteStatusOverride(@PathVariable String appName,
@PathVariable String id, @RequestParam String lastDirtyTimestamp) {
}
@GetMapping(value = { "/apps", "/apps/delta", "/vips/{address}", "/svips/{address}" })
public Applications getApplications(@PathVariable(required = false) String address,
@RequestParam(required = false) String regions) {
return new Applications();
}
@GetMapping(value = "/apps/{appName}")
public Application getApplication(@PathVariable String appName) {
return new Application();
}
@GetMapping(value = { "/apps/{appName}/{id}", "/instances/{id}" })
public InstanceInfo getInstance(@PathVariable(required = false) String appName,
@PathVariable String id) {
return INFO;
}
}

139
spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/http/RestTemplateEurekaHttpClientTest.java

@ -0,0 +1,139 @@ @@ -0,0 +1,139 @@
/*
* Copyright 2017 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.springframework.cloud.netflix.eureka.http;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
import org.springframework.http.HttpStatus;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.appinfo.providers.EurekaConfigBasedInstanceInfoProvider;
import com.netflix.discovery.shared.resolver.DefaultEndpoint;
import com.netflix.discovery.shared.transport.EurekaHttpClient;
/**
* @author Daniel Lavoie
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = EurekaServerMockApplication.class, properties = { "debug=true",
"security.basic.enabled=true" }, webEnvironment = WebEnvironment.RANDOM_PORT)
@DirtiesContext
public class RestTemplateEurekaHttpClientTest {
@Autowired
private InetUtils inetUtils;
@Value("http://${security.user.name}:${security.user.password}@localhost:${local.server.port}")
private String serviceUrl;
private EurekaHttpClient eurekaHttpClient;
private InstanceInfo info;
@Before
public void setup() {
eurekaHttpClient = new RestTemplateTransportClientFactory()
.newClient(new DefaultEndpoint(serviceUrl));
EurekaInstanceConfigBean config = new EurekaInstanceConfigBean(inetUtils);
String appname = "customapp";
config.setIpAddress("127.0.0.1");
config.setHostname("localhost");
config.setAppname(appname);
config.setVirtualHostName(appname);
config.setSecureVirtualHostName(appname);
config.setNonSecurePort(4444);
config.setInstanceId("127.0.0.1:customapp:4444");
info = new EurekaConfigBasedInstanceInfoProvider(config).get();
}
@Test
public void testRegister() {
Assert.assertEquals(HttpStatus.OK.value(),
eurekaHttpClient.register(info).getStatusCode());
}
@Test
public void testCancel() {
Assert.assertEquals(HttpStatus.OK.value(),
eurekaHttpClient.cancel("test", "test").getStatusCode());
}
@Test
public void testSendHeartBeat() {
Assert.assertEquals(HttpStatus.OK.value(), eurekaHttpClient
.sendHeartBeat("test", "test", info, null).getStatusCode());
}
@Test
public void testStatusUpdate() {
Assert.assertEquals(HttpStatus.OK.value(), eurekaHttpClient
.statusUpdate("test", "test", InstanceStatus.UP, info).getStatusCode());
}
@Test
public void testDeleteStatusOverride() {
Assert.assertEquals(HttpStatus.OK.value(), eurekaHttpClient
.deleteStatusOverride("test", "test", info).getStatusCode());
}
@Test
public void testGetApplications() {
Assert.assertNotNull(eurekaHttpClient.getApplications().getEntity());
Assert.assertNotNull(eurekaHttpClient.getApplications("us", "eu").getEntity());
}
@Test
public void testGetDelta() {
eurekaHttpClient.getDelta().getEntity();
eurekaHttpClient.getDelta("us", "eu").getEntity();
}
@Test
public void testGetVips() {
eurekaHttpClient.getVip("test");
eurekaHttpClient.getVip("test", "us", "eu");
}
@Test
public void testGetSecureVip() {
eurekaHttpClient.getSecureVip("test");
eurekaHttpClient.getSecureVip("test", "us", "eu");
}
@Test
public void testGetApplication() {
eurekaHttpClient.getApplication("test");
}
@Test
public void testGetInstance() {
eurekaHttpClient.getInstance("test");
eurekaHttpClient.getInstance("test", "test");
}
}

29
spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/http/RestTemplateTransportClientFactoriesTest.java

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
/*
* Copyright 2017 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.springframework.cloud.netflix.eureka.http;
import org.junit.Test;
/**
* @author Daniel Lavoie
*/
public class RestTemplateTransportClientFactoriesTest {
@Test(expected = UnsupportedOperationException.class)
public void testJerseyIsUnsuported() {
new RestTemplateTransportClientFactories().newTransportClientFactory(null, null);
}
}

57
spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/http/RestTemplateTransportClientFactoryTest.java

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
/*
* Copyright 2017 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.springframework.cloud.netflix.eureka.http;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.netflix.discovery.shared.resolver.DefaultEndpoint;
/**
* @author Daniel Lavoie
*/
public class RestTemplateTransportClientFactoryTest {
private RestTemplateTransportClientFactory transportClientFatory;
@Before
public void setup() {
transportClientFatory = new RestTemplateTransportClientFactory();
}
@Test
public void testWithoutUserInfo() {
transportClientFatory.newClient(new DefaultEndpoint("http://localhost:8761"));
}
@Test
public void testInvalidUserInfo() {
transportClientFatory
.newClient(new DefaultEndpoint("http://test@localhost:8761"));
}
@Test
public void testUserInfo() {
transportClientFatory
.newClient(new DefaultEndpoint("http://test:test@localhost:8761"));
}
@After
public void shutdown() {
transportClientFatory.shutdown();
}
}

9
spring-cloud-netflix-eureka-client/src/test/resources/application.yml

@ -2,4 +2,11 @@ @@ -2,4 +2,11 @@
foo3:
ribbon:
NFLoadBalancerPingClassName: com.netflix.loadbalancer.DummyPing
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
security:
basic:
enabled: false
user:
name: test
password: test
Loading…
Cancel
Save