Skip to content

Commit b91da7f

Browse files
refactor: improve connection handling and add ConnectionException
1 parent d0fa61b commit b91da7f

8 files changed

Lines changed: 827 additions & 90 deletions

File tree

lib/src/main/java/com/github/getcurrentthread/soopapi/client/SOOPChatClient.java

Lines changed: 115 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import com.github.getcurrentthread.soopapi.config.SOOPChatConfig;
1111
import com.github.getcurrentthread.soopapi.connection.ConnectionManager;
12+
import com.github.getcurrentthread.soopapi.connection.SOOPConnection;
13+
import com.github.getcurrentthread.soopapi.exception.ConnectionException;
1214
import com.github.getcurrentthread.soopapi.model.Message;
1315
import com.github.getcurrentthread.soopapi.util.SOOPChatUtils;
1416

@@ -19,6 +21,7 @@ public class SOOPChatClient implements AutoCloseable {
1921
private final ConnectionManager connectionManager;
2022
private final List<IChatMessageObserver> observers;
2123
private volatile boolean isConnected;
24+
private volatile SOOPConnection connection;
2225

2326
public SOOPChatClient(SOOPChatConfig config) {
2427
this.config = validateConfig(config);
@@ -56,6 +59,11 @@ private void notifyObservers(Message message) {
5659
}
5760
}
5861

62+
/**
63+
* 채팅에 비동기적으로 연결합니다.
64+
*
65+
* @return 연결 작업을 나타내는 CompletableFuture
66+
*/
5967
public CompletableFuture<Void> connectToChat() {
6068
if (isConnected) {
6169
return CompletableFuture.completedFuture(null);
@@ -64,30 +72,89 @@ public CompletableFuture<Void> connectToChat() {
6472
return CompletableFuture.runAsync(
6573
() -> {
6674
try {
67-
connectionManager
68-
.connect(config, this::notifyObservers)
69-
.thenRun(() -> isConnected = true)
70-
.exceptionally(
71-
throwable -> {
72-
LOGGER.log(
73-
Level.SEVERE, "Failed to connect", throwable);
74-
return null;
75-
})
76-
.join();
75+
connection =
76+
connectionManager.connect(config, this::notifyObservers).join();
77+
isConnected = true;
7778
} catch (Exception e) {
78-
throw new CompletionException("Failed to connect to chat", e);
79+
LOGGER.log(Level.SEVERE, "채팅 연결 실패", e);
80+
if (e.getCause() instanceof ConnectionException) {
81+
throw new CompletionException(e.getCause());
82+
} else {
83+
throw new CompletionException("채팅 연결 실패", e);
84+
}
7985
}
8086
});
8187
}
8288

89+
/**
90+
* 채팅 연결을 시도하고 완료될 때까지 현재 스레드를 차단합니다.
91+
*
92+
* @throws ConnectionException 연결 오류가 발생한 경우
93+
*/
94+
public void connectToChattingBlocking() throws ConnectionException {
95+
try {
96+
connectToChat().join();
97+
} catch (CompletionException e) {
98+
if (e.getCause() instanceof ConnectionException) {
99+
throw (ConnectionException) e.getCause();
100+
} else {
101+
throw new ConnectionException("채팅 연결 실패", e);
102+
}
103+
}
104+
}
105+
106+
/**
107+
* 연결이 끊어진 경우 재연결을 시도합니다.
108+
*
109+
* @return 재연결 작업을 나타내는 CompletableFuture
110+
*/
111+
public CompletableFuture<Void> reconnect() {
112+
if (connection == null) {
113+
return CompletableFuture.failedFuture(
114+
new IllegalStateException("연결이 초기화되지 않았습니다. 먼저 connectToChat을 호출하세요."));
115+
}
116+
117+
return connection
118+
.reconnect()
119+
.thenRun(() -> isConnected = true)
120+
.exceptionally(
121+
e -> {
122+
isConnected = false;
123+
LOGGER.log(Level.SEVERE, "재연결 실패", e);
124+
throw new CompletionException(e);
125+
});
126+
}
127+
128+
/**
129+
* 현재 연결 상태를 확인합니다.
130+
*
131+
* @return 연결 상태 정보를 포함하는 CompletableFuture
132+
*/
133+
public CompletableFuture<ConnectionStatus> getConnectionStatus() {
134+
if (connection == null) {
135+
return CompletableFuture.completedFuture(new ConnectionStatus(false, false, 0));
136+
}
137+
138+
return connection
139+
.getStatus()
140+
.thenApply(
141+
status ->
142+
new ConnectionStatus(
143+
status.isConnected(),
144+
status.isReconnecting(),
145+
status.getRetryCount()));
146+
}
147+
148+
/** 현재 연결을 해제합니다. */
83149
public void disconnect() {
84150
if (!isConnected) {
85151
return;
86152
}
87153

88154
try {
89-
connectionManager.disconnect(config.getBid());
155+
connectionManager.disconnect(config.getBid()).join();
90156
isConnected = false;
157+
connection = null;
91158
} catch (Exception e) {
92159
LOGGER.log(Level.WARNING, "Error during disconnect", e);
93160
}
@@ -98,11 +165,46 @@ public void close() {
98165
disconnect();
99166
}
100167

168+
/**
169+
* 현재 연결 상태를 반환합니다.
170+
*
171+
* @return 연결 상태
172+
*/
101173
public boolean isConnected() {
102-
return isConnected;
174+
return isConnected && (connection != null && connection.isConnected());
103175
}
104176

177+
/**
178+
* 방송인 ID를 반환합니다.
179+
*
180+
* @return 방송인 ID
181+
*/
105182
public String getBid() {
106183
return config.getBid();
107184
}
185+
186+
/** 연결 상태 정보를 제공하는 클래스 */
187+
public static class ConnectionStatus {
188+
private final boolean connected;
189+
private final boolean reconnecting;
190+
private final int retryCount;
191+
192+
public ConnectionStatus(boolean connected, boolean reconnecting, int retryCount) {
193+
this.connected = connected;
194+
this.reconnecting = reconnecting;
195+
this.retryCount = retryCount;
196+
}
197+
198+
public boolean isConnected() {
199+
return connected;
200+
}
201+
202+
public boolean isReconnecting() {
203+
return reconnecting;
204+
}
205+
206+
public int getRetryCount() {
207+
return retryCount;
208+
}
209+
}
108210
}

lib/src/main/java/com/github/getcurrentthread/soopapi/config/SOOPChatConfig.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,140 @@
11
package com.github.getcurrentthread.soopapi.config;
22

3+
import java.time.Duration;
4+
35
import javax.net.ssl.SSLContext;
46

57
public class SOOPChatConfig {
68
private final String bid;
79
private final String bno;
810
private final SSLContext sslContext;
11+
private final Duration connectionTimeout;
12+
private final int maxRetryAttempts;
13+
14+
private static final Duration DEFAULT_CONNECTION_TIMEOUT = Duration.ofSeconds(30);
15+
private static final int DEFAULT_MAX_RETRY_ATTEMPTS = 5;
916

1017
private SOOPChatConfig(Builder builder) {
1118
this.bid = builder.bid;
1219
this.bno = builder.bno;
1320
this.sslContext = builder.sslContext;
21+
this.connectionTimeout = builder.connectionTimeout;
22+
this.maxRetryAttempts = builder.maxRetryAttempts;
1423
}
1524

25+
/**
26+
* 방송인 ID를 반환합니다.
27+
*
28+
* @return 방송인 ID
29+
*/
1630
public String getBid() {
1731
return bid;
1832
}
1933

34+
/**
35+
* 방송 번호를 반환합니다.
36+
*
37+
* @return 방송 번호 (null일 수 있음)
38+
*/
2039
public String getBno() {
2140
return bno;
2241
}
2342

43+
/**
44+
* SSL Context를 반환합니다.
45+
*
46+
* @return SSL Context (null일 수 있음)
47+
*/
2448
public SSLContext getSSLContext() {
2549
return sslContext;
2650
}
2751

52+
/**
53+
* 연결 타임아웃을 반환합니다.
54+
*
55+
* @return 연결 타임아웃
56+
*/
57+
public Duration getConnectionTimeout() {
58+
return connectionTimeout;
59+
}
60+
61+
/**
62+
* 최대 재시도 횟수를 반환합니다.
63+
*
64+
* @return 최대 재시도 횟수
65+
*/
66+
public int getMaxRetryAttempts() {
67+
return maxRetryAttempts;
68+
}
69+
70+
/** SOOPChatConfig 빌더 클래스 */
2871
public static class Builder {
2972
private String bid;
3073
private String bno;
3174
private SSLContext sslContext;
75+
private Duration connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
76+
private int maxRetryAttempts = DEFAULT_MAX_RETRY_ATTEMPTS;
3277

78+
/**
79+
* 방송인 ID를 설정합니다.
80+
*
81+
* @param bid 방송인 ID
82+
* @return 빌더 인스턴스
83+
*/
3384
public Builder bid(String bid) {
3485
this.bid = bid;
3586
return this;
3687
}
3788

89+
/**
90+
* 방송 번호를 설정합니다.
91+
*
92+
* @param bno 방송 번호
93+
* @return 빌더 인스턴스
94+
*/
3895
public Builder bno(String bno) {
3996
this.bno = bno;
4097
return this;
4198
}
4299

100+
/**
101+
* SSL Context를 설정합니다.
102+
*
103+
* @param sslContext SSL Context
104+
* @return 빌더 인스턴스
105+
*/
43106
public Builder sslContext(SSLContext sslContext) {
44107
this.sslContext = sslContext;
45108
return this;
46109
}
47110

111+
/**
112+
* 연결 타임아웃을 설정합니다.
113+
*
114+
* @param connectionTimeout 연결 타임아웃
115+
* @return 빌더 인스턴스
116+
*/
117+
public Builder connectionTimeout(Duration connectionTimeout) {
118+
this.connectionTimeout = connectionTimeout;
119+
return this;
120+
}
121+
122+
/**
123+
* 최대 재시도 횟수를 설정합니다.
124+
*
125+
* @param maxRetryAttempts 최대 재시도 횟수
126+
* @return 빌더 인스턴스
127+
*/
128+
public Builder maxRetryAttempts(int maxRetryAttempts) {
129+
this.maxRetryAttempts = maxRetryAttempts;
130+
return this;
131+
}
132+
133+
/**
134+
* SOOPChatConfig 인스턴스를 생성합니다.
135+
*
136+
* @return 구성된 SOOPChatConfig 인스턴스
137+
*/
48138
public SOOPChatConfig build() {
49139
return new SOOPChatConfig(this);
50140
}

0 commit comments

Comments
 (0)