Optionally remove Jersey dependencies from Eureka Client (#1850)

Fixes gh-1849
This commit is contained in:
Daniel Lavoie
2017-07-21 21:55:57 +02:00
committed by Spencer Gibb
parent fd783734ac
commit 348c96bf13
17 changed files with 982 additions and 13 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -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 {
public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config,
DiscoveryClientOptionalArgs args,
AbstractDiscoveryClientOptionalArgs<?> args,
ApplicationEventPublisher publisher) {
super(applicationInfoManager, config, args);
this.applicationInfoManager = applicationInfoManager;

View File

@@ -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
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;
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;
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
* @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 {
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 {
@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 {
@Autowired
private ApplicationContext context;
@Autowired(required = false)
private DiscoveryClientOptionalArgs optionalArgs;
@Autowired
private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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() {
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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