rewrite ws

This commit is contained in:
luvletter2333 2022-11-03 20:52:16 +08:00
parent 0b68460d21
commit 46f7f843b1
No known key found for this signature in database
GPG Key ID: A26A8880836E1978
4 changed files with 68 additions and 100 deletions

View File

@ -347,7 +347,8 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1"
implementation "com.squareup.okhttp3:okhttp:4.10.0"
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.10"
implementation 'com.neovisionaries:nv-websocket-client:2.14'
implementation 'dnsjava:dnsjava:3.4.1'
implementation "org.dizitart:nitrite:3.4.3"

View File

@ -35,7 +35,7 @@ public class NekoConfig {
public static ConfigItem migrate = addConfig("NekoConfigMigrate", configTypeBool, false);
public static ConfigItem largeAvatarInDrawer = addConfig("AvatarAsBackground", configTypeInt, 0); // 0:TG Default 1:NekoX Default 2:Large Avatar
public static ConfigItem unreadBadgeOnBackButton = addConfig("unreadBadgeOnBackButton", configTypeBool, false);
public static ConfigItem customPublicProxyIP = addConfig("customPublicProxyIP", configTypeString, "");
// public static ConfigItem customPublicProxyIP = addConfig("customPublicProxyIP", configTypeString, "");
public static ConfigItem update_download_soucre = addConfig("update_download_soucre", configTypeInt, 0); // 0: Github 1: Channel 2:CDNDrive, removed
public static ConfigItem useCustomEmoji = addConfig("useCustomEmoji", configTypeBool, false);
public static ConfigItem repeatConfirm = addConfig("repeatConfirm", configTypeBool, false);

View File

@ -2,12 +2,19 @@ package tw.nekomimi.nekogram.proxy.tcp2ws;
import android.annotation.SuppressLint;
import com.neovisionaries.ws.client.ThreadType;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFactory;
import com.neovisionaries.ws.client.WebSocketFrame;
import com.neovisionaries.ws.client.WebSocketListener;
import com.neovisionaries.ws.client.WebSocketState;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.checkerframework.common.value.qual.IntVal;
import org.telegram.messenger.FileLog;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -16,17 +23,19 @@ import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocket;
import cn.hutool.http.ssl.AndroidSupportSSLFactory;
import cn.hutool.http.ssl.CustomProtocolsSSLFactory;
import tw.nekomimi.nekogram.NekoConfig;
import tw.nekomimi.nekogram.utils.DnsFactory;
@ -37,7 +46,7 @@ public class WsProxyHandler extends Thread {
private final WsLoader.Bean bean;
private Socket clientSocket;
private WebSocket wsSocket = null;
private WebSocket webSocket = null;
private final byte[] buffer = new byte[4096];
private String wsHost = "";
@ -67,7 +76,7 @@ public class WsProxyHandler extends Thread {
FileLog.d("socks5 handshake and websocket connection done");
// Start read from client socket and send to websocket
this.clientSocket.setSoTimeout(1000);
while (clientSocket != null && wsSocket != null && wsStatus.get() == STATUS_OPENED && !clientSocket.isClosed() && !clientSocket.isInputShutdown()) {
while (clientSocket != null && webSocket != null && wsStatus.get() == STATUS_OPENED && !clientSocket.isClosed() && !clientSocket.isInputShutdown()) {
int readLen = 0;
try {
readLen = this.clientInputStream.read(buffer);
@ -77,9 +86,12 @@ public class WsProxyHandler extends Thread {
continue;
}
FileLog.d(String.format("[%s] read %d from local", wsHost, readLen));
if (readLen == -1) throw new Exception(String.format("[%s] socks closed", wsHost));;
if (wsStatus.get() != STATUS_OPENED) throw new Exception(String.format("[%s] ws closed when trying to write", wsHost));;
this.wsSocket.send(ByteString.of(buffer, 0, readLen));
if (readLen == -1) throw new Exception(String.format("[%s] socks closed", wsHost));
;
if (wsStatus.get() != STATUS_OPENED)
throw new Exception(String.format("[%s] ws closed when trying to write", wsHost));
;
this.webSocket.sendBinary(Arrays.copyOf(buffer, readLen));
}
} catch (SocketException se) {
if ("Socket closed".equals(se.getMessage())) {
@ -107,95 +119,50 @@ public class WsProxyHandler extends Thread {
} catch (IOException e) {
// ignore
}
// try {
// if (wsSocket != null) {
// wsSocket.cancel();
// }
// } catch (Exception e) {
// // ignore
// }
try {
if (webSocket != null) {
webSocket.sendClose();
}
} catch (Exception e) {
// ignore
}
clientSocket = null;
// wsSocket = null;
webSocket = null;
}
private static volatile OkHttpClient okhttpClient = null;
private static final Object okhttpLock = new Object();
private static OkHttpClient getOkHttpClientInstance() {
if (okhttpClient == null) {
synchronized (okhttpLock) {
if (okhttpClient == null) {
okhttpClient = new OkHttpClient.Builder()
.dns(s -> {
ArrayList<InetAddress> ret = new ArrayList<>();
FileLog.d("okhttpWS: resolving: " + s);
if (StringUtils.isNotBlank(NekoConfig.customPublicProxyIP.String())) {
ret.add(InetAddress.getByName(NekoConfig.customPublicProxyIP.String()));
} else {
ret.addAll(DnsFactory.lookup(s));
}
FileLog.d("okhttpWS: resolved: " + ret);
return ret;
})
.build();
}
}
}
return okhttpClient;
}
private void connectToServer(String wsHost) {
private void connectToServer(String wsHost) throws Exception {
this.wsHost = wsHost;
FileLog.e(new Exception("WS: Connect To Server"));
getOkHttpClientInstance()
.newWebSocket(new Request.Builder()
.url((bean.getTls() ? "wss://" : "ws://") + wsHost + "/api")
.build(), new WebSocketListener() {
@Override
public void onOpen(@NotNull okhttp3.WebSocket webSocket, @NotNull Response response) {
WsProxyHandler.this.wsSocket = webSocket;
wsStatus.set(STATUS_OPENED);
connecting.countDown();
}
WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000);
webSocket = factory.createSocket((bean.getTls() ? "wss://" : "ws://") + wsHost + "/api");
webSocket.addListener(new WebSocketAdapter() {
@Override
public void onBinaryMessage(WebSocket websocket, byte[] binary) throws Exception {
WsProxyHandler.this.clientOutputStream.write(binary);
}
@Override
public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
FileLog.e("[" + wsHost + "] Failure:" + t);
wsStatus.set(STATUS_FAILED);
connecting.countDown();
}
@Override
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
FileLog.e(cause);
wsStatus.set(STATUS_FAILED);
}
@Override
public void onMessage(@NotNull okhttp3.WebSocket webSocket, @NotNull ByteString bytes) {
FileLog.d("[" + wsHost + "] Received " + bytes.size() + " bytes from ws");
try {
if (wsStatus.get() == STATUS_OPENED && !WsProxyHandler.this.clientSocket.isClosed())
WsProxyHandler.this.clientOutputStream.write(bytes.toByteArray());
} catch (IOException e) {
FileLog.e(e);
wsStatus.set(STATUS_FAILED);
webSocket.cancel();
}
}
@Override
public void onConnectError(WebSocket websocket, WebSocketException exception) throws Exception {
FileLog.d(String.format("[%s] WS connect failed: %s", wsHost, exception.toString()));
wsStatus.set(STATUS_FAILED);
connecting.countDown();
}
@Override
public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
FileLog.d("[" + wsHost + "] Received text: " + text);
}
@Override
public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
FileLog.d("[" + wsHost + "] Closed: " + code + " " + reason);
wsStatus.set(STATUS_CLOSED);
}
@Override
public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
FileLog.d("[" + wsHost + "] Closing: " + code + " " + reason);
wsStatus.set(STATUS_CLOSED);
}
});
@Override
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
FileLog.d(String.format("[%s] WS connected", wsHost));
wsStatus.set(STATUS_OPENED);
connecting.countDown();
}
});
webSocket.addProtocol("binary");
webSocket.connect();
}
private static final byte[] RESP_AUTH = new byte[]{0x05, 0x00};

View File

@ -119,9 +119,9 @@ public class NekoGeneralSettingsActivity extends BaseFragment {
private final AbstractConfigCell autoUpdateSubInfoRow = cellGroup.appendCell(new ConfigCellTextCheck(NekoConfig.autoUpdateSubInfo));
private final AbstractConfigCell useSystemDNSRow = cellGroup.appendCell(new ConfigCellTextCheck(NekoConfig.useSystemDNS));
private final AbstractConfigCell customDoHRow = cellGroup.appendCell(new ConfigCellTextInput(null, NekoConfig.customDoH, "https://1.0.0.1/dns-query", null));
private final AbstractConfigCell customPublicProxyIPRow = cellGroup.appendCell(new ConfigCellTextDetail(NekoConfig.customPublicProxyIP, (view, position) -> {
customDialog_BottomInputString(position, NekoConfig.customPublicProxyIP, LocaleController.getString("customPublicProxyIPNotice"), "IP");
}, LocaleController.getString("UsernameEmpty", R.string.UsernameEmpty)));
// private final AbstractConfigCell customPublicProxyIPRow = cellGroup.appendCell(new ConfigCellTextDetail(NekoConfig.customPublicProxyIP, (view, position) -> {
// customDialog_BottomInputString(position, NekoConfig.customPublicProxyIP, LocaleController.getString("customPublicProxyIPNotice"), "IP");
// }, LocaleController.getString("UsernameEmpty", R.string.UsernameEmpty)));
private final AbstractConfigCell dividerConnection = cellGroup.appendCell(new ConfigCellDivider());
private final AbstractConfigCell headerFolder = cellGroup.appendCell(new ConfigCellHeader(LocaleController.getString("Folder")));