├── .github └── workflows │ └── build.yml ├── .gitignore ├── README.md ├── flux-mono ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── mynotes │ │ │ └── spring │ │ │ └── fluxmono │ │ │ └── FluxMonoApplication.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── mynotes │ └── spring │ ├── fluxmono │ ├── BackPressureTest.java │ ├── CombiningTest.java │ ├── ErrorTest.java │ ├── FilterTest.java │ ├── FluxAndMonoFactoryMethods.java │ ├── FluxMonoApplicationTest.java │ ├── InfiniteStream.java │ ├── MapTest.java │ ├── TestUtils.java │ └── VirtualTimeTest.java │ └── reactor │ └── ThreadLocalReactive.java ├── gateway ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── gateway │ │ │ ├── GatewayApplication.java │ │ │ └── configuration │ │ │ └── SecurityConfiguration.java │ └── resources │ │ └── application.properties │ └── test │ ├── java │ └── com │ │ └── example │ │ └── gateway │ │ └── configuration │ │ └── SecurityConfigurationIT.java │ └── resources │ └── application.yml ├── pom.xml ├── reactive-db-mongo ├── .gitignore ├── Readme.md ├── docker-compose.yml ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── djcodes │ │ │ └── spring │ │ │ └── reactivedb │ │ │ ├── ReactiveDbApplication.java │ │ │ ├── controller │ │ │ └── ReservationController.java │ │ │ ├── document │ │ │ └── Reservation.java │ │ │ └── repository │ │ │ └── ReservationRepository.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── djcodes │ └── spring │ └── reactivedb │ ├── ReactiveDbApplicationTests.java │ ├── controller │ └── ReservationControllerTest.java │ └── repository │ └── ReservationRepositoryTest.java ├── reactive-db-postgres ├── .gitignore ├── Readme.md ├── docker-compose.yml ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── djcodes │ │ │ └── spring │ │ │ └── reactivedb │ │ │ ├── ReactiveDbApplication.java │ │ │ └── postgres │ │ │ ├── Reservation.java │ │ │ ├── ReservationRepository.java │ │ │ ├── ReservationService.java │ │ │ └── SampleDataInitializer.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── djcodes │ └── spring │ └── reactivedb │ └── ReactiveDbApplicationTests.java ├── reactive-producer-consumer ├── pom.xml ├── reactive-web-consumer │ ├── .gitignore │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── mynotes │ │ │ │ └── spring │ │ │ │ └── reactive │ │ │ │ └── reactivewebapp │ │ │ │ ├── Hobby.java │ │ │ │ ├── Person.java │ │ │ │ ├── ReactiveWebAppApplication.java │ │ │ │ ├── calls │ │ │ │ ├── CallPersonUsingRestTemplate.java │ │ │ │ ├── CallPersonUsingWebClient_Step1.java │ │ │ │ ├── CallPersonUsingWebClient_Step2.java │ │ │ │ ├── CallPersonUsingWebClient_Step3.java │ │ │ │ └── CallPersonUsingWebClient_Step4.java │ │ │ │ ├── exception │ │ │ │ └── ExceptionClient.java │ │ │ │ └── steps │ │ │ │ ├── Step1.java │ │ │ │ ├── Step2a.java │ │ │ │ ├── Step2a1.java │ │ │ │ ├── Step2a2.java │ │ │ │ ├── Step2b.java │ │ │ │ ├── Step2c.java │ │ │ │ ├── Step2d.java │ │ │ │ ├── Step2e.java │ │ │ │ └── Step3a.java │ │ └── resources │ │ │ ├── application.properties │ │ │ └── logback.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── mynotes │ │ └── spring │ │ └── reactive │ │ └── reactivewebapp │ │ └── ReactiveWebAppApplicationTests.java └── reactive-web-producer │ ├── .gitignore │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── mynotes │ │ │ └── spring │ │ │ └── reactive │ │ │ ├── Application.java │ │ │ ├── controller │ │ │ ├── CalculateController.java │ │ │ ├── ExceptionController.java │ │ │ ├── FluxMonoController.java │ │ │ ├── GreetReactiveController.java │ │ │ ├── Greeting.java │ │ │ ├── HttpBinController.java │ │ │ ├── Person.java │ │ │ └── PersonController.java │ │ │ ├── dto │ │ │ └── CalculateResponseDTO.java │ │ │ ├── exception │ │ │ └── ControllerExceptionHandler.java │ │ │ ├── handler │ │ │ └── HandlerFunctions.java │ │ │ ├── router │ │ │ └── RouterFunctionConfig.java │ │ │ └── service │ │ │ ├── CalculateService.java │ │ │ └── HttpBinService.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── mynotes │ └── spring │ └── reactive │ ├── controller │ ├── CalculateControllerTest.java │ ├── ExceptionControllerTest.java │ ├── FluxMonoControllerTests.java │ ├── HttpBinControllerMockWebServerTest.java │ └── HttpBinControllerWireMockTest.java │ ├── router │ └── RouterTests.java │ └── service │ ├── HttpBinServiceMockServerNettyTest.java │ ├── HttpBinServiceMockWebServerTest.java │ └── HttpBinServiceWireMockTest.java ├── reactive-web ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── reactive │ │ │ └── reactiveweb │ │ │ ├── FunctionalJavaConfiguration.java │ │ │ ├── Greeting.java │ │ │ ├── GreetingsRestController.java │ │ │ ├── ReactiveWebApplication.java │ │ │ └── WebSocketConfiguration.java │ └── resources │ │ ├── application.properties │ │ └── static │ │ └── client.html │ └── test │ └── java │ └── com │ └── example │ └── reactive │ └── reactiveweb │ └── ReactiveWebApplicationTests.java └── webflux-in-servlet ├── .gitignore ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── mynotes │ │ └── reactive │ │ └── demo │ │ └── webfluxinservlet │ │ └── controller │ │ ├── WebfluxInServletApplication.java │ │ ├── controller │ │ ├── ApiController.java │ │ └── HttpBinController.java │ │ └── exceptions │ │ ├── AppExceptionHandler.java │ │ ├── RestTemplateCustomException.java │ │ ├── RestTemplateResponseErrorHandler.java │ │ └── WebClientCustomException.java └── resources │ └── application.properties └── test └── java └── com └── mynotes └── reactive └── demo └── webfluxinservlet └── controller ├── ApiControllerIT.java └── HttpBinControllerIT.java /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | - main 11 | pull_request: 12 | 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | 21 | - name: Build 22 | uses: actions/setup-java@v3 23 | with: 24 | distribution: 'temurin' 25 | java-version: '17' 26 | cache: 'maven' 27 | - run: mvn -pl gateway clean install 28 | 29 | 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | .mvn 4 | mvn* 5 | .DS_Store 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeansrequest.getHeader("X- 13 | .sts4-cache 14 | . 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /build/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-reactive 2 | -------------------------------------------------------------------------------- /flux-mono/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /flux-mono/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.8 9 | 10 | 11 | com.mynotes.spring 12 | flux-mono 13 | 0.0.1-SNAPSHOT 14 | flux-mono 15 | Demo project for Spring Boot 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-webflux 25 | 26 | 27 | org.apache.commons 28 | commons-lang3 29 | 30 | 31 | org.projectlombok 32 | lombok 33 | true 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-test 38 | test 39 | 40 | 41 | org.junit.vintage 42 | junit-vintage-engine 43 | 44 | 45 | 46 | 47 | io.projectreactor 48 | reactor-test 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-maven-plugin 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /flux-mono/src/main/java/com/mynotes/spring/fluxmono/FluxMonoApplication.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.fluxmono; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class FluxMonoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(FluxMonoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /flux-mono/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /flux-mono/src/test/java/com/mynotes/spring/fluxmono/BackPressureTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.fluxmono; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import reactor.core.publisher.BaseSubscriber; 5 | import reactor.core.publisher.Flux; 6 | import reactor.test.StepVerifier; 7 | 8 | public class BackPressureTest { 9 | 10 | @Test 11 | public void backPressureTest(){ 12 | Flux flux = Flux.range(1,10).log(); 13 | 14 | StepVerifier.create(flux) 15 | .expectSubscription() 16 | .expectNext(1) 17 | .thenRequest(1) 18 | .expectNext(2) 19 | .thenRequest(1) 20 | .expectNext(3) 21 | .thenCancel() 22 | .verify(); 23 | 24 | } 25 | 26 | @Test 27 | public void backPressureTest_subscribe(){ 28 | Flux flux = Flux.range(1,10).log(); 29 | 30 | flux.subscribe( 31 | element -> System.out.println("Element :: "+element), 32 | exception -> System.out.println("Error"), 33 | () -> System.out.println("Complete"), 34 | subscription -> subscription.request(2)); 35 | } 36 | 37 | @Test 38 | public void backPressureTest_custom(){ 39 | Flux flux = Flux.range(1,10).log(); 40 | 41 | flux.subscribe(new BaseSubscriber() { 42 | @Override 43 | protected void hookOnNext(Integer value) { 44 | request(1); 45 | System.out.println("Value received = "+ value); 46 | if(value==4){ 47 | cancel(); 48 | } 49 | } 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /flux-mono/src/test/java/com/mynotes/spring/fluxmono/CombiningTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.fluxmono; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import reactor.core.publisher.Flux; 5 | import reactor.test.StepVerifier; 6 | 7 | import java.time.Duration; 8 | import java.util.List; 9 | 10 | public class CombiningTest { 11 | 12 | @Test 13 | public void combineUsingMerge(){ 14 | 15 | Flux flux1 = Flux.just("a","b","c"); 16 | Flux flux2 = Flux.just("d","e","f"); 17 | 18 | 19 | Flux finalFlux = Flux.merge(flux1,flux2).log(); 20 | 21 | StepVerifier.create(finalFlux) 22 | .expectNext("a","b","c","d","e","f") 23 | .verifyComplete(); 24 | } 25 | 26 | @Test 27 | public void combineUsingMerge_delay(){ 28 | 29 | Flux flux1 = Flux.just("a","b","c").delayElements(Duration.ofSeconds(1)); 30 | Flux flux2 = Flux.just("d","e","f").delayElements(Duration.ofSeconds(1)); 31 | 32 | 33 | Flux finalFlux = Flux.merge(flux1,flux2).log(); 34 | 35 | StepVerifier.create(finalFlux) 36 | .expectNextCount(6) 37 | .verifyComplete(); 38 | } 39 | 40 | @Test 41 | public void combineUsingContact(){ 42 | 43 | Flux flux1 = Flux.just("a","b","c").delayElements(Duration.ofSeconds(1)); 44 | Flux flux2 = Flux.just("d","e","f").delayElements(Duration.ofSeconds(1)); 45 | 46 | 47 | Flux finalFlux = Flux.concat(flux1,flux2).log(); 48 | 49 | StepVerifier.create(finalFlux) 50 | .expectNext("a","b","c","d","e","f") 51 | .verifyComplete(); 52 | } 53 | 54 | @Test 55 | public void combineUsingZip(){ 56 | 57 | Flux flux1 = Flux.just("a","b","c").delayElements(Duration.ofSeconds(1)); 58 | Flux flux2 = Flux.just("d","e","f").delayElements(Duration.ofSeconds(2)); 59 | 60 | 61 | Flux finalFlux = Flux.zip(flux1,flux2, (t1, t2) -> { 62 | return t1.concat(t2); 63 | }).log(); 64 | 65 | StepVerifier.create(finalFlux) 66 | .expectNext("ad","be","cf") 67 | .verifyComplete(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /flux-mono/src/test/java/com/mynotes/spring/fluxmono/ErrorTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.fluxmono; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import reactor.core.publisher.Flux; 5 | import reactor.test.StepVerifier; 6 | import reactor.util.retry.Retry; 7 | 8 | import java.time.Duration; 9 | 10 | public class ErrorTest { 11 | @Test 12 | public void fluxTestAndErrorTest() { 13 | Flux myFlux = Flux.just("a", "b", "c") 14 | .concatWith(Flux.error(new RuntimeException("Error123"))) 15 | .concatWith(Flux.just("e")).log(); 16 | 17 | StepVerifier.create(myFlux) 18 | .expectNext("a") 19 | .expectNext("b") 20 | .expectNext("c") 21 | //.expectError(RuntimeException.class) //Cannot have both together 22 | .expectErrorMessage("Error123") 23 | .verify(); 24 | 25 | } 26 | 27 | @Test 28 | public void fluxError_onErrorResume() { 29 | Flux myFlux = Flux.just("a", "b", "c") 30 | .concatWith(Flux.error(new RuntimeException("Error123"))) 31 | .concatWith(Flux.just("e")) 32 | .onErrorResume( e -> { 33 | System.out.println("Something broke"+ e.getMessage()); 34 | return Flux.just("default"); 35 | }).log(); 36 | 37 | StepVerifier.create(myFlux) 38 | .expectNext("a") 39 | .expectNext("b") 40 | .expectNext("c") 41 | .expectNext("default")// <== 42 | .verifyComplete(); 43 | 44 | } 45 | 46 | @Test 47 | public void fluxError_onErrorReturn() { 48 | Flux myFlux = Flux.just("a", "b", "c") 49 | .concatWith(Flux.error(new RuntimeException("Error123"))) 50 | .concatWith(Flux.just("e")) 51 | .onErrorReturn("default").log(); 52 | 53 | StepVerifier.create(myFlux) 54 | .expectNext("a") 55 | .expectNext("b") 56 | .expectNext("c") 57 | .expectNext("default")// <== 58 | .verifyComplete(); 59 | 60 | } 61 | 62 | @Test 63 | public void fluxError_onErrorMap() { 64 | Flux myFlux = Flux.just("a", "b", "c") 65 | .concatWith(Flux.error(new RuntimeException("Error123"))) 66 | .concatWith(Flux.just("e")) 67 | .onErrorMap(e -> new IllegalArgumentException("Check input")) 68 | .log(); 69 | 70 | StepVerifier.create(myFlux) 71 | .expectNext("a") 72 | .expectNext("b") 73 | .expectNext("c") 74 | .expectError(IllegalArgumentException.class) 75 | .verify(); 76 | 77 | } 78 | 79 | @Test 80 | public void fluxError_Retry() { 81 | Flux myFlux = Flux.just("a", "b", "c") 82 | .concatWith(Flux.error(new RuntimeException("Error123"))) 83 | .concatWith(Flux.just("e")) 84 | .retry(1) 85 | .log(); 86 | 87 | StepVerifier.create(myFlux) 88 | .expectNext("a", "b", "c") 89 | .expectNext("a", "b", "c") 90 | .expectError(RuntimeException.class) 91 | .verify(); 92 | 93 | } 94 | 95 | @Test 96 | public void fluxError_RetryBackOff() { 97 | Flux myFlux = Flux.just("a", "b", "c") 98 | .concatWith(Flux.error(new RuntimeException("Error123"))) 99 | .concatWith(Flux.just("e")) 100 | .retryWhen(Retry.backoff(1,Duration.ofSeconds(5))) 101 | .log(); 102 | 103 | StepVerifier.create(myFlux) 104 | .expectNext("a", "b", "c") 105 | .expectNext("a", "b", "c") 106 | .expectError(RuntimeException.class) 107 | .verify(); 108 | 109 | } 110 | 111 | 112 | 113 | } 114 | -------------------------------------------------------------------------------- /flux-mono/src/test/java/com/mynotes/spring/fluxmono/FilterTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.fluxmono; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import reactor.core.publisher.Flux; 5 | import reactor.test.StepVerifier; 6 | 7 | import java.util.List; 8 | 9 | public class FilterTest { 10 | 11 | @Test 12 | public void fluxFilterTest(){ 13 | List myList = List.of("foo","bar","baz"); 14 | 15 | Flux flux = Flux.fromIterable(myList) 16 | .filter(s -> s.startsWith("ba")) 17 | .log(); 18 | 19 | StepVerifier.create(flux) 20 | .expectNext("bar", "baz") 21 | .verifyComplete(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /flux-mono/src/test/java/com/mynotes/spring/fluxmono/FluxAndMonoFactoryMethods.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.fluxmono; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import reactor.core.publisher.Flux; 5 | import reactor.core.publisher.Mono; 6 | import reactor.test.StepVerifier; 7 | 8 | import java.util.List; 9 | import java.util.function.Supplier; 10 | import java.util.stream.Stream; 11 | 12 | public class FluxAndMonoFactoryMethods { 13 | 14 | @Test 15 | public void fluxFromIterable(){ 16 | List myList = List.of("a","b","c"); 17 | 18 | Flux flux = Flux.fromIterable(myList).log(); 19 | 20 | StepVerifier.create(flux) 21 | .expectNext("a","b","c") 22 | .verifyComplete(); 23 | 24 | } 25 | 26 | @Test 27 | public void fluxFromArray(){ 28 | String[] myArr = new String[]{"a","b","c"}; 29 | 30 | Flux flux = Flux.fromArray(myArr).log(); 31 | 32 | StepVerifier.create(flux) 33 | .expectNext("a","b","c") 34 | .verifyComplete(); 35 | 36 | } 37 | 38 | @Test 39 | public void fluxFromStream(){ 40 | Stream stream = List.of("a","b","c").stream(); 41 | 42 | Flux flux = Flux.fromStream(stream).log(); 43 | 44 | StepVerifier.create(flux) 45 | .expectNext("a","b","c") 46 | .verifyComplete(); 47 | 48 | } 49 | 50 | @Test 51 | public void fluxFromRange(){ 52 | 53 | Flux flux = Flux.range(1,10); 54 | 55 | StepVerifier.create(flux) 56 | .expectNextCount(10) 57 | .verifyComplete(); 58 | 59 | } 60 | 61 | @Test 62 | public void monoFromJustOrEmpty(){ 63 | 64 | Mono mono =Mono.justOrEmpty(null); 65 | 66 | StepVerifier.create(mono.log()) 67 | .verifyComplete(); 68 | 69 | } 70 | 71 | @Test 72 | public void monoFromSupplier(){ 73 | 74 | Supplier supplier = () -> "a"; 75 | 76 | Mono mono =Mono.fromSupplier(supplier); 77 | 78 | StepVerifier.create(mono.log()) 79 | .expectNext("a") 80 | .verifyComplete(); 81 | 82 | } 83 | 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /flux-mono/src/test/java/com/mynotes/spring/fluxmono/FluxMonoApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.fluxmono; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import reactor.core.publisher.ConnectableFlux; 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | import reactor.test.StepVerifier; 8 | 9 | import java.time.Duration; 10 | 11 | class FluxMonoApplicationTest { 12 | 13 | @Test 14 | public void flux() { 15 | Flux myFlux = Flux.just("a", "b", "c"); 16 | 17 | myFlux.subscribe(System.out::println); 18 | 19 | } 20 | 21 | @Test 22 | public void fluxTest() { 23 | Flux myFlux = Flux.just("a", "b", "c").log(); 24 | 25 | StepVerifier.create(myFlux) 26 | .expectNext("a") 27 | .expectNext("b") 28 | .expectNext("c") 29 | .verifyComplete(); 30 | 31 | StepVerifier.create(myFlux) 32 | .expectNext("a","b", "c") 33 | .verifyComplete(); 34 | 35 | StepVerifier.create(myFlux) 36 | .expectNextCount(3) 37 | .verifyComplete(); 38 | } 39 | 40 | @Test 41 | public void fluxTestOnComplete() { 42 | Flux myFlux = Flux.just("a", "b", "c"); 43 | 44 | myFlux.subscribe(System.out::println, 45 | (e) -> System.err.println(e), 46 | () -> System.out.println("Completed")); 47 | 48 | } 49 | 50 | @Test 51 | public void fluxTestAndError() { 52 | Flux myFlux = Flux.just("a", "b", "c") 53 | .concatWith(Flux.error(new RuntimeException("Error123"))).log(); 54 | 55 | myFlux.subscribe(System.out::println, 56 | (e) -> System.err.println(e)); 57 | 58 | } 59 | 60 | @Test 61 | public void fluxTestAndErrorTest() { 62 | Flux myFlux = Flux.just("a", "b", "c") 63 | .concatWith(Flux.error(new RuntimeException("Error123"))).log(); 64 | 65 | StepVerifier.create(myFlux) 66 | .expectNext("a") 67 | .expectNext("b") 68 | .expectNext("c") 69 | //.expectError(RuntimeException.class) //Cannot have both together 70 | .expectErrorMessage("Error123") 71 | .verify(); 72 | 73 | } 74 | 75 | @Test 76 | public void fluxTestAndErrorAndFLux() { 77 | Flux myFlux = Flux.just("a", "b", "c") 78 | .concatWith(Flux.error(new RuntimeException("Error"))) 79 | .concatWith(Flux.just("After Error")) 80 | .log(); 81 | 82 | myFlux.subscribe(System.out::println, 83 | (e) -> System.err.println(e)); 84 | 85 | } 86 | 87 | @Test 88 | public void monoTest(){ 89 | Mono myMono = Mono.just("a").log(); 90 | 91 | StepVerifier.create(myMono) 92 | .expectNext("a") 93 | .verifyComplete(); 94 | } 95 | 96 | @Test 97 | public void monoErrorTest(){ 98 | 99 | Mono myMono = Mono.error(new RuntimeException("Error")).log(); 100 | 101 | StepVerifier.create(myMono) 102 | .expectErrorMessage("Error") 103 | .verify(); 104 | } 105 | 106 | @Test 107 | public void coldPublishers() throws InterruptedException { 108 | Flux myFlux = Flux.just("a", "b", "c", "d", "e", "f") 109 | .delayElements(Duration.ofSeconds(1)); 110 | 111 | // Subscriber 1 112 | myFlux.subscribe(s -> System.out.println("Element in Subscriber 1 :: "+ s)); 113 | 114 | Thread.sleep(2000); 115 | 116 | // Subscriber 2 117 | myFlux.subscribe(s -> System.out.println("Element in Subscriber 2 :: "+ s)); 118 | 119 | Thread.sleep(5000); 120 | } 121 | 122 | @Test 123 | public void hotPublishers() throws InterruptedException { 124 | Flux myFlux = Flux.just("a", "b", "c", "d", "e", "f") 125 | .delayElements(Duration.ofSeconds(1)); 126 | 127 | // Make myflux hot publisher 128 | ConnectableFlux connectableFlux = myFlux.publish(); 129 | connectableFlux.connect(); 130 | 131 | // Subscriber 1 132 | connectableFlux.subscribe(s -> System.out.println("Element in Subscriber 1 :: "+ s)); 133 | Thread.sleep(2000); 134 | 135 | // Subscriber 2 136 | connectableFlux.subscribe(s -> System.out.println("Element in Subscriber 2 :: "+ s)); 137 | 138 | Thread.sleep(5000); 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /flux-mono/src/test/java/com/mynotes/spring/fluxmono/InfiniteStream.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.fluxmono; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import reactor.core.publisher.Flux; 5 | import reactor.test.StepVerifier; 6 | 7 | import java.time.Duration; 8 | 9 | public class InfiniteStream { 10 | 11 | @Test 12 | public void infiniteStream() throws InterruptedException { 13 | Flux infinite = Flux.interval(Duration.ofMillis(100)).log(); 14 | 15 | infinite.subscribe(element -> System.out.println("Element :: "+element)); 16 | 17 | Thread.sleep(3000); 18 | 19 | } 20 | 21 | @Test 22 | public void infiniteStreamTest() throws InterruptedException { 23 | Flux finiteFlux = Flux.interval(Duration.ofMillis(100)) 24 | .take(3).log(); 25 | 26 | StepVerifier.create(finiteFlux) 27 | .expectNext(0l,1l,2l) 28 | .verifyComplete(); 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /flux-mono/src/test/java/com/mynotes/spring/fluxmono/MapTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.fluxmono; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import reactor.core.publisher.Flux; 5 | import reactor.test.StepVerifier; 6 | 7 | import java.util.List; 8 | import java.util.Locale; 9 | 10 | import static reactor.core.scheduler.Schedulers.parallel; 11 | 12 | public class MapTest { 13 | 14 | @Test 15 | public void usingMap(){ 16 | List myList = List.of("foo","bar","baz"); 17 | 18 | Flux flux = Flux.fromIterable(myList) 19 | .map(s -> s.toUpperCase(Locale.ROOT)) 20 | .log(); 21 | 22 | StepVerifier.create(flux) 23 | .expectNext("FOO","BAR","BAZ") 24 | .verifyComplete(); 25 | } 26 | 27 | @Test 28 | public void usingMap_datatype(){ 29 | List myList = List.of("foo","bar","baz"); 30 | 31 | Flux flux = Flux.fromIterable(myList) 32 | .map(s -> s.length()) 33 | .log(); 34 | 35 | StepVerifier.create(flux) 36 | .expectNext(3,3,3) 37 | .verifyComplete(); 38 | } 39 | 40 | @Test 41 | public void flatMap(){ 42 | List myList = List.of("one","two","three", "four", "five","six"); 43 | TestUtils.startTimer(); 44 | Flux flux = Flux.fromIterable(myList) 45 | .flatMap(s -> Flux.just(convertToUpperCase(s))) 46 | .log(); 47 | 48 | StepVerifier.create(flux) 49 | .expectNext("ONE","TWO","THREE","FOUR", "FIVE","SIX") 50 | .verifyComplete(); 51 | TestUtils.stopTimer(); 52 | } 53 | 54 | private String convertToUpperCase(String input){ 55 | try { 56 | Thread.sleep(1000); 57 | } catch (InterruptedException e) { 58 | e.printStackTrace(); 59 | } 60 | return input.toUpperCase(Locale.ROOT); 61 | } 62 | 63 | @Test 64 | public void flatMap_parallel(){ 65 | List myList = List.of("one","two","three", "four", "five","six"); 66 | TestUtils.startTimer(); 67 | Flux flux = Flux.fromIterable(myList) 68 | .window(2) 69 | .flatMap(s -> s.map(this::convertToUpperCase).subscribeOn(parallel())) 70 | .log(); 71 | 72 | StepVerifier.create(flux) 73 | .expectNextCount(6) //Not same order 74 | .verifyComplete(); 75 | TestUtils.stopTimer(); 76 | } 77 | 78 | @Test 79 | public void flatMap_parallel_sequential(){ 80 | List myList = List.of("one","two","three", "four", "five","six"); 81 | TestUtils.startTimer(); 82 | Flux flux = Flux.fromIterable(myList) 83 | .window(2) 84 | .flatMapSequential(s -> s.map(this::convertToUpperCase).subscribeOn(parallel())) 85 | .log(); 86 | 87 | StepVerifier.create(flux) 88 | .expectNext("ONE","TWO","THREE","FOUR", "FIVE","SIX") 89 | .verifyComplete(); 90 | TestUtils.stopTimer(); 91 | } 92 | 93 | 94 | 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /flux-mono/src/test/java/com/mynotes/spring/fluxmono/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.fluxmono; 2 | 3 | import org.apache.commons.lang3.time.StopWatch; 4 | 5 | public class TestUtils { 6 | 7 | public static StopWatch stopWatch = new StopWatch(); 8 | 9 | public static void delay(long delayMilliSeconds) { 10 | try{ 11 | Thread.sleep(delayMilliSeconds); 12 | }catch (Exception e){ 13 | System.out.println("Exception is :" + e.getMessage()); 14 | } 15 | } 16 | 17 | public static void startTimer(){ 18 | stopWatch.reset(); 19 | stopWatch.start(); 20 | } 21 | 22 | public static void stopTimer(){ 23 | stopWatch.stop(); 24 | System.out.println("Total Time Taken : " +stopWatch.getTime()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /flux-mono/src/test/java/com/mynotes/spring/fluxmono/VirtualTimeTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.fluxmono; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import reactor.core.publisher.Flux; 5 | import reactor.test.StepVerifier; 6 | import reactor.test.scheduler.VirtualTimeScheduler; 7 | 8 | import java.time.Duration; 9 | 10 | public class VirtualTimeTest { 11 | 12 | @Test 13 | public void withoutVirtualTime() throws InterruptedException { 14 | TestUtils.startTimer(); 15 | Flux myFlux = Flux.just("a", "b", "c", "d", "e", "f") 16 | .delayElements(Duration.ofSeconds(1)) 17 | .log(); 18 | 19 | StepVerifier.create(myFlux) 20 | .expectNextCount(6) 21 | .verifyComplete(); 22 | TestUtils.stopTimer(); 23 | 24 | } 25 | 26 | @Test 27 | public void withVirtualTime() throws InterruptedException { 28 | VirtualTimeScheduler.getOrSet(); 29 | TestUtils.startTimer(); 30 | Flux myFlux = Flux.just("a", "b", "c", "d", "e", "f") 31 | .delayElements(Duration.ofSeconds(1)) 32 | .log(); 33 | 34 | StepVerifier.withVirtualTime(() -> myFlux) 35 | .expectSubscription() 36 | .thenAwait(Duration.ofSeconds(6)) 37 | .expectNextCount(6) 38 | .verifyComplete(); 39 | TestUtils.stopTimer(); 40 | 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /flux-mono/src/test/java/com/mynotes/spring/reactor/ThreadLocalReactive.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactor; 2 | 3 | import java.time.Duration; 4 | import java.util.concurrent.TimeUnit; 5 | import java.util.function.Function; 6 | import org.junit.jupiter.api.Test; 7 | import reactor.core.publisher.Mono; 8 | import reactor.core.scheduler.Schedulers; 9 | import reactor.util.context.Context; 10 | 11 | //https://bsideup.github.io/posts/daily_reactive/thread_locals/ 12 | public class ThreadLocalReactive { 13 | 14 | static final ThreadLocal USER_ID = new ThreadLocal<>(); 15 | 16 | // Fix hook in reactor 17 | static { 18 | Function decorator = task -> { 19 | // Capture 20 | String userId = USER_ID.get(); 21 | 22 | return () -> { 23 | String previous = USER_ID.get(); 24 | // Restore 25 | USER_ID.set(userId); 26 | try { 27 | // Call the original task 28 | task.run(); 29 | } 30 | finally { 31 | // Cleanup 32 | USER_ID.set(previous); 33 | } 34 | }; 35 | }; 36 | Schedulers.onScheduleHook("my-hook", decorator); 37 | } 38 | 39 | 40 | @Test 41 | public void testThreadLocals_reactor() { 42 | USER_ID.set("bsideup"); 43 | 44 | Mono.just("Hello %s") 45 | .delayElement(Duration.ofSeconds(1)) 46 | .doOnNext(greeting -> { 47 | // WIll print "Hello null". Bummer! 48 | System.out.println(String.format(greeting, USER_ID.get())); 49 | }) 50 | .block(); 51 | } 52 | 53 | @Test 54 | public void reactor_context() { 55 | Mono.just("Hello %s") 56 | .delayElement(Duration.ofSeconds(1)) 57 | .transform(flux -> Mono.deferWithContext(ctx -> { 58 | return flux.doOnNext(greeting -> { 59 | // Get it from the Context 60 | String userId = ctx.get("userId"); 61 | System.out.println(String.format(greeting, userId)); 62 | }); 63 | })) 64 | .subscriberContext(Context.of("userId", "bsideup")) 65 | .block(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /gateway/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.0.2 9 | 10 | 11 | com.example 12 | gateway 13 | 0.0.1-SNAPSHOT 14 | gateway 15 | Demo project for Spring Boot 16 | 17 | 17 18 | false 19 | false 20 | 2022.0.0 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-security 26 | 27 | 28 | org.springframework.cloud 29 | spring-cloud-starter-gateway 30 | 31 | 32 | org.springframework.cloud 33 | spring-cloud-starter-netflix-eureka-client 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | true 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | org.springframework.security 47 | spring-security-test 48 | test 49 | 50 | 51 | org.mock-server 52 | mockserver-netty 53 | 5.14.0 54 | test 55 | 56 | 57 | 58 | 59 | 60 | org.springframework.cloud 61 | spring-cloud-dependencies 62 | ${spring-cloud.version} 63 | pom 64 | import 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-maven-plugin 74 | 75 | 76 | 77 | org.projectlombok 78 | lombok 79 | 80 | 81 | 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-surefire-plugin 86 | 87 | 88 | 89 | ${surefireArgLine} 90 | ${skip.unit.tests} 91 | 92 | 93 | **/*IT.java 94 | **/*It.java 95 | 96 | 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-failsafe-plugin 102 | 103 | 104 | 105 | ${failsafeArgLine} 106 | ${skip.integration.tests} 107 | 108 | **/*It.java 109 | **/*IT.java 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /gateway/src/main/java/com/example/gateway/GatewayApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.gateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | 7 | @SpringBootApplication 8 | @EnableDiscoveryClient 9 | public class GatewayApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(GatewayApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /gateway/src/main/java/com/example/gateway/configuration/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.gateway.configuration; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashSet; 5 | import java.util.Objects; 6 | import java.util.Set; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.http.HttpMethod; 10 | import org.springframework.http.server.reactive.ServerHttpRequest; 11 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 12 | import org.springframework.security.config.web.server.ServerHttpSecurity; 13 | import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; 14 | import org.springframework.security.web.server.SecurityWebFilterChain; 15 | import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository; 16 | import org.springframework.security.web.server.csrf.CsrfToken; 17 | import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestAttributeHandler; 18 | import org.springframework.web.server.WebFilter; 19 | import reactor.core.publisher.Mono; 20 | 21 | import static org.springframework.http.HttpMethod.DELETE; 22 | import static org.springframework.http.HttpMethod.PATCH; 23 | import static org.springframework.http.HttpMethod.POST; 24 | import static org.springframework.http.HttpMethod.PUT; 25 | import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.match; 26 | import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.notMatch; 27 | 28 | @Configuration 29 | @EnableWebFluxSecurity 30 | public class SecurityConfiguration { 31 | 32 | private static final Set CSRF_METHOD_TO_CHECK = 33 | new HashSet<>(Arrays.asList(POST, PUT, PATCH, DELETE)); 34 | 35 | public static String AUTHORIZATION_COOKIE_NAME = "Authorization"; 36 | 37 | @Bean 38 | public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity serverHttpSecurity) { 39 | return serverHttpSecurity 40 | .authorizeExchange() 41 | .anyExchange() 42 | .permitAll() 43 | .and() 44 | .csrf() 45 | .csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) 46 | .csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler()) 47 | .requireCsrfProtectionMatcher( 48 | exchange -> { 49 | ServerHttpRequest request = exchange.getRequest(); 50 | if(CSRF_METHOD_TO_CHECK.contains(request.getMethod()) 51 | && request.getCookies().containsKey(AUTHORIZATION_COOKIE_NAME)){ 52 | return match(); 53 | } else { 54 | return notMatch(); 55 | } 56 | } 57 | ) 58 | .and() 59 | .logout().disable().build(); 60 | 61 | } 62 | 63 | 64 | //Reactive fix - https://github.com/spring-projects/spring-security/issues/5766#issuecomment-564636167 65 | @Bean 66 | WebFilter addCsrfToken() { 67 | return (exchange, next) -> exchange 68 | .>getAttribute(CsrfToken.class.getName()) 69 | .doOnSuccess(token -> {}) // do nothing, just subscribe :/ 70 | .then(next.filter(exchange)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /gateway/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gateway/src/test/java/com/example/gateway/configuration/SecurityConfigurationIT.java: -------------------------------------------------------------------------------- 1 | package com.example.gateway.configuration; 2 | 3 | 4 | import java.time.Duration; 5 | import org.junit.jupiter.api.AfterAll; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.extension.ExtendWith; 11 | import org.mockserver.integration.ClientAndServer; 12 | import org.mockserver.socket.PortFactory; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.boot.test.web.server.LocalServerPort; 15 | import org.springframework.test.context.junit.jupiter.SpringExtension; 16 | import org.springframework.test.web.reactive.server.WebTestClient; 17 | 18 | import static org.mockserver.model.HttpRequest.request; 19 | import static org.mockserver.model.HttpResponse.response; 20 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 21 | 22 | @ExtendWith(SpringExtension.class) 23 | @SpringBootTest(webEnvironment = RANDOM_PORT) 24 | public class SecurityConfigurationIT { 25 | 26 | protected static int mockServerPort; 27 | protected WebTestClient webClient; 28 | protected ClientAndServer mockServer; 29 | private static final String CSRF_HEADER = "X-XSRF-TOKEN"; 30 | private static final String CSRF_COOKIE = "XSRF-TOKEN"; 31 | private static final String AUTHORIZATION = "Authorization"; 32 | @LocalServerPort 33 | private int port; 34 | 35 | @BeforeAll 36 | static void beforeClass() { 37 | mockServerPort = PortFactory.findFreePort(); 38 | System.setProperty("mock.server.port", String.valueOf(mockServerPort)); 39 | } 40 | 41 | @AfterAll 42 | public static void afterClass() { 43 | System.clearProperty("mock.server.port"); 44 | } 45 | 46 | @BeforeEach 47 | void setUp() { 48 | webClient = WebTestClient.bindToServer().responseTimeout(Duration.ofSeconds(60)) 49 | .baseUrl("http://localhost:" + port).build(); 50 | mockServer = ClientAndServer.startClientAndServer(mockServerPort); 51 | mockServer.when(request().withPath("/test")) 52 | .respond(response().withStatusCode(200)); 53 | 54 | } 55 | 56 | @AfterEach 57 | public void tearDown() { 58 | webClient.delete(); 59 | mockServer.stop(); 60 | } 61 | 62 | @Test 63 | void given_postRequest_withAuthHeader_andNoCsrfHeaderAndCookie_then_expectOk_and_CsrfCookie() { 64 | webClient.post().uri("/test") 65 | .header(AUTHORIZATION, AUTHORIZATION) 66 | .exchange() 67 | .expectStatus().isOk() 68 | .expectCookie().exists(CSRF_COOKIE); 69 | } 70 | 71 | @Test 72 | void given_postRequest_withAuthCookie_andNoCsrfHeaderAndCookie_then_expectOk_and_CsrfCookie() { 73 | webClient.post().uri("/test") 74 | .cookie(AUTHORIZATION, AUTHORIZATION) 75 | .exchange() 76 | .expectStatus().isForbidden() 77 | .expectCookie().doesNotExist(CSRF_COOKIE); 78 | } 79 | 80 | 81 | 82 | @Test 83 | void given_postRequest_withAuthHeader_andCsrfHeaderAndCookie_then_expectOk_and_noCsrfCookie() { 84 | webClient.post().uri("/test") 85 | .header(AUTHORIZATION, AUTHORIZATION) 86 | .header(CSRF_HEADER, "asd123") 87 | .cookie(CSRF_COOKIE, "asd123") 88 | .exchange() 89 | .expectStatus().isOk() 90 | .expectCookie().doesNotExist(CSRF_COOKIE); 91 | } 92 | 93 | @Test 94 | void given_postRequest_withAuthCookie_andCsrfHeaderAndCookie_then_expectOk_and_noCsrfCookie() { 95 | webClient.post().uri("/test") 96 | .cookie(AUTHORIZATION, AUTHORIZATION) 97 | .header(CSRF_HEADER, "asd123") 98 | .cookie(CSRF_COOKIE, "asd123") 99 | .exchange() 100 | .expectStatus().isOk() 101 | .expectCookie().doesNotExist(CSRF_COOKIE); 102 | } 103 | 104 | 105 | } 106 | -------------------------------------------------------------------------------- /gateway/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | client: 3 | enabled: false 4 | 5 | spring: 6 | cloud: 7 | gateway: 8 | routes: 9 | - id: test 10 | uri: http://localhost:${mock.server.port} 11 | order: 1 12 | predicates: 13 | - Path=/test 14 | logging: 15 | level: 16 | root: debug 17 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.mynotes.reactive.demo 8 | learn-spring-reactive-root 9 | 0.0.1-SNAPSHOT 10 | pom 11 | 12 | learn-spring-reactive-root 13 | Demo project for spring microservices k8s 14 | 15 | 16 | flux-mono 17 | reactive-db-mongo 18 | reactive-db-postgres 19 | reactive-producer-consumer 20 | reactive-web 21 | webflux-in-servlet 22 | gateway 23 | 24 | 25 | -------------------------------------------------------------------------------- /reactive-db-mongo/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /reactive-db-mongo/Readme.md: -------------------------------------------------------------------------------- 1 | ## Java 13 2 | 3 | ## Spin up MongoDB 4 | 5 | ``` 6 | docker-compose up -d 7 | ``` 8 | 9 | ### Connect to mongodb 10 | If you have not provided any root username/password you can connect to it 11 | without any credential in Spring boot or mongo client. 12 | 13 | Use mongoshell or compass GUI or any other of your liking. 14 | 15 | -------------------------------------------------------------------------------- /reactive-db-mongo/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | mongodb_container: 4 | image: mongo:latest 5 | # environment: 6 | # MONGO_INITDB_ROOT_USERNAME: root 7 | # MONGO_INITDB_ROOT_PASSWORD: password 8 | ports: 9 | - 27017:27017 10 | 11 | -------------------------------------------------------------------------------- /reactive-db-mongo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.5 9 | 10 | 11 | com.djcodes.spring 12 | reactive-db-mongo 13 | 0.0.1-SNAPSHOT 14 | reactive-db-mongo 15 | Demo project for Spring Boot 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-mongodb-reactive 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-webflux 29 | 30 | 31 | org.projectlombok 32 | lombok 33 | true 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-test 38 | test 39 | 40 | 41 | io.projectreactor 42 | reactor-test 43 | test 44 | 45 | 46 | de.flapdoodle.embed 47 | de.flapdoodle.embed.mongo 48 | test 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-maven-plugin 57 | 58 | 59 | 60 | 61 | 62 | 63 | spring-milestones 64 | Spring Milestones 65 | https://repo.spring.io/milestone 66 | 67 | 68 | 69 | 70 | spring-milestones 71 | Spring Milestones 72 | https://repo.spring.io/milestone 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /reactive-db-mongo/src/main/java/com/djcodes/spring/reactivedb/ReactiveDbApplication.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb; 2 | 3 | import com.djcodes.spring.reactivedb.document.Reservation; 4 | import com.djcodes.spring.reactivedb.repository.ReservationRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.CommandLineRunner; 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | import org.springframework.context.annotation.Conditional; 10 | import org.springframework.context.annotation.Profile; 11 | import org.springframework.stereotype.Component; 12 | import reactor.core.publisher.Flux; 13 | 14 | import java.time.LocalDate; 15 | import java.util.List; 16 | 17 | @SpringBootApplication 18 | public class ReactiveDbApplication { 19 | 20 | public static void main(String[] args) { 21 | SpringApplication.run(ReactiveDbApplication.class, args); 22 | } 23 | 24 | } 25 | 26 | @Component 27 | @Profile("dev") 28 | class DataInitializer implements CommandLineRunner { 29 | 30 | @Autowired 31 | ReservationRepository reservationRepository; 32 | 33 | List sampleData = List.of(new Reservation(null, "John", LocalDate.of(2020, 04, 05)), 34 | new Reservation(null, "Jane", LocalDate.of(2020, 04, 12)), 35 | new Reservation(null, "Max", LocalDate.of(2020, 05, 23)), 36 | new Reservation("abc123", "Paul", LocalDate.of(2020, 05, 12))); 37 | 38 | @Override 39 | public void run(String... args) throws Exception { 40 | reservationRepository.deleteAll() 41 | .thenMany(Flux.fromIterable(sampleData)) 42 | .flatMap(reservationRepository::save) 43 | .doOnNext(item -> System.out.println("inserted Item" + item)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /reactive-db-mongo/src/main/java/com/djcodes/spring/reactivedb/controller/ReservationController.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb.controller; 2 | 3 | import com.djcodes.spring.reactivedb.document.Reservation; 4 | import com.djcodes.spring.reactivedb.repository.ReservationRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpEntity; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.*; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | 13 | @RestController 14 | @RequestMapping("/reservations") 15 | public class ReservationController { 16 | 17 | @Autowired 18 | ReservationRepository reservationRepository; 19 | 20 | @GetMapping 21 | public Flux getAll(){ 22 | return reservationRepository.findAll(); 23 | } 24 | 25 | @GetMapping("/{id}") 26 | public Mono> findById(@PathVariable String id){ 27 | return reservationRepository.findById(id) 28 | .map(element -> new ResponseEntity<>(element,HttpStatus.OK)) 29 | .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND)); 30 | } 31 | 32 | @PostMapping 33 | @ResponseStatus(HttpStatus.CREATED) 34 | public Mono create(@RequestBody Reservation reservation){ 35 | return reservationRepository.save(reservation); 36 | } 37 | 38 | @DeleteMapping("/{id}") 39 | public Mono delete(@PathVariable String id){ 40 | return reservationRepository.deleteById(id); 41 | } 42 | 43 | @PutMapping("/{id}") 44 | public Mono> update(@PathVariable String id, @RequestBody Reservation reservation){ 45 | return reservationRepository.findById(id) 46 | .flatMap(element -> { 47 | element.setName(reservation.getName()); 48 | element.setDate(reservation.getDate()); 49 | return reservationRepository.save(element); 50 | }).map(updatedElement -> new ResponseEntity<>(updatedElement,HttpStatus.OK)) 51 | .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND)); 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /reactive-db-mongo/src/main/java/com/djcodes/spring/reactivedb/document/Reservation.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb.document; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.mongodb.core.mapping.Document; 8 | 9 | import java.time.LocalDate; 10 | 11 | @Document 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class Reservation { 16 | 17 | @Id 18 | private String id; 19 | private String name; 20 | private LocalDate date; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /reactive-db-mongo/src/main/java/com/djcodes/spring/reactivedb/repository/ReservationRepository.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb.repository; 2 | 3 | import com.djcodes.spring.reactivedb.document.Reservation; 4 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 5 | import org.springframework.stereotype.Service; 6 | import reactor.core.publisher.Flux; 7 | 8 | 9 | public interface ReservationRepository extends ReactiveCrudRepository { 10 | 11 | Flux findByName(String name); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /reactive-db-mongo/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: dev 4 | 5 | --- 6 | spring: 7 | config: 8 | activate: 9 | on-profile: dev 10 | data: 11 | mongodb: 12 | host: localhost 13 | port: 27017 14 | database: local -------------------------------------------------------------------------------- /reactive-db-mongo/src/test/java/com/djcodes/spring/reactivedb/ReactiveDbApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ReactiveDbApplicationTests { 8 | 9 | // @Test 10 | // void contextLoads() { 11 | // } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /reactive-db-mongo/src/test/java/com/djcodes/spring/reactivedb/controller/ReservationControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb.controller; 2 | 3 | import com.djcodes.spring.reactivedb.document.Reservation; 4 | import com.djcodes.spring.reactivedb.repository.ReservationRepository; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient; 10 | import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; 11 | import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.test.annotation.DirtiesContext; 15 | import org.springframework.test.context.junit.jupiter.SpringExtension; 16 | import org.springframework.test.web.reactive.server.WebTestClient; 17 | import reactor.core.publisher.Flux; 18 | import reactor.core.publisher.Mono; 19 | import reactor.test.StepVerifier; 20 | 21 | import java.time.LocalDate; 22 | import java.util.List; 23 | 24 | import static org.junit.jupiter.api.Assertions.*; 25 | 26 | 27 | @ExtendWith(SpringExtension.class) 28 | @SpringBootTest 29 | @AutoConfigureWebTestClient 30 | class ReservationControllerTest { 31 | 32 | @Autowired 33 | WebTestClient webTestClient; 34 | 35 | @Autowired 36 | ReservationRepository reservationRepository; 37 | 38 | List sampleData = List.of(new Reservation(null, "John", LocalDate.of(2020, 04, 05)), 39 | new Reservation(null, "Jane", LocalDate.of(2020, 04, 12)), 40 | new Reservation(null, "Max", LocalDate.of(2020, 05, 23)), 41 | new Reservation("abc123", "Paul", LocalDate.of(2020, 05, 12))); 42 | 43 | @BeforeEach 44 | public void setup() { 45 | reservationRepository.deleteAll() 46 | .thenMany(Flux.fromIterable(sampleData)) 47 | .flatMap(reservationRepository::save) 48 | .doOnNext(item -> System.out.println("inserted Item" + item)) 49 | .blockLast(); 50 | } 51 | 52 | @Test 53 | public void getAll() { 54 | webTestClient.get().uri("/reservations") 55 | .exchange() 56 | .expectStatus().isOk() 57 | .expectHeader().contentType(MediaType.APPLICATION_JSON) 58 | .expectBodyList(Reservation.class) 59 | .hasSize(4); 60 | } 61 | 62 | @Test 63 | public void getSingle_success() { 64 | webTestClient.get().uri("/reservations/abc123") 65 | .exchange() 66 | .expectStatus().isOk() 67 | .expectHeader().contentType(MediaType.APPLICATION_JSON) 68 | .expectBody(Reservation.class); 69 | } 70 | 71 | @Test 72 | public void getSingle_not_found() { 73 | webTestClient.get().uri("/reservations/asd") 74 | .exchange() 75 | .expectStatus().isNotFound(); 76 | } 77 | 78 | @Test 79 | public void save() { 80 | Reservation request = new Reservation(null, "Emma", LocalDate.of(2021,04,11)); 81 | webTestClient.post().uri("/reservations") 82 | .contentType(MediaType.APPLICATION_JSON) 83 | .body(Mono.just(request), Reservation.class) 84 | .exchange() 85 | .expectStatus().isCreated() 86 | .expectBody() 87 | .jsonPath("$.id").isNotEmpty() 88 | .jsonPath("$.name").isEqualTo("Emma"); 89 | } 90 | 91 | @Test 92 | public void delete() { 93 | webTestClient.delete().uri("/reservations/abc123") 94 | .exchange() 95 | .expectStatus().isOk() 96 | .expectBody(Void.class); 97 | } 98 | 99 | @Test 100 | public void update() { 101 | Reservation request = new Reservation(null, "Paul", LocalDate.of(2021, 11, 12)); 102 | webTestClient.put().uri("/reservations/abc123") 103 | .contentType(MediaType.APPLICATION_JSON) 104 | .body(Mono.just(request), Reservation.class) 105 | .exchange() 106 | .expectStatus().isOk() 107 | .expectBody() 108 | .jsonPath("$.id").isNotEmpty() 109 | .jsonPath("$.name").isEqualTo("Paul") 110 | .jsonPath("$.date").isEqualTo("2021-11-12"); 111 | } 112 | 113 | @Test 114 | public void update_notfound() { 115 | Reservation request = new Reservation(null, "Paul", LocalDate.of(2021, 11, 12)); 116 | webTestClient.put().uri("/reservations/abc") 117 | .contentType(MediaType.APPLICATION_JSON) 118 | .body(Mono.just(request), Reservation.class) 119 | .exchange() 120 | .expectStatus().isNotFound(); 121 | } 122 | } -------------------------------------------------------------------------------- /reactive-db-mongo/src/test/java/com/djcodes/spring/reactivedb/repository/ReservationRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb.repository; 2 | 3 | import com.djcodes.spring.reactivedb.document.Reservation; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; 9 | import org.springframework.test.context.junit.jupiter.SpringExtension; 10 | import reactor.core.publisher.Flux; 11 | import reactor.core.publisher.Mono; 12 | import reactor.test.StepVerifier; 13 | 14 | import java.time.LocalDate; 15 | import java.util.List; 16 | 17 | @DataMongoTest 18 | @ExtendWith(SpringExtension.class) 19 | public class ReservationRepositoryTest { 20 | 21 | @Autowired 22 | ReservationRepository reservationRepository; 23 | 24 | List sampleData = List.of(new Reservation(null, "John", LocalDate.of(2020, 04, 05)), 25 | new Reservation(null, "Jane", LocalDate.of(2020, 04, 12)), 26 | new Reservation(null, "Max", LocalDate.of(2020, 05, 23)), 27 | new Reservation("abc123", "Paul", LocalDate.of(2020, 05, 12))); 28 | 29 | @BeforeEach 30 | public void setup() { 31 | reservationRepository.deleteAll() 32 | .thenMany(Flux.fromIterable(sampleData)) 33 | .flatMap(reservationRepository::save) 34 | .doOnNext(item -> System.out.println("inserted Item" + item)) 35 | .blockLast(); 36 | } 37 | 38 | @Test 39 | public void getAllItems() { 40 | StepVerifier.create(reservationRepository.findAll()) 41 | .expectSubscription() 42 | .expectNextCount(4) 43 | .verifyComplete(); 44 | } 45 | 46 | @Test 47 | public void findByName() { 48 | StepVerifier.create(reservationRepository.findByName("John")) 49 | .expectSubscription() 50 | .expectNextMatches( 51 | item -> item.getName().equals("John") 52 | ) 53 | .verifyComplete(); 54 | } 55 | 56 | @Test 57 | public void saveReservation() { 58 | 59 | Reservation reservation = new Reservation(null, "Emma", LocalDate.of(2020, 04, 12)); 60 | 61 | Mono savedReservation = reservationRepository.save(reservation); 62 | 63 | StepVerifier.create(savedReservation) 64 | .expectSubscription() 65 | .expectNextMatches( 66 | item -> item.getId() != null && item.getName().equals("Emma") 67 | ) 68 | .verifyComplete(); 69 | } 70 | 71 | @Test 72 | public void updateReservation() { 73 | 74 | 75 | Flux updateFlux = reservationRepository.findByName("John") 76 | .map(reservation -> { 77 | reservation.setDate(LocalDate.of(2020, 06, 12)); 78 | return reservation; 79 | }) 80 | .flatMap(reservation -> reservationRepository.save(reservation)); 81 | 82 | StepVerifier.create(updateFlux) 83 | .expectSubscription() 84 | .expectNextMatches( 85 | item -> item.getDate().equals(LocalDate.of(2020, 06, 12)) 86 | ) 87 | .verifyComplete(); 88 | } 89 | 90 | @Test 91 | public void deleteReservation() { 92 | 93 | 94 | Mono deleteReservation = reservationRepository.findByName("John") 95 | .flatMap(reservation -> reservationRepository.delete(reservation)).then(); 96 | 97 | StepVerifier.create(deleteReservation) 98 | .expectSubscription() 99 | .verifyComplete(); 100 | 101 | StepVerifier.create(reservationRepository.findAll()) 102 | .expectSubscription() 103 | .expectNextCount(3) 104 | .verifyComplete(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /reactive-db-postgres/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /reactive-db-postgres/Readme.md: -------------------------------------------------------------------------------- 1 | ## Spin up Postgres 2 | 3 | ``` 4 | docker-compose up -d 5 | ``` 6 | 7 | ### Connect to Postgres 8 | Give the require credential in application.properties. 9 | 10 | To connect to postgres you could use any client like DBeaver, HeidiSql etc. 11 | 12 | Create tables by running following command: 13 | 14 | ``` 15 | CREATE TABLE reservation ( 16 | id SERIAL PRIMARY KEY, 17 | name VARCHAR(100) NOT NULL 18 | ); 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- /reactive-db-postgres/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | postgres: 4 | image: postgres 5 | ports: 6 | - "5432:5432" 7 | environment: 8 | POSTGRES_PASSWORD: password 9 | 10 | -------------------------------------------------------------------------------- /reactive-db-postgres/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.3.0.M3 9 | 10 | 11 | com.djcodes.spring 12 | reactive-db-postgres 13 | 0.0.1-SNAPSHOT 14 | reactive-db-postgres 15 | Demo project for Spring Boot 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | io.r2dbc 24 | r2dbc-postgresql 25 | runtime 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-data-r2dbc 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-webflux 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | true 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | org.junit.vintage 47 | junit-vintage-engine 48 | 49 | 50 | 51 | 52 | io.projectreactor 53 | reactor-test 54 | test 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-maven-plugin 63 | 64 | 65 | 66 | 67 | 68 | 69 | spring-milestones 70 | Spring Milestones 71 | https://repo.spring.io/milestone 72 | 73 | 74 | 75 | 76 | spring-milestones 77 | Spring Milestones 78 | https://repo.spring.io/milestone 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /reactive-db-postgres/src/main/java/com/djcodes/spring/reactivedb/ReactiveDbApplication.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb; 2 | 3 | import io.r2dbc.spi.ConnectionFactory; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager; 8 | import org.springframework.transaction.ReactiveTransactionManager; 9 | import org.springframework.transaction.annotation.EnableTransactionManagement; 10 | import org.springframework.transaction.reactive.TransactionalOperator; 11 | 12 | @SpringBootApplication 13 | @EnableTransactionManagement 14 | public class ReactiveDbApplication { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(ReactiveDbApplication.class, args); 18 | } 19 | 20 | @Bean 21 | ReactiveTransactionManager r2dbcTransactionManager(ConnectionFactory cf) { 22 | return new R2dbcTransactionManager(cf); 23 | } 24 | 25 | @Bean 26 | TransactionalOperator transactionalOperator(ReactiveTransactionManager rtm) { 27 | return TransactionalOperator.create(rtm); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /reactive-db-postgres/src/main/java/com/djcodes/spring/reactivedb/postgres/Reservation.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb.postgres; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.data.annotation.Id; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class Reservation { 12 | 13 | @Id 14 | private Integer id; 15 | private String name; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /reactive-db-postgres/src/main/java/com/djcodes/spring/reactivedb/postgres/ReservationRepository.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb.postgres; 2 | 3 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 4 | 5 | interface ReservationRepository extends ReactiveCrudRepository { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /reactive-db-postgres/src/main/java/com/djcodes/spring/reactivedb/postgres/ReservationService.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb.postgres; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Service; 5 | import org.springframework.transaction.annotation.Transactional; 6 | import org.springframework.transaction.reactive.TransactionalOperator; 7 | import org.springframework.util.Assert; 8 | import reactor.core.publisher.Flux; 9 | 10 | @Service 11 | @Transactional 12 | @RequiredArgsConstructor 13 | class ReservationService { 14 | 15 | private final ReservationRepository reservationRepository; 16 | private final TransactionalOperator transactionalOperator; 17 | 18 | public Flux saveAll(String... names) { 19 | Flux reservations = Flux.fromArray(names) 20 | .map(name -> new Reservation(null, name)) 21 | .flatMap(r -> this.reservationRepository.save(r)) 22 | .doOnNext(this::assertValid); 23 | return reservations; 24 | //explicitely telling if not using the annotation - @Transactional - @EnableTransactionManagement 25 | //return this.transactionalOperator.transactional(reservations); 26 | } 27 | 28 | private void assertValid(Reservation r) { 29 | Assert.isTrue(r.getName() != null 30 | && r.getName().length() > 2, "the name must be more than 2 letters"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /reactive-db-postgres/src/main/java/com/djcodes/spring/reactivedb/postgres/SampleDataInitializer.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb.postgres; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.log4j.Log4j2; 5 | import org.springframework.boot.context.event.ApplicationReadyEvent; 6 | import org.springframework.context.event.EventListener; 7 | import org.springframework.data.r2dbc.core.DatabaseClient; 8 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 9 | import org.springframework.stereotype.Component; 10 | import reactor.core.publisher.Flux; 11 | 12 | @Component 13 | @RequiredArgsConstructor 14 | @Log4j2 15 | public class SampleDataInitializer { 16 | 17 | private final ReservationService reservationService; 18 | 19 | private final ReservationRepository reservationRepository; 20 | 21 | 22 | @EventListener(ApplicationReadyEvent.class) 23 | public void ready() { 24 | 25 | // Wrong name condition 26 | // Flux reservationFlux = reservationService 27 | // .saveAll("Jane", "John", "Max", "Josh", "Jo"); 28 | 29 | Flux reservationFlux = reservationService 30 | .saveAll("Jane", "John", "Max", "Josh", "Aloy"); 31 | 32 | this.reservationRepository 33 | .deleteAll() 34 | .thenMany(reservationFlux) 35 | .thenMany(this.reservationRepository.findAll()) 36 | .subscribe(log::info); 37 | 38 | 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /reactive-db-postgres/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | spring.r2dbc.url=r2dbc:postgres://localhost 3 | spring.r2dbc.username=postgres 4 | spring.r2dbc.password=password -------------------------------------------------------------------------------- /reactive-db-postgres/src/test/java/com/djcodes/spring/reactivedb/ReactiveDbApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.djcodes.spring.reactivedb; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ReactiveDbApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /reactive-producer-consumer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.mynotes.spring.reactive 8 | reactive-producer-consumer 9 | 0.0.1-SNAPSHOT 10 | pom 11 | 12 | reactive-producer-consumer 13 | Demo 14 | 15 | 16 | reactive-web-producer 17 | reactive-web-consumer 18 | 19 | 20 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.mynotes.spring.reactive 7 | reactive-web-consumer 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | reactive-web-consumer 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.4.5 18 | 19 | 20 | 21 | 22 | 11 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-webflux 29 | 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | true 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-test 39 | test 40 | 41 | 42 | org.junit.vintage 43 | junit-vintage-engine 44 | 45 | 46 | 47 | 48 | io.projectreactor 49 | reactor-test 50 | test 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-maven-plugin 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/Hobby.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp; 2 | 3 | public class Hobby { 4 | 5 | private int personId; 6 | 7 | private String hobby; 8 | 9 | public Hobby() { 10 | 11 | } 12 | 13 | public Hobby(int personId, String hobby) { 14 | super(); 15 | this.personId = personId; 16 | this.hobby = hobby; 17 | } 18 | 19 | public int getPersonId() { 20 | return personId; 21 | } 22 | 23 | public String getHobby() { 24 | return hobby; 25 | } 26 | 27 | public void setPersonId(int personId) { 28 | this.personId = personId; 29 | } 30 | 31 | public void setHobby(String hobby) { 32 | this.hobby = hobby; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/Person.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp; 2 | 3 | public class Person { 4 | 5 | private int id; 6 | 7 | private String name; 8 | 9 | public Person() { 10 | } 11 | 12 | public Person(int id, String name) { 13 | super(); 14 | this.id = id; 15 | this.name = name; 16 | } 17 | 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public void setId(int id) { 27 | this.id = id; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "Person [id=" + id + ", name=" + name + "]"; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/ReactiveWebAppApplication.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.core.io.buffer.DataBuffer; 9 | import org.springframework.http.server.reactive.ServerHttpRequest; 10 | import org.springframework.http.server.reactive.ServerHttpRequestDecorator; 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.web.filter.CommonsRequestLoggingFilter; 13 | import reactor.core.publisher.Flux; 14 | 15 | import java.io.ByteArrayOutputStream; 16 | import java.io.IOException; 17 | import java.nio.channels.Channels; 18 | import java.nio.charset.StandardCharsets; 19 | 20 | @SpringBootApplication 21 | public class ReactiveWebAppApplication { 22 | 23 | public static void main(String[] args) { 24 | SpringApplication.run(ReactiveWebAppApplication.class, args); 25 | } 26 | 27 | // Below wont work with new spring boot > 2.3.2 28 | // @Bean 29 | // public CommonsRequestLoggingFilter requestLoggingFilter() { 30 | // CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter(); 31 | // loggingFilter.setIncludeClientInfo(true); 32 | // loggingFilter.setIncludeQueryString(true); 33 | // loggingFilter.setIncludePayload(true); 34 | // loggingFilter.setIncludeHeaders(false); 35 | // return loggingFilter; 36 | // } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/calls/CallPersonUsingRestTemplate.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.calls; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.client.RestTemplate; 9 | import org.springframework.web.util.DefaultUriBuilderFactory; 10 | 11 | import com.mynotes.spring.reactive.reactivewebapp.Person; 12 | 13 | public class CallPersonUsingRestTemplate { 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(CallPersonUsingRestTemplate.class); 16 | 17 | private static RestTemplate restTemplate = new RestTemplate(); 18 | 19 | static { 20 | String baseUrl = "http://localhost:8080"; 21 | restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(baseUrl)); 22 | } 23 | 24 | public static void main(String[] args) { 25 | Instant start = Instant.now(); 26 | 27 | for (int i = 1; i <= 5; i++) { 28 | restTemplate.getForObject("/person/{id}", Person.class, i); 29 | } 30 | 31 | logTime(start); 32 | } 33 | 34 | private static void logTime(Instant start) { 35 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/calls/CallPersonUsingWebClient_Step1.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.calls; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | 10 | import com.mynotes.spring.reactive.reactivewebapp.Person; 11 | 12 | public class CallPersonUsingWebClient_Step1 { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(CallPersonUsingWebClient_Step1.class); 15 | 16 | private static WebClient client = WebClient.create("http://localhost:8080"); 17 | 18 | public static void main(String[] args) { 19 | 20 | Instant start = Instant.now(); 21 | 22 | for (int i = 1; i <= 5; i++) { 23 | client.get().uri("/person/{id}", i).retrieve().bodyToMono(Person.class); 24 | } 25 | 26 | logTime(start); 27 | } 28 | 29 | private static void logTime(Instant start) { 30 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/calls/CallPersonUsingWebClient_Step2.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.calls; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | 10 | import com.mynotes.spring.reactive.reactivewebapp.Person; 11 | 12 | public class CallPersonUsingWebClient_Step2 { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(CallPersonUsingWebClient_Step2.class); 15 | 16 | private static WebClient client = WebClient.create("http://localhost:8080"); 17 | 18 | public static void main(String[] args) { 19 | 20 | Instant start = Instant.now(); 21 | 22 | for (int i = 1; i <= 5; i++) { 23 | client.get().uri("/person/{id}", i).retrieve().bodyToMono(Person.class).subscribe(); 24 | } 25 | 26 | logTime(start); 27 | } 28 | 29 | private static void logTime(Instant start) { 30 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/calls/CallPersonUsingWebClient_Step3.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.calls; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.web.reactive.function.client.WebClient; 12 | 13 | import com.mynotes.spring.reactive.reactivewebapp.Person; 14 | 15 | import reactor.core.publisher.Mono; 16 | 17 | public class CallPersonUsingWebClient_Step3 { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(CallPersonUsingWebClient_Step3.class); 20 | 21 | private static WebClient client = WebClient.create("http://localhost:8080"); 22 | 23 | public static void main(String[] args) { 24 | 25 | Instant start = Instant.now(); 26 | 27 | List> list = Stream.of(1, 2, 3, 4, 5) 28 | .map(i -> client.get().uri("/person/{id}", i).retrieve().bodyToMono(Person.class)) 29 | .collect(Collectors.toList()); 30 | 31 | Mono.when(list).block(); 32 | 33 | logTime(start); 34 | } 35 | 36 | private static void logTime(Instant start) { 37 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/calls/CallPersonUsingWebClient_Step4.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.calls; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | 10 | import com.mynotes.spring.reactive.reactivewebapp.Person; 11 | 12 | public class CallPersonUsingWebClient_Step4 { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(CallPersonUsingWebClient_Step4.class); 15 | 16 | private static WebClient client = WebClient.create("http://localhost:8080"); 17 | 18 | public static void main(String[] args) { 19 | 20 | Instant start = Instant.now(); 21 | 22 | for (int i = 1; i <= 5; i++) { 23 | client.get().uri("/person/{id}", i).retrieve().bodyToMono(Person.class).block(); 24 | } 25 | 26 | logTime(start); 27 | } 28 | 29 | private static void logTime(Instant start) { 30 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/exception/ExceptionClient.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.exception; 2 | 3 | import com.mynotes.spring.reactive.reactivewebapp.Person; 4 | import com.mynotes.spring.reactive.reactivewebapp.steps.Step2d; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | import reactor.core.publisher.Mono; 10 | 11 | public class ExceptionClient { 12 | 13 | private static final Logger logger = LoggerFactory.getLogger(ExceptionClient.class); 14 | 15 | private static WebClient client = WebClient.create("http://localhost:8080"); 16 | 17 | public static void main(String[] args) throws InterruptedException { 18 | 19 | client.get().uri("/exception/flux") 20 | .retrieve() 21 | .onStatus(HttpStatus::is5xxServerError, clientResponse -> { 22 | Mono errorMono = clientResponse.bodyToMono(String.class); 23 | return errorMono.flatMap(errMsg -> { 24 | logger.debug("Error Message is "+ errMsg); 25 | return Mono.error(new RuntimeException(errMsg)); 26 | }); 27 | }) 28 | ; 29 | 30 | 31 | client.get().uri("/exception/flux") 32 | .exchange() 33 | .flatMapMany(clientResponse -> { 34 | if(clientResponse.statusCode().is5xxServerError()){ 35 | return clientResponse.bodyToMono(String.class) 36 | .flatMap(errMsg -> { 37 | logger.error("Error Message is "+ errMsg); 38 | throw new RuntimeException(errMsg); 39 | }); 40 | }else{ 41 | return clientResponse.bodyToFlux(Integer.class); 42 | } 43 | }); 44 | 45 | 46 | Thread.sleep(5000); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/steps/Step1.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.steps; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.client.RestTemplate; 9 | import org.springframework.web.util.DefaultUriBuilderFactory; 10 | 11 | import com.mynotes.spring.reactive.reactivewebapp.Person; 12 | 13 | public class Step1 { 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(Step1.class); 16 | 17 | private static RestTemplate restTemplate = new RestTemplate(); 18 | 19 | static { 20 | String baseUrl = "http://localhost:8080"; 21 | restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(baseUrl)); 22 | } 23 | 24 | public static void main(String[] args) { 25 | Instant start = Instant.now(); 26 | 27 | for (int i = 1; i <= 5; i++) { 28 | restTemplate.getForObject("/person/{id}", Person.class, i); 29 | } 30 | 31 | logTime(start); 32 | } 33 | 34 | private static void logTime(Instant start) { 35 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/steps/Step2a.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.steps; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | 10 | import com.mynotes.spring.reactive.reactivewebapp.Person; 11 | 12 | public class Step2a { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(Step2a.class); 15 | 16 | private static WebClient client = WebClient.create("http://localhost:8080"); 17 | 18 | public static void main(String[] args) { 19 | 20 | Instant start = Instant.now(); 21 | 22 | for (int i = 1; i <= 3; i++) { 23 | client.get().uri("/person/{id}", i).retrieve().bodyToMono(Person.class); 24 | } 25 | 26 | logTime(start); 27 | } 28 | 29 | private static void logTime(Instant start) { 30 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/steps/Step2a1.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.steps; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | 10 | import com.mynotes.spring.reactive.reactivewebapp.Person; 11 | 12 | public class Step2a1 { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(Step2a1.class); 15 | 16 | private static WebClient client = WebClient.create("http://localhost:8080"); 17 | 18 | public static void main(String[] args) { 19 | 20 | Instant start = Instant.now(); 21 | 22 | for (int i = 1; i <= 3; i++) { 23 | client.get().uri("/person/{id}", i).retrieve().bodyToMono(Person.class).subscribe(); 24 | } 25 | 26 | logTime(start); 27 | } 28 | 29 | private static void logTime(Instant start) { 30 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/steps/Step2a2.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.steps; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | 10 | import com.mynotes.spring.reactive.reactivewebapp.Person; 11 | 12 | public class Step2a2 { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(Step2a2.class); 15 | 16 | private static WebClient client = WebClient.create("http://localhost:8080"); 17 | 18 | public static void main(String[] args) { 19 | 20 | Instant start = Instant.now(); 21 | 22 | for (int i = 1; i <= 5; i++) { 23 | client.get().uri("/person/{id}", i).retrieve().bodyToMono(Person.class).block(); 24 | } 25 | 26 | logTime(start); 27 | } 28 | 29 | private static void logTime(Instant start) { 30 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/steps/Step2b.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.steps; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.web.reactive.function.client.WebClient; 12 | 13 | import com.mynotes.spring.reactive.reactivewebapp.Person; 14 | 15 | import reactor.core.publisher.Mono; 16 | 17 | public class Step2b { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(Step2b.class); 20 | 21 | private static WebClient client = WebClient.create("http://localhost:8080"); 22 | 23 | public static void main(String[] args) { 24 | 25 | Instant start = Instant.now(); 26 | 27 | List> list = Stream.of(1, 2, 3, 4, 5) 28 | .map(i -> client.get().uri("/person/{id}", i).retrieve().bodyToMono(Person.class)) 29 | .collect(Collectors.toList()); 30 | 31 | Mono.when(list).block(); 32 | 33 | logTime(start); 34 | } 35 | 36 | private static void logTime(Instant start) { 37 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/steps/Step2c.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.steps; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | 10 | import com.mynotes.spring.reactive.reactivewebapp.Person; 11 | 12 | import reactor.core.publisher.Flux; 13 | 14 | public class Step2c { 15 | 16 | private static final Logger logger = LoggerFactory.getLogger(Step2c.class); 17 | 18 | private static WebClient client = WebClient.create("http://localhost:8080?delay=2"); 19 | 20 | public static void main(String[] args) { 21 | 22 | Instant start = Instant.now(); 23 | 24 | Flux.range(1, 3).flatMap(i -> client.get().uri("/person/{id}", i).retrieve().bodyToMono(Person.class)) 25 | .blockLast(); 26 | 27 | logTime(start); 28 | } 29 | 30 | private static void logTime(Instant start) { 31 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/steps/Step2d.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.steps; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.http.HttpHeaders; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.web.reactive.function.client.WebClient; 11 | 12 | import com.mynotes.spring.reactive.reactivewebapp.Person; 13 | 14 | import reactor.core.publisher.Flux; 15 | 16 | public class Step2d { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(Step2d.class); 19 | 20 | private static WebClient client = WebClient.create("http://localhost:8080?delay=2"); 21 | 22 | public static void main(String[] args) { 23 | 24 | Instant start = Instant.now(); 25 | 26 | Flux.range(1, 3).flatMap(i -> client.get().uri("/person/{id}", i).exchange().flatMap(response -> { 27 | HttpStatus status = response.statusCode(); 28 | HttpHeaders headers = response.headers().asHttpHeaders(); 29 | logger.debug("Got status=" + status + ", headers=" + headers); 30 | return response.bodyToMono(Person.class); 31 | })).blockLast(); 32 | 33 | logTime(start); 34 | } 35 | 36 | private static void logTime(Instant start) { 37 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/steps/Step2e.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.steps; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | 10 | import com.mynotes.spring.reactive.reactivewebapp.Hobby; 11 | import com.mynotes.spring.reactive.reactivewebapp.Person; 12 | 13 | import reactor.core.publisher.Flux; 14 | 15 | public class Step2e { 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(Step2e.class); 18 | 19 | private static WebClient client = WebClient.create("http://localhost:8080?delay=2"); 20 | 21 | public static void main(String[] args) { 22 | 23 | Instant start = Instant.now(); 24 | 25 | Flux.range(1, 3) 26 | .flatMap(i -> client.get().uri("/person/{id}", i).retrieve().bodyToMono(Person.class).flatMap( 27 | person -> client.get().uri("/person/{id}/hobby", i).retrieve().bodyToMono(Hobby.class))) 28 | .blockLast(); 29 | 30 | logTime(start); 31 | } 32 | 33 | private static void logTime(Instant start) { 34 | logger.debug("Elapsed time: " + Duration.between(start, Instant.now()).toMillis() + "ms"); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/java/com/mynotes/spring/reactive/reactivewebapp/steps/Step3a.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp.steps; 2 | 3 | import org.springframework.web.reactive.function.client.WebClient; 4 | 5 | import com.mynotes.spring.reactive.reactivewebapp.Person; 6 | 7 | public class Step3a { 8 | 9 | private static WebClient client = WebClient.create("http://localhost:8080"); 10 | 11 | public static void main(String[] args) { 12 | 13 | client.get().uri("/persons/events").retrieve().bodyToFlux(Person.class).take(4L).blockFirst(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8081 2 | logging.level.com.mynotes=DEBUG 3 | logging.level.org.springframework=TRACE 4 | spring.http.log-request-details=true 5 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss} [%thread] %logger{0} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-consumer/src/test/java/com/mynotes/spring/reactive/reactivewebapp/ReactiveWebAppApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.reactivewebapp; 2 | 3 | 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | import org.springframework.test.context.junit4.SpringRunner; 6 | 7 | 8 | public class ReactiveWebAppApplicationTests { 9 | 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.mynotes.spring.reactive 7 | reactive-web-producer 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | reactive-web-producer 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.4.5 18 | 19 | 20 | 21 | 22 | 11 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-webflux 29 | 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | true 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-test 39 | test 40 | 41 | 42 | org.junit.vintage 43 | junit-vintage-engine 44 | 45 | 46 | 47 | 48 | io.projectreactor 49 | reactor-test 50 | test 51 | 52 | 53 | 54 | com.github.tomakehurst 55 | wiremock-jre8 56 | 2.28.0 57 | test 58 | 59 | 60 | 61 | com.squareup.okhttp3 62 | okhttp 63 | test 64 | 65 | 66 | com.squareup.okhttp3 67 | mockwebserver 68 | test 69 | 70 | 71 | 72 | org.mock-server 73 | mockserver-netty 74 | 5.11.2 75 | test 76 | 77 | 78 | 79 | 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-maven-plugin 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/Application.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/controller/CalculateController.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | import com.mynotes.spring.reactive.dto.CalculateResponseDTO; 4 | import com.mynotes.spring.reactive.service.CalculateService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import reactor.core.publisher.Mono; 11 | 12 | @RestController 13 | @RequestMapping("/calculate") 14 | public class CalculateController { 15 | 16 | @Autowired 17 | CalculateService calculateService; 18 | 19 | @GetMapping("/{input}") 20 | public Mono calculate(@PathVariable int input) { 21 | return this.calculateService.squareAndRoot(input); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/controller/ExceptionController.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.web.bind.annotation.*; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Mono; 8 | 9 | import java.time.Duration; 10 | 11 | @RestController 12 | @RequestMapping("/exception") 13 | public class ExceptionController { 14 | 15 | @GetMapping("/flux") 16 | public Flux flux(){ 17 | return Flux.just(1,2,3,4) 18 | .concatWith(Mono.error(new RuntimeException("Runtime Exception"))) 19 | .log(); 20 | } 21 | 22 | @GetMapping("/flux/{limit}") 23 | public Flux fluxLimit(@PathVariable int limit){ 24 | 25 | if(limit < 1){ 26 | return Flux.error(new IllegalArgumentException("Limit cannot be less than 1.")); 27 | } 28 | 29 | return Flux.range(1,limit) 30 | .log(); 31 | } 32 | 33 | @ExceptionHandler 34 | public ResponseEntity handleRuntimeException(RuntimeException exception){ 35 | System.out.println("Exception :: "+ exception.getMessage()); 36 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(exception.getMessage()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/controller/FluxMonoController.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | import java.time.Duration; 4 | import org.springframework.http.MediaType; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | @RestController 11 | public class FluxMonoController { 12 | 13 | @GetMapping("/flux") 14 | public Flux flux(){ 15 | return Flux.just(1,2,3,4) 16 | .delayElements(Duration.ofSeconds(1)) 17 | .log(); 18 | } 19 | 20 | @GetMapping(value = "/flux-stream", produces = MediaType.APPLICATION_STREAM_JSON_VALUE) 21 | public Flux fluxStream(){ 22 | return Flux.just(1,2,3,4) 23 | .delayElements(Duration.ofSeconds(1)) 24 | .log(); 25 | } 26 | 27 | @GetMapping(value = "/flux-stream-infinite", produces = MediaType.APPLICATION_STREAM_JSON_VALUE) 28 | public Flux fluxStreamInfinite(){ 29 | return Flux.interval(Duration.ofSeconds(1)) 30 | .log(); 31 | } 32 | 33 | @GetMapping("/mono") 34 | public Mono mono(){ 35 | return Mono.just(1) 36 | .log(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/controller/GreetReactiveController.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | import org.reactivestreams.Publisher; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import reactor.core.publisher.Flux; 12 | 13 | @RestController 14 | public class GreetReactiveController { 15 | 16 | @GetMapping("/greetings") 17 | public Publisher greetingPublisher() { 18 | Flux greetingFlux = Flux.generate(sink -> sink.next(new Greeting("Hello"))).take(50); 19 | return greetingFlux; 20 | } 21 | 22 | @GetMapping(value = "/greetings/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 23 | public Publisher sseGreetings() { 24 | Flux delayElements = Flux 25 | .generate(sink -> sink.next(new Greeting("Hello @" + Instant.now().toString()))) 26 | .delayElements(Duration.ofSeconds(1)); 27 | return delayElements; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/controller/Greeting.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | public class Greeting { 4 | 5 | private String msg; 6 | 7 | public Greeting() {} 8 | 9 | public Greeting(String msg) { 10 | this.msg = msg; 11 | } 12 | 13 | public String getMsg() { 14 | return msg; 15 | } 16 | 17 | public void setMsg(String msg) { 18 | this.msg = msg; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/controller/HttpBinController.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | import com.mynotes.spring.reactive.dto.CalculateResponseDTO; 4 | import com.mynotes.spring.reactive.service.HttpBinService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import reactor.core.publisher.Mono; 11 | 12 | @RestController 13 | @RequestMapping("/httpbin") 14 | public class HttpBinController { 15 | 16 | @Autowired 17 | HttpBinService httpBinService; 18 | 19 | @GetMapping("/call") 20 | public Mono anything() { 21 | return this.httpBinService.binCall(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/controller/Person.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | public class Person { 4 | 5 | private int id; 6 | 7 | private String name; 8 | 9 | public Person() { 10 | 11 | } 12 | 13 | public Person(int id, String name) { 14 | super(); 15 | this.id = id; 16 | this.name = name; 17 | } 18 | 19 | public int getId() { 20 | return id; 21 | } 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public void setId(int id) { 28 | this.id = id; 29 | } 30 | 31 | public void setName(String name) { 32 | this.name = name; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/controller/PersonController.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | public class PersonController { 13 | 14 | private static List personList = new ArrayList<>(); 15 | 16 | static { 17 | personList.add(new Person(1, "John")); 18 | personList.add(new Person(2, "Jane")); 19 | personList.add(new Person(3, "Max")); 20 | personList.add(new Person(4, "Alex")); 21 | personList.add(new Person(5, "Aloy")); 22 | personList.add(new Person(6, "Sarah")); 23 | } 24 | 25 | @GetMapping("/person/{id}") 26 | public Person getPerson(@PathVariable int id, @RequestParam(defaultValue = "2") int delay) 27 | throws InterruptedException { 28 | Thread.sleep(delay * 1000); 29 | return personList.stream().filter((person) -> person.getId() == id).findFirst().get(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/dto/CalculateResponseDTO.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class CalculateResponseDTO { 11 | 12 | int input; 13 | long square; 14 | double sqrt; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/exception/ControllerExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.web.bind.annotation.ControllerAdvice; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | 8 | @ControllerAdvice 9 | public class ControllerExceptionHandler { 10 | 11 | @ExceptionHandler 12 | public ResponseEntity handleRuntimeException(IllegalArgumentException exception){ 13 | System.out.println("Exception :: "+ exception.getMessage()); 14 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exception.getMessage()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/handler/HandlerFunctions.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.handler; 2 | 3 | import org.springframework.http.MediaType; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.web.reactive.function.server.ServerRequest; 6 | import org.springframework.web.reactive.function.server.ServerResponse; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | @Component 11 | public class HandlerFunctions { 12 | 13 | public Mono flux(ServerRequest serverRequest) { 14 | return ServerResponse.ok() 15 | .contentType(MediaType.APPLICATION_JSON) 16 | .body( 17 | Flux.just(1, 2, 3, 4) 18 | .log(), Integer.class 19 | ); 20 | } 21 | 22 | public Mono mono(ServerRequest serverRequest) { 23 | return ServerResponse.ok() 24 | .contentType(MediaType.APPLICATION_JSON) 25 | .body( 26 | Mono.just(1) 27 | .log(), Integer.class 28 | ); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/router/RouterFunctionConfig.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.router; 2 | 3 | import static org.springframework.web.reactive.function.server.RequestPredicates.GET; 4 | import static org.springframework.web.reactive.function.server.RequestPredicates.accept; 5 | 6 | import com.mynotes.spring.reactive.handler.HandlerFunctions; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.web.reactive.function.server.RouterFunction; 11 | import org.springframework.web.reactive.function.server.RouterFunctions; 12 | import org.springframework.web.reactive.function.server.ServerResponse; 13 | 14 | @Configuration 15 | public class RouterFunctionConfig { 16 | 17 | @Bean 18 | public RouterFunction route(HandlerFunctions handlerFunctions){ 19 | return RouterFunctions 20 | .route(GET("/functional/flux").and(accept(MediaType.APPLICATION_JSON)), handlerFunctions::flux) 21 | .andRoute(GET("/functional/mono").and(accept(MediaType.APPLICATION_JSON)), handlerFunctions::mono); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/service/CalculateService.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.service; 2 | 3 | import com.mynotes.spring.reactive.dto.CalculateResponseDTO; 4 | import org.springframework.stereotype.Service; 5 | import reactor.core.publisher.Mono; 6 | 7 | @Service 8 | public class CalculateService { 9 | 10 | //Request and response 11 | public Mono squareAndRoot(int input) { 12 | return Mono.just(input) 13 | .map(this::calculate); 14 | } 15 | 16 | public CalculateResponseDTO calculate(int input) { 17 | return new CalculateResponseDTO(input, Math.multiplyExact(input, input), Math.sqrt(input)); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/java/com/mynotes/spring/reactive/service/HttpBinService.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.service; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.reactive.function.client.ExchangeFilterFunction; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | import reactor.core.publisher.Mono; 10 | 11 | import java.util.stream.Collectors; 12 | 13 | @Service 14 | @Slf4j 15 | public class HttpBinService {; 16 | 17 | private final WebClient webClient; 18 | 19 | public HttpBinService(@Value("${my.service.url:}") String baseURL){ 20 | this.webClient = WebClient.builder().baseUrl(baseURL) 21 | .filter(logRequest()) 22 | .filter(logResposneStatus()) 23 | .defaultHeader("headerKey","headerValue").build(); 24 | } 25 | 26 | public Mono binCall() { 27 | return this.webClient.get().uri("/anything") 28 | .retrieve() 29 | .onStatus(status -> status.equals(HttpStatus.NOT_FOUND), 30 | clientResponse -> Mono.error(new RuntimeException("not_found"))) 31 | .bodyToMono(String.class).onErrorReturn("error"); 32 | } 33 | 34 | private ExchangeFilterFunction logRequest() { 35 | return (clientRequest, next) -> { 36 | log.info("Request: " + clientRequest.method()+ " " + clientRequest.url()); 37 | clientRequest.headers() 38 | .forEach((name, values) -> { 39 | log.info(String.format("Header '%s' = %s", name, values.stream().collect(Collectors.joining(",")))); 40 | }); 41 | return next.exchange(clientRequest); 42 | }; 43 | } 44 | 45 | private ExchangeFilterFunction logResposneStatus() { 46 | return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { 47 | log.info("Response Status {}", clientResponse.statusCode()); 48 | return Mono.just(clientResponse); 49 | }); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhananjay12/learn-spring-reactive/d6670aa45c5d77a1041875126fe3e89c86a5a0a6/reactive-producer-consumer/reactive-web-producer/src/main/resources/application.properties -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/test/java/com/mynotes/spring/reactive/controller/CalculateControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | import com.mynotes.spring.reactive.dto.CalculateResponseDTO; 4 | import com.mynotes.spring.reactive.service.CalculateService; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.mockito.InjectMocks; 8 | import org.mockito.Mock; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; 11 | import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.mock.mockito.MockBean; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.test.context.junit.jupiter.SpringExtension; 16 | import org.springframework.test.web.reactive.server.WebTestClient; 17 | import reactor.core.publisher.Flux; 18 | import reactor.test.StepVerifier; 19 | 20 | @ExtendWith(SpringExtension.class) 21 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 22 | @AutoConfigureWebTestClient 23 | public class CalculateControllerTest { 24 | 25 | @Autowired 26 | WebTestClient webTestClient; 27 | 28 | @Test 29 | public void fluxTest(){ 30 | 31 | // Method 1 32 | Flux result = webTestClient.get().uri("/calculate/1") 33 | .accept(MediaType.APPLICATION_JSON) 34 | .exchange() 35 | .expectStatus().isOk() 36 | .returnResult(CalculateResponseDTO.class) 37 | .getResponseBody(); 38 | 39 | StepVerifier.create(result) 40 | .expectNextCount(1) 41 | .verifyComplete(); 42 | 43 | //Method 2 44 | webTestClient.get().uri("/calculate/9") 45 | .accept(MediaType.APPLICATION_JSON) 46 | .exchange() 47 | .expectStatus().isOk() 48 | .expectBody() 49 | .jsonPath("$.input").isNotEmpty() 50 | .jsonPath("$.square").isEqualTo(81) 51 | .jsonPath("$.sqrt").isEqualTo(3) 52 | ; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/test/java/com/mynotes/spring/reactive/controller/ExceptionControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; 7 | import org.springframework.test.context.junit.jupiter.SpringExtension; 8 | import org.springframework.test.web.reactive.server.WebTestClient; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | @ExtendWith(SpringExtension.class) 13 | @WebFluxTest 14 | class ExceptionControllerTest { 15 | 16 | @Autowired 17 | WebTestClient webTestClient; 18 | 19 | @Test 20 | public void fluxTest(){ 21 | webTestClient.get().uri("/exception/flux") 22 | .exchange() 23 | .expectStatus().is5xxServerError() 24 | .expectBody(String.class) 25 | .isEqualTo("Runtime Exception"); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/test/java/com/mynotes/spring/reactive/controller/FluxMonoControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.test.context.junit.jupiter.SpringExtension; 11 | import org.springframework.test.web.reactive.server.WebTestClient; 12 | import reactor.core.publisher.Flux; 13 | import reactor.test.StepVerifier; 14 | 15 | @ExtendWith(SpringExtension.class) 16 | @WebFluxTest 17 | public class FluxMonoControllerTests { 18 | 19 | @Autowired 20 | WebTestClient webTestClient; 21 | 22 | @Test 23 | public void fluxTest(){ 24 | 25 | // Method 1 26 | Flux intergerFlux = webTestClient.get().uri("/flux") 27 | .accept(MediaType.APPLICATION_JSON) 28 | .exchange() 29 | .expectStatus().isOk() 30 | .returnResult(Integer.class) 31 | .getResponseBody(); 32 | 33 | StepVerifier.create(intergerFlux) 34 | .expectSubscription() 35 | .expectNext(1) 36 | .expectNext(2) 37 | .expectNext(3) 38 | .expectNext(4) 39 | .verifyComplete(); 40 | 41 | //Method 2 42 | webTestClient.get().uri("/flux") 43 | .accept(MediaType.APPLICATION_JSON) 44 | .exchange() 45 | .expectStatus().isOk() 46 | .expectHeader().contentType(MediaType.APPLICATION_JSON) 47 | .expectBodyList(Integer.class) 48 | .hasSize(4); 49 | } 50 | 51 | 52 | @Test 53 | public void fluxStreamInfiniteTest(){ 54 | 55 | Flux longStreamFlux = webTestClient.get().uri("/flux-stream-infinite") 56 | .accept(MediaType.APPLICATION_STREAM_JSON) 57 | .exchange() 58 | .expectStatus().isOk() 59 | .returnResult(Long.class) 60 | .getResponseBody(); 61 | 62 | StepVerifier.create(longStreamFlux) 63 | .expectNext(0l) 64 | .expectNext(1l) 65 | .expectNext(2l) 66 | .thenCancel() 67 | .verify(); 68 | } 69 | 70 | 71 | @Test 72 | public void monoTest(){ 73 | // Different way 74 | Integer expectedValue = Integer.valueOf(1); 75 | 76 | webTestClient.get().uri("/mono") 77 | .accept(MediaType.APPLICATION_JSON) 78 | .exchange() 79 | .expectStatus().isOk() 80 | .expectBody(Integer.class) 81 | .consumeWith((response) -> { 82 | assertEquals(expectedValue, response.getResponseBody()); 83 | }); 84 | 85 | } 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/test/java/com/mynotes/spring/reactive/controller/HttpBinControllerMockWebServerTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | 4 | import com.github.tomakehurst.wiremock.WireMockServer; 5 | import com.github.tomakehurst.wiremock.client.WireMock; 6 | import com.github.tomakehurst.wiremock.core.WireMockConfiguration; 7 | import com.mynotes.spring.reactive.Application; 8 | import com.mynotes.spring.reactive.service.HttpBinService; 9 | import lombok.extern.slf4j.Slf4j; 10 | import okhttp3.mockwebserver.Dispatcher; 11 | import okhttp3.mockwebserver.MockResponse; 12 | import okhttp3.mockwebserver.MockWebServer; 13 | import okhttp3.mockwebserver.RecordedRequest; 14 | import org.junit.jupiter.api.*; 15 | import org.junit.jupiter.api.extension.ExtendWith; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; 18 | import org.springframework.boot.test.context.SpringBootTest; 19 | import org.springframework.http.MediaType; 20 | import org.springframework.test.context.DynamicPropertyRegistry; 21 | import org.springframework.test.context.DynamicPropertySource; 22 | import org.springframework.test.context.TestPropertySource; 23 | import org.springframework.test.context.junit.jupiter.SpringExtension; 24 | import org.springframework.test.web.reactive.server.WebTestClient; 25 | 26 | import java.io.IOException; 27 | 28 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 29 | 30 | @ExtendWith(SpringExtension.class) 31 | @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 32 | @AutoConfigureWebTestClient 33 | @Slf4j 34 | public class HttpBinControllerMockWebServerTest { 35 | 36 | @Autowired 37 | WebTestClient webTestClient; 38 | 39 | public static MockWebServer mockBackEnd; 40 | 41 | @BeforeAll 42 | static void setUp() throws IOException { 43 | mockBackEnd = new MockWebServer(); 44 | mockBackEnd.start(); 45 | // Use Dynamic Properties register instead of this 46 | //System.setProperty("my.service.url", String.format("http://localhost:%s",mockBackEnd.getPort())); 47 | } 48 | @DynamicPropertySource 49 | static void registerPgProperties(DynamicPropertyRegistry registry) { 50 | registry.add("my.service.url", 51 | () -> String.format("http://localhost:%s",mockBackEnd.getPort())); 52 | } 53 | 54 | 55 | @AfterAll 56 | static void tearDown() throws IOException { 57 | mockBackEnd.shutdown(); 58 | } 59 | 60 | 61 | 62 | @Test 63 | public void httpBinAnything() { 64 | 65 | mockBackEnd.enqueue(new MockResponse().setBody("test").setResponseCode(200)); 66 | 67 | //Method 1 68 | webTestClient.get().uri("/httpbin/call") 69 | .accept(MediaType.APPLICATION_JSON) 70 | .exchange() 71 | .expectStatus().isOk() 72 | .expectBody(String.class) 73 | .isEqualTo("test") 74 | ; 75 | } 76 | 77 | @Test 78 | public void httpBinAnything_with_Dispatcher() { 79 | 80 | final Dispatcher dispatcher = new Dispatcher() { 81 | @Override 82 | public MockResponse dispatch(RecordedRequest request) { 83 | switch (request.getPath()) { 84 | case "/anything": 85 | return new MockResponse().setResponseCode(200).setBody("test"); 86 | case "/users/2": 87 | return new MockResponse().setResponseCode(500); 88 | case "/users/3": 89 | return new MockResponse().setResponseCode(200).setBody("{\"id\": 1, \"name\":\"duke\"}"); 90 | } 91 | return new MockResponse().setResponseCode(404); 92 | } 93 | }; 94 | 95 | mockBackEnd.setDispatcher(dispatcher); 96 | 97 | //Method 1 98 | webTestClient.get().uri("/httpbin/call") 99 | .accept(MediaType.APPLICATION_JSON) 100 | .exchange() 101 | .expectStatus().isOk() 102 | .expectBody(String.class) 103 | .isEqualTo("test") 104 | ; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/test/java/com/mynotes/spring/reactive/controller/HttpBinControllerWireMockTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.controller; 2 | 3 | 4 | import com.github.tomakehurst.wiremock.WireMockServer; 5 | import com.github.tomakehurst.wiremock.client.WireMock; 6 | import com.github.tomakehurst.wiremock.core.WireMockConfiguration; 7 | import com.mynotes.spring.reactive.Application; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.junit.jupiter.api.AfterEach; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.extension.ExtendWith; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.http.MediaType; 17 | import org.springframework.test.context.TestPropertySource; 18 | import org.springframework.test.context.junit.jupiter.SpringExtension; 19 | import org.springframework.test.web.reactive.server.WebTestClient; 20 | 21 | import static com.github.tomakehurst.wiremock.client.WireMock.ok; 22 | 23 | @ExtendWith(SpringExtension.class) 24 | @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 25 | @AutoConfigureWebTestClient 26 | @TestPropertySource(properties = {"my.service.url="+ HttpBinControllerWireMockTest.externalUri}) 27 | @Slf4j 28 | public class HttpBinControllerWireMockTest { 29 | 30 | static final String externalUri = "http://localhost:12345"; 31 | 32 | @Autowired 33 | WebTestClient webTestClient; 34 | 35 | private WireMockServer wireMockServer; 36 | 37 | @BeforeEach 38 | public void startWireMock() { 39 | wireMockServer = new WireMockServer(WireMockConfiguration.options().port(12345)); 40 | wireMockServer.start(); 41 | } 42 | @AfterEach 43 | public void stopWireMock() { 44 | wireMockServer.stop(); 45 | wireMockServer = null; 46 | } 47 | 48 | @Test 49 | public void httpBinAnything() { 50 | 51 | wireMockServer 52 | .stubFor( 53 | WireMock.get("/anything").willReturn(ok("test"))); 54 | 55 | //Method 1 56 | webTestClient.get().uri("/httpbin/call") 57 | .accept(MediaType.APPLICATION_JSON) 58 | .exchange() 59 | .expectStatus().isOk() 60 | .expectBody(String.class) 61 | .isEqualTo("test") 62 | ; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/test/java/com/mynotes/spring/reactive/router/RouterTests.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.router; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient; 7 | import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.test.context.junit.jupiter.SpringExtension; 11 | import org.springframework.test.web.reactive.server.WebTestClient; 12 | import reactor.core.publisher.Flux; 13 | import reactor.test.StepVerifier; 14 | 15 | @ExtendWith(SpringExtension.class) 16 | @SpringBootTest 17 | @AutoConfigureWebTestClient 18 | public class RouterTests { 19 | 20 | @Autowired 21 | WebTestClient webTestClient; 22 | 23 | @Test 24 | public void fluxTest() { 25 | 26 | // Method 1 27 | Flux intergerFlux = webTestClient.get().uri("/functional/flux") 28 | .accept(MediaType.APPLICATION_JSON) 29 | .exchange() 30 | .expectStatus().isOk() 31 | .returnResult(Integer.class) 32 | .getResponseBody(); 33 | 34 | StepVerifier.create(intergerFlux) 35 | .expectSubscription() 36 | .expectNext(1) 37 | .expectNext(2) 38 | .expectNext(3) 39 | .expectNext(4) 40 | .verifyComplete(); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/test/java/com/mynotes/spring/reactive/service/HttpBinServiceMockServerNettyTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.service; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.mockserver.integration.ClientAndServer; 8 | import org.mockserver.socket.PortFactory; 9 | import reactor.core.publisher.Mono; 10 | import reactor.test.StepVerifier; 11 | 12 | import static org.mockserver.model.HttpRequest.request; 13 | import static org.mockserver.model.HttpResponse.response; 14 | 15 | public class HttpBinServiceMockServerNettyTest { 16 | 17 | private static int mockTestServerPort; 18 | protected ClientAndServer mockTestServer; 19 | private HttpBinService httpBinService; 20 | 21 | @BeforeAll 22 | public static void beforeClass() { 23 | mockTestServerPort = PortFactory.findFreePort(); 24 | } 25 | 26 | @BeforeEach 27 | public void setUp() { 28 | mockTestServer = ClientAndServer. 29 | startClientAndServer(mockTestServerPort); 30 | httpBinService = new HttpBinService("http://localhost:" + mockTestServer.getPort()); 31 | } 32 | 33 | @AfterEach 34 | public void cleanup() { 35 | mockTestServer.stop(); 36 | } 37 | 38 | @Test 39 | public void bindCall_success() { 40 | mockTestServer 41 | .when(request() 42 | .withPath("/anything") 43 | .withHeader("headerKey", "headerValue")) 44 | .respond( 45 | response().withStatusCode(200) 46 | .withBody("test")); 47 | 48 | Mono result = httpBinService.binCall(); 49 | 50 | StepVerifier.create(result) 51 | .expectNextMatches(res -> res.equalsIgnoreCase("test")) 52 | .verifyComplete(); 53 | 54 | } 55 | 56 | @Test 57 | public void binCall_header_check_not_found() { 58 | mockTestServer 59 | .when(request() 60 | .withPath("/anything") 61 | .withHeader("invalid", "invalid")) 62 | .respond( 63 | response().withStatusCode(404)); 64 | 65 | Mono result = httpBinService.binCall(); 66 | 67 | StepVerifier.create(result) 68 | .expectErrorMessage("not_found"); 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/test/java/com/mynotes/spring/reactive/service/HttpBinServiceMockWebServerTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.service; 2 | 3 | 4 | import okhttp3.mockwebserver.MockResponse; 5 | import okhttp3.mockwebserver.MockWebServer; 6 | import org.junit.jupiter.api.AfterAll; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | import reactor.core.publisher.Mono; 11 | import reactor.test.StepVerifier; 12 | 13 | import java.io.IOException; 14 | 15 | class HttpBinServiceMockWebServerTest { 16 | 17 | public static MockWebServer mockBackEnd; 18 | 19 | private HttpBinService httpBinService; 20 | 21 | @BeforeAll 22 | static void setUp() throws IOException { 23 | mockBackEnd = new MockWebServer(); 24 | mockBackEnd.start(); 25 | } 26 | 27 | @AfterAll 28 | static void tearDown() throws IOException { 29 | mockBackEnd.shutdown(); 30 | } 31 | 32 | @BeforeEach 33 | void initialize() { 34 | String baseUrl = String.format("http://localhost:%s", 35 | mockBackEnd.getPort()); 36 | httpBinService = new HttpBinService(baseUrl); 37 | } 38 | 39 | @Test 40 | public void binCallTest(){ 41 | mockBackEnd.enqueue(new MockResponse().setBody("test").setResponseCode(200)); 42 | 43 | Mono result = httpBinService.binCall(); 44 | 45 | StepVerifier.create(result) 46 | .expectNextMatches(res -> res.equalsIgnoreCase("test")) 47 | .verifyComplete(); 48 | } 49 | 50 | @Test 51 | public void binCallTest_invalid(){ 52 | mockBackEnd.enqueue(new MockResponse().setBody("test1").setResponseCode(200)); 53 | 54 | Mono result = httpBinService.binCall(); 55 | 56 | StepVerifier.create(result) 57 | .expectNextMatches(res -> !res.equalsIgnoreCase("test")) 58 | .verifyComplete(); 59 | } 60 | 61 | 62 | } -------------------------------------------------------------------------------- /reactive-producer-consumer/reactive-web-producer/src/test/java/com/mynotes/spring/reactive/service/HttpBinServiceWireMockTest.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.spring.reactive.service; 2 | 3 | import com.github.tomakehurst.wiremock.WireMockServer; 4 | import com.github.tomakehurst.wiremock.client.WireMock; 5 | import com.github.tomakehurst.wiremock.core.WireMockConfiguration; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.http.HttpHeaders; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import reactor.core.publisher.Mono; 13 | import reactor.test.StepVerifier; 14 | 15 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | import static org.junit.jupiter.api.Assertions.*; 18 | 19 | class HttpBinServiceWireMockTest { 20 | 21 | private WireMockServer wireMockServer; 22 | private HttpBinService httpBinService; 23 | 24 | @BeforeEach 25 | public void startWireMock() { 26 | wireMockServer = new WireMockServer(WireMockConfiguration.options().dynamicPort()); 27 | wireMockServer.start(); 28 | httpBinService = new HttpBinService(wireMockServer.baseUrl()); 29 | } 30 | @AfterEach 31 | public void stopWireMock() { 32 | wireMockServer.stop(); 33 | wireMockServer = null; 34 | } 35 | 36 | @Test 37 | public void binCall_success() { 38 | 39 | wireMockServer 40 | .stubFor( 41 | WireMock.get("/anything").withHeader("headerKey", equalTo("headerValue")).willReturn( 42 | okJson("test"))); 43 | 44 | Mono result = httpBinService.binCall(); 45 | 46 | StepVerifier.create(result) 47 | .expectNextMatches(res -> res.equalsIgnoreCase("test")) 48 | .verifyComplete(); 49 | } 50 | 51 | @Test 52 | public void binCall_header_check_not_found() { 53 | 54 | wireMockServer 55 | .stubFor( 56 | WireMock.get("/anything").withHeader("invalid", equalTo("invalid")).willReturn( 57 | okJson("test"))); 58 | 59 | Mono result = httpBinService.binCall(); 60 | 61 | StepVerifier.create(result) 62 | .expectErrorMessage("not_found"); 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /reactive-web/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /reactive-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.djcodes.spring 7 | reactive-web 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | reactive-web 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.2.6.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 13 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-webflux 31 | 32 | 33 | org.projectlombok 34 | lombok 35 | true 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-test 40 | test 41 | 42 | 43 | io.projectreactor 44 | reactor-test 45 | test 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-maven-plugin 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /reactive-web/src/main/java/com/example/reactive/reactiveweb/FunctionalJavaConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.reactive.reactiveweb; 2 | 3 | import static org.springframework.web.reactive.function.server.RequestPredicates.GET; 4 | import static org.springframework.web.reactive.function.server.RouterFunctions.route; 5 | 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.web.reactive.function.server.RouterFunction; 9 | import org.springframework.web.reactive.function.server.ServerResponse; 10 | 11 | import reactor.core.publisher.Flux; 12 | 13 | @Configuration 14 | public class FunctionalJavaConfiguration { 15 | 16 | @Bean 17 | RouterFunction routes(){ 18 | return route(GET("/route/hello"), request -> ServerResponse.ok().body(Flux.just("Hello World"),String.class)) 19 | .andRoute(GET("/route/hi"), request -> ServerResponse.ok().body(Flux.just("Hi"),String.class)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /reactive-web/src/main/java/com/example/reactive/reactiveweb/Greeting.java: -------------------------------------------------------------------------------- 1 | package com.example.reactive.reactiveweb; 2 | 3 | public class Greeting { 4 | 5 | private String text; 6 | 7 | public Greeting(String text) { 8 | this.text=text; 9 | } 10 | public String getText() { 11 | return text; 12 | } 13 | 14 | public void setText(String text) { 15 | this.text = text; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /reactive-web/src/main/java/com/example/reactive/reactiveweb/GreetingsRestController.java: -------------------------------------------------------------------------------- 1 | package com.example.reactive.reactiveweb; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import org.reactivestreams.Publisher; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import reactor.core.publisher.Flux; 10 | 11 | @RestController 12 | public class GreetingsRestController { 13 | 14 | @GetMapping(value = "/greeting/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE) 15 | Publisher sseGreetings() { 16 | return Flux 17 | .generate(sink -> sink.next(new Greeting("Hello @" + Instant.now().toString()))) 18 | .delayElements(Duration.ofSeconds(1)) 19 | ; 20 | } 21 | 22 | @GetMapping("/greeting") 23 | Publisher greetingPublisher() { 24 | Flux greetingFlux = Flux.generate(sink -> sink.next(new Greeting("Hello"))).take(100); 25 | return greetingFlux; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /reactive-web/src/main/java/com/example/reactive/reactiveweb/ReactiveWebApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.reactive.reactiveweb; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ReactiveWebApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ReactiveWebApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /reactive-web/src/main/java/com/example/reactive/reactiveweb/WebSocketConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.reactive.reactiveweb; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.Collections; 6 | 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.web.reactive.HandlerMapping; 10 | import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; 11 | import org.springframework.web.reactive.socket.WebSocketHandler; 12 | import org.springframework.web.reactive.socket.WebSocketMessage; 13 | import org.springframework.web.reactive.socket.WebSocketSession; 14 | import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter; 15 | 16 | import reactor.core.publisher.Flux; 17 | import reactor.core.publisher.Mono; 18 | 19 | @Configuration 20 | public class WebSocketConfiguration { 21 | 22 | @Bean 23 | WebSocketHandlerAdapter webSocketHandlerAdapter() { 24 | return new WebSocketHandlerAdapter(); 25 | } 26 | 27 | @Bean 28 | WebSocketHandler webSocketHandler() { 29 | return session -> { 30 | 31 | Flux generate = Flux 32 | .generate(sink -> sink.next(new Greeting("Hello @" + Instant.now().toString()))) 33 | .map(g -> session.textMessage(g.getText())).delayElements(Duration.ofSeconds(1)) 34 | .doFinally(signalType -> System.out.println("Goodbye")); 35 | return session.send(generate); 36 | }; 37 | } 38 | 39 | @Bean 40 | HandlerMapping handlerMapping() { 41 | SimpleUrlHandlerMapping simpleUrlHandlerMapping=new SimpleUrlHandlerMapping(); 42 | simpleUrlHandlerMapping.setUrlMap(Collections.singletonMap("/ws/hello",webSocketHandler())); 43 | simpleUrlHandlerMapping.setOrder(10); 44 | 45 | return simpleUrlHandlerMapping; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /reactive-web/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhananjay12/learn-spring-reactive/d6670aa45c5d77a1041875126fe3e89c86a5a0a6/reactive-web/src/main/resources/application.properties -------------------------------------------------------------------------------- /reactive-web/src/main/resources/static/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /reactive-web/src/test/java/com/example/reactive/reactiveweb/ReactiveWebApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.reactive.reactiveweb; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class ReactiveWebApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /webflux-in-servlet/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /webflux-in-servlet/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.5 9 | 10 | 11 | com.mynotes.reactive.demo 12 | webflux-in-servlet 13 | 0.0.1-SNAPSHOT 14 | webflux-in-servlet 15 | Demo project for Spring Boot 16 | 17 | 11 18 | 2020.0.2 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-webflux 24 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-tomcat 36 | 37 | 38 | org.springframework.cloud 39 | spring-cloud-starter-kubernetes-fabric8-all 40 | 41 | 42 | org.projectlombok 43 | lombok 44 | true 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-test 49 | test 50 | 51 | 52 | 53 | com.squareup.okhttp3 54 | okhttp 55 | test 56 | 57 | 58 | com.squareup.okhttp3 59 | mockwebserver 60 | test 61 | 62 | 63 | 64 | 65 | 66 | org.springframework.cloud 67 | spring-cloud-dependencies 68 | ${spring-cloud.version} 69 | pom 70 | import 71 | 72 | 73 | 74 | 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-maven-plugin 79 | 80 | 81 | 82 | org.projectlombok 83 | lombok 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /webflux-in-servlet/src/main/java/com/mynotes/reactive/demo/webfluxinservlet/controller/WebfluxInServletApplication.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.reactive.demo.webfluxinservlet.controller; 2 | 3 | import com.mynotes.reactive.demo.webfluxinservlet.controller.exceptions.RestTemplateResponseErrorHandler; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.web.client.RestTemplate; 9 | 10 | @SpringBootApplication 11 | public class WebfluxInServletApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(WebfluxInServletApplication.class, args); 15 | } 16 | 17 | @Bean 18 | @LoadBalanced 19 | public RestTemplate restTemplate(){ 20 | return new RestTemplate(); 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /webflux-in-servlet/src/main/java/com/mynotes/reactive/demo/webfluxinservlet/controller/controller/ApiController.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.reactive.demo.webfluxinservlet.controller.controller; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.http.HttpHeaders; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.http.server.reactive.ServerHttpRequest; 7 | import org.springframework.util.MultiValueMap; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestHeader; 10 | import org.springframework.web.bind.annotation.ResponseBody; 11 | import org.springframework.web.bind.annotation.RestController; 12 | import org.springframework.web.server.ServerWebExchange; 13 | import reactor.core.publisher.Flux; 14 | import reactor.core.publisher.Mono; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | import java.time.Duration; 18 | import java.util.Enumeration; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.stream.Collectors; 22 | 23 | @RestController 24 | @Slf4j 25 | public class ApiController { 26 | 27 | @GetMapping("/headers") 28 | @ResponseBody 29 | public Map headers(@RequestHeader MultiValueMap headers) { 30 | 31 | Map map = new HashMap<>(); 32 | 33 | headers.forEach((key, value) -> { 34 | log.info(String.format("Header '%s' = %s", key, value.stream().collect(Collectors.joining("|")))); 35 | map.put(key, value.stream().collect(Collectors.joining("|"))); 36 | }); 37 | 38 | return map; 39 | } 40 | 41 | @GetMapping("/check1") 42 | public Mono check1() { 43 | 44 | return Mono.just("check1"); 45 | } 46 | 47 | @GetMapping(value ="/delay" , produces= MediaType.TEXT_EVENT_STREAM_VALUE) 48 | public Flux delay(ServerWebExchange exchange) { 49 | 50 | return Flux.just("delay1","delay2","delay3").delayElements(Duration.ofSeconds(1)).log(); 51 | } 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /webflux-in-servlet/src/main/java/com/mynotes/reactive/demo/webfluxinservlet/controller/controller/HttpBinController.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.reactive.demo.webfluxinservlet.controller.controller; 2 | 3 | import com.mynotes.reactive.demo.webfluxinservlet.controller.exceptions.RestTemplateResponseErrorHandler; 4 | import com.mynotes.reactive.demo.webfluxinservlet.controller.exceptions.WebClientCustomException; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import org.springframework.web.client.RestTemplate; 12 | import org.springframework.web.reactive.function.client.WebClient; 13 | import reactor.core.publisher.Mono; 14 | 15 | @RestController 16 | @RequestMapping("/bin") 17 | public class HttpBinController { 18 | 19 | private final RestTemplate restTemplate; 20 | 21 | private final WebClient webClient; 22 | 23 | private final String baseURL; 24 | 25 | public HttpBinController(RestTemplate restTemplate, 26 | @Value("${my.service.url:https://httpbin.org}") String baseURL, 27 | RestTemplateResponseErrorHandler handler){ 28 | this.restTemplate = restTemplate; 29 | this.restTemplate.setErrorHandler(handler); 30 | this.webClient = WebClient.builder().baseUrl(baseURL).build(); 31 | this.baseURL = baseURL; 32 | } 33 | 34 | @GetMapping("/rest/get") 35 | public ResponseEntity restTemplateGet(){ 36 | return this.restTemplate.getForEntity(baseURL+"/get",String.class); 37 | } 38 | 39 | @GetMapping("/web/get") 40 | public Mono webClientGet(){ 41 | return this.webClient.get().uri(baseURL+"/get") 42 | .retrieve() 43 | .onStatus(HttpStatus::is4xxClientError, 44 | clientResponse -> Mono.error(new WebClientCustomException("Client 4xx error", clientResponse.statusCode()))) 45 | .onStatus(HttpStatus::is5xxServerError, 46 | clientResponse -> Mono.error(new WebClientCustomException("Client 5xx error", clientResponse.statusCode()))) 47 | .bodyToMono(String.class); 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /webflux-in-servlet/src/main/java/com/mynotes/reactive/demo/webfluxinservlet/controller/exceptions/AppExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.reactive.demo.webfluxinservlet.controller.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.web.bind.annotation.ExceptionHandler; 6 | import org.springframework.web.bind.annotation.ResponseBody; 7 | import org.springframework.web.bind.annotation.RestControllerAdvice; 8 | 9 | @RestControllerAdvice 10 | public class AppExceptionHandler { 11 | 12 | @ResponseBody 13 | @ExceptionHandler(value = RestTemplateCustomException.class) 14 | public ResponseEntity handleException(RestTemplateCustomException exception) { 15 | return ResponseEntity.status(exception.getStatus()).body(exception.getMsg()); 16 | } 17 | 18 | @ResponseBody 19 | @ExceptionHandler(value = WebClientCustomException.class) 20 | public ResponseEntity handleException(WebClientCustomException exception) { 21 | return ResponseEntity.status(exception.getStatus()).body(exception.getMsg()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /webflux-in-servlet/src/main/java/com/mynotes/reactive/demo/webfluxinservlet/controller/exceptions/RestTemplateCustomException.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.reactive.demo.webfluxinservlet.controller.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class RestTemplateCustomException extends RuntimeException { 6 | 7 | private static final long serialVersionUID = 1L; 8 | private String msg; 9 | private HttpStatus status; 10 | 11 | public RestTemplateCustomException(String msg, HttpStatus status) { 12 | this.msg = msg; 13 | this.status = status; 14 | 15 | } 16 | 17 | public String getMsg() { 18 | return msg; 19 | } 20 | 21 | public HttpStatus getStatus() { 22 | return status; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /webflux-in-servlet/src/main/java/com/mynotes/reactive/demo/webfluxinservlet/controller/exceptions/RestTemplateResponseErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.reactive.demo.webfluxinservlet.controller.exceptions; 2 | 3 | import java.io.IOException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.client.ClientHttpResponse; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.client.ResponseErrorHandler; 8 | 9 | @Component 10 | public class RestTemplateResponseErrorHandler implements ResponseErrorHandler { 11 | 12 | @Override 13 | public boolean hasError(ClientHttpResponse httpResponse) 14 | throws IOException { 15 | 16 | return ( 17 | httpResponse.getStatusCode().is4xxClientError() 18 | || httpResponse.getStatusCode().is5xxServerError()); 19 | } 20 | 21 | @Override 22 | public void handleError(ClientHttpResponse httpResponse) 23 | throws IOException { 24 | 25 | if (httpResponse.getStatusCode() 26 | .series() == HttpStatus.Series.SERVER_ERROR) { 27 | throw new RestTemplateCustomException("Server 5xx error rest template", httpResponse.getStatusCode()); 28 | } else if (httpResponse.getStatusCode() 29 | .series() == HttpStatus.Series.CLIENT_ERROR) { 30 | throw new RestTemplateCustomException("Client 4xx error rest template",httpResponse.getStatusCode()); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /webflux-in-servlet/src/main/java/com/mynotes/reactive/demo/webfluxinservlet/controller/exceptions/WebClientCustomException.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.reactive.demo.webfluxinservlet.controller.exceptions; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class WebClientCustomException extends RuntimeException { 6 | 7 | private static final long serialVersionUID = 1L; 8 | private String msg; 9 | private HttpStatus status; 10 | 11 | public WebClientCustomException(String msg, HttpStatus status) { 12 | this.msg = msg; 13 | this.status = status; 14 | 15 | } 16 | 17 | public String getMsg() { 18 | return msg; 19 | } 20 | 21 | public HttpStatus getStatus() { 22 | return status; 23 | } 24 | } -------------------------------------------------------------------------------- /webflux-in-servlet/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /webflux-in-servlet/src/test/java/com/mynotes/reactive/demo/webfluxinservlet/controller/ApiControllerIT.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.reactive.demo.webfluxinservlet.controller; 2 | 3 | import java.io.IOException; 4 | import lombok.extern.slf4j.Slf4j; 5 | import okhttp3.mockwebserver.MockWebServer; 6 | import org.junit.jupiter.api.AfterAll; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.ExtendWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.test.context.DynamicPropertyRegistry; 15 | import org.springframework.test.context.DynamicPropertySource; 16 | import org.springframework.test.context.junit.jupiter.SpringExtension; 17 | import org.springframework.test.web.reactive.server.WebTestClient; 18 | 19 | @ExtendWith(SpringExtension.class) 20 | @SpringBootTest(classes = WebfluxInServletApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 21 | @AutoConfigureWebTestClient 22 | @Slf4j 23 | public class ApiControllerIT { 24 | 25 | WebTestClient webTestClient; 26 | 27 | @Test 28 | public void httpBinAnything() { 29 | 30 | //Method 1 31 | webTestClient.get().uri("/check1") 32 | .accept(MediaType.APPLICATION_JSON) 33 | .exchange() 34 | .expectStatus().isOk() 35 | .expectBody(String.class) 36 | .isEqualTo("check1") 37 | ; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /webflux-in-servlet/src/test/java/com/mynotes/reactive/demo/webfluxinservlet/controller/HttpBinControllerIT.java: -------------------------------------------------------------------------------- 1 | package com.mynotes.reactive.demo.webfluxinservlet.controller; 2 | 3 | import java.io.IOException; 4 | import lombok.extern.slf4j.Slf4j; 5 | import okhttp3.mockwebserver.Dispatcher; 6 | import okhttp3.mockwebserver.MockResponse; 7 | import okhttp3.mockwebserver.MockWebServer; 8 | import okhttp3.mockwebserver.RecordedRequest; 9 | import org.junit.jupiter.api.AfterAll; 10 | import org.junit.jupiter.api.BeforeAll; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.extension.ExtendWith; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.http.MediaType; 17 | import org.springframework.test.context.DynamicPropertyRegistry; 18 | import org.springframework.test.context.DynamicPropertySource; 19 | import org.springframework.test.context.junit.jupiter.SpringExtension; 20 | import org.springframework.test.web.reactive.server.WebTestClient; 21 | 22 | @ExtendWith(SpringExtension.class) 23 | @SpringBootTest(classes = WebfluxInServletApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 24 | @AutoConfigureWebTestClient 25 | @Slf4j 26 | public class HttpBinControllerIT { 27 | 28 | @Autowired 29 | WebTestClient webTestClient; 30 | 31 | public static MockWebServer mockBackEnd; 32 | 33 | @BeforeAll 34 | static void setUp() throws IOException { 35 | mockBackEnd = new MockWebServer(); 36 | mockBackEnd.start(); 37 | } 38 | @DynamicPropertySource 39 | static void registerPgProperties(DynamicPropertyRegistry registry) { 40 | registry.add("my.service.url", 41 | () -> String.format("http://localhost:%s",mockBackEnd.getPort())); 42 | } 43 | 44 | 45 | @AfterAll 46 | static void tearDown() throws IOException { 47 | mockBackEnd.shutdown(); 48 | } 49 | 50 | @Test 51 | public void webClientGet_200() { 52 | 53 | final Dispatcher dispatcher = new Dispatcher() { 54 | @Override 55 | public MockResponse dispatch(RecordedRequest request) { 56 | switch (request.getPath()) { 57 | case "/get": 58 | return new MockResponse().setResponseCode(200).setBody("test"); 59 | } 60 | return new MockResponse().setResponseCode(404); 61 | } 62 | }; 63 | 64 | mockBackEnd.setDispatcher(dispatcher); 65 | 66 | webTestClient.get().uri("/bin/web/get") 67 | .accept(MediaType.APPLICATION_JSON) 68 | .exchange() 69 | .expectStatus().isOk() 70 | .expectBody(String.class) 71 | .isEqualTo("test"); 72 | } 73 | 74 | @Test 75 | public void webClientGet_4xx() { 76 | 77 | final Dispatcher dispatcher = new Dispatcher() { 78 | @Override 79 | public MockResponse dispatch(RecordedRequest request) { 80 | switch (request.getPath()) { 81 | case "/get": 82 | return new MockResponse().setResponseCode(417).setBody("Expectation failed"); 83 | } 84 | return new MockResponse().setResponseCode(404); 85 | } 86 | }; 87 | 88 | mockBackEnd.setDispatcher(dispatcher); 89 | 90 | webTestClient.get().uri("/bin/web/get") 91 | .accept(MediaType.APPLICATION_JSON) 92 | .exchange() 93 | //Because of exception handling its 4xx else it would be 500 because we are putting mono.error 94 | .expectStatus().is4xxClientError() 95 | .expectBody(String.class) 96 | .isEqualTo("Client 4xx error"); 97 | } 98 | 99 | @Test 100 | public void webClientGet_5xx() { 101 | 102 | final Dispatcher dispatcher = new Dispatcher() { 103 | @Override 104 | public MockResponse dispatch(RecordedRequest request) { 105 | switch (request.getPath()) { 106 | case "/get": 107 | return new MockResponse().setResponseCode(500).setBody("Server error"); 108 | } 109 | return new MockResponse().setResponseCode(404); 110 | } 111 | }; 112 | 113 | mockBackEnd.setDispatcher(dispatcher); 114 | 115 | webTestClient.get().uri("/bin/web/get") 116 | .accept(MediaType.APPLICATION_JSON) 117 | .exchange() 118 | //In exception handling we are setting the same status what we got from call 119 | .expectStatus().is5xxServerError() 120 | .expectBody(String.class) 121 | .isEqualTo("Client 5xx error"); 122 | } 123 | 124 | @Test 125 | public void restTemplateGet_200() { 126 | 127 | final Dispatcher dispatcher = new Dispatcher() { 128 | @Override 129 | public MockResponse dispatch(RecordedRequest request) { 130 | switch (request.getPath()) { 131 | case "/get": 132 | return new MockResponse().setResponseCode(200).setBody("test"); 133 | } 134 | return new MockResponse().setResponseCode(404); 135 | } 136 | }; 137 | 138 | mockBackEnd.setDispatcher(dispatcher); 139 | 140 | webTestClient.get().uri("/bin/rest/get") 141 | .accept(MediaType.APPLICATION_JSON) 142 | .exchange() 143 | .expectStatus().isOk() 144 | .expectBody(String.class) 145 | .isEqualTo("test") 146 | ; 147 | } 148 | 149 | @Test 150 | public void restTemplateGet_4xx() { 151 | 152 | final Dispatcher dispatcher = new Dispatcher() { 153 | @Override 154 | public MockResponse dispatch(RecordedRequest request) { 155 | switch (request.getPath()) { 156 | case "/get": 157 | return new MockResponse().setResponseCode(417).setBody("Expectation failed"); 158 | } 159 | return new MockResponse().setResponseCode(404); 160 | } 161 | }; 162 | 163 | mockBackEnd.setDispatcher(dispatcher); 164 | 165 | webTestClient.get().uri("/bin/rest/get") 166 | .accept(MediaType.APPLICATION_JSON) 167 | .exchange() 168 | //Because of exception handling its 4xx else it would be 500 because we are putting mono.error 169 | .expectStatus().is4xxClientError() 170 | .expectBody(String.class) 171 | .isEqualTo("Client 4xx error rest template") 172 | ; 173 | } 174 | 175 | @Test 176 | public void restTemplateGet_5xx() { 177 | 178 | final Dispatcher dispatcher = new Dispatcher() { 179 | @Override 180 | public MockResponse dispatch(RecordedRequest request) { 181 | switch (request.getPath()) { 182 | case "/get": 183 | return new MockResponse().setResponseCode(500).setBody("Server error"); 184 | } 185 | return new MockResponse().setResponseCode(404); 186 | } 187 | }; 188 | 189 | mockBackEnd.setDispatcher(dispatcher); 190 | 191 | webTestClient.get().uri("/bin/rest/get") 192 | .accept(MediaType.APPLICATION_JSON) 193 | .exchange() 194 | //In exception handling we are setting the same status what we got from call 195 | .expectStatus().is5xxServerError() 196 | .expectBody(String.class) 197 | .isEqualTo("Server 5xx error rest template") 198 | ; 199 | } 200 | 201 | } 202 | --------------------------------------------------------------------------------