Skip to content

Latest commit

 

History

History
218 lines (161 loc) · 7.03 KB

@Controller.md

File metadata and controls

218 lines (161 loc) · 7.03 KB

@Controller로 webFlux를 사용해보자.

프로젝트 생성

스프링부트 프로젝트를 생성하여 build.gradle에 Dependencies를 설정한다. 필요한 의존성과, webflux 의존성을 설정해준다.

implementation 'org.springframework.boot:spring-boot-starter-webflux'

Flux 반환 유형

Flux는 Reactive Streams의 Publisher를 구현한 N개 요소의 스트림을 표현하는 Reactor 클래스이다. 기본적으로 text/plain으로 응답이 반환되지만, Server-Sent EventJSON Stream으로 반환할 수도 있다.

Flux의 반환 유형은 클라이언트가 헤더에 응답 유형을 어떻게 설정하느냐에 따라 달라진다.

아래같은 코드가 있다고 해보자.

@RestController
public class HelloController {

    @GetMapping("/")
    Flux<String> hello() {
        return Flux.just("Hello", "World");
    }
}

일반적으로 요청을 보내면 text/plain으로 반환이 온다.

//text/plain
$ curl -i localhost:8080
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    10    0    10    0     0    909      0 --:--:-- --:--:-- --:--:--  1000HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/plain;charset=UTF-8

HelloWorld

Accept 헤더에 text/event-stream를 지정하면 Server-Sent Event, application/stream+json를 지정하면 JSON Stream으로 반환된다. (하지만 위 컨트롤러 코드에서는 단순 문자열을 반환했기 때문에, JSON과 plain text의 차이가 없다.)

//text/event-stream
$ curl -i localhost:8080 -H 'Accept: text/event-stream'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    24    0    24    0     0   1846      0 --:--:-- --:--:-- --:--:--  2000HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream;charset=UTF-8

data:Hello

data:World
//application/stream+json
$ curl -i localhost:8080 -H 'Accept: application/stream+json'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    10    0    10    0     0    714      0 --:--:-- --:--:-- --:--:--   769HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/stream+json;charset=UTF-8

HelloWorld

무한 Stream

Flux 반환을 java.util.stream.Stream형으로 주는 것도 가능하다. 다음은 stream 메소드를 작성하여, 무한 Stream을 작성하고, 그 중에 10건을 Flux로 변환하여 반환해 보자.

@RestController
public class HelloController {

    @GetMapping("/")
    Flux<String> hello() {
        return Flux.just("Hello", "World");
    }

    @GetMapping("/stream")
    Flux<Map<String, Integer>> stream() {
        Stream<Integer> stream = Stream.iterate(0, i -> i + 1); // Java8의 무한Stream
        return Flux.fromStream(stream.limit(10))
                .map(i -> Collections.singletonMap("value", i));
    }
}

/stream에 대한 세 가지 응답은 각각와 아래와 같다.

일반 JSON

$ curl -i localhost:8080/stream
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   121    0   121    0     0  11000      0 --:--:-- --:--:-- --:--:-- 12100HTTP/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'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   180    0   180    0     0  12000      0 --:--:-- --:--:-- --:--:-- 12000HTTP/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'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   120    0   120    0     0   7500      0 --:--:-- --:--:-- --:--:--  7500HTTP/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}

application/jsonapplication/stream+json의 차이를 볼 수 있다.

만약 코드에서 limit을 붙이지 않고 코드를 아래와 같이 작성한다면 무한 Stream을 받을 수도 있다. (단 application/json의 경우에는 응답이 반환되지 않을 것이다.)

    @GetMapping("/stream")
    Flux<Map<String, Integer>> stream() {
        Stream<Integer> stream = Stream.iterate(0, i -> i + 1); // Java8의 무한Stream
        return Flux.fromStream(stream)
                .map(i -> Collections.singletonMap("value", i));
    }

요청인자를 비동기로

요청을 받는 것 또한 비동기적으로 처리할 수 있다.

@RequestBody으로 요청 본문으로 받아 대문자로 변환하는 map의 결과 Mono를 그대로 반환하는 메소드를 추가해보자. 일반적으로 String으로 요청을 받는다면 NonBlocking으로 동기화 처리되지만, Mono에 감싸서 받으면 chain/compose로 비동기처리할 수 있게 된다.

Mono는 1개 또는 0개의 요소를 가지도록 한다.

@RestController
public class HelloController {

    @GetMapping("/")
    Flux<String> hello() {
        return Flux.just("Hello", "World");
    }

    @GetMapping("/stream")
    Flux<Map<String, Integer>> stream() {
        Stream<Integer> stream = Stream.iterate(0, i -> i + 1);
        return Flux.fromStream(stream).zipWith(Flux.interval(Duration.ofSeconds(1)))
                .map(tuple -> Collections.singletonMap("value", tuple.getT1() /* 튜플의 첫 번째 요소 = Stream<Integer> 요소 */));
    }

    @PostMapping("/echo")
    Mono<String> echo(@RequestBody Mono<String> body) {
        return body.map(String::toUpperCase);
    }
}
$  curl -i localhost:8080/echo -H 'Content-Type: application/json' -d rlaisqls
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    14  100     7  100     7   1166   1166 --:--:-- --:--:-- --:--:--  2800HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
Content-Length: 7

RLAISQLS

1건만 처리해야 한다면 Mono를 사용하는 것이 명시적이지만, 여러 건수의 Stream을 처리하고 싶다면 Flux로 해야 한다.