Category: webclient

  • Spring WebClient Failed to Resolve Example

    Have you ever encountered Spring’s WebClient throwing a “failed to resolve ‘hostname’ after x queries” error?. Well, if you ran my Crowsnest project prior to my latest merge, then chances are you may have. Especially, if you ran it inside your internal network. Here’s what the error looks like.

    Spring WebClient Failed to Resolve Error

    Spring WebClient Failed to Resolve Cause

    As the error states, the target hostname can’t be resolved. It is a DNS thing. By default WebClient uses the JVM implementation to resolve the hostname. In my case, my Linux box was inside a VPN and somehow Java’s standard networking library (e.g. java.net.InetAddress) was ignoring the local DNS settings. Worked fine when the target was accessible in the Internet.

    Spring WebClient Failed to Resolve Sorted

    To solve this dilemma, we simply change the default hostname resolver to something that can do the job. Luckily, there is Netty’s DNS resolver we can use. It is io.netty.resolver.DefaultAddressResolverGroup, and we just tell Spring to use it. So WebClient will now use Netty’s DNS Resovler thru HttpClient and no longer the default JVM implementation. We’ll need to change a couple of things in our code.

    Create a new WebClient that will be injected in other parts of the code.

    package net.codesamples.crowsnest;
    
    // ... other imports snipped
    import io.netty.resolver.DefaultAddressResolverGroup;
    import reactor.netty.http.client.HttpClient;
    
    @SpringBootApplication
    @EnableScheduling
    public class CrowsnestApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(CrowsnestApplication.class, args);
    	}
    
    	@Bean
    	public WebClient webClient() {
    		HttpClient httpClient = HttpClient.create().resolver(DefaultAddressResolverGroup.INSTANCE);
    		return WebClient.builder()
    				.clientConnector(new ReactorClientHttpConnector(httpClient)).build();
    	}
    }

    Use the bean in other parts of the code thru @Autowired.

    package net.codesamples.crowsnest;
    
    // ... impoerts snipped
    
    @Component
    public class ScheduledPings {
        private static final Logger log = LoggerFactory.getLogger(ScheduledPings.class);
    
        private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
    
        @Autowired
        EnvironmentConfigurationWatcher environmentConfigurationWatcher;
    
        @Autowired
        SimpMessagingTemplate simpMessagingTemplate;
    
        @Autowired
        private WebClient webClient;
    
        private List<Environment> internalEnvironmentList = new ArrayList<>();
    
        private ObjectMapper mapper = new ObjectMapper();
    
        @Scheduled(cron = "${cron.expression}")
        public void pinger() {
            log.info("The time is now {}", dateFormat.format(new Date()));
            List<Environment> environmentList = environmentConfigurationWatcher.getEnvironmentList();
            log.info("Environment list: {}", environmentList);
    
            for (Environment environment : environmentList) {
                for (App app : environment.getApps()) {
                    Mono<String> mono = webClient.get()
                            .uri(app.getUrl())
                            .exchangeToMono(clientResponse -> {
                                HttpStatusCode statusCode = clientResponse.statusCode();
                                if (statusCode.is2xxSuccessful()) {
                                    app.setStatus("up");
                                }
                                log.info("StatusCode: {} = {}", statusCode, app.getUrl());
                                return clientResponse.bodyToMono(String.class);
                            })
                            .onErrorResume(Exception.class, exception -> {
                                log.info("Exception {}", exception.getMessage());
                                app.setStatus("down");
                                return Mono.empty();
                            });
                    mono.subscribe();
                }
            }
    
    // ... code snipped
    }

    Spring WebClient Failed to Resolve Fixed

    Things should now work again. If it does not, let me know.

    I need more WebClients…