aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/AsyncServer.java2
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java10
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/SocketIOClient.java378
-rw-r--r--AndroidAsyncSample/src/com/koushikdutta/async/sample/MainActivity.java40
-rw-r--r--README.md35
5 files changed, 453 insertions, 12 deletions
diff --git a/AndroidAsync/src/com/koushikdutta/async/AsyncServer.java b/AndroidAsync/src/com/koushikdutta/async/AsyncServer.java
index 0b89c53..40386eb 100644
--- a/AndroidAsync/src/com/koushikdutta/async/AsyncServer.java
+++ b/AndroidAsync/src/com/koushikdutta/async/AsyncServer.java
@@ -444,7 +444,7 @@ public class AsyncServer {
// if there is nothing to select now, make sure we don't have an empty key set
// which means it would be time to turn this thread off.
if (selector.keys().size() == 0 && !keepRunning) {
- Log.i(LOGTAG, "Shutting down. keys: " + selector.keys().size() + " keepRunning: " + keepRunning);
+// Log.i(LOGTAG, "Shutting down. keys: " + selector.keys().size() + " keepRunning: " + keepRunning);
return;
}
}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java
index 4cd4b48..0036c23 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java
@@ -16,7 +16,6 @@ import junit.framework.Assert;
import org.json.JSONException;
import org.json.JSONObject;
-import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -35,7 +34,6 @@ import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.callback.RequestCallback;
import com.koushikdutta.async.http.AsyncHttpClientMiddleware.OnRequestCompleteData;
import com.koushikdutta.async.http.libcore.RawHeaders;
-import com.koushikdutta.async.http.libcore.ResponseHeaders;
import com.koushikdutta.async.stream.OutputStreamDataCallback;
public class AsyncHttpClient {
@@ -481,9 +479,9 @@ public class AsyncHttpClient {
public void onCompleted(Exception ex, WebSocket webSocket);
}
- public void websocket(final AsyncHttpRequest req, String protocol, final WebSocketConnectCallback callback) {
+ public Cancelable websocket(final AsyncHttpRequest req, String protocol, final WebSocketConnectCallback callback) {
WebSocketImpl.addWebSocketUpgradeHeaders(req.getHeaders().getHeaders(), protocol);
- execute(req, new HttpConnectCallback() {
+ return execute(req, new HttpConnectCallback() {
@Override
public void onConnectCompleted(Exception ex, AsyncHttpResponse response) {
if (ex != null) {
@@ -498,9 +496,9 @@ public class AsyncHttpClient {
});
}
- public void websocket(String uri, String protocol, final WebSocketConnectCallback callback) {
+ public Cancelable websocket(String uri, String protocol, final WebSocketConnectCallback callback) {
final AsyncHttpGet get = new AsyncHttpGet(uri);
- websocket(get, protocol, callback);
+ return websocket(get, protocol, callback);
}
AsyncServer getServer() {
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/SocketIOClient.java b/AndroidAsync/src/com/koushikdutta/async/http/SocketIOClient.java
new file mode 100644
index 0000000..42ec18c
--- /dev/null
+++ b/AndroidAsync/src/com/koushikdutta/async/http/SocketIOClient.java
@@ -0,0 +1,378 @@
+package com.koushikdutta.async.http;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import com.koushikdutta.async.Cancelable;
+import com.koushikdutta.async.NullDataCallback;
+import com.koushikdutta.async.SimpleCancelable;
+import com.koushikdutta.async.callback.CompletedCallback;
+import com.koushikdutta.async.http.AsyncHttpClient.WebSocketConnectCallback;
+
+public class SocketIOClient {
+ public static interface SocketIOConnectCallback {
+ public void onConnectCompleted(Exception ex, SocketIOClient client);
+ }
+
+ public static interface SocketIOCallback {
+ public void on(String event, JSONArray arguments);
+ public void onDisconnect(int code, String reason);
+ public void onJSON(JSONObject json);
+ public void onMessage(String message);
+ public void onError(Exception error);
+ }
+
+ 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(Handler handler, final SocketIOConnectCallback callback, final Exception e) {
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onConnectCompleted(e, null);
+ }
+ });
+ }
+ else {
+ callback.onConnectCompleted(e, null);
+ }
+ }
+
+ private static class CancelableImpl extends SimpleCancelable {
+ public Cancelable session;
+ public Cancelable websocket;
+
+ @Override
+ public Cancelable cancel() {
+ if (isCompleted())
+ return this;
+
+ if (isCanceled())
+ return this;
+
+ if (session != null)
+ session.cancel();
+ if (websocket != null)
+ websocket.cancel();
+ return super.cancel();
+ }
+ }
+
+ public static Cancelable connect(final AsyncHttpClient client, String uri, final SocketIOConnectCallback callback) {
+ // get the socket.io endpoint
+ final String websocketUrl = uri.replaceAll("/$", "") + "/socket.io/1/";
+
+ final Handler handler;
+ if (Looper.myLooper() == null)
+ handler = null;
+ else
+ handler = new Handler();
+
+
+ final CancelableImpl cancel = new CancelableImpl();
+
+ AsyncHttpPost post = new AsyncHttpPost(websocketUrl);
+ // initiate a session
+ cancel.session = client.execute(post, new AsyncHttpClient.StringCallback() {
+ @Override
+ public void onCompleted(final Exception e, AsyncHttpResponse response, String result) {
+ if (e != null) {
+ reportError(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<String> set = new HashSet<String>(Arrays.asList(transports));
+ if (!set.contains("websocket"))
+ throw new Exception("websocket not supported");
+
+ cancel.websocket = client.websocket(websocketUrl + "websocket/" + session, null, new WebSocketConnectCallback() {
+ @Override
+ public void onCompleted(Exception ex, WebSocket webSocket) {
+ if (ex != null) {
+ reportError(handler, callback, ex);
+ return;
+ }
+
+ final SocketIOClient client = new SocketIOClient(webSocket, handler);
+ client.heartbeat = heartbeat;
+ client.attach(callback);
+ }
+ });
+ }
+ catch (Exception ex) {
+ reportError(handler, callback, ex);
+ }
+ }
+ });
+
+
+ return cancel;
+ }
+
+ 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;
+ }
+
+ WebSocket webSocket;
+ private SocketIOClient(WebSocket webSocket, Handler handler) {
+ this.webSocket = webSocket;
+ this.handler = handler;
+ }
+
+ boolean connected;
+ boolean disconnected;
+ int heartbeat;
+ Runnable heartbeatRunner = new Runnable() {
+ @Override
+ public void run() {
+ if (heartbeat <= 0 || disconnected || !connected || !webSocket.isOpen())
+ return;
+ webSocket.send("2:::");
+ webSocket.getServer().postDelayed(this, heartbeat);
+ }
+ };
+
+ Handler handler;
+ private void attach(final SocketIOConnectCallback callback) {
+ webSocket.setDataCallback(new NullDataCallback());
+ webSocket.setClosedCallback(new CompletedCallback() {
+ @Override
+ public void onCompleted(final Exception ex) {
+ 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 (!disconnected) {
+ if (closedCallback != null)
+ closedCallback.onCompleted(ex == null ? new Exception("connection failed") : ex);
+ }
+ }
+ };
+
+ if (handler != null) {
+ handler.post(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) {
+ handler.post(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");
+
+ connected = true;
+ heartbeatRunner.run();
+ 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) {
+ handler.post(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) {
+ handler.post(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.getJSONArray("args");
+
+ // ack
+ if(!"".equals(messageId)) {
+ webSocket.send(String.format("6:::%s", messageId));
+ }
+
+ if (eventCallback != null) {
+ if (handler != null) {
+ handler.post(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(handler, callback, ex);
+ }
+ else {
+ disconnected = true;
+ if (closedCallback != null) {
+ closedCallback.onCompleted(ex);
+ }
+ }
+ }
+ }
+ });
+ }
+}
diff --git a/AndroidAsyncSample/src/com/koushikdutta/async/sample/MainActivity.java b/AndroidAsyncSample/src/com/koushikdutta/async/sample/MainActivity.java
index 2f23cc4..764dd5b 100644
--- a/AndroidAsyncSample/src/com/koushikdutta/async/sample/MainActivity.java
+++ b/AndroidAsyncSample/src/com/koushikdutta/async/sample/MainActivity.java
@@ -2,20 +2,18 @@ package com.koushikdutta.async.sample;
import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.net.URLConnection;
import java.util.ArrayList;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
+import org.json.JSONArray;
+import org.json.JSONObject;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
-import android.net.http.HttpResponseCache;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
@@ -31,6 +29,11 @@ import com.koushikdutta.async.http.AsyncHttpClient;
import com.koushikdutta.async.http.AsyncHttpPost;
import com.koushikdutta.async.http.AsyncHttpResponse;
import com.koushikdutta.async.http.ResponseCacheMiddleware;
+import com.koushikdutta.async.http.SocketIOClient;
+import com.koushikdutta.async.http.SocketIOClient.EventCallback;
+import com.koushikdutta.async.http.SocketIOClient.JSONCallback;
+import com.koushikdutta.async.http.SocketIOClient.SocketIOConnectCallback;
+import com.koushikdutta.async.http.SocketIOClient.StringCallback;
import com.koushikdutta.async.http.UrlEncodedFormBody;
public class MainActivity extends Activity {
@@ -71,6 +74,35 @@ public class MainActivity extends Activity {
chart = (ImageView)findViewById(R.id.chart);
showCacheToast();
+
+
+ SocketIOClient.connect(AsyncHttpClient.getDefaultInstance(), "http://192.168.1.2:3000", new SocketIOConnectCallback() {
+ @Override
+ public void onConnectCompleted(Exception ex, SocketIOClient client) {
+ System.out.println("hello!");
+
+ client.setStringCallback(new StringCallback() {
+ @Override
+ public void onString(String string) {
+ System.out.println(string);
+ }
+ });
+
+ client.setEventCallback(new EventCallback() {
+ @Override
+ public void onEvent(String event, JSONArray arguments) {
+ System.out.println("event: " + event + " args: " + arguments.toString());
+ }
+ });
+
+ client.setJSONCallback(new JSONCallback() {
+ @Override
+ public void onJSON(JSONObject json) {
+ System.out.println("json: " + json.toString());
+ }
+ });
+ }
+ });
}
void showCacheToast() {
diff --git a/README.md b/README.md
index aa0cb80..16c780e 100644
--- a/README.md
+++ b/README.md
@@ -87,7 +87,40 @@ AsyncHttpClient.getDefaultInstance().websocket(get, "my-protocol", new WebSocket
```
-### AndroidAsync also let's you create simple HTTP servers:
+### AndroidAsync also supports socket.io
+
+```java
+SocketIOClient.connect(AsyncHttpClient.getDefaultInstance(), "http://192.168.1.2:3000", new SocketIOConnectCallback() {
+ @Override
+ public void onConnectCompleted(Exception ex, SocketIOClient client) {
+ System.out.println("hello!");
+
+ client.setStringCallback(new StringCallback() {
+ @Override
+ public void onString(String string) {
+ System.out.println(string);
+ }
+ });
+
+ client.setEventCallback(new EventCallback() {
+ @Override
+ public void onEvent(String event, JSONArray arguments) {
+ System.out.println("event: " + event + " args: " + arguments.toString());
+ }
+ });
+
+ client.setJSONCallback(new JSONCallback() {
+ @Override
+ public void onJSON(JSONObject json) {
+ System.out.println("json: " + json.toString());
+ }
+ });
+ }
+});
+```
+
+
+### AndroidAsync also let's you create simple HTTP servers (and websocket servers):
```java
// listen on port 5000