com.koushikdutta.async.http.SocketIOClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of androidasync Show documentation
Show all versions of androidasync Show documentation
Asynchronous socket, http(s) (client+server) and websocket library for android. Based on nio, not threads.
package com.koushikdutta.async.http;
import java.util.Arrays;
import java.util.HashSet;
import org.json.JSONArray;
import org.json.JSONObject;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import com.koushikdutta.async.AsyncServer;
import com.koushikdutta.async.NullDataCallback;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.future.Cancellable;
import com.koushikdutta.async.future.Future;
import com.koushikdutta.async.future.SimpleFuture;
import com.koushikdutta.async.http.AsyncHttpClient.WebSocketConnectCallback;
public class SocketIOClient {
public static interface SocketIOConnectCallback {
public void onConnectCompleted(Exception ex, SocketIOClient client);
}
public static interface JSONCallback {
public void onJSON(JSONObject json);
}
public static interface StringCallback {
public void onString(String string);
}
public static interface EventCallback {
public void onEvent(String event, JSONArray arguments);
}
private static void reportError(FutureImpl future, Handler handler, final SocketIOConnectCallback callback, final Exception e) {
if (!future.setComplete(e))
return;
if (handler != null) {
AsyncServer.post(handler, new Runnable() {
@Override
public void run() {
callback.onConnectCompleted(e, null);
}
});
}
else {
callback.onConnectCompleted(e, null);
}
}
private void emitRaw(int type, String message) {
webSocket.send(String.format("%d:::%s", type, message));
}
public void emit(String name, JSONArray args) {
final JSONObject event = new JSONObject();
try {
event.put("name", name);
event.put("args", args);
emitRaw(5, event.toString());
}
catch (Exception e) {
}
}
public void emit(final String message) {
emitRaw(3, message);
}
public void emit(final JSONObject jsonMessage) {
emitRaw(4, jsonMessage.toString());
}
private static class FutureImpl extends SimpleFuture {
}
public static class SocketIORequest extends AsyncHttpPost {
String channel;
public String getChannel() {
return channel;
}
public SocketIORequest(String uri) {
super(Uri.parse(uri).buildUpon().encodedPath("/socket.io/1/").build().toString());
channel = Uri.parse(uri).getPath();
if (TextUtils.isEmpty(channel))
channel = null;
}
}
public static Future connect(final AsyncHttpClient client, String uri, final SocketIOConnectCallback callback) {
return connect(client, new SocketIORequest(uri), callback);
}
public static Future connect(final AsyncHttpClient client, final SocketIORequest request, final SocketIOConnectCallback callback) {
final Handler handler = Looper.myLooper() == null ? null : new Handler();
final FutureImpl ret = new FutureImpl();
// dont invoke onto main handler, as it is unnecessary until a session is ready or failed
request.setHandler(null);
// initiate a session
Cancellable cancel = client.executeString(request, new AsyncHttpClient.StringCallback() {
@Override
public void onCompleted(final Exception e, AsyncHttpResponse response, String result) {
if (e != null) {
reportError(ret, handler, callback, e);
return;
}
try {
String[] parts = result.split(":");
String session = parts[0];
final int heartbeat;
if (!"".equals(parts[1]))
heartbeat = Integer.parseInt(parts[1]) / 2 * 1000;
else
heartbeat = 0;
String transportsLine = parts[3];
String[] transports = transportsLine.split(",");
HashSet set = new HashSet(Arrays.asList(transports));
if (!set.contains("websocket"))
throw new Exception("websocket not supported");
final String sessionUrl = request.getUri().toString() + "websocket/" + session + "/";
final SocketIOClient socketio = new SocketIOClient(handler, heartbeat, sessionUrl, client);
socketio.reconnect(callback, ret);
}
catch (Exception ex) {
reportError(ret, handler, callback, ex);
}
}
});
ret.setParent(cancel);
return ret;
}
CompletedCallback closedCallback;
public CompletedCallback getClosedCallback() {
return closedCallback;
}
public void setClosedCallback(CompletedCallback callback) {
closedCallback = callback;
}
JSONCallback jsonCallback;
public JSONCallback getJSONCallback() {
return jsonCallback;
}
public void setJSONCallback(JSONCallback callback) {
jsonCallback = callback;
}
StringCallback stringCallback;
public StringCallback getStringCallback() {
return stringCallback;
}
public void setStringCallback(StringCallback callback) {
stringCallback = callback;
}
EventCallback eventCallback;
public EventCallback getEventCallback() {
return eventCallback;
}
public void setEventCallback(EventCallback callback) {
eventCallback = callback;
}
String sessionUrl;
WebSocket webSocket;
AsyncHttpClient httpClient;
private SocketIOClient(Handler handler, int heartbeat, String sessionUrl, AsyncHttpClient httpCliet) {
this.handler = handler;
this.heartbeat = heartbeat;
this.sessionUrl = sessionUrl;
this.httpClient = httpCliet;
}
public boolean isConnected() {
return connected && !disconnected && webSocket != null && webSocket.isOpen();
}
public void disconnect() {
webSocket.setStringCallback(null);
webSocket.setDataCallback(null);
webSocket.setClosedCallback(null);
webSocket.close();
webSocket = null;
}
private void reconnect(final SocketIOConnectCallback callback, final FutureImpl ret) {
if (isConnected()) {
httpClient.getServer().post(new Runnable() {
@Override
public void run() {
ret.setComplete(new Exception("already connected"));
}
});
return;
}
connected = false;
disconnected = false;
Cancellable cancel = httpClient.websocket(sessionUrl, null, new WebSocketConnectCallback() {
@Override
public void onCompleted(Exception ex, WebSocket webSocket) {
if (ex != null) {
reportError(ret, handler, callback, ex);
return;
}
SocketIOClient.this.webSocket = webSocket;
attach(callback, ret);
}
});
ret.setParent(cancel);
}
private Future reconnect(final SocketIOConnectCallback callback) {
FutureImpl ret = new FutureImpl();
reconnect(callback, ret);
return ret;
}
boolean connected;
boolean disconnected;
int heartbeat;
void setupHeartbeat() {
final WebSocket ws = webSocket;
Runnable heartbeatRunner = new Runnable() {
@Override
public void run() {
if (heartbeat <= 0 || disconnected || !connected || ws != webSocket || ws == null || !ws.isOpen())
return;
webSocket.send("2:::");
webSocket.getServer().postDelayed(this, heartbeat);
}
};
heartbeatRunner.run();
}
Handler handler;
private void attach(final SocketIOConnectCallback callback, final FutureImpl future) {
webSocket.setDataCallback(new NullDataCallback());
webSocket.setClosedCallback(new CompletedCallback() {
@Override
public void onCompleted(final Exception ex) {
final boolean wasDiconnected = disconnected;
disconnected = true;
webSocket = null;
Runnable runner = new Runnable() {
@Override
public void run() {
if (!connected) {
// closed connection before open...
callback.onConnectCompleted(ex == null ? new Exception("connection failed") : ex, null);
}
else if (!wasDiconnected) {
if (closedCallback != null)
closedCallback.onCompleted(ex == null ? new Exception("connection failed") : ex);
}
}
};
if (handler != null) {
AsyncServer.post(handler, runner);
}
else {
runner.run();
}
}
});
webSocket.setStringCallback(new WebSocket.StringCallback() {
@Override
public void onStringAvailable(String message) {
try {
// Log.d(TAG, "Message: " + message);
String[] parts = message.split(":", 4);
int code = Integer.parseInt(parts[0]);
switch (code) {
case 0:
if (!connected)
throw new Exception("received disconnect before client connect");
disconnected = true;
// disconnect
webSocket.close();
if (closedCallback != null) {
if (handler != null) {
AsyncServer.post(handler, new Runnable() {
@Override
public void run() {
closedCallback.onCompleted(null);
}
});
}
else {
closedCallback.onCompleted(null);
}
}
break;
case 1:
// connect
if (connected)
throw new Exception("received duplicate connect event");
if (!future.setComplete(SocketIOClient.this))
throw new Exception("request canceled");
connected = true;
setupHeartbeat();
callback.onConnectCompleted(null, SocketIOClient.this);
break;
case 2:
// heartbeat
webSocket.send("2::");
break;
case 3: {
if (!connected)
throw new Exception("received message before client connect");
// message
final String messageId = parts[1];
final String dataString = parts[3];
// ack
if(!"".equals(messageId)) {
webSocket.send(String.format("6:::%s", messageId));
}
if (stringCallback != null) {
if (handler != null) {
AsyncServer.post(handler, new Runnable() {
@Override
public void run() {
stringCallback.onString(dataString);
}
});
}
else {
stringCallback.onString(dataString);
}
}
break;
}
case 4: {
if (!connected)
throw new Exception("received message before client connect");
//json message
final String messageId = parts[1];
final String dataString = parts[3];
final JSONObject jsonMessage = new JSONObject(dataString);
// ack
if(!"".equals(messageId)) {
webSocket.send(String.format("6:::%s", messageId));
}
if (jsonCallback != null) {
if (handler != null) {
AsyncServer.post(handler, new Runnable() {
@Override
public void run() {
jsonCallback.onJSON(jsonMessage);
}
});
}
else {
jsonCallback.onJSON(jsonMessage);
}
}
break;
}
case 5: {
if (!connected)
throw new Exception("received message before client connect");
final String messageId = parts[1];
final String dataString = parts[3];
JSONObject data = new JSONObject(dataString);
final String event = data.getString("name");
final JSONArray args = data.optJSONArray("args");
// ack
if(!"".equals(messageId)) {
webSocket.send(String.format("6:::%s", messageId));
}
if (eventCallback != null) {
if (handler != null) {
AsyncServer.post(handler, new Runnable() {
@Override
public void run() {
eventCallback.onEvent(event, args);
}
});
}
else {
eventCallback.onEvent(event, args);
}
}
break;
}
case 6:
// ACK
break;
case 7:
// error
throw new Exception(message);
case 8:
// noop
break;
default:
throw new Exception("unknown code");
}
}
catch (Exception ex) {
webSocket.close();
if (!connected) {
reportError(future, handler, callback, ex);
}
else {
disconnected = true;
if (closedCallback != null) {
closedCallback.onCompleted(ex);
}
}
}
}
});
}
}