📕 목차
1. Heart beating
2. How to define a heartbeat header on the server side?
3. Can it be solved using RabbitMQ settings?
4. Heartbeat negotiation interceptor
1. Heart beating
📌 Introduction
이전 포스팅에서 heart beat interval 권장 사항을 제시해주지 않았었다고 한 적이 있던 거 같은데, 추후 확인해보니 IETF에서 박동 검사는 25sec 주기를 권장한다고 한다.
그래서 spring boot에서도, 개발자가 별도의 설정을 하지 않으면 25초로 값을 할당한다고 되어 있다.
근데 iOS팀에서 "web socket이 자꾸 끊어졌다가 재연결되는데 이게 맞냐?"고 질문이 들어왔고,
이 때까지만 해도 iOS에서 사용하는 Stomp 라이브러리는 heart beat를 자동으로 정해주지 않나 보구나 정도로만 생각했다.
(물론 갑자기 든 의문은..서버에서 이미 값을 정했는데, 왜 클라이언트에서 말썽을 일으키냐는 점도 있었다.)
그래서 heart-beat에 대해 알려주고, 이거 설정만 하면 될 거예요 ㅇㅇ 하고 돌려보냈었는데, 새벽에 갑자기 연락이 왔다.
Socket 서버를 만들면서 얼핏 주워들었던 내용에 의하면, .setHeartBeat() 인지 뭔지에 값을 할당하면 ms 단위로 박동 검사 주기를 정할 수 있고, 기본값은 25sec라고만 들었지
갑자기 heart-beat 헤더는 뭐고, 값은 왜 두 개나 들어간단 말이지?
심지어 heart-beat 헤더는 클라이언트가 CONNECT 프레임에 담는 것 외에도, CONNECTED 프레임의 헤더에도 담겨 있었는데, 그럼 두 헤더가 다르다면 누가 우선 순위를 갖게 되는 거지?
어쨌든 iOS팀에서 "heart-beat:0,25000"으로 정하니까 잘 된다고 하니, 그냥 이대로 끝냈어도 될 문제긴 했다.
그러나 언제나 같은 이유로 여기서 끝낼 생각이 없었다.
지식의 공백은 어떠한 이유로든 용납할 수 없다.
📌 Stomp Spec
Stomp 공식 문서에 아주 친절하게 관련 내용이 나와 있었다.
개발에 급급하다는 이유로 이런 부분을 놓치고 갔었다니...
여튼 내용을 정리하면 이러하다.
(오역이 있을 수도 있어서, 원문을 읽어보길 바랍니다. 요새 영어 공부 중이라..)
- heart-beating을 활성화하기 위해, 각 참여자는 "할 수 있는 것"과 "다른 참여자에게 하길 바라는 것"을 선언할 수 있다.
- heart-beat 헤더를 사용할 것이라면, 반드시 콤마로 구분한 두 개의 양수를 사용하라.
- 첫 번째 수 (CAN DO)
- 0은 heart-beats를 보낼 수 없음을 의미한다.
- 양수를 넣으면, '나'가 '상대' 측에 전송할 수 있는 박동 검사 주기를 의미한다.
- 두 번째 수 (WANT TO)
- 0은 heart-beats를 받길 원하지 않음을 의미한다.
- 양수를 넣으면, '나'가 '상대' 측에 원하는 박동 검사 주기를 의미한다.
- 첫 번째 수 (CAN DO)
- heart-beat 헤더는 OPTIONAL이지만, 포함하지 않으면 "heart-beat:0,0"과 동일하게 취급하여, 박동 검사를 할 수도, 하고 싶지도 않은 상태라고 상대 측에 전달된다.
CONNECT
heart-beat: <cx>, <cy>
CONNECTED
heart-beat: <sx>, <sy>
- 만약, <cx> 혹은 <sy>가 0이라면, "client는 heart beat를 할 수 없고, server는 heart beat를 받길 원하지 않는 상태"이므로 none이 된다.
- 반면, <cx>, <sy>가 다르면, 그 값은 MAX(<cx>, <sy>) ms로 결정된다.
- <sx>, <cy>도 마찬가지로 MAX 값으로 결정된다.
- 왜 min이 아니라 max냐면, 내가 15sec 간격으로 보낼 수 있다고 해도, 상대가 25sec 간격으로 받고 싶다고 하면 25sec로 보내자고 절충하기 위함이다.
아하, 그 말은 즉슨 나와 상대가 heart-beat를 송/수신할 주기를 Connect 과정에서 결정해야 한다는 말이 된다.
그러나 여전히 몇 가지 의문이 들었다.
- 한 쪽에서 heart-beat를 보낼 수 없거나, 받고 싶지 않은 경우에도 MAX 값으로 결정되는지?
- spring에선 막연히 박동 검사 주기를 25초로 잡고 있다고 들었는데, 헤더는 어떻게 반환되고 있는 건지?
두 의문은 한 가지 문제점으로 부터 출발하는데, 만약 Stomp 스펙대로라면 애초에 iOS 팀에서 heart-beat 헤더를 정해주든 말든, 적어도 client to server 혹은 server to client 한 쪽은 25sec로 잡혀있었어야 했다.
그런데 아예 실패한다는 게 말이 돼?? 이건 또 프레임워크가 내 뒤통수를 쳤다는 말밖에 되지 않는다.
📌 Client Test
일단 spring에서 자동으로 결정해주고 있다는 박동 검사 주기가 어떻게 되는 지 확인해보자.
iOS 팀에게 부탁해서 2가지 요청을 서버로 전달했다.
- heart-beat 헤더를 포함하지 않은 경우
- "heart-beat:8000,8000" 헤더를 포함한 경우
Spring이 박동 검사 주기를 25sec로 자동 할당해놨다는 것이 사실이라면, 전송/수신 적어도 둘 중 하나는 25,000이라는 값을 가져야만 했다.
애석하게도, 주기는 client가 전달한 값을 그대로 반환하고 있었다.
이건 최악의 경우였는데, server가 "<sx>,<sy>"에 대한 정보는 일절 없이, 그저 클라이언트가 하자고 하는 대로 따라가고 있다는 방증이었다.
2. How to define a heartbeat header on the server side?
👷 열심히 삽질한 파트입니다.
📌 spring은 자동으로 heart-beat를 25 sec로 잡아준다며?
대체 자동으로 25 sec로 잡아준다는 말은 어디서부터 시작한 건지 찾아보니, 처음에 클라이언트를 SockJS로 테스트 할 때 주워들은 정보였던 것 같았다.
그 당시엔 Heart beat가 오고 가는 것도 로그에 상세히 출력됐었으므로, 이에 대해 어떠한 의구심을 품지 않은 게 원인이었다.
심지어 저 자동으로 잡아주는 메서드도 withSockJs()를 하지 않는 현재 상황에선 작동하지 않는다는 것이다.
그렇다. 나는 또 원효대사 해골 스프링에 당한 것이었다. 🤦♂️
아니, 사실 스프링은 죄가 없다. 내가 멍청해서 그렇다.
멍청 비용을 청산하러 가보자.
📌 DefaultHandshakeHandler
@EnableWebSocketMessageBroker
public class WebSocketMessageBrokerConfig implements WebSocketMessageBrokerConfigurer {
...
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint(chatServerProperties.getEndpoint())
.setHandshakeHandler(new DefaultHandshakeHandler(new TomcatRequestUpgradeStrategy()))
.setAllowedOriginPatterns(chatServerProperties.getAllowedOriginPatterns().toArray(new String[0]));
}
...
}
별도의 설정을 해주지 않으면, 기본적으로 HandshakeHandler는 위와 같은 형태로 구성된다.
(더 간단한 방법은 WebSocketConfigurer를 구현하고, HandshakeInterceptor의 before, after 메서드를 재정의하여 등록하는 방법도 있다.)
public class DefaultHandshakeHandler extends AbstractHandshakeHandler implements ServletContextAware {
public DefaultHandshakeHandler() {
}
public DefaultHandshakeHandler(RequestUpgradeStrategy requestUpgradeStrategy) {
super(requestUpgradeStrategy);
}
@Override
public void setServletContext(ServletContext servletContext) {
RequestUpgradeStrategy strategy = getRequestUpgradeStrategy();
if (strategy instanceof ServletContextAware servletContextAware) {
servletContextAware.setServletContext(servletContext);
}
}
}
DefaultHandshakeHandler는 ServletContext를 초기화하는 것 외엔 별도의 구현이 없다.
실제 웹 소켓 연결 과정 등을 처리하는 역할을 수행하는 것은 AbstractHandshakeHandler 내부에 구현되어 있는데,
여기서 WebSocketHandler를 구현한 SubProtocolWebSocketHandler를 이용하여 부가 기능을 넣을 수도 있다.
Spring 공식 문서에선 이를 활용하는 몇 가지 방법을 제시해주고 있다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").setHandshakeHandler(handshakeHandler());
}
@Bean
public DefaultHandshakeHandler handshakeHandler() {
JettyRequestUpgradeStrategy strategy = new JettyRequestUpgradeStrategy();
strategy.addWebSocketConfigurer(configurable -> {
policy.setInputBufferSize(4 * 8192);
policy.setIdleTimeout(600000);
});
return new DefaultHandshakeHandler(strategy);
}
}
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
registry.setMessageSizeLimit(4 * 8192);
registry.setTimeToFirstMessage(30000);
}
}
그러나 이것들은 전부 초기에 Connection 연결을 위한 Handshake 과정 자체에만 관심이 있지,
실제 박동 검사를 어떻게 수행할 지에 대해 설정해주는 방법을 제시해주진 않는다.
어떻게든 해내라고 하면 하겠지만, 정말 이런 무식한 방법밖엔 없는 걸까?
📌 MessageBrokerRegistry
참고할 만한 자료도 너무 희박해서, 그냥 여기저기 . 찍으면서 메서드 종류를 하나하나 확인하던 중에 드디어 내가 원하는 단어를 포함한 메서드를 찾을 수 있었다.
그러나 이 메서드에서 정한 Interval은 Server와 Broker 사이의 박동 검사 주기를 결정하는 것이므로, 여전히 내가 원하는 것이 아니다.
실제로 이 메서드에 25,000이라는 값을 전달해도, 클라이언트와의 heart-beat는 10sec로 결정된다.
25sec라는 박동 검사는 Server와 외부 브로커인 RabbitMQ 사이의 STOMP 연결에 반영되는 것을 확인할 수 있다.
좀 더 찾아보니, 공식 문서에서 위 내용을 다루고 있었다.
내가 예상한대로, STOMP Broker relay가 연결된 모든 WebSocket 클라이언트에 대해 별도의 TCP 연결을 만드는 건 맞지만, heart-beat는 server와 external message broker 사이의 주기를 결정할 뿐이다. (기본값은 10초)
📌 Task Scheduler
Simple Broker를 사용하는 경우라면, registry에서 다음과 같이 박동 검사 주기를 결정할 수 있다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private TaskScheduler messageBrokerTaskScheduler;
@Autowired
public void setMessageBrokerTaskScheduler(@Lazy TaskScheduler taskScheduler) {
this.messageBrokerTaskScheduler = taskScheduler;
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/")
.setHeartbeatValue(new long[] {10000, 20000})
.setTaskScheduler(this.messageBrokerTaskScheduler);
// ...
}
}
하지만 외부 브로커를 사용하는 시점에서 SimpleBroker를 함께 사용한다는 건 정신적으로 해로운 생각이었다.
setTaskScheduler()는 외부 브로커 설정에서도 제공하길래 혹시나 싶었지만, 이건 heartbeat를 협상하기 보단, 이미 협상한 heartbeat를 가지고 client의 연결을 실시간으로 관리하기 위한 테크닉에 불과하다.
3. Can it be solved using RabbitMQ settings?
📌 External Message Broker와 heartbeat
그럼 대체 뭘 어떻게 해야 한다는 걸까?
이 해답을 찾지 못 해서, 거의 30분 동안 무의미한 client 측의 heartbeat 헤더를 바꿔가면서 재연결을 반복하고 있었다.
그러다 뭔가 이상한 점을 알게 되었다.
Client 측의 헤더를 "heartbeat:20000,0"으로 설정했더니, RabbitMQ의 connection에서 heartbeat가 20sec로 설정되어 있던 것이다.
이번에는 역전시켜서 "heartbeat:0,20000"으로 바꿔보니, heartbeat가 0s로 설정되었다.
아직 구체적으로 어떻게 써먹어야 할 지는 모르겠지만, 어쨌든 client측의 heartbeat 헤더가 어쩌면 Spring이 아니라, 외부 브로커인 RabbitMQ와 협상 과정을 체결하고 있을 수도 있다는 생각을 하게 되었다.
📌 setRequestedHeartbeat
그럼 RabbitMQ 연결 설정인 ConnectionFactory 쪽에서, heartbeat 설정을 제어할 수 있는 방법이 있지 않을까?
아니나 다를까, heartbeat를 결정할 수 있는 메서드를 제공하고 있었다.
하지만 뭔가 해답이 되지 않을 것이라 짐작했던 이유는, <sx>, <sy> 값을 결정할 수 있어야 하는데...
이건 그저 하나의 인자만을 받고 있기 때문이었다.
아니나 다를까, 주석을 읽자마자 이건 아니다 싶었다.
- server에서 이 값을 0이 아닌 값을 넣으면, 이걸 절반 나눈 주기로 박동 검사를 실행한다.
- 그러나 어떤 값을 정해놓던, 클라이언트가 어떤 값이라도 제공하면 그 값을 사용한다.
이건 결국 client가 박동 검사 주기를 결정하게 되므로, 여전히 STOMP 정책과는 맞지 않았다.
그래도 혹시나 싶어서, 25sec로 맞춰두고 테스트 해봤으나 결과는 여전했다.
이걸 여기 쓰는 게 맞을까...
나중에서야 알게 된 이유지만, 저 메서드는 Stomp heartbeat가 아닌, AMQP 기반의 heartbeat 설정 방식이다. (그래서 박동 주기 협상 알고리즘이 달랐던 것)
이 메서드의 값을 설정해야 하는 이유는, client가 값을 낮출 수는 있어도, 늘릴 수는 없도록 제약을 걸기 위함이라고 한다.
그러나 나처럼, Client가 Stomp 통신을 시도하려는 경우, 이런 설정은...딱히 별 효과를 보지 못 한다.
📌 RabbitMQ Configuration
Spring에서 제어할 방법이 없다면, RabbitMQ 설정 자체를 바꿔버리면 어떨까?
이쯤되니, 드디어 협상 과정이란 키워드가 나오기 시작한다.
그리고 저 값을 수정하면 된다고 하는데..여전히 두 개의 양수를 받는 게 아니라는 점에서 꺼림칙하다.
이걸 보면, 외부 브로커를 사용했을 때, 협상 과정을 Spring이 아닌 RabbitMQ가 결정한다는 것이 확실해졌다.
이는 SimpleBroker는 Spring에서 heartbeat를 결정할 수 있었지만, 외부 브로커를 사용할 때는 안 되는 이유가 명확해진다.
SimpleBroker는 Spring 내부에서 동작하는 메모리 기반 브로커이므로, heartbeat를 직접 제어할 수 있으며, client와의 협상 주체도 spring이 된다. (애초에 STOMP 프로토콜 레벨의 협상 과정이 불필요함.)
그러나 외부 브로커, 특히 RabbitMQ를 사용할 때는 이야기가 달라진다.
내 예상과는 완전히 다르게, heartbeat 협상을 외부 브로커가 담당하게 된다.
그럼 일단 저 heartbeat 값을 수정해보자.
# Set
rabbitmqctl eval 'application:set_env(rabbit, heartbeat, 1800).'
# Get
rabbitmqctl eval 'application:get_env(rabbit, heartbeat).'
config를 수정하는 방식은 rabbitmq를 껐다 켜야 하므로, 일단 동작 여부를 확인하기 위해 런타입에서 값을 조작해주었다.
공식 문서의 설명에 따르면, 협상 알고리즘은 다음과 같다.
- 둘 다 0으로 설정하면, heart-beat를 완전히 비활성화하는 것을 의미하므로 권장하지 않는다.
- 둘 중 하나가 0이면, 더 큰 값을 고른다.
- 둘 다 0이 아닌 양수를 가지면, 더 작은 값을 고른다.
사실 이때 눈치 챘어야 했다. 이게 Stomp 명세와 다른 이유는 Stomp를 위한 heartbeat 설정이 아니었다는 것을.
client에서 "heartbeat:30000,30000"을 헤더로 전송한 결과, 30sec가 설정되었다. ㅋㅋ
명세에 의하면, 25 sec가 나와야 했지만....그렇다. 이것도 AMQP를 위한 heartbeat 설정이었던 것이다.
RabbitMQ 공식 문서만 보고 알아차렸어야 했는데, 마음이 너무 급했는지 당연한 판단력을 잃었었나 보다.
위 문서에서 확인 사살 당한 후에야 다른 방법을 모색하기 시작했다.
📌 RabbitMQ Stomp
여기까지만 해도, 난 rabbitmq 설정을 통해 구현할 수 있을 것이라 굳게 믿었다.
얼마나 굳게 믿었냐면, rabbitmq 오픈 소스 코드까지 전부다 까봤다.
우선 공식 문서를 보면, STOMP에선 heartbeat 협상 알고리즘이 조금 다른데, RabbitMQ Stomp plugin은 이걸 충분히 지원하고 있다고 한다.
그러나, rabbitmq stomp의 config를 설정할 수 있는 옵션 중에 hearbeat는 없었다.
아니, 이걸 대체 왜 제공을 안 해줌?????? 진짜 이해 안 가네.
어쩌면 내가 또 공식 문서를 잘못 보고 있을 수도 있지 않은가?
그래서 진짜 공식 문서 그 자체인 깃헙으로 쳐들어갔다.
process_connect(Implicit, Frame,
State = #proc_state{channel = none,
config = Config,
ssl_login_name = SSLLoginName,
adapter_info = AdapterInfo}) ->
process_request(
fun(StateN) ->
case negotiate_version(Frame) of
{ok, Version} ->
FT = frame_transformer(Version),
Frame1 = FT(Frame),
{Auth, {Username, Passwd}} = creds(Frame1, SSLLoginName, Config),
{ok, DefaultVHost} = application:get_env(
rabbitmq_stomp, default_vhost),
{ProtoName, _} = AdapterInfo#amqp_adapter_info.protocol,
Res = do_login(
Username, Passwd,
login_header(Frame1, ?HEADER_HOST, DefaultVHost),
login_header(Frame1, ?HEADER_HEART_BEAT, "0,0"),
AdapterInfo#amqp_adapter_info{
protocol = {ProtoName, Version}}, Version,
StateN#proc_state{frame_transformer = FT,
auth_mechanism = Auth,
auth_login = Username}),
case {Res, Implicit} of
{{ok, _, StateN1}, implicit} -> ok(StateN1);
_ -> Res
end;
{error, no_common_version} ->
error("Version mismatch",
"Supported versions are ~ts~n",
[string:join(?SUPPORTED_VERSIONS, ",")],
StateN)
end
end,
State).
난생 처음 보는 언어긴 해도, 어차피 언어가 거기서 거기인지라 이해를 못할 정도는 아니었다.
저것도 해봐야 HEADER_HEART_BEAT가 NULL이면, "0,0"으로 설정해라 뭐 그런 의미일 것이다.
그렇다면 HEADER_HEART_BEAT는 어디서 결정하고 있는가?
스크립트 상단에 import 해오는 파일 정보를 확인할 수 있다.
-include("rabbit_stomp_headers.hrl").
-define(HEADER_HEART_BEAT, "heart-beat").
여기까지 오니, 비로소 heart-beat의 헤더의 존재를 찾아낼 수 있었으나...그게 다였다. ㅠ
저 상수에 값을 전달할 수 있는 구멍을 찾아보다가, 아래에 있는 HEADER_ARGUMENTS 항목이 눈에 띄었다.
저게 대체 뭘까 싶다가, 혹시나 싶어 공식 문서를 보니 rabbitmq stomp plugin에서, 추가로 사용 가능한 oprtional argument 항목들을 정리해놓은 거였다.
놀랍게도 이게 끝이다.
그 어디에도 rabbitmq stomp의 heartbeat를 설정할 수 있다는 이야기는 존재하지 않았다.
2010년 댓글이라서 안 믿었는데, 14년이 지금까지도 내가 이걸 설정할 방법이 없다는 게 말이 되냐...
문서에서 말했던 "RabbitMQ STOMP plugin fully supports this feature"라는 말은, 단순히 STOMP 1.2의 heartbeat 기능을 완벽하게 지원한다는 의미였던 것이다.
클라이언트가 요청하는 heartbeat 설정을 잘 처리할 수 있다는 뜻이지, 서버가 heartbeat를 설정할 수 있도록 허용한다는 말은 아니었다..얼탱이가 없네 진짜.
4. Heartbeat negotiation interceptor
📌 그냥 내가 만들면 되는 거 아니야?
지금까지 과정을 보면, 결국 heartbeat 헤더는 Spring을 거쳐갈 뿐, 실제 협상 과정은 외부 브로커인 RabbitMQ와 진행한다.
그리고 RabbitMQ Stomp plugin은 그저 클라이언트가 하자는 대로 하는 미친 포용력을 자랑한다.
그러다 문득, 한 가지 아이디어가 떠올랐는데..
Spring 거쳐갈 때, 내가 협상 로직을 만들어버리면 되는 것 아닌가?
CONNECT 프레임을 전달 받으면, interceptor에서 heart-beat 헤더를 열어서 협상 로직을 수행한 이후, header를 재설정하는 건 일도 아니었다.
📌 Implementation
@Slf4j
@Component
public class HeartBeatNegotiationInterceptor implements ConnectCommandHandler {
private static final String HEART_BEAT_HEADER = "heart-beat";
private static final long SERVER_HEARTBEAT_SEND = 25000; // sx
private static final long SERVER_HEARTBEAT_RECEIVE = 25000; // sy
@Override
public boolean isSupport(StompCommand command) {
return StompCommand.CONNECT.equals(command);
}
@Override
public void handle(Message<?> message, StompHeaderAccessor accessor) {
String heartbeat = accessor.getFirstNativeHeader(HEART_BEAT_HEADER);
long clientToServer = SERVER_HEARTBEAT_RECEIVE;
long serverToClient = SERVER_HEARTBEAT_SEND;
if (heartbeat == null || heartbeat.equals("0,0")) { // 그냥 명세용. 이렇게 설정한 이유를 명시적으로 알리기 위함.
log.debug("Client attempted connection without heart-beat. Enforcing server's heart-beat policy: {},{}",
SERVER_HEARTBEAT_SEND, SERVER_HEARTBEAT_RECEIVE);
}
if (heartbeat != null) {
String[] parts = heartbeat.split(",");
if (parts.length == 2) {
long cx = Long.parseLong(parts[0]);
long cy = Long.parseLong(parts[1]);
clientToServer = (cx != 0) ? Math.max(cx, SERVER_HEARTBEAT_RECEIVE) : SERVER_HEARTBEAT_RECEIVE;
serverToClient = (cy != 0) ? Math.max(SERVER_HEARTBEAT_SEND, cy) : SERVER_HEARTBEAT_SEND;
log.info("Heart-beat negotiation - Client wants: {}, Server wants: {}",
heartbeat, SERVER_HEARTBEAT_SEND + "," + SERVER_HEARTBEAT_RECEIVE);
log.info("Negotiated heart-beat - Client to Server: {}, Server to Client: {}",
clientToServer, serverToClient);
}
}
accessor.setNativeHeader(HEART_BEAT_HEADER, clientToServer + "," + serverToClient);
}
}
방법 찾느라 허비한 시간 대비, 구현은 5분만에 끝났다. ㅋㅋㅋㅋㅋㅋㅋ
server 측에서 sx, sy를 준비시켜두고, CONNECT 프레임을 수신하면, STOMP 프로토콜 명세에 맞추어 헤더를 조작한다.
물론 여기서 조작한 값은 Spring에선 의미가 없다.
그러나 조작된 heartbeat 헤더는 곧장 rabbitmq로 전달될 것이고, rabbitmq는 또 어미의 마음으로 그저 수용해줄 것이다.
🤔 한 쪽에서 0을 보내면, MAX를 정해야 할까, 0으로 잡아야 할까?
처음에는 0,0으로 값이 들어오면, heartbeat를 0,0으로 맞췄었다.
애초에 client가 보내지도 못 하겠고, 받고 싶지도 않겠다는데 강제로 25sec로 맞추는 게 맞나 싶었으니..
그러나 0,0으로 연결하는 건 권장하지 않기도 하고, 이 정도도 수용 못 할 정도라면 연결 안 해주는 게 맞지 않을까 싶어서 25sec로 고정시켜 버렸다. (최소한 이정도는 client에서 충족해줘야 함)
그리고 좀비 커넥션도 방지할 수 있으므로, 안전성과 보안 측면에서도 0,0은 거부하는 것이 옳다고 생각했다.
그럼에도 이를 어떻게 처리하는 것이 올바른 지에 대해선, 좀 더 고민이 필요하다.
📌 Client Test
비록 허무할 정도로 쉽게 구현하여 해결하긴 했지만, 무심코 지나갈 뻔한 지식을 놓치지 않고 주워갈 수 있어서 다행이었다고 생각한다.
그보다 RabbitMQ Stomp plugin은 저거 진짜 왜 지원 안 해주냐..컨트리뷰터라도 달아볼까. 😏
여튼 분명 내 접근법이나 문제 해결 방법이 잘못된 부분도 많을 것이라 생각한다.
어쩌면 지원해주고 있는 기능인데, 내가 못 찾고 뻘짓한 걸 수도 있다.
그래도 나름대로의 결론을 찾아낸 것에 일단은 만족하고...아니 벌써 새벽 2시 반이네. 하..