Search
Duplicate

SockJS, STOMP

SockJS

SockJSλŠ” μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ WebSocket APIλ₯Ό μ‚¬μš©ν•˜λ„λ‘ ν—ˆμš©ν•˜μ§€λ§Œ, λΈŒλΌμš°μ €μ—μ„œ WebSocket을 μ§€μ›ν•˜μ§€ μ•ŠλŠ” κ²½μš°μ— λŒ€μ•ˆμœΌλ‘œ μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ μ½”λ“œλ₯Ό λ³€κ²½ν•  ν•„μš” 없이 λŸ°νƒ€μž„μ— ν•„μš”ν•  λ•Œ λŒ€μ²΄λ₯Ό ν•˜λŠ” 것이닀.
SockJSλŠ” λ‹€μ–‘ν•œ κΈ°μˆ μ„ μ΄μš©ν•΄ μ›Ήμ†ŒμΌ“μ„ μ§€μ›ν•˜μ§€ μ•ŠλŠ” λΈŒλΌμš°μ €μ—μ„œ μ •μƒμ μœΌλ‘œ λ™μž‘ν•˜λ„λ‘ ν•΄μ€€λ‹€. 전솑 νƒ€μž…μ€ 크게 λ‹€μŒμ˜ 3κ°€μ§€λ‘œ λΆ„λ₯˜λœλ‹€
β€’
WebSocket
β€’
HTTP Streaming
β€’
HTTP Long Polling

STOMP (Simple/Stream Text Oriented Message Protocol)

WebSocket μœ„μ—μ„œ λ™μž‘ν•˜λŠ” 문자 기반 λ©”μ‹œμ§• ν”„λ‘œν† μ½œ, ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„κ°€ 전솑할 λ©”μ‹œμ§€μ˜ μœ ν˜•, ν˜•μ‹, λ‚΄μš©λ“€μ„ μ •μ˜ν•˜λŠ” λ§€μ»€λ‹ˆμ¦˜μ΄λ‹€.
기본적으둜 pub/sub ꡬ쑰둜 λ˜μ–΄μžˆμ–΄, λ©”μ‹œμ§€λ₯Ό μ „μ†‘ν•˜κ³  λ°›μ•„ μ²˜λ¦¬ν•˜λŠ” 뢀뢄이 ν™•μ‹€νžˆ μ •ν•΄μ Έ μžˆλ‹€.
http와 λ§ˆμ°¬κ°€μ§€λ‘œ frame을 μ‚¬μš©ν•΄ μ „μ†‘ν•˜λŠ” ν”„λ‘œν† μ½œμ΄λ‹€.
pub/sub κ΅¬μ‘°λž€?

STOMP의 Frame ꡬ쑰

COMMAND // λ©”μ‹œμ§€ νƒ€μž…μ„ λ‚˜νƒ€λ‚΄λŠ” λ¬Έμžμ—΄, SEND, SUBSCRIBE, UNSUBSCRIBE 등이 μžˆλ‹€. header1 : value1 // μΆ”κ°€ 정보λ₯Ό μ œκ³΅ν•˜λŠ” 헀더 header2 : value2 // μΆ”κ°€ 정보λ₯Ό μ œκ³΅ν•˜λŠ” 헀더 Body^@ // λ©”μ‹œμ§€μ˜ λ‚΄μš©, ^@ : NULL λ¬Έμžμ΄λ‹€. Body의 끝을 λ‚˜νƒ€λ‚Έλ‹€.
Java
볡사

SWAPSWAP의 Frame ꡬ쑰 μ˜ˆμ‹œ (μ±„νŒ… λ°© μž…μž₯μ‹œ μˆœμ„œλŒ€λ‘œ)

1.
μ›Ή μ†ŒμΌ“ μ—°κ²° μ΄ˆκΈ°ν™”, ν΄λΌμ΄μ–ΈνŠΈκ°€ μ„œλ²„μ—κ²Œ μ›Ή μ†ŒμΌ“ 연결을 μš”μ²­ν•˜λŠ”λ° μ‚¬μš©
2.
μ›Ή μ†ŒμΌ“ 연결이 μ„±κ³΅μ μœΌλ‘œ μ„€μ • λ˜μ—ˆμŒμ„ λ‚˜νƒ€λƒ„
3.
ν΄λΌμ΄μ–ΈνŠΈκ°€ νŠΉμ • λŒ€μƒμ— λŒ€ν•œ ꡬ독을 μš”μ²­ν•˜λŠ” SUBSCRIBE ν”„λ ˆμž„
4.
ν΄λΌμ΄μ–ΈνŠΈκ°€ λ©”μ‹œμ§€λ₯Ό νŠΉμ • λŒ€μƒμœΌλ‘œ λ³΄λ‚΄λŠ” λͺ…λ Ήμ–΄
β€’
destination : λ©”μ‹œμ§€κ°€ μ „μ†‘λ˜λŠ” λŒ€μƒμ˜ λͺ©μ μ§€
5.
νŠΉμ • μ±„νŒ…λ°©μœΌλ‘œλΆ€ν„° μˆ˜μ‹ λœ μ‹€μ œ λ©”μ‹œμ§€ λ‚΄μš©μ„ λ‚˜νƒ€λ‚΄λŠ” MESSAGE ν”„λ ˆμž„
β€’
destination : λ©”μ‹œμ§€κ°€ μ „μ†‘λœ λŒ€μƒμ˜ λͺ©μ μ§€

μ±„νŒ…λ°© μž…μž₯ μ‹œ μ‹€ν–‰λ˜λŠ” 슀크립트

1.
chatroom.htmlκ°€ λ‘œλ“œλ  λ•Œ μ›Ή μ†ŒμΌ“ 연결을 생성. /ws/chatλŠ” μ›Ή μ†ŒμΌ“ μ—”λ“œν¬μΈνŠΈμ˜ URL.
2.
Stomp.js 라이브러리λ₯Ό μ‚¬μš©ν•˜μ—¬ WebSocket을 Stomp ν”„λ‘œν† μ½œλ‘œ λž˜ν•‘.
β€’
onConnectλŠ” μ—°κ²° 성곡 μ‹œ , onErrorλŠ” μ‹€νŒ¨ μ‹œ μ‹€ν–‰λ˜λŠ” 콜백 ν•¨μˆ˜.
3.
μ—°κ²° 성곡 μ‹œ /quere/chat/room’ + chatRoomId 에 λŒ€ν•œ ꡬ독을 μ„€μ •ν•œλ‹€.
4.
λ©”μ‹œμ§€λ₯Ό 보낼 λ•Œ μ‹€ν–‰λ˜λŠ” ν•¨μˆ˜
β€’
/app/chat/message둜 λ©”μ‹œμ§€λ₯Ό 보낸닀.
5.
보낸 λ©”μ‹œμ§€λŠ” ν•΄λ‹Ή λ©”μ†Œλ“œλ‘œ 듀어와 chatRoomService.saveMessage(message)μ—μ„œ 처리된 ν›„
6.
β€œ/queue/chat/room” + message.roomId()둜 λ©”μ‹œμ§€λ₯Ό 전솑 β†’ ν•΄λ‹Ή 채널을 κ΅¬λ…ν•˜κ³  μžˆλŠ” κ΅¬λ…μžλŠ” λ©”μ‹œμ§€λ₯Ό λ°›λŠ”λ‹€.

STOMPλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³  순수 μ›Ήμ†ŒμΌ“μ„ 썼을 λ•Œμ˜ 문제점

μ›Ή μ†ŒμΌ“μ€ 기본적으둜 Text, Binary νƒ€μž…μ˜ λ©”μ‹œμ§€λ₯Ό μ–‘λ°©ν–₯으둜 주고받을 수 μžˆλŠ” ν”„λ‘œν† μ½œμ΄λ‹€.
그런데 μ›Ήμ†ŒμΌ“λ§Œ μ‚¬μš©ν•˜λŠ” ν”„λ‘œμ νŠΈκ°€ 컀지면, μ£Όκ³  λ°›λŠ” λ©”μ‹œμ§€μ— λŒ€ν•œ ν˜•μ‹μ΄ μ€‘μš”ν•˜κ²Œ λœλ‹€.

μ—¬κΈ°μ„œ STOMPλ₯Ό μ‚¬μš©ν•˜λ©΄ μ–΄λ– ν•œ μž₯점을 λˆ„λ¦΄ 수 μžˆμ„κΉŒ

STOMPλŠ” μ»€λ§¨λ“œ, 헀더, λ°”λ””λ‘œ 이루어진 ν”„λ ˆμž„ λ‹¨μœ„λ₯Ό μ •μ˜ν•΄λ‘μ—ˆκΈ° λ•Œλ¬Έμ— λ©”μ‹œμ§• ν”„λ‘œν† μ½œκ³Ό λ©”μ‹œμ§• ν˜•μ‹μ„ κ°œλ°œν•  ν•„μš”κ°€ μ—†λ‹€. (즉 ν•˜μœ„ ν”„λ‘œν† μ½œκ³Ό λ©”μ‹œμ§€ μ»¨λ²€μ…˜μ„ μ •μ˜ν•  ν•„μš”κ°€ μ—†λ‹€)
μ—°κ²° μ£Όμ†Œλ§ˆλ‹€ μƒˆλ‘œμš΄ ν•Έλ“€λŸ¬λ₯Ό κ΅¬ν˜„ν•˜κ³  섀정해쀄 ν•„μš”κ°€ μ—†λ‹€. WebSocket만 μ‚¬μš©ν•œλ‹€λ©΄ ν…μŠ€νŠΈ 기반의 μ›Ή μ†ŒμΌ“ 톡신을 μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄μ„œλŠ” TextWebSocketHandlerλ₯Ό 상속 받은 ν•Έλ“€λŸ¬λ₯Ό κ΅¬ν˜„ν•΄ μ£Όμ–΄μ•Ό ν•œλ‹€.
λ©”μ‹œμ§€ 브둜컀λ₯Ό μ‚¬μš©ν•˜λ©΄ ꡬ독을 κ΄€λ¦¬ν•˜κ³  λ©”μ‹œμ§€λ₯Ό Broadcast ν•˜λŠ”λ° μ‚¬μš©ν•  수 μžˆλ‹€. λ˜ν•œ μ™ΈλΆ€ Messaging Queueλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.
μœ„ 그림은 순수 μ›Ή μ†ŒμΌ“μ„ μ‚¬μš©ν–ˆμ„ λ•Œμ΄κ³ , 였λ₯Έμͺ½μ€ STOMPλ₯Ό μ‚¬μš©ν–ˆμ„ λ•Œμ΄λ‹€.
μ›Ή μ†ŒμΌ“λ§Œ μ‚¬μš©ν–ˆμ„ λ•ŒλŠ” λ‚  κ²ƒμ˜ λ©”μ‹œμ§€λ§Œ μ˜€κ³ κ°€λŠ” λ°˜λ©΄μ— STOMPλ₯Ό μ‚¬μš©ν–ˆμ„ λ•ŒλŠ” COMMAND, header, body의 ν˜•νƒœλ‘œ 였고 κ°€λŠ” 것을 λ³Ό 수 μžˆλ‹€.

λ™μž‘ 흐름

β€’
λ©”μ‹œμ§€λ₯Ό λ³΄λ‚΄λ €λŠ” λ°œμ‹ μž, λ©”μ‹œμ§€λ₯Ό λ°›μœΌλ €λŠ” κ΅¬λ…μžκ°€ μžˆλ‹€. κ΅¬λ…μžλŠ” /topic μ΄λΌλŠ” 경둜λ₯Ό κ΅¬λ…ν•˜κ³  μžˆλ‹€.
β€’
λ°œμ‹ μžλŠ” /topic을 destination ν—€λ”λ‘œ λ„£μ–΄ λ©”μ‹œμ§€λ₯Ό λ©”μ‹œμ§€ 브둜컀λ₯Ό 톡해 κ΅¬λ…μžλ“€μ—κ²Œ κ³§λ°”λ‘œ 솑신할 수 μžˆλ‹€.
β€’
ν˜Ήμ€ μ„œλ²„ λ‚΄μ—μ„œ μ–΄λ–€ κ°€κ³΅μ²˜λ¦¬κ°€ ν•„μš”ν•˜λ‹€λ©΄ /app 경둜둜 λ©”μ‹œμ§€λ₯Ό 솑신할 μˆ˜λ„ μžˆλ‹€.
β—¦
μ„œλ²„κ°€ 가곡 처리λ₯Ό 끝낸 데이터λ₯Ό /topicμ΄λΌλŠ” 경둜λ₯Ό λ‹΄μ•„ λ©”μ‹œμ§€ λΈŒλ‘œμ»€μ—κ²Œ μ „λ‹¬ν•˜λ©΄ λ©”μ‹œμ§€ λΈŒλ‘œμ»€λŠ” 전달받은 λ©”μ‹œμ§€λ₯Ό /topic을 κ΅¬λ…ν•˜λŠ” κ΅¬λ…μžλ“€μ—κ²Œ μ΅œμ’…μ μœΌλ‘œ μ „λ‹¬ν•œλ‹€.

Springμ—μ„œ STOMPλ₯Ό μ‚¬μš©ν–ˆμ„ λ•Œ μž₯점

β€’
ν•˜μœ„ ν”„λ‘œν† μ½œ, μ»¨λ²€μ…˜μ„ λ”°λ‘œ μ •μ˜ν•  ν•„μš”κ°€ μ—†λ‹€.
β—¦
STOMPκ°€ ν”„λ ˆμž„ λ‹¨μœ„λ‘œ μ •μ˜ν•΄μ€€λ‹€.
β€’
μ—°κ²° μ£Όμ†Œλ§ˆλ‹€ μƒˆλ‘œμš΄ Handlerλ₯Ό κ΅¬ν˜„ν•˜κ³  섀정해쀄 ν•„μš”κ°€ μ—†λ‹€.
β—¦
@Controller μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ©΄ λœλ‹€.
β€’
μ™ΈλΆ€ Messaging Queueλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.
β—¦
Spring이 기본적으둜 μ œκ³΅ν•˜λŠ” λ‚΄μž₯ λ©”μ‹œμ§€ λΈŒλ‘œμ½”κ°€ μ•„λ‹Œ μ™ΈλΆ€ λ©”μ‹œμ§€ 큐λ₯Ό μ—°λ™ν•΄μ„œ μ‚¬μš©ν•  수 μžˆλ‹€.
β€’
Spring Securityλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.
β—¦
μ˜€κ³ κ°€λŠ” λ©”μ‹œμ§€μ— λŒ€ν•œ λ³΄μ•ˆ 섀정을 ν•  수 μžˆλ‹€.
μ—¬κΈ°μ„œ ChannelInterceptorλŠ” λ©”μ‹œμ§€ 전솑 μ „κ³Ό 후에 νŠΉμ • μž‘μ—…μ„ μˆ˜ν–‰ν•  수 μžˆλ„λ‘ ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€μ΄λ‹€. μœ„ preSendλ©”μ„œλ“œλŠ” λ©”μ‹œμ§€κ°€ μ‹€μ œλ‘œ μ±„λ„λ‘œ μ „μ†‘λ˜κΈ° 전에 ν˜ΈμΆœλ˜λŠ”λ°, 이λ₯Ό 톡해 λ©”μ‹œμ§€λ₯Ό κ°€λ‘œμ±„κ³  μ›ν•˜λŠ” μž‘μ—…μ„ μˆ˜ν–‰ν•  수 μžˆλ‹€.
μœ„ 같은 κ²½μš°μ—λŠ” STOMP의 Frameκ΅¬μ‘°μ—μ„œ COMMANDκ°€ CONNECT일 λ•Œ, 즉 ν΄λΌμ΄μ–ΈνŠΈκ°€ μ„œλ²„μ—κ²Œ 연결을 μš”μ²­ν•  λ•Œ μ‹€ν–‰λ˜λŠ” λ©”μ„œλ“œμ΄λ‹€. λ§Œμ•½ ν•΄λ‹Ή if()λ¬Έ μ•ˆμ— JWT 토큰을 validation ν•˜λŠ” λ©”μ„œλ“œλŠ” λ„£μ–΄μ€€λ‹€λ©΄ 연결을 μš”μ²­ν•˜κΈ° 전에 JWT 토큰을 검증할 수 μžˆμ„ 것이닀.