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.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -14,16 +15,22 @@ import java.util.Arrays;
|
|||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
final class AppEventSocketClient {
|
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 {
|
interface Listener {
|
||||||
void onMessage(String text);
|
void onMessage(String text);
|
||||||
void onClosed();
|
void onClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Listener listener;
|
private final Listener listener;
|
||||||
|
private final Object writeLock = new Object();
|
||||||
private Socket socket;
|
private Socket socket;
|
||||||
private BufferedInputStream input;
|
private BufferedInputStream input;
|
||||||
private BufferedOutputStream output;
|
private BufferedOutputStream output;
|
||||||
private volatile boolean closed;
|
private volatile boolean closed;
|
||||||
|
private Thread heartbeatThread;
|
||||||
|
|
||||||
AppEventSocketClient(Listener listener) {
|
AppEventSocketClient(Listener listener) {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
@@ -40,12 +47,11 @@ final class AppEventSocketClient {
|
|||||||
sendFrame(8, new byte[0]);
|
sendFrame(8, new byte[0]);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
try {
|
closeSocketQuietly();
|
||||||
if (socket != null) {
|
}
|
||||||
socket.close();
|
|
||||||
}
|
boolean isClosed() {
|
||||||
} catch (Exception ignored) {
|
return closed;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void run(String baseUrl) {
|
private void run(String baseUrl) {
|
||||||
@@ -55,6 +61,7 @@ final class AppEventSocketClient {
|
|||||||
input = new BufferedInputStream(socket.getInputStream());
|
input = new BufferedInputStream(socket.getInputStream());
|
||||||
output = new BufferedOutputStream(socket.getOutputStream());
|
output = new BufferedOutputStream(socket.getOutputStream());
|
||||||
handshake(uri);
|
handshake(uri);
|
||||||
|
startHeartbeat();
|
||||||
while (!closed) {
|
while (!closed) {
|
||||||
Frame frame = readFrame();
|
Frame frame = readFrame();
|
||||||
if (frame.opcode == 1) {
|
if (frame.opcode == 1) {
|
||||||
@@ -69,12 +76,7 @@ final class AppEventSocketClient {
|
|||||||
} finally {
|
} finally {
|
||||||
closed = true;
|
closed = true;
|
||||||
listener.onClosed();
|
listener.onClosed();
|
||||||
try {
|
closeSocketQuietly();
|
||||||
if (socket != null) {
|
|
||||||
socket.close();
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,10 +93,39 @@ final class AppEventSocketClient {
|
|||||||
if (port == -1) {
|
if (port == -1) {
|
||||||
port = "wss".equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
|
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())) {
|
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 {
|
private void handshake(URI uri) throws Exception {
|
||||||
@@ -181,32 +212,47 @@ final class AppEventSocketClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendFrame(int opcode, byte[] payload) throws Exception {
|
private void sendFrame(int opcode, byte[] payload) throws Exception {
|
||||||
if (output == null) {
|
synchronized (writeLock) {
|
||||||
return;
|
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(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++) {
|
private void closeSocketQuietly() {
|
||||||
masked[i] = (byte) (masked[i] ^ mask[i % 4]);
|
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 {
|
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.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -19,6 +20,10 @@ import java.util.concurrent.RejectedExecutionException;
|
|||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
final class TerminalSocketClient {
|
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 {
|
interface Listener {
|
||||||
void onConnected();
|
void onConnected();
|
||||||
void onOutput(String data);
|
void onOutput(String data);
|
||||||
@@ -35,6 +40,7 @@ final class TerminalSocketClient {
|
|||||||
private BufferedOutputStream output;
|
private BufferedOutputStream output;
|
||||||
private volatile boolean closed;
|
private volatile boolean closed;
|
||||||
private Thread thread;
|
private Thread thread;
|
||||||
|
private Thread heartbeatThread;
|
||||||
|
|
||||||
TerminalSocketClient(Listener listener) {
|
TerminalSocketClient(Listener listener) {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
@@ -97,6 +103,7 @@ final class TerminalSocketClient {
|
|||||||
"rows", rows
|
"rows", rows
|
||||||
);
|
);
|
||||||
listener.onConnected();
|
listener.onConnected();
|
||||||
|
startHeartbeat();
|
||||||
readLoop();
|
readLoop();
|
||||||
} catch (Exception error) {
|
} catch (Exception error) {
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
@@ -123,10 +130,39 @@ final class TerminalSocketClient {
|
|||||||
if (port == -1) {
|
if (port == -1) {
|
||||||
port = "wss".equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
|
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())) {
|
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 {
|
private void handshake(URI uri) throws Exception {
|
||||||
@@ -279,6 +315,10 @@ final class TerminalSocketClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void closeSocketQuietly() {
|
private void closeSocketQuietly() {
|
||||||
|
if (heartbeatThread != null) {
|
||||||
|
heartbeatThread.interrupt();
|
||||||
|
heartbeatThread = null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (socket != null) {
|
if (socket != null) {
|
||||||
socket.close();
|
socket.close();
|
||||||
|
|||||||
Reference in New Issue
Block a user