From 18916c029448ff70f0280cf5503567e49c431e7a Mon Sep 17 00:00:00 2001 From: Corey Downing and Rick Kawala Date: Tue, 16 Jul 2013 16:49:07 -0700 Subject: [PATCH 01/13] Use Uri's raw path for creating GET string to avoid removing URL encoding --- .../koushikdutta/async/http/AsyncHttpRequest.java | 2 +- .../com/koushikdutta/async/test/HttpClientTests.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java index 212d98cda..3a6fab5cc 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java @@ -34,7 +34,7 @@ public String getMethod() { @Override public String toString() { - String path = AsyncHttpRequest.this.getUri().getPath(); + String path = AsyncHttpRequest.this.getUri().getRawPath(); if (path.length() == 0) path = "/"; String query = AsyncHttpRequest.this.getUri().getRawQuery(); diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/HttpClientTests.java b/AndroidAsyncTest/src/com/koushikdutta/async/test/HttpClientTests.java index 1817cebaa..f39246a0c 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/HttpClientTests.java +++ b/AndroidAsyncTest/src/com/koushikdutta/async/test/HttpClientTests.java @@ -10,12 +10,9 @@ import com.koushikdutta.async.callback.DataCallback; import com.koushikdutta.async.future.Future; import com.koushikdutta.async.future.FutureCallback; -import com.koushikdutta.async.http.AsyncHttpClient; +import com.koushikdutta.async.http.*; import com.koushikdutta.async.http.AsyncHttpClient.DownloadCallback; import com.koushikdutta.async.http.AsyncHttpClient.StringCallback; -import com.koushikdutta.async.http.AsyncHttpGet; -import com.koushikdutta.async.http.AsyncHttpResponse; -import com.koushikdutta.async.http.ResponseCacheMiddleware; import com.koushikdutta.async.http.callback.HttpConnectCallback; import com.koushikdutta.async.http.server.AsyncHttpServer; import com.koushikdutta.async.http.server.AsyncHttpServerRequest; @@ -26,6 +23,7 @@ import junit.framework.TestCase; import java.io.File; +import java.net.URI; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; @@ -330,4 +328,10 @@ public void onCompleted(Exception e, AsyncHttpResponse source, String result) { proxyServer.stop(); } } + + public void testUriPathWithSpaces() throws Exception { + AsyncHttpRequest request = new AsyncHttpRequest(URI.create("http://jpkc.seiee.sjtu.edu.cn/ds/ds2/Course%20lecture/chapter%2010.pdf"), AsyncHttpGet.METHOD); + String requestLine = request.getRequestLine().toString(); + assertEquals("GET /ds/ds2/Course%20lecture/chapter%2010.pdf HTTP/1.1", requestLine); + } } From 81a5c68b7307ae008a2484400194dc22a9072b8a Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Wed, 17 Jul 2013 23:51:13 -0700 Subject: [PATCH 02/13] expose socket object. fix readString on ByteBufferList. Change-Id: I07993c7354432d32b25de1e7b9c097c16ac99a4a --- .../koushikdutta/async/AsyncDatagramSocket.java | 3 --- .../koushikdutta/async/AsyncNetworkSocket.java | 5 +++++ .../async/AsyncSSLSocketWrapper.java | 16 +++++++++++++--- .../com/koushikdutta/async/ByteBufferList.java | 1 + .../com/koushikdutta/async/ChannelWrapper.java | 1 + .../async/DatagramChannelWrapper.java | 8 +++++--- .../async/ServerSocketChannelWrapper.java | 5 +++++ .../koushikdutta/async/SocketChannelWrapper.java | 5 +++++ 8 files changed, 35 insertions(+), 9 deletions(-) diff --git a/AndroidAsync/src/com/koushikdutta/async/AsyncDatagramSocket.java b/AndroidAsync/src/com/koushikdutta/async/AsyncDatagramSocket.java index 0dd83be9d..8e6fd54c2 100644 --- a/AndroidAsync/src/com/koushikdutta/async/AsyncDatagramSocket.java +++ b/AndroidAsync/src/com/koushikdutta/async/AsyncDatagramSocket.java @@ -1,10 +1,7 @@ package com.koushikdutta.async; -import android.util.Log; - import java.io.IOException; import java.net.InetSocketAddress; -import java.net.SocketAddress; import java.nio.ByteBuffer; public class AsyncDatagramSocket extends AsyncNetworkSocket { diff --git a/AndroidAsync/src/com/koushikdutta/async/AsyncNetworkSocket.java b/AndroidAsync/src/com/koushikdutta/async/AsyncNetworkSocket.java index 1b1c1432c..83998ac4c 100644 --- a/AndroidAsync/src/com/koushikdutta/async/AsyncNetworkSocket.java +++ b/AndroidAsync/src/com/koushikdutta/async/AsyncNetworkSocket.java @@ -1,6 +1,7 @@ package com.koushikdutta.async; import android.util.Log; + import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.callback.DataCallback; import com.koushikdutta.async.callback.WritableCallback; @@ -350,4 +351,8 @@ public InetSocketAddress getRemoteAddress() { public int getLocalPort() { return mChannel.getLocalPort(); } + + public Object getSocket() { + return getChannel().getSocket(); + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java b/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java index aa4ffd79e..f09430f6a 100644 --- a/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java +++ b/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java @@ -1,19 +1,29 @@ package com.koushikdutta.async; import android.os.Build; + import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.callback.DataCallback; import com.koushikdutta.async.callback.WritableCallback; import com.koushikdutta.async.wrapper.AsyncSocketWrapper; + import org.apache.http.conn.ssl.StrictHostnameVerifier; -import javax.net.ssl.*; -import javax.net.ssl.SSLEngineResult.HandshakeStatus; -import javax.net.ssl.SSLEngineResult.Status; import java.nio.ByteBuffer; import java.security.KeyStore; import java.security.cert.X509Certificate; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLEngineResult.Status; +import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket { AsyncSocket mSocket; BufferedDataEmitter mEmitter; diff --git a/AndroidAsync/src/com/koushikdutta/async/ByteBufferList.java b/AndroidAsync/src/com/koushikdutta/async/ByteBufferList.java index ad9e9b805..950e74f7a 100644 --- a/AndroidAsync/src/com/koushikdutta/async/ByteBufferList.java +++ b/AndroidAsync/src/com/koushikdutta/async/ByteBufferList.java @@ -329,6 +329,7 @@ public String readString() { builder.append(new String(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining())); reclaim(bb); } + remaining = 0; return builder.toString(); } diff --git a/AndroidAsync/src/com/koushikdutta/async/ChannelWrapper.java b/AndroidAsync/src/com/koushikdutta/async/ChannelWrapper.java index 0cc445578..0fd550172 100644 --- a/AndroidAsync/src/com/koushikdutta/async/ChannelWrapper.java +++ b/AndroidAsync/src/com/koushikdutta/async/ChannelWrapper.java @@ -47,4 +47,5 @@ public void close() throws IOException { } public abstract int getLocalPort(); + public abstract Object getSocket(); } diff --git a/AndroidAsync/src/com/koushikdutta/async/DatagramChannelWrapper.java b/AndroidAsync/src/com/koushikdutta/async/DatagramChannelWrapper.java index 2478857f5..9d3c84c46 100644 --- a/AndroidAsync/src/com/koushikdutta/async/DatagramChannelWrapper.java +++ b/AndroidAsync/src/com/koushikdutta/async/DatagramChannelWrapper.java @@ -1,10 +1,7 @@ package com.koushikdutta.async; -import android.util.Log; - import java.io.IOException; import java.net.InetSocketAddress; -import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.DatagramChannel; @@ -86,4 +83,9 @@ public long read(ByteBuffer[] byteBuffers) throws IOException { public long read(ByteBuffer[] byteBuffers, int i, int i2) throws IOException { return mChannel.read(byteBuffers, i, i2); } + + @Override + public Object getSocket() { + return mChannel.socket(); + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/ServerSocketChannelWrapper.java b/AndroidAsync/src/com/koushikdutta/async/ServerSocketChannelWrapper.java index 3e9d97512..d13bd71a4 100644 --- a/AndroidAsync/src/com/koushikdutta/async/ServerSocketChannelWrapper.java +++ b/AndroidAsync/src/com/koushikdutta/async/ServerSocketChannelWrapper.java @@ -73,4 +73,9 @@ public long read(ByteBuffer[] byteBuffers, int i, int i2) throws IOException { assert false; throw new IOException(msg); } + + @Override + public Object getSocket() { + return mChannel.socket(); + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/SocketChannelWrapper.java b/AndroidAsync/src/com/koushikdutta/async/SocketChannelWrapper.java index 3d599c777..73b1195cc 100644 --- a/AndroidAsync/src/com/koushikdutta/async/SocketChannelWrapper.java +++ b/AndroidAsync/src/com/koushikdutta/async/SocketChannelWrapper.java @@ -67,4 +67,9 @@ public long read(ByteBuffer[] byteBuffers) throws IOException { public long read(ByteBuffer[] byteBuffers, int i, int i2) throws IOException { return mChannel.read(byteBuffers, i, i2); } + + @Override + public Object getSocket() { + return mChannel.socket(); + } } From 3845a962a217e3c57de4340394982dc07342d422 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Thu, 18 Jul 2013 20:45:24 -0700 Subject: [PATCH 03/13] Fix event ack Change-Id: I4ea3f10d261b3422ec98400359be3a2a7b3a4ab5 --- .../http/socketio/SocketIOConnection.java | 6 ++-- .../async/test/SocketIOTests.java | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java index 22ebc5a7d..b2e1e2ee4 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java @@ -293,10 +293,12 @@ public void onSelect(SocketIOClient client) { }); } - private Acknowledge acknowledge(final String messageId) { - if (TextUtils.isEmpty(messageId)) + private Acknowledge acknowledge(final String _messageId) { + if (TextUtils.isEmpty(_messageId)) return null; + final String messageId = _messageId.replaceAll("\\+$", ""); + return new Acknowledge() { @Override public void acknowledge(JSONArray arguments) { diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java b/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java index 103ee7049..97583042b 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java +++ b/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java @@ -169,4 +169,32 @@ public void run() { assertTrue(disconnectTrigger.get(TIMEOUT, TimeUnit.MILLISECONDS)); assertTrue(reconnectTrigger.get(TIMEOUT, TimeUnit.MILLISECONDS)); } + + public void testEventAck() throws Exception { + final TriggerFuture trigger = new TriggerFuture(); + SocketIOClient client = SocketIOClient.connect(AsyncHttpClient.getDefaultInstance(), "http://192.168.1.2:3000/", null).get(); + + final JSONArray args = new JSONArray(); + args.put("echo"); + + client.on("scoop", new EventCallback() { + @Override + public void onEvent(JSONArray argument, Acknowledge acknowledge) { + acknowledge.acknowledge(args); + + } + }); + + client.on("ack", new EventCallback() { + @Override + public void onEvent(JSONArray argument, Acknowledge acknowledge) { + + trigger.trigger(args.optString(0, null).equals("echo")); + } + }); + + client.emit("poop", args); + + assertTrue(trigger.get(TIMEOUT, TimeUnit.MILLISECONDS)); + } } From e240d15482fcf2d91aaca8ab2a8cf71a9a037191 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Thu, 18 Jul 2013 20:54:06 -0700 Subject: [PATCH 04/13] switch to external test server Change-Id: I2bf941202892439a12c21eb7f8ad08a81e73f1d9 --- .../src/com/koushikdutta/async/test/SocketIOTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java b/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java index 97583042b..c98735d0f 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java +++ b/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java @@ -172,7 +172,7 @@ public void run() { public void testEventAck() throws Exception { final TriggerFuture trigger = new TriggerFuture(); - SocketIOClient client = SocketIOClient.connect(AsyncHttpClient.getDefaultInstance(), "http://192.168.1.2:3000/", null).get(); + SocketIOClient client = SocketIOClient.connect(AsyncHttpClient.getDefaultInstance(), "http://koush.clockworkmod.com:8080/", null).get(); final JSONArray args = new JSONArray(); args.put("echo"); From 75bb636f40d3b2e4344d22887ce58377a8995668 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Thu, 18 Jul 2013 22:51:19 -0700 Subject: [PATCH 05/13] Close dangling snapshots and streams in ResponseCacheMiddleware. Change-Id: Ibf3ba8e1e35b6a2926ec55c6781debda949c29ae --- .../src/com/koushikdutta/async/Util.java | 6 +- .../async/http/ResponseCacheMiddleware.java | 58 ++++++++++++++----- .../koushikdutta/async/test/CacheTests.java | 27 +++++++++ 3 files changed, 73 insertions(+), 18 deletions(-) diff --git a/AndroidAsync/src/com/koushikdutta/async/Util.java b/AndroidAsync/src/com/koushikdutta/async/Util.java index 1c8326365..001f07b65 100644 --- a/AndroidAsync/src/com/koushikdutta/async/Util.java +++ b/AndroidAsync/src/com/koushikdutta/async/Util.java @@ -183,13 +183,13 @@ public static void writeAll(DataSink sink, byte[] bytes, CompletedCallback callb writeAll(sink, bbl, callback); } - public static AsyncSocket getWrappedSocket(AsyncSocket socket, Class wrappedClass) { + public static T getWrappedSocket(AsyncSocket socket, Class wrappedClass) { if (wrappedClass.isInstance(socket)) - return socket; + return (T)socket; while (socket instanceof AsyncSocketWrapper) { socket = ((AsyncSocketWrapper)socket).getSocket(); if (wrappedClass.isInstance(socket)) - return socket; + return (T)socket; } return null; } diff --git a/AndroidAsync/src/com/koushikdutta/async/http/ResponseCacheMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/ResponseCacheMiddleware.java index c0fb37f62..2dc118805 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/ResponseCacheMiddleware.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/ResponseCacheMiddleware.java @@ -251,6 +251,7 @@ public AsyncServer getServer() { } public static class CacheData implements Parcelable { + DiskLruCache.Snapshot snapshot; CacheResponse candidate; long contentLength; ResponseHeaders cachedResponseHeaders; @@ -278,7 +279,7 @@ public Cancellable getSocket(final GetSocketData data) { } String key = uriToKey(data.request.getUri()); - DiskLruCache.Snapshot snapshot; + DiskLruCache.Snapshot snapshot = null; Entry entry; try { snapshot = cache.get(key); @@ -299,7 +300,7 @@ public Cancellable getSocket(final GetSocketData data) { snapshot.close(); return null; } - + CacheResponse candidate = entry.isHttps() ? new EntrySecureCacheResponse(entry, snapshot) : new EntryCacheResponse(entry, snapshot); Map> responseHeadersMap; @@ -310,6 +311,7 @@ public Cancellable getSocket(final GetSocketData data) { } catch (Exception e) { networkCount++; + snapshot.close(); return null; } if (responseHeadersMap == null || cachedResponseBody == null) { @@ -319,6 +321,7 @@ public Cancellable getSocket(final GetSocketData data) { catch (Exception e) { } networkCount++; + snapshot.close(); return null; } @@ -351,6 +354,7 @@ public void run() { else if (responseSource == ResponseSource.CONDITIONAL_CACHE) { data.request.logi("Response may be served from conditional cache"); CacheData cacheData = new CacheData(); + cacheData.snapshot = snapshot; cacheData.contentLength = contentLength; cacheData.cachedResponseHeaders = cachedResponseHeaders; cacheData.candidate = candidate; @@ -367,6 +371,7 @@ else if (responseSource == ResponseSource.CONDITIONAL_CACHE) { catch (Exception e) { } networkCount++; + snapshot.close(); return null; } } @@ -567,9 +572,10 @@ public void onBodyDecoder(OnBodyData data) { bodySpewer.spew(); return; } - + // did not validate, so fall through and cache the response data.state.remove("cache-data"); + cacheData.snapshot.close(); } if (!caching) @@ -623,17 +629,24 @@ public void onBodyDecoder(OnBodyData data) { @Override public void onRequestComplete(OnRequestCompleteData data) { - BodyCacher cacher = data.state.getParcelable("body-cacher"); - if (cacher == null) - return; + CacheData cacheData = data.state.getParcelable("cache-data"); + if (cacheData != null && cacheData.snapshot != null) + cacheData.snapshot.close(); - try { - if (data.exception != null) - cacher.abort(); - else - cacher.commit(); - } - catch (Exception e) { + CachedSocket cachedSocket = Util.getWrappedSocket(data.socket, CachedSocket.class); + if (cachedSocket != null) + ((SnapshotCacheResponse)cachedSocket.cacheResponse).getSnapshot().close(); + + BodyCacher cacher = data.state.getParcelable("body-cacher"); + if (cacher != null) { + try { + if (data.exception != null) + cacher.abort(); + else + cacher.commit(); + } + catch (Exception e) { + } } } @@ -901,11 +914,20 @@ private static InputStream newBodyInputStream(final DiskLruCache.Snapshot snapsh }; } - static class EntryCacheResponse extends CacheResponse { + static interface SnapshotCacheResponse { + public DiskLruCache.Snapshot getSnapshot(); + } + + static class EntryCacheResponse extends CacheResponse implements SnapshotCacheResponse { private final Entry entry; private final DiskLruCache.Snapshot snapshot; private final InputStream in; + @Override + public DiskLruCache.Snapshot getSnapshot() { + return snapshot; + } + public EntryCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) { this.entry = entry; this.snapshot = snapshot; @@ -921,11 +943,17 @@ public EntryCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) { } } - static class EntrySecureCacheResponse extends SecureCacheResponse { + static class EntrySecureCacheResponse extends SecureCacheResponse implements SnapshotCacheResponse { private final Entry entry; private final DiskLruCache.Snapshot snapshot; private final InputStream in; + @Override + public DiskLruCache.Snapshot getSnapshot() { + return snapshot; + } + + public EntrySecureCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) { this.entry = entry; this.snapshot = snapshot; diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/CacheTests.java b/AndroidAsyncTest/src/com/koushikdutta/async/test/CacheTests.java index 96d619fc0..9aa985122 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/CacheTests.java +++ b/AndroidAsyncTest/src/com/koushikdutta/async/test/CacheTests.java @@ -5,6 +5,7 @@ import com.koushikdutta.async.AsyncServer; import com.koushikdutta.async.http.AsyncHttpClient; import com.koushikdutta.async.http.ResponseCacheMiddleware; +import com.koushikdutta.async.http.libcore.DiskLruCache; import com.koushikdutta.async.http.libcore.HttpDate; import com.koushikdutta.async.http.server.AsyncHttpServer; import com.koushikdutta.async.http.server.AsyncHttpServerRequest; @@ -51,4 +52,30 @@ public void onRequest(AsyncHttpServerRequest request, AsyncHttpServerResponse re client.getMiddleware().remove(cache); } } + +// static public boolean deleteDirectory(File path) { +// if (path.exists()) { +// File[] files = path.listFiles(); +// if (files != null) { +// for (int i = 0; i < files.length; i++) { +// if (files[i].isDirectory()) { +// deleteDirectory(files[i]); +// } else { +// files[i].delete(); +// } +// } +// } +// } +// return (path.delete()); +// } + +// public void testDiskLruCache() throws Exception { +// File dir = new File(Environment.getExternalStorageDirectory(), "AndroidAsyncTest/cache-test"); +// deleteDirectory(dir); +// DiskLruCache cache = DiskLruCache.open(dir, 0, 1000, 10000000); +// DiskLruCache.Editor editor = cache.edit("stuff"); +// +// DiskLruCache cache2 = DiskLruCache.open(dir, 0, 2, 10000000); +// DiskLruCache.Snapshot snapshot = cache2.get("stuff"); +// } } From acd3c9156c7ff4834e1a3ad1c1132e750ee44353 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Fri, 19 Jul 2013 00:18:03 -0700 Subject: [PATCH 06/13] switch to jakewharton disklrucache. Change-Id: Ic2d6fedabef76d57d454239dd6e4be3c9a788721 --- .../async/http/libcore/DiskLruCache.java | 207 ++++++++---------- 1 file changed, 90 insertions(+), 117 deletions(-) diff --git a/AndroidAsync/src/com/koushikdutta/async/http/libcore/DiskLruCache.java b/AndroidAsync/src/com/koushikdutta/async/http/libcore/DiskLruCache.java index 729b401fa..f7d1625da 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/libcore/DiskLruCache.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/libcore/DiskLruCache.java @@ -16,6 +16,8 @@ package com.koushikdutta.async.http.libcore; +import com.koushikdutta.async.Util; + import java.io.BufferedWriter; import java.io.Closeable; import java.io.EOFException; @@ -30,6 +32,7 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; @@ -64,12 +67,12 @@ * entry may have only one editor at one time; if a value is not available to be * edited then {@link #edit} will return null. *
    - *
  • When an entry is being created it is necessary to - * supply a full set of values; the empty value should be used as a - * placeholder if necessary. - *
  • When an entry is being edited, it is not necessary - * to supply data for every value; values default to their previous - * value. + *
  • When an entry is being created it is necessary to + * supply a full set of values; the empty value should be used as a + * placeholder if necessary. + *
  • When an entry is being edited, it is not necessary + * to supply data for every value; values default to their previous + * value. *
* Every {@link #edit} call must be matched by a call to {@link Editor#commit} * or {@link Editor#abort}. Committing is atomic: a read observes the full set @@ -87,8 +90,8 @@ */ public final class DiskLruCache implements Closeable { static final String JOURNAL_FILE = "journal"; - static final String JOURNAL_FILE_TMP = "journal.tmp"; - static final String JOURNAL_FILE_BKP = "journal.bkp"; + static final String JOURNAL_FILE_TEMP = "journal.tmp"; + static final String JOURNAL_FILE_BACKUP = "journal.bkp"; static final String MAGIC = "libcore.io.DiskLruCache"; static final String VERSION_1 = "1"; static final long ANY_SEQUENCE_NUMBER = -1; @@ -141,14 +144,14 @@ public final class DiskLruCache implements Closeable { private final File directory; private final File journalFile; private final File journalFileTmp; - private final File journalFileBkp; + private final File journalFileBackup; private final int appVersion; private long maxSize; private final int valueCount; private long size = 0; private Writer journalWriter; - private final LinkedHashMap lruEntries - = new LinkedHashMap(0, 0.75f, true); + private final LinkedHashMap lruEntries = + new LinkedHashMap(0, 0.75f, true); private int redundantOpCount; /** @@ -159,13 +162,13 @@ public final class DiskLruCache implements Closeable { private long nextSequenceNumber = 0; /** This cache uses a single background thread to evict entries. */ - final ThreadPoolExecutor executorService = new ThreadPoolExecutor(0, 1, - 60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); + final ThreadPoolExecutor executorService = + new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); private final Callable cleanupCallable = new Callable() { public Void call() throws Exception { synchronized (DiskLruCache.this) { if (journalWriter == null) { - return null; // closed + return null; // Closed. } trimToSize(); if (journalRebuildRequired()) { @@ -181,8 +184,8 @@ private DiskLruCache(File directory, int appVersion, int valueCount, long maxSiz this.directory = directory; this.appVersion = appVersion; this.journalFile = new File(directory, JOURNAL_FILE); - this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP); - this.journalFileBkp = new File(directory, JOURNAL_FILE_BKP); + this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP); + this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP); this.valueCount = valueCount; this.maxSize = maxSize; } @@ -192,13 +195,12 @@ private DiskLruCache(File directory, int appVersion, int valueCount, long maxSiz * there. * * @param directory a writable directory - * @param appVersion * @param valueCount the number of values per cache entry. Must be positive. * @param maxSize the maximum number of bytes this cache should use to store * @throws IOException if reading or writing the cache directory fails */ public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) - throws IOException { + throws IOException { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } @@ -206,35 +208,39 @@ public static DiskLruCache open(File directory, int appVersion, int valueCount, throw new IllegalArgumentException("valueCount <= 0"); } - // if a bkp file exists, use it instead - File bkpFile = new File(directory, JOURNAL_FILE_BKP); - if (bkpFile.exists()) { + // If a bkp file exists, use it instead. + File backupFile = new File(directory, JOURNAL_FILE_BACKUP); + if (backupFile.exists()) { File journalFile = new File(directory, JOURNAL_FILE); - // if journal file also exists just delete backup file + // If journal file also exists just delete backup file. if (journalFile.exists()) { - bkpFile.delete(); + backupFile.delete(); } else { - renameTo(bkpFile, journalFile, false); + renameTo(backupFile, journalFile, false); } } - // prefer to pick up where we left off + // Prefer to pick up where we left off. DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); if (cache.journalFile.exists()) { try { cache.readJournal(); cache.processJournal(); - cache.journalWriter = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(cache.journalFile, true), Charsets.US_ASCII)); + cache.journalWriter = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), Charsets.US_ASCII)); return cache; } catch (IOException journalIsCorrupt) { - System.out.println("DiskLruCache " + directory + " is corrupt: " - + journalIsCorrupt.getMessage() + ", removing"); + System.out + .println("DiskLruCache " + + directory + + " is corrupt: " + + journalIsCorrupt.getMessage() + + ", removing"); cache.delete(); } } - // create a new empty cache + // Create a new empty cache. directory.mkdirs(); cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); cache.rebuildJournal(); @@ -242,8 +248,7 @@ public static DiskLruCache open(File directory, int appVersion, int valueCount, } private void readJournal() throws IOException { - StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), - Charsets.US_ASCII); + StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Charsets.US_ASCII); try { String magic = reader.readLine(); String version = reader.readLine(); @@ -251,12 +256,12 @@ private void readJournal() throws IOException { String valueCountString = reader.readLine(); String blank = reader.readLine(); if (!MAGIC.equals(magic) - || !VERSION_1.equals(version) - || !Integer.toString(appVersion).equals(appVersionString) - || !Integer.toString(valueCount).equals(valueCountString) - || !"".equals(blank)) { - throw new IOException("unexpected journal header: [" - + magic + ", " + version + ", " + valueCountString + ", " + blank + "]"); + || !VERSION_1.equals(version) + || !Integer.toString(appVersion).equals(appVersionString) + || !Integer.toString(valueCount).equals(valueCountString) + || !"".equals(blank)) { + throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " + + valueCountString + ", " + blank + "]"); } int lineCount = 0; @@ -307,7 +312,7 @@ private void readJournalLine(String line) throws IOException { } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) { entry.currentEditor = new Editor(entry); } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) { - // this work was already done by calling lruEntries.get() + // This work was already done by calling lruEntries.get(). } else { throw new IOException("unexpected journal line: " + line); } @@ -345,8 +350,8 @@ private synchronized void rebuildJournal() throws IOException { journalWriter.close(); } - Writer writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(journalFileTmp), Charsets.US_ASCII)); + Writer writer = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(journalFileTmp), Charsets.US_ASCII)); try { writer.write(MAGIC); writer.write("\n"); @@ -370,30 +375,22 @@ private synchronized void rebuildJournal() throws IOException { } if (journalFile.exists()) { - renameTo(journalFile, journalFileBkp, true); + renameTo(journalFile, journalFileBackup, true); } renameTo(journalFileTmp, journalFile, false); - journalFileBkp.delete(); + journalFileBackup.delete(); - journalWriter = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(journalFile, true), Charsets.US_ASCII)); + journalWriter = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(journalFile, true), Charsets.US_ASCII)); } private static void deleteIfExists(File file) throws IOException { - /*try { - Libcore.os.remove(file.getPath()); - } catch (ErrnoException errnoException) { - if (errnoException.errno != OsConstants.ENOENT) { - throw errnoException.rethrowAsIOException(); - } - }*/ if (file.exists() && !file.delete()) { throw new IOException(); } } - private static void renameTo(File from, File to, boolean deleteDestination) - throws IOException { + private static void renameTo(File from, File to, boolean deleteDestination) throws IOException { if (deleteDestination) { deleteIfExists(to); } @@ -419,18 +416,16 @@ public synchronized Snapshot get(String key) throws IOException { return null; } - /* - * Open all streams eagerly to guarantee that we see a single published - * snapshot. If we opened streams lazily then the streams could come - * from different edits. - */ + // Open all streams eagerly to guarantee that we see a single published + // snapshot. If we opened streams lazily then the streams could come + // from different edits. InputStream[] ins = new InputStream[valueCount]; try { for (int i = 0; i < valueCount; i++) { ins[i] = new FileInputStream(entry.getCleanFile(i)); } } catch (FileNotFoundException e) { - // a file must have been deleted manually! + // A file must have been deleted manually! for (int i = 0; i < valueCount; i++) { if (ins[i] != null) { IoUtils.closeQuietly(ins[i]); @@ -462,29 +457,27 @@ private synchronized Editor edit(String key, long expectedSequenceNumber) throws checkNotClosed(); validateKey(key); Entry entry = lruEntries.get(key); - if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER - && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) { - return null; // snapshot is stale + if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null + || entry.sequenceNumber != expectedSequenceNumber)) { + return null; // Snapshot is stale. } if (entry == null) { entry = new Entry(key); lruEntries.put(key, entry); } else if (entry.currentEditor != null) { - return null; // another edit is in progress + return null; // Another edit is in progress. } Editor editor = new Editor(entry); entry.currentEditor = editor; - // flush the journal before creating files to prevent file leaks + // Flush the journal before creating files to prevent file leaks. journalWriter.write(DIRTY + ' ' + key + '\n'); journalWriter.flush(); return editor; } - /** - * Returns the directory where this cache stores its data. - */ + /** Returns the directory where this cache stores its data. */ public File getDirectory() { return directory; } @@ -493,7 +486,7 @@ public File getDirectory() { * Returns the maximum number of bytes that this cache should use to store * its data. */ - public long getMaxSize() { + public synchronized long getMaxSize() { return maxSize; } @@ -521,7 +514,7 @@ private synchronized void completeEdit(Editor editor, boolean success) throws IO throw new IllegalStateException(); } - // if this edit is creating the entry for the first time, every index must have a value + // If this edit is creating the entry for the first time, every index must have a value. if (success && !entry.readable) { for (int i = 0; i < valueCount; i++) { if (!editor.written[i]) { @@ -563,6 +556,7 @@ private synchronized void completeEdit(Editor editor, boolean success) throws IO lruEntries.remove(entry.key); journalWriter.write(REMOVE + ' ' + entry.key + '\n'); } + journalWriter.flush(); if (size > maxSize || journalRebuildRequired()) { executorService.submit(cleanupCallable); @@ -574,9 +568,9 @@ private synchronized void completeEdit(Editor editor, boolean success) throws IO * and eliminate at least 2000 ops. */ private boolean journalRebuildRequired() { - final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000; - return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD - && redundantOpCount >= lruEntries.size(); + final int redundantOpCompactThreshold = 2000; + return redundantOpCount >= redundantOpCompactThreshold // + && redundantOpCount >= lruEntries.size(); } /** @@ -595,7 +589,7 @@ public synchronized boolean remove(String key) throws IOException { for (int i = 0; i < valueCount; i++) { File file = entry.getCleanFile(i); - if (!file.delete()) { + if (file.exists() && !file.delete()) { throw new IOException("failed to delete " + file); } size -= entry.lengths[i]; @@ -613,10 +607,8 @@ public synchronized boolean remove(String key) throws IOException { return true; } - /** - * Returns true if this cache has been closed. - */ - public boolean isClosed() { + /** Returns true if this cache has been closed. */ + public synchronized boolean isClosed() { return journalWriter == null; } @@ -626,21 +618,17 @@ private void checkNotClosed() { } } - /** - * Force buffered operations to the filesystem. - */ + /** Force buffered operations to the filesystem. */ public synchronized void flush() throws IOException { checkNotClosed(); trimToSize(); journalWriter.flush(); } - /** - * Closes this cache. Stored values will remain on the filesystem. - */ + /** Closes this cache. Stored values will remain on the filesystem. */ public synchronized void close() throws IOException { if (journalWriter == null) { - return; // already closed + return; // Already closed. } for (Entry entry : new ArrayList(lruEntries.values())) { if (entry.currentEditor != null) { @@ -654,7 +642,7 @@ public synchronized void close() throws IOException { private void trimToSize() throws IOException { while (size > maxSize) { - Map.Entry toEvict = lruEntries.entrySet().iterator().next();//lruEntries.eldest(); + Map.Entry toEvict = lruEntries.entrySet().iterator().next(); remove(toEvict.getKey()); } } @@ -672,8 +660,7 @@ public void delete() throws IOException { private void validateKey(String key) { Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); if (!matcher.matches()) { - throw new IllegalArgumentException( - "keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\""); + throw new IllegalArgumentException("keys must match regex [a-z0-9_-]{1,64}: \"" + key + "\""); } } @@ -681,9 +668,7 @@ private static String inputStreamToString(InputStream in) throws IOException { return Streams.readFully(new InputStreamReader(in, Charsets.UTF_8)); } - /** - * A snapshot of the values for an entry. - */ + /** A snapshot of the values for an entry. */ public final class Snapshot implements Closeable { private final String key; private final long sequenceNumber; @@ -706,23 +691,17 @@ public Editor edit() throws IOException { return DiskLruCache.this.edit(key, sequenceNumber); } - /** - * Returns the unbuffered stream with the value for {@code index}. - */ + /** Returns the unbuffered stream with the value for {@code index}. */ public InputStream getInputStream(int index) { return ins[index]; } - /** - * Returns the string value for {@code index}. - */ + /** Returns the string value for {@code index}. */ public String getString(int index) throws IOException { return inputStreamToString(getInputStream(index)); } - /** - * Returns the byte length of the value for {@code index}. - */ + /** Returns the byte length of the value for {@code index}. */ public long getLength(int index) { return lengths[index]; } @@ -737,13 +716,11 @@ public void close() { private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() { @Override public void write(int b) throws IOException { - //Eat all writes silently. Nom nom. + // Eat all writes silently. Nom nom. } }; - /** - * Edits the values for an entry. - */ + /** Edits the values for an entry. */ public final class Editor { private final Entry entry; private final boolean[] written; @@ -797,7 +774,7 @@ public OutputStream newOutputStream(int index) throws IOException { throw new IllegalStateException(); } if (!entry.readable) { - written[index] = true; + written[index] = true; } File dirtyFile = entry.getDirtyFile(index); FileOutputStream outputStream; @@ -807,19 +784,17 @@ public OutputStream newOutputStream(int index) throws IOException { // Attempt to recreate the cache directory. directory.mkdirs(); try { - outputStream = new FileOutputStream(dirtyFile); + outputStream = new FileOutputStream(dirtyFile); } catch (FileNotFoundException e2) { - // We are unable to recover. Silently eat the writes. - return NULL_OUTPUT_STREAM; + // We are unable to recover. Silently eat the writes. + return NULL_OUTPUT_STREAM; } } return new FaultHidingOutputStream(outputStream); } } - /** - * Sets the value at {@code index} to {@code value}. - */ + /** Sets the value at {@code index} to {@code value}. */ public void set(int index, String value) throws IOException { Writer writer = null; try { @@ -837,7 +812,7 @@ public void set(int index, String value) throws IOException { public void commit() throws IOException { if (hasErrors) { completeEdit(this, false); - remove(entry.key); // the previous entry is stale + remove(entry.key); // The previous entry is stale. } else { completeEdit(this, true); } @@ -906,7 +881,7 @@ private final class Entry { /** Lengths of this entry's files. */ private final long[] lengths; - /** True if this entry has ever been published */ + /** True if this entry has ever been published. */ private boolean readable; /** The ongoing edit or null if this entry is not being edited. */ @@ -928,9 +903,7 @@ public String getLengths() throws IOException { return result.toString(); } - /** - * Set lengths using decimal numbers like "10123". - */ + /** Set lengths using decimal numbers like "10123". */ private void setLengths(String[] strings) throws IOException { if (strings.length != valueCount) { throw invalidLengths(strings); @@ -957,4 +930,4 @@ public File getDirtyFile(int i) { return new File(directory, key + "." + i + ".tmp"); } } -} +} \ No newline at end of file From 5841cf07fd6c230236002fd837bd6a1820dfd616 Mon Sep 17 00:00:00 2001 From: Rennie Petersen Date: Fri, 19 Jul 2013 21:48:26 +0200 Subject: [PATCH 07/13] Provide non-generic name for sample app Give the sample app the name "AndroidAsync Sample" in Android launcher, instead of the generic name "MainActivity"- --- AndroidAsyncSample/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidAsyncSample/res/values/strings.xml b/AndroidAsyncSample/res/values/strings.xml index 7004c0aa7..9c97f143b 100644 --- a/AndroidAsyncSample/res/values/strings.xml +++ b/AndroidAsyncSample/res/values/strings.xml @@ -3,6 +3,6 @@ AndroidAsyncSample Hello world! Settings - MainActivity + AndroidAsync Sample Download Images \ No newline at end of file From 9f9f30829af564af38512d6e97303b680c51b686 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Sun, 21 Jul 2013 23:39:28 -0700 Subject: [PATCH 08/13] 115 Change-Id: Ia8787cddebbc0a4da6910762f344544edb664d6b --- AndroidAsync/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AndroidAsync/AndroidManifest.xml b/AndroidAsync/AndroidManifest.xml index ef09ea01b..670f9ee29 100644 --- a/AndroidAsync/AndroidManifest.xml +++ b/AndroidAsync/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="115" + android:versionName="1.1.5" > Date: Mon, 22 Jul 2013 12:20:54 +0200 Subject: [PATCH 09/13] Specify UTF-8 charset in Content-Type The default HTTP charset is ISO-8859-1 so we need to specify UTF-8, which is what is being sent to the client. --- .../async/http/server/AsyncHttpServerResponseImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java index 1e93cd99a..b88c0c82a 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java @@ -128,7 +128,7 @@ public void send(String contentType, String string) { mContentLength = bytes.length; mRawHeaders.set("Content-Length", Integer.toString(bytes.length)); mRawHeaders.set("Content-Type", contentType); - + writeHead(); mSink.write(ByteBuffer.wrap(string.getBytes())); onEnd(); @@ -150,12 +150,12 @@ protected void report(Exception e) { @Override public void send(String string) { responseCode(200); - send("text/html", string); + send("text/html; charset=utf8", string); } @Override public void send(JSONObject json) { - send("application/json", json.toString()); + send("application/json; charset=utf8", json.toString()); } public void sendFile(File file) { From 7872cb60ae33f2c07f0f48c670be3e8be43a0c47 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Mon, 22 Jul 2013 10:20:01 -0700 Subject: [PATCH 10/13] do null writes --- AndroidAsync/src/com/koushikdutta/async/Util.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AndroidAsync/src/com/koushikdutta/async/Util.java b/AndroidAsync/src/com/koushikdutta/async/Util.java index 001f07b65..e8be273e6 100644 --- a/AndroidAsync/src/com/koushikdutta/async/Util.java +++ b/AndroidAsync/src/com/koushikdutta/async/Util.java @@ -168,8 +168,7 @@ public static void writeAll(final DataSink sink, final ByteBufferList bb, final sink.setWriteableCallback(wc = new WritableCallback() { @Override public void onWriteable() { - if (bb.remaining() > 0) - sink.write(bb); + sink.write(bb); if (bb.remaining() == 0 && callback != null) callback.onCompleted(null); } From 6d637771026fdbe33c9ddfad3dc0a7a120c6cb07 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Mon, 22 Jul 2013 12:32:30 -0700 Subject: [PATCH 11/13] add support for ws:// and wss:// --- .../src/com/koushikdutta/async/http/AsyncHttpClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java index 01df96ab6..4b2e97d6e 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java @@ -635,7 +635,7 @@ public void onConnectCompleted(Exception ex, AsyncHttpResponse response) { public Future websocket(String uri, String protocol, final WebSocketConnectCallback callback) { assert callback != null; - final AsyncHttpGet get = new AsyncHttpGet(uri); + final AsyncHttpGet get = new AsyncHttpGet(uri.replace("ws://", "http://").replace("wss://", "https://")); return websocket(get, protocol, callback); } From 2ff1c6901089e58ecd6020a3ad7342b998ad43e8 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Mon, 22 Jul 2013 13:09:41 -0700 Subject: [PATCH 12/13] socket.io reconnect should also reconnect any previously connected endpoints --- .../http/socketio/SocketIOConnection.java | 24 ++++++-- .../async/test/SocketIOTests.java | 55 +++++++++++++++---- 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java index b2e1e2ee4..4f30271bc 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java @@ -3,17 +3,13 @@ import android.os.Handler; import android.text.TextUtils; -import com.koushikdutta.async.AsyncServer; import com.koushikdutta.async.NullDataCallback; import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.future.Cancellable; import com.koushikdutta.async.future.DependentCancellable; -import com.koushikdutta.async.future.Future; -import com.koushikdutta.async.future.SimpleFuture; import com.koushikdutta.async.http.AsyncHttpClient; import com.koushikdutta.async.http.AsyncHttpResponse; import com.koushikdutta.async.http.WebSocket; -import com.koushikdutta.async.http.server.AsyncHttpServer; import org.json.JSONArray; import org.json.JSONObject; @@ -94,12 +90,15 @@ void reconnect(final DependentCancellable child) { return; } + request.logi("Reconnecting socket.io"); + // dont invoke onto main handler, as it is unnecessary until a session is ready or failed request.setHandler(null); // initiate a session Cancellable cancel = httpClient.executeString(request, new AsyncHttpClient.StringCallback() { @Override public void onCompleted(final Exception e, AsyncHttpResponse response, String result) { + request.logi("socket.io session received"); if (e != null) { reportDisconnect(e); return; @@ -202,6 +201,12 @@ public void run() { long reconnectDelay = 1000L; private void reportDisconnect(final Exception ex) { + if (ex != null) { + request.loge("socket.io disconnected", ex); + } + else { + request.logi("socket.io disconnected"); + } select(null, new SelectCallback() { @Override public void onSelect(SocketIOClient client) { @@ -393,5 +398,16 @@ public void onStringAvailable(String message) { } } }); + + // now reconnect all the sockets that may have been previously connected + select(null, new SelectCallback() { + @Override + public void onSelect(SocketIOClient client) { + if (TextUtils.isEmpty(client.endpoint)) + return; + + connect(client); + } + }); } } diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java b/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java index c98735d0f..e2ae3fff8 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java +++ b/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java @@ -25,8 +25,13 @@ public class SocketIOTests extends TestCase { public static final long TIMEOUT = 10000L; - - + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + AsyncServer.getDefault().stop(); + } + class TriggerFuture extends SimpleFuture { public void trigger(boolean val) { setComplete(val); @@ -88,7 +93,7 @@ public void onString(String string, Acknowledge acknowledge) { }); assertTrue(trigger.get(TIMEOUT, TimeUnit.MILLISECONDS)); } - + public void testEchoServer() throws Exception { final TriggerFuture trigger1 = new TriggerFuture(); final TriggerFuture trigger2 = new TriggerFuture(); @@ -136,12 +141,44 @@ public void onJSON(JSONObject json, Acknowledge acknowledge) { public void testReconnect() throws Exception { final TriggerFuture disconnectTrigger = new TriggerFuture(); final TriggerFuture reconnectTrigger = new TriggerFuture(); + final TriggerFuture endpointReconnectTrigger = new TriggerFuture(); + final TriggerFuture echoTrigger = new TriggerFuture(); - SocketIOClient.connect(AsyncHttpClient.getDefaultInstance(), "http://koush.clockworkmod.com:8080", new ConnectCallback() { + SocketIORequest req = new SocketIORequest("http://koush.clockworkmod.com:8080"); + req.setLogging("socket.io", Log.VERBOSE); + SocketIOClient.connect(AsyncHttpClient.getDefaultInstance(), req, new ConnectCallback() { @Override public void onConnectCompleted(Exception ex, final SocketIOClient client) { assertNull(ex); + client.of("/chat", new ConnectCallback() { + @Override + public void onConnectCompleted(Exception ex, final SocketIOClient client) { + client.setReconnectCallback(new ReconnectCallback() { + @Override + public void onReconnect() { + client.emit("hello"); + endpointReconnectTrigger.trigger(true); + } + }); + + client.setStringCallback(new StringCallback() { + @Override + public void onString(String string, Acknowledge acknowledge) { + echoTrigger.trigger("hello".equals(string)); + } + }); + + AsyncServer.getDefault().postDelayed(new Runnable() { + @Override + public void run() { + // this will trigger a reconnect + client.getWebSocket().close(); + } + }, 200); + } + }); + client.setDisconnectCallback(new DisconnectCallback() { @Override public void onDisconnect(Exception e) { @@ -155,19 +192,13 @@ public void onReconnect() { reconnectTrigger.trigger(true); } }); - - AsyncServer.getDefault().postDelayed(new Runnable() { - @Override - public void run() { - // this will trigger a reconnect - client.getWebSocket().close(); - } - }, 200); } }); assertTrue(disconnectTrigger.get(TIMEOUT, TimeUnit.MILLISECONDS)); assertTrue(reconnectTrigger.get(TIMEOUT, TimeUnit.MILLISECONDS)); + assertTrue(endpointReconnectTrigger.get(TIMEOUT, TimeUnit.MILLISECONDS)); + assertTrue(echoTrigger.get(TIMEOUT, TimeUnit.MILLISECONDS)); } public void testEventAck() throws Exception { From c463e3f85c5fd06f56c30e0d3f1e72920a2d6ec8 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Mon, 22 Jul 2013 13:43:56 -0700 Subject: [PATCH 13/13] add reconnect api --- .../com/koushikdutta/async/future/Future.java | 1 + .../async/future/SimpleFuture.java | 7 ++ .../async/http/socketio/SocketIOClient.java | 4 + .../http/socketio/SocketIOConnection.java | 81 +++++++++---------- 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/AndroidAsync/src/com/koushikdutta/async/future/Future.java b/AndroidAsync/src/com/koushikdutta/async/future/Future.java index 4bca39db4..efccf0cf8 100644 --- a/AndroidAsync/src/com/koushikdutta/async/future/Future.java +++ b/AndroidAsync/src/com/koushikdutta/async/future/Future.java @@ -8,4 +8,5 @@ public interface Future extends Cancellable, java.util.concurrent.Future { * @return */ public Future setCallback(FutureCallback callback); + public > C then(C callback); } diff --git a/AndroidAsync/src/com/koushikdutta/async/future/SimpleFuture.java b/AndroidAsync/src/com/koushikdutta/async/future/SimpleFuture.java index a3a67d21a..dde77a066 100644 --- a/AndroidAsync/src/com/koushikdutta/async/future/SimpleFuture.java +++ b/AndroidAsync/src/com/koushikdutta/async/future/SimpleFuture.java @@ -151,6 +151,13 @@ public SimpleFuture setCallback(FutureCallback callback) { return this; } + @Override + public > C then(C callback) { + callback.setParent(this); + setCallback(callback); + return callback; + } + @Override public SimpleFuture setParent(Cancellable parent) { super.setParent(parent); diff --git a/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOClient.java b/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOClient.java index bc4c09215..06a3d8cb6 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOClient.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOClient.java @@ -174,6 +174,10 @@ public void of(String endpoint, ConnectCallback connectCallback) { connection.connect(new SocketIOClient(connection, endpoint, connectCallback)); } + public void reconnect() { + connection.reconnect(null); + } + public WebSocket getWebSocket() { return connection.webSocket; } diff --git a/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java index 4f30271bc..e6a7ca49d 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java @@ -7,6 +7,8 @@ import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.future.Cancellable; import com.koushikdutta.async.future.DependentCancellable; +import com.koushikdutta.async.future.FutureCallback; +import com.koushikdutta.async.future.TransformFuture; import com.koushikdutta.async.http.AsyncHttpClient; import com.koushikdutta.async.http.AsyncHttpResponse; import com.koushikdutta.async.http.WebSocket; @@ -85,66 +87,63 @@ public void disconnect(SocketIOClient client) { webSocket = null; } + Cancellable connecting; void reconnect(final DependentCancellable child) { if (isConnected()) { return; } + // if a connection is in progress, just wait. + if (connecting != null && !connecting.isDone() && !connecting.isCancelled()) { + if (child != null) + child.setParent(connecting); + return; + } + request.logi("Reconnecting socket.io"); // dont invoke onto main handler, as it is unnecessary until a session is ready or failed request.setHandler(null); - // initiate a session - Cancellable cancel = httpClient.executeString(request, new AsyncHttpClient.StringCallback() { + + Cancellable connecting = httpClient.executeString(request) + .then(new TransformFuture() { @Override - public void onCompleted(final Exception e, AsyncHttpResponse response, String result) { - request.logi("socket.io session received"); + protected void transform(String result) throws Exception { + 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 set = new HashSet(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, null) + .setCallback(getCompletionCallback()); + } + }) + .setCallback(new FutureCallback() { + @Override + public void onCompleted(Exception e, WebSocket 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 set = new HashSet(Arrays.asList(transports)); - if (!set.contains("websocket")) - throw new Exception("websocket not supported"); - - final String sessionUrl = request.getUri().toString() + "websocket/" + session + "/"; - - Cancellable cancel = 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(); - } - }); - - if (child != null) - child.setParent(cancel); - } - catch (Exception ex) { - reportDisconnect(ex); - } + reconnectDelay = 1000L; + SocketIOConnection.this.webSocket = result; + attach(); } }); if (child != null) - child.setParent(cancel); + child.setParent(connecting); } void setupHeartbeat() {