diff --git a/pom.xml b/pom.xml index bf225d82f0..890ab23ee6 100644 --- a/pom.xml +++ b/pom.xml @@ -581,6 +581,7 @@ spring-5-reactive-client spring-5-reactive-oauth spring-5-reactive-security + spring-5-reactive-netty spring-5-security spring-5-security-oauth diff --git a/spring-5-reactive-netty/.gitignore b/spring-5-reactive-netty/.gitignore new file mode 100644 index 0000000000..70ed41e73a --- /dev/null +++ b/spring-5-reactive-netty/.gitignore @@ -0,0 +1,11 @@ +# Folders # +**/.idea +**/target + +# Files # +*.log + +# Packaged files # +*.jar +*.war +*.ear \ No newline at end of file diff --git a/spring-5-reactive-netty/README.md b/spring-5-reactive-netty/README.md new file mode 100644 index 0000000000..09f7cc0e24 --- /dev/null +++ b/spring-5-reactive-netty/README.md @@ -0,0 +1,3 @@ +## Spring 5 Reactive Project With Netty Server + +Includes configuration options for Netty server. diff --git a/spring-5-reactive-netty/pom.xml b/spring-5-reactive-netty/pom.xml new file mode 100644 index 0000000000..48fc0b201f --- /dev/null +++ b/spring-5-reactive-netty/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + com.baeldung + spring-5-reactive-netty + 0.0.1-SNAPSHOT + spring-5-reactive-netty + jar + Spring 5 sample project about reactive web with Netty server + + + com.baeldung + parent-boot-2 + 0.0.1-SNAPSHOT + ../parent-boot-2 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.projectlombok + lombok + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/CustomNettyWebServerFactory.java b/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/CustomNettyWebServerFactory.java new file mode 100644 index 0000000000..8a1cdbba97 --- /dev/null +++ b/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/CustomNettyWebServerFactory.java @@ -0,0 +1,36 @@ +package com.baeldung.serverconfig; + +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; +import org.springframework.boot.web.embedded.netty.NettyServerCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import reactor.netty.http.server.HttpServer; + +@Configuration +@Profile("skipAutoConfig") +public class CustomNettyWebServerFactory { + + @Bean + public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() { + NettyReactiveWebServerFactory webServerFactory = new NettyReactiveWebServerFactory(); + webServerFactory.addServerCustomizers(new EventLoopNettyCustomizer()); + return webServerFactory; + } + + private static class EventLoopNettyCustomizer implements NettyServerCustomizer { + + @Override + public HttpServer apply(HttpServer httpServer) { + EventLoopGroup parentGroup = new NioEventLoopGroup(); + EventLoopGroup childGroup = new NioEventLoopGroup(); + return httpServer + .tcpConfiguration(tcpServer -> tcpServer.bootstrap( + serverBootstrap -> serverBootstrap.group(parentGroup, childGroup).channel(NioServerSocketChannel.class) + )); + } + } +} diff --git a/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/GreetingController.java b/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/GreetingController.java new file mode 100644 index 0000000000..9cb5b27ac5 --- /dev/null +++ b/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/GreetingController.java @@ -0,0 +1,23 @@ +package com.baeldung.serverconfig; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/greet") +public class GreetingController { + + private final GreetingService greetingService; + + public GreetingController(GreetingService greetingService) { + this.greetingService = greetingService; + } + + @GetMapping("/{name}") + private Mono greet(@PathVariable String name) { + return greetingService.greet(name); + } +} diff --git a/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/GreetingService.java b/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/GreetingService.java new file mode 100644 index 0000000000..5440f526aa --- /dev/null +++ b/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/GreetingService.java @@ -0,0 +1,12 @@ +package com.baeldung.serverconfig; + +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service +public class GreetingService { + + public Mono greet(String name) { + return Mono.just("Greeting " + name); + } +} diff --git a/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/NettyWebServerFactoryPortCustomizer.java b/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/NettyWebServerFactoryPortCustomizer.java new file mode 100644 index 0000000000..152e1285aa --- /dev/null +++ b/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/NettyWebServerFactoryPortCustomizer.java @@ -0,0 +1,30 @@ +package com.baeldung.serverconfig; + +import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; +import org.springframework.boot.web.embedded.netty.NettyServerCustomizer; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.stereotype.Component; +import reactor.netty.http.server.HttpServer; + +@Component +public class NettyWebServerFactoryPortCustomizer implements WebServerFactoryCustomizer { + + @Override + public void customize(NettyReactiveWebServerFactory serverFactory) { + serverFactory.addServerCustomizers(new PortCustomizer(8443)); + } + + private static class PortCustomizer implements NettyServerCustomizer { + + private final int port; + + private PortCustomizer(int port) { + this.port = port; + } + + @Override + public HttpServer apply(HttpServer httpServer) { + return httpServer.port(port); + } + } +} diff --git a/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/NettyWebServerFactorySslCustomizer.java b/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/NettyWebServerFactorySslCustomizer.java new file mode 100644 index 0000000000..d0ad0dcac5 --- /dev/null +++ b/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/NettyWebServerFactorySslCustomizer.java @@ -0,0 +1,26 @@ +package com.baeldung.serverconfig; + +import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; +import org.springframework.boot.web.embedded.netty.SslServerCustomizer; +import org.springframework.boot.web.server.Http2; +import org.springframework.boot.web.server.Ssl; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.stereotype.Component; + +@Component +public class NettyWebServerFactorySslCustomizer implements WebServerFactoryCustomizer { + + @Override + public void customize(NettyReactiveWebServerFactory serverFactory) { + Ssl ssl = new Ssl(); + ssl.setEnabled(true); + ssl.setKeyStore("classpath:sample.jks"); + ssl.setKeyAlias("alias"); + ssl.setKeyPassword("password"); + ssl.setKeyStorePassword("secret"); + Http2 http2 = new Http2(); + http2.setEnabled(false); + serverFactory.addServerCustomizers(new SslServerCustomizer(ssl, http2, null)); + serverFactory.setPort(8443); + } +} diff --git a/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/ServerConfigApplication.java b/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/ServerConfigApplication.java new file mode 100644 index 0000000000..9d420cc7da --- /dev/null +++ b/spring-5-reactive-netty/src/main/java/com/baeldung/serverconfig/ServerConfigApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.serverconfig; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ServerConfigApplication { + + public static void main(String[] args) { + SpringApplication.run(ServerConfigApplication.class, args); + } +} diff --git a/spring-5-reactive-netty/src/main/resources/logback.xml b/spring-5-reactive-netty/src/main/resources/logback.xml new file mode 100644 index 0000000000..48b68c6bf1 --- /dev/null +++ b/spring-5-reactive-netty/src/main/resources/logback.xml @@ -0,0 +1,31 @@ + + + + + + %black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{1.}): %msg%n%throwable + + + + + + netty-access.log + + %msg%n + + + + + + + + + + + + + + + + diff --git a/spring-5-reactive-netty/src/main/resources/sample.jks b/spring-5-reactive-netty/src/main/resources/sample.jks new file mode 100644 index 0000000000..6aa9a28053 Binary files /dev/null and b/spring-5-reactive-netty/src/main/resources/sample.jks differ diff --git a/spring-5-reactive-netty/src/test/java/com/baeldung/serverconfig/GreetingControllerIntegrationTest.java b/spring-5-reactive-netty/src/test/java/com/baeldung/serverconfig/GreetingControllerIntegrationTest.java new file mode 100644 index 0000000000..3c2c08321a --- /dev/null +++ b/spring-5-reactive-netty/src/test/java/com/baeldung/serverconfig/GreetingControllerIntegrationTest.java @@ -0,0 +1,41 @@ +package com.baeldung.serverconfig; + +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; +import reactor.core.publisher.Mono; + +@RunWith(SpringRunner.class) +@WebFluxTest +public class GreetingControllerIntegrationTest { + + @Autowired + private WebTestClient webClient; + + @MockBean + private GreetingService greetingService; + + private final String name = "Baeldung"; + + @Before + public void setUp() { + when(greetingService.greet(name)).thenReturn(Mono.just("Greeting Baeldung")); + } + + @Test + public void shouldGreet() { + webClient.get().uri("/greet/{name}", name) + .exchange() + .expectStatus() + .isOk() + .expectBody(String.class) + .isEqualTo("Greeting Baeldung"); + } +} diff --git a/spring-5-reactive-netty/src/test/java/com/baeldung/serverconfig/GreetingLiveTest.java b/spring-5-reactive-netty/src/test/java/com/baeldung/serverconfig/GreetingLiveTest.java new file mode 100644 index 0000000000..7c4a37c890 --- /dev/null +++ b/spring-5-reactive-netty/src/test/java/com/baeldung/serverconfig/GreetingLiveTest.java @@ -0,0 +1,56 @@ +package com.baeldung.serverconfig; + +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import javax.net.ssl.SSLException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec; +import reactor.netty.http.client.HttpClient; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) +public class GreetingLiveTest { + + private static final String BASE_URL = "https://localhost:8443"; + + private WebTestClient webTestClient; + + @Before + public void setup() throws SSLException { + webTestClient = WebTestClient.bindToServer(getConnector()) + .baseUrl(BASE_URL) + .build(); + } + + @Test + public void shouldGreet() { + final String name = "Baeldung"; + + ResponseSpec response = webTestClient.get() + .uri("/greet/{name}", name) + .exchange(); + + response.expectStatus() + .isOk() + .expectBody(String.class) + .isEqualTo("Greeting Baeldung"); + } + + private ReactorClientHttpConnector getConnector() throws SSLException { + SslContext sslContext = SslContextBuilder + .forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build(); + HttpClient httpClient = HttpClient.create().secure(t -> t.sslContext(sslContext)); + return new ReactorClientHttpConnector(httpClient); + } +} diff --git a/spring-5-reactive-netty/src/test/java/com/baeldung/serverconfig/GreetingSkipAutoConfigLiveTest.java b/spring-5-reactive-netty/src/test/java/com/baeldung/serverconfig/GreetingSkipAutoConfigLiveTest.java new file mode 100644 index 0000000000..646742b3d7 --- /dev/null +++ b/spring-5-reactive-netty/src/test/java/com/baeldung/serverconfig/GreetingSkipAutoConfigLiveTest.java @@ -0,0 +1,8 @@ +package com.baeldung.serverconfig; + +import org.springframework.test.context.ActiveProfiles; + +@ActiveProfiles("skipAutoConfig") +public class GreetingSkipAutoConfigLiveTest extends GreetingLiveTest { + +} diff --git a/spring-5-reactive-netty/src/test/resources/logback-test.xml b/spring-5-reactive-netty/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..12cedf5952 --- /dev/null +++ b/spring-5-reactive-netty/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + + %black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{1.}): %msg%n%throwable + + + + + + +