eureka unknownHostException in service discovery - java

I've two microservices,
eureka-client-1 running on localhost:8081
eureka-client-2 running on localhost:8082
Both these are DiscoveryClients registered with 'eureka-server' running on localhost:8761.
In code snippet below, I'm trying to call eureka-client-2 from eureka-client-1. Instead of calling http://localhost:8082, i want to call http://eureka-client-2 but this throws java.net.UnknownHostException during Eureka service discovery.
After searching, I found that i need to use "Brixton" to get it done.
Is there a way to do it with Camden.SR3 ?
Please suggest.
#Component
public class HystrixDemoService {
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
#HystrixCommand(fallbackMethod = "getFallbackCustomerName")
public String getCustomerName() {
RestTemplate restTemplate = new RestTemplate();
URI uri = URI.create("http://eureka-client-2"); // fails here
return restTemplate.getForObject(uri, String.class);
}
public String getFallbackCustomerName() {
System.out.println("coming inside fallback method");
return "Resillient Customer";
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo-pranay-eureka-client1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo-pranay-eureka-client1</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties for client 1, similar for client 2(just change in name i.e. eureka-client-2)
spring.application.name=eureka-client-1
server.port=8081
eureka:
client:
registerWithEureka: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
leaseRenewalIntervalInSeconds: 10
statusPageUrlPath: /info
healthCheckUrlPath: /health
application.properties for eureka server
spring.application.name=eureka-service
server.port=8761
eureka:
client:
registerWithEureka: false
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
leaseRenewalIntervalInSeconds: 10
statusPageUrlPath: /info
healthCheckUrlPath: /health

For me the only thing that I need to do to fix the issue was to add the spring annotation #LoadBalanced to
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}

The solution is that you miss #LoadBalanced.
if you use Spring Boot or Sprint Cloud, I prefer to use the RestTemplateBuilder builder
#Bean
#LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
If you use spring only, I suggest you to new RestTemplate()
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}

As commented earlier I believe you might be missing Ribbon configuration in eureka-client-1.
First, I would move:
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
to a configuration class.
Add Ribbon configuration to application.yml, something like:
the-eureka-client-2:
ribbon:
# Eureka vipAddress of the target service
DeploymentContextBasedVipAddresses: eureka-client-2
#listOfServers: localhost:${SERVER.PORT}
NIWSServerListClassName: com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
# Interval to refresh the server list from the source (ms)
ServerListRefreshInterval: 30000
Inject restTemplate in HystrixDemoService class instead of instantiating a new one for every request. RestTemplate is thread-safe:
#Component
public class HystrixDemoService {
#Autowired
public RestTemplate restTemplate;
...
#HystrixCommand(fallbackMethod = "getFallbackCustomerName")
public String getCustomerName() {
URI uri = URI.create("http://eureka-client-2");
return this.restTemplate.getForObject(uri, String.class);
}
where the-eureka-client-2 is a key that maps to the registered service with name: eureka-client-2
I blogged about Microservices Registration and Discovery using Spring Cloud Eureka, Ribbon and Feign which also includes source code of the Discovery server and two clients developed using Jersey 1 and Spring MVC.

Changes below worked for me.
#SpringBootApplication
public class DemoPranayEurekaClient1Application {
public static void main(String[] args) {
SpringApplication.run(DemoPranayEurekaClient1Application.class, args);
}
}
#EnableDiscoveryClient
#EnableCircuitBreaker
#RestController
class HystrixDemoApplication {
#Autowired
HystrixDemoService hystrixDemoService;
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
#RequestMapping("/")
public String name() {
String str = hystrixDemoService.getCustomerName();
return "I'm A talking to "+str;
}
}
Below is the line of code that is used for choosing instance of eureka-client-2...
ServiceInstance instance = loadBalancer.choose("eureka-client-2");
#Component
public class HystrixDemoService {
#Autowired
private LoadBalancerClient loadBalancer;
#HystrixCommand(fallbackMethod = "getFallbackCustomerName")
public String getCustomerName() {
RestTemplate restTemplate = new RestTemplate();
ServiceInstance instance = loadBalancer.choose("eureka-client-2");
URI uri = instance.getUri();
return restTemplate.getForObject(uri, String.class);
}
public String getFallbackCustomerName() {
System.out.println("coming inside fallback method");
return "Resillient Customer";
}
}

Related

spring security hasAuthority("SCOPE_xxx") method not working with spring authorization server version 0.2.0

I have created an authorization server using the new spring authorization server module. I am able to get the token successfully but when I try to use the token against a protected endpoint with hasAuthority() I get forbidden 403 error. Below my pom.xml file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.erycoking</groupId>
<artifactId>auth-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>auth-service</name>
<description>Auth Service</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.0</spring-cloud.version>
<jhipster-dependencies.version>7.0.1</jhipster-dependencies.version>
<liquibase.version>4.6.1</liquibase.version>
</properties>
<dependencies>
<dependency>
<groupId>tech.jhipster</groupId>
<artifactId>jhipster-framework</artifactId>
<version>7.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<!-- Jackson Configurations -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hppc</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>problem-spring-web</artifactId>
<version>0.26.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<!-- Inherited version from Spring Boot can't be used because of regressions -->
<version>${liquibase.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>${liquibase.version}</version>
</plugin>
</plugins>
</build>
</project>
Below is my Authorization Server config
#Configuration(proxyBeanMethods = false)
public class AuthServerConfig {
private final DataSource dataSource;
private final AuthProperties authProps;
private final TokenSettings tokenSettings;
public AuthServerConfig(DataSource dataSource, AuthProperties authProps, TokenSettings tokenSettings) {
this.dataSource = dataSource;
this.authProps = authProps;
this.tokenSettings = tokenSettings;
}
#Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
JdbcRegisteredClientRepository clientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
clientRepository.save(webClient());
return clientRepository;
}
private RegisteredClient webClient() {
return RegisteredClient.withId("98a9104c-a9c7-4d7c-ad03-ec61bcfeab36")
.clientId(authProps.getClientId())
.clientName(authProps.getClientName())
.clientSecret(authProps.getClientSecret())
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/authorized")
.scope("create").scope("read").scope("write").scope("update").scope("delete")
.tokenSettings(tokenSettings)
.build();
}
#Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}
#Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
#Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
#Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer(authProps.getIssuerUri())
.build();
}
}
And this is my Security Config
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig {
private final UserDetailsService userDetailsService;
public SecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/management/**").permitAll()
.antMatchers("/h2-console/**").permitAll()
//this does not work
.antMatchers(HttpMethod.POST, "/auth/user").hasAuthority(AuthoritiesConstants.ADMIN)
//this does not work
.antMatchers(HttpMethod.GET, "/auth/user").hasAuthority("SCOPE_read")
.anyRequest().authenticated()
.and()
.csrf().disable()
.headers().frameOptions().disable()
.and()
.formLogin(withDefaults())
.userDetailsService(userDetailsService);
return http.build();
}
#Bean
public PasswordEncoder delegatingPasswordEncoder() {
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("bcrypt", new BCryptPasswordEncoder());
DelegatingPasswordEncoder passwordEncoder = new DelegatingPasswordEncoder("bcrypt", encoders);
passwordEncoder.setDefaultPasswordEncoderForMatches(new BCryptPasswordEncoder());
return passwordEncoder;
}
#Bean
public TokenSettings tokenSettings() {
return TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofMinutes(1))
.refreshTokenTimeToLive(Duration.ofHours(24))
.build();
}
}
Here is my user detail service
#Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
return userRepository.findOneWithRolesByEmailIgnoreCase(login)
.map(user -> createSpringSecurityUser(login, user))
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) {
if (!user.isActivated()) {
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
user.getRoles().forEach(e -> {
grantedAuthorities.add(new SimpleGrantedAuthority(e.getName()));
e.getPermissions().forEach(p -> grantedAuthorities.add(new SimpleGrantedAuthority(p.getName())));
});
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), grantedAuthorities);
}
}
when using the token to make a request to an endpoint that requires authentication alone it succeeds but when I try using it on an endpoint that requires a role it fails.
What could be the issue?
Based on a brief discussion in comments to clarify your setup, it seems that you are configuring your authorization server as a regular secured application with the intention of using scopes as authorities. However, this is an incorrect usage of the authorization server.
Note: I have not seen any reference to an OAuth 2.0 client or resource server in your description, so I'm assuming you are trying to hit endpoints directly on the authorization server. If that's not the case, let me know.
There are three applications involved in an OAuth 2.0 protected setup:
Authorization server
Resource server
Client
Your configuration is only for #1 (as far as I can tell). The authorization server contains two filter chains and additionally a configuration for a single oauth client. The two filter chains do the following:
Secure endpoints provided by the authorization server framework
Secure the login endpoint(s) the user will interact with prior to using the authorization endpoint (/oauth2/authorize) to obtain an authorization code, which the client will later use to obtain an access token
The scopes you have configured would allow a user (resource owner) to grant an oauth client the ability to make a protected call to a resource server using an access token. Only when the client makes a call to a resource server will your configured scopes be used. When the user directly interacts with an endpoint on the authorization server using a browser, the configuration for form login is in play, which as I mentioned in comments, uses roles from your database.
See the SpringOne 2021 repository and presentation to understand how to take an application from an unsecured application to a secured one, and then see how we turn it into a resource server, which uses scopes as authorities.
The presentation demonstrates all three applications, though the focus is on the resource server, which matches closely what you are trying to accomplish with scopes as authorities.

Spring Cloud config client not picking endpoints from Config server

Eureka configuration is pulled perfectly from the config server but the endpoints are not pulled from config-server.
Please let me know if I am missing something.
#Service
#RefreshScope
public class OrderService {
#Autowired
private OrderRepo orderRepo;
#Autowired
#Lazy
private RestTemplate restTemplate;
#Value("${payment.endpoints}")
private String DO_PAYMENT_URL;
public TransactionResponse saveOrder(TransactionRequest request) {
System.err.println(request);
TransactionResponse response = new TransactionResponse();
Order order = request.getOrder();
Payment payment = request.getPayment();
payment.setOrderId(order.getId());
response.setOrder(orderRepo.save(order));
// rest call
Payment paymentResponse =
// restTemplate.postForObject("http://localhost:8083/payment/doPayment",payment,
// Payment.class);
// restTemplate.postForObject("http://PAYMENT-SERVICE/payment/doPayment",payment,
// Payment.class);
restTemplate.postForObject(DO_PAYMENT_URL + "/doPayment", payment, Payment.class);
response.setMessage(
paymentResponse.getStatus().equalsIgnoreCase("success") ? "payment successful" : "payment failed");
response.setTransactionId(paymentResponse.getTransactionId());
return response;
}
}
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.hk</groupId>
<artifactId>order-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>order-service</name>
<description>order service</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR11</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
git-config-file :
https://gitlab.com/nickybesra/microservice-cloud-config/-/blob/main/application.yml
ORDER-SERVICE bootstrap.yml:
spring:
cloud:
config:
uri:
- http://localhost:9196
microservice code :
https://gitlab.com/nickybesra/microservice-2021

Feign : file upload configuration

I found a solution to upload a file with Feign but my configuration creates another problem. I have several Feign clients for differents services used from a client sevice.
Now when I submit a simple form I get this error :
feign.codec.EncodeException: class java.util.ArrayList is not a type
supported by this encoder
Before I had this configuration that worked perfectly but not anymore, I don't know why.
#Component
#RestController
#FeignClient(name = "zuul-server"
,configuration = {ProxyImages.MultipartSupportConfig.class})
#RibbonClient(name = "oauth2-server")
public interface ProxyImages {
class MultipartSupportConfig {
#Bean
#Primary
#Scope("prototype")
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
}
#RequestMapping(value = {"oauth2-server/auth/user/avatar"}, consumes = {"multipart/form-data"})
ResponseEntity<String> saveUserAvatar(#RequestPart(name = "file") MultipartFile file);$
}
I've no idea why the MultipartSupportConfig class is not taken into account any more.
So I added a #configuration annotation to the MultipartSupportConfig class
#Configuration
class MultipartSupportConfig
and it works again, but now it seems that this encoder is always used and I'd like to use it only for that proxy. How can I do that ?
Here is the dependencies I use
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.M8</spring-cloud.version>
<joinfaces.version>3.2.4</joinfaces.version>
</properties>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.3.0</version>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Thanks.
Problem fixed.
#RestController
#FeignClient(name = "zuul-server")
public interface ProxyImages {
#Configuration
class MultipartSupportConfig {
#Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
#Bean
#Primary
#Scope("prototype")
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
#RequestMapping(value = {"oauth2-server/auth/user/avatar"}, consumes = {"multipart/form-data"})
ResponseEntity<String> saveUserAvatar(#RequestPart(name = "file") MultipartFile file);
}

Spring Boot with AclPermissionEvaluator resulting in IllegalStateException: No ServletContext set

Hello experts,
I'm currently learning Spring Boot and I want to use it with Spring Security ACL.
Following the documentation of Spring Security and a tutorial on Baeldung.com, I think I got an understanding about what is needed. I also looked into the DMS example of Spring. I stumbled across another example by searching for a solution.
Based on this information, I have build my application. For reference, you can find the current application on GitHub.
The current issue
When I start the application, I'm getting a java.lang.IllegalStateException: No ServletContext set thrown. As far as I understand, this is due to the fact that during Spring Boot's auto-configure magic, my #Configuration annotated classes are initialized before the ServletContext is initialized.
Link to full stack trace on pastebin.
What I have done so far
Based on my research (mostly on StackOverflow) and my current understanding of the issue, it should help to put the responsible Beans into an own #Configuration annotated class. Unfortunately, here I'm currently lost.
Related questions led me to this thinking (see this question, or this one.)
My environment
macOS 10.12.6
jdk1.8.0_144
IntelliJ IDEA 2017.3.3 (Ultimate Edition)
Spring Boot v1.5.9.RELEASE
Relevant project files
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.moritzrupp</groupId>
<artifactId>traderdiary</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Trader Diary</name>
<description>Trader Diary is an easy to use web application to create a journal
about your trades with great reporting on top of it.</description>
<licenses>
<license>
<name>GNU General Public License (GPL) v3.0</name>
<url>https://www.gnu.org/licenses/gpl-3.0.txt</url>
</license>
</licenses>
<developers>
<developer>
<id>moritzrupp</id>
<name>Moritz Rupp</name>
<email>moritz.rupp#gmail.com</email>
<url>https://www.moritzrupp.de</url>
<timezone>DE</timezone>
</developer>
</developers>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
application.properties
# --------------------------------------------
# Datasource Properties
# --------------------------------------------
spring.h2.console.enabled=true
spring.h2.console.path=/h2
spring.datasource.url=jdbc:h2:mem:trader-diary-h2-db
spring.datasource.platform=h2
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
TraderDiaryApplication.java
#SpringBootApplication
public class TraderDiaryApplication {
public static void main(String[] args) {
SpringApplication.run(TraderDiaryApplication.class, args);
}
}
DataSourceConfig.java
#Configuration
public class DataSourceConfig {
#Bean
public DataSource traderDiaryDataSource(DataSourceProperties
dataSourceProperties) {
return dataSourceProperties.initializeDataSourceBuilder().build();
}
}
WebSecurityConfig.java
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("traderDiaryDataSource")
private DataSource dataSource;
#Override
protected void configure(HttpSecurity http) throws Exception {
// I think it is not relevant for the issue, see GitHub repo
...
}
#Bean
public PasswordEncoder passwordEncoder() {
// I think it is not relevant for the issue, see GitHub repo
...
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider(...) {
// I think it is not relevant for the issue, see GitHub repo
...
}
#Bean
public MethodSecurityExpressionHandler aclExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
AclPermissionCacheOptimizer permissionCacheOptimizer =
new AclPermissionCacheOptimizer(aclService());
expressionHandler.setPermissionEvaluator(permissionEvaluator());
expressionHandler
.setPermissionCacheOptimizer(permissionCacheOptimizer);
return expressionHandler;
}
#Bean
public PermissionEvaluator permissionEvaluator() {
return new AclPermissionEvaluator(aclService());
}
#Bean
public JdbcMutableAclService aclService() {
return new JdbcMutableAclService(dataSource, lookupStrategy(),
aclCache());
}
#Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(),
aclAuthorizationStrategy(), new ConsoleAuditLogger());
}
#Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(),
permissionGrantingStrategy(), aclAuthorizationStrategy());
}
#Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
#Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
#Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(new
ConsoleAuditLogger());
}
#Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_ADMIN"));
}
/* This is due to an earlier issue: DataSource required */
#Configuration
protected static class AclMethodSecurityConfig extends
GlobalMethodSecurityConfiguration {
#Autowired
#Qualifier("daoAuthenticationProvider")
private AuthenticationProvider authenticationProvider;
#Autowired
#Qualifier("aclExpressionHandler")
private MethodSecurityExpressionHandler aclExpressionHandler;
#Autowired
public void configureAuthManager(AuthenticationManagerBuilder
authenticationManagerBuilder) {
authenticationManagerBuilder
.authenticationProvider(authenticationProvider);
}
#Override
protected MethodSecurityExpressionHandler
createExpressionHandler() {
return aclExpressionHandler;
}
}
}
Thanks a lot for all input! If any further information is required, I will happily provide it.
Thanks and best regards
Moritz
Hey all,
I managed to resolve the issue myself. I did a step-by-step approach with commenting/uncommenting all the MethodSecurity related stuff.
I pin-pointed it down to the creation of the DefaultMethodSecurityExpressionHandler. This was causing the IllegalStateException: No ServletContext set.
Then, I have created a new Class MethodSecurityConfig.java and I put all the related code there. Now the application is starting again and I can continue with the developed.
#Rakesh: Thanks for your input!
MethodSecurityConfig.java
#Configuration
public class MethodSecurityConfig {
private DataSource dataSource;
#Autowired
#Qualifier("traderDiaryDataSource")
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
#Bean
public MethodSecurityExpressionHandler aclExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
AclPermissionCacheOptimizer permissionCacheOptimizer =
new AclPermissionCacheOptimizer(aclService());
expressionHandler.setPermissionEvaluator(permissionEvaluator());
expressionHandler
.setPermissionCacheOptimizer(permissionCacheOptimizer);
return expressionHandler;
}
#Bean
public PermissionEvaluator permissionEvaluator() {
return new AclPermissionEvaluator(aclService());
}
#Bean
public JdbcMutableAclService aclService() {
return new JdbcMutableAclService(dataSource,
lookupStrategy(), aclCache());
}
#Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(),
aclAuthorizationStrategy(), new ConsoleAuditLogger());
}
#Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(),
permissionGrantingStrategy(), aclAuthorizationStrategy());
}
#Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
#Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
#Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(
new ConsoleAuditLogger());
}
#Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_ADMIN"));
}
}
WebSecurityConfig.java
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
#Configuration
protected static class AclMethodSecurityConfig
extends GlobalMethodSecurityConfiguration {
private MethodSecurityExpressionHandler aclExpressionHandler;
#Autowired
#Qualifier("aclExpressionHandler")
public void setAclExpressionHandler(
MethodSecurityExpressionHandler aclExpressionHandler) {
this.aclExpressionHandler = aclExpressionHandler;
}
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return aclExpressionHandler;
}
}
}
Just to follow up on your own answer and my previous comment where I was also lost with this one (for over 2 days!). In the end it was also overriding the configure(AuthenticationManagerBuilder auth) method in a subclass of GlobalMethodSecurityConfiguration which was also annotated with #EnableGlobalMethodSecurity.
As I am new to Spring I am unfortunately unable to provide details as to why this is the way but if I put the configuring of the AuthenticationManagerBuilder in a class that extends WebSecurityConfigurereAdapter (like I originally had) then I always end up getting a IllegalStateException: No ServletContext set error.
Here is the final code I used:
#Configuration
#EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true )
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return defaultMethodSecurityExpressionHandler();
}
#Override
protected void configure( final AuthenticationManagerBuilder authenticationManagerBuilder ) throws Exception {
authenticationManagerBuilder.authenticationProvider( authenticationProvider() );
}
#Bean
public JdbcDaoImpl jdbcDao() {
JdbcDaoImpl jdbcDao = new JdbcDaoImpl();
jdbcDao.setDataSource( dataSource() );
return jdbcDao;
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService( jdbcDao() );
authenticationProvider.setPasswordEncoder( encoder() );
return authenticationProvider;
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder( 11 );
}
#Bean( name = "myDS")
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName( "com.mysql.cj.jdbc.Driver" );
dataSource.setUrl( "jdbc:mysql://localhost:3306/java_spring_angular_rest_security_acl2" );
dataSource.setUsername( "root" );
dataSource.setPassword( "alphax" );
return dataSource;
}
#Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), permissionGrantingStrategy(), aclAuthorizationStrategy());
}
#Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
#Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
#Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource(), aclCache(), aclAuthorizationStrategy(), permissionGrantingStrategy());
}
#Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
#Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
}
#Bean( name = "defaultMethodSecurityExpressionHandler")
public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService());
expressionHandler.setPermissionEvaluator(permissionEvaluator);
expressionHandler.setPermissionCacheOptimizer( new AclPermissionCacheOptimizer( aclService() ) );
return expressionHandler;
}
#Bean( name = "myAclService")
public JdbcMutableAclService aclService() {
JdbcMutableAclService jdbcMutableAclService = new JdbcMutableAclService(dataSource(), lookupStrategy(), aclCache());
jdbcMutableAclService.setClassIdentityQuery( "SELECT ##IDENTITY" );
jdbcMutableAclService.setSidIdentityQuery( "SELECT ##IDENTITY" );
return jdbcMutableAclService;
}
}
and here is the pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fireduptech.spring.rest</groupId>
<artifactId>hero</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>hero</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-acl -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache-core -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Hope that helps someone :)

Spring RestTemplate & AsyncRestTemplate with Netty4 hangs forever

Very simple setup:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo-rest-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo-rest-client</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.5.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>4.1.5.Final</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>9.3.1</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hystrix</artifactId>
<version>9.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
And a test case to demonstrate different usages of AsyncRestTemplate:
SampleTests.java
package com.example;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandProperties;
import feign.RequestLine;
import feign.hystrix.HystrixFeign;
import feign.hystrix.SetterFactory;
import org.junit.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.Netty4ClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.RestTemplate;
public class SampleTests {
private static final String URL = "https://api.github.com/users/octocat";
private static final int DEFAULT_SLEEP_MILLIS = 20;
private static final int DEFAULT_TIMEOUT = 10000;
#Test(timeout = DEFAULT_TIMEOUT)
public void syncRestNetty() throws Exception {
RestTemplate restTemplate = new RestTemplate(new Netty4ClientHttpRequestFactory());
ResponseEntity<String> response = restTemplate.getForEntity(URL, String.class);
System.out.println("response = " + response);
}
#Test(timeout = DEFAULT_TIMEOUT)
public void asyncRestNetty() throws Exception {
AsyncRestTemplate restTemplate = new AsyncRestTemplate(new Netty4ClientHttpRequestFactory());
ListenableFuture<ResponseEntity<String>> listenableFuture = restTemplate.getForEntity(URL, String.class);
listenableFuture.addCallback(result -> System.out.println("result = " + result), Throwable::printStackTrace);
while (!listenableFuture.isDone()) {
Thread.sleep(DEFAULT_SLEEP_MILLIS);
}
System.out.println("the end");
}
#Test
public void asyncRestOkHttp() throws Exception {
AsyncRestTemplate restTemplate = new AsyncRestTemplate(new OkHttp3ClientHttpRequestFactory());
ListenableFuture<ResponseEntity<String>> listenableFuture = restTemplate.getForEntity(URL, String.class);
listenableFuture.addCallback(result -> System.out.println("result = " + result), Throwable::printStackTrace);
while (!listenableFuture.isDone()) {
Thread.sleep(DEFAULT_SLEEP_MILLIS);
}
System.out.println("the end");
}
#Test
public void asyncRestHystrixFeign() throws Exception {
GitHub gitHub = HystrixFeign.builder()
.setterFactory((target, method) -> new SetterFactory.Default().create(target, method).andCommandPropertiesDefaults(HystrixCommandProperties.defaultSetter().withExecutionTimeoutInMilliseconds(10000)))
.target(GitHub.class, "https://api.github.com");
HystrixCommand<String> command = gitHub.octocatAsync();
command.toObservable().subscribe(result -> System.out.println("result = " + result), Throwable::printStackTrace);
while (!command.isExecutionComplete()) {
Thread.sleep(DEFAULT_SLEEP_MILLIS);
}
System.out.println("command.getExecutionTimeInMilliseconds() = " + command.getExecutionTimeInMilliseconds());
System.out.println("the end");
}
interface GitHub {
#RequestLine("GET /users/octocat")
HystrixCommand<String> octocatAsync();
}
}
When trying to run the tests which use Netty they just hang forever. (To see this please remove the JUnit timeout constraint). But if I run the exact same code with other clients everything works as expected.
I have tried different versions of Spring Boot and Netty but did not succeed. And from the logs everything looks ok.
What am I missing here?
EDIT:
Opened a ticket https://jira.spring.io/browse/SPR-14744 as suggested on Spring Gitter
EDIT-2:
Answer from Brian Clozel helped me find the issue which is related to Netty not realizing the server sent an empty response (a particular case with Github API and plain http) so I am marking it as accepted.
Can you try to configure your request factory with a Netty Sslcontext?
Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory();
nettyFactory.setSslContext(SslContextBuilder.forClient().build());
AsyncRestTemplate restTemplate = new AsyncRestTemplate(nettyFactory);
Without that context, the client is trying to send plaintext requests to the https endpoint; in that case, you're probably getting an HTTP 400 response.
In your example code, the throwable should be an instance of HttpClientErrorException, and you could get that information by logging the response status or its body with exception.getResponseBodyAsString().

Categories