From 7b2014678ef492d756ec117467aa239cfd03c7da Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koushd@gmail.com> Date: Fri, 4 May 2012 00:01:26 -0700 Subject: [PATCH 01/56] Add initial Socket.IO support. --- .gitignore | 6 +- project.properties | 2 +- .../android_websockets/HybiParser.java | 35 ++- .../android_websockets/SocketIOClient.java | 226 ++++++++++++++++++ 4 files changed, 265 insertions(+), 4 deletions(-) create mode 100644 src/com/codebutler/android_websockets/SocketIOClient.java diff --git a/.gitignore b/.gitignore index 81d81b4..bd55530 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ -gen/ -local.properties +bin +gen +.classpath +.project diff --git a/project.properties b/project.properties index 1d35d2d..cd0ca12 100644 --- a/project.properties +++ b/project.properties @@ -12,4 +12,4 @@ android.library=true # Project target. -target=android-14 +target=android-8 diff --git a/src/com/codebutler/android_websockets/HybiParser.java b/src/com/codebutler/android_websockets/HybiParser.java index 5565c1a..44eb1c7 100644 --- a/src/com/codebutler/android_websockets/HybiParser.java +++ b/src/com/codebutler/android_websockets/HybiParser.java @@ -332,9 +332,42 @@ private int getInteger(byte[] bytes) throws ProtocolError { } return (int) i; } + + /** + * Copied from AOSP Arrays.java. + */ + /** + * Copies elements from {@code original} into a new array, from indexes start (inclusive) to + * end (exclusive). The original order of elements is preserved. + * If {@code end} is greater than {@code original.length}, the result is padded + * with the value {@code (byte) 0}. + * + * @param original the original array + * @param start the start index, inclusive + * @param end the end index, exclusive + * @return the new array + * @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length} + * @throws IllegalArgumentException if {@code start > end} + * @throws NullPointerException if {@code original == null} + * @since 1.6 + */ + private static byte[] copyOfRange(byte[] original, int start, int end) { + if (start > end) { + throw new IllegalArgumentException(); + } + int originalLength = original.length; + if (start < 0 || start > originalLength) { + throw new ArrayIndexOutOfBoundsException(); + } + int resultLength = end - start; + int copyLength = Math.min(resultLength, originalLength - start); + byte[] result = new byte[resultLength]; + System.arraycopy(original, start, result, 0, copyLength); + return result; + } private byte[] slice(byte[] array, int start) { - return Arrays.copyOfRange(array, start, array.length); + return copyOfRange(array, start, array.length); } public static class ProtocolError extends IOException { diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java new file mode 100644 index 0000000..3767444 --- /dev/null +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -0,0 +1,226 @@ +package com.codebutler.android_websockets; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashSet; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.net.http.AndroidHttpClient; +import android.os.Looper; + +public class SocketIOClient { + public static interface Handler { + public void onConnect(); + + public void on(String event, JSONArray arguments); + + public void onDisconnect(int code, String reason); + + public void onError(Exception error); + } + + URI mURI; + Handler mHandler; + String mSession; + int mHeartbeat; + int mClosingTimeout; + WebSocketClient mClient; + + public SocketIOClient(URI uri, Handler handler) { + mURI = uri; + mHandler = handler; + } + + private static String downloadUriAsString(final HttpUriRequest req) throws IOException { + AndroidHttpClient client = AndroidHttpClient.newInstance("android-websockets"); + try { + HttpResponse res = client.execute(req); + return readToEnd(res.getEntity().getContent()); + } + finally { + client.close(); + } + } + + private static byte[] readToEndAsArray(InputStream input) throws IOException { + DataInputStream dis = new DataInputStream(input); + byte[] stuff = new byte[1024]; + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + int read = 0; + while ((read = dis.read(stuff)) != -1) { + buff.write(stuff, 0, read); + } + + return buff.toByteArray(); + } + + private static String readToEnd(InputStream input) throws IOException { + return new String(readToEndAsArray(input)); + } + + android.os.Handler mSendHandler; + Looper mSendLooper; + + public void send(String name, JSONArray args) throws JSONException { + final JSONObject event = new JSONObject(); + event.put("name", name); + event.put("args", args); + System.out.println(event.toString()); + mSendHandler.post(new Runnable() { + @Override + public void run() { + mClient.send(String.format("5:::%s", event.toString())); + } + }); + } + + private void connectSession() throws URISyntaxException { + mClient = new WebSocketClient(new URI(mURI.toString() + "/socket.io/1/websocket/" + mSession), new WebSocketClient.Handler() { + @Override + public void onMessage(byte[] data) { + try { + mClient.disconnect(); + } + catch (IOException e) { + } + mHandler.onError(new Exception("Unexpected binary data")); + } + + @Override + public void onMessage(String message) { + try { + System.out.println(message); + String[] parts = message.split(":", 4); + int code = Integer.parseInt(parts[0]); + switch (code) { + case 1: + onConnect(); + break; + case 2: + // heartbeat + break; + case 3: + // message + case 4: + // json message + throw new Exception("message type not supported"); + case 5: { + final String messageId = parts[1]; + final String dataString = parts[3]; + JSONObject data = new JSONObject(dataString); + String event = data.getString("name"); + JSONArray args = data.getJSONArray("args"); + if (!"".equals(messageId)) { + mSendHandler.post(new Runnable() { + @Override + public void run() { + mClient.send(String.format("6:::%s", messageId)); + } + }); + } + mHandler.on(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) { + onError(ex); + } + } + + @Override + public void onError(Exception error) { + cleanup(); + mHandler.onError(error); + } + + @Override + public void onDisconnect(int code, String reason) { + cleanup(); + // attempt reconnect? + mHandler.onDisconnect(code, reason); + } + + @Override + public void onConnect() { + mSendHandler.postDelayed(new Runnable() { + @Override + public void run() { + mSendHandler.postDelayed(this, mHeartbeat); + mClient.send("2:::"); + } + }, mHeartbeat); + } + }, null); + mClient.connect(); + } + + public void disconnect() throws IOException { + cleanup(); + } + + private void cleanup() { + try { + mClient.disconnect(); + } + catch (IOException e) { + } + mSendLooper.quit(); + mSendLooper = null; + mSendHandler = null; + } + + public void connect() { + new Thread() { + public void run() { + HttpPost post = new HttpPost(mURI.toString() + "/socket.io/1/"); + try { + String line = downloadUriAsString(post); + String[] parts = line.split(":"); + mSession = parts[0]; + String heartbeat = parts[1]; + if (!"".equals(heartbeat)) + mHeartbeat = Integer.parseInt(heartbeat) / 2 * 1000; + 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"); + + Looper.prepare(); + mSendLooper = Looper.myLooper(); + mSendHandler = new android.os.Handler(); + + connectSession(); + + Looper.loop(); + } + catch (Exception e) { + mHandler.onError(e); + } + }; + }.start(); + } +} From 47a7822850ebe347ab724f475b9be848dbbfa4fa Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koushd@gmail.com> Date: Fri, 4 May 2012 00:08:57 -0700 Subject: [PATCH 02/56] Add proper cleanup for errors. --- src/com/codebutler/android_websockets/SocketIOClient.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index 3767444..d568410 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -89,11 +89,7 @@ private void connectSession() throws URISyntaxException { mClient = new WebSocketClient(new URI(mURI.toString() + "/socket.io/1/websocket/" + mSession), new WebSocketClient.Handler() { @Override public void onMessage(byte[] data) { - try { - mClient.disconnect(); - } - catch (IOException e) { - } + cleanup(); mHandler.onError(new Exception("Unexpected binary data")); } @@ -146,6 +142,7 @@ public void run() { } } catch (Exception ex) { + cleanup(); onError(ex); } } From 3e4a15972781ab98bbaeb98c2475720cc2ed3069 Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koushd@gmail.com> Date: Fri, 4 May 2012 10:05:43 -0700 Subject: [PATCH 03/56] Fix cleanup to null out the client. Prevent multiple connections while already connected. --- src/com/codebutler/android_websockets/SocketIOClient.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index d568410..a603907 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -156,7 +156,7 @@ public void onError(Exception error) { @Override public void onDisconnect(int code, String reason) { cleanup(); - // attempt reconnect? + // attempt reconnect with same session? mHandler.onDisconnect(code, reason); } @@ -181,6 +181,7 @@ public void disconnect() throws IOException { private void cleanup() { try { mClient.disconnect(); + mClient = null; } catch (IOException e) { } @@ -190,6 +191,8 @@ private void cleanup() { } public void connect() { + if (mClient != null) + return; new Thread() { public void run() { HttpPost post = new HttpPost(mURI.toString() + "/socket.io/1/"); From 2c88ddbdf0994cc8d83e394b3264d068651b620f Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koush@koushikdutta.com> Date: Fri, 4 May 2012 10:12:01 -0700 Subject: [PATCH 04/56] Documentation for Socket.IO support. --- README.md | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 17a934d..eeb13d6 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Ported from JavaScript to Java by [Eric Butler](https://twitter.com/codebutler) ## Usage -Here's the entire API: +Here's the entire WebSocket API: ```java List<BasicNameValuePair> extraHeaders = Arrays.asList( @@ -52,6 +52,49 @@ client.send(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }); client.disconnect(); ``` +And here's the Socket.IO API built on top of that: + + +```java +List<BasicNameValuePair> extraHeaders = Arrays.asList( + new BasicNameValuePair("Cookie", "session=abcd"); +); + +WebSocketClient client = new WebSocketClient(URI.create("wss://example.com"), new WebSocketClient.Handler() { + @Override + public void onConnect() { + Log.d(TAG, "Connected!"); + } + + @Override + public void on(String event, JSONArray arguments) { + Log.d(TAG, String.format("Got event %s: %s", event, arguments.toString())); + } + + @Override + public void onDisconnect(int code, String reason) { + Log.d(TAG, String.format("Disconnected! Code: %d Reason: %s", code, reason)); + } + + @Override + public void onError(Exception error) { + Log.e(TAG, "Error!", error); + } +}, extraHeaders); + +client.connect(); + +// Later… +JSONArray arguments = new JSONArray(); +arguments.put("first argument"); +JSONObject second = new JSONObject(); +second.put("dictionary", true); +arguments.put(second) +client.send("hello", arguments); +client.disconnect(); +``` + + ## TODO From 76d8cf5b6b640f4cd65e8868a6f7dc085ab525df Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koush@koushikdutta.com> Date: Fri, 4 May 2012 10:12:25 -0700 Subject: [PATCH 05/56] Remove extraHeaders for Socket.IO connections. --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index eeb13d6..f518aac 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,6 @@ And here's the Socket.IO API built on top of that: ```java -List<BasicNameValuePair> extraHeaders = Arrays.asList( - new BasicNameValuePair("Cookie", "session=abcd"); -); - WebSocketClient client = new WebSocketClient(URI.create("wss://example.com"), new WebSocketClient.Handler() { @Override public void onConnect() { @@ -80,7 +76,7 @@ WebSocketClient client = new WebSocketClient(URI.create("wss://example.com"), ne public void onError(Exception error) { Log.e(TAG, "Error!", error); } -}, extraHeaders); +}); client.connect(); From b0c10ccab25a43499f78e18c06110fa1c1ed1903 Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koush@koushikdutta.com> Date: Fri, 4 May 2012 10:13:19 -0700 Subject: [PATCH 06/56] Copyright for my changes. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f518aac..78533bc 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ client.disconnect(); Copyright (c) 2009-2012 James Coglan Copyright (c) 2012 Eric Butler + Copyright (c) 2012 Koushik Dutta Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in From 44caf8e6fb8e080cc2ee4fb60c527f4f9dbf4723 Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koush@koushikdutta.com> Date: Fri, 4 May 2012 10:15:43 -0700 Subject: [PATCH 07/56] Update the title and credits. --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 78533bc..e05556b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ -# WebSocket client for Android +# WebSocket and Socket.IO client for Android -A very simple bare-minimum WebSocket client for Android. +A very simple bare-minimum WebSocket and Socket.IO client for Android. ## Credits The hybi parser is based on code from the [faye project](https://github.com/faye/faye-websocket-node). Faye is Copyright (c) 2009-2012 James Coglan. Many thanks for the great open-source library! -Ported from JavaScript to Java by [Eric Butler](https://twitter.com/codebutler) <eric@codebutler.com>. +The hybi parser was ported from JavaScript to Java by [Eric Butler](https://twitter.com/codebutler) <eric@codebutler.com>. + +The WebSocket client was written by [Eric Butler](https://twitter.com/codebutler) <eric@codebutler.com>. + +The Socket.IO client was written by [Koushik Dutta](https://twitter.com/koush). ## Usage From c511e7329258cbc672bbc493ada35fddc119e7a3 Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koush@koushikdutta.com> Date: Fri, 4 May 2012 10:16:45 -0700 Subject: [PATCH 08/56] Clean up the titles around usage. --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e05556b..c2260f6 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,7 @@ The WebSocket client was written by [Eric Butler](https://twitter.com/codebutler The Socket.IO client was written by [Koushik Dutta](https://twitter.com/koush). -## Usage - -Here's the entire WebSocket API: +## WebSocket Usage ```java List<BasicNameValuePair> extraHeaders = Arrays.asList( @@ -56,8 +54,7 @@ client.send(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }); client.disconnect(); ``` -And here's the Socket.IO API built on top of that: - +## Socket.IO Usage ```java WebSocketClient client = new WebSocketClient(URI.create("wss://example.com"), new WebSocketClient.Handler() { From 6558e4c5e0f173b508f2fd72159f1e70249021d5 Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koush@koushikdutta.com> Date: Fri, 4 May 2012 10:19:36 -0700 Subject: [PATCH 09/56] Update SocketIOClient to actually use the SocketIOClient class. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c2260f6..54b898c 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ client.disconnect(); ## Socket.IO Usage ```java -WebSocketClient client = new WebSocketClient(URI.create("wss://example.com"), new WebSocketClient.Handler() { +SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new SocketIOClient.Handler() { @Override public void onConnect() { Log.d(TAG, "Connected!"); From 4f49431e16ba37412f7764eacfe36b3b93c25156 Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koushd@gmail.com> Date: Fri, 4 May 2012 11:17:42 -0700 Subject: [PATCH 10/56] Rename send to emit, similar to how the node/javascript code names it. --- README.md | 2 +- src/com/codebutler/android_websockets/SocketIOClient.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 54b898c..dcc3ce9 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ arguments.put("first argument"); JSONObject second = new JSONObject(); second.put("dictionary", true); arguments.put(second) -client.send("hello", arguments); +client.emit("hello", arguments); client.disconnect(); ``` diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index a603907..af461fb 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -72,7 +72,7 @@ private static String readToEnd(InputStream input) throws IOException { android.os.Handler mSendHandler; Looper mSendLooper; - public void send(String name, JSONArray args) throws JSONException { + public void emit(String name, JSONArray args) throws JSONException { final JSONObject event = new JSONObject(); event.put("name", name); event.put("args", args); From 8bce20f2a9fc256c19d5332770dd3dd65ab44b67 Mon Sep 17 00:00:00 2001 From: Filip Zawada <me@filimanjaro.com> Date: Thu, 17 May 2012 20:56:11 +0200 Subject: [PATCH 11/56] Fixed bug with incorrect URL when base URI with trailing slash used. --- .../codebutler/android_websockets/SocketIOClient.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index af461fb..f4ff3f9 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -30,15 +30,15 @@ public static interface Handler { public void onError(Exception error); } - URI mURI; + String mURL; Handler mHandler; String mSession; int mHeartbeat; - int mClosingTimeout; WebSocketClient mClient; public SocketIOClient(URI uri, Handler handler) { - mURI = uri; + // remove trailing "/" from URI, in case user provided e.g. http://test.com/ + mURL = uri.toString().replaceAll("/$", "") + "/socket.io/1/"; mHandler = handler; } @@ -86,7 +86,7 @@ public void run() { } private void connectSession() throws URISyntaxException { - mClient = new WebSocketClient(new URI(mURI.toString() + "/socket.io/1/websocket/" + mSession), new WebSocketClient.Handler() { + mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Handler() { @Override public void onMessage(byte[] data) { cleanup(); @@ -195,7 +195,7 @@ public void connect() { return; new Thread() { public void run() { - HttpPost post = new HttpPost(mURI.toString() + "/socket.io/1/"); + HttpPost post = new HttpPost(mURL); try { String line = downloadUriAsString(post); String[] parts = line.split(":"); @@ -224,3 +224,4 @@ public void run() { }.start(); } } + From 71be505536e4286957d241c74d7eceeca5220211 Mon Sep 17 00:00:00 2001 From: Mike Stewart <mike.stewart@unb.ca> Date: Thu, 29 Nov 2012 14:04:44 -0400 Subject: [PATCH 12/56] Update README.md Missing semi-colon in example code. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dcc3ce9..7425c42 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ JSONArray arguments = new JSONArray(); arguments.put("first argument"); JSONObject second = new JSONObject(); second.put("dictionary", true); -arguments.put(second) +arguments.put(second); client.emit("hello", arguments); client.disconnect(); ``` From 80322b33031d80a78f80d6244af471eb1c186987 Mon Sep 17 00:00:00 2001 From: Brandon Kase <bkase@cmu.edu> Date: Wed, 2 Jan 2013 15:56:02 -0500 Subject: [PATCH 13/56] Fire onConnect event when connection established In the SocketIOClient, `onConnect()` never called `mHandler.onConnect()` --- src/com/codebutler/android_websockets/SocketIOClient.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index af461fb..cc335ef 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -169,6 +169,7 @@ public void run() { mClient.send("2:::"); } }, mHeartbeat); + mHandler.onConnect(); } }, null); mClient.connect(); From 6adfa2af5ff051575ec70fd2c4fea8bcf6597311 Mon Sep 17 00:00:00 2001 From: Brandon Kase <bkase@cmu.edu> Date: Wed, 2 Jan 2013 16:05:55 -0500 Subject: [PATCH 14/56] Fixed crash on a no-argument message If a message from the socket.io server is emitted with no arguments, it now is properly received --- src/com/codebutler/android_websockets/SocketIOClient.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index af461fb..615f70a 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -116,7 +116,12 @@ public void onMessage(String message) { final String dataString = parts[3]; JSONObject data = new JSONObject(dataString); String event = data.getString("name"); - JSONArray args = data.getJSONArray("args"); + JSONArray args; + try { + args = data.getJSONArray("args"); + } catch (JSONException e) { + args = new JSONArray(); + } if (!"".equals(messageId)) { mSendHandler.post(new Runnable() { @Override From b45e80c023064fde6ba57e95e53e6368ba519c3f Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koush@koushikdutta.com> Date: Thu, 3 Jan 2013 13:38:53 -0800 Subject: [PATCH 15/56] Update src/com/codebutler/android_websockets/HybiParser.java Fix logic error from the javascript port. --- src/com/codebutler/android_websockets/HybiParser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/codebutler/android_websockets/HybiParser.java b/src/com/codebutler/android_websockets/HybiParser.java index 44eb1c7..dcbcc87 100644 --- a/src/com/codebutler/android_websockets/HybiParser.java +++ b/src/com/codebutler/android_websockets/HybiParser.java @@ -149,7 +149,7 @@ private void parseOpcode(byte data) throws ProtocolError { throw new ProtocolError("Bad opcode"); } - if (FRAGMENTED_OPCODES.contains(mOpcode) && !mFinal) { + if (!FRAGMENTED_OPCODES.contains(mOpcode) && !mFinal) { throw new ProtocolError("Expected non-final packet"); } @@ -413,4 +413,4 @@ public byte[] readBytes(int length) throws IOException { return buffer; } } -} \ No newline at end of file +} From e33c05bbf7b84e38aed82306d44250caf44f3f62 Mon Sep 17 00:00:00 2001 From: Mike Stewart <mike.stewart@unb.ca> Date: Thu, 10 Jan 2013 02:45:12 -0400 Subject: [PATCH 16/56] Added heartbeat replies, fixed two bugs, and added logging. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Client will now send a heartbeat back to the server when it receives one, so the connection won't time out. Removed unreachable catch block in cleanup() method, and changed WebSocketClient.Handler() to a Listener() to match the recent change to to the WebSocketClient interface. Changed print statements to use Log, to be consistent with the rest of the package. --- .../android_websockets/SocketIOClient.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index 97ec0b4..45786e6 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -18,6 +18,7 @@ import android.net.http.AndroidHttpClient; import android.os.Looper; +import android.util.Log; public class SocketIOClient { public static interface Handler { @@ -30,6 +31,8 @@ public static interface Handler { public void onError(Exception error); } + private static final String TAG = "SocketIOClient"; + String mURL; Handler mHandler; String mSession; @@ -76,7 +79,7 @@ public void emit(String name, JSONArray args) throws JSONException { final JSONObject event = new JSONObject(); event.put("name", name); event.put("args", args); - System.out.println(event.toString()); + Log.d(TAG, "Emitting event: " + event.toString()); mSendHandler.post(new Runnable() { @Override public void run() { @@ -86,7 +89,7 @@ public void run() { } private void connectSession() throws URISyntaxException { - mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Handler() { + mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Listener() { @Override public void onMessage(byte[] data) { cleanup(); @@ -96,7 +99,7 @@ public void onMessage(byte[] data) { @Override public void onMessage(String message) { try { - System.out.println(message); + Log.d(TAG, "Message: " + message); String[] parts = message.split(":", 4); int code = Integer.parseInt(parts[0]); switch (code) { @@ -105,6 +108,7 @@ public void onMessage(String message) { break; case 2: // heartbeat + mClient.send("2::"); break; case 3: // message @@ -185,12 +189,9 @@ public void disconnect() throws IOException { } private void cleanup() { - try { - mClient.disconnect(); - mClient = null; - } - catch (IOException e) { - } + mClient.disconnect(); + mClient = null; + mSendLooper.quit(); mSendLooper = null; mSendHandler = null; From 86617e55550cb31ad354ff9a07dd8054a82e501d Mon Sep 17 00:00:00 2001 From: Mike Stewart <mike.stewart@unb.ca> Date: Tue, 5 Feb 2013 00:49:29 -0400 Subject: [PATCH 17/56] Create an SSL socket for https connections. --- src/com/codebutler/android_websockets/WebSocketClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java index 7e3343c..3b14a9f 100644 --- a/src/com/codebutler/android_websockets/WebSocketClient.java +++ b/src/com/codebutler/android_websockets/WebSocketClient.java @@ -69,7 +69,7 @@ public void connect() { @Override public void run() { try { - int port = (mURI.getPort() != -1) ? mURI.getPort() : (mURI.getScheme().equals("wss") ? 443 : 80); + int port = (mURI.getPort() != -1) ? mURI.getPort() : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 : 80); String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath(); if (!TextUtils.isEmpty(mURI.getQuery())) { @@ -79,7 +79,7 @@ public void run() { String originScheme = mURI.getScheme().equals("wss") ? "https" : "http"; URI origin = new URI(originScheme, "//" + mURI.getHost(), null); - SocketFactory factory = mURI.getScheme().equals("wss") ? getSSLSocketFactory() : SocketFactory.getDefault(); + SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault(); mSocket = factory.createSocket(mURI.getHost(), port); PrintWriter out = new PrintWriter(mSocket.getOutputStream()); From 4b2fa51cedb3c432cee85e5a3bab6e8ab9eba32f Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay.shenoy@sourcebits.com> Date: Fri, 1 Mar 2013 17:55:34 +0530 Subject: [PATCH 18/56] Added manifest target sdk --- AndroidManifest.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index eb6f10c..80e8295 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -4,4 +4,8 @@ package="com.codebutler.android_websockets" android:versionCode="1" android:versionName="0.01"> + + <uses-sdk + android:minSdkVersion="8" + android:targetSdkVersion="8" /> </manifest> From fbd83b55a06062f2a4bf246ed72aa092b036259f Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay.shenoy@sourcebits.com> Date: Fri, 1 Mar 2013 18:09:28 +0530 Subject: [PATCH 19/56] Added support for JSONMessage and Message to SocketIOClient --- .../android_websockets/SocketIOClient.java | 69 +++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index 45786e6..89a275f 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -28,6 +28,10 @@ public static interface Handler { public void onDisconnect(int code, String reason); + public void onJSONMessage(JSONObject jsonMessage); + + public void onMessage(String message); + public void onError(Exception error); } @@ -87,6 +91,27 @@ public void run() { } }); } + + public void emit(final String message) { + mSendHandler.post(new Runnable() { + + @Override + public void run() { + mClient.send(String.format("3:::%s", message)); + } + }); + } + + public void emit(final JSONObject jsonMessage) { + + mSendHandler.post(new Runnable() { + + @Override + public void run() { + mClient.send(String.format("4:::%s", jsonMessage.toString())); + } + }); + } private void connectSession() throws URISyntaxException { mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Listener() { @@ -110,11 +135,47 @@ public void onMessage(String message) { // heartbeat mClient.send("2::"); break; - case 3: + case 3: { // message - case 4: - // json message - throw new Exception("message type not supported"); + final String messageId = parts[1]; + final String dataString = parts[3]; + + if(!"".equals(messageId)) { + mSendHandler.post(new Runnable() { + + @Override + public void run() { + mClient.send(String.format("6:::%s", messageId)); + } + }); + } + mHandler.onMessage(dataString); + break; + } + case 4: { + //json message + final String messageId = parts[1]; + final String dataString = parts[3]; + + JSONObject jsonMessage = null; + + try { + jsonMessage = new JSONObject(dataString); + } catch(JSONException e) { + jsonMessage = new JSONObject(); + } + if(!"".equals(messageId)) { + mSendHandler.post(new Runnable() { + + @Override + public void run() { + mClient.send(String.format("6:::%s", messageId)); + } + }); + } + mHandler.onJSONMessage(jsonMessage); + break; + } case 5: { final String messageId = parts[1]; final String dataString = parts[3]; From acc35521562bb1b3c0d4e682fcca003d0fb05601 Mon Sep 17 00:00:00 2001 From: Vinay Shenoy <vinaysshenoy@gmail.com> Date: Sat, 2 Mar 2013 07:16:51 +0530 Subject: [PATCH 20/56] Renamed method onJSONMessage to onJSON --- src/com/codebutler/android_websockets/SocketIOClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index 89a275f..8e5ccf9 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -28,7 +28,7 @@ public static interface Handler { public void onDisconnect(int code, String reason); - public void onJSONMessage(JSONObject jsonMessage); + public void onJSON(JSONObject json); public void onMessage(String message); @@ -173,7 +173,7 @@ public void run() { } }); } - mHandler.onJSONMessage(jsonMessage); + mHandler.onJSON(jsonMessage); break; } case 5: { From f440715a972dd260ca5438f3e7e74ab5fcbc0375 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinaysshenoy@gmail.com> Date: Sat, 2 Mar 2013 09:48:02 +0530 Subject: [PATCH 21/56] Update README.md --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7425c42..72ce59a 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,19 @@ SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new public void on(String event, JSONArray arguments) { Log.d(TAG, String.format("Got event %s: %s", event, arguments.toString())); } + + @Override + public void onJSON(JSONObject json) { + try { + Log.d(TAG, String.format("Got JSON Object: %s", json.toString())); + } catch(JSONException e) { + } + } + + @Override + public void onMessage(String message) { + Log.d(TAG, String.format("Got message: %s", message)); + } @Override public void onDisconnect(int code, String reason) { @@ -82,12 +95,14 @@ SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new client.connect(); // Later… +client.emit("Message"); //Message JSONArray arguments = new JSONArray(); arguments.put("first argument"); JSONObject second = new JSONObject(); +client.emit(second); //JSON Message second.put("dictionary", true); arguments.put(second); -client.emit("hello", arguments); +client.emit("hello", arguments); //Event client.disconnect(); ``` From fc5d02338e63f6c977ec8dae8a019b272232e2f2 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinaysshenoy@gmail.com> Date: Sat, 2 Mar 2013 09:50:37 +0530 Subject: [PATCH 22/56] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72ce59a..0667347 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,8 @@ client.emit("Message"); //Message JSONArray arguments = new JSONArray(); arguments.put("first argument"); JSONObject second = new JSONObject(); -client.emit(second); //JSON Message second.put("dictionary", true); +client.emit(second); //JSON Message arguments.put(second); client.emit("hello", arguments); //Event client.disconnect(); From a6a3b99fb032bd2fc96c0d8d0275b4eed830acfd Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koushd@gmail.com> Date: Wed, 6 Mar 2013 07:34:32 -0800 Subject: [PATCH 23/56] update project properties and build xml --- .gitignore | 1 + build.xml | 13 +++++++++++-- proguard-project.txt | 20 ++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 proguard-project.txt diff --git a/.gitignore b/.gitignore index bd55530..926c55d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ bin gen .classpath .project +local.properties diff --git a/build.xml b/build.xml index 7db9217..876bc4b 100644 --- a/build.xml +++ b/build.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<project name="android_websockets" default="help"> +<project name="android-websockets" default="help"> <!-- The local.properties file is created and updated by the 'android' tool. It contains the path to the SDK. It should *NOT* be checked into @@ -28,6 +28,15 @@ --> <property file="ant.properties" /> + <!-- if sdk.dir was not set from one of the property file, then + get it from the ANDROID_HOME env var. + This must be done before we load project.properties since + the proguard config can use sdk.dir --> + <property environment="env" /> + <condition property="sdk.dir" value="${env.ANDROID_HOME}"> + <isset property="env.ANDROID_HOME" /> + </condition> + <!-- The project.properties file is created and updated by the 'android' tool, as well as ADT. @@ -41,7 +50,7 @@ <!-- quick check on sdk.dir --> <fail - message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var" + message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." unless="sdk.dir" /> diff --git a/proguard-project.txt b/proguard-project.txt new file mode 100644 index 0000000..f2fe155 --- /dev/null +++ b/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} From 3cbe3eefab54c70e6610bf4dd14fc74a8de10365 Mon Sep 17 00:00:00 2001 From: Mike Stewart <mike.stewart@unb.ca> Date: Sat, 9 Mar 2013 14:09:13 -0400 Subject: [PATCH 24/56] Fixed double firing of onConnect event in SocketIOClient handler. --- src/com/codebutler/android_websockets/SocketIOClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index 8e5ccf9..21e2593 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -129,7 +129,8 @@ public void onMessage(String message) { int code = Integer.parseInt(parts[0]); switch (code) { case 1: - onConnect(); + // connect + mHandler.onConnect(); break; case 2: // heartbeat @@ -239,7 +240,6 @@ public void run() { mClient.send("2:::"); } }, mHeartbeat); - mHandler.onConnect(); } }, null); mClient.connect(); From 1174d7e8751b7b572420b4fbd1ac4e54d1df3a03 Mon Sep 17 00:00:00 2001 From: Peter Foley <pefoley2@verizon.net> Date: Tue, 2 Apr 2013 10:12:11 -0400 Subject: [PATCH 25/56] add isConnected method I converted my application from AutobahnAndroid to android-websockets to take advantage of ssl support. android-websockets did not have a isConnected method, which is used in my application, so I implemented one. --- .../android_websockets/WebSocketClient.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java index 3b14a9f..82aadbe 100644 --- a/src/com/codebutler/android_websockets/WebSocketClient.java +++ b/src/com/codebutler/android_websockets/WebSocketClient.java @@ -36,6 +36,7 @@ public class WebSocketClient { private Handler mHandler; private List<BasicNameValuePair> mExtraHeaders; private HybiParser mParser; + private boolean mConnected; private final Object mSendLock = new Object(); @@ -47,8 +48,9 @@ public static void setTrustManagers(TrustManager[] tm) { public WebSocketClient(URI uri, Listener listener, List<BasicNameValuePair> extraHeaders) { mURI = uri; - mListener = listener; + mListener = listener; mExtraHeaders = extraHeaders; + mConnected = false; mParser = new HybiParser(this); mHandlerThread = new HandlerThread("websocket-thread"); @@ -119,17 +121,21 @@ public void run() { mListener.onConnect(); + mConnected = true; + // Now decode websocket frames. mParser.start(stream); } catch (EOFException ex) { Log.d(TAG, "WebSocket EOF!", ex); mListener.onDisconnect(0, "EOF"); + mConnected = false; } catch (SSLException ex) { // Connection reset by peer Log.d(TAG, "Websocket SSL error!", ex); mListener.onDisconnect(0, "SSL"); + mConnected = false; } catch (Exception ex) { mListener.onError(ex); @@ -147,6 +153,7 @@ public void run() { try { mSocket.close(); mSocket = null; + mConnected = false; } catch (IOException ex) { Log.d(TAG, "Error while disconnecting", ex); mListener.onError(ex); @@ -164,6 +171,10 @@ public void send(byte[] data) { sendFrame(mParser.frame(data)); } + public boolean isConnected() { + return mConnected; + } + private StatusLine parseStatusLine(String line) { if (TextUtils.isEmpty(line)) { return null; From dad5c6fb6e62bb50cfcf83dab2d055ac191eac0f Mon Sep 17 00:00:00 2001 From: orenda <oren.david@gmail.com> Date: Wed, 8 May 2013 15:00:13 +0300 Subject: [PATCH 26/56] Update SocketIOClient.java --- src/com/codebutler/android_websockets/SocketIOClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index 21e2593..a16464a 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -250,7 +250,8 @@ public void disconnect() throws IOException { } private void cleanup() { - mClient.disconnect(); + if (mClient != null) + mClient.disconnect(); mClient = null; mSendLooper.quit(); From c62edc2ad90be555db49249336c757215b84ac1d Mon Sep 17 00:00:00 2001 From: Chris Peterson <cpeterson@mozilla.com> Date: Thu, 9 May 2013 14:41:20 -0700 Subject: [PATCH 27/56] Fix syntax errors in README.md's example code --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0667347..64d852e 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,10 @@ The Socket.IO client was written by [Koushik Dutta](https://twitter.com/koush). ```java List<BasicNameValuePair> extraHeaders = Arrays.asList( - new BasicNameValuePair("Cookie", "session=abcd"); + new BasicNameValuePair("Cookie", "session=abcd") ); -WebSocketClient client = new WebSocketClient(URI.create("wss://irccloud.com"), new WebSocketClient.Handler() { +WebSocketClient client = new WebSocketClient(URI.create("wss://irccloud.com"), new WebSocketClient.Listener() { @Override public void onConnect() { Log.d(TAG, "Connected!"); @@ -32,7 +32,7 @@ WebSocketClient client = new WebSocketClient(URI.create("wss://irccloud.com"), n @Override public void onMessage(byte[] data) { - Log.d(TAG, String.format("Got binary message! %s", toHexString(data)); + Log.d(TAG, String.format("Got binary message! %s", toHexString(data))); } @Override From e1877d697ca25e15392b9012a907e0e96e6034a2 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Mon, 20 May 2013 22:02:27 +0530 Subject: [PATCH 28/56] Added support for Endpoints --- .gitignore | 1 + .../android_websockets/SocketIOClient.java | 117 ++++++++++++++---- 2 files changed, 93 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 926c55d..e66abab 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ gen .classpath .project local.properties +/.settings diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index 21e2593..58e2268 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -18,6 +18,7 @@ import android.net.http.AndroidHttpClient; import android.os.Looper; +import android.text.TextUtils; import android.util.Log; public class SocketIOClient { @@ -36,16 +37,28 @@ public static interface Handler { } private static final String TAG = "SocketIOClient"; - + String mURL; Handler mHandler; String mSession; int mHeartbeat; WebSocketClient mClient; + String mEndpoint; public SocketIOClient(URI uri, Handler handler) { - // remove trailing "/" from URI, in case user provided e.g. http://test.com/ - mURL = uri.toString().replaceAll("/$", "") + "/socket.io/1/"; + this(uri, handler, null); + } + + public SocketIOClient(URI uri, Handler handler, String namespace) { + mEndpoint = namespace; + + if (TextUtils.isEmpty(namespace)) { + mEndpoint = "socket.io"; + } + + // remove trailing "/" from URI, in case user provided e.g. + // http://test.com/ + mURL = uri.toString().replaceAll("/$", "") + "/" + mEndpoint + "/1/"; mHandler = handler; } @@ -54,8 +67,7 @@ private static String downloadUriAsString(final HttpUriRequest req) throws IOExc try { HttpResponse res = client.execute(req); return readToEnd(res.getEntity().getContent()); - } - finally { + } finally { client.close(); } } @@ -91,21 +103,21 @@ public void run() { } }); } - + public void emit(final String message) { mSendHandler.post(new Runnable() { - + @Override public void run() { mClient.send(String.format("3:::%s", message)); } }); } - + public void emit(final JSONObject jsonMessage) { - + mSendHandler.post(new Runnable() { - + @Override public void run() { mClient.send(String.format("4:::%s", jsonMessage.toString())); @@ -140,10 +152,10 @@ public void onMessage(String message) { // message final String messageId = parts[1]; final String dataString = parts[3]; - - if(!"".equals(messageId)) { + + if (!"".equals(messageId)) { mSendHandler.post(new Runnable() { - + @Override public void run() { mClient.send(String.format("6:::%s", messageId)); @@ -154,20 +166,20 @@ public void run() { break; } case 4: { - //json message + // json message final String messageId = parts[1]; final String dataString = parts[3]; - + JSONObject jsonMessage = null; - + try { jsonMessage = new JSONObject(dataString); - } catch(JSONException e) { + } catch (JSONException e) { jsonMessage = new JSONObject(); } - if(!"".equals(messageId)) { + if (!"".equals(messageId)) { mSendHandler.post(new Runnable() { - + @Override public void run() { mClient.send(String.format("6:::%s", messageId)); @@ -211,8 +223,7 @@ public void run() { default: throw new Exception("unknown code"); } - } - catch (Exception ex) { + } catch (Exception ex) { cleanup(); onError(ex); } @@ -252,7 +263,7 @@ public void disconnect() throws IOException { private void cleanup() { mClient.disconnect(); mClient = null; - + mSendLooper.quit(); mSendLooper = null; mSendHandler = null; @@ -284,12 +295,68 @@ public void run() { connectSession(); Looper.loop(); - } - catch (Exception e) { + } catch (Exception e) { mHandler.onError(e); } }; }.start(); } -} + /** + * Connect to an endpoint + */ + public void connectToEndpoint(final String endpoint) { + + if (mClient.isConnected() && !TextUtils.isEmpty(endpoint)) { + mEndpoint = endpoint; + mSendHandler.post(new Runnable() { + + @Override + public void run() { + mClient.send("1::" + endpoint); + } + }); + } + } + + /** + * Disconnect from an endpoint or socket + * + * @param endpoint + * {@code null} to disconnect the entire socket, otherwise the + * endpoint to disconnect from + */ + public void sendDisconnect(final String endpoint) { + + if (TextUtils.isEmpty(endpoint)) { + + mSendHandler.post(new Runnable() { + + @Override + public void run() { + mClient.send("0"); + } + }); + } + + else { + mSendHandler.post(new Runnable() { + + @Override + public void run() { + mClient.send("0::" + endpoint); + } + }); + } + } + + /** + * Get the current connected endpoint + * + * @return The current connected endpoint, "socket.io" if connected to the + * default endpoint + */ + public String getConnectedEndpoint() { + return mEndpoint; + } +} From b7e9b765c361f0b4a9a2cfab004576eb39b53930 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Mon, 20 May 2013 22:10:39 +0530 Subject: [PATCH 29/56] Added listener event for connecting to endpoints --- src/com/codebutler/android_websockets/SocketIOClient.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index 58e2268..00a08c2 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -25,6 +25,8 @@ public class SocketIOClient { public static interface Handler { public void onConnect(); + public void onConnectToEndpoint(String endpoint); + public void on(String event, JSONArray arguments); public void onDisconnect(int code, String reason); @@ -142,7 +144,11 @@ public void onMessage(String message) { switch (code) { case 1: // connect - mHandler.onConnect(); + if (!TextUtils.isEmpty(parts[2])) { + mHandler.onConnectToEndpoint(parts[2]); + } else { + mHandler.onConnect(); + } break; case 2: // heartbeat From 4c28f20bd101a8247fdddd566517d91846627a93 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Mon, 20 May 2013 23:30:54 +0530 Subject: [PATCH 30/56] Added support for Acknowledges --- .../android_websockets/SocketIOClient.java | 78 +++++++++++++++++-- 1 file changed, 70 insertions(+), 8 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index c07ee5a..1a861dd 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -8,6 +8,7 @@ import java.net.URISyntaxException; import java.util.Arrays; import java.util.HashSet; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; @@ -20,6 +21,7 @@ import android.os.Looper; import android.text.TextUtils; import android.util.Log; +import android.util.SparseArray; public class SocketIOClient { public static interface Handler { @@ -38,6 +40,10 @@ public static interface Handler { public void onError(Exception error); } + public static interface Acknowledge { + public void acknowledge(String[] args); + } + private static final String TAG = "SocketIOClient"; String mURL; @@ -46,6 +52,8 @@ public static interface Handler { int mHeartbeat; WebSocketClient mClient; String mEndpoint; + private AtomicInteger mMessageIdCounter; + private SparseArray<Acknowledge> mAcknowledges; public SocketIOClient(URI uri, Handler handler) { this(uri, handler, null); @@ -53,7 +61,8 @@ public SocketIOClient(URI uri, Handler handler) { public SocketIOClient(URI uri, Handler handler, String namespace) { mEndpoint = namespace; - + mAcknowledges = new SparseArray<SocketIOClient.Acknowledge>(); + mMessageIdCounter = new AtomicInteger(0); if (TextUtils.isEmpty(namespace)) { mEndpoint = "socket.io"; } @@ -74,6 +83,10 @@ private static String downloadUriAsString(final HttpUriRequest req) throws IOExc } } + private int getNextMessageId() { + return mMessageIdCounter.incrementAndGet(); + } + private static byte[] readToEndAsArray(InputStream input) throws IOException { DataInputStream dis = new DataInputStream(input); byte[] stuff = new byte[1024]; @@ -94,35 +107,60 @@ private static String readToEnd(InputStream input) throws IOException { Looper mSendLooper; public void emit(String name, JSONArray args) throws JSONException { + emit(name, args, null); + } + + public void emit(String name, JSONArray args, Acknowledge acknowledge) throws JSONException { final JSONObject event = new JSONObject(); event.put("name", name); event.put("args", args); - Log.d(TAG, "Emitting event: " + event.toString()); + + final int nextId = getNextMessageId(); + if (acknowledge != null) { + mAcknowledges.put(nextId, acknowledge); + } mSendHandler.post(new Runnable() { @Override public void run() { - mClient.send(String.format("5:::%s", event.toString())); + mClient.send(String.format("5:" + nextId + "+::%s", event.toString())); } }); } - public void emit(final String message) { + public void emit(final JSONObject jsonMessage) throws JSONException { + emit(jsonMessage, null); + } + + public void emit(final JSONObject jsonMessage, Acknowledge acknowledge) throws JSONException { + + final int nextId = getNextMessageId(); + if (acknowledge != null) { + mAcknowledges.put(nextId, acknowledge); + } mSendHandler.post(new Runnable() { @Override public void run() { - mClient.send(String.format("3:::%s", message)); + mClient.send(String.format("4:" + nextId + "+::%s", jsonMessage.toString())); } }); } - public void emit(final JSONObject jsonMessage) { + public void emit(final String message) { + emit(message, (Acknowledge) null); + } + public void emit(final String message, Acknowledge acknowledge) { + + final int nextId = getNextMessageId(); + if (acknowledge != null) { + mAcknowledges.put(nextId, acknowledge); + } mSendHandler.post(new Runnable() { @Override public void run() { - mClient.send(String.format("4:::%s", jsonMessage.toString())); + mClient.send(String.format("3:" + nextId + "+::%s", message)); } }); } @@ -219,6 +257,29 @@ public void run() { } case 6: // ACK + if (parts[3] != null && parts[3].contains("+")) { + String[] ackParts = parts[3].split("\\+"); + int ackId = Integer.valueOf(ackParts[0]); + + String ackArgs = ackParts[1]; + + int startIndex = ackArgs.indexOf('[') + 1; + + ackArgs = ackArgs.substring(startIndex, ackArgs.length() - 1); + + Acknowledge acknowledge = mAcknowledges.get(ackId); + + if (acknowledge != null) { + + String[] params = ackArgs.split(","); + for (int i = 0; i < params.length; i++) { + params[i] = params[i].replace("\"", ""); + } + acknowledge.acknowledge(params); + } + + mAcknowledges.remove(ackId); + } break; case 7: // error @@ -270,7 +331,8 @@ private void cleanup() { if (mClient != null) mClient.disconnect(); mClient = null; - + mMessageIdCounter.set(0); + mAcknowledges.clear(); mSendLooper.quit(); mSendLooper = null; mSendHandler = null; From 2f586e0d26702d9ef4e6ec2a2482a88ed6a16513 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinaysshenoy@gmail.com> Date: Mon, 20 May 2013 23:39:18 +0530 Subject: [PATCH 31/56] Update README.md --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 64d852e..d720fee 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,22 @@ client.emit(second); //JSON Message arguments.put(second); client.emit("hello", arguments); //Event client.disconnect(); + +/*How to use Acknowledges +*Call any of the emit() methods +*with the Acknowledge parameter. +* +*Each message must have a new +*Acknowledge object as they +*are matched against the Message Id. +*/ +client.emit("Message", new Acknowledge { + + @Override + public void acknowledge(String[] args) { + //Perform your acknowledge handling here + } +}); ``` From f7487f263630037949528d6424ec296b4e0f28ed Mon Sep 17 00:00:00 2001 From: Lior Sion <lior.sion@gmail.com> Date: Tue, 21 May 2013 16:31:19 +0300 Subject: [PATCH 32/56] add end pointed connected override to the handler the new endpoint connected callback is mandatory in the handler interface, without it the README example doesn't work --- .gitignore | 1 + README.md | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/.gitignore b/.gitignore index e66abab..a361a6e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ gen .project local.properties /.settings +project.properties diff --git a/README.md b/README.md index 64d852e..e384ddc 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,11 @@ WebSocketClient client = new WebSocketClient(URI.create("wss://irccloud.com"), n public void onError(Exception error) { Log.e(TAG, "Error!", error); } + + @Override + public void onConnectToEndpoint(String endpoint) { + Log.d(TAG, "Connected to endpoint") + } }, extraHeaders); client.connect(); @@ -90,6 +95,11 @@ SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new public void onError(Exception error) { Log.e(TAG, "Error!", error); } + + @Override + public void onConnectToEndpoint(String endpoint) { + Log.d(TAG, "Connected to endpoint") + } }); client.connect(); From 3a2f9d9e5841089c56f0a5508bcbe947b65677be Mon Sep 17 00:00:00 2001 From: Lior Sion <lior.sion@gmail.com> Date: Tue, 21 May 2013 19:19:33 +0300 Subject: [PATCH 33/56] only socketio client has endpoints, normal websocket doesn't --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index e384ddc..00957da 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,6 @@ WebSocketClient client = new WebSocketClient(URI.create("wss://irccloud.com"), n Log.e(TAG, "Error!", error); } - @Override - public void onConnectToEndpoint(String endpoint) { - Log.d(TAG, "Connected to endpoint") - } }, extraHeaders); client.connect(); From 604cfdd96433dda894b7506ab25de101711aac24 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinaysshenoy@gmail.com> Date: Wed, 22 May 2013 21:11:17 +0530 Subject: [PATCH 34/56] Update README.md The update for onConnectToEndpoint() got overwritten by my changes for Acknowledges. Adding the method back to the README. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index d720fee..0a17fb3 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,11 @@ SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new public void onError(Exception error) { Log.e(TAG, "Error!", error); } + + @Override + public void onConnectToEndpoint(String endpoint) { + Log.d(TAG, "Connected to endpoint") + } }); client.connect(); From 21ef8eeb111f1be4fece260f7ccb16808a4692ab Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Wed, 22 May 2013 22:35:09 +0530 Subject: [PATCH 35/56] Added support for Acknowledges --- README.md | 21 +++++ .../android_websockets/SocketIOClient.java | 78 +++++++++++++++++-- 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 64d852e..8c3c870 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,11 @@ SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new public void onError(Exception error) { Log.e(TAG, "Error!", error); } + + @Override + public void onConnectToEndpoint(String endpoint) { + Log.d(TAG, "Connected to:" + endpoint); + } }); client.connect(); @@ -104,6 +109,22 @@ client.emit(second); //JSON Message arguments.put(second); client.emit("hello", arguments); //Event client.disconnect(); + +/*How to use Acknowledges +*Call any of the emit() methods +*with the Acknowledge parameter. +* +*Each message must have a new +*Acknowledge object as they +*are matched against the Message Id. +*/ +client.emit("Message", new Acknowledge { + + @Override + public void acknowledge(String[] args) { + //Perform your acknowledge handling here + } +}); ``` diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index c07ee5a..1a861dd 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -8,6 +8,7 @@ import java.net.URISyntaxException; import java.util.Arrays; import java.util.HashSet; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; @@ -20,6 +21,7 @@ import android.os.Looper; import android.text.TextUtils; import android.util.Log; +import android.util.SparseArray; public class SocketIOClient { public static interface Handler { @@ -38,6 +40,10 @@ public static interface Handler { public void onError(Exception error); } + public static interface Acknowledge { + public void acknowledge(String[] args); + } + private static final String TAG = "SocketIOClient"; String mURL; @@ -46,6 +52,8 @@ public static interface Handler { int mHeartbeat; WebSocketClient mClient; String mEndpoint; + private AtomicInteger mMessageIdCounter; + private SparseArray<Acknowledge> mAcknowledges; public SocketIOClient(URI uri, Handler handler) { this(uri, handler, null); @@ -53,7 +61,8 @@ public SocketIOClient(URI uri, Handler handler) { public SocketIOClient(URI uri, Handler handler, String namespace) { mEndpoint = namespace; - + mAcknowledges = new SparseArray<SocketIOClient.Acknowledge>(); + mMessageIdCounter = new AtomicInteger(0); if (TextUtils.isEmpty(namespace)) { mEndpoint = "socket.io"; } @@ -74,6 +83,10 @@ private static String downloadUriAsString(final HttpUriRequest req) throws IOExc } } + private int getNextMessageId() { + return mMessageIdCounter.incrementAndGet(); + } + private static byte[] readToEndAsArray(InputStream input) throws IOException { DataInputStream dis = new DataInputStream(input); byte[] stuff = new byte[1024]; @@ -94,35 +107,60 @@ private static String readToEnd(InputStream input) throws IOException { Looper mSendLooper; public void emit(String name, JSONArray args) throws JSONException { + emit(name, args, null); + } + + public void emit(String name, JSONArray args, Acknowledge acknowledge) throws JSONException { final JSONObject event = new JSONObject(); event.put("name", name); event.put("args", args); - Log.d(TAG, "Emitting event: " + event.toString()); + + final int nextId = getNextMessageId(); + if (acknowledge != null) { + mAcknowledges.put(nextId, acknowledge); + } mSendHandler.post(new Runnable() { @Override public void run() { - mClient.send(String.format("5:::%s", event.toString())); + mClient.send(String.format("5:" + nextId + "+::%s", event.toString())); } }); } - public void emit(final String message) { + public void emit(final JSONObject jsonMessage) throws JSONException { + emit(jsonMessage, null); + } + + public void emit(final JSONObject jsonMessage, Acknowledge acknowledge) throws JSONException { + + final int nextId = getNextMessageId(); + if (acknowledge != null) { + mAcknowledges.put(nextId, acknowledge); + } mSendHandler.post(new Runnable() { @Override public void run() { - mClient.send(String.format("3:::%s", message)); + mClient.send(String.format("4:" + nextId + "+::%s", jsonMessage.toString())); } }); } - public void emit(final JSONObject jsonMessage) { + public void emit(final String message) { + emit(message, (Acknowledge) null); + } + public void emit(final String message, Acknowledge acknowledge) { + + final int nextId = getNextMessageId(); + if (acknowledge != null) { + mAcknowledges.put(nextId, acknowledge); + } mSendHandler.post(new Runnable() { @Override public void run() { - mClient.send(String.format("4:::%s", jsonMessage.toString())); + mClient.send(String.format("3:" + nextId + "+::%s", message)); } }); } @@ -219,6 +257,29 @@ public void run() { } case 6: // ACK + if (parts[3] != null && parts[3].contains("+")) { + String[] ackParts = parts[3].split("\\+"); + int ackId = Integer.valueOf(ackParts[0]); + + String ackArgs = ackParts[1]; + + int startIndex = ackArgs.indexOf('[') + 1; + + ackArgs = ackArgs.substring(startIndex, ackArgs.length() - 1); + + Acknowledge acknowledge = mAcknowledges.get(ackId); + + if (acknowledge != null) { + + String[] params = ackArgs.split(","); + for (int i = 0; i < params.length; i++) { + params[i] = params[i].replace("\"", ""); + } + acknowledge.acknowledge(params); + } + + mAcknowledges.remove(ackId); + } break; case 7: // error @@ -270,7 +331,8 @@ private void cleanup() { if (mClient != null) mClient.disconnect(); mClient = null; - + mMessageIdCounter.set(0); + mAcknowledges.clear(); mSendLooper.quit(); mSendLooper = null; mSendHandler = null; From 36cc20902b49d97c29b28e8db23ebe77c135bb5d Mon Sep 17 00:00:00 2001 From: Chris Peterson <cpeterson@mozilla.com> Date: Thu, 23 May 2013 12:52:27 -0700 Subject: [PATCH 36/56] Avoid mSocket NPE if WebSocketClient.disconnect() is called twice --- .../android_websockets/WebSocketClient.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java index 82aadbe..a75ec64 100644 --- a/src/com/codebutler/android_websockets/WebSocketClient.java +++ b/src/com/codebutler/android_websockets/WebSocketClient.java @@ -150,14 +150,16 @@ public void disconnect() { mHandler.post(new Runnable() { @Override public void run() { - try { - mSocket.close(); + if (mSocket != null) { + try { + mSocket.close(); + } catch (IOException ex) { + Log.d(TAG, "Error while disconnecting", ex); + mListener.onError(ex); + } mSocket = null; - mConnected = false; - } catch (IOException ex) { - Log.d(TAG, "Error while disconnecting", ex); - mListener.onError(ex); } + mConnected = false; } }); } From 57be2c6dd240a91b8f16db1e59a858536ccb4734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Mussini?= <maximomussini@gmail.com> Date: Sat, 1 Jun 2013 10:15:57 -0300 Subject: [PATCH 37/56] Avoid calling cleanup twice on error condition --- src/com/codebutler/android_websockets/SocketIOClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index 1a861dd..eeef8da 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -291,7 +291,6 @@ public void run() { throw new Exception("unknown code"); } } catch (Exception ex) { - cleanup(); onError(ex); } } From 61be4e5c55c5d2afb66ef119f6616fb07427c35d Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Thu, 6 Jun 2013 20:35:06 +0530 Subject: [PATCH 38/56] Fixed msgId in emit() messages being appended with '+' when Ackowledge not added --- .../android_websockets/SocketIOClient.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index eeef8da..c4e638c 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -110,7 +110,7 @@ public void emit(String name, JSONArray args) throws JSONException { emit(name, args, null); } - public void emit(String name, JSONArray args, Acknowledge acknowledge) throws JSONException { + public void emit(String name, JSONArray args, final Acknowledge acknowledge) throws JSONException { final JSONObject event = new JSONObject(); event.put("name", name); event.put("args", args); @@ -122,7 +122,7 @@ public void emit(String name, JSONArray args, Acknowledge acknowledge) throws JS mSendHandler.post(new Runnable() { @Override public void run() { - mClient.send(String.format("5:" + nextId + "+::%s", event.toString())); + mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") +"::%s", event.toString())); } }); } @@ -131,7 +131,7 @@ public void emit(final JSONObject jsonMessage) throws JSONException { emit(jsonMessage, null); } - public void emit(final JSONObject jsonMessage, Acknowledge acknowledge) throws JSONException { + public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) throws JSONException { final int nextId = getNextMessageId(); if (acknowledge != null) { @@ -141,7 +141,7 @@ public void emit(final JSONObject jsonMessage, Acknowledge acknowledge) throws J @Override public void run() { - mClient.send(String.format("4:" + nextId + "+::%s", jsonMessage.toString())); + mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + "::%s", jsonMessage.toString())); } }); } @@ -150,7 +150,7 @@ public void emit(final String message) { emit(message, (Acknowledge) null); } - public void emit(final String message, Acknowledge acknowledge) { + public void emit(final String message, final Acknowledge acknowledge) { final int nextId = getNextMessageId(); if (acknowledge != null) { @@ -160,7 +160,7 @@ public void emit(final String message, Acknowledge acknowledge) { @Override public void run() { - mClient.send(String.format("3:" + nextId + "+::%s", message)); + mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") +"::%s", message)); } }); } From 7583bf9c9162ae3b5a118c058bc0111e4def0a00 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Sat, 15 Jun 2013 11:46:08 +0530 Subject: [PATCH 39/56] Implemented auto-reconnect with exponential backoff for SocketIOClient --- .../android_websockets/SocketIOClient.java | 342 ++++++++++-------- 1 file changed, 195 insertions(+), 147 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index c4e638c..10ad9b9 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -24,6 +24,12 @@ import android.util.SparseArray; public class SocketIOClient { + + private static final String TAG = "SocketIOClient"; + + private static final int CONNECT_MULTIPLIER = 2; + private static final int MAX_CONNECT_POWER = 10; + public static interface Handler { public void onConnect(); @@ -44,22 +50,27 @@ public static interface Acknowledge { public void acknowledge(String[] args); } - private static final String TAG = "SocketIOClient"; - - String mURL; - Handler mHandler; - String mSession; - int mHeartbeat; - WebSocketClient mClient; - String mEndpoint; + private String mURL; + private Handler mHandler; + private String mSession; + private int mHeartbeat; + private WebSocketClient mClient; + private String mEndpoint; private AtomicInteger mMessageIdCounter; private SparseArray<Acknowledge> mAcknowledges; + private boolean mReconnectOnError; + private int mConnectPower; public SocketIOClient(URI uri, Handler handler) { - this(uri, handler, null); + this(uri, handler, null, false); + } + + public SocketIOClient(URI uri, Handler handler, boolean reconnectOnError) { + this(uri, handler, null, reconnectOnError); } - public SocketIOClient(URI uri, Handler handler, String namespace) { + public SocketIOClient(URI uri, Handler handler, String namespace, + boolean reconnectOnError) { mEndpoint = namespace; mAcknowledges = new SparseArray<SocketIOClient.Acknowledge>(); mMessageIdCounter = new AtomicInteger(0); @@ -71,10 +82,13 @@ public SocketIOClient(URI uri, Handler handler, String namespace) { // http://test.com/ mURL = uri.toString().replaceAll("/$", "") + "/" + mEndpoint + "/1/"; mHandler = handler; + mConnectPower = 1; + mReconnectOnError = reconnectOnError; } private static String downloadUriAsString(final HttpUriRequest req) throws IOException { - AndroidHttpClient client = AndroidHttpClient.newInstance("android-websockets"); + AndroidHttpClient client = AndroidHttpClient + .newInstance("android-websockets"); try { HttpResponse res = client.execute(req); return readToEnd(res.getEntity().getContent()); @@ -122,7 +136,9 @@ public void emit(String name, JSONArray args, final Acknowledge acknowledge) thr mSendHandler.post(new Runnable() { @Override public void run() { - mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") +"::%s", event.toString())); + mClient.send(String.format("5:" + nextId + + (acknowledge == null ? "" : "+") + "::%s", + event.toString())); } }); } @@ -141,7 +157,9 @@ public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) th @Override public void run() { - mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + "::%s", jsonMessage.toString())); + mClient.send(String.format("4:" + nextId + + (acknowledge == null ? "" : "+") + "::%s", + jsonMessage.toString())); } }); } @@ -160,168 +178,197 @@ public void emit(final String message, final Acknowledge acknowledge) { @Override public void run() { - mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") +"::%s", message)); + mClient.send(String.format("3:" + nextId + + (acknowledge == null ? "" : "+") + "::%s", message)); } }); } private void connectSession() throws URISyntaxException { - mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Listener() { - @Override - public void onMessage(byte[] data) { - cleanup(); - mHandler.onError(new Exception("Unexpected binary data")); - } - - @Override - public void onMessage(String message) { - try { - Log.d(TAG, "Message: " + message); - String[] parts = message.split(":", 4); - int code = Integer.parseInt(parts[0]); - switch (code) { - case 1: - // connect - if (!TextUtils.isEmpty(parts[2])) { - mHandler.onConnectToEndpoint(parts[2]); - } else { - mHandler.onConnect(); - } - break; - case 2: - // heartbeat - mClient.send("2::"); - break; - case 3: { - // message - final String messageId = parts[1]; - final String dataString = parts[3]; - - if (!"".equals(messageId)) { - mSendHandler.post(new Runnable() { - - @Override - public void run() { - mClient.send(String.format("6:::%s", messageId)); - } - }); - } - mHandler.onMessage(dataString); - break; + mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), + new WebSocketClient.Listener() { + @Override + public void onMessage(byte[] data) { + cleanup(); + mHandler + .onError(new Exception("Unexpected binary data")); } - case 4: { - // json message - final String messageId = parts[1]; - final String dataString = parts[3]; - - JSONObject jsonMessage = null; + @Override + public void onMessage(String message) { try { - jsonMessage = new JSONObject(dataString); - } catch (JSONException e) { - jsonMessage = new JSONObject(); - } - if (!"".equals(messageId)) { - mSendHandler.post(new Runnable() { + Log.d(TAG, "Message: " + message); + String[] parts = message.split(":", 4); + int code = Integer.parseInt(parts[0]); + switch (code) { + case 1: + // connect + if (!TextUtils.isEmpty(parts[2])) { + mHandler.onConnectToEndpoint(parts[2]); + } else { + mHandler.onConnect(); + } + break; + case 2: + // heartbeat + mClient.send("2::"); + break; + case 3: { + // message + final String messageId = parts[1]; + final String dataString = parts[3]; + + if (!"".equals(messageId)) { + mSendHandler.post(new Runnable() { + + @Override + public void run() { + mClient.send(String.format( + "6:::%s", messageId)); + } + }); + } + mHandler.onMessage(dataString); + break; + } + case 4: { + // json message + final String messageId = parts[1]; + final String dataString = parts[3]; - @Override - public void run() { - mClient.send(String.format("6:::%s", messageId)); + JSONObject jsonMessage = null; + + try { + jsonMessage = new JSONObject(dataString); + } catch (JSONException e) { + jsonMessage = new JSONObject(); } - }); - } - mHandler.onJSON(jsonMessage); - break; - } - case 5: { - final String messageId = parts[1]; - final String dataString = parts[3]; - JSONObject data = new JSONObject(dataString); - String event = data.getString("name"); - JSONArray args; - try { - args = data.getJSONArray("args"); - } catch (JSONException e) { - args = new JSONArray(); - } - if (!"".equals(messageId)) { - mSendHandler.post(new Runnable() { - @Override - public void run() { - mClient.send(String.format("6:::%s", messageId)); + if (!"".equals(messageId)) { + mSendHandler.post(new Runnable() { + + @Override + public void run() { + mClient.send(String.format( + "6:::%s", messageId)); + } + }); } - }); - } - mHandler.on(event, args); - break; - } - case 6: - // ACK - if (parts[3] != null && parts[3].contains("+")) { - String[] ackParts = parts[3].split("\\+"); - int ackId = Integer.valueOf(ackParts[0]); + mHandler.onJSON(jsonMessage); + break; + } + case 5: { + final String messageId = parts[1]; + final String dataString = parts[3]; + JSONObject data = new JSONObject(dataString); + String event = data.getString("name"); + JSONArray args; + try { + args = data.getJSONArray("args"); + } catch (JSONException e) { + args = new JSONArray(); + } + if (!"".equals(messageId)) { + mSendHandler.post(new Runnable() { + @Override + public void run() { + mClient.send(String.format( + "6:::%s", messageId)); + } + }); + } + mHandler.on(event, args); + break; + } + case 6: + // ACK + if (parts[3] != null && parts[3].contains("+")) { + String[] ackParts = parts[3].split("\\+"); + int ackId = Integer.valueOf(ackParts[0]); - String ackArgs = ackParts[1]; + String ackArgs = ackParts[1]; - int startIndex = ackArgs.indexOf('[') + 1; + int startIndex = ackArgs.indexOf('[') + 1; - ackArgs = ackArgs.substring(startIndex, ackArgs.length() - 1); + ackArgs = ackArgs.substring(startIndex, + ackArgs.length() - 1); - Acknowledge acknowledge = mAcknowledges.get(ackId); + Acknowledge acknowledge = mAcknowledges + .get(ackId); - if (acknowledge != null) { + if (acknowledge != null) { - String[] params = ackArgs.split(","); - for (int i = 0; i < params.length; i++) { - params[i] = params[i].replace("\"", ""); + String[] params = ackArgs.split(","); + for (int i = 0; i < params.length; i++) { + params[i] = params[i].replace("\"", + ""); + } + acknowledge.acknowledge(params); + } + + mAcknowledges.remove(ackId); } - acknowledge.acknowledge(params); + break; + case 7: + // error + throw new Exception(message); + case 8: + // noop + break; + default: + throw new Exception("unknown code"); } - - mAcknowledges.remove(ackId); + } catch (Exception ex) { + onError(ex); } - break; - case 7: - // error - throw new Exception(message); - case 8: - // noop - break; - default: - throw new Exception("unknown code"); } - } catch (Exception ex) { - onError(ex); - } - } - @Override - public void onError(Exception error) { - cleanup(); - mHandler.onError(error); - } + @Override + public void onError(Exception error) { + cleanup(); + mHandler.onError(error); - @Override - public void onDisconnect(int code, String reason) { - cleanup(); - // attempt reconnect with same session? - mHandler.onDisconnect(code, reason); - } + if (mReconnectOnError) { + mSendHandler.postDelayed(new Runnable() { + + @Override + public void run() { + + //Doing this check here because the client can be forcibly connected before the Runnable runs + if (!mClient.isConnected()) { + connect(); + } + + } + }, getDelayInMillis()); + } + } - @Override - public void onConnect() { - mSendHandler.postDelayed(new Runnable() { @Override - public void run() { - mSendHandler.postDelayed(this, mHeartbeat); - mClient.send("2:::"); + public void onDisconnect(int code, String reason) { + cleanup(); + // attempt reconnect with same session? + mHandler.onDisconnect(code, reason); } - }, mHeartbeat); - } - }, null); + + @Override + public void onConnect() { + mConnectPower = 1; + mSendHandler.postDelayed(new Runnable() { + @Override + public void run() { + mSendHandler.postDelayed(this, mHeartbeat); + mClient.send("2:::"); + } + }, mHeartbeat); + } + }, null); mClient.connect(); } + private long getDelayInMillis() { + return (long) (Math.pow(CONNECT_MULTIPLIER, (mConnectPower == MAX_CONNECT_POWER) ? MAX_CONNECT_POWER : mConnectPower++) * 1000); + } + public void disconnect() throws IOException { cleanup(); } @@ -352,7 +399,8 @@ public void run() { mHeartbeat = Integer.parseInt(heartbeat) / 2 * 1000; String transportsLine = parts[3]; String[] transports = transportsLine.split(","); - HashSet<String> set = new HashSet<String>(Arrays.asList(transports)); + HashSet<String> set = new HashSet<String>( + Arrays.asList(transports)); if (!set.contains("websocket")) throw new Exception("websocket not supported"); From fb3865c87215f4769700bfed21ae167d4a4c0201 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Sat, 15 Jun 2013 11:54:18 +0530 Subject: [PATCH 40/56] Changed cleanup code because we can reconnect on error --- .../android_websockets/SocketIOClient.java | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index 10ad9b9..d205683 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -324,16 +324,28 @@ public void run() { @Override public void onError(Exception error) { - cleanup(); + + // Removed call to cleanup because now we can + // reconnect automatically so we need the mSendHandler + // instance mHandler.onError(error); if (mReconnectOnError) { + + if (mClient != null) + mClient.disconnect(); + mClient = null; + mMessageIdCounter.set(0); + mAcknowledges.clear(); + mSendHandler.postDelayed(new Runnable() { @Override public void run() { - - //Doing this check here because the client can be forcibly connected before the Runnable runs + + // Doing this check here because the client + // can be forcibly connected before the + // Runnable runs if (!mClient.isConnected()) { connect(); } @@ -341,6 +353,10 @@ public void run() { } }, getDelayInMillis()); } + + else { + cleanup(); + } } @Override @@ -366,7 +382,9 @@ public void run() { } private long getDelayInMillis() { - return (long) (Math.pow(CONNECT_MULTIPLIER, (mConnectPower == MAX_CONNECT_POWER) ? MAX_CONNECT_POWER : mConnectPower++) * 1000); + return (long) (Math.pow(CONNECT_MULTIPLIER, + (mConnectPower == MAX_CONNECT_POWER) ? MAX_CONNECT_POWER + : mConnectPower++) * 1000); } public void disconnect() throws IOException { From c3d08a1ffa464ee6c90513c25d57065c1fbc782d Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Sat, 15 Jun 2013 11:56:57 +0530 Subject: [PATCH 41/56] Fixed formatting --- .../android_websockets/SocketIOClient.java | 343 +++++++++--------- 1 file changed, 165 insertions(+), 178 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index d205683..0f3557c 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -87,8 +87,7 @@ public SocketIOClient(URI uri, Handler handler, String namespace, } private static String downloadUriAsString(final HttpUriRequest req) throws IOException { - AndroidHttpClient client = AndroidHttpClient - .newInstance("android-websockets"); + AndroidHttpClient client = AndroidHttpClient.newInstance("android-websockets"); try { HttpResponse res = client.execute(req); return readToEnd(res.getEntity().getContent()); @@ -137,8 +136,7 @@ public void emit(String name, JSONArray args, final Acknowledge acknowledge) thr @Override public void run() { mClient.send(String.format("5:" + nextId - + (acknowledge == null ? "" : "+") + "::%s", - event.toString())); + + (acknowledge == null ? "" : "+") + "::%s", event.toString())); } }); } @@ -158,8 +156,7 @@ public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) th @Override public void run() { mClient.send(String.format("4:" + nextId - + (acknowledge == null ? "" : "+") + "::%s", - jsonMessage.toString())); + + (acknowledge == null ? "" : "+") + "::%s", jsonMessage.toString())); } }); } @@ -185,206 +182,197 @@ public void run() { } private void connectSession() throws URISyntaxException { - mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), - new WebSocketClient.Listener() { - @Override - public void onMessage(byte[] data) { - cleanup(); - mHandler - .onError(new Exception("Unexpected binary data")); - } + mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Listener() { + @Override + public void onMessage(byte[] data) { + cleanup(); + mHandler.onError(new Exception("Unexpected binary data")); + } - @Override - public void onMessage(String message) { - try { - Log.d(TAG, "Message: " + message); - String[] parts = message.split(":", 4); - int code = Integer.parseInt(parts[0]); - switch (code) { - case 1: - // connect - if (!TextUtils.isEmpty(parts[2])) { - mHandler.onConnectToEndpoint(parts[2]); - } else { - mHandler.onConnect(); - } - break; - case 2: - // heartbeat - mClient.send("2::"); - break; - case 3: { - // message - final String messageId = parts[1]; - final String dataString = parts[3]; - - if (!"".equals(messageId)) { - mSendHandler.post(new Runnable() { - - @Override - public void run() { - mClient.send(String.format( - "6:::%s", messageId)); - } - }); + @Override + public void onMessage(String message) { + try { + Log.d(TAG, "Message: " + message); + String[] parts = message.split(":", 4); + int code = Integer.parseInt(parts[0]); + switch (code) { + case 1: + // connect + if (!TextUtils.isEmpty(parts[2])) { + mHandler.onConnectToEndpoint(parts[2]); + } else { + mHandler.onConnect(); + } + break; + case 2: + // heartbeat + mClient.send("2::"); + break; + case 3: { + // message + final String messageId = parts[1]; + final String dataString = parts[3]; + + if (!"".equals(messageId)) { + mSendHandler.post(new Runnable() { + + @Override + public void run() { + mClient.send(String.format("6:::%s", messageId)); } - mHandler.onMessage(dataString); - break; - } - case 4: { - // json message - final String messageId = parts[1]; - final String dataString = parts[3]; + }); + } + mHandler.onMessage(dataString); + break; + } + case 4: { + // json message + final String messageId = parts[1]; + final String dataString = parts[3]; - JSONObject jsonMessage = null; + JSONObject jsonMessage = null; - try { - jsonMessage = new JSONObject(dataString); - } catch (JSONException e) { - jsonMessage = new JSONObject(); - } - if (!"".equals(messageId)) { - mSendHandler.post(new Runnable() { - - @Override - public void run() { - mClient.send(String.format( - "6:::%s", messageId)); - } - }); - } - mHandler.onJSON(jsonMessage); - break; - } - case 5: { - final String messageId = parts[1]; - final String dataString = parts[3]; - JSONObject data = new JSONObject(dataString); - String event = data.getString("name"); - JSONArray args; - try { - args = data.getJSONArray("args"); - } catch (JSONException e) { - args = new JSONArray(); + try { + jsonMessage = new JSONObject(dataString); + } catch (JSONException e) { + jsonMessage = new JSONObject(); + } + if (!"".equals(messageId)) { + mSendHandler.post(new Runnable() { + + @Override + public void run() { + mClient.send(String.format("6:::%s", messageId)); } - if (!"".equals(messageId)) { - mSendHandler.post(new Runnable() { - @Override - public void run() { - mClient.send(String.format( - "6:::%s", messageId)); - } - }); + }); + } + mHandler.onJSON(jsonMessage); + break; + } + case 5: { + final String messageId = parts[1]; + final String dataString = parts[3]; + JSONObject data = new JSONObject(dataString); + String event = data.getString("name"); + JSONArray args; + try { + args = data.getJSONArray("args"); + } catch (JSONException e) { + args = new JSONArray(); + } + if (!"".equals(messageId)) { + mSendHandler.post(new Runnable() { + @Override + public void run() { + mClient.send(String.format("6:::%s", messageId)); } - mHandler.on(event, args); - break; - } - case 6: - // ACK - if (parts[3] != null && parts[3].contains("+")) { - String[] ackParts = parts[3].split("\\+"); - int ackId = Integer.valueOf(ackParts[0]); - - String ackArgs = ackParts[1]; + }); + } + mHandler.on(event, args); + break; + } + case 6: + // ACK + if (parts[3] != null && parts[3].contains("+")) { + String[] ackParts = parts[3].split("\\+"); + int ackId = Integer.valueOf(ackParts[0]); - int startIndex = ackArgs.indexOf('[') + 1; + String ackArgs = ackParts[1]; - ackArgs = ackArgs.substring(startIndex, - ackArgs.length() - 1); + int startIndex = ackArgs.indexOf('[') + 1; - Acknowledge acknowledge = mAcknowledges - .get(ackId); + ackArgs = ackArgs.substring(startIndex, ackArgs.length() - 1); - if (acknowledge != null) { + Acknowledge acknowledge = mAcknowledges.get(ackId); - String[] params = ackArgs.split(","); - for (int i = 0; i < params.length; i++) { - params[i] = params[i].replace("\"", - ""); - } - acknowledge.acknowledge(params); - } + if (acknowledge != null) { - mAcknowledges.remove(ackId); + String[] params = ackArgs.split(","); + for (int i = 0; i < params.length; i++) { + params[i] = params[i].replace("\"", ""); } - break; - case 7: - // error - throw new Exception(message); - case 8: - // noop - break; - default: - throw new Exception("unknown code"); + acknowledge.acknowledge(params); } - } catch (Exception ex) { - onError(ex); + + mAcknowledges.remove(ackId); } + break; + case 7: + // error + throw new Exception(message); + case 8: + // noop + break; + default: + throw new Exception("unknown code"); } + } catch (Exception ex) { + onError(ex); + } + } - @Override - public void onError(Exception error) { - - // Removed call to cleanup because now we can - // reconnect automatically so we need the mSendHandler - // instance - mHandler.onError(error); - - if (mReconnectOnError) { - - if (mClient != null) - mClient.disconnect(); - mClient = null; - mMessageIdCounter.set(0); - mAcknowledges.clear(); - - mSendHandler.postDelayed(new Runnable() { + @Override + public void onError(Exception error) { - @Override - public void run() { + // Removed call to cleanup because now we can + // reconnect automatically so we need the mSendHandler + // instance + mHandler.onError(error); - // Doing this check here because the client - // can be forcibly connected before the - // Runnable runs - if (!mClient.isConnected()) { - connect(); - } + if (mReconnectOnError) { - } - }, getDelayInMillis()); - } + if (mClient != null) + mClient.disconnect(); + mClient = null; + mMessageIdCounter.set(0); + mAcknowledges.clear(); + + mSendHandler.postDelayed(new Runnable() { + + @Override + public void run() { + + // Doing this check here because the client + // can be forcibly connected before the + // Runnable runs + if (!mClient.isConnected()) { + connect(); + } - else { - cleanup(); } - } + }, getDelayInMillis()); + } - @Override - public void onDisconnect(int code, String reason) { - cleanup(); - // attempt reconnect with same session? - mHandler.onDisconnect(code, reason); - } + else { + cleanup(); + } + } + + @Override + public void onDisconnect(int code, String reason) { + cleanup(); + // attempt reconnect with same session? + mHandler.onDisconnect(code, reason); + } + @Override + public void onConnect() { + mConnectPower = 1; + mSendHandler.postDelayed(new Runnable() { @Override - public void onConnect() { - mConnectPower = 1; - mSendHandler.postDelayed(new Runnable() { - @Override - public void run() { - mSendHandler.postDelayed(this, mHeartbeat); - mClient.send("2:::"); - } - }, mHeartbeat); + public void run() { + mSendHandler.postDelayed(this, mHeartbeat); + mClient.send("2:::"); } - }, null); + }, mHeartbeat); + } + }, null); mClient.connect(); } private long getDelayInMillis() { - return (long) (Math.pow(CONNECT_MULTIPLIER, - (mConnectPower == MAX_CONNECT_POWER) ? MAX_CONNECT_POWER - : mConnectPower++) * 1000); + return (long) (Math.pow(CONNECT_MULTIPLIER, (mConnectPower == MAX_CONNECT_POWER) ? MAX_CONNECT_POWER + : mConnectPower++) * 1000); } public void disconnect() throws IOException { @@ -417,8 +405,7 @@ public void run() { mHeartbeat = Integer.parseInt(heartbeat) / 2 * 1000; String transportsLine = parts[3]; String[] transports = transportsLine.split(","); - HashSet<String> set = new HashSet<String>( - Arrays.asList(transports)); + HashSet<String> set = new HashSet<String>(Arrays.asList(transports)); if (!set.contains("websocket")) throw new Exception("websocket not supported"); From 17dbf039cbf4575ecef151052815e65da187db37 Mon Sep 17 00:00:00 2001 From: Nicolas <nicolas.barranger@zi-apps.fr> Date: Mon, 1 Jul 2013 16:17:01 +0200 Subject: [PATCH 42/56] Add support of message endPoint for types : message, message JSON and event --- src/com/codebutler/android_websockets/SocketIOClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index c4e638c..482bc3b 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -122,7 +122,7 @@ public void emit(String name, JSONArray args, final Acknowledge acknowledge) thr mSendHandler.post(new Runnable() { @Override public void run() { - mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") +"::%s", event.toString())); + mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") +":%s:%s", (mEndpoint == null ? "" : mEndpoint), event.toString())); } }); } @@ -141,7 +141,7 @@ public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) th @Override public void run() { - mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + "::%s", jsonMessage.toString())); + mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (mEndpoint == null ? "" : mEndpoint), jsonMessage.toString())); } }); } @@ -160,7 +160,7 @@ public void emit(final String message, final Acknowledge acknowledge) { @Override public void run() { - mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") +"::%s", message)); + mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") +":%s:%s", (mEndpoint == null ? "" : mEndpoint), message)); } }); } From 386dfed30ab10a47948b3265291f8bc8304d29a7 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Fri, 5 Jul 2013 21:22:47 +0530 Subject: [PATCH 43/56] Changed endpoints to be optional when emitting messages --- .../android_websockets/SocketIOClient.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java index 102bb67..f3de7a3 100644 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ b/src/com/codebutler/android_websockets/SocketIOClient.java @@ -124,6 +124,12 @@ public void emit(String name, JSONArray args) throws JSONException { } public void emit(String name, JSONArray args, final Acknowledge acknowledge) throws JSONException { + + emit(name, args, acknowledge, null); + } + + public void emit(String name, JSONArray args, final Acknowledge acknowledge, final String endpoint) throws JSONException { + final JSONObject event = new JSONObject(); event.put("name", name); event.put("args", args); @@ -135,7 +141,7 @@ public void emit(String name, JSONArray args, final Acknowledge acknowledge) thr mSendHandler.post(new Runnable() { @Override public void run() { - mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") +":%s:%s", (mEndpoint == null ? "" : mEndpoint), event.toString())); + mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (endpoint == null ? "" : endpoint), event.toString())); } }); } @@ -146,6 +152,11 @@ public void emit(final JSONObject jsonMessage) throws JSONException { public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) throws JSONException { + emit(jsonMessage, acknowledge, null); + } + + public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge, final String endpoint) throws JSONException { + final int nextId = getNextMessageId(); if (acknowledge != null) { mAcknowledges.put(nextId, acknowledge); @@ -154,7 +165,7 @@ public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) th @Override public void run() { - mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (mEndpoint == null ? "" : mEndpoint), jsonMessage.toString())); + mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (endpoint == null ? "" : endpoint), jsonMessage.toString())); } }); } @@ -165,6 +176,11 @@ public void emit(final String message) { public void emit(final String message, final Acknowledge acknowledge) { + emit(message, acknowledge, null); + } + + public void emit(final String message, final Acknowledge acknowledge, final String endpoint) { + final int nextId = getNextMessageId(); if (acknowledge != null) { mAcknowledges.put(nextId, acknowledge); @@ -173,7 +189,7 @@ public void emit(final String message, final Acknowledge acknowledge) { @Override public void run() { - mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") +":%s:%s", (mEndpoint == null ? "" : mEndpoint), message)); + mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (endpoint == null ? "" : endpoint), message)); } }); } From ad5a9f315695e48db27591f563957c3674357a41 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Tue, 9 Jul 2013 23:26:45 +0530 Subject: [PATCH 44/56] Ported AndroidAsync SocketIO framework --- .../android_websockets/SocketIOClient.java | 496 ------------------ .../async/http/socketio/Acknowledge.java | 7 + .../async/http/socketio/ConnectCallback.java | 5 + .../http/socketio/DisconnectCallback.java | 8 + .../async/http/socketio/ErrorCallback.java | 8 + .../async/http/socketio/EventCallback.java | 7 + .../async/http/socketio/EventEmitter.java | 54 ++ .../async/http/socketio/JSONCallback.java | 8 + .../http/socketio/ReconnectCallback.java | 5 + .../async/http/socketio/SocketIOClient.java | 179 +++++++ .../http/socketio/SocketIOConnection.java | 396 ++++++++++++++ .../async/http/socketio/StringCallback.java | 5 + src/com/koushikdutta/async/util/HashList.java | 26 + .../koushikdutta/http/AsyncHttpClient.java | 126 +++++ .../http}/HybiParser.java | 36 +- .../http/WebSocket.java} | 166 ++++-- 16 files changed, 971 insertions(+), 561 deletions(-) delete mode 100644 src/com/codebutler/android_websockets/SocketIOClient.java create mode 100644 src/com/koushikdutta/async/http/socketio/Acknowledge.java create mode 100644 src/com/koushikdutta/async/http/socketio/ConnectCallback.java create mode 100644 src/com/koushikdutta/async/http/socketio/DisconnectCallback.java create mode 100644 src/com/koushikdutta/async/http/socketio/ErrorCallback.java create mode 100644 src/com/koushikdutta/async/http/socketio/EventCallback.java create mode 100644 src/com/koushikdutta/async/http/socketio/EventEmitter.java create mode 100644 src/com/koushikdutta/async/http/socketio/JSONCallback.java create mode 100644 src/com/koushikdutta/async/http/socketio/ReconnectCallback.java create mode 100644 src/com/koushikdutta/async/http/socketio/SocketIOClient.java create mode 100644 src/com/koushikdutta/async/http/socketio/SocketIOConnection.java create mode 100644 src/com/koushikdutta/async/http/socketio/StringCallback.java create mode 100644 src/com/koushikdutta/async/util/HashList.java create mode 100644 src/com/koushikdutta/http/AsyncHttpClient.java rename src/com/{codebutler/android_websockets => koushikdutta/http}/HybiParser.java (93%) rename src/com/{codebutler/android_websockets/WebSocketClient.java => koushikdutta/http/WebSocket.java} (70%) diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java deleted file mode 100644 index f3de7a3..0000000 --- a/src/com/codebutler/android_websockets/SocketIOClient.java +++ /dev/null @@ -1,496 +0,0 @@ -package com.codebutler.android_websockets; - -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.concurrent.atomic.AtomicInteger; - -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpUriRequest; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import android.net.http.AndroidHttpClient; -import android.os.Looper; -import android.text.TextUtils; -import android.util.Log; -import android.util.SparseArray; - -public class SocketIOClient { - - private static final String TAG = "SocketIOClient"; - - private static final int CONNECT_MULTIPLIER = 2; - private static final int MAX_CONNECT_POWER = 10; - - public static interface Handler { - public void onConnect(); - - public void onConnectToEndpoint(String endpoint); - - 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 Acknowledge { - public void acknowledge(String[] args); - } - - private String mURL; - private Handler mHandler; - private String mSession; - private int mHeartbeat; - private WebSocketClient mClient; - private String mEndpoint; - private AtomicInteger mMessageIdCounter; - private SparseArray<Acknowledge> mAcknowledges; - private boolean mReconnectOnError; - private int mConnectPower; - - public SocketIOClient(URI uri, Handler handler) { - this(uri, handler, null, false); - } - - public SocketIOClient(URI uri, Handler handler, boolean reconnectOnError) { - this(uri, handler, null, reconnectOnError); - } - - public SocketIOClient(URI uri, Handler handler, String namespace, - boolean reconnectOnError) { - mEndpoint = namespace; - mAcknowledges = new SparseArray<SocketIOClient.Acknowledge>(); - mMessageIdCounter = new AtomicInteger(0); - if (TextUtils.isEmpty(namespace)) { - mEndpoint = "socket.io"; - } - - // remove trailing "/" from URI, in case user provided e.g. - // http://test.com/ - mURL = uri.toString().replaceAll("/$", "") + "/" + mEndpoint + "/1/"; - mHandler = handler; - mConnectPower = 1; - mReconnectOnError = reconnectOnError; - } - - private static String downloadUriAsString(final HttpUriRequest req) throws IOException { - AndroidHttpClient client = AndroidHttpClient.newInstance("android-websockets"); - try { - HttpResponse res = client.execute(req); - return readToEnd(res.getEntity().getContent()); - } finally { - client.close(); - } - } - - private int getNextMessageId() { - return mMessageIdCounter.incrementAndGet(); - } - - private static byte[] readToEndAsArray(InputStream input) throws IOException { - DataInputStream dis = new DataInputStream(input); - byte[] stuff = new byte[1024]; - ByteArrayOutputStream buff = new ByteArrayOutputStream(); - int read = 0; - while ((read = dis.read(stuff)) != -1) { - buff.write(stuff, 0, read); - } - - return buff.toByteArray(); - } - - private static String readToEnd(InputStream input) throws IOException { - return new String(readToEndAsArray(input)); - } - - android.os.Handler mSendHandler; - Looper mSendLooper; - - public void emit(String name, JSONArray args) throws JSONException { - emit(name, args, null); - } - - public void emit(String name, JSONArray args, final Acknowledge acknowledge) throws JSONException { - - emit(name, args, acknowledge, null); - } - - public void emit(String name, JSONArray args, final Acknowledge acknowledge, final String endpoint) throws JSONException { - - final JSONObject event = new JSONObject(); - event.put("name", name); - event.put("args", args); - - final int nextId = getNextMessageId(); - if (acknowledge != null) { - mAcknowledges.put(nextId, acknowledge); - } - mSendHandler.post(new Runnable() { - @Override - public void run() { - mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (endpoint == null ? "" : endpoint), event.toString())); - } - }); - } - - public void emit(final JSONObject jsonMessage) throws JSONException { - emit(jsonMessage, null); - } - - public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) throws JSONException { - - emit(jsonMessage, acknowledge, null); - } - - public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge, final String endpoint) throws JSONException { - - final int nextId = getNextMessageId(); - if (acknowledge != null) { - mAcknowledges.put(nextId, acknowledge); - } - mSendHandler.post(new Runnable() { - - @Override - public void run() { - mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (endpoint == null ? "" : endpoint), jsonMessage.toString())); - } - }); - } - - public void emit(final String message) { - emit(message, (Acknowledge) null); - } - - public void emit(final String message, final Acknowledge acknowledge) { - - emit(message, acknowledge, null); - } - - public void emit(final String message, final Acknowledge acknowledge, final String endpoint) { - - final int nextId = getNextMessageId(); - if (acknowledge != null) { - mAcknowledges.put(nextId, acknowledge); - } - mSendHandler.post(new Runnable() { - - @Override - public void run() { - mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (endpoint == null ? "" : endpoint), message)); - } - }); - } - - private void connectSession() throws URISyntaxException { - mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Listener() { - @Override - public void onMessage(byte[] data) { - cleanup(); - mHandler.onError(new Exception("Unexpected binary data")); - } - - @Override - public void onMessage(String message) { - try { - Log.d(TAG, "Message: " + message); - String[] parts = message.split(":", 4); - int code = Integer.parseInt(parts[0]); - switch (code) { - case 1: - // connect - if (!TextUtils.isEmpty(parts[2])) { - mHandler.onConnectToEndpoint(parts[2]); - } else { - mHandler.onConnect(); - } - break; - case 2: - // heartbeat - mClient.send("2::"); - break; - case 3: { - // message - final String messageId = parts[1]; - final String dataString = parts[3]; - - if (!"".equals(messageId)) { - mSendHandler.post(new Runnable() { - - @Override - public void run() { - mClient.send(String.format("6:::%s", messageId)); - } - }); - } - mHandler.onMessage(dataString); - break; - } - case 4: { - // json message - final String messageId = parts[1]; - final String dataString = parts[3]; - - JSONObject jsonMessage = null; - - try { - jsonMessage = new JSONObject(dataString); - } catch (JSONException e) { - jsonMessage = new JSONObject(); - } - if (!"".equals(messageId)) { - mSendHandler.post(new Runnable() { - - @Override - public void run() { - mClient.send(String.format("6:::%s", messageId)); - } - }); - } - mHandler.onJSON(jsonMessage); - break; - } - case 5: { - final String messageId = parts[1]; - final String dataString = parts[3]; - JSONObject data = new JSONObject(dataString); - String event = data.getString("name"); - JSONArray args; - try { - args = data.getJSONArray("args"); - } catch (JSONException e) { - args = new JSONArray(); - } - if (!"".equals(messageId)) { - mSendHandler.post(new Runnable() { - @Override - public void run() { - mClient.send(String.format("6:::%s", messageId)); - } - }); - } - mHandler.on(event, args); - break; - } - case 6: - // ACK - if (parts[3] != null && parts[3].contains("+")) { - String[] ackParts = parts[3].split("\\+"); - int ackId = Integer.valueOf(ackParts[0]); - - String ackArgs = ackParts[1]; - - int startIndex = ackArgs.indexOf('[') + 1; - - ackArgs = ackArgs.substring(startIndex, ackArgs.length() - 1); - - Acknowledge acknowledge = mAcknowledges.get(ackId); - - if (acknowledge != null) { - - String[] params = ackArgs.split(","); - for (int i = 0; i < params.length; i++) { - params[i] = params[i].replace("\"", ""); - } - acknowledge.acknowledge(params); - } - - mAcknowledges.remove(ackId); - } - break; - case 7: - // error - throw new Exception(message); - case 8: - // noop - break; - default: - throw new Exception("unknown code"); - } - } catch (Exception ex) { - onError(ex); - } - } - - @Override - public void onError(Exception error) { - - // Removed call to cleanup because now we can - // reconnect automatically so we need the mSendHandler - // instance - mHandler.onError(error); - - if (mReconnectOnError) { - - if (mClient != null) - mClient.disconnect(); - mClient = null; - mMessageIdCounter.set(0); - mAcknowledges.clear(); - - mSendHandler.postDelayed(new Runnable() { - - @Override - public void run() { - - // Doing this check here because the client - // can be forcibly connected before the - // Runnable runs - if (!mClient.isConnected()) { - connect(); - } - - } - }, getDelayInMillis()); - } - - else { - cleanup(); - } - } - - @Override - public void onDisconnect(int code, String reason) { - cleanup(); - // attempt reconnect with same session? - mHandler.onDisconnect(code, reason); - } - - @Override - public void onConnect() { - mConnectPower = 1; - mSendHandler.postDelayed(new Runnable() { - @Override - public void run() { - mSendHandler.postDelayed(this, mHeartbeat); - mClient.send("2:::"); - } - }, mHeartbeat); - } - }, null); - mClient.connect(); - } - - private long getDelayInMillis() { - return (long) (Math.pow(CONNECT_MULTIPLIER, (mConnectPower == MAX_CONNECT_POWER) ? MAX_CONNECT_POWER - : mConnectPower++) * 1000); - } - - public void disconnect() throws IOException { - cleanup(); - } - - private void cleanup() { - if (mClient != null) - mClient.disconnect(); - mClient = null; - mMessageIdCounter.set(0); - mAcknowledges.clear(); - mSendLooper.quit(); - mSendLooper = null; - mSendHandler = null; - } - - public void connect() { - if (mClient != null) - return; - new Thread() { - public void run() { - HttpPost post = new HttpPost(mURL); - try { - String line = downloadUriAsString(post); - String[] parts = line.split(":"); - mSession = parts[0]; - String heartbeat = parts[1]; - if (!"".equals(heartbeat)) - mHeartbeat = Integer.parseInt(heartbeat) / 2 * 1000; - 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"); - - Looper.prepare(); - mSendLooper = Looper.myLooper(); - mSendHandler = new android.os.Handler(); - - connectSession(); - - Looper.loop(); - } catch (Exception e) { - mHandler.onError(e); - } - }; - }.start(); - } - - /** - * Connect to an endpoint - */ - public void connectToEndpoint(final String endpoint) { - - if (mClient.isConnected() && !TextUtils.isEmpty(endpoint)) { - mEndpoint = endpoint; - mSendHandler.post(new Runnable() { - - @Override - public void run() { - mClient.send("1::" + endpoint); - } - }); - } - } - - /** - * Disconnect from an endpoint or socket - * - * @param endpoint - * {@code null} to disconnect the entire socket, otherwise the - * endpoint to disconnect from - */ - public void sendDisconnect(final String endpoint) { - - if (TextUtils.isEmpty(endpoint)) { - - mSendHandler.post(new Runnable() { - - @Override - public void run() { - mClient.send("0"); - } - }); - } - - else { - mSendHandler.post(new Runnable() { - - @Override - public void run() { - mClient.send("0::" + endpoint); - } - }); - } - } - - /** - * Get the current connected endpoint - * - * @return The current connected endpoint, "socket.io" if connected to the - * default endpoint - */ - public String getConnectedEndpoint() { - return mEndpoint; - } -} diff --git a/src/com/koushikdutta/async/http/socketio/Acknowledge.java b/src/com/koushikdutta/async/http/socketio/Acknowledge.java new file mode 100644 index 0000000..8bf1c64 --- /dev/null +++ b/src/com/koushikdutta/async/http/socketio/Acknowledge.java @@ -0,0 +1,7 @@ +package com.koushikdutta.async.http.socketio; + +import org.json.JSONArray; + +public interface Acknowledge { + void acknowledge(JSONArray arguments); +} diff --git a/src/com/koushikdutta/async/http/socketio/ConnectCallback.java b/src/com/koushikdutta/async/http/socketio/ConnectCallback.java new file mode 100644 index 0000000..7ff69d7 --- /dev/null +++ b/src/com/koushikdutta/async/http/socketio/ConnectCallback.java @@ -0,0 +1,5 @@ +package com.koushikdutta.async.http.socketio; + +public interface ConnectCallback { + public void onConnectCompleted(Exception ex, SocketIOClient client); +} \ No newline at end of file diff --git a/src/com/koushikdutta/async/http/socketio/DisconnectCallback.java b/src/com/koushikdutta/async/http/socketio/DisconnectCallback.java new file mode 100644 index 0000000..97ec4b6 --- /dev/null +++ b/src/com/koushikdutta/async/http/socketio/DisconnectCallback.java @@ -0,0 +1,8 @@ +package com.koushikdutta.async.http.socketio; + +/** + * Created by koush on 7/2/13. + */ +public interface DisconnectCallback { + void onDisconnect(Exception e); +} diff --git a/src/com/koushikdutta/async/http/socketio/ErrorCallback.java b/src/com/koushikdutta/async/http/socketio/ErrorCallback.java new file mode 100644 index 0000000..6411667 --- /dev/null +++ b/src/com/koushikdutta/async/http/socketio/ErrorCallback.java @@ -0,0 +1,8 @@ +package com.koushikdutta.async.http.socketio; + +/** + * Created by koush on 7/2/13. + */ +public interface ErrorCallback { + void onError(String error); +} diff --git a/src/com/koushikdutta/async/http/socketio/EventCallback.java b/src/com/koushikdutta/async/http/socketio/EventCallback.java new file mode 100644 index 0000000..191a663 --- /dev/null +++ b/src/com/koushikdutta/async/http/socketio/EventCallback.java @@ -0,0 +1,7 @@ +package com.koushikdutta.async.http.socketio; + +import org.json.JSONArray; + +public interface EventCallback { + public void onEvent(JSONArray argument, Acknowledge acknowledge); +} \ No newline at end of file diff --git a/src/com/koushikdutta/async/http/socketio/EventEmitter.java b/src/com/koushikdutta/async/http/socketio/EventEmitter.java new file mode 100644 index 0000000..1c1af7c --- /dev/null +++ b/src/com/koushikdutta/async/http/socketio/EventEmitter.java @@ -0,0 +1,54 @@ +package com.koushikdutta.async.http.socketio; + +import com.koushikdutta.async.util.HashList; + +import org.json.JSONArray; + +import java.util.Iterator; +import java.util.List; + +/** + * Created by koush on 7/1/13. + */ +public class EventEmitter { + interface OnceCallback extends EventCallback { + } + + HashList<EventCallback> callbacks = new HashList<EventCallback>(); + void onEvent(String event, JSONArray arguments, Acknowledge acknowledge) { + List<EventCallback> list = callbacks.get(event); + if (list == null) + return; + Iterator<EventCallback> iter = list.iterator(); + while (iter.hasNext()) { + EventCallback cb = iter.next(); + cb.onEvent(arguments, acknowledge); + if (cb instanceof OnceCallback) + iter.remove(); + } + } + + public void addListener(String event, EventCallback callback) { + on(event, callback); + } + + public void once(final String event, final EventCallback callback) { + on(event, new OnceCallback() { + @Override + public void onEvent(JSONArray arguments, Acknowledge acknowledge) { + callback.onEvent(arguments, acknowledge); + } + }); + } + + public void on(String event, EventCallback callback) { + callbacks.add(event, callback); + } + + public void removeListener(String event, EventCallback callback) { + List<EventCallback> list = callbacks.get(event); + if (list == null) + return; + list.remove(callback); + } +} diff --git a/src/com/koushikdutta/async/http/socketio/JSONCallback.java b/src/com/koushikdutta/async/http/socketio/JSONCallback.java new file mode 100644 index 0000000..52d8aff --- /dev/null +++ b/src/com/koushikdutta/async/http/socketio/JSONCallback.java @@ -0,0 +1,8 @@ +package com.koushikdutta.async.http.socketio; + +import org.json.JSONObject; + +public interface JSONCallback { + public void onJSON(JSONObject json, Acknowledge acknowledge); +} + diff --git a/src/com/koushikdutta/async/http/socketio/ReconnectCallback.java b/src/com/koushikdutta/async/http/socketio/ReconnectCallback.java new file mode 100644 index 0000000..bd427f4 --- /dev/null +++ b/src/com/koushikdutta/async/http/socketio/ReconnectCallback.java @@ -0,0 +1,5 @@ +package com.koushikdutta.async.http.socketio; + +public interface ReconnectCallback { + public void onReconnect(); +} \ No newline at end of file diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java new file mode 100644 index 0000000..a4ad818 --- /dev/null +++ b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java @@ -0,0 +1,179 @@ +package com.koushikdutta.async.http.socketio; + +import org.json.JSONArray; +import org.json.JSONObject; + +import android.os.Handler; +import android.text.TextUtils; + +import com.koushikdutta.http.AsyncHttpClient; +import com.koushikdutta.http.AsyncHttpClient.SocketIORequest; +import com.koushikdutta.http.WebSocket; + +public class SocketIOClient extends EventEmitter { + boolean connected; + boolean disconnected; + + private void emitRaw(int type, String message, Acknowledge acknowledge) { + connection.emitRaw(type, this, message, acknowledge); + } + + public void emit(String name, JSONArray args) { + emit(name, args, null); + } + + public void emit(final String message) { + emit(message, (Acknowledge) null); + } + + public void emit(final JSONObject jsonMessage) { + emit(jsonMessage, null); + } + + public void emit(String name, JSONArray args, Acknowledge acknowledge) { + final JSONObject event = new JSONObject(); + try { + event.put("name", name); + event.put("args", args); + emitRaw(5, event.toString(), acknowledge); + } catch (Exception e) { + } + } + + public void emit(final String message, Acknowledge acknowledge) { + emitRaw(3, message, acknowledge); + } + + public void emit(final JSONObject jsonMessage, Acknowledge acknowledge) { + emitRaw(4, jsonMessage.toString(), acknowledge); + } + + public static void connect(String uri, final ConnectCallback callback, final Handler handler) { + connect(new SocketIORequest(uri), callback, handler); + } + + ConnectCallback connectCallback; + + public static void connect(final SocketIORequest request, final ConnectCallback callback, final Handler handler) { + + final SocketIOConnection connection = new SocketIOConnection(handler, new AsyncHttpClient(), request); + + final ConnectCallback wrappedCallback = new ConnectCallback() { + @Override + public void onConnectCompleted(Exception ex, SocketIOClient client) { + if (ex != null || TextUtils.isEmpty(request.getEndpoint())) { + if (callback != null) + callback.onConnectCompleted(ex, client); + + return; + } + + // remove the root client since that's not actually being used. + connection.clients.remove(client); + + // connect to the endpoint we want + client.of(request.getEndpoint(), new ConnectCallback() { + @Override + public void onConnectCompleted(Exception ex, SocketIOClient client) { + if (callback != null) + callback.onConnectCompleted(ex, client); + } + }); + } + }; + + connection.clients.add(new SocketIOClient(connection, "", wrappedCallback)); + connection.reconnect(); + + } + + ErrorCallback errorCallback; + + public ErrorCallback getErrorCallback() { + return errorCallback; + } + + public void setErrorCallback(ErrorCallback callback) { + errorCallback = callback; + } + + DisconnectCallback disconnectCallback; + + public DisconnectCallback getDisconnectCallback() { + return disconnectCallback; + } + + public void setDisconnectCallback(DisconnectCallback callback) { + disconnectCallback = callback; + } + + ReconnectCallback reconnectCallback; + + public ReconnectCallback getReconnectCallback() { + return reconnectCallback; + } + + public void setReconnectCallback(ReconnectCallback callback) { + reconnectCallback = 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; + } + + SocketIOConnection connection; + String endpoint; + + private SocketIOClient(SocketIOConnection connection, String endpoint, + ConnectCallback callback) { + this.endpoint = endpoint; + this.connection = connection; + this.connectCallback = callback; + } + + public boolean isConnected() { + return connected && !disconnected && connection.isConnected(); + } + + public void disconnect() { + connection.disconnect(this); + DisconnectCallback disconnectCallback = this.disconnectCallback; + if (disconnectCallback != null) { + disconnectCallback.onDisconnect(null); + } + } + + public void of(String endpoint, ConnectCallback connectCallback) { + connection.connect(new SocketIOClient(connection, endpoint, connectCallback)); + } + + public WebSocket getWebSocket() { + return connection.webSocket; + } +} diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java new file mode 100644 index 0000000..53900ad --- /dev/null +++ b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java @@ -0,0 +1,396 @@ +package com.koushikdutta.async.http.socketio; + +import android.os.Handler; +import android.text.TextUtils; + +import org.json.JSONArray; +import org.json.JSONObject; + +import com.koushikdutta.http.AsyncHttpClient; +import com.koushikdutta.http.AsyncHttpClient.SocketIORequest; +import com.koushikdutta.http.WebSocket; +import com.koushikdutta.http.WebSocket.ClosedCallback; +import com.koushikdutta.http.WebSocket.DataCallback; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Hashtable; + +/** + * Created by koush on 7/1/13. + */ +class SocketIOConnection { + + private Handler mHandler; + AsyncHttpClient httpClient; + int heartbeat; + ArrayList<SocketIOClient> clients = new ArrayList<SocketIOClient>(); + WebSocket webSocket; + SocketIORequest request; + + public SocketIOConnection(Handler handler, AsyncHttpClient httpClient, + SocketIORequest request) { + mHandler = handler; + this.httpClient = httpClient; + this.request = request; + } + + public boolean isConnected() { + return webSocket != null && webSocket.isOpen(); + } + + Hashtable<String, Acknowledge> acknowledges = new Hashtable<String, Acknowledge>(); + int ackCount; + + public void emitRaw(int type, SocketIOClient client, String message, Acknowledge acknowledge) { + String ack = ""; + if (acknowledge != null) { + String id = "" + ackCount++; + ack = id + "+"; + acknowledges.put(id, acknowledge); + } + webSocket.send(String.format("%d:%s:%s:%s", type, ack, client.endpoint, message)); + } + + public void connect(SocketIOClient client) { + clients.add(client); + webSocket.send(String.format("1::%s", client.endpoint)); + } + + public void disconnect(SocketIOClient client) { + clients.remove(client); + + // see if we can leave this endpoint completely + boolean needsEndpointDisconnect = true; + for (SocketIOClient other : clients) { + // if this is the default endpoint (which disconnects everything), + // or another client is using this endpoint, + // we can't disconnect + if (TextUtils.equals(other.endpoint, client.endpoint) + || TextUtils.isEmpty(client.endpoint)) { + needsEndpointDisconnect = false; + break; + } + } + + if (needsEndpointDisconnect) + webSocket.send(String.format("0::%s", client.endpoint)); + + // and see if we can disconnect the socket completely + if (clients.size() > 0) + return; + + webSocket.setStringCallback(null); + webSocket.setClosedCallback(null); + webSocket.disconnect(); + webSocket = null; + } + + void reconnect() { + if (isConnected()) { + return; + } + + // initiate a session + httpClient.executeString(request, new AsyncHttpClient.StringCallback() { + @Override + public void onCompleted(final Exception e, String result) { + if (e != null) { + reportDisconnect(e); + return; + } + + try { + String[] parts = result.split(":"); + String session = parts[0]; + 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"); + + final String sessionUrl = request.getUri().toString() + + "websocket/" + session + "/"; + + httpClient.websocket(sessionUrl, null, new AsyncHttpClient.WebSocketConnectCallback() { + @Override + public void onCompleted(Exception ex, WebSocket webSocket) { + if (ex != null) { + reportDisconnect(ex); + return; + } + + reconnectDelay = 1000L; + SocketIOConnection.this.webSocket = webSocket; + attach(); + } + }); + + } catch (Exception ex) { + reportDisconnect(ex); + } + } + }); + + } + + void setupHeartbeat() { + final WebSocket ws = webSocket; + Runnable heartbeatRunner = new Runnable() { + @Override + public void run() { + if (heartbeat <= 0 || ws != webSocket || ws == null + || !ws.isOpen()) + return; + webSocket.send("2:::"); + + mHandler.postDelayed(this, heartbeat); + } + }; + heartbeatRunner.run(); + } + + private interface SelectCallback { + void onSelect(SocketIOClient client); + } + + private void select(String endpoint, SelectCallback callback) { + for (SocketIOClient client : clients) { + if (endpoint == null || TextUtils.equals(client.endpoint, endpoint)) { + callback.onSelect(client); + } + } + } + + private void delayReconnect() { + if (webSocket != null || clients.size() == 0) + return; + + // see if any client has disconnected, + // and that we need a reconnect + boolean disconnected = false; + for (SocketIOClient client : clients) { + if (client.disconnected) { + disconnected = true; + break; + } + } + + if (!disconnected) + return; + + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + reconnect(); + } + }, reconnectDelay); + reconnectDelay *= 2; + } + + long reconnectDelay = 1000L; + + private void reportDisconnect(final Exception ex) { + select(null, new SelectCallback() { + @Override + public void onSelect(SocketIOClient client) { + if (client.connected) { + client.disconnected = true; + DisconnectCallback closed = client.getDisconnectCallback(); + if (closed != null) + closed.onDisconnect(ex); + } else { + // client has never connected, this is a initial connect + // failure + ConnectCallback callback = client.connectCallback; + if (callback != null) + callback.onConnectCompleted(ex, client); + } + } + }); + + delayReconnect(); + } + + private void reportConnect(String endpoint) { + select(endpoint, new SelectCallback() { + @Override + public void onSelect(SocketIOClient client) { + if (client.isConnected()) + return; + if (!client.connected) { + // normal connect + client.connected = true; + ConnectCallback callback = client.connectCallback; + if (callback != null) + callback.onConnectCompleted(null, client); + } else if (client.disconnected) { + // reconnect + client.disconnected = false; + ReconnectCallback callback = client.reconnectCallback; + if (callback != null) + callback.onReconnect(); + } else { + // double connect? + // assert false; + } + } + }); + } + + private void reportJson(String endpoint, final JSONObject jsonMessage, final Acknowledge acknowledge) { + select(endpoint, new SelectCallback() { + @Override + public void onSelect(SocketIOClient client) { + JSONCallback callback = client.jsonCallback; + if (callback != null) + callback.onJSON(jsonMessage, acknowledge); + } + }); + } + + private void reportString(String endpoint, final String string, final Acknowledge acknowledge) { + select(endpoint, new SelectCallback() { + @Override + public void onSelect(SocketIOClient client) { + StringCallback callback = client.stringCallback; + if (callback != null) + callback.onString(string, acknowledge); + } + }); + } + + private void reportEvent(String endpoint, final String event, final JSONArray arguments, final Acknowledge acknowledge) { + select(endpoint, new SelectCallback() { + @Override + public void onSelect(SocketIOClient client) { + client.onEvent(event, arguments, acknowledge); + } + }); + } + + private void reportError(String endpoint, final String error) { + select(endpoint, new SelectCallback() { + @Override + public void onSelect(SocketIOClient client) { + ErrorCallback callback = client.errorCallback; + if (callback != null) + callback.onError(error); + } + }); + } + + private Acknowledge acknowledge(final String messageId) { + if (TextUtils.isEmpty(messageId)) + return null; + + return new Acknowledge() { + @Override + public void acknowledge(JSONArray arguments) { + String data = ""; + if (arguments != null) + data += "+" + arguments.toString(); + webSocket.send(String.format("6:::%s%s", messageId, data)); + } + }; + } + + private void attach() { + setupHeartbeat(); + + webSocket.setDataCallback(new DataCallback() { + + @Override + public void onDataAvailable(byte[] data) { + // Do nothing + } + }); + webSocket.setClosedCallback(new ClosedCallback() { + + @Override + public void onCompleted(final Exception ex) { + webSocket = null; + reportDisconnect(ex); + } + }); + + 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: + // disconnect + webSocket.disconnect(); + reportDisconnect(null); + break; + case 1: + // connect + reportConnect(parts[2]); + break; + case 2: + // heartbeat + webSocket.send("2::"); + break; + case 3: { + // message + reportString(parts[2], parts[3], acknowledge(parts[1])); + break; + } + case 4: { + // json message + final String dataString = parts[3]; + final JSONObject jsonMessage = new JSONObject(dataString); + reportJson(parts[2], jsonMessage, acknowledge(parts[1])); + break; + } + case 5: { + final String dataString = parts[3]; + final JSONObject data = new JSONObject(dataString); + final String event = data.getString("name"); + final JSONArray args = data.optJSONArray("args"); + reportEvent(parts[2], event, args, acknowledge(parts[1])); + break; + } + case 6: + // ACK + final String[] ackParts = parts[3].split("\\+", 2); + Acknowledge ack = acknowledges.remove(ackParts[0]); + if (ack == null) + return; + JSONArray arguments = null; + if (ackParts.length == 2) + arguments = new JSONArray(ackParts[1]); + ack.acknowledge(arguments); + break; + case 7: + // error + reportError(parts[2], parts[3]); + break; + case 8: + // noop + break; + default: + throw new Exception("unknown code"); + } + } catch (Exception ex) { + webSocket.setClosedCallback(null); + webSocket.disconnect(); + webSocket = null; + reportDisconnect(ex); + } + } + }); + + webSocket.startParsing(); + } +} diff --git a/src/com/koushikdutta/async/http/socketio/StringCallback.java b/src/com/koushikdutta/async/http/socketio/StringCallback.java new file mode 100644 index 0000000..7062b0f --- /dev/null +++ b/src/com/koushikdutta/async/http/socketio/StringCallback.java @@ -0,0 +1,5 @@ +package com.koushikdutta.async.http.socketio; + +public interface StringCallback { + public void onString(String string, Acknowledge acknowledge); +} \ No newline at end of file diff --git a/src/com/koushikdutta/async/util/HashList.java b/src/com/koushikdutta/async/util/HashList.java new file mode 100644 index 0000000..2b2a0c6 --- /dev/null +++ b/src/com/koushikdutta/async/util/HashList.java @@ -0,0 +1,26 @@ +package com.koushikdutta.async.util; + +import java.util.ArrayList; +import java.util.Hashtable; + +/** + * Created by koush on 5/27/13. + */ +public class HashList<T> extends Hashtable<String, ArrayList<T>> { + public HashList() { + } + + public boolean contains(String key) { + ArrayList<T> check = get(key); + return check != null && check.size() > 0; + } + + public void add(String key, T value) { + ArrayList<T> ret = get(key); + if (ret == null) { + ret = new ArrayList<T>(); + put(key, ret); + } + ret.add(value); + } +} diff --git a/src/com/koushikdutta/http/AsyncHttpClient.java b/src/com/koushikdutta/http/AsyncHttpClient.java new file mode 100644 index 0000000..434ff51 --- /dev/null +++ b/src/com/koushikdutta/http/AsyncHttpClient.java @@ -0,0 +1,126 @@ +package com.koushikdutta.http; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpPost; + +import android.net.Uri; +import android.net.http.AndroidHttpClient; +import android.os.AsyncTask; + +public class AsyncHttpClient { + + public AsyncHttpClient() { + + } + + public static class SocketIORequest { + + private String mUri; + private String mEndpoint; + + public SocketIORequest(String uri) { + this(uri, null); + } + + public SocketIORequest(String uri, String endpoint) { + + mUri = Uri.parse(uri).buildUpon().encodedPath("/socket.io/1/").build().toString(); + mEndpoint = endpoint; + } + + public String getUri() { + + return mUri; + } + + public String getEndpoint() { + + return mEndpoint; + } + } + + public static interface StringCallback { + public void onCompleted(final Exception e, String result); + } + + public static interface WebSocketConnectCallback { + public void onCompleted(Exception ex, WebSocket webSocket); + } + + public void executeString(final SocketIORequest socketIORequest, final StringCallback stringCallback) { + + new AsyncTask<Void, Void, Void>() { + + @Override + protected Void doInBackground(Void... params) { + + AndroidHttpClient httpClient = AndroidHttpClient.newInstance("android-websockets-2.0"); + HttpPost post = new HttpPost(socketIORequest.getUri()); + + try { + HttpResponse res = httpClient.execute(post); + String responseString = readToEnd(res.getEntity().getContent()); + + if (stringCallback != null) { + stringCallback.onCompleted(null, responseString); + } + + } catch (IOException e) { + + if (stringCallback != null) { + stringCallback.onCompleted(e, null); + } + } finally { + httpClient.close(); + httpClient = null; + } + return null; + } + }.execute(); + } + + private byte[] readToEndAsArray(InputStream input) throws IOException { + DataInputStream dis = new DataInputStream(input); + byte[] stuff = new byte[1024]; + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + int read = 0; + while ((read = dis.read(stuff)) != -1) { + buff.write(stuff, 0, read); + } + + return buff.toByteArray(); + } + + private String readToEnd(InputStream input) throws IOException { + return new String(readToEndAsArray(input)); + } + + public void websocket(final String uri, final String protocol, final WebSocketConnectCallback callback) { + + WebSocket.create(URI.create(uri), new WebSocket.Listener() { + + @Override + public void onConnect(WebSocket websocket) { + if (callback != null) { + callback.onCompleted(null, websocket); + } + + } + + @Override + public void onError(Exception ex) { + if (callback != null) { + callback.onCompleted(ex, null); + } + + } + }); + } + +} diff --git a/src/com/codebutler/android_websockets/HybiParser.java b/src/com/koushikdutta/http/HybiParser.java similarity index 93% rename from src/com/codebutler/android_websockets/HybiParser.java rename to src/com/koushikdutta/http/HybiParser.java index 1f83f2a..6f2f609 100644 --- a/src/com/codebutler/android_websockets/HybiParser.java +++ b/src/com/koushikdutta/http/HybiParser.java @@ -28,18 +28,22 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -package com.codebutler.android_websockets; +package com.koushikdutta.http; -import android.util.Log; - -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.List; +import android.util.Log; + public class HybiParser { private static final String TAG = "HybiParser"; - private WebSocketClient mClient; + private WebSocket mWebSocket; private boolean mMasking = true; @@ -91,8 +95,8 @@ public class HybiParser { OP_CONTINUATION, OP_TEXT, OP_BINARY ); - public HybiParser(WebSocketClient client) { - mClient = client; + public HybiParser(WebSocket client) { + mWebSocket = client; } private static byte[] mask(byte[] payload, byte[] mask, int offset) { @@ -128,7 +132,7 @@ public void start(HappyDataInputStream stream) throws IOException { break; } } - mClient.getListener().onDisconnect(0, "EOF"); + mWebSocket.getClosedCallback().onCompleted(new IOException("EOF")); } private void parseOpcode(byte data) throws ProtocolError { @@ -241,12 +245,12 @@ private byte[] frame(Object data, int opcode, int errorCode) { } public void ping(String message) { - mClient.send(frame(message, OP_PING, -1)); + mWebSocket.send(frame(message, OP_PING, -1)); } public void close(int code, String reason) { if (mClosed) return; - mClient.send(frame(reason, OP_CLOSE, code)); + mWebSocket.send(frame(reason, OP_CLOSE, code)); mClosed = true; } @@ -262,9 +266,9 @@ private void emitFrame() throws IOException { if (mFinal) { byte[] message = mBuffer.toByteArray(); if (mMode == MODE_TEXT) { - mClient.getListener().onMessage(encode(message)); + mWebSocket.getStringCallback().onStringAvailable(encode(message)); } else { - mClient.getListener().onMessage(message); + mWebSocket.getDataCallback().onDataAvailable(message); } reset(); } @@ -272,7 +276,7 @@ private void emitFrame() throws IOException { } else if (opcode == OP_TEXT) { if (mFinal) { String messageText = encode(payload); - mClient.getListener().onMessage(messageText); + mWebSocket.getStringCallback().onStringAvailable(messageText); } else { mMode = MODE_TEXT; mBuffer.write(payload); @@ -280,7 +284,7 @@ private void emitFrame() throws IOException { } else if (opcode == OP_BINARY) { if (mFinal) { - mClient.getListener().onMessage(payload); + mWebSocket.getDataCallback().onDataAvailable(payload); } else { mMode = MODE_BINARY; mBuffer.write(payload); @@ -290,12 +294,12 @@ private void emitFrame() throws IOException { int code = (payload.length >= 2) ? 256 * payload[0] + payload[1] : 0; String reason = (payload.length > 2) ? encode(slice(payload, 2)) : null; Log.d(TAG, "Got close op! " + code + " " + reason); - mClient.getListener().onDisconnect(code, reason); + mWebSocket.getClosedCallback().onCompleted(new IOException("Got close op! " + code + " " + reason)); } else if (opcode == OP_PING) { if (payload.length > 125) { throw new ProtocolError("Ping payload too large"); } Log.d(TAG, "Sending pong!!"); - mClient.sendFrame(frame(payload, OP_PONG, -1)); + mWebSocket.sendFrame(frame(payload, OP_PONG, -1)); } else if (opcode == OP_PONG) { String message = encode(payload); diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/koushikdutta/http/WebSocket.java similarity index 70% rename from src/com/codebutler/android_websockets/WebSocketClient.java rename to src/com/koushikdutta/http/WebSocket.java index a75ec64..f6fd3f1 100644 --- a/src/com/codebutler/android_websockets/WebSocketClient.java +++ b/src/com/koushikdutta/http/WebSocket.java @@ -1,20 +1,5 @@ -package com.codebutler.android_websockets; +package com.koushikdutta.http; -import android.os.Handler; -import android.os.HandlerThread; -import android.text.TextUtils; -import android.util.Base64; -import android.util.Log; -import org.apache.http.*; -import org.apache.http.client.HttpResponseException; -import org.apache.http.message.BasicLineParser; -import org.apache.http.message.BasicNameValuePair; - -import javax.net.SocketFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; import java.io.EOFException; import java.io.IOException; import java.io.OutputStream; @@ -25,18 +10,47 @@ import java.security.NoSuchAlgorithmException; import java.util.List; -public class WebSocketClient { - private static final String TAG = "WebSocketClient"; +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpStatus; +import org.apache.http.NameValuePair; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpResponseException; +import org.apache.http.message.BasicLineParser; +import org.apache.http.message.BasicNameValuePair; + +import com.koushikdutta.http.HybiParser.HappyDataInputStream; + +import android.os.Handler; +import android.os.HandlerThread; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; + +public class WebSocket { + private static final String TAG = "WebSocket"; - private URI mURI; - private Listener mListener; - private Socket mSocket; - private Thread mThread; - private HandlerThread mHandlerThread; - private Handler mHandler; + private URI mURI; + private Listener mListener; + private Socket mSocket; + private Thread mThread; + private HandlerThread mHandlerThread; + private Handler mHandler; private List<BasicNameValuePair> mExtraHeaders; - private HybiParser mParser; - private boolean mConnected; + private HybiParser mParser; + private boolean mConnected; + + private DataCallback mDataCallback; + private StringCallback mStringCallback; + private ClosedCallback mClosedCallback; + + private HappyDataInputStream stream; private final Object mSendLock = new Object(); @@ -46,12 +60,13 @@ public static void setTrustManagers(TrustManager[] tm) { sTrustManagers = tm; } - public WebSocketClient(URI uri, Listener listener, List<BasicNameValuePair> extraHeaders) { - mURI = uri; - mListener = listener; + public WebSocket(URI uri, Listener listener, + List<BasicNameValuePair> extraHeaders) { + mURI = uri; + mListener = listener; mExtraHeaders = extraHeaders; - mConnected = false; - mParser = new HybiParser(this); + mConnected = false; + mParser = new HybiParser(this); mHandlerThread = new HandlerThread("websocket-thread"); mHandlerThread.start(); @@ -68,20 +83,26 @@ public void connect() { } mThread = new Thread(new Runnable() { + @Override public void run() { try { - int port = (mURI.getPort() != -1) ? mURI.getPort() : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 : 80); + int port = (mURI.getPort() != -1) ? mURI.getPort() + : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 + : 80); - String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath(); + String path = TextUtils.isEmpty(mURI.getPath()) ? "/" + : mURI.getPath(); if (!TextUtils.isEmpty(mURI.getQuery())) { path += "?" + mURI.getQuery(); } - String originScheme = mURI.getScheme().equals("wss") ? "https" : "http"; + String originScheme = mURI.getScheme().equals("wss") ? "https" + : "http"; URI origin = new URI(originScheme, "//" + mURI.getHost(), null); - SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault(); + SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() + : SocketFactory.getDefault(); mSocket = factory.createSocket(mURI.getHost(), port); PrintWriter out = new PrintWriter(mSocket.getOutputStream()); @@ -100,7 +121,7 @@ public void run() { out.print("\r\n"); out.flush(); - HybiParser.HappyDataInputStream stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream()); + stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream()); // Read HTTP response status line. StatusLine statusLine = parseStatusLine(readLine(stream)); @@ -119,22 +140,19 @@ public void run() { } } - mListener.onConnect(); + mListener.onConnect(WebSocket.this); mConnected = true; - // Now decode websocket frames. - mParser.start(stream); - } catch (EOFException ex) { Log.d(TAG, "WebSocket EOF!", ex); - mListener.onDisconnect(0, "EOF"); + mListener.onError(ex); mConnected = false; } catch (SSLException ex) { // Connection reset by peer Log.d(TAG, "Websocket SSL error!", ex); - mListener.onDisconnect(0, "SSL"); + mListener.onError(ex); mConnected = false; } catch (Exception ex) { @@ -145,6 +163,16 @@ public void run() { mThread.start(); } + public void startParsing() { + + // Now decode websocket frames. + try { + mParser.start(stream); + } catch (IOException e) { + mClosedCallback.onCompleted(e); + } + } + public void disconnect() { if (mSocket != null) { mHandler.post(new Runnable() { @@ -173,7 +201,7 @@ public void send(byte[] data) { sendFrame(mParser.frame(data)); } - public boolean isConnected() { + public boolean isOpen() { return mConnected; } @@ -233,12 +261,47 @@ public void run() { }); } - public interface Listener { - public void onConnect(); - public void onMessage(String message); - public void onMessage(byte[] data); - public void onDisconnect(int code, String reason); - public void onError(Exception error); + public static interface Listener { + public void onConnect(WebSocket webSocket); + + public void onError(Exception ex); + + } + + public static interface DataCallback { + public void onDataAvailable(byte[] data); + } + + public static interface StringCallback { + public void onStringAvailable(String message); + } + + public static interface ClosedCallback { + public void onCompleted(Exception ex); + } + + public DataCallback getDataCallback() { + return mDataCallback; + } + + public void setDataCallback(DataCallback dataCallback) { + this.mDataCallback = dataCallback; + } + + public StringCallback getStringCallback() { + return mStringCallback; + } + + public void setStringCallback(StringCallback stringCallback) { + this.mStringCallback = stringCallback; + } + + public ClosedCallback getClosedCallback() { + return mClosedCallback; + } + + public void setClosedCallback(ClosedCallback closedCallback) { + this.mClosedCallback = closedCallback; } private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException { @@ -246,4 +309,9 @@ private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, context.init(null, sTrustManagers, null); return context.getSocketFactory(); } + + public static void create(URI uri, Listener listener) { + WebSocket webSocket = new WebSocket(uri, listener, null); + webSocket.connect(); + } } From 92911b8f8865d4fd567a220dfb18904fb1ecc8a7 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Wed, 10 Jul 2013 01:25:03 +0530 Subject: [PATCH 45/56] Removed eventcallback addition to SocketIOClient as we need to use addListener() --- .../async/http/socketio/SocketIOClient.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java index a4ad818..46a0617 100644 --- a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java +++ b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java @@ -137,16 +137,6 @@ public void setStringCallback(StringCallback callback) { stringCallback = callback; } - EventCallback eventCallback; - - public EventCallback getEventCallback() { - return eventCallback; - } - - public void setEventCallback(EventCallback callback) { - eventCallback = callback; - } - SocketIOConnection connection; String endpoint; From e70653596603355bfec099f5a75ddf50169a2d85 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Thu, 11 Jul 2013 22:53:06 +0530 Subject: [PATCH 46/56] Added wrappers around Callbacks to mimic earlier Handler interface --- .../async/http/socketio/EventCallback.java | 2 +- .../async/http/socketio/EventEmitter.java | 7 +- .../async/http/socketio/SocketIOClient.java | 155 ++++++++++++++---- .../http/socketio/SocketIOConnection.java | 58 +++---- src/com/koushikdutta/async/util/HashList.java | 3 + .../koushikdutta/http/AsyncHttpClient.java | 10 +- src/com/koushikdutta/http/HybiParser.java | 7 +- .../{WebSocket.java => WebSocketClient.java} | 12 +- 8 files changed, 181 insertions(+), 73 deletions(-) rename src/com/koushikdutta/http/{WebSocket.java => WebSocketClient.java} (96%) diff --git a/src/com/koushikdutta/async/http/socketio/EventCallback.java b/src/com/koushikdutta/async/http/socketio/EventCallback.java index 191a663..d416b2d 100644 --- a/src/com/koushikdutta/async/http/socketio/EventCallback.java +++ b/src/com/koushikdutta/async/http/socketio/EventCallback.java @@ -3,5 +3,5 @@ import org.json.JSONArray; public interface EventCallback { - public void onEvent(JSONArray argument, Acknowledge acknowledge); + public void onEvent(String event, JSONArray argument, Acknowledge acknowledge); } \ No newline at end of file diff --git a/src/com/koushikdutta/async/http/socketio/EventEmitter.java b/src/com/koushikdutta/async/http/socketio/EventEmitter.java index 1c1af7c..f6dbb7a 100644 --- a/src/com/koushikdutta/async/http/socketio/EventEmitter.java +++ b/src/com/koushikdutta/async/http/socketio/EventEmitter.java @@ -15,6 +15,7 @@ interface OnceCallback extends EventCallback { } HashList<EventCallback> callbacks = new HashList<EventCallback>(); + void onEvent(String event, JSONArray arguments, Acknowledge acknowledge) { List<EventCallback> list = callbacks.get(event); if (list == null) @@ -22,7 +23,7 @@ void onEvent(String event, JSONArray arguments, Acknowledge acknowledge) { Iterator<EventCallback> iter = list.iterator(); while (iter.hasNext()) { EventCallback cb = iter.next(); - cb.onEvent(arguments, acknowledge); + cb.onEvent(event, arguments, acknowledge); if (cb instanceof OnceCallback) iter.remove(); } @@ -35,8 +36,8 @@ public void addListener(String event, EventCallback callback) { public void once(final String event, final EventCallback callback) { on(event, new OnceCallback() { @Override - public void onEvent(JSONArray arguments, Acknowledge acknowledge) { - callback.onEvent(arguments, acknowledge); + public void onEvent(String event, JSONArray arguments, Acknowledge acknowledge) { + callback.onEvent(event, arguments, acknowledge); } }); } diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java index 46a0617..d678a61 100644 --- a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java +++ b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java @@ -1,5 +1,7 @@ package com.koushikdutta.async.http.socketio; +import java.util.List; + import org.json.JSONArray; import org.json.JSONObject; @@ -8,12 +10,14 @@ import com.koushikdutta.http.AsyncHttpClient; import com.koushikdutta.http.AsyncHttpClient.SocketIORequest; -import com.koushikdutta.http.WebSocket; +import com.koushikdutta.http.WebSocketClient; public class SocketIOClient extends EventEmitter { boolean connected; boolean disconnected; + private SocketIOCallbacks mSocketIOCallbacks; + private void emitRaw(int type, String message, Acknowledge acknowledge) { connection.emitRaw(type, this, message, acknowledge); } @@ -62,9 +66,11 @@ public static void connect(final SocketIORequest request, final ConnectCallback @Override public void onConnectCompleted(Exception ex, SocketIOClient client) { if (ex != null || TextUtils.isEmpty(request.getEndpoint())) { - if (callback != null) + if (callback != null) { + client.setupCallbacks(); callback.onConnectCompleted(ex, client); - + } + return; } @@ -75,8 +81,10 @@ public void onConnectCompleted(Exception ex, SocketIOClient client) { client.of(request.getEndpoint(), new ConnectCallback() { @Override public void onConnectCompleted(Exception ex, SocketIOClient client) { - if (callback != null) + if (callback != null) { + client.setupCallbacks(); callback.onConnectCompleted(ex, client); + } } }); } @@ -86,57 +94,131 @@ public void onConnectCompleted(Exception ex, SocketIOClient client) { connection.reconnect(); } + + private void setupCallbacks() { + setStringCallback(new StringCallback() { - ErrorCallback errorCallback; + @Override + public void onString(String string, Acknowledge acknowledge) { + + if (mSocketIOCallbacks != null) { + mSocketIOCallbacks.onMessage(string); + } + } + }); + + setJSONCallback(new JSONCallback() { + + @Override + public void onJSON(JSONObject json, Acknowledge acknowledge) { + + if (mSocketIOCallbacks != null) { + mSocketIOCallbacks.onJSON(json); + } + + } + }); + + setReconnectCallback(new ReconnectCallback() { + + @Override + public void onReconnect() { + + if (mSocketIOCallbacks != null) { + mSocketIOCallbacks.onReconnect(); + } + + } + }); + + setDisconnectCallback(new DisconnectCallback() { + + @Override + public void onDisconnect(Exception e) { + + if (mSocketIOCallbacks != null) { + mSocketIOCallbacks.onDisconnect(0, e.getMessage()); + } + + } + }); + + setErrorCallback(new ErrorCallback() { + + @Override + public void onError(String error) { + if (mSocketIOCallbacks != null) { + mSocketIOCallbacks.onError(new Exception(error)); + } - public ErrorCallback getErrorCallback() { - return errorCallback; + } + }); } - public void setErrorCallback(ErrorCallback callback) { + ErrorCallback errorCallback; + + private void setErrorCallback(ErrorCallback callback) { errorCallback = callback; } DisconnectCallback disconnectCallback; - public DisconnectCallback getDisconnectCallback() { - return disconnectCallback; + private void setDisconnectCallback(DisconnectCallback callback) { + disconnectCallback = callback; } - public void setDisconnectCallback(DisconnectCallback callback) { - disconnectCallback = callback; + public DisconnectCallback getDisconnectCallback() { + return disconnectCallback; } ReconnectCallback reconnectCallback; - public ReconnectCallback getReconnectCallback() { - return reconnectCallback; - } - - public void setReconnectCallback(ReconnectCallback callback) { + private void setReconnectCallback(ReconnectCallback callback) { reconnectCallback = callback; } JSONCallback jsonCallback; - public JSONCallback getJSONCallback() { - return jsonCallback; - } - - public void setJSONCallback(JSONCallback callback) { + private void setJSONCallback(JSONCallback callback) { jsonCallback = callback; } StringCallback stringCallback; - public StringCallback getStringCallback() { - return stringCallback; + private void setStringCallback(StringCallback callback) { + stringCallback = callback; } - public void setStringCallback(StringCallback callback) { - stringCallback = callback; + public void setSocketIOCallbacks(SocketIOCallbacks callbacks) { + mSocketIOCallbacks = callbacks; + } + + public SocketIOCallbacks getSocketIOCallbacks() { + return mSocketIOCallbacks; } + public void listenForEvents(List<String> events) { + + if(events == null) { + return; + } + + EventCallback callback = new EventCallback() { + + @Override + public void onEvent(String event, JSONArray argument, Acknowledge acknowledge) { + + if(mSocketIOCallbacks != null) { + mSocketIOCallbacks.on(event, argument); + } + } + }; + + for(String event : events) { + on(event, callback); + } + } + SocketIOConnection connection; String endpoint; @@ -158,12 +240,27 @@ public void disconnect() { disconnectCallback.onDisconnect(null); } } - + public void of(String endpoint, ConnectCallback connectCallback) { connection.connect(new SocketIOClient(connection, endpoint, connectCallback)); } - public WebSocket getWebSocket() { - return connection.webSocket; + public WebSocketClient getWebSocket() { + return connection.webSocketClient; + } + + public static interface SocketIOCallbacks { + + public void on(String event, JSONArray arguments); + + public void onDisconnect(int code, String reason); + + public void onReconnect(); + + public void onJSON(JSONObject json); + + public void onMessage(String message); + + public void onError(Exception error); } } diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java index 53900ad..93aae55 100644 --- a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java +++ b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java @@ -8,9 +8,9 @@ import com.koushikdutta.http.AsyncHttpClient; import com.koushikdutta.http.AsyncHttpClient.SocketIORequest; -import com.koushikdutta.http.WebSocket; -import com.koushikdutta.http.WebSocket.ClosedCallback; -import com.koushikdutta.http.WebSocket.DataCallback; +import com.koushikdutta.http.WebSocketClient; +import com.koushikdutta.http.WebSocketClient.ClosedCallback; +import com.koushikdutta.http.WebSocketClient.DataCallback; import java.util.ArrayList; import java.util.Arrays; @@ -26,7 +26,7 @@ class SocketIOConnection { AsyncHttpClient httpClient; int heartbeat; ArrayList<SocketIOClient> clients = new ArrayList<SocketIOClient>(); - WebSocket webSocket; + WebSocketClient webSocketClient; SocketIORequest request; public SocketIOConnection(Handler handler, AsyncHttpClient httpClient, @@ -37,7 +37,7 @@ public SocketIOConnection(Handler handler, AsyncHttpClient httpClient, } public boolean isConnected() { - return webSocket != null && webSocket.isOpen(); + return webSocketClient != null && webSocketClient.isOpen(); } Hashtable<String, Acknowledge> acknowledges = new Hashtable<String, Acknowledge>(); @@ -50,12 +50,12 @@ public void emitRaw(int type, SocketIOClient client, String message, Acknowledge ack = id + "+"; acknowledges.put(id, acknowledge); } - webSocket.send(String.format("%d:%s:%s:%s", type, ack, client.endpoint, message)); + webSocketClient.send(String.format("%d:%s:%s:%s", type, ack, client.endpoint, message)); } public void connect(SocketIOClient client) { clients.add(client); - webSocket.send(String.format("1::%s", client.endpoint)); + webSocketClient.send(String.format("1::%s", client.endpoint)); } public void disconnect(SocketIOClient client) { @@ -75,16 +75,16 @@ public void disconnect(SocketIOClient client) { } if (needsEndpointDisconnect) - webSocket.send(String.format("0::%s", client.endpoint)); + webSocketClient.send(String.format("0::%s", client.endpoint)); // and see if we can disconnect the socket completely if (clients.size() > 0) return; - webSocket.setStringCallback(null); - webSocket.setClosedCallback(null); - webSocket.disconnect(); - webSocket = null; + webSocketClient.setStringCallback(null); + webSocketClient.setClosedCallback(null); + webSocketClient.disconnect(); + webSocketClient = null; } void reconnect() { @@ -120,14 +120,14 @@ public void onCompleted(final Exception e, String result) { httpClient.websocket(sessionUrl, null, new AsyncHttpClient.WebSocketConnectCallback() { @Override - public void onCompleted(Exception ex, WebSocket webSocket) { + public void onCompleted(Exception ex, WebSocketClient webSocket) { if (ex != null) { reportDisconnect(ex); return; } reconnectDelay = 1000L; - SocketIOConnection.this.webSocket = webSocket; + SocketIOConnection.this.webSocketClient = webSocket; attach(); } }); @@ -141,14 +141,14 @@ public void onCompleted(Exception ex, WebSocket webSocket) { } void setupHeartbeat() { - final WebSocket ws = webSocket; + final WebSocketClient ws = webSocketClient; Runnable heartbeatRunner = new Runnable() { @Override public void run() { - if (heartbeat <= 0 || ws != webSocket || ws == null + if (heartbeat <= 0 || ws != webSocketClient || ws == null || !ws.isOpen()) return; - webSocket.send("2:::"); + webSocketClient.send("2:::"); mHandler.postDelayed(this, heartbeat); } @@ -169,7 +169,7 @@ private void select(String endpoint, SelectCallback callback) { } private void delayReconnect() { - if (webSocket != null || clients.size() == 0) + if (webSocketClient != null || clients.size() == 0) return; // see if any client has disconnected, @@ -296,7 +296,7 @@ public void acknowledge(JSONArray arguments) { String data = ""; if (arguments != null) data += "+" + arguments.toString(); - webSocket.send(String.format("6:::%s%s", messageId, data)); + webSocketClient.send(String.format("6:::%s%s", messageId, data)); } }; } @@ -304,23 +304,23 @@ public void acknowledge(JSONArray arguments) { private void attach() { setupHeartbeat(); - webSocket.setDataCallback(new DataCallback() { + webSocketClient.setDataCallback(new DataCallback() { @Override public void onDataAvailable(byte[] data) { // Do nothing } }); - webSocket.setClosedCallback(new ClosedCallback() { + webSocketClient.setClosedCallback(new ClosedCallback() { @Override public void onCompleted(final Exception ex) { - webSocket = null; + webSocketClient = null; reportDisconnect(ex); } }); - webSocket.setStringCallback(new WebSocket.StringCallback() { + webSocketClient.setStringCallback(new WebSocketClient.StringCallback() { @Override public void onStringAvailable(String message) { try { @@ -330,7 +330,7 @@ public void onStringAvailable(String message) { switch (code) { case 0: // disconnect - webSocket.disconnect(); + webSocketClient.disconnect(); reportDisconnect(null); break; case 1: @@ -339,7 +339,7 @@ public void onStringAvailable(String message) { break; case 2: // heartbeat - webSocket.send("2::"); + webSocketClient.send("2::"); break; case 3: { // message @@ -383,14 +383,14 @@ public void onStringAvailable(String message) { throw new Exception("unknown code"); } } catch (Exception ex) { - webSocket.setClosedCallback(null); - webSocket.disconnect(); - webSocket = null; + webSocketClient.setClosedCallback(null); + webSocketClient.disconnect(); + webSocketClient = null; reportDisconnect(ex); } } }); - webSocket.startParsing(); + webSocketClient.startParsing(); } } diff --git a/src/com/koushikdutta/async/util/HashList.java b/src/com/koushikdutta/async/util/HashList.java index 2b2a0c6..0036661 100644 --- a/src/com/koushikdutta/async/util/HashList.java +++ b/src/com/koushikdutta/async/util/HashList.java @@ -7,6 +7,9 @@ * Created by koush on 5/27/13. */ public class HashList<T> extends Hashtable<String, ArrayList<T>> { + + private static final long serialVersionUID = 1L; + public HashList() { } diff --git a/src/com/koushikdutta/http/AsyncHttpClient.java b/src/com/koushikdutta/http/AsyncHttpClient.java index 434ff51..a09c340 100644 --- a/src/com/koushikdutta/http/AsyncHttpClient.java +++ b/src/com/koushikdutta/http/AsyncHttpClient.java @@ -13,6 +13,10 @@ import android.net.http.AndroidHttpClient; import android.os.AsyncTask; +/** + * + * Created by Vinay S Shenoy on 07/09/2013 + */ public class AsyncHttpClient { public AsyncHttpClient() { @@ -50,7 +54,7 @@ public static interface StringCallback { } public static interface WebSocketConnectCallback { - public void onCompleted(Exception ex, WebSocket webSocket); + public void onCompleted(Exception ex, WebSocketClient webSocket); } public void executeString(final SocketIORequest socketIORequest, final StringCallback stringCallback) { @@ -103,10 +107,10 @@ private String readToEnd(InputStream input) throws IOException { public void websocket(final String uri, final String protocol, final WebSocketConnectCallback callback) { - WebSocket.create(URI.create(uri), new WebSocket.Listener() { + WebSocketClient.create(URI.create(uri), new WebSocketClient.Listener() { @Override - public void onConnect(WebSocket websocket) { + public void onConnect(WebSocketClient websocket) { if (callback != null) { callback.onCompleted(null, websocket); } diff --git a/src/com/koushikdutta/http/HybiParser.java b/src/com/koushikdutta/http/HybiParser.java index 6f2f609..56cfd1b 100644 --- a/src/com/koushikdutta/http/HybiParser.java +++ b/src/com/koushikdutta/http/HybiParser.java @@ -43,7 +43,7 @@ public class HybiParser { private static final String TAG = "HybiParser"; - private WebSocket mWebSocket; + private WebSocketClient mWebSocket; private boolean mMasking = true; @@ -95,7 +95,7 @@ public class HybiParser { OP_CONTINUATION, OP_TEXT, OP_BINARY ); - public HybiParser(WebSocket client) { + public HybiParser(WebSocketClient client) { mWebSocket = client; } @@ -375,6 +375,9 @@ private byte[] slice(byte[] array, int start) { } public static class ProtocolError extends IOException { + + private static final long serialVersionUID = 1L; + public ProtocolError(String detailMessage) { super(detailMessage); } diff --git a/src/com/koushikdutta/http/WebSocket.java b/src/com/koushikdutta/http/WebSocketClient.java similarity index 96% rename from src/com/koushikdutta/http/WebSocket.java rename to src/com/koushikdutta/http/WebSocketClient.java index f6fd3f1..dcd6ad5 100644 --- a/src/com/koushikdutta/http/WebSocket.java +++ b/src/com/koushikdutta/http/WebSocketClient.java @@ -33,8 +33,8 @@ import android.util.Base64; import android.util.Log; -public class WebSocket { - private static final String TAG = "WebSocket"; +public class WebSocketClient { + private static final String TAG = "WebSocketClient"; private URI mURI; private Listener mListener; @@ -60,7 +60,7 @@ public static void setTrustManagers(TrustManager[] tm) { sTrustManagers = tm; } - public WebSocket(URI uri, Listener listener, + public WebSocketClient(URI uri, Listener listener, List<BasicNameValuePair> extraHeaders) { mURI = uri; mListener = listener; @@ -140,7 +140,7 @@ public void run() { } } - mListener.onConnect(WebSocket.this); + mListener.onConnect(WebSocketClient.this); mConnected = true; @@ -262,7 +262,7 @@ public void run() { } public static interface Listener { - public void onConnect(WebSocket webSocket); + public void onConnect(WebSocketClient webSocket); public void onError(Exception ex); @@ -311,7 +311,7 @@ private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, } public static void create(URI uri, Listener listener) { - WebSocket webSocket = new WebSocket(uri, listener, null); + WebSocketClient webSocket = new WebSocketClient(uri, listener, null); webSocket.connect(); } } From 58c1f5ec31ca6fae773389a6f2e0994ace9d043a Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Thu, 11 Jul 2013 23:34:30 +0530 Subject: [PATCH 47/56] Removed Handler interface, Invoking callbacks on the passed Handler --- .../async/http/socketio/SocketIOClient.java | 150 ++++-------------- .../http/socketio/SocketIOConnection.java | 92 ++++++++--- 2 files changed, 107 insertions(+), 135 deletions(-) diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java index d678a61..58f2197 100644 --- a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java +++ b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java @@ -1,7 +1,5 @@ package com.koushikdutta.async.http.socketio; -import java.util.List; - import org.json.JSONArray; import org.json.JSONObject; @@ -13,10 +11,10 @@ import com.koushikdutta.http.WebSocketClient; public class SocketIOClient extends EventEmitter { + boolean connected; boolean disconnected; - - private SocketIOCallbacks mSocketIOCallbacks; + Handler handler; private void emitRaw(int type, String message, Acknowledge acknowledge) { connection.emitRaw(type, this, message, acknowledge); @@ -66,8 +64,9 @@ public static void connect(final SocketIORequest request, final ConnectCallback @Override public void onConnectCompleted(Exception ex, SocketIOClient client) { if (ex != null || TextUtils.isEmpty(request.getEndpoint())) { + + client.handler = handler; if (callback != null) { - client.setupCallbacks(); callback.onConnectCompleted(ex, client); } @@ -82,7 +81,6 @@ public void onConnectCompleted(Exception ex, SocketIOClient client) { @Override public void onConnectCompleted(Exception ex, SocketIOClient client) { if (callback != null) { - client.setupCallbacks(); callback.onConnectCompleted(ex, client); } } @@ -94,76 +92,20 @@ public void onConnectCompleted(Exception ex, SocketIOClient client) { connection.reconnect(); } - - private void setupCallbacks() { - setStringCallback(new StringCallback() { - - @Override - public void onString(String string, Acknowledge acknowledge) { - - if (mSocketIOCallbacks != null) { - mSocketIOCallbacks.onMessage(string); - } - } - }); - - setJSONCallback(new JSONCallback() { - - @Override - public void onJSON(JSONObject json, Acknowledge acknowledge) { - - if (mSocketIOCallbacks != null) { - mSocketIOCallbacks.onJSON(json); - } - - } - }); - - setReconnectCallback(new ReconnectCallback() { - - @Override - public void onReconnect() { - - if (mSocketIOCallbacks != null) { - mSocketIOCallbacks.onReconnect(); - } - - } - }); - - setDisconnectCallback(new DisconnectCallback() { - - @Override - public void onDisconnect(Exception e) { - - if (mSocketIOCallbacks != null) { - mSocketIOCallbacks.onDisconnect(0, e.getMessage()); - } - - } - }); - - setErrorCallback(new ErrorCallback() { - - @Override - public void onError(String error) { - if (mSocketIOCallbacks != null) { - mSocketIOCallbacks.onError(new Exception(error)); - } - - } - }); - } ErrorCallback errorCallback; - private void setErrorCallback(ErrorCallback callback) { + public void setErrorCallback(ErrorCallback callback) { errorCallback = callback; } + public ErrorCallback getErrorCallback() { + return errorCallback; + } + DisconnectCallback disconnectCallback; - private void setDisconnectCallback(DisconnectCallback callback) { + public void setDisconnectCallback(DisconnectCallback callback) { disconnectCallback = callback; } @@ -173,50 +115,32 @@ public DisconnectCallback getDisconnectCallback() { ReconnectCallback reconnectCallback; - private void setReconnectCallback(ReconnectCallback callback) { + public void setReconnectCallback(ReconnectCallback callback) { reconnectCallback = callback; } + public ReconnectCallback getReconnectCallback() { + return reconnectCallback; + } + JSONCallback jsonCallback; - private void setJSONCallback(JSONCallback callback) { + public void setJSONCallback(JSONCallback callback) { jsonCallback = callback; } + public JSONCallback getJSONCallback() { + return jsonCallback; + } + StringCallback stringCallback; - private void setStringCallback(StringCallback callback) { + public void setStringCallback(StringCallback callback) { stringCallback = callback; } - public void setSocketIOCallbacks(SocketIOCallbacks callbacks) { - mSocketIOCallbacks = callbacks; - } - - public SocketIOCallbacks getSocketIOCallbacks() { - return mSocketIOCallbacks; - } - - public void listenForEvents(List<String> events) { - - if(events == null) { - return; - } - - EventCallback callback = new EventCallback() { - - @Override - public void onEvent(String event, JSONArray argument, Acknowledge acknowledge) { - - if(mSocketIOCallbacks != null) { - mSocketIOCallbacks.on(event, argument); - } - } - }; - - for(String event : events) { - on(event, callback); - } + public StringCallback getStringCallback() { + return stringCallback; } SocketIOConnection connection; @@ -235,12 +159,20 @@ public boolean isConnected() { public void disconnect() { connection.disconnect(this); - DisconnectCallback disconnectCallback = this.disconnectCallback; + final DisconnectCallback disconnectCallback = this.disconnectCallback; if (disconnectCallback != null) { - disconnectCallback.onDisconnect(null); + handler.post(new Runnable() { + + @Override + public void run() { + disconnectCallback.onDisconnect(null); + + } + }); + } } - + public void of(String endpoint, ConnectCallback connectCallback) { connection.connect(new SocketIOClient(connection, endpoint, connectCallback)); } @@ -249,18 +181,4 @@ public WebSocketClient getWebSocket() { return connection.webSocketClient; } - public static interface SocketIOCallbacks { - - public void on(String event, JSONArray arguments); - - public void onDisconnect(int code, String reason); - - public void onReconnect(); - - public void onJSON(JSONObject json); - - public void onMessage(String message); - - public void onError(Exception error); - } } diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java index 93aae55..ea71f5d 100644 --- a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java +++ b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java @@ -199,18 +199,36 @@ public void run() { private void reportDisconnect(final Exception ex) { select(null, new SelectCallback() { @Override - public void onSelect(SocketIOClient client) { + public void onSelect(final SocketIOClient client) { if (client.connected) { client.disconnected = true; - DisconnectCallback closed = client.getDisconnectCallback(); - if (closed != null) - closed.onDisconnect(ex); + final DisconnectCallback closed = client.getDisconnectCallback(); + if (closed != null) { + mHandler.post(new Runnable() { + + @Override + public void run() { + closed.onDisconnect(ex); + + } + }); + + } } else { // client has never connected, this is a initial connect // failure - ConnectCallback callback = client.connectCallback; - if (callback != null) - callback.onConnectCompleted(ex, client); + final ConnectCallback callback = client.connectCallback; + if (callback != null) { + mHandler.post(new Runnable() { + + @Override + public void run() { + callback.onConnectCompleted(ex, client); + + } + }); + + } } } }); @@ -248,9 +266,18 @@ private void reportJson(String endpoint, final JSONObject jsonMessage, final Ack select(endpoint, new SelectCallback() { @Override public void onSelect(SocketIOClient client) { - JSONCallback callback = client.jsonCallback; - if (callback != null) - callback.onJSON(jsonMessage, acknowledge); + final JSONCallback callback = client.jsonCallback; + if (callback != null) { + mHandler.post(new Runnable() { + + @Override + public void run() { + callback.onJSON(jsonMessage, acknowledge); + + } + }); + } + } }); } @@ -259,9 +286,18 @@ private void reportString(String endpoint, final String string, final Acknowledg select(endpoint, new SelectCallback() { @Override public void onSelect(SocketIOClient client) { - StringCallback callback = client.stringCallback; - if (callback != null) - callback.onString(string, acknowledge); + final StringCallback callback = client.stringCallback; + if (callback != null) { + mHandler.post(new Runnable() { + + @Override + public void run() { + callback.onString(string, acknowledge); + + } + }); + + } } }); } @@ -269,8 +305,16 @@ public void onSelect(SocketIOClient client) { private void reportEvent(String endpoint, final String event, final JSONArray arguments, final Acknowledge acknowledge) { select(endpoint, new SelectCallback() { @Override - public void onSelect(SocketIOClient client) { - client.onEvent(event, arguments, acknowledge); + public void onSelect(final SocketIOClient client) { + mHandler.post(new Runnable() { + + @Override + public void run() { + client.onEvent(event, arguments, acknowledge); + + } + }); + } }); } @@ -279,9 +323,19 @@ private void reportError(String endpoint, final String error) { select(endpoint, new SelectCallback() { @Override public void onSelect(SocketIOClient client) { - ErrorCallback callback = client.errorCallback; - if (callback != null) - callback.onError(error); + final ErrorCallback callback = client.errorCallback; + if (callback != null) { + + mHandler.post(new Runnable() { + + @Override + public void run() { + callback.onError(error); + + } + }); + + } } }); } @@ -390,7 +444,7 @@ public void onStringAvailable(String message) { } } }); - + webSocketClient.startParsing(); } } From e61b05e986e1b681bfe2e50e792b72f37f729164 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Fri, 12 Jul 2013 00:43:03 +0530 Subject: [PATCH 48/56] Repackaged HybiParser and WebSocketClient --- .../android_websockets}/HybiParser.java | 2 +- .../android_websockets}/WebSocketClient.java | 19 +++++++------------ .../async/http/socketio/SocketIOClient.java | 2 +- .../http/socketio/SocketIOConnection.java | 6 +++--- .../koushikdutta/http/AsyncHttpClient.java | 2 ++ 5 files changed, 14 insertions(+), 17 deletions(-) rename src/com/{koushikdutta/http => codebutler/android_websockets}/HybiParser.java (99%) rename src/com/{koushikdutta/http => codebutler/android_websockets}/WebSocketClient.java (95%) diff --git a/src/com/koushikdutta/http/HybiParser.java b/src/com/codebutler/android_websockets/HybiParser.java similarity index 99% rename from src/com/koushikdutta/http/HybiParser.java rename to src/com/codebutler/android_websockets/HybiParser.java index 56cfd1b..5aa1ed5 100644 --- a/src/com/koushikdutta/http/HybiParser.java +++ b/src/com/codebutler/android_websockets/HybiParser.java @@ -28,7 +28,7 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -package com.koushikdutta.http; +package com.codebutler.android_websockets; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; diff --git a/src/com/koushikdutta/http/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java similarity index 95% rename from src/com/koushikdutta/http/WebSocketClient.java rename to src/com/codebutler/android_websockets/WebSocketClient.java index dcd6ad5..a42f2e9 100644 --- a/src/com/koushikdutta/http/WebSocketClient.java +++ b/src/com/codebutler/android_websockets/WebSocketClient.java @@ -1,4 +1,4 @@ -package com.koushikdutta.http; +package com.codebutler.android_websockets; import java.io.EOFException; import java.io.IOException; @@ -25,7 +25,7 @@ import org.apache.http.message.BasicLineParser; import org.apache.http.message.BasicNameValuePair; -import com.koushikdutta.http.HybiParser.HappyDataInputStream; +import com.codebutler.android_websockets.HybiParser.HappyDataInputStream; import android.os.Handler; import android.os.HandlerThread; @@ -87,22 +87,17 @@ public void connect() { @Override public void run() { try { - int port = (mURI.getPort() != -1) ? mURI.getPort() - : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 - : 80); + int port = (mURI.getPort() != -1) ? mURI.getPort() : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 : 80); - String path = TextUtils.isEmpty(mURI.getPath()) ? "/" - : mURI.getPath(); + String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath(); if (!TextUtils.isEmpty(mURI.getQuery())) { path += "?" + mURI.getQuery(); } - String originScheme = mURI.getScheme().equals("wss") ? "https" - : "http"; + String originScheme = mURI.getScheme().equals("wss") ? "https" : "http"; URI origin = new URI(originScheme, "//" + mURI.getHost(), null); - SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() - : SocketFactory.getDefault(); + SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault(); mSocket = factory.createSocket(mURI.getHost(), port); PrintWriter out = new PrintWriter(mSocket.getOutputStream()); @@ -164,7 +159,7 @@ public void run() { } public void startParsing() { - + // Now decode websocket frames. try { mParser.start(stream); diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java index 58f2197..af704fe 100644 --- a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java +++ b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java @@ -6,9 +6,9 @@ import android.os.Handler; import android.text.TextUtils; +import com.codebutler.android_websockets.WebSocketClient; import com.koushikdutta.http.AsyncHttpClient; import com.koushikdutta.http.AsyncHttpClient.SocketIORequest; -import com.koushikdutta.http.WebSocketClient; public class SocketIOClient extends EventEmitter { diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java index ea71f5d..b328717 100644 --- a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java +++ b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java @@ -6,11 +6,11 @@ import org.json.JSONArray; import org.json.JSONObject; +import com.codebutler.android_websockets.WebSocketClient; +import com.codebutler.android_websockets.WebSocketClient.ClosedCallback; +import com.codebutler.android_websockets.WebSocketClient.DataCallback; import com.koushikdutta.http.AsyncHttpClient; import com.koushikdutta.http.AsyncHttpClient.SocketIORequest; -import com.koushikdutta.http.WebSocketClient; -import com.koushikdutta.http.WebSocketClient.ClosedCallback; -import com.koushikdutta.http.WebSocketClient.DataCallback; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/com/koushikdutta/http/AsyncHttpClient.java b/src/com/koushikdutta/http/AsyncHttpClient.java index a09c340..4d9f02e 100644 --- a/src/com/koushikdutta/http/AsyncHttpClient.java +++ b/src/com/koushikdutta/http/AsyncHttpClient.java @@ -9,6 +9,8 @@ import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; +import com.codebutler.android_websockets.WebSocketClient; + import android.net.Uri; import android.net.http.AndroidHttpClient; import android.os.AsyncTask; From 7215aea58eaad6c47a90e2654b18ed37dba653b6 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinay@notarealid.com> Date: Fri, 12 Jul 2013 22:22:41 +0530 Subject: [PATCH 49/56] Switched back to original WebsocketClient and HybiParser --- .../android_websockets/HybiParser.java | 37 ++- .../android_websockets/WebSocketClient.java | 149 ++++-------- .../http/socketio/SocketIOConnection.java | 219 +++++++++--------- .../koushikdutta/http/AsyncHttpClient.java | 27 +-- 4 files changed, 166 insertions(+), 266 deletions(-) diff --git a/src/com/codebutler/android_websockets/HybiParser.java b/src/com/codebutler/android_websockets/HybiParser.java index 5aa1ed5..e13eb8d 100644 --- a/src/com/codebutler/android_websockets/HybiParser.java +++ b/src/com/codebutler/android_websockets/HybiParser.java @@ -30,20 +30,16 @@ package com.codebutler.android_websockets; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; +import android.util.Log; + +import java.io.*; import java.util.Arrays; import java.util.List; -import android.util.Log; - public class HybiParser { private static final String TAG = "HybiParser"; - private WebSocketClient mWebSocket; + private WebSocketClient mClient; private boolean mMasking = true; @@ -96,7 +92,7 @@ public class HybiParser { ); public HybiParser(WebSocketClient client) { - mWebSocket = client; + mClient = client; } private static byte[] mask(byte[] payload, byte[] mask, int offset) { @@ -132,7 +128,7 @@ public void start(HappyDataInputStream stream) throws IOException { break; } } - mWebSocket.getClosedCallback().onCompleted(new IOException("EOF")); + mClient.getListener().onDisconnect(0, "EOF"); } private void parseOpcode(byte data) throws ProtocolError { @@ -245,12 +241,12 @@ private byte[] frame(Object data, int opcode, int errorCode) { } public void ping(String message) { - mWebSocket.send(frame(message, OP_PING, -1)); + mClient.send(frame(message, OP_PING, -1)); } public void close(int code, String reason) { if (mClosed) return; - mWebSocket.send(frame(reason, OP_CLOSE, code)); + mClient.send(frame(reason, OP_CLOSE, code)); mClosed = true; } @@ -266,9 +262,9 @@ private void emitFrame() throws IOException { if (mFinal) { byte[] message = mBuffer.toByteArray(); if (mMode == MODE_TEXT) { - mWebSocket.getStringCallback().onStringAvailable(encode(message)); + mClient.getListener().onMessage(encode(message)); } else { - mWebSocket.getDataCallback().onDataAvailable(message); + mClient.getListener().onMessage(message); } reset(); } @@ -276,7 +272,7 @@ private void emitFrame() throws IOException { } else if (opcode == OP_TEXT) { if (mFinal) { String messageText = encode(payload); - mWebSocket.getStringCallback().onStringAvailable(messageText); + mClient.getListener().onMessage(messageText); } else { mMode = MODE_TEXT; mBuffer.write(payload); @@ -284,7 +280,7 @@ private void emitFrame() throws IOException { } else if (opcode == OP_BINARY) { if (mFinal) { - mWebSocket.getDataCallback().onDataAvailable(payload); + mClient.getListener().onMessage(payload); } else { mMode = MODE_BINARY; mBuffer.write(payload); @@ -294,12 +290,12 @@ private void emitFrame() throws IOException { int code = (payload.length >= 2) ? 256 * payload[0] + payload[1] : 0; String reason = (payload.length > 2) ? encode(slice(payload, 2)) : null; Log.d(TAG, "Got close op! " + code + " " + reason); - mWebSocket.getClosedCallback().onCompleted(new IOException("Got close op! " + code + " " + reason)); + mClient.getListener().onDisconnect(code, reason); } else if (opcode == OP_PING) { if (payload.length > 125) { throw new ProtocolError("Ping payload too large"); } Log.d(TAG, "Sending pong!!"); - mWebSocket.sendFrame(frame(payload, OP_PONG, -1)); + mClient.sendFrame(frame(payload, OP_PONG, -1)); } else if (opcode == OP_PONG) { String message = encode(payload); @@ -375,9 +371,6 @@ private byte[] slice(byte[] array, int start) { } public static class ProtocolError extends IOException { - - private static final long serialVersionUID = 1L; - public ProtocolError(String detailMessage) { super(detailMessage); } @@ -420,4 +413,4 @@ public byte[] readBytes(int length) throws IOException { return buffer; } } -} +} \ No newline at end of file diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java index a42f2e9..0475d5e 100644 --- a/src/com/codebutler/android_websockets/WebSocketClient.java +++ b/src/com/codebutler/android_websockets/WebSocketClient.java @@ -1,5 +1,20 @@ package com.codebutler.android_websockets; +import android.os.Handler; +import android.os.HandlerThread; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; +import org.apache.http.*; +import org.apache.http.client.HttpResponseException; +import org.apache.http.message.BasicLineParser; +import org.apache.http.message.BasicNameValuePair; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; import java.io.EOFException; import java.io.IOException; import java.io.OutputStream; @@ -10,47 +25,18 @@ import java.security.NoSuchAlgorithmException; import java.util.List; -import javax.net.SocketFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; - -import org.apache.http.Header; -import org.apache.http.HttpException; -import org.apache.http.HttpStatus; -import org.apache.http.NameValuePair; -import org.apache.http.StatusLine; -import org.apache.http.client.HttpResponseException; -import org.apache.http.message.BasicLineParser; -import org.apache.http.message.BasicNameValuePair; - -import com.codebutler.android_websockets.HybiParser.HappyDataInputStream; - -import android.os.Handler; -import android.os.HandlerThread; -import android.text.TextUtils; -import android.util.Base64; -import android.util.Log; - public class WebSocketClient { private static final String TAG = "WebSocketClient"; - private URI mURI; - private Listener mListener; - private Socket mSocket; - private Thread mThread; - private HandlerThread mHandlerThread; - private Handler mHandler; + private URI mURI; + private Listener mListener; + private Socket mSocket; + private Thread mThread; + private HandlerThread mHandlerThread; + private Handler mHandler; private List<BasicNameValuePair> mExtraHeaders; - private HybiParser mParser; - private boolean mConnected; - - private DataCallback mDataCallback; - private StringCallback mStringCallback; - private ClosedCallback mClosedCallback; - - private HappyDataInputStream stream; + private HybiParser mParser; + private boolean mConnected; private final Object mSendLock = new Object(); @@ -60,13 +46,12 @@ public static void setTrustManagers(TrustManager[] tm) { sTrustManagers = tm; } - public WebSocketClient(URI uri, Listener listener, - List<BasicNameValuePair> extraHeaders) { - mURI = uri; - mListener = listener; + public WebSocketClient(URI uri, Listener listener, List<BasicNameValuePair> extraHeaders) { + mURI = uri; + mListener = listener; mExtraHeaders = extraHeaders; - mConnected = false; - mParser = new HybiParser(this); + mConnected = false; + mParser = new HybiParser(this); mHandlerThread = new HandlerThread("websocket-thread"); mHandlerThread.start(); @@ -83,7 +68,6 @@ public void connect() { } mThread = new Thread(new Runnable() { - @Override public void run() { try { @@ -116,7 +100,7 @@ public void run() { out.print("\r\n"); out.flush(); - stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream()); + HybiParser.HappyDataInputStream stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream()); // Read HTTP response status line. StatusLine statusLine = parseStatusLine(readLine(stream)); @@ -135,19 +119,22 @@ public void run() { } } - mListener.onConnect(WebSocketClient.this); + mListener.onConnect(); mConnected = true; + // Now decode websocket frames. + mParser.start(stream); + } catch (EOFException ex) { Log.d(TAG, "WebSocket EOF!", ex); - mListener.onError(ex); + mListener.onDisconnect(0, "EOF"); mConnected = false; } catch (SSLException ex) { // Connection reset by peer Log.d(TAG, "Websocket SSL error!", ex); - mListener.onError(ex); + mListener.onDisconnect(0, "SSL"); mConnected = false; } catch (Exception ex) { @@ -158,16 +145,6 @@ public void run() { mThread.start(); } - public void startParsing() { - - // Now decode websocket frames. - try { - mParser.start(stream); - } catch (IOException e) { - mClosedCallback.onCompleted(e); - } - } - public void disconnect() { if (mSocket != null) { mHandler.post(new Runnable() { @@ -196,7 +173,7 @@ public void send(byte[] data) { sendFrame(mParser.frame(data)); } - public boolean isOpen() { + public boolean isConnected() { return mConnected; } @@ -256,47 +233,12 @@ public void run() { }); } - public static interface Listener { - public void onConnect(WebSocketClient webSocket); - - public void onError(Exception ex); - - } - - public static interface DataCallback { - public void onDataAvailable(byte[] data); - } - - public static interface StringCallback { - public void onStringAvailable(String message); - } - - public static interface ClosedCallback { - public void onCompleted(Exception ex); - } - - public DataCallback getDataCallback() { - return mDataCallback; - } - - public void setDataCallback(DataCallback dataCallback) { - this.mDataCallback = dataCallback; - } - - public StringCallback getStringCallback() { - return mStringCallback; - } - - public void setStringCallback(StringCallback stringCallback) { - this.mStringCallback = stringCallback; - } - - public ClosedCallback getClosedCallback() { - return mClosedCallback; - } - - public void setClosedCallback(ClosedCallback closedCallback) { - this.mClosedCallback = closedCallback; + public interface Listener { + public void onConnect(); + public void onMessage(String message); + public void onMessage(byte[] data); + public void onDisconnect(int code, String reason); + public void onError(Exception error); } private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException { @@ -304,9 +246,4 @@ private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, context.init(null, sTrustManagers, null); return context.getSocketFactory(); } - - public static void create(URI uri, Listener listener) { - WebSocketClient webSocket = new WebSocketClient(uri, listener, null); - webSocket.connect(); - } -} +} \ No newline at end of file diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java index b328717..669a288 100644 --- a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java +++ b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java @@ -1,22 +1,23 @@ package com.koushikdutta.async.http.socketio; -import android.os.Handler; -import android.text.TextUtils; +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Hashtable; import org.json.JSONArray; import org.json.JSONObject; +import android.os.Handler; +import android.text.TextUtils; + import com.codebutler.android_websockets.WebSocketClient; -import com.codebutler.android_websockets.WebSocketClient.ClosedCallback; -import com.codebutler.android_websockets.WebSocketClient.DataCallback; +import com.codebutler.android_websockets.WebSocketClient.Listener; import com.koushikdutta.http.AsyncHttpClient; import com.koushikdutta.http.AsyncHttpClient.SocketIORequest; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Hashtable; - /** * Created by koush on 7/1/13. */ @@ -37,7 +38,7 @@ public SocketIOConnection(Handler handler, AsyncHttpClient httpClient, } public boolean isConnected() { - return webSocketClient != null && webSocketClient.isOpen(); + return webSocketClient != null && webSocketClient.isConnected(); } Hashtable<String, Acknowledge> acknowledges = new Hashtable<String, Acknowledge>(); @@ -81,8 +82,6 @@ public void disconnect(SocketIOClient client) { if (clients.size() > 0) return; - webSocketClient.setStringCallback(null); - webSocketClient.setClosedCallback(null); webSocketClient.disconnect(); webSocketClient = null; } @@ -117,20 +116,104 @@ public void onCompleted(final Exception e, String result) { final String sessionUrl = request.getUri().toString() + "websocket/" + session + "/"; - - httpClient.websocket(sessionUrl, null, new AsyncHttpClient.WebSocketConnectCallback() { + + SocketIOConnection.this.webSocketClient = new WebSocketClient(URI.create(sessionUrl), new Listener() { + @Override - public void onCompleted(Exception ex, WebSocketClient webSocket) { - if (ex != null) { + public void onMessage(byte[] data) { + //Do nothing + + } + + @Override + public void onMessage(String message) { + try { + // Log.d(TAG, "Message: " + message); + String[] parts = message.split(":", 4); + int code = Integer.parseInt(parts[0]); + switch (code) { + case 0: + // disconnect + webSocketClient.disconnect(); + reportDisconnect(null); + break; + case 1: + // connect + reportConnect(parts[2]); + break; + case 2: + // heartbeat + webSocketClient.send("2::"); + break; + case 3: { + // message + reportString(parts[2], parts[3], acknowledge(parts[1])); + break; + } + case 4: { + // json message + final String dataString = parts[3]; + final JSONObject jsonMessage = new JSONObject(dataString); + reportJson(parts[2], jsonMessage, acknowledge(parts[1])); + break; + } + case 5: { + final String dataString = parts[3]; + final JSONObject data = new JSONObject(dataString); + final String event = data.getString("name"); + final JSONArray args = data.optJSONArray("args"); + reportEvent(parts[2], event, args, acknowledge(parts[1])); + break; + } + case 6: + // ACK + final String[] ackParts = parts[3].split("\\+", 2); + Acknowledge ack = acknowledges.remove(ackParts[0]); + if (ack == null) + return; + JSONArray arguments = null; + if (ackParts.length == 2) + arguments = new JSONArray(ackParts[1]); + ack.acknowledge(arguments); + break; + case 7: + // error + reportError(parts[2], parts[3]); + break; + case 8: + // noop + break; + default: + throw new Exception("unknown code"); + } + } catch (Exception ex) { + webSocketClient.disconnect(); + webSocketClient = null; reportDisconnect(ex); - return; } - + + + } + + @Override + public void onError(Exception error) { + reportDisconnect(error); + } + + @Override + public void onDisconnect(int code, String reason) { + + reportDisconnect(new IOException(String.format("Disconnected code %d for reason %s", code, reason))); + } + + @Override + public void onConnect() { reconnectDelay = 1000L; - SocketIOConnection.this.webSocketClient = webSocket; - attach(); + setupHeartbeat(); + } - }); + }, null); + SocketIOConnection.this.webSocketClient.connect(); } catch (Exception ex) { reportDisconnect(ex); @@ -146,7 +229,7 @@ void setupHeartbeat() { @Override public void run() { if (heartbeat <= 0 || ws != webSocketClient || ws == null - || !ws.isOpen()) + || !ws.isConnected()) return; webSocketClient.send("2:::"); @@ -355,96 +438,6 @@ public void acknowledge(JSONArray arguments) { }; } - private void attach() { - setupHeartbeat(); - - webSocketClient.setDataCallback(new DataCallback() { - - @Override - public void onDataAvailable(byte[] data) { - // Do nothing - } - }); - webSocketClient.setClosedCallback(new ClosedCallback() { - - @Override - public void onCompleted(final Exception ex) { - webSocketClient = null; - reportDisconnect(ex); - } - }); + - webSocketClient.setStringCallback(new WebSocketClient.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: - // disconnect - webSocketClient.disconnect(); - reportDisconnect(null); - break; - case 1: - // connect - reportConnect(parts[2]); - break; - case 2: - // heartbeat - webSocketClient.send("2::"); - break; - case 3: { - // message - reportString(parts[2], parts[3], acknowledge(parts[1])); - break; - } - case 4: { - // json message - final String dataString = parts[3]; - final JSONObject jsonMessage = new JSONObject(dataString); - reportJson(parts[2], jsonMessage, acknowledge(parts[1])); - break; - } - case 5: { - final String dataString = parts[3]; - final JSONObject data = new JSONObject(dataString); - final String event = data.getString("name"); - final JSONArray args = data.optJSONArray("args"); - reportEvent(parts[2], event, args, acknowledge(parts[1])); - break; - } - case 6: - // ACK - final String[] ackParts = parts[3].split("\\+", 2); - Acknowledge ack = acknowledges.remove(ackParts[0]); - if (ack == null) - return; - JSONArray arguments = null; - if (ackParts.length == 2) - arguments = new JSONArray(ackParts[1]); - ack.acknowledge(arguments); - break; - case 7: - // error - reportError(parts[2], parts[3]); - break; - case 8: - // noop - break; - default: - throw new Exception("unknown code"); - } - } catch (Exception ex) { - webSocketClient.setClosedCallback(null); - webSocketClient.disconnect(); - webSocketClient = null; - reportDisconnect(ex); - } - } - }); - - webSocketClient.startParsing(); - } } diff --git a/src/com/koushikdutta/http/AsyncHttpClient.java b/src/com/koushikdutta/http/AsyncHttpClient.java index 4d9f02e..b8a15d0 100644 --- a/src/com/koushikdutta/http/AsyncHttpClient.java +++ b/src/com/koushikdutta/http/AsyncHttpClient.java @@ -4,17 +4,16 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; -import java.net.URI; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; -import com.codebutler.android_websockets.WebSocketClient; - import android.net.Uri; import android.net.http.AndroidHttpClient; import android.os.AsyncTask; +import com.codebutler.android_websockets.WebSocketClient; + /** * * Created by Vinay S Shenoy on 07/09/2013 @@ -107,26 +106,4 @@ private String readToEnd(InputStream input) throws IOException { return new String(readToEndAsArray(input)); } - public void websocket(final String uri, final String protocol, final WebSocketConnectCallback callback) { - - WebSocketClient.create(URI.create(uri), new WebSocketClient.Listener() { - - @Override - public void onConnect(WebSocketClient websocket) { - if (callback != null) { - callback.onCompleted(null, websocket); - } - - } - - @Override - public void onError(Exception ex) { - if (callback != null) { - callback.onCompleted(ex, null); - } - - } - }); - } - } From 29e00424461b5238a306c2d56e399b72451a3004 Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinaysshenoy@gmail.com> Date: Fri, 12 Jul 2013 22:47:51 +0530 Subject: [PATCH 50/56] Update README.md --- README.md | 129 +++++++++++++++++++++++++++++------------------------- 1 file changed, 70 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 63b1a0d..9005dcd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # WebSocket and Socket.IO client for Android -A very simple bare-minimum WebSocket and Socket.IO client for Android. - ## Credits The hybi parser is based on code from the [faye project](https://github.com/faye/faye-websocket-node). Faye is Copyright (c) 2009-2012 James Coglan. Many thanks for the great open-source library! @@ -12,6 +10,8 @@ The WebSocket client was written by [Eric Butler](https://twitter.com/codebutler The Socket.IO client was written by [Koushik Dutta](https://twitter.com/koush). +The Socket.IO client component was ported from Koushik Dutta's AndroidAsync(https://github.com/koush/AndroidAsync) by [Vinay S Shenoy](https://twitter.com/vinaysshenoy) + ## WebSocket Usage ```java @@ -58,75 +58,86 @@ client.disconnect(); ## Socket.IO Usage ```java -SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new SocketIOClient.Handler() { - @Override - public void onConnect() { - Log.d(TAG, "Connected!"); - } +SocketIOClient.connect("http://localhost:80", new ConnectCallback() { @Override - public void on(String event, JSONArray arguments) { - Log.d(TAG, String.format("Got event %s: %s", event, arguments.toString())); + public void onConnectCompleted(Exception ex, SocketIOClient client) { + + if (ex != null) { + return; + } + + //Save the returned SocketIOClient instance into a variable so you can disconnect it later + client.setDisconnectCallback(MainActivity.this); + client.setErrorCallback(MainActivity.this); + client.setJSONCallback(MainActivity.this); + client.setStringCallback(MainActivity.this); + client.addListener("news", MainActivity.this); + + client.of("/chat", new ConnectCallback() { + + @Override + public void onConnectCompleted(Exception ex, SocketIOClient client) { + + if (ex != null) { + ex.printStackTrace(); + return; + } + + //This client instance will be using the same websocket as the original client, + //but will point to the indicated endpoint + client.setDisconnectCallback(MainActivity.this); + client.setErrorCallback(MainActivity.this); + client.setJSONCallback(MainActivity.this); + client.setStringCallback(MainActivity.this); + client.addListener("a message", MainActivity.this); + + } + }); + } - - @Override - public void onJSON(JSONObject json) { - try { - Log.d(TAG, String.format("Got JSON Object: %s", json.toString())); - } catch(JSONException e) { - } +}, new Handler()); + + +@Override +public void onEvent(String event, JSONArray argument, Acknowledge acknowledge) { + try { + Log.d("MainActivity", "Event:" + event + "Arguments:" + + argument.toString(2)); + } catch (JSONException e) { + e.printStackTrace(); } - @Override - public void onMessage(String message) { - Log.d(TAG, String.format("Got message: %s", message)); - } +} - @Override - public void onDisconnect(int code, String reason) { - Log.d(TAG, String.format("Disconnected! Code: %d Reason: %s", code, reason)); - } +@Override +public void onString(String string, Acknowledge acknowledge) { + Log.d("MainActivity", string); - @Override - public void onError(Exception error) { - Log.e(TAG, "Error!", error); - } - - @Override - public void onConnectToEndpoint(String endpoint) { - Log.d(TAG, "Connected to:" + endpoint); +} +@Override +public void onJSON(JSONObject json, Acknowledge acknowledge) { + try { + Log.d("MainActivity", "json:" + json.toString(2)); + } catch (JSONException e) { + e.printStackTrace(); } -}); -client.connect(); +} -// Later… -client.emit("Message"); //Message -JSONArray arguments = new JSONArray(); -arguments.put("first argument"); -JSONObject second = new JSONObject(); -second.put("dictionary", true); -client.emit(second); //JSON Message -arguments.put(second); -client.emit("hello", arguments); //Event -client.disconnect(); +@Override +public void onError(String error) { + Log.d("MainActivity", error); + +} + +@Override +public void onDisconnect(Exception e) { + Log.d(mComponentTag, "Disconnected:" + e.getMessage()); + +} -/*How to use Acknowledges -*Call any of the emit() methods -*with the Acknowledge parameter. -* -*Each message must have a new -*Acknowledge object as they -*are matched against the Message Id. -*/ -client.emit("Message", new Acknowledge { - - @Override - public void acknowledge(String[] args) { - //Perform your acknowledge handling here - } -}); ``` From 383d075fb6fe992bfde97287c885ffc98c78dfaf Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy <vinaysshenoy@gmail.com> Date: Fri, 12 Jul 2013 22:49:17 +0530 Subject: [PATCH 51/56] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9005dcd..a50657a 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,8 @@ SocketIOClient.connect("http://localhost:80", new ConnectCallback() { client.setErrorCallback(MainActivity.this); client.setJSONCallback(MainActivity.this); client.setStringCallback(MainActivity.this); + + //You need to explicitly specify which events you are interested in receiving client.addListener("news", MainActivity.this); client.of("/chat", new ConnectCallback() { From 9e5b14b09ac7781f5515b69d3c3352c172de26c5 Mon Sep 17 00:00:00 2001 From: Payden Sutherland <payden@paydensutherland.com> Date: Mon, 14 Oct 2013 00:36:45 -0400 Subject: [PATCH 52/56] Verify server's Sec-WebSocket-Accept --- .../android_websockets/WebSocketClient.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java index 0475d5e..84b46b2 100644 --- a/src/com/codebutler/android_websockets/WebSocketClient.java +++ b/src/com/codebutler/android_websockets/WebSocketClient.java @@ -22,6 +22,7 @@ import java.net.Socket; import java.net.URI; import java.security.KeyManagementException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; @@ -85,12 +86,13 @@ public void run() { mSocket = factory.createSocket(mURI.getHost(), port); PrintWriter out = new PrintWriter(mSocket.getOutputStream()); + String secretKey = createSecret(); out.print("GET " + path + " HTTP/1.1\r\n"); out.print("Upgrade: websocket\r\n"); out.print("Connection: Upgrade\r\n"); out.print("Host: " + mURI.getHost() + "\r\n"); out.print("Origin: " + origin.toString() + "\r\n"); - out.print("Sec-WebSocket-Key: " + createSecret() + "\r\n"); + out.print("Sec-WebSocket-Key: " + secretKey + "\r\n"); out.print("Sec-WebSocket-Version: 13\r\n"); if (mExtraHeaders != null) { for (NameValuePair pair : mExtraHeaders) { @@ -115,7 +117,12 @@ public void run() { while (!TextUtils.isEmpty(line = readLine(stream))) { Header header = parseHeader(line); if (header.getName().equals("Sec-WebSocket-Accept")) { - // FIXME: Verify the response... + String expected = expectedKey(secretKey); + if (expected == null) { + throw new Exception("SHA-1 algorithm not found"); + } else if (!expected.equals(header.getValue())) { + throw new Exception("Invalid Sec-WebSocket-Accept, expected: " + expected + ", got: " + header.getValue()); + } } } @@ -208,6 +215,19 @@ private String readLine(HybiParser.HappyDataInputStream reader) throws IOExcepti return string.toString(); } + private String expectedKey(String secret) { + //concatenate, SHA1-hash, base64-encode + try { + final String GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + final String secretGUID = secret + GUID; + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] digest = md.digest(secretGUID.getBytes()); + return Base64.encodeToString(digest, Base64.DEFAULT).trim(); + } catch (NoSuchAlgorithmException e) { + return null; + } + } + private String createSecret() { byte[] nonce = new byte[16]; for (int i = 0; i < 16; i++) { From 3f5788ae77b2fa7940ccd204b3b17702f8b1fef1 Mon Sep 17 00:00:00 2001 From: Rahul Parsani <rahul.parsani@bubblemotion.com> Date: Thu, 30 Jan 2014 22:47:37 +0800 Subject: [PATCH 53/56] Better handle queries when building the session url --- .../async/http/socketio/SocketIOConnection.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java index 669a288..f8efcdd 100644 --- a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java +++ b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java @@ -10,6 +10,7 @@ import org.json.JSONArray; import org.json.JSONObject; +import android.net.Uri; import android.os.Handler; import android.text.TextUtils; @@ -114,8 +115,9 @@ public void onCompleted(final Exception e, String result) { if (!set.contains("websocket")) throw new Exception("websocket not supported"); - final String sessionUrl = request.getUri().toString() - + "websocket/" + session + "/"; + final String sessionUrl = Uri.parse(request.getUri()).buildUpon() + .appendPath("websocket").appendPath(session) + .build().toString(); SocketIOConnection.this.webSocketClient = new WebSocketClient(URI.create(sessionUrl), new Listener() { From 75e052b360834d8e24c36403a642e401573720a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Campama=CC=81?= <sergiocampama@gmail.com> Date: Fri, 11 Apr 2014 16:40:38 -0300 Subject: [PATCH 54/56] Added support for headers in SocketIORequest Added a new constructor that accepts a `List<BasicNameValuePair>` containing extra headers to add to the `HttpPost` request. --- .../koushikdutta/http/AsyncHttpClient.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/com/koushikdutta/http/AsyncHttpClient.java b/src/com/koushikdutta/http/AsyncHttpClient.java index b8a15d0..c1855a5 100644 --- a/src/com/koushikdutta/http/AsyncHttpClient.java +++ b/src/com/koushikdutta/http/AsyncHttpClient.java @@ -4,9 +4,15 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; +import org.apache.http.message.BasicNameValuePair; import android.net.Uri; import android.net.http.AndroidHttpClient; @@ -28,26 +34,33 @@ public static class SocketIORequest { private String mUri; private String mEndpoint; + private List<BasicNameValuePair> mHeaders; public SocketIORequest(String uri) { this(uri, null); } public SocketIORequest(String uri, String endpoint) { + this(uri, endpoint, null); + } + public SocketIORequest(String uri, String endpoint, List<BasicNameValuePair> headers) { mUri = Uri.parse(uri).buildUpon().encodedPath("/socket.io/1/").build().toString(); mEndpoint = endpoint; + mHeaders = headers; } public String getUri() { - return mUri; } public String getEndpoint() { - return mEndpoint; } + + public List<BasicNameValuePair> getHeaders() { + return mHeaders; + } } public static interface StringCallback { @@ -67,6 +80,7 @@ protected Void doInBackground(Void... params) { AndroidHttpClient httpClient = AndroidHttpClient.newInstance("android-websockets-2.0"); HttpPost post = new HttpPost(socketIORequest.getUri()); + addHeadersToRequest(post, socketIORequest.getHeaders()); try { HttpResponse res = httpClient.execute(post); @@ -87,6 +101,16 @@ protected Void doInBackground(Void... params) { } return null; } + + private void addHeadersToRequest(HttpRequest request, List<BasicNameValuePair> headers) { + if (headers != null) { + Iterator<BasicNameValuePair> it = headers.iterator(); + while (it.hasNext()) { + BasicNameValuePair header = it.next(); + request.addHeader(header.getName(), header.getValue()); + } + } + } }.execute(); } From 0f1a2fae11a02ce710d02345f08346cae07663fa Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koush@koushikdutta.com> Date: Fri, 11 Apr 2014 13:28:03 -0700 Subject: [PATCH 55/56] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index a50657a..38d042b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +# THIS LIBRARY IS DEPRECATED IN FAVOR OF: + +https://github.com/koush/AndroidAsync + # WebSocket and Socket.IO client for Android ## Credits From d18376158a7839d701692b8a54abc2bf5d407968 Mon Sep 17 00:00:00 2001 From: Koushik Dutta <koush@koushikdutta.com> Date: Fri, 11 Apr 2014 13:28:23 -0700 Subject: [PATCH 56/56] Update README.md --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 38d042b..26aa1f7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # THIS LIBRARY IS DEPRECATED IN FAVOR OF: -https://github.com/koush/AndroidAsync +[AndroidAsync](https://github.com/koush/AndroidAsync) + + + + + + + # WebSocket and Socket.IO client for Android