001/* 002 * Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.agentsflex.core.model.client.impl; 017 018import com.agentsflex.core.model.chat.ChatConfig; 019import com.agentsflex.core.model.chat.log.ChatMessageLogger; 020import com.agentsflex.core.model.client.OkHttpClientUtil; 021import com.agentsflex.core.model.client.StreamClient; 022import com.agentsflex.core.model.client.StreamClientListener; 023import okhttp3.*; 024import okio.ByteString; 025import org.jetbrains.annotations.NotNull; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029import java.util.Map; 030 031public class WebSocketClient extends WebSocketListener implements StreamClient { 032 private static final Logger log = LoggerFactory.getLogger(WebSocketClient.class); 033 private OkHttpClient okHttpClient; 034 private WebSocket webSocket; 035 private StreamClientListener listener; 036 private ChatConfig config; 037 private boolean isStop = false; 038 private String payload; 039 040 041 public WebSocketClient() { 042 this(OkHttpClientUtil.buildDefaultClient()); 043 } 044 045 public WebSocketClient(OkHttpClient okHttpClient) { 046 if (okHttpClient == null) { 047 throw new IllegalArgumentException("OkHttpClient must not be null"); 048 } 049 this.okHttpClient = okHttpClient; 050 } 051 052 public OkHttpClient getOkHttpClient() { 053 return okHttpClient; 054 } 055 056 public void setOkHttpClient(OkHttpClient okHttpClient) { 057 this.okHttpClient = okHttpClient; 058 } 059 060 @Override 061 public void start(String url, Map<String, String> headers, String payload, StreamClientListener listener, ChatConfig config) { 062 if (isStop) { 063 throw new IllegalStateException("WebSocketClient has been stopped and cannot be reused."); 064 } 065 066 this.listener = listener; 067 this.payload = payload; 068 this.config = config; 069 this.isStop = false; 070 071 Request.Builder builder = new Request.Builder().url(url); 072 if (headers != null && !headers.isEmpty()) { 073 headers.forEach(builder::addHeader); 074 } 075 076 Request request = builder.build(); 077 078 // 创建 WebSocket 连接 079 this.webSocket = okHttpClient.newWebSocket(request, this); 080 ChatMessageLogger.logRequest(config, payload); 081 } 082 083 @Override 084 public void stop() { 085 closeWebSocketAndNotify(); 086 } 087 088 089 //webSocket events 090 @Override 091 public void onOpen(WebSocket webSocket, Response response) { 092 webSocket.send(payload); 093 this.listener.onStart(this); 094 } 095 096 @Override 097 public void onMessage(WebSocket webSocket, String text) { 098 ChatMessageLogger.logResponse(config, text); 099 this.listener.onMessage(this, text); 100 } 101 102 @Override 103 public void onMessage(WebSocket webSocket, ByteString bytes) { 104 this.onMessage(webSocket, bytes.utf8()); 105 } 106 107 @Override 108 public void onClosing(WebSocket webSocket, int code, String reason) { 109 closeWebSocketAndNotify(); 110 } 111 112 @Override 113 public void onFailure(WebSocket webSocket, Throwable t, Response response) { 114 try { 115 Throwable failureThrowable = Util.getFailureThrowable(t, response); 116 this.listener.onFailure(this, failureThrowable); 117 } finally { 118 closeWebSocketAndNotify(); 119 } 120 } 121 122 @Override 123 public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) { 124 closeWebSocketAndNotify(); 125 } 126 127 128 /** 129 * 安全关闭 WebSocket 并通知监听器(确保只执行一次) 130 */ 131 private void closeWebSocketAndNotify() { 132 if (isStop) return; 133 synchronized (this) { 134 if (isStop) return; 135 isStop = true; 136 137 // 先通知 onStop 138 if (this.listener != null) { 139 try { 140 this.listener.onStop(this); 141 } catch (Exception e) { 142 log.warn(e.getMessage(), e); 143 } 144 } 145 146 // 再关闭 WebSocket(幂等:close 多次无害,但避免空指针) 147 if (this.webSocket != null) { 148 try { 149 this.webSocket.close(1000, ""); // 正常关闭 150 } catch (Exception e) { 151 // 忽略关闭异常(连接可能已断) 152 } finally { 153 this.webSocket = null; 154 } 155 } 156 } 157 } 158}