30

I want to establish a communication between a client and server application using Springs new reactive webflux extension.

For dependency management I use gradle. My build.gradle file on the server, as well as on the client side basically is:

buildscript {
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/snapshot" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.0.BUILD-SNAPSHOT")
    }
}

repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/snapshot" }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: "io.spring.dependency-management" 

dependencies {
    compile("org.springframework.boot:spring-boot-starter-webflux")
}

(It should be noted that 2.0.0.BUILD-SNAPSHOT is a moving target and the problem at hand may just vanish one day due to changes inside the dependency)

When I start the server side application everything starts up well, including the start of an embedded netty server.

But when start the client application also a netty server is started, causing a "java.net.BindException: Address already in use", because the clientside netty server listens on the same port as the serverside netty server.

My question is: Why is netty started on the client side in the first place and how can I prevent it?

According to the Spring-Boot Documentation Spring tries to determine if Web support is required and configures the Spring Application context accordingly.

And according to the Docs this can be overridden by a call to setWebEnvironment(false). My client startup code then looks like:

@SpringBootApplication(scanBasePackages = { "com.tatics.flux.main" })
public class Client {
    public static void main(String[] args) throws Exception {
        SpringApplication app = new SpringApplication(Client.class);
        app.setWebEnvironment(false);
        app.run(Client.class, args);

        WebClient webClient = WebClient.create();

        Mono<String> result = webClient
                .post()
                .uri("http://localhost:8080/fluxService")

                // This does not work any more: .body("Hallo")
                // and must be replaced by:
                .body(BodyInserters.fromObject("Hallo"))

                .accept(MediaType.TEXT_PLAIN)
                .exchange()
                .flatMap(response -> response.bodyToMono(String.class));
    }
}

Unfortunately netty is still started. Also I note that setWebEnvironment(false) is marked as deprecated.

Any help on how to prevent netty from starting but otherwise preserve all webflux-dependencies is appreciated.

Here is an excerpt from the auto-configuration Report:

=========================
AUTO-CONFIGURATION REPORT
=========================

Positive matches:
-----------------
...

ReactiveWebServerAutoConfiguration matched:
  - found ReactiveWebApplicationContext (OnWebApplicationCondition)

ReactiveWebServerAutoConfiguration#defaultReactiveWebServerCustomizer matched:
  - @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.web.reactive.DefaultReactiveWebServerCustomizer; SearchStrategy: all) did not find any beans (OnBeanCondition)

ReactiveWebServerConfiguration.ReactorNettyAutoConfiguration matched:
  - @ConditionalOnClass found required class 'reactor.ipc.netty.http.server.HttpServer'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
  - @ConditionalOnMissingBean (types: org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; SearchStrategy: all) did not find any beans (OnBeanCondition)

ReactorCoreAutoConfiguration matched:
  - @ConditionalOnClass found required classes 'reactor.core.publisher.Mono', 'reactor.core.publisher.Flux'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)

...

Negative matches:
-----------------
...
ReactiveWebServerConfiguration.JettyAutoConfiguration:
  Did not match:
     - @ConditionalOnClass did not find required class 'org.eclipse.jetty.server.Server' (OnClassCondition)

ReactiveWebServerConfiguration.TomcatAutoConfiguration:
  Did not match:
     - @ConditionalOnClass did not find required class 'org.apache.catalina.startup.Tomcat' (OnClassCondition)

ReactiveWebServerConfiguration.UndertowAutoConfiguration:
  Did not match:
     - @ConditionalOnClass did not find required class 'io.undertow.Undertow' (OnClassCondition)

...

ReactiveWebServerConfiguration.JettyAutoConfiguration:
  Did not match:
     - @ConditionalOnClass did not find required class 'org.eclipse.jetty.server.Server' (OnClassCondition)

ReactiveWebServerConfiguration.TomcatAutoConfiguration:
  Did not match:
     - @ConditionalOnClass did not find required class 'org.apache.catalina.startup.Tomcat' (OnClassCondition)

ReactiveWebServerConfiguration.UndertowAutoConfiguration:
  Did not match:
     - @ConditionalOnClass did not find required class 'io.undertow.Undertow' (OnClassCondition)
Frank Kaiser
  • 461
  • 1
  • 5
  • 6

2 Answers2

32

Addition to @Brian_Clozel answer:

You can disable Netty (or any other server) by specifying inside an application.yml:

spring.main.web-application-type: none

or application.properties:

spring.main.web-application-type=none
Yan Pak
  • 1,767
  • 2
  • 19
  • 15
14

The main issue with your code is that you're currently creating a SpringApplication, then you customize it - to finally drop everything and run the static method run(Object primarySource, String... args).

The following should work:

@SpringBootApplication
public class Client {

    public static void main(String[] args) throws Exception {
        SpringApplication app = new SpringApplication(Client.class);
        app.setWebApplicationType(WebApplicationType.NONE);
        app.run(args);
    }

    @Bean
    public CommandLineRunner myCommandLineRunner() {
      return args -> {
        // we have to block here, since command line runners don't
        // consume reactive types and simply return after the execution
        String result = WebClient.create("http://localhost:8080")
                .post()
                .uri("/fluxService")
                .body("Hallo")
                .accept(MediaType.TEXT_PLAIN)
                .retrieve()
                .bodyToMono(String.class)
                .block();
        // print the result?
      };
    }
}

If not, please run your application using the --debug flag and add to your question the relevant parts of the auto-configuration report, especially the auto-configurations dealing with servers.

Brian Clozel
  • 56,583
  • 15
  • 167
  • 176
  • Hello Brian, I exchanged app.setWebEnvironment(false) for app.setWebApplicationType(WebApplicationType.NONE); Unfortunately this did not prevent netty startup and subsequent "address already in use" error. Where could I have gotten information about the setWebApplicationType(WebApplicationType.NONE) parameter? – Frank Kaiser Apr 28 '17 at 08:53
  • Hi! You could get that information from your IDE or the javadoc, since `setWebEnvironment` is now deprecated as of 2.0. Please share your auto-configuration report so we can figure out why your server is starting. – Brian Clozel Apr 28 '17 at 09:03
  • 1
    I added an excerpt of the auto-configuration-report to my post – Frank Kaiser Apr 28 '17 at 09:33
  • I've edited my answer to reflect where the problem comes from; i.e. don't call the static `run` method if you want to customize the `SpringApplication` instance. – Brian Clozel May 02 '17 at 07:26
  • Brian, you were perfectly right and my problem is **solved**. I just want to add that the call to `body("Hallo")` does not work any more due to changes of the build-snapshot. This call must be replaced by `.body(BodyInserters.fromObject("Hallo"))`. I'll edit that change that in my query. – Frank Kaiser May 02 '17 at 11:53
  • Ah yes, thanks. We had to change a few things in that area to avoid confusion between providing a Publisher and an actual object. Thanks for updating your question. – Brian Clozel May 02 '17 at 12:41
  • @FrankKaiser For client, there is no need to use Spring Boot's `SpringApplication`, just create a plain application class(with main method) and use `WebClient` to call server APIs. – Hantsy Aug 23 '17 at 02:38
  • 2
    Here is the spring documentation about how to [disable the internal web server](https://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-web-servers.html#howto-disable-web-server). In most cases if there is a application property, there is also a option to set it in your Java code, just as @BrianClozel showed it. – Tobske Jun 21 '18 at 06:56