Skip to content

Commit e0d435a

Browse files
author
kevin
committed
fix(websocket): improve error handling in OkHttpWebSocketClient
1 parent d4b8af4 commit e0d435a

1 file changed

Lines changed: 79 additions & 2 deletions

File tree

src/main/java/com/alibaba/dashscope/protocol/okhttp/OkHttpWebSocketClient.java

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.alibaba.dashscope.exception.NoApiKeyException;
1111
import com.alibaba.dashscope.protocol.*;
1212
import com.alibaba.dashscope.protocol.Protocol;
13+
import com.alibaba.dashscope.utils.ApiKeywords;
1314
import com.alibaba.dashscope.utils.Constants;
1415
import com.alibaba.dashscope.utils.JsonUtils;
1516
import com.alibaba.dashscope.utils.StringUtils;
@@ -184,6 +185,23 @@ private void establishWebSocketClient(
184185
flowable.timeout(60, TimeUnit.SECONDS).blockingSubscribe();
185186
return;
186187
} catch (Throwable ex) {
188+
// Unwrap RxJava-wrapped exceptions to find the original ApiException.
189+
Throwable unwrapped = ex;
190+
while (unwrapped != null && !(unwrapped instanceof ApiException)) {
191+
unwrapped = unwrapped.getCause();
192+
}
193+
194+
// Client-side errors (e.g. invalid URL, invalid API key) should not be retried or wrapped.
195+
// Rethrow immediately so the caller sees the original error code.
196+
if (unwrapped instanceof ApiException) {
197+
ApiException apiEx = (ApiException) unwrapped;
198+
// Only rethrow 4xx errors directly; 5xx errors should still be retried.
199+
if (apiEx.getStatus() != null
200+
&& apiEx.getStatus().getStatusCode() >= 400
201+
&& apiEx.getStatus().getStatusCode() < 500) {
202+
throw apiEx;
203+
}
204+
}
187205
reconnectionTimes += 1;
188206
errorMessage = ex.getMessage();
189207
log.error(errorMessage);
@@ -256,6 +274,51 @@ public void onClosing(WebSocket webSocket, int code, String reason) {
256274
}
257275
}
258276

277+
/**
278+
* Parse WebSocket handshake failure response body to extract error details. Returns an
279+
* ApiException with the original error code/message if parsing succeeds, otherwise returns null.
280+
*/
281+
private ApiException parseWebSocketHandshakeError(
282+
int httpStatusCode, String responseBody, Throwable cause) {
283+
if (responseBody == null || responseBody.isEmpty()) {
284+
return null;
285+
}
286+
287+
try {
288+
JsonObject jsonResponse = JsonUtils.parse(responseBody);
289+
String code = "";
290+
String message = "";
291+
String requestId = "";
292+
293+
if (jsonResponse.has(ApiKeywords.REQUEST_ID)) {
294+
requestId = jsonResponse.get(ApiKeywords.REQUEST_ID).getAsString();
295+
}
296+
if (jsonResponse.has(ApiKeywords.CODE) && !jsonResponse.get(ApiKeywords.CODE).isJsonNull()) {
297+
code = jsonResponse.get(ApiKeywords.CODE).getAsString();
298+
}
299+
if (jsonResponse.has(ApiKeywords.MESSAGE)) {
300+
message = jsonResponse.get(ApiKeywords.MESSAGE).getAsString();
301+
}
302+
303+
// If we have a business error code, use it directly with the HTTP status code
304+
if (!code.isEmpty()) {
305+
Status status =
306+
Status.builder()
307+
.statusCode(httpStatusCode)
308+
.code(code)
309+
.message(message)
310+
.requestId(requestId)
311+
.isJson(true)
312+
.build();
313+
return new ApiException(status, cause);
314+
}
315+
} catch (Throwable e) {
316+
log.debug("Failed to parse WebSocket handshake error response as JSON", e);
317+
}
318+
319+
return null;
320+
}
321+
259322
@Override
260323
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
261324
// Invoked when a web socket has been closed due to an error reading from or
@@ -270,8 +333,10 @@ public void onFailure(WebSocket webSocket, Throwable t, Response response) {
270333
}
271334

272335
String responseBody = "";
336+
int httpStatusCode = 0;
273337
// Get response body if there is.
274338
if (response != null) {
339+
httpStatusCode = response.code();
275340
try {
276341
responseBody = response.body().string();
277342
} catch (IOException ex) {
@@ -284,11 +349,23 @@ public void onFailure(WebSocket webSocket, Throwable t, Response response) {
284349
t.getMessage(), t.getCause(), responseBody);
285350
log.error(failureMessage);
286351
isOpen.set(false);
352+
353+
// Try to parse the response body for structured error information
354+
ApiException parsedException = parseWebSocketHandshakeError(httpStatusCode, responseBody, t);
355+
287356
if (connectionEmitter != null && !connectionEmitter.isCancelled()) {
288-
connectionEmitter.onError(new Exception(failureMessage, t));
357+
if (parsedException != null) {
358+
connectionEmitter.onError(parsedException);
359+
} else {
360+
connectionEmitter.onError(new Exception(failureMessage, t));
361+
}
289362
} else if (responseEmitter != null && !responseEmitter.isCancelled()) {
290363
// error on request
291-
responseEmitter.onError(new Exception(failureMessage, t));
364+
if (parsedException != null) {
365+
responseEmitter.onError(parsedException);
366+
} else {
367+
responseEmitter.onError(new Exception(failureMessage, t));
368+
}
292369
} else {
293370
log.error(failureMessage);
294371
}

0 commit comments

Comments
 (0)