티스토리 뷰

서버

스프링웹플럭스_3

Hilu 2021. 11. 24. 21:53

오늘은 SpringWeb Flux 도입을 하기 위한 기초 단계로 Webclient 에 샘플 프로젝트를 통해서 알아 보도록 하겠다.

 

Spring WebFlux 샘플코드 

1. 샘플 프로젝트 다운로드

  • mac 에서 샘플 프로젝트 다운로드
curl https://start.spring.io/starter.tgz  \
       -d bootVersion=2.4.4 \
       -d dependencies=webflux \
       -d baseDir=spring-webflux-tutorial \
       -d artifactId=webflux \
       -d packageName=com.devkuma.webflux \
       -d applicationName=HelloWebFluxApplication \
       -d type=gradle-project | tar -xzvf -

 

2. SpringWebFlux Dependency 추가

 

Maven

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webflux -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webflux</artifactId>
    <version>5.3.12</version>
</dependency>

 

Gradle

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-webflux'
}

 

3. Controller 예제

@RestController
public class HelloController {

    @GetMapping("/")
    Flux<String> hello() {
        return Flux.just("Hello", "World");
    }
}
  • 위의 코드를 실행 하면 아래와 같이 출력된다.
$ curl -i localhost:8080/

HTTP/1.1 200 OK

transfer-encoding: chunked

Content-Type: text/plain;charset=UTF-8



HelloWorld
  • Flux 는 Reactive Streams의 Publisher 를 구현한 N 요소의 스트림을 표현하는 Reactor Class 이다. 기본적으로 text plain 으로 반환 된다. 아래와 같은 형식으로도 반환이 가능하다고 한다.
    • Server-Sent Event (스트림 단위로 잘라서 리턴해 준다)
$ curl -i localhost:8080 -H 'Accept: text/event-stream'
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream;charset=UTF-8

data:Hello

data:World

 

    • JSON Stream (Json 경우는 text/plain 과 같은 응답이온다)
$ curl -i localhost:8080 -H 'Accept: application/stream+json'
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/stream+json;charset=UTF-8

HelloWorld

  • 무한 스트림 (Stream) 생성 예제 (10건)
@GetMapping("/stream")
Flux<Map<String, Integer>> stream() {
  Stream<Integer> stream= Stream.iterate(0, i -> i + 1);
  return Flux.fromStream(stream.limit(10))
  .map(i -> Collections.singletonMap("value", i));
}

 

  • JSON 응답 
curl -i localhost:8080/stream
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/json

[{"value":0},{"value":1},{"value":2},{"value":3},{"value":4},{"value":5},{"value":6},{"value":7},{"value":8},{"value":9}]
  • Server-Sent Event 응답
curl -i localhost:8080/stream -H 'Accept: text/event-stream'
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream;charset=UTF-8

data:{"value":0}

data:{"value":1}

data:{"value":2}

data:{"value":3}

data:{"value":4}

data:{"value":5}

data:{"value":6}

data:{"value":7}

data:{"value":8}

data:{"value":9}
  • JSON Stream
curl -i localhost:8080/stream -H 'Accept: application/stream+json'
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/stream+json

{"value":0}
{"value":1}
{"value":2}
{"value":3}
{"value":4}
{"value":5}
{"value":6}
{"value":7}
{"value":8}
{"value":9}

  • Post 
  • Controller (Mono type)
@PostMapping("/postSample")
Mono<String> postSample(@RequestBody Mono<String> body) {
	return body.map(String::toUpperCase);
}​

 

 

=> Mono 는 0~1 개의 Publisher(data) 이고, Flux 는 0~N 개의 Publisher(data) 이다.

  • postSample Result
curl -i localhost:8080/postSample -H 'Content-Type: application/json' -d sampletest
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
Content-Length: 10

SAMPLETEST

 

  • Controller (Flux type)
 @PostMapping("/postSampleFlux")
    Flux<Map<String, String>> postSampleFlux(@RequestBody Mono<String> body) {
        Stream<Integer> stream = Stream.iterate(0, i -> i + 1);
        return Flux.fromStream(stream.limit(5))
                .map(i -> Collections.singletonMap("value", body.map(String::toString) + "-" + i.toString()));
    }
  • postSampleFlux Result
curl -i localhost:8080/postSampleFlux -H 'Content-Type: application/json' -d sampletest
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/json

[{"value":"MonoMap-0"},{"value":"MonoMap-1"},{"value":"MonoMap-2"},{"value":"MonoMap-3"},{"value":"MonoMap-4"}]

MVC 에서의 Controller 모델과는 다른 Router Functions 모델

=> MVC 에서는 어노테이션으(@RestController, GetMapping, PostMapping...) 로 Router를 선언해 준 모델이라면, Routor Functions 모델은 어노테이션 대신에 경로화 Handler 조합으로 Router 를 선언 한다.

 

Get

 

WebApplication

 

Get Sample Code

@SpringBootApplication
public class HelloWebFluxApplication {

	public static void main(String[] args) {
		SpringApplication.run(HelloWebFluxApplication.class, args);
	}

	@Bean
	RouterFunction<ServerResponse> routes() {
		return RouterFunctions.route(RequestPredicates.GET("/"), request -> ServerResponse.ok().body(Flux.just("Hello", "World!"), String.class));
	}
}

 

Static Import Sample Code

	@Bean
	RouterFunction<ServerResponse> simpleRoutes() {
		return route(GET("/"), request ->
				ok().body(Flux.just("Hello", "World!"), String.class));
	}

 

Result

curl -i localhost:8080/ -H 'Content-Type: application/json'
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/plain;charset=UTF-8

HelloWorld!

 

Handler 를 사용한 Get Router 설정

 

#HelloRouter

@Configuration
public class HelloRouter {

    @Bean
    RouterFunction<ServerResponse> routes(HelloHandler helloHandler) {
        return route(GET("/"), helloHandler::hello)
                .andRoute(GET("/stream"), helloHandler::stream)
                ;
    }
}

 

#HelloHandler

package com.devkuma.webflux;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.Map;
import java.util.stream.Stream;

import static org.springframework.web.reactive.function.BodyInserters.fromPublisher;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

@Component
public class HelloHandler {

    public Mono<ServerResponse> hello(ServerRequest req) {
        return ok().body(Flux.just("Hello", "World!"), String.class);
    }

    public Mono<ServerResponse> stream(ServerRequest req) {
        Stream<Integer> stream = Stream.iterate(0, i -> i + 1);
        Flux<Map<String, Integer>> flux = Flux.fromStream(stream.limit(10))
                .map(i -> Collections.singletonMap("value", i));
        return ok().contentType(MediaType.APPLICATION_JSON)
                .body(fromPublisher(flux, new ParameterizedTypeReference<Map<String, Integer>>(){}));

    }

}

 

Result

curl -i localhost:8080/ -H 'Content-Type: application/json'
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/plain;charset=UTF-8

HelloWorld!




curl -i localhost:8080/stream
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/json

[{"value":0},{"value":1},{"value":2},{"value":3},{"value":4},{"value":5},{"value":6},{"value":7},{"value":8},{"value":9}]

 

Handler 를 사용한 Post Router 설정

 

#HelloRouter

  @Bean
    RouterFunction<ServerResponse> routes(HelloHandler helloHandler) {
        return route(GET("/"), helloHandler::hello)
                .andRoute(GET("/stream"), helloHandler::stream)
                .andRoute(POST("/echo"), helloHandler::echo)
                .andRoute(POST("/postSample"), helloHandler::postSampleFlux)
                ;
    }

#HelloHandler

@Component
public class HelloHandler {

    public Mono<ServerResponse> hello(ServerRequest req) {
        return ok().body(Flux.just("Hello", "World!"), String.class);
    }

    public Mono<ServerResponse> stream(ServerRequest req) {
        Stream<Integer> stream = Stream.iterate(0, i -> i + 1);
        Flux<Map<String, Integer>> flux = Flux.fromStream(stream.limit(10))
                .map(i -> Collections.singletonMap("value", i));
        return ok().contentType(MediaType.APPLICATION_JSON)
                .body(fromPublisher(flux, new ParameterizedTypeReference<Map<String, Integer>>(){}));

    }

    public Mono<ServerResponse> echo(ServerRequest req) {
        Mono<String> body = req.bodyToMono(String.class).map(String::toUpperCase);
        return ok().body(body,String.class);
    }

    public Mono <ServerResponse> postSampleFlux(ServerRequest req) {
        Flux<Map<String, Integer>> body = req.body(toFlux(
                new ParameterizedTypeReference<Map<String, Integer>>(){}));

        return ok().contentType(MediaType.TEXT_EVENT_STREAM)
                .body(fromPublisher(body.map(m->Collections.singletonMap("double", m.get("value") * 2))
                , new ParameterizedTypeReference<Map<String, Integer>>(){})
        );
    }
}

 

Result

curl -i localhost:8080/echo -d sampletest
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
Content-Length: 10

SAMPLETEST

curl -i localhost:8080/postSample -d '{"value":1}{"value":2}{"value":3}' -H 'Content-Type: application/stream+json'  -H 'Accept: text/event-stream'
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream;charset=UTF-8

data:{"double":2}

data:{"double":4}

data:{"double":6}

 

참고 : 

 

: https://github.com/spring-guides/gs-reactive-rest-service

 

GitHub - spring-guides/gs-reactive-rest-service: Building a Reactive RESTful Web Service :: Learn how to create a RESTful web se

Building a Reactive RESTful Web Service :: Learn how to create a RESTful web service with Reactive Spring and consume it with WebClient. - GitHub - spring-guides/gs-reactive-rest-service: Building ...

github.com

 

참조 : 

https://medium.com/@odysseymoon/spring-webclient-%EC%82%AC%EC%9A%A9%EB%B2%95-5f92d295edc0

 

Spring WebClient 사용법

Spring 어플리케이션에서 HTTP 요청을 할 땐 주로 RestTemplate 을 사용했었습니다. 하지만 Spring 5.0 버전부터는 RestTemplate 은 유지 모드로 변경되고 향후 deprecated 될 예정입니다.

medium.com

http://www.devkuma.com/pages/1514

 

프로그램 개발 지식 공유, devkuma

데브쿠마는 프로그래밍 개발에 대한 지식을 공유합니다.

www.devkuma.com

 

'서버' 카테고리의 다른 글

스프링웹플럭스_5  (1) 2022.01.08
스프링웹플럭스_4  (0) 2021.12.17
스프링웹플럭스_2  (0) 2021.11.11
스프링웹플럭스_1  (0) 2021.11.10
Spring - Constructor Injection  (0) 2019.05.07
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함