Hi yoahn 개발블로그

[SpringBoot] Web Socket 사용하기 (STOMP 메시지 프로토콜) 본문

Framework & Library/springboot

[SpringBoot] Web Socket 사용하기 (STOMP 메시지 프로토콜)

hi._.0seon 2021. 6. 24. 23:05
반응형

https://spring.io/guides/gs/messaging-stomp-websocket/

 

Using WebSocket to build an interactive web application

this guide is designed to get you productive as quickly as possible and using the latest Spring project releases and techniques as recommended by the Spring team

spring.io

build.gradle

plugins {
	id 'org.springframework.boot' version '2.5.1'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-websocket'
	implementation 'org.webjars:webjars-locator-core'
	implementation 'org.webjars:sockjs-client:1.0.2'
	implementation 'org.webjars:stomp-websocket:2.3.3'
	implementation 'org.webjars:bootstrap:3.3.7'
	implementation 'org.webjars:jquery:3.1.1-1'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
	useJUnitPlatform()
}

 

빌드 시스템 구축이 다 되었으니 이제 STOMP 메시지 서비스를 만들 수 있다.

서비스 상호 작용을 고려하여 프로세스를 시작한다.

 

서비스는 STOMP 메세지에 이름이 포함된 메시지를 수락한다. 이름이 Fred 인 경우 다음과 유사할 수 있다.

{
	"name": "Fred"
}

이름을 전달하는 메시지를 모델링하려면 이름 속성 및 해당 getName() 메서드를 사용하여 다음과 같은 Java 객체 생성

package com.example.messagingstompwebsocket;

public class HelloMessage {

  private String name;

  public HelloMessage() {
  }

  public HelloMessage(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

메시지를 수신하고 이름을 추출하면, 서비스는 인사말을 생성하고 클라이언트가 가입한 별도의 대기열에 인사말을 게시하여 메시지를 처리한다. 인사말은 다음 목록과 같이 JSON 객체로도 표시된다.

{
    "content": "Hello, Fred!"
}

더보기

STOMP 메시지 프로토콜

: 웹 상에서 텍스트 송,수신을 위해 미리 정의된 특정한 규칙

텍스트 기반 프로토콜이어서 개발자가 읽기 쉽고 사용하기 좋다.

 

STOMP에 정의한 규칙만 잘 지키면 여러 언어, 여러 플랫폼간에서 메세지를 상호 운영할 수 있다.

전송 방식

STOMP 는 메시지를 수신할 대상 집합을 관리하는 일을 한다. STOMP는 메시지에 대한 스펙만을 정의하고 있기 때문에 기능 구현은 전적으로 서버에 맡긴다.

Frame 구조

명령과 추가적인 헤더, 추가적인 바디로 구성된다.

Frame: 몇개의 텍스트 라인으로 지정된 구조. 첫번째 라인은 텍스트(명령어)이고 이후 key:value 형태로 헤더 정보를 포함한다.

header 이후에 공백 줄을 하나 더 추가하는 것으로 header의 끝을 설정할 수 있다.

 

header 이후에는 Payload(Body)가 존재한다.

Payload = 전송되는 데이터 (JSON data 부분)

COMMAND
header1:value1
header2:value2

Body^@
더보기

COMMAND 명령어 리스트

  • CONNECT
  • SEND
  • SUBSCRIBE
  • UNSUBSCRIBE
  • BEGIN
  • COMMIT
  • ABORT
  • ACK
  • NACK
  • DISCONNECT

인사말 표현을 모델링하려면 컨텐츠 속성 및 해당하는 getContent() 메서드가 있는 기존의 평범한 Java 개체를 다음 목록과 같이 추가하십시오

package com.example.messagingstompwebsocket;

public class Greeting {

  private String content;

  public Greeting() {
  }

  public Greeting(String content) {
    this.content = content;
  }

  public String getContent() {
    return content;
  }

}

스프링은 Jackson JSON 라이브러리를 사용하여 `Greeting`유형의 인스턴스를 JSON으로 자동 정렬

 

다음은, hello 메시지를 받고 greeting 메시지를 보내는 컨트롤러를 생성한다.

 

Spring의 STOMP 메시징 작업 방식에서 STOMP 메시지는 @Controller 클래스로 라우팅 될 수 있다.

package com.example.messagingstompwebsocket;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;

@Controller
public class GreetingController {

  @MessageMapping("/hello")
  @SendTo("/topic/greetings")
  public Greeting greeting(HelloMessage message) throws Exception {
    Thread.sleep(1000); // simulated delay
    return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
  }

}

@MessageMapping 어노테이션은 만약 /hello 목적지로 메시지가 전송되는 경우 greeting() 메소드가 호출된다. 메시지의 페이로드가 HelloMessage 개체에 바인딩되어 greeting()으로 전달된다.

 

내부적으로, 이 방법의 구현은 스레드를 1초 동안 절전 상태로 만들어 처리 지연을 시뮬레이션합니다. 이는 클라이언트가 메시지를 보낸 후 서버가 비동기식으로 메시지를 처리해야 하는 한 시간이 걸릴 수 있음을 보여주기 위한 것입니다. 고객은 응답을 기다리지 않고 필요한 모든 작업을 계속할 수 있습니다.

 

1초 지연 후 greeting() 메소드는 Greeting 객체를 생성하고 반환합니다. 반환 값은 @SendTo 주석에 지정된 대로 /topic/greetings 의 모든 구독자에게 브로드캐스트됩니다. 입력 메시지의 이름은 삭제되므로 클라이언트 측의 브라우저 DOM에서 다시 반향되고 렌더링됩니다.

Configure Spring for STOMP messaging

이제 서비스의 필수 구성 요소가 생성되었으므로 WebSocket 및 STOMP 메시징을 사용하도록 Spring을 구성할 수 있습니다.

 

WebSocketConfig

package com.example.messagingstompwebsocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

  @Override
  public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker("/topic");
    config.setApplicationDestinationPrefixes("/app");
  }

  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/gs-guide-websocket").withSockJS();
  }

}

@Configuration : 이 클래스가 스프링 설정 클래스라는 것을 나타냄

@EnableWebSocketMessageBroker 는 메시지 브로커를 통해 WebSocket 메시지 처리를 활성화 한다.

 

`configureMessageBroker()` 메소드는 메시지 브로커를 구성하는 WebSocketMessageBrokerConfigurer의 기본 메소드를 구현한다. 간단한 메모리 기반 메시지 브로커가 /topic이 앞에 붙은 대상의 클라이언트로 greeting 메시지를 전달할 수 있도록 enableSimpleBroker()를 호출하는 것으로 시작된다. 또한 @MessageMapping 어노테이션이 붙은 메소드에 바인딩된 메시지에 /app 접두사를 지정한다. 이 접두사는 모든 메시지 매핑을 정의하는데 사용된다.

ex)  /app/hello => GreetingController.greeting()메서드가 처리하도록 매핑된 엔드포인트

 

`registerStompEndpoints()` 메소드는 `/gs-guide-websocket` Endpoint를 등록하여 웹 소켓을 사용할 수 없는 경우 대체 전송을 사용할 수 있도록 SockJS fallback 옵션을 사용하도록 설정한다. SockJS 클라이언트는 `/gs-guide-websocket`에 연결하고 사용 가능한 최상의 전송을 사용하여 연결하려고 시도할 것이다.

Create a Browser Client

서버 사이드

<!DOCTYPE html>
<html>
<head>
    <title>Hello WebSocket</title>
    <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="/main.css" rel="stylesheet">
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/app.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">WebSocket connection:</label>
                    <button id="connect" class="btn btn-default" type="submit">Connect</button>
                    <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                    </button>
                </div>
            </form>
        </div>
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="name">What is your name?</label>
                    <input type="text" id="name" class="form-control" placeholder="Your name here...">
                </div>
                <button id="send" class="btn btn-default" type="submit">Send</button>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Greetings</th>
                </tr>
                </thead>
                <tbody id="greetings">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

이 HTML파일은 웹 소켓과 STOMP를 통해 서버와 통신하는데 사용될 SockJS 및 STOMP Javascript 라이브러리를 가져온다. 또한 클라이언트 애플리케이션의 로직이 포함된 app.js도 가져온다.

var stompClient = null;

function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#greetings").html("");
}

function connect() {
    var socket = new SockJS('/gs-guide-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/greetings', function (greeting) {
            showGreeting(JSON.parse(greeting.body).content);
        });
    });
}

function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
}

function sendName() {
    stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
}

function showGreeting(message) {
    $("#greetings").append("<tr><td>" + message + "</td></tr>");
}

$(function () {
    $("form").on('submit', function (e) {
        e.preventDefault();
    });
    $( "#connect" ).click(function() { connect(); });
    $( "#disconnect" ).click(function() { disconnect(); });
    $( "#send" ).click(function() { sendName(); });
});

주요 기능 -> connect() & sendName() 함수

`connect()`기능은 SockJS와 stomp.js를 사용하여 SockJS서버가 연결을 대기하는 /gs-guide-websocket에 대한 연결을 연다. 연결에 성공하면 클라이언트는 /topic/greetings 목적지를 구독하고 서버가 greeting 메시지를 게시한다. 해당 목적지에서 greeting 이 수신되면 DOM에 문단 요소를 추가하여 greeting 메시지를 표시한다.

 

`sendName()`기능은 사용자가 입력한 이름을 검색하고 STOMP 클라이언트를 사용하여 /app/hello 목적지로 전송한다. (GreetingController.greeting()에서 수신) 원하는 경우 main.css를 생략하거나 빈 main.css를 생성하여 <link>를 해결할 수 있다.

반응형
Comments