io.socket.engineio.client.transports.WebSocket Maven / Gradle / Ivy
package io.socket.engineio.client.transports;
import io.socket.engineio.client.Transport;
import io.socket.engineio.parser.Packet;
import io.socket.engineio.parser.Parser;
import io.socket.parseqs.ParseQS;
import io.socket.thread.EventThread;
import io.socket.utf8.UTF8Exception;
import io.socket.yeast.Yeast;
import okhttp3.Dns;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.ws.WebSocketCall;
import okhttp3.ws.WebSocketListener;
import okio.Buffer;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import static okhttp3.ws.WebSocket.BINARY;
import static okhttp3.ws.WebSocket.TEXT;
public class WebSocket extends Transport {
public static final String NAME = "websocket";
private static final Logger logger = Logger.getLogger(PollingXHR.class.getName());
private okhttp3.ws.WebSocket ws;
private WebSocketCall wsCall;
public WebSocket(Options opts) {
super(opts);
this.name = NAME;
}
protected void doOpen() {
Map> headers = new TreeMap>(String.CASE_INSENSITIVE_ORDER);
this.emit(EVENT_REQUEST_HEADERS, headers);
final WebSocket self = this;
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
// turn off timeouts (github.com/socketio/engine.io-client-java/issues/32)
.connectTimeout(0, TimeUnit.MILLISECONDS)
.readTimeout(0, TimeUnit.MILLISECONDS)
.writeTimeout(0, TimeUnit.MILLISECONDS);
if (this.sslContext != null) {
SSLSocketFactory factory = sslContext.getSocketFactory();// (SSLSocketFactory) SSLSocketFactory.getDefault();
clientBuilder.sslSocketFactory(factory);
}
if (this.hostnameVerifier != null) {
clientBuilder.hostnameVerifier(this.hostnameVerifier);
}
Request.Builder builder = new Request.Builder().url(uri());
for (Map.Entry> entry : headers.entrySet()) {
for (String v : entry.getValue()) {
builder.addHeader(entry.getKey(), v);
}
}
final Request request = builder.build();
final OkHttpClient client = clientBuilder.dns(new Dns() {
@Override
public List lookup(String hostname) throws UnknownHostException {
if (hostname == null) throw new UnknownHostException("hostname == null");
try {
return Arrays.asList(InetAddress.getAllByName(hostname));
} catch (Exception e) {
throw new UnknownHostException(e.getMessage());
}
}
}).build();
wsCall = WebSocketCall.create(client, request);
wsCall.enqueue(new WebSocketListener() {
@Override
public void onOpen(okhttp3.ws.WebSocket webSocket, Response response) {
ws = webSocket;
final Map> headers = response.headers().toMultimap();
EventThread.exec(new Runnable() {
@Override
public void run() {
self.emit(EVENT_RESPONSE_HEADERS, headers);
self.onOpen();
}
});
}
@Override
public void onMessage(final ResponseBody responseBody) throws IOException {
Object data = null;
if (responseBody.contentType() == TEXT) {
data = responseBody.string();
} else if (responseBody.contentType() == BINARY) {
data = responseBody.source().readByteArray();
} else {
EventThread.exec(new Runnable() {
@Override
public void run() {
self.onError("Unknown payload type: " + responseBody.contentType(), new IllegalStateException());
}
});
}
responseBody.source().close();
final Object finalData = data;
EventThread.exec(new Runnable() {
@Override
public void run() {
if (finalData == null) {
return;
}
if (finalData instanceof String) {
self.onData((String) finalData);
} else {
self.onData((byte[]) finalData);
}
}
});
}
@Override
public void onPong(Buffer payload) {
}
@Override
public void onClose(int code, String reason) {
EventThread.exec(new Runnable() {
@Override
public void run() {
self.onClose();
}
});
}
@Override
public void onFailure(final IOException e, final Response response) {
EventThread.exec(new Runnable() {
@Override
public void run() {
self.onError("websocket error", e);
}
});
}
});
client.dispatcher().executorService().shutdown();
}
protected void write(Packet[] packets) throws UTF8Exception {
final WebSocket self = this;
this.writable = false;
final Runnable done = new Runnable() {
@Override
public void run() {
// fake drain
// defer to next tick to allow Socket to clear writeBuffer
EventThread.nextTick(new Runnable() {
@Override
public void run() {
self.writable = true;
self.emit(EVENT_DRAIN);
}
});
}
};
final int[] total = new int[]{packets.length};
for (Packet packet : packets) {
Parser.encodePacket(packet, new Parser.EncodeCallback() {
@Override
public void call(Object packet) {
try {
if (packet instanceof String) {
self.ws.sendMessage(RequestBody.create(TEXT, (String) packet));
} else if (packet instanceof byte[]) {
self.ws.sendMessage(RequestBody.create(BINARY, (byte[]) packet));
}
} catch (IOException e) {
logger.fine("websocket closed before onclose event");
} catch (IllegalStateException e) {
logger.fine("websocket IllegalStateException " + e.getMessage());
doClose();
}
if (0 == --total[0]) done.run();
}
});
}
}
@Override
protected void onClose() {
super.onClose();
}
protected void doClose() {
if (wsCall != null) {
wsCall.cancel();
}
if (ws != null) {
try {
ws.close(1000, "");
} catch (IOException e) {
// websocket already closed
} catch (IllegalStateException e) {
// websocket already closed
}
}
}
protected String uri() {
Map query = this.query;
if (query == null) {
query = new HashMap();
}
String schema = this.secure ? "wss" : "ws";
String port = "";
if (this.port > 0 && (("wss".equals(schema) && this.port != 443)
|| ("ws".equals(schema) && this.port != 80))) {
port = ":" + this.port;
}
if (this.timestampRequests) {
query.put(this.timestampParam, Yeast.yeast());
}
String derivedQuery = ParseQS.encode(query);
if (derivedQuery.length() > 0) {
derivedQuery = "?" + derivedQuery;
}
boolean ipv6 = this.hostname.contains(":");
return schema + "://" + (ipv6 ? "[" + this.hostname + "]" : this.hostname) + port + this.path + derivedQuery;
}
}