From 7b2014678ef492d756ec117467aa239cfd03c7da Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Fri, 4 May 2012 00:01:26 -0700 Subject: [PATCH 01/25] 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 set = new HashSet(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 Date: Fri, 4 May 2012 00:08:57 -0700 Subject: [PATCH 02/25] 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 Date: Fri, 4 May 2012 10:05:43 -0700 Subject: [PATCH 03/25] 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 Date: Fri, 4 May 2012 10:12:01 -0700 Subject: [PATCH 04/25] 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 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 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 Date: Fri, 4 May 2012 10:12:25 -0700 Subject: [PATCH 05/25] 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 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 Date: Fri, 4 May 2012 10:13:19 -0700 Subject: [PATCH 06/25] 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 Date: Fri, 4 May 2012 10:15:43 -0700 Subject: [PATCH 07/25] 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) . +The hybi parser was ported from JavaScript to Java by [Eric Butler](https://twitter.com/codebutler) . + +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 From c511e7329258cbc672bbc493ada35fddc119e7a3 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Fri, 4 May 2012 10:16:45 -0700 Subject: [PATCH 08/25] 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 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 Date: Fri, 4 May 2012 10:19:36 -0700 Subject: [PATCH 09/25] 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 Date: Fri, 4 May 2012 11:17:42 -0700 Subject: [PATCH 10/25] 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 Date: Thu, 17 May 2012 20:56:11 +0200 Subject: [PATCH 11/25] 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 Date: Thu, 29 Nov 2012 14:04:44 -0400 Subject: [PATCH 12/25] 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 Date: Wed, 2 Jan 2013 15:56:02 -0500 Subject: [PATCH 13/25] 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 Date: Wed, 2 Jan 2013 16:05:55 -0500 Subject: [PATCH 14/25] 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 Date: Thu, 3 Jan 2013 13:38:53 -0800 Subject: [PATCH 15/25] 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 Date: Thu, 10 Jan 2013 02:45:12 -0400 Subject: [PATCH 16/25] 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 Date: Tue, 5 Feb 2013 00:49:29 -0400 Subject: [PATCH 17/25] 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 Date: Fri, 1 Mar 2013 17:55:34 +0530 Subject: [PATCH 18/25] 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"> + + From fbd83b55a06062f2a4bf246ed72aa092b036259f Mon Sep 17 00:00:00 2001 From: Vinay S Shenoy Date: Fri, 1 Mar 2013 18:09:28 +0530 Subject: [PATCH 19/25] 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 Date: Sat, 2 Mar 2013 07:16:51 +0530 Subject: [PATCH 20/25] 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 Date: Sat, 2 Mar 2013 09:48:02 +0530 Subject: [PATCH 21/25] 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 Date: Sat, 2 Mar 2013 09:50:37 +0530 Subject: [PATCH 22/25] 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 Date: Wed, 6 Mar 2013 07:34:32 -0800 Subject: [PATCH 23/25] 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 @@ - + + + + + + + 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 Date: Sat, 9 Mar 2013 14:09:13 -0400 Subject: [PATCH 24/25] 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 Date: Tue, 2 Apr 2013 10:12:11 -0400 Subject: [PATCH 25/25] 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 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 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;