티스토리 뷰
오늘은 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
참조 :
https://medium.com/@odysseymoon/spring-webclient-%EC%82%AC%EC%9A%A9%EB%B2%95-5f92d295edc0
http://www.devkuma.com/pages/1514
- Total
- Today
- Yesterday
- MSA
- EC2
- 웹서비스
- kafka
- nodejs
- data crawling
- MQ
- 테스트주도개발
- GateWayApi
- 웹개발
- AWS
- 켄트 백
- SpringBoot
- TDD
- Python #FastAPI
- 테스트 주도 개발
- 테스트
- mongodb
- 퀜트백
- Python
- fastapi
- 분산처리
- data mining
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |