Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d0f218d52 | ||
|
|
01f2c66f48 | ||
|
|
8fda3fa2da | ||
|
|
69f40e7d36 | ||
|
|
bc918e7664 | ||
|
|
aba3930417 | ||
|
|
4476f41aa6 | ||
|
|
5122683761 | ||
|
|
924835dd55 |
@@ -4,6 +4,7 @@ import android.util.Base64;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -14,16 +15,22 @@ import java.util.Arrays;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
final class AppEventSocketClient {
|
||||
private static final long HEARTBEAT_INTERVAL_MS = 15000L;
|
||||
private static final int SOCKET_CONNECT_TIMEOUT_MS = 10000;
|
||||
private static final int SOCKET_READ_TIMEOUT_MS = 45000;
|
||||
|
||||
interface Listener {
|
||||
void onMessage(String text);
|
||||
void onClosed();
|
||||
}
|
||||
|
||||
private final Listener listener;
|
||||
private final Object writeLock = new Object();
|
||||
private Socket socket;
|
||||
private BufferedInputStream input;
|
||||
private BufferedOutputStream output;
|
||||
private volatile boolean closed;
|
||||
private Thread heartbeatThread;
|
||||
|
||||
AppEventSocketClient(Listener listener) {
|
||||
this.listener = listener;
|
||||
@@ -40,12 +47,11 @@ final class AppEventSocketClient {
|
||||
sendFrame(8, new byte[0]);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
closeSocketQuietly();
|
||||
}
|
||||
|
||||
boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
private void run(String baseUrl) {
|
||||
@@ -55,6 +61,7 @@ final class AppEventSocketClient {
|
||||
input = new BufferedInputStream(socket.getInputStream());
|
||||
output = new BufferedOutputStream(socket.getOutputStream());
|
||||
handshake(uri);
|
||||
startHeartbeat();
|
||||
while (!closed) {
|
||||
Frame frame = readFrame();
|
||||
if (frame.opcode == 1) {
|
||||
@@ -69,12 +76,7 @@ final class AppEventSocketClient {
|
||||
} finally {
|
||||
closed = true;
|
||||
listener.onClosed();
|
||||
try {
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
closeSocketQuietly();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,10 +93,39 @@ final class AppEventSocketClient {
|
||||
if (port == -1) {
|
||||
port = "wss".equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
|
||||
}
|
||||
Socket raw = new Socket();
|
||||
raw.connect(new InetSocketAddress(uri.getHost(), port), SOCKET_CONNECT_TIMEOUT_MS);
|
||||
Socket connected;
|
||||
if ("wss".equalsIgnoreCase(uri.getScheme())) {
|
||||
return SSLSocketFactory.getDefault().createSocket(uri.getHost(), port);
|
||||
connected = ((SSLSocketFactory) SSLSocketFactory.getDefault())
|
||||
.createSocket(raw, uri.getHost(), port, true);
|
||||
} else {
|
||||
connected = raw;
|
||||
}
|
||||
return new Socket(uri.getHost(), port);
|
||||
connected.setKeepAlive(true);
|
||||
connected.setTcpNoDelay(true);
|
||||
connected.setSoTimeout(SOCKET_READ_TIMEOUT_MS);
|
||||
return connected;
|
||||
}
|
||||
|
||||
private void startHeartbeat() {
|
||||
heartbeatThread = new Thread(() -> {
|
||||
while (!closed) {
|
||||
try {
|
||||
Thread.sleep(HEARTBEAT_INTERVAL_MS);
|
||||
if (!closed) {
|
||||
sendFrame(9, new byte[0]);
|
||||
}
|
||||
} catch (InterruptedException ignored) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
} catch (Exception error) {
|
||||
closeSocketQuietly();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, "app-events-ws-heartbeat");
|
||||
heartbeatThread.start();
|
||||
}
|
||||
|
||||
private void handshake(URI uri) throws Exception {
|
||||
@@ -181,32 +212,47 @@ final class AppEventSocketClient {
|
||||
}
|
||||
|
||||
private void sendFrame(int opcode, byte[] payload) throws Exception {
|
||||
if (output == null) {
|
||||
return;
|
||||
}
|
||||
output.write(0x80 | opcode);
|
||||
byte[] mask = new byte[4];
|
||||
new SecureRandom().nextBytes(mask);
|
||||
int length = payload.length;
|
||||
if (length < 126) {
|
||||
output.write(0x80 | length);
|
||||
} else if (length <= 0xffff) {
|
||||
output.write(0x80 | 126);
|
||||
output.write((length >>> 8) & 0xff);
|
||||
output.write(length & 0xff);
|
||||
} else {
|
||||
output.write(0x80 | 127);
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
output.write((length >>> (8 * i)) & 0xff);
|
||||
synchronized (writeLock) {
|
||||
if (output == null) {
|
||||
return;
|
||||
}
|
||||
output.write(0x80 | opcode);
|
||||
byte[] mask = new byte[4];
|
||||
new SecureRandom().nextBytes(mask);
|
||||
int length = payload.length;
|
||||
if (length < 126) {
|
||||
output.write(0x80 | length);
|
||||
} else if (length <= 0xffff) {
|
||||
output.write(0x80 | 126);
|
||||
output.write((length >>> 8) & 0xff);
|
||||
output.write(length & 0xff);
|
||||
} else {
|
||||
output.write(0x80 | 127);
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
output.write((length >>> (8 * i)) & 0xff);
|
||||
}
|
||||
}
|
||||
output.write(mask);
|
||||
byte[] masked = Arrays.copyOf(payload, payload.length);
|
||||
for (int i = 0; i < masked.length; i++) {
|
||||
masked[i] = (byte) (masked[i] ^ mask[i % 4]);
|
||||
}
|
||||
output.write(masked);
|
||||
output.flush();
|
||||
}
|
||||
output.write(mask);
|
||||
byte[] masked = Arrays.copyOf(payload, payload.length);
|
||||
for (int i = 0; i < masked.length; i++) {
|
||||
masked[i] = (byte) (masked[i] ^ mask[i % 4]);
|
||||
}
|
||||
|
||||
private void closeSocketQuietly() {
|
||||
if (heartbeatThread != null) {
|
||||
heartbeatThread.interrupt();
|
||||
heartbeatThread = null;
|
||||
}
|
||||
try {
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
output.write(masked);
|
||||
output.flush();
|
||||
}
|
||||
|
||||
private static final class Frame {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -19,6 +20,10 @@ import java.util.concurrent.RejectedExecutionException;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
final class TerminalSocketClient {
|
||||
private static final long HEARTBEAT_INTERVAL_MS = 15000L;
|
||||
private static final int SOCKET_CONNECT_TIMEOUT_MS = 10000;
|
||||
private static final int SOCKET_READ_TIMEOUT_MS = 45000;
|
||||
|
||||
interface Listener {
|
||||
void onConnected();
|
||||
void onOutput(String data);
|
||||
@@ -35,6 +40,7 @@ final class TerminalSocketClient {
|
||||
private BufferedOutputStream output;
|
||||
private volatile boolean closed;
|
||||
private Thread thread;
|
||||
private Thread heartbeatThread;
|
||||
|
||||
TerminalSocketClient(Listener listener) {
|
||||
this.listener = listener;
|
||||
@@ -97,6 +103,7 @@ final class TerminalSocketClient {
|
||||
"rows", rows
|
||||
);
|
||||
listener.onConnected();
|
||||
startHeartbeat();
|
||||
readLoop();
|
||||
} catch (Exception error) {
|
||||
if (!closed) {
|
||||
@@ -123,10 +130,39 @@ final class TerminalSocketClient {
|
||||
if (port == -1) {
|
||||
port = "wss".equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
|
||||
}
|
||||
Socket raw = new Socket();
|
||||
raw.connect(new InetSocketAddress(uri.getHost(), port), SOCKET_CONNECT_TIMEOUT_MS);
|
||||
Socket connected;
|
||||
if ("wss".equalsIgnoreCase(uri.getScheme())) {
|
||||
return SSLSocketFactory.getDefault().createSocket(uri.getHost(), port);
|
||||
connected = ((SSLSocketFactory) SSLSocketFactory.getDefault())
|
||||
.createSocket(raw, uri.getHost(), port, true);
|
||||
} else {
|
||||
connected = raw;
|
||||
}
|
||||
return new Socket(uri.getHost(), port);
|
||||
connected.setKeepAlive(true);
|
||||
connected.setTcpNoDelay(true);
|
||||
connected.setSoTimeout(SOCKET_READ_TIMEOUT_MS);
|
||||
return connected;
|
||||
}
|
||||
|
||||
private void startHeartbeat() {
|
||||
heartbeatThread = new Thread(() -> {
|
||||
while (!closed) {
|
||||
try {
|
||||
Thread.sleep(HEARTBEAT_INTERVAL_MS);
|
||||
if (!closed) {
|
||||
sendFrame(9, new byte[0]);
|
||||
}
|
||||
} catch (InterruptedException ignored) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
} catch (Exception error) {
|
||||
closeSocketQuietly();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, "terminal-ws-heartbeat");
|
||||
heartbeatThread.start();
|
||||
}
|
||||
|
||||
private void handshake(URI uri) throws Exception {
|
||||
@@ -279,6 +315,10 @@ final class TerminalSocketClient {
|
||||
}
|
||||
|
||||
private void closeSocketQuietly() {
|
||||
if (heartbeatThread != null) {
|
||||
heartbeatThread.interrupt();
|
||||
heartbeatThread = null;
|
||||
}
|
||||
try {
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
|
||||
Reference in New Issue
Block a user