3.2. ์›น์†Œ์ผ“ API(WebSocket API)

์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์›น์†Œ์ผ“ ๋ฉ”์‹œ์ง€๋ฅผ ํ•ธ๋“ค๋งํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž‘์„ฑํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์›น์†Œ์ผ“ API๋ฅผ ์ œ๊ณตํ•œ๋‹ค.


3.2.1. ์„œ๋ฒ„(Server)

์›น์†Œ์ผ“ ์„œ๋ฒ„๋ฅผ ์ž‘์„ฑํ•˜๋ ค๋ฉด, ๋จผ์ € WebSocketHandler๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ๋Š” ์ด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค:

Java:

import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;

public class MyWebSocketHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        // ...
    }
}

Kotlin:

import org.springframework.web.reactive.socket.WebSocketHandler
import org.springframework.web.reactive.socket.WebSocketSession

class MyWebSocketHandler : WebSocketHandler {

    override fun handle(session: WebSocketSession): Mono<Void> {
        // ...
    }
}

๊ทธ ๋‹ค์Œ์—๋Š” ํ•ธ๋“ค๋Ÿฌ๋ฅผ URL์— ๋งตํ•‘ํ•˜๊ณ  WebSocketHandlerAdapter๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ๋ฅผ ์ฐธ์กฐํ•˜๋ผ:

Java:

@Configuration
class WebConfig {

    @Bean
    public HandlerMapping handlerMapping() {
        Map<String, WebSocketHandler> map = new HashMap<>();
        map.put("/path", new MyWebSocketHandler());
        int order = -1; // before annotated controllers

        return new SimpleUrlHandlerMapping(map, order);
    }

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter();
    }
}

Kotlin:

@Configuration
class WebConfig {

    @Bean
    fun handlerMapping(): HandlerMapping {
        val map = mapOf("/path" to MyWebSocketHandler())
        val order = -1 // before annotated controllers

        return SimpleUrlHandlerMapping(map, order)
    }

    @Bean
    fun handlerAdapter() =  WebSocketHandlerAdapter()
}


3.2.2. WebSocketHandler

WebSocketHandler์˜ handle ๋ฉ”์„œ๋“œ๋Š” WebSocketSession์„ ๋ฐ›์•„์„œ ์„ธ์…˜ ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋œ ๊ฒƒ์„ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด Mono<Void>๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์„ธ์…˜์€ ๋‘ ๊ฐœ์˜ ์ŠคํŠธ๋ฆผ์„ ํ†ตํ•ด ์ฒ˜๋ฆฌ๋˜๋Š”๋ฐ, ๊ฐ๊ฐ ์ธ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€์™€ ์•„์›ƒ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€๋ฅผ ์œ„ํ•œ ๊ฒƒ์ด๋‹ค. ๋‹ค์Œ ํ‘œ๋Š” ์ŠคํŠธ๋ฆผ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด์„œ ์„ค๋ช…ํ•œ๋‹ค:

WebSocketSession ๋ฉ”์„œ๋“œ ์„ค๋ช…
Flux<WebSocketMessage> receive() ์ธ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€ ์ŠคํŠธ๋ฆผ์— ์ ‘๊ทผํ•˜๊ณ  ์ปค๋„ฅ์…˜์ด ๋‹ซํž ๋•Œ ์™„๋ฃŒ๋œ๋‹ค.
Mono<Void> send(Publisher<WebSocketMessage>) ์ „์†กํ•  ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์•„ ๋ฉ”์‹œ์ง€๋ฅผ ์ž‘์„ฑํ•˜๊ณ , ์†Œ์Šค๊ฐ€ ์™„๋ฃŒ๋˜๊ณ  ๋ฉ”์‹œ์ง€ ์ž‘์„ฑ์ด ๋๋‚  ๋•Œ, Mono<Void>๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

WebSocketHandler๋Š” ์ธ๋ฐ”์šด๋“œ์™€ ์•„์šด๋ฐ”์šด๋“œ ์ŠคํŠธ๋ฆผ์„ ํ•˜๋‚˜๋กœ ํ†ตํ•ฉํ•œ ํ”Œ๋กœ์šฐ๋กœ ๊ตฌ์„ฑํ•˜๊ณ , ์ด ํ”Œ๋กœ์šฐ์˜ ์™„๋ฃŒ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” Mono<Void>๋ฅผ ๋ฆฌํ„ดํ•ด์•ผ ํ•œ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์š”๊ตฌ ์‚ฌํ•ญ์— ๋”ฐ๋ผ์„œ ํ†ตํ•ฉ๋œ ํ”Œ๋กœ์šฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ ์™„๋ฃŒ๋œ๋‹ค:

  • ์ธ๋ฐ”์šด๋“œ ๋˜๋Š” ์•„์›ƒ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€ ์ŠคํŠธ๋ฆผ์ด ์™„๋ฃŒ๋์„ ๋•Œ
  • ์ธ๋ฐ”์šด๋“œ ์ŠคํŠธ๋ฆผ์ด ์™„๋ฃŒ๋˜๊ณ (์ฆ‰, ์ปค๋„ฅ์…˜์ด ๋‹ซํ˜”์„ ๋•Œ), ์•„์›ƒ๋ฐ”์šด๋“œ ์ŠคํŠธ๋ฆผ์ด ๋ฌดํ•œ ์ŠคํŠธ๋ฆผ์ผ ๋•Œ
  • ์„ ํƒ๋œ ์‹œ์ ์—์„œ, WebSocketSession์˜ close ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด์„œ

์ธ๋ฐ”์šด๋“œ์™€ ์•„์›ƒ๋ฐ”์ธ๋“œ ๋ฉ”์‹œ์ง€ ์ŠคํŠธ๋ฆผ์ด ํ•จ๊ป˜ ๊ตฌ์„ฑ๋œ ๊ฒฝ์šฐ, ์ปค๋„ฅ์…˜์ด ์—ด๋ ค์žˆ๋Š”์ง€ ํ™•ใ…‡๋‹ˆํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. ๋ฆฌ์•กํ‹ฐ๋ธŒ ์ŠคํŠธ๋ฆผ ์‹œ๊ทธ๋„์ด ํ™œ๋™์„ ์ข…๋ฃŒํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ธ๋ฐ”์šด๋“œ ์ŠคํŠธ๋ฆผ์€ ์™„๋ฃŒ ๋˜๋Š” ์˜ค๋ฅ˜ ์‹ ํ˜ธ๋ฅผ ์ˆ˜์‹ ํ•˜๊ณ  ์•„์›ƒ๋ฐ”์šด๋“œ ์ŠคํŠธ๋ฆผ์€ ์ทจ์†Œ ์‹ ํ˜ธ๋ฅผ ์ˆ˜์‹ ํ•œ๋‹ค.

ํ•ธ๋“ค๋Ÿฌ์˜ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๊ตฌํ˜„์ฒด๋Š” ์ธ๋ฐ”์šด๋“œ ์ŠคํŠธ๋ฆผ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ๋Š” ์ด๋ฅผ ๋ณด์—ฌ์ค€๋‹ค:

Java:

class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        return session.receive()            (1)
                .doOnNext(message -> {
                    // ...                  (2)
                })
                .concatMap(message -> {
                    // ...                  (3)
                })
                .then();                    (4)
    }
}

Kotlin:

class ExampleHandler : WebSocketHandler {

    override fun handle(session: WebSocketSession): Mono<Void> {
        return session.receive()            (1)
                .doOnNext {
                    // ...                  (2)
                }
                .concatMap {
                    // ...                  (3)
                }
                .then()                     (4)
    }
}

(1) ์ธ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€ ์ŠคํŠธ๋ฆผ์— ์ ‘๊ทผํ•œ๋‹ค.
(2) ๊ฐ ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.
(3) ๋ฉ”์‹œ์ง€ ์ฝ˜ํ…์ธ ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ค‘์ฒฉ๋œ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
(4) ์ˆ˜์‹ ์ด ์™„๋ฃŒ๋˜๋ฉด Mono<Void>๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ค‘์ฒฉ๋œ ๋น„๋™๊ธฐ ์ž‘์—…์˜ ๊ฒฝ์šฐ, ํ’€๋ง๋œ ๋ฐ์ดํ„ฐ ๋ฒ„ํผ(pooled data buffers)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋ฒ„(์˜ˆ๋ฅผ ๋“ค๋ฉด, Netty)์—์„œ message.retain()์„ ํ˜ธ์ถœํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ธฐ ์ „์— ๋ฒ„ํผ๊ฐ€ ๋น„์›Œ์งˆ ์ˆ˜ ์žˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋ฐ์ดํ„ฐ ๋ฒ„ํผ์™€ ์ฝ”๋ฑ(Data Buffers and Codecs)์„ ์ฐธ๊ณ ํ•˜๋ผ.

๋‹ค์Œ์€ ์ธ๋ฐ”์šด๋“œ์™€ ์•„์›ƒ๋ฐ”์šด๋“œ ์ŠคํŠธ๋ฆผ์„ ๊ฒฐํ•ฉํ•œ๋‹ค:

Java:

class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {

        Flux<WebSocketMessage> output = session.receive()               (1)
                .doOnNext(message -> {
                    // ...
                })
                .concatMap(message -> {
                    // ...
                })
                .map(value -> session.textMessage("Echo " + value));    (2)

        return session.send(output);                                    (3)
    }
}

Kotlin:

class ExampleHandler : WebSocketHandler {

    override fun handle(session: WebSocketSession): Mono<Void> {

        val output = session.receive()                      (1)
                .doOnNext {
                    // ...
                }
                .concatMap {
                    // ...
                }
                .map { session.textMessage("Echo $it") }    (2)

        return session.send(output)                         (3)
    }
}

(1) ์ธ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€ ์ŠคํŠธ๋ฆผ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.
(2) ์•„์›ƒ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ฒฐํ•ฉ๋œ ํ”Œ๋กœ์šฐ๋ฅผ ๋งŒ๋“ ๋‹ค.
(3) ์ˆ˜์‹ ํ•˜๋Š” ๋™์•ˆ์—๋Š” ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒํ•˜์ง€ ์•Š๋Š” Mono<Void>๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ธ๋ฐ”์šด๋“œ์™€ ์•„์›ƒ๋ฐ”์šด๋“œ ์ŠคํŠธ๋ฆผ์€ ๋…๋ฆฝ์ ์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์™„๋ฃŒ์‹œ์—๋งŒ ๊ฒฐํ•ฉ๋  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค์Œ์€ ๊ทธ ์˜ˆ์ œ๋‹ค:

Java:

class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {

        Mono<Void> input = session.receive()                                (1)
                .doOnNext(message -> {
                    // ...
                })
                .concatMap(message -> {
                    // ...
                })
                .then();

        Flux<String> source = ... ;
        Mono<Void> output = session.send(source.map(session::textMessage)); (2)

        return Mono.zip(input, output).then();                              (3)
    }
}

Kotlin:

class ExampleHandler : WebSocketHandler {

    override fun handle(session: WebSocketSession): Mono<Void> {

        val input = session.receive()                                   
                .doOnNext {
                    // ...
                }
                .concatMap {
                    // ...
                }
                .then()

        val source: Flux<String> = ...
        val output = session.send(source.map(session::textMessage))     

        return Mono.zip(input, output).then()                           
    }
}

(1) ์ธ๋ฐ”์šด๋“œ ๋ฉ”์‹œ์ง€ ์ŠคํŠธ๋ฆผ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.
(2) ๋ฐœ์‹  ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•œ๋‹ค.
(3) ์ŠคํŠธ๋ฆผ์„ ๊ฒฐํ•ฉํ•˜๊ณ  ๋‘ ์ŠคํŠธ๋ฆผ์ด ๋ชจ๋‘ ๋๋‚˜๋ฉด ์ข…๋ฃŒํ•˜๋Š” Mono<Void>๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.


3.2.3. DataBuffer

DataBuffer๋Š” ์›นํ”Œ๋Ÿญ์Šค์˜ ๋ฐ”์ดํŠธ ๋ฒ„ํผ๋‹ค. ๊ด€๋ จํ•ด์„œ๋Š” ์Šคํ”„๋ง ์ฝ”์–ด ๋ ˆํผ๋Ÿฐ์Šค์˜ ๋ฐ์ดํ„ฐ ๋ฒ„ํผ์™€ ์ฝ”๋ฑ(Data Buffers and Codecs) ์„น์…˜์—์„œ ๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•œ๋‹ค. ์ค‘์š”ํ•œ ์ ์€ ๋„คํ‹ฐ(Netty)์™€ ๊ฐ™์€ ์ผ๋ถ€ ์„œ๋ฒ„์—์„œ๋Š” ๋ฐ”์ดํŠธ ๋ฒ„ํผ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ ํ’€์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ฐธ์กฐ๋ฅผ ์นด์šดํŠธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ํ”ผํ•˜๋ ค๋ฉด ์†Œ๋น„(consume)ํ•œ ๋‹ค์Œ์—๋Š” ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ•ด์ œํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

๋„คํ‹ฐ์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ, ์ž…๋ ฅ ๋ฐ์ดํ„ฐ ๋ฒ„ํผ๊ฐ€ ํ•ด์ œ๋˜์ง€ ์•Š๊ณ  ์œ ์ง€ํ•ด์•ผ ํ•œ๋‹ค๋ฉด DataBufferUtils.retain(dataBuffer)๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๋ฒ„ํผ์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์†Œ๋น„ํ–ˆ๋‹ค๋ฉด DataBufferUtils.release(dataBuffer)๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค.


3.2.4. Handshake

WebSocketHandlerAdapter๋Š” WebSocketService์— ์ฒ˜๋ฆฌ๋ฅผ ์œ„์ž„ํ•œ๋‹ค. ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด HandshakeWebSocketService๋Š” ์›น์†Œ์ผ“ ์š”์ฒญ์— ๋Œ€ํ•œ ๊ธฐ๋ณธ์ ์ธ ๊ฒ€์‚ฌ๋ฅผ ํ•œ ๋‹ค์Œ์— ๊ตฌ๋™์ค‘์ธ ์„œ๋ฒ„์— ๋Œ€ํ•ด์„œ RequestUpgradeStrategy๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ํ˜„์žฌ ๋ฆฌ์•กํ„ฐ ๋„คํ‹ฐ(Reactor Netty), ํ†ฐ์บฃ(Tomcat), ์ œํ‹ฐ(Jetty) ๊ทธ๋ฆฌ๊ณ  ์–ธ๋”ํ† ์šฐ(Undertow)๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€์›ํ•œ๋‹ค.

HandshakeWebSocketService๋Š” sessionAttributePredicate ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, Predicate<String>๋ฅผ ์„ค์ •ํ•˜์—ฌ WebSession์œผ๋กœ๋ถ€ํ„ฐ ์†์„ฑ์„ ์ถ”์ถœํ•˜๊ณ  WebSocketSession์˜ ์†์„ฑ์œผ๋กœ ์ž…๋ ฅํ•œ๋‹ค.


3.2.5. ์„œ๋ฒ„ ์„ค์ •(Server Configuration)

๊ฐ ์„œ๋ฒ„์˜ RequestUpgradeStrategy ๊ตฌํ˜„์ฒด๋ฅผ ์ด์šฉํ•˜์—ฌ ์›น์†Œ์ผ“ ์—”์ง„ ๊ด€๋ จํ•œ ์›น์†Œ์ผ“ ๊ด€๋ จ ์„ค์ •์„ ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ๋Š” ํ†ฐ์บฃ์—์„œ ์‹คํ–‰๋˜๋Š” ์›น์†Œ์ผ“ ์˜ต์…˜์„ ์„ค์ •ํ•œ๋‹ค:

Java:

@Configuration
class WebConfig {

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter(webSocketService());
    }

    @Bean
    public WebSocketService webSocketService() {
        TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
        strategy.setMaxSessionIdleTimeout(0L);
        return new HandshakeWebSocketService(strategy);
    }
}

Kotlin:

@Configuration
class WebConfig {

    @Bean
    fun handlerAdapter() =
            WebSocketHandlerAdapter(webSocketService())

    @Bean
    fun webSocketService(): WebSocketService {
        val strategy = TomcatRequestUpgradeStrategy().apply {
            setMaxSessionIdleTimeout(0L)
        }
        return HandshakeWebSocketService(strategy)
    }
}

์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์˜ต์…˜์„ ํ™•์ธํ•˜๋ ค๋ฉด ์„œ๋ฒ„์˜ ์—…๊ทธ๋ ˆ์ด๋“œ ์ „๋žต(upgrade strategy)์„ ํ™•์ธํ•˜๋ผ. ํ˜„์žฌ ํ†ฐ์บฃ๊ณผ ์ œํ‹ฐ๋งŒ์ด ์ด ์˜ต์…˜์„ ์ œ๊ณตํ•œ๋‹ค.


3.2.6. CORS

CORS๋ฅผ ์„ค์ •ํ•˜๊ณ  ์›น์†Œ์ผ“ ์—”๋“œํฌ์ธํŠธ๋กœ์˜ ์ ‘๊ทผ์„ ์ œํ•œํ•˜๋Š” ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ WebSocketHandler๊ฐ€ CorsConfigurationSource๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ํ—ˆ์šฉํ•  origin, ํ—ค๋” ๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ์ƒ์„ธ ์„ค์ •๋“ฑ์„ ๊ฐ€์ง„ CorsConfiguration์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋งŒ์ผ ์ด๋ ‡๊ฒŒ ํ•  ์ˆ˜ ์—†๋‹ค๋ฉด, SimpleUrlHandler์˜ corsConfigurations ์†์„ฑ์— URL ํŒจํ„ด ๋ณ„๋กœ CORS ์„ค์ •์„ ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค. ๋งŒ์ผ ๋‘ ๋ฐฉ๋ฒ• ๋ชจ๋‘ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, CorsConfiguration์˜ comine ๋ฉ”์„œ๋“œ์—์„œ ๋‘ ์„ค์ •์€ ๊ฒฐํ•ฉ๋œ๋‹ค.


3.2.7. ํด๋ผ์ด์–ธํŠธ(Client)

์Šคํ”„๋ง ์›นํ”Œ๋Ÿญ์Šค๋Š” ๋ฆฌ์•กํ„ฐ ๋„คํ‹ฐ(Reactor Netty), ํ†ฐ์บฃ(Tomcat), ์ œํ‹ฐ(Jetty), ์–ธ๋”ํ† ์šฐ(Undertow) ๊ทธ๋ฆฌ๊ณ  ํ‘œ์ค€ ์ž๋ฐ”(JSR-356)์— ๋Œ€ํ•œ ๊ตฌํ˜„์ฒด๋กœ WebSocketClient ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

์›น์†Œ์ผ“ ์„ธ์…˜์„ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ํด๋ผ์ด์–ธํŠธ์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ•ด๋‹น execute ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

Java:

WebSocketClient client = new ReactorNettyWebSocketClient();

URI url = new URI("ws://localhost:8080/path");
client.execute(url, session ->
        session.receive()
                .doOnNext(System.out::println)
                .then());

Kotlin:

val client = ReactorNettyWebSocketClient()

        val url = URI("ws://localhost:8080/path")
        client.execute(url) { session ->
            session.receive()
                    .doOnNext(::println)
            .then()
        }

์ œํ‹ฐ(Jetty)์™€ ๊ฐ™์€ LifeCycle ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์ผ๋ถ€ ํด๋ผ์ด์–ธํŠธ๋Š” ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ์ค‘์ง€ํ•˜๊ณ  ์‹œ์ž‘ํ•ด์•ผ ํ•œ๋‹ค. ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ธฐ๋ณธ ์›น์†Œ์ผ“ ํด๋ผ์ด์–ธํŠธ์˜ ์„ค์ •๊ณผ ๊ด€๋ จ๋œ ์ƒ์„ฑ์ž ์˜ต์…˜์ด ์žˆ๋‹ค.


๋ชฉ์ฐจ ๊ฐ€์ด๋“œ