diff options
author | cretin45 <cretin45@gmail.com> | 2014-09-09 12:13:07 -0700 |
---|---|---|
committer | cretin45 <cretin45@gmail.com> | 2014-09-09 14:26:09 -0700 |
commit | 2ac87dd1859f3ca4b7bb7709c5d24091d6d889f3 (patch) | |
tree | b09221245098c938ca99cf021ba15dd89032985e | |
parent | d7c7470a2599a6f76b7f92ed8015b0ec1b1e45c2 (diff) | |
parent | 0b99866b2b74a37a419183e1ff4b1c59974cfa8d (diff) | |
download | AndroidAsync-2ac87dd1859f3ca4b7bb7709c5d24091d6d889f3.tar.gz AndroidAsync-2ac87dd1859f3ca4b7bb7709c5d24091d6d889f3.tar.bz2 AndroidAsync-2ac87dd1859f3ca4b7bb7709c5d24091d6d889f3.zip |
Merge upstream into cm-11.0
Change-Id: I304cfea73e62d7a78d4eb1e0a766d6bb1666476f
91 files changed, 959 insertions, 610 deletions
@@ -4,3 +4,9 @@ local.properties gen .gradle build +.idea/ +.DS_Store + +okhttp +okio +libs diff --git a/AndroidAsync/Android.mk b/AndroidAsync/Android.mk index bf03dcb..e32c65c 100644 --- a/AndroidAsync/Android.mk +++ b/AndroidAsync/Android.mk @@ -19,7 +19,6 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := AndroidAsync -LOCAL_SDK_VERSION := 8 LOCAL_SRC_FILES := $(call all-java-files-under, src) include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/AndroidAsync/AndroidAsync-AndroidAsync.iml b/AndroidAsync/AndroidAsync-AndroidAsync.iml new file mode 100644 index 0000000..e506543 --- /dev/null +++ b/AndroidAsync/AndroidAsync-AndroidAsync.iml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/../.." external.system.id="GRADLE" external.system.module.group="Gradle.AndroidAsync" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> + <component name="FacetManager"> + <facet type="android-gradle" name="Android-Gradle"> + <configuration> + <option name="GRADLE_PROJECT_PATH" value=":AndroidAsync:AndroidAsync" /> + </configuration> + </facet> + <facet type="android" name="Android"> + <configuration> + <option name="SELECTED_BUILD_VARIANT" value="debug" /> + <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> + <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugJava" /> + <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" /> + <option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" /> + <option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugTestSources" /> + <option name="ALLOW_USER_CONFIGURATION" value="false" /> + <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" /> + <option name="RES_FOLDERS_RELATIVE_PATH" value="" /> + <option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" /> + <option name="LIBRARY_PROJECT" value="true" /> + </configuration> + </facet> + </component> + <component name="NewModuleRootManager" inherit-compiler-output="false"> + <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/test/debug" isTestSource="true" generated="true" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/test/debug" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/test/res" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/test/assets" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/test/src" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" /> + <excludeFolder url="file://$MODULE_DIR$/build/libs" /> + <excludeFolder url="file://$MODULE_DIR$/build/outputs" /> + <excludeFolder url="file://$MODULE_DIR$/build/poms" /> + <excludeFolder url="file://$MODULE_DIR$/build/tmp" /> + </content> + <orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> + diff --git a/AndroidAsync/AndroidAsync.iml b/AndroidAsync/AndroidAsync.iml index 8c69f52..edb7738 100644 --- a/AndroidAsync/AndroidAsync.iml +++ b/AndroidAsync/AndroidAsync.iml @@ -4,6 +4,9 @@ <facet type="android" name="Android"> <configuration> <option name="LIBRARY_PROJECT" value="true" /> + <proGuardCfgFiles> + <file>file://$APPLICATION_HOME_DIR$/sdk/tools/proguard/proguard-android.txt</file> + </proGuardCfgFiles> <option name="UPDATE_PROPERTY_FILES" value="true" /> <notImportedProperties> <property>MANIFEST_FILE_PATH</property> diff --git a/AndroidAsync/AndroidManifest.xml b/AndroidAsync/AndroidManifest.xml index 4496f74..ae615c1 100644 --- a/AndroidAsync/AndroidManifest.xml +++ b/AndroidAsync/AndroidManifest.xml @@ -1,15 +1,12 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" package="com.koushikdutta.async" - android:versionCode="125" - android:versionName="1.2.5" > + android:versionCode="138" + android:versionName="1.3.8"> <uses-sdk - android:minSdkVersion="8" - android:targetSdkVersion="18" /> + tools:node="replace" /> <uses-permission android:name="android.permission.INTERNET"/> - <uses-permission android:name="android.permission.READ_LOGS"/> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <application> </application> diff --git a/AndroidAsync/build.gradle b/AndroidAsync/build.gradle index 9f7f19d..0a1985d 100644 --- a/AndroidAsync/build.gradle +++ b/AndroidAsync/build.gradle @@ -3,36 +3,42 @@ buildscript { maven { url 'http://repo1.maven.org/maven2' } } dependencies { - classpath 'com.android.tools.build:gradle:0.10.+' + classpath 'com.android.tools.build:gradle:+' } } -apply plugin: 'android-library' - -dependencies { - compile 'com.android.support:support-v4:13.0.0' -} +apply plugin: 'com.android.library' android { - sourceSets { - main { - manifest.srcFile 'AndroidManifest.xml' - - java { - srcDir 'src/' - } - } - } + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' - compileSdkVersion 19 - buildToolsVersion "19.0.3" + jniLibs.srcDirs = ['libs/'] + + java.srcDirs=['src/' +// , '../conscrypt/' +// , '../compat/' + ] + } + androidTest.java.srcDirs=['test/src/'] + androidTest.res.srcDirs=['test/res/'] + androidTest.assets.srcDirs=['test/assets/'] + } + + lintOptions { + abortOnError false + } defaultConfig { - minSdkVersion 7 + minSdkVersion 9 targetSdkVersion 19 } + + compileSdkVersion 19 + buildToolsVersion "20.0.0" } // upload to maven task if (System.getenv().I_AM_KOUSH == 'true') { - apply from: 'https://raw.github.com/koush/mvn-repo/master/maven.gradle' + apply from: 'https://raw.githubusercontent.com/koush/mvn-repo/master/maven.gradle' } diff --git a/AndroidAsync/project.properties b/AndroidAsync/project.properties index 91d2b02..edc832b 100644 --- a/AndroidAsync/project.properties +++ b/AndroidAsync/project.properties @@ -13,3 +13,5 @@ # Project target. target=android-19 android.library=true + + diff --git a/AndroidAsync/src/com/koushikdutta/async/AsyncNetworkSocket.java b/AndroidAsync/src/com/koushikdutta/async/AsyncNetworkSocket.java index 2677960..19fd2c3 100644 --- a/AndroidAsync/src/com/koushikdutta/async/AsyncNetworkSocket.java +++ b/AndroidAsync/src/com/koushikdutta/async/AsyncNetworkSocket.java @@ -5,6 +5,7 @@ import android.util.Log; import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.callback.DataCallback; import com.koushikdutta.async.callback.WritableCallback; +import com.koushikdutta.async.util.Allocator; import java.io.IOException; import java.net.InetSocketAddress; @@ -29,7 +30,7 @@ public class AsyncNetworkSocket implements AsyncSocket { InetSocketAddress socketAddress; void attach(SocketChannel channel, InetSocketAddress socketAddress) throws IOException { this.socketAddress = socketAddress; - maxAlloc = 256 * 1024; // 256K + allocator = new Allocator(); mChannel = new SocketChannelWrapper(channel); } @@ -37,7 +38,7 @@ public class AsyncNetworkSocket implements AsyncSocket { mChannel = new DatagramChannelWrapper(channel); // keep udp at roughly the mtu, which is 1540 or something // letting it grow freaks out nio apparently. - maxAlloc = 8192; + allocator = new Allocator(8192); } ChannelWrapper getChannel() { @@ -136,8 +137,7 @@ public class AsyncNetworkSocket implements AsyncSocket { private ByteBufferList pending = new ByteBufferList(); // private ByteBuffer[] buffers = new ByteBuffer[8]; - int maxAlloc; - int mToAlloc = 0; + Allocator allocator; int onReadable() { spitPending(); // even if the socket is paused, @@ -150,7 +150,7 @@ public class AsyncNetworkSocket implements AsyncSocket { boolean closed = false; // ByteBufferList.obtainArray(buffers, Math.min(Math.max(mToAlloc, 2 << 11), maxAlloc)); - ByteBuffer b = ByteBufferList.obtain(Math.min(Math.max(mToAlloc, 2 << 11), maxAlloc)); + ByteBuffer b = allocator.allocate(); // keep track of the max mount read during this read cycle // so we can be quicker about allocations during the next // time this socket reads. @@ -163,7 +163,7 @@ public class AsyncNetworkSocket implements AsyncSocket { total += read; } if (read > 0) { - mToAlloc = (int)read * 2; + allocator.track(read); b.flip(); // for (int i = 0; i < buffers.length; i++) { // ByteBuffer b = buffers[i]; @@ -174,6 +174,9 @@ public class AsyncNetworkSocket implements AsyncSocket { pending.add(b); Util.emitAllData(this, pending); } + else { + ByteBufferList.reclaim(b); + } if (closed) { reportEndPending(null); @@ -361,4 +364,9 @@ public class AsyncNetworkSocket implements AsyncSocket { public Object getSocket() { return getChannel().getSocket(); } + + @Override + public String charset() { + return null; + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocket.java b/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocket.java index e45d9c0..ba52642 100644 --- a/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocket.java +++ b/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocket.java @@ -2,6 +2,9 @@ package com.koushikdutta.async; import java.security.cert.X509Certificate; +import javax.net.ssl.SSLEngine; + public interface AsyncSSLSocket extends AsyncSocket { public X509Certificate[] getPeerCertificates(); + public SSLEngine getSSLEngine(); } diff --git a/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java b/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java index ff45a61..0c53f56 100644 --- a/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java +++ b/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java @@ -5,6 +5,7 @@ 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.util.Allocator; import com.koushikdutta.async.wrapper.AsyncSocketWrapper; import org.apache.http.conn.ssl.StrictHostnameVerifier; @@ -27,40 +28,103 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket { + public interface HandshakeCallback { + public void onHandshakeCompleted(Exception e, AsyncSSLSocket socket); + } + + static SSLContext defaultSSLContext; + AsyncSocket mSocket; BufferedDataEmitter mEmitter; BufferedDataSink mSink; - ByteBuffer mReadTmp = ByteBufferList.obtain(8192); - boolean mUnwrapping = false; + boolean mUnwrapping; + SSLEngine engine; + boolean finishedHandshake; + private int mPort; + private String mHost; + private boolean mWrapping; HostnameVerifier hostnameVerifier; + HandshakeCallback handshakeCallback; + X509Certificate[] peerCertificates; + WritableCallback mWriteableCallback; + DataCallback mDataCallback; + TrustManager[] trustManagers; + boolean clientMode; - @Override - public void end() { - mSocket.end(); + static { + // following is the "trust the system" certs setup + try { + // critical extension 2.5.29.15 is implemented improperly prior to 4.0.3. + // https://code.google.com/p/android/issues/detail?id=9307 + // https://groups.google.com/forum/?fromgroups=#!topic/netty/UCfqPPk5O4s + // certs that use this extension will throw in Cipher.java. + // fallback is to use a custom SSLContext, and hack around the x509 extension. + if (Build.VERSION.SDK_INT <= 15) + throw new Exception(); + defaultSSLContext = SSLContext.getInstance("Default"); + } + catch (Exception ex) { + try { + defaultSSLContext = SSLContext.getInstance("TLS"); + TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { + for (X509Certificate cert : certs) { + if (cert != null && cert.getCriticalExtensionOIDs() != null) + cert.getCriticalExtensionOIDs().remove("2.5.29.15"); + } + } + } }; + defaultSSLContext.init(null, trustAllCerts, null); + } + catch (Exception ex2) { + ex.printStackTrace(); + ex2.printStackTrace(); + } + } } - public AsyncSSLSocketWrapper(AsyncSocket socket, String host, int port) { - this(socket, host, port, sslContext, null, null, true); + public static SSLContext getDefaultSSLContext() { + return defaultSSLContext; } - TrustManager[] trustManagers; - boolean clientMode; + public static void handshake(AsyncSocket socket, + String host, int port, + SSLEngine sslEngine, + TrustManager[] trustManagers, HostnameVerifier verifier, boolean clientMode, + final HandshakeCallback callback) { + AsyncSSLSocketWrapper wrapper = new AsyncSSLSocketWrapper(socket, host, port, sslEngine, trustManagers, verifier, clientMode); + wrapper.handshakeCallback = callback; + socket.setClosedCallback(new CompletedCallback() { + @Override + public void onCompleted(Exception ex) { + callback.onHandshakeCompleted(new SSLException(ex), null); + } + }); + try { + wrapper.engine.beginHandshake(); + wrapper.handleHandshakeStatus(wrapper.engine.getHandshakeStatus()); + } catch (SSLException e) { + wrapper.report(e); + } + } - public AsyncSSLSocketWrapper(AsyncSocket socket, String host, int port, SSLContext sslContext, TrustManager[] trustManagers, HostnameVerifier verifier, boolean clientMode) { + private AsyncSSLSocketWrapper(AsyncSocket socket, + String host, int port, + SSLEngine sslEngine, + TrustManager[] trustManagers, HostnameVerifier verifier, boolean clientMode) { mSocket = socket; hostnameVerifier = verifier; this.clientMode = clientMode; this.trustManagers = trustManagers; + this.engine = sslEngine; - if (sslContext == null) - sslContext = AsyncSSLSocketWrapper.sslContext; - - if (host != null) { - engine = sslContext.createSSLEngine(host, port); - } - else { - engine = sslContext.createSSLEngine(); - } mHost = host; mPort = port; engine.setUseClientMode(clientMode); @@ -77,6 +141,8 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket // aka exhcange.setDatacallback mEmitter = new BufferedDataEmitter(socket); + final Allocator allocator = new Allocator(); + allocator.setMinAlloc(8192); final ByteBufferList transformed = new ByteBufferList(); mEmitter.setDataCallback(new DataCallback() { @Override @@ -86,8 +152,10 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket try { mUnwrapping = true; - mReadTmp.position(0); - mReadTmp.limit(mReadTmp.capacity()); + if (bb.hasRemaining()) { + ByteBuffer all = bb.getAll(); + bb.add(all); + } ByteBuffer b = ByteBufferList.EMPTY_BYTEBUFFER; while (true) { @@ -95,11 +163,18 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket b = bb.remove(); } int remaining = b.remaining(); - - SSLEngineResult res = engine.unwrap(b, mReadTmp); + int before = transformed.remaining(); + + SSLEngineResult res; + { + // wrap to prevent access to the readBuf + ByteBuffer readBuf = allocator.allocate(); + res = engine.unwrap(b, readBuf); + addToPending(transformed, readBuf); + allocator.track(transformed.remaining() - before); + } if (res.getStatus() == Status.BUFFER_OVERFLOW) { - addToPending(transformed); - mReadTmp = ByteBufferList.obtain(mReadTmp.remaining() * 2); + allocator.setMinAlloc(allocator.getMinAlloc() * 2); remaining = -1; } else if (res.getStatus() == Status.BUFFER_UNDERFLOW) { @@ -113,14 +188,13 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket bb.addFirst(b); b = ByteBufferList.EMPTY_BYTEBUFFER; } - handleResult(res); - if (b.remaining() == remaining) { + handleHandshakeStatus(res.getHandshakeStatus()); + if (b.remaining() == remaining && before == transformed.remaining()) { bb.addFirst(b); break; } } - addToPending(transformed); Util.emitAllData(AsyncSSLSocketWrapper.this, transformed); } catch (SSLException ex) { @@ -134,81 +208,46 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket }); } - void addToPending(ByteBufferList out) { - if (mReadTmp.position() > 0) { - mReadTmp.flip(); - out.add(mReadTmp); - mReadTmp = ByteBufferList.obtain(mReadTmp.capacity()); - } + @Override + public SSLEngine getSSLEngine() { + return engine; } - static SSLContext sslContext; - - static { - // following is the "trust the system" certs setup - try { - // critical extension 2.5.29.15 is implemented improperly prior to 4.0.3. - // https://code.google.com/p/android/issues/detail?id=9307 - // https://groups.google.com/forum/?fromgroups=#!topic/netty/UCfqPPk5O4s - // certs that use this extension will throw in Cipher.java. - // fallback is to use a custom SSLContext, and hack around the x509 extension. - if (Build.VERSION.SDK_INT <= 15) - throw new Exception(); - sslContext = SSLContext.getInstance("Default"); + void addToPending(ByteBufferList out, ByteBuffer mReadTmp) { + mReadTmp.flip(); + if (mReadTmp.hasRemaining()) { + out.add(mReadTmp); } - catch (Exception ex) { - try { - sslContext = SSLContext.getInstance("TLS"); - TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - - public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { - } - - public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { - for (X509Certificate cert : certs) { - if (cert != null && cert.getCriticalExtensionOIDs() != null) - cert.getCriticalExtensionOIDs().remove("2.5.29.15"); - } - } - } }; - sslContext.init(null, trustAllCerts, null); - } - catch (Exception ex2) { - ex.printStackTrace(); - ex2.printStackTrace(); - } + else { + ByteBufferList.reclaim(mReadTmp); } } - SSLEngine engine; - boolean finishedHandshake = false; - private String mHost; + @Override + public void end() { + mSocket.end(); + } public String getHost() { return mHost; } - private int mPort; - public int getPort() { return mPort; } - private void handleResult(SSLEngineResult res) { - if (res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { + private void handleHandshakeStatus(HandshakeStatus status) { + if (status == HandshakeStatus.NEED_TASK) { final Runnable task = engine.getDelegatedTask(); task.run(); } - if (res.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) { + if (status == HandshakeStatus.NEED_WRAP) { write(ByteBufferList.EMPTY_BYTEBUFFER); } - if (res.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) { + if (status == HandshakeStatus.NEED_UNWRAP) { mEmitter.onDataAvailable(); } @@ -255,6 +294,11 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket throw e; } } + else { + finishedHandshake = true; + } + handshakeCallback.onHandshakeCompleted(null, this); + handshakeCallback = null; if (mWriteableCallback != null) mWriteableCallback.onWriteable(); mEmitter.onDataAvailable(); @@ -278,7 +322,6 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket assert !mWriteTmp.hasRemaining(); } - private boolean mWrapping = false; int calculateAlloc(int remaining) { // alloc 50% more than we need for writing @@ -320,7 +363,7 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket else { mWriteTmp = ByteBufferList.obtain(calculateAlloc(bb.remaining())); } - handleResult(res); + handleHandshakeStatus(res.getHandshakeStatus()); } catch (SSLException e) { report(e); @@ -345,10 +388,8 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket // if the handshake is finished, don't send // 0 bytes of data, since that makes the ssl connection die. // it wraps a 0 byte package, and craps out. - if (finishedHandshake && bb.remaining() == 0) { - mWrapping = false; - return; - } + if (finishedHandshake && bb.remaining() == 0) + break; remaining = bb.remaining(); try { ByteBuffer[] arr = bb.getAllArray(); @@ -364,8 +405,8 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket } else { mWriteTmp = ByteBufferList.obtain(calculateAlloc(bb.remaining())); + handleHandshakeStatus(res.getHandshakeStatus()); } - handleResult(res); } catch (SSLException e) { report(e); @@ -376,8 +417,6 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket mWrapping = false; } - WritableCallback mWriteableCallback; - @Override public void setWriteableCallback(WritableCallback handler) { mWriteableCallback = handler; @@ -389,13 +428,21 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket } private void report(Exception e) { + final HandshakeCallback hs = handshakeCallback; + if (hs != null) { + handshakeCallback = null; + mSocket.setDataCallback(new NullDataCallback()); + mSocket.end(); + mSocket.close(); + hs.onHandshakeCompleted(e, null); + return; + } + CompletedCallback cb = getEndCallback(); if (cb != null) cb.onCompleted(e); } - DataCallback mDataCallback; - @Override public void setDataCallback(DataCallback callback) { mDataCallback = callback; @@ -471,10 +518,13 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket return mSocket; } - X509Certificate[] peerCertificates; - @Override public X509Certificate[] getPeerCertificates() { return peerCertificates; } + + @Override + public String charset() { + return null; + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/AsyncServer.java b/AndroidAsync/src/com/koushikdutta/async/AsyncServer.java index b5dce58..be4a363 100644 --- a/AndroidAsync/src/com/koushikdutta/async/AsyncServer.java +++ b/AndroidAsync/src/com/koushikdutta/async/AsyncServer.java @@ -126,7 +126,12 @@ public class AsyncServer { synchronousWorkers.execute(new Runnable() { @Override public void run() { - selector.wakeupOnce(); + try { + selector.wakeupOnce(); + } + catch (Exception e) { + Log.i(LOGTAG, "Selector Exception? L Preview?"); + } } }); } @@ -224,7 +229,9 @@ public class AsyncServer { // Log.i(LOGTAG, "****AsyncServer is shutting down.****"); final SelectorWrapper currentSelector; final Semaphore semaphore; + final boolean isAffinityThread; synchronized (this) { + isAffinityThread = isAffinityThread(); currentSelector = mSelector; if (currentSelector == null) return; @@ -251,7 +258,8 @@ public class AsyncServer { mAffinity = null; } try { - semaphore.acquire(); + if (!isAffinityThread) + semaphore.acquire(); } catch (Exception e) { } @@ -600,6 +608,10 @@ public class AsyncServer { runLoop(server, selector, queue); } catch (ClosedSelectorException e) { + StreamUtility.closeQuietly(selector.getSelector()); + } + catch (AsyncSelectorException e) { + StreamUtility.closeQuietly(selector.getSelector()); } // see if we keep looping, this must be in a synchronized block since the queue is accessed. synchronized (server) { @@ -719,6 +731,9 @@ public class AsyncServer { } } } + catch (NullPointerException e) { + throw new AsyncSelectorException(e); + } catch (IOException e) { throw new AsyncSelectorException(e); } diff --git a/AndroidAsync/src/com/koushikdutta/async/BufferedDataEmitter.java b/AndroidAsync/src/com/koushikdutta/async/BufferedDataEmitter.java index 7d238b4..5c88b31 100644 --- a/AndroidAsync/src/com/koushikdutta/async/BufferedDataEmitter.java +++ b/AndroidAsync/src/com/koushikdutta/async/BufferedDataEmitter.java @@ -32,7 +32,7 @@ public class BufferedDataEmitter implements DataEmitter, DataCallback { if (mDataCallback != null && !mPaused && mBuffers.remaining() > 0) mDataCallback.onDataAvailable(this, mBuffers); - if (mEnded && mBuffers.remaining() == 0) + if (mEnded && mBuffers.remaining() == 0 && mEndCallback != null) mEndCallback.onCompleted(mEndException); } @@ -96,4 +96,9 @@ public class BufferedDataEmitter implements DataEmitter, DataCallback { public AsyncServer getServer() { return mEmitter.getServer(); } + + @Override + public String charset() { + return mEmitter.charset(); + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/BufferedDataSink.java b/AndroidAsync/src/com/koushikdutta/async/BufferedDataSink.java index 096be5b..ed4f87f 100644 --- a/AndroidAsync/src/com/koushikdutta/async/BufferedDataSink.java +++ b/AndroidAsync/src/com/koushikdutta/async/BufferedDataSink.java @@ -36,8 +36,6 @@ public class BufferedDataSink implements DataSink { if (mPendingWrites.remaining() == 0) { if (endPending) mDataSink.end(); - if (closePending) - mDataSink.close(); } } if (!mPendingWrites.hasRemaining() && mWritable != null) @@ -113,16 +111,11 @@ public class BufferedDataSink implements DataSink { @Override public boolean isOpen() { - return !closePending && mDataSink.isOpen(); + return mDataSink.isOpen(); } - boolean closePending; @Override public void close() { - if (mPendingWrites.hasRemaining()) { - closePending = true; - return; - } mDataSink.close(); } diff --git a/AndroidAsync/src/com/koushikdutta/async/ByteBufferList.java b/AndroidAsync/src/com/koushikdutta/async/ByteBufferList.java index 794b74e..fe5b50d 100644 --- a/AndroidAsync/src/com/koushikdutta/async/ByteBufferList.java +++ b/AndroidAsync/src/com/koushikdutta/async/ByteBufferList.java @@ -1,15 +1,20 @@ package com.koushikdutta.async; +import android.annotation.TargetApi; +import android.os.Build; import android.os.Looper; -import java.nio.Buffer; +import com.koushikdutta.async.util.Charsets; + +import java.io.IOException; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.ArrayList; +import java.nio.charset.Charset; import java.util.Comparator; -import java.util.LinkedList; import java.util.PriorityQueue; +@TargetApi(Build.VERSION_CODES.GINGERBREAD) public class ByteBufferList { ArrayDeque<ByteBuffer> mBuffers = new ArrayDeque<ByteBuffer>(); @@ -44,9 +49,12 @@ public class ByteBufferList { public byte[] getAllByteArray() { // fast path to return the contents of the first and only byte buffer, // if that's what we're looking for. avoids allocation. - if (mBuffers.size() == 1 && mBuffers.peek().capacity() == remaining()) { - remaining = 0; - return mBuffers.remove().array(); + if (mBuffers.size() == 1) { + ByteBuffer peek = mBuffers.peek(); + if (peek.capacity() == remaining() && peek.isDirect()) { + remaining = 0; + return mBuffers.remove().array(); + } } byte[] ret = new byte[remaining()]; @@ -75,7 +83,25 @@ public class ByteBufferList { public boolean hasRemaining() { return remaining() > 0; } - + + public short peekShort() { + return read(2).duplicate().getShort(); + } + + public int peekInt() { + return read(4).duplicate().getInt(); + } + + public long peekLong() { + return read(8).duplicate().getLong(); + } + + public byte[] peekBytes(int size) { + byte[] ret = new byte[size]; + read(size).duplicate().get(ret); + return ret; + } + public int getInt() { int ret = read(4).getInt(); remaining -= 4; @@ -203,46 +229,7 @@ public class ByteBufferList { return first.order(order); } - ByteBuffer ret = null; - int retOffset = 0; - int allocSize = 0; - - // attempt to find a buffer that can fit this, and the necessary - // alloc size to not leave anything leftover in the final buffer. - for (ByteBuffer b: mBuffers) { - if (allocSize >= count) - break; - // see if this fits... - if ((ret == null || b.capacity() > ret.capacity()) && b.capacity() >= count) { - ret = b; - retOffset = allocSize; - } - allocSize += b.remaining(); - } - - if (ret != null && ret.capacity() > allocSize) { - // move the current contents of the target bytebuffer around to its final position - System.arraycopy(ret.array(), ret.arrayOffset() + ret.position(), ret.array(), ret.arrayOffset() + retOffset, ret.remaining()); - int retRemaining = ret.remaining(); - ret.position(0); - ret.limit(allocSize); - allocSize = 0; - while (allocSize < count) { - ByteBuffer b = mBuffers.remove(); - if (b != ret) { - System.arraycopy(b.array(), b.arrayOffset() + b.position(), ret.array(), ret.arrayOffset() + allocSize, b.remaining()); - allocSize += b.remaining(); - reclaim(b); - } - else { - allocSize += retRemaining; - } - } - mBuffers.addFirst(ret); - return ret.order(order); - } - - ret = obtain(count); + ByteBuffer ret = obtain(count); ret.limit(count); byte[] bytes = ret.array(); int offset = 0; @@ -346,24 +333,43 @@ public class ByteBufferList { System.out.println(peekString()); } - // not doing toString as this is really nasty in the debugger... public String peekString() { + return peekString(null); + } + + // not doing toString as this is really nasty in the debugger... + public String peekString(Charset charset) { + if (charset == null) + charset = Charsets.US_ASCII; StringBuilder builder = new StringBuilder(); for (ByteBuffer bb: mBuffers) { - builder.append(new String(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining())); + byte[] bytes; + int offset; + int length; + if (bb.isDirect()) { + bytes = new byte[bb.remaining()]; + offset = 0; + length = bb.remaining(); + bb.get(bytes); + } + else { + bytes = bb.array(); + offset = bb.arrayOffset() + bb.position(); + length = bb.remaining(); + } + builder.append(new String(bytes, offset, length, charset)); } return builder.toString(); } public String readString() { - StringBuilder builder = new StringBuilder(); - while (mBuffers.size() > 0) { - ByteBuffer bb = mBuffers.remove(); - builder.append(new String(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining())); - reclaim(bb); - } - remaining = 0; - return builder.toString(); + return readString(null); + } + + public String readString(Charset charset) { + String ret = peekString(charset); + recycle(); + return ret; } static class Reclaimer implements Comparator<ByteBuffer> { @@ -387,7 +393,7 @@ public class ByteBufferList { } private static int MAX_SIZE = 1024 * 1024; - private static int MAX_ITEM_SIZE = 1024 * 256; + public static int MAX_ITEM_SIZE = 1024 * 256; static int currentSize = 0; static int maxItem = 0; @@ -503,4 +509,22 @@ public class ByteBufferList { } public static final ByteBuffer EMPTY_BYTEBUFFER = ByteBuffer.allocate(0); + + public static void writeOutputStream(OutputStream out, ByteBuffer b) throws IOException { + byte[] bytes; + int offset; + int length; + if (b.isDirect()) { + bytes = new byte[b.remaining()]; + offset = 0; + length = b.remaining(); + b.get(bytes); + } + else { + bytes = b.array(); + offset = b.arrayOffset() + b.position(); + length = b.remaining(); + } + out.write(bytes, offset, length); + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/DataEmitter.java b/AndroidAsync/src/com/koushikdutta/async/DataEmitter.java index 2574901..cf8ac2a 100644 --- a/AndroidAsync/src/com/koushikdutta/async/DataEmitter.java +++ b/AndroidAsync/src/com/koushikdutta/async/DataEmitter.java @@ -14,4 +14,5 @@ public interface DataEmitter { public void setEndCallback(CompletedCallback callback); public CompletedCallback getEndCallback(); public AsyncServer getServer(); + public String charset(); } diff --git a/AndroidAsync/src/com/koushikdutta/async/DataEmitterBase.java b/AndroidAsync/src/com/koushikdutta/async/DataEmitterBase.java index 529ec8d..90e05c7 100644 --- a/AndroidAsync/src/com/koushikdutta/async/DataEmitterBase.java +++ b/AndroidAsync/src/com/koushikdutta/async/DataEmitterBase.java @@ -41,4 +41,9 @@ public abstract class DataEmitterBase implements DataEmitter { public DataCallback getDataCallback() { return mDataCallback; } + + @Override + public String charset() { + return null; + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/FilteredDataEmitter.java b/AndroidAsync/src/com/koushikdutta/async/FilteredDataEmitter.java index 71bcfa9..10e8504 100644 --- a/AndroidAsync/src/com/koushikdutta/async/FilteredDataEmitter.java +++ b/AndroidAsync/src/com/koushikdutta/async/FilteredDataEmitter.java @@ -85,4 +85,11 @@ public class FilteredDataEmitter extends DataEmitterBase implements DataEmitter, public void close() { mEmitter.close(); } + + @Override + public String charset() { + if (mEmitter == null) + return null; + return mEmitter.charset(); + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/PushParser.java b/AndroidAsync/src/com/koushikdutta/async/PushParser.java index c662f94..e02ac7f 100644 --- a/AndroidAsync/src/com/koushikdutta/async/PushParser.java +++ b/AndroidAsync/src/com/koushikdutta/async/PushParser.java @@ -360,7 +360,6 @@ public class PushParser implements DataCallback { "}\n"; //null != "AndroidAsync: tap callback could not be found. Proguard? Use this in your proguard config:\n" + fail; - assert false; - return null; + throw new AssertionError(fail); } } diff --git a/AndroidAsync/src/com/koushikdutta/async/ZipDataSink.java b/AndroidAsync/src/com/koushikdutta/async/ZipDataSink.java index 42cd71a..6838590 100644 --- a/AndroidAsync/src/com/koushikdutta/async/ZipDataSink.java +++ b/AndroidAsync/src/com/koushikdutta/async/ZipDataSink.java @@ -15,7 +15,6 @@ public class ZipDataSink extends FilteredDataSink { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ZipOutputStream zop = new ZipOutputStream(bout); - boolean first = true; public void putNextEntry(ZipEntry ze) throws IOException { zop.putNextEntry(ze); @@ -31,7 +30,8 @@ public class ZipDataSink extends FilteredDataSink { closed.onCompleted(e); } - public void close() { + @Override + public void end() { try { zop.close(); } @@ -41,7 +41,7 @@ public class ZipDataSink extends FilteredDataSink { } setMaxBuffer(Integer.MAX_VALUE); write(new ByteBufferList()); - super.close(); + super.end(); } @Override @@ -50,7 +50,7 @@ public class ZipDataSink extends FilteredDataSink { if (bb != null) { while (bb.size() > 0) { ByteBuffer b = bb.remove(); - zop.write(b.array(), b.arrayOffset() + b.position(), b.remaining()); + ByteBufferList.writeOutputStream(zop, b); ByteBufferList.reclaim(b); } } diff --git a/AndroidAsync/src/com/koushikdutta/async/future/ConvertFuture.java b/AndroidAsync/src/com/koushikdutta/async/future/ConvertFuture.java new file mode 100644 index 0000000..b07ac43 --- /dev/null +++ b/AndroidAsync/src/com/koushikdutta/async/future/ConvertFuture.java @@ -0,0 +1,13 @@ +package com.koushikdutta.async.future; + +/** + * Created by koush on 6/21/14. + */ +public abstract class ConvertFuture<T, F> extends TransformFuture<T, F> { + @Override + protected final void transform(F result) throws Exception { + setComplete(convert(result)); + } + + protected abstract Future<T> convert(F result) throws Exception; +} diff --git a/AndroidAsync/src/com/koushikdutta/async/future/HandlerFuture.java b/AndroidAsync/src/com/koushikdutta/async/future/HandlerFuture.java index 81c1822..d03eaf9 100644 --- a/AndroidAsync/src/com/koushikdutta/async/future/HandlerFuture.java +++ b/AndroidAsync/src/com/koushikdutta/async/future/HandlerFuture.java @@ -1,31 +1,39 @@ package com.koushikdutta.async.future; import android.os.Handler; +import android.os.Looper; /** * Created by koush on 12/25/13. */ -public class HandlerFuture<T> extends TransformFuture<T, T> { - Handler handler = new Handler(); +public class HandlerFuture<T> extends SimpleFuture<T> { + Handler handler; - @Override - protected void error(final Exception e) { - handler.post(new Runnable() { - @Override - public void run() { - HandlerFuture.super.error(e); - } - }); + public HandlerFuture() { + Looper looper = Looper.myLooper(); + if (looper == null) + looper = Looper.getMainLooper(); + handler = new Handler(looper); } @Override - protected void transform(final T result) throws Exception { - handler.post(new Runnable() { + public SimpleFuture<T> setCallback(final FutureCallback<T> callback) { + FutureCallback<T> wrapped = new FutureCallback<T>() { @Override - public void run() { - if (!isCancelled()) - setComplete(result); + public void onCompleted(final Exception e, final T result) { + if (Looper.myLooper() == handler.getLooper()) { + callback.onCompleted(e, result); + return; + } + + handler.post(new Runnable() { + @Override + public void run() { + onCompleted(e, result); + } + }); } - }); + }; + return super.setCallback(wrapped); } } diff --git a/AndroidAsync/src/com/koushikdutta/async/future/SimpleFuture.java b/AndroidAsync/src/com/koushikdutta/async/future/SimpleFuture.java index dd33d54..11f5df3 100644 --- a/AndroidAsync/src/com/koushikdutta/async/future/SimpleFuture.java +++ b/AndroidAsync/src/com/koushikdutta/async/future/SimpleFuture.java @@ -14,6 +14,17 @@ public class SimpleFuture<T> extends SimpleCancellable implements DependentFutur boolean silent; FutureCallback<T> callback; + public SimpleFuture() { + } + + public SimpleFuture(T value) { + setComplete(value); + } + + public SimpleFuture(Exception e) { + setComplete(e); + } + @Override public boolean cancel(boolean mayInterruptIfRunning) { return cancel(); @@ -166,7 +177,7 @@ public class SimpleFuture<T> extends SimpleCancellable implements DependentFutur } @Override - public <C extends FutureCallback<T>> C then(C callback) { + public final <C extends FutureCallback<T>> C then(C callback) { if (callback instanceof DependentCancellable) ((DependentCancellable)callback).setParent(this); setCallback(callback); diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java index 7127d30..0034dbe 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java @@ -1,6 +1,8 @@ package com.koushikdutta.async.http; +import android.annotation.SuppressLint; import android.net.Uri; +import android.os.Build; import android.text.TextUtils; import com.koushikdutta.async.AsyncSSLException; @@ -36,9 +38,14 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; import java.net.URI; import java.net.URL; import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeoutException; public class AsyncHttpClient { @@ -67,6 +74,43 @@ public class AsyncHttpClient { insertMiddleware(sslSocketMiddleware = new AsyncSSLSocketMiddleware(this)); } + + @SuppressLint("NewApi") + private static void setupAndroidProxy(AsyncHttpRequest request) { + // using a explicit proxy? + if (request.proxyHost != null) + return; + + List<Proxy> proxies; + try { + proxies = ProxySelector.getDefault().select(URI.create(request.getUri().toString())); + } + catch (Exception e) { + // uri parsing craps itself sometimes. + return; + } + if (proxies.isEmpty()) + return; + Proxy proxy = proxies.get(0); + if (proxy.type() != Proxy.Type.HTTP) + return; + if (!(proxy.address() instanceof InetSocketAddress)) + return; + InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address(); + String proxyHost; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + proxyHost = proxyAddress.getHostString(); + } + else { + InetAddress address = proxyAddress.getAddress(); + if (address!=null) + proxyHost = address.getHostAddress(); + else + proxyHost = proxyAddress.getHostName(); + } + request.enableProxy(proxyHost, proxyAddress.getPort()); + } + public AsyncSocketMiddleware getSocketMiddleware() { return socketMiddleware; } @@ -260,15 +304,16 @@ public class AsyncHttpClient { int responseCode = headers.getResponseCode(); if ((responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == 307) && request.getFollowRedirect()) { String location = headers.get("Location"); - Uri redirect = Uri.parse(location); - if (redirect == null || redirect.getScheme() == null) { - try { + Uri redirect; + try { + redirect = Uri.parse(location); + if (redirect.getScheme() == null) { redirect = Uri.parse(new URL(new URL(uri.toString()), location).toString()); } - catch (Exception e) { - reportConnectedCompleted(cancel, e, this, request, callback); - return; - } + } + catch (Exception e) { + reportConnectedCompleted(cancel, e, this, request, callback); + return; } final String method = request.getMethod().equals(AsyncHttpHead.METHOD) ? AsyncHttpHead.METHOD : AsyncHttpGet.METHOD; AsyncHttpRequest newReq = new AsyncHttpRequest(redirect, method); @@ -277,6 +322,7 @@ public class AsyncHttpClient { newReq.LOGTAG = request.LOGTAG; newReq.proxyHost = request.proxyHost; newReq.proxyPort = request.proxyPort; + setupAndroidProxy(newReq); copyHeader(request, newReq, "User-Agent"); copyHeader(request, newReq, "Range"); request.logi("Redirecting"); @@ -371,6 +417,9 @@ public class AsyncHttpClient { } }; + // set up the system default proxy and connect + setupAndroidProxy(request); + synchronized (mMiddleware) { for (AsyncHttpClientMiddleware middleware: mMiddleware) { Cancellable socketCancellable = middleware.getSocket(data); diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClientMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClientMiddleware.java index 2f2e4a8..135ec69 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClientMiddleware.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClientMiddleware.java @@ -5,33 +5,11 @@ import com.koushikdutta.async.DataEmitter; import com.koushikdutta.async.callback.ConnectCallback; import com.koushikdutta.async.future.Cancellable; import com.koushikdutta.async.http.libcore.ResponseHeaders; +import com.koushikdutta.async.util.UntypedHashtable; import java.util.Hashtable; public interface AsyncHttpClientMiddleware { - public static class UntypedHashtable { - private Hashtable<String, Object> hash = new Hashtable<String, Object>(); - - public void put(String key, Object value) { - hash.put(key, value); - } - - public void remove(String key) { - hash.remove(key); - } - - public <T> T get(String key, T defaultValue) { - T ret = get(key); - if (ret == null) - return defaultValue; - return ret; - } - - public <T> T get(String key) { - return (T)hash.get(key); - } - } - public static class GetSocketData { public UntypedHashtable state = new UntypedHashtable(); public AsyncHttpRequest request; @@ -50,11 +28,11 @@ public interface AsyncHttpClientMiddleware { public static class OnBodyData extends OnHeadersReceivedData { public DataEmitter bodyEmitter; } - + public static class OnRequestCompleteData extends OnBodyData { public Exception exception; } - + public Cancellable getSocket(GetSocketData data); public void onSocket(OnSocketData data); public void onHeadersReceived(OnHeadersReceivedData data); diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java index 35d9902..22316b5 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java @@ -16,7 +16,6 @@ import org.apache.http.RequestLine; import org.apache.http.message.BasicHeader; import org.apache.http.params.HttpParams; -import java.net.URI; import java.util.List; import java.util.Map; diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponseImpl.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponseImpl.java index 233c50b..70f10f7 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponseImpl.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponseImpl.java @@ -1,5 +1,7 @@ package com.koushikdutta.async.http; +import android.text.TextUtils; + import com.koushikdutta.async.AsyncServer; import com.koushikdutta.async.AsyncSocket; import com.koushikdutta.async.ByteBufferList; @@ -15,8 +17,10 @@ import com.koushikdutta.async.http.body.AsyncHttpRequestBody; import com.koushikdutta.async.http.filter.ChunkedOutputFilter; import com.koushikdutta.async.http.libcore.RawHeaders; import com.koushikdutta.async.http.libcore.ResponseHeaders; +import com.koushikdutta.async.util.Charsets; import java.nio.ByteBuffer; +import java.nio.charset.Charset; abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements AsyncHttpResponse { private AsyncHttpRequestBody mWriter; @@ -40,7 +44,7 @@ abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements Asyn if (mWriter != null) { if (mRequest.getHeaders().getContentType() == null) mRequest.getHeaders().setContentType(mWriter.getContentType()); - if (mWriter.length() > 0) { + if (mWriter.length() >= 0) { mRequest.getHeaders().setContentLength(mWriter.length()); mSink = mSocket; } @@ -99,18 +103,19 @@ abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements Asyn } } }; - + protected abstract void onHeadersReceived(); - + StringCallback mHeaderCallback = new StringCallback() { private RawHeaders mRawHeaders = new RawHeaders(); @Override public void onStringAvailable(String s) { try { + s = s.trim(); if (mRawHeaders.getStatusLine() == null) { mRawHeaders.setStatusLine(s); } - else if (!"\r".equals(s)) { + else if (!TextUtils.isEmpty(s)) { mRawHeaders.addLine(s); } else { @@ -218,11 +223,6 @@ abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements Asyn } @Override - public void close() { - mSink.close(); - } - - @Override public void setClosedCallback(CompletedCallback handler) { mSink.setClosedCallback(handler); } @@ -236,4 +236,14 @@ abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements Asyn public AsyncServer getServer() { return mSocket.getServer(); } + + @Override + public String charset() { + Multimap mm = Multimap.parseHeader(getHeaders().getHeaders(), "Content-Type"); + String cs; + if (mm != null && null != (cs = mm.getString("charset")) && Charset.isSupported(cs)) { + return cs; + } + return null; + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLEngineConfigurator.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLEngineConfigurator.java new file mode 100644 index 0000000..36af9c1 --- /dev/null +++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLEngineConfigurator.java @@ -0,0 +1,7 @@ +package com.koushikdutta.async.http; + +import javax.net.ssl.SSLEngine; + +public interface AsyncSSLEngineConfigurator { + public void configureEngine(SSLEngine engine, String host, int port); +} diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLSocketMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLSocketMiddleware.java index 7329911..82cc3ea 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLSocketMiddleware.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLSocketMiddleware.java @@ -3,7 +3,7 @@ package com.koushikdutta.async.http; import android.net.Uri; import android.text.TextUtils; -import com.koushikdutta.async.AsyncSSLException; +import com.koushikdutta.async.AsyncSSLSocket; import com.koushikdutta.async.AsyncSSLSocketWrapper; import com.koushikdutta.async.AsyncSocket; import com.koushikdutta.async.LineEmitter; @@ -12,36 +12,79 @@ import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.callback.ConnectCallback; import com.koushikdutta.async.http.libcore.RawHeaders; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; -import java.io.IOException; -import java.net.URI; - public class AsyncSSLSocketMiddleware extends AsyncSocketMiddleware { public AsyncSSLSocketMiddleware(AsyncHttpClient client) { super(client, "https", 443); } - SSLContext sslContext; + protected SSLContext sslContext; public void setSSLContext(SSLContext sslContext) { this.sslContext = sslContext; } - TrustManager[] trustManagers; + public SSLContext getSSLContext() { + return sslContext != null ? sslContext : AsyncSSLSocketWrapper.getDefaultSSLContext(); + } + + protected TrustManager[] trustManagers; public void setTrustManagers(TrustManager[] trustManagers) { this.trustManagers = trustManagers; } - HostnameVerifier hostnameVerifier; + protected HostnameVerifier hostnameVerifier; public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { this.hostnameVerifier = hostnameVerifier; } + protected List<AsyncSSLEngineConfigurator> engineConfigurators = new ArrayList<AsyncSSLEngineConfigurator>(); + + public void addEngineConfigurator(AsyncSSLEngineConfigurator engineConfigurator) { + engineConfigurators.add(engineConfigurator); + } + + public void clearEngineConfigurators() { + engineConfigurators.clear(); + } + + protected SSLEngine createConfiguredSSLEngine(String host, int port) { + SSLContext sslContext = getSSLContext(); + SSLEngine sslEngine = sslContext.createSSLEngine(); + + for (AsyncSSLEngineConfigurator configurator : engineConfigurators) { + configurator.configureEngine(sslEngine, host, port); + } + + return sslEngine; + } + + protected AsyncSSLSocketWrapper.HandshakeCallback createHandshakeCallback(final ConnectCallback callback) { + return new AsyncSSLSocketWrapper.HandshakeCallback() { + @Override + public void onHandshakeCompleted(Exception e, AsyncSSLSocket socket) { + callback.onConnectCompleted(e, socket); + } + }; + } + + protected void tryHandshake(final ConnectCallback callback, AsyncSocket socket, final Uri uri, final int port) { + AsyncSSLSocketWrapper.handshake(socket, uri.getHost(), port, + createConfiguredSSLEngine(uri.getHost(), port), + trustManagers, hostnameVerifier, true, + createHandshakeCallback(callback)); + } + @Override protected ConnectCallback wrapCallback(final ConnectCallback callback, final Uri uri, final int port, final boolean proxied) { return new ConnectCallback() { @@ -49,7 +92,7 @@ public class AsyncSSLSocketMiddleware extends AsyncSocketMiddleware { public void onConnectCompleted(Exception ex, final AsyncSocket socket) { if (ex == null) { if (!proxied) { - callback.onConnectCompleted(null, new AsyncSSLSocketWrapper(socket, uri.getHost(), port, sslContext, trustManagers, hostnameVerifier, true)); + tryHandshake(callback, socket, uri, port); } else { // this SSL connection is proxied, must issue a CONNECT request to the proxy server @@ -81,7 +124,7 @@ public class AsyncSSLSocketMiddleware extends AsyncSocketMiddleware { socket.setDataCallback(null); socket.setEndCallback(null); if (TextUtils.isEmpty(s.trim())) { - callback.onConnectCompleted(null, new AsyncSSLSocketWrapper(socket, uri.getHost(), port, sslContext, trustManagers, hostnameVerifier, true)); + tryHandshake(callback, socket, uri, port); } else { callback.onConnectCompleted(new IOException("unknown second status line"), socket); diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncSocketMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncSocketMiddleware.java index 8e8a36e..e293d23 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncSocketMiddleware.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncSocketMiddleware.java @@ -50,7 +50,7 @@ public class AsyncSocketMiddleware extends SimpleMiddleware { this(client, "http", 80); } - AsyncHttpClient mClient; + protected AsyncHttpClient mClient; protected ConnectCallback wrapCallback(ConnectCallback callback, Uri uri, int port, boolean proxied) { return callback; diff --git a/AndroidAsync/src/com/koushikdutta/async/http/HttpUtil.java b/AndroidAsync/src/com/koushikdutta/async/http/HttpUtil.java index dba79c0..9b08241 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/HttpUtil.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/HttpUtil.java @@ -35,7 +35,7 @@ public class HttpUtil { return new StringBody(); } if (MultipartFormDataBody.CONTENT_TYPE.equals(ct)) { - return new MultipartFormDataBody(contentType, values); + return new MultipartFormDataBody(values); } } } diff --git a/AndroidAsync/src/com/koushikdutta/async/http/HybiParser.java b/AndroidAsync/src/com/koushikdutta/async/http/HybiParser.java index dd8e82c..e1b4015 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/HybiParser.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/HybiParser.java @@ -302,6 +302,10 @@ abstract class HybiParser { return frame(OP_BINARY, data, -1, offset, length); } + public byte[] pingFrame(String data) { + return frame(OP_PING, data, -1); + } + /** * Flip the opcode so to avoid the name collision with the public method * @@ -378,10 +382,6 @@ abstract class HybiParser { return frame; } - public void ping(String message) { -// send(frame(message, OP_PING, -1)); - } - public void close(int code, String reason) { if (mClosed) return; sendFrame(frame(OP_CLOSE, reason, code)); @@ -432,7 +432,7 @@ abstract class HybiParser { } } else if (opcode == OP_CLOSE) { - int code = (payload.length >= 2) ? 256 * payload[0] + payload[1] : 0; + int code = (payload.length >= 2) ? 256 * (payload[0] & 0xFF) + (payload[1] & 0xFF) : 0; String reason = (payload.length > 2) ? encode(slice(payload, 2)) : null; // Log.d(TAG, "Got close op! " + code + " " + reason); onDisconnect(code, reason); @@ -444,13 +444,14 @@ abstract class HybiParser { } else if (opcode == OP_PONG) { String message = encode(payload); - // FIXME: Fire callback... + onPong(message); // Log.d(TAG, "Got pong! " + message); } } protected abstract void onMessage(byte[] payload); protected abstract void onMessage(String payload); + protected abstract void onPong(String payload); protected abstract void onDisconnect(int code, String reason); protected abstract void report(Exception ex); @@ -509,4 +510,4 @@ abstract class HybiParser { return value; } -}
\ No newline at end of file +} diff --git a/AndroidAsync/src/com/koushikdutta/async/http/Multimap.java b/AndroidAsync/src/com/koushikdutta/async/http/Multimap.java index 24dfc5a..628bdd0 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/Multimap.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/Multimap.java @@ -50,6 +50,8 @@ public class Multimap extends Hashtable<String, List<String>> implements Iterabl } public static Multimap parseHeader(String header) { + if (header == null) + return null; Multimap map = new Multimap(); String[] parts = header.split(";"); for (String part: parts) { diff --git a/AndroidAsync/src/com/koushikdutta/async/http/ResponseCacheMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/ResponseCacheMiddleware.java index cfa693f..3715fb7 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/ResponseCacheMiddleware.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/ResponseCacheMiddleware.java @@ -8,24 +8,23 @@ import com.koushikdutta.async.AsyncServer; import com.koushikdutta.async.AsyncSocket; import com.koushikdutta.async.ByteBufferList; import com.koushikdutta.async.DataEmitter; -import com.koushikdutta.async.DataEmitterBase; import com.koushikdutta.async.FilteredDataEmitter; import com.koushikdutta.async.Util; import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.callback.WritableCallback; import com.koushikdutta.async.future.Cancellable; import com.koushikdutta.async.future.SimpleCancellable; -import com.koushikdutta.async.http.libcore.Charsets; +import com.koushikdutta.async.util.Charsets; import com.koushikdutta.async.http.libcore.RawHeaders; import com.koushikdutta.async.http.libcore.ResponseHeaders; import com.koushikdutta.async.http.libcore.ResponseSource; import com.koushikdutta.async.http.libcore.StrictLineReader; +import com.koushikdutta.async.util.Allocator; import com.koushikdutta.async.util.FileCache; import com.koushikdutta.async.util.StreamUtility; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; -import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -35,7 +34,6 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.CacheResponse; -import java.net.URI; import java.nio.ByteBuffer; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; @@ -45,6 +43,8 @@ import java.security.cert.X509Certificate; import java.util.List; import java.util.Map; +import javax.net.ssl.SSLEngine; + public class ResponseCacheMiddleware extends SimpleMiddleware { public static final int ENTRY_METADATA = 0; public static final int ENTRY_BODY = 1; @@ -147,6 +147,9 @@ public class ResponseCacheMiddleware extends SimpleMiddleware { RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap); ResponseHeaders cachedResponseHeaders = new ResponseHeaders(data.request.getUri(), rawResponseHeaders); + rawResponseHeaders.set("Content-Length", String.valueOf(contentLength)); + rawResponseHeaders.removeAll("Content-Encoding"); + rawResponseHeaders.removeAll("Transfer-Encoding"); cachedResponseHeaders.setLocalTimestamps(System.currentTimeMillis(), System.currentTimeMillis()); long now = System.currentTimeMillis(); @@ -155,9 +158,6 @@ public class ResponseCacheMiddleware extends SimpleMiddleware { if (responseSource == ResponseSource.CACHE) { data.request.logi("Response retrieved from cache"); final CachedSocket socket = entry.isHttps() ? new CachedSSLSocket(candidate, contentLength) : new CachedSocket(candidate, contentLength); - rawResponseHeaders.removeAll("Content-Encoding"); - rawResponseHeaders.removeAll("Transfer-Encoding"); - rawResponseHeaders.set("Content-Length", String.valueOf(contentLength)); socket.pending.add(ByteBuffer.wrap(rawResponseHeaders.toHeaderString().getBytes())); server.post(new Runnable() { @@ -219,14 +219,14 @@ public class ResponseCacheMiddleware extends SimpleMiddleware { if (cacheData != null) { if (cacheData.cachedResponseHeaders.validate(data.headers)) { data.request.logi("Serving response from conditional cache"); + data.headers.getHeaders().removeAll("Content-Length"); data.headers = cacheData.cachedResponseHeaders.combine(data.headers); data.headers.getHeaders().setStatusLine(cacheData.cachedResponseHeaders.getHeaders().getStatusLine()); data.headers.getHeaders().set(SERVED_FROM, CONDITIONAL_CACHE); conditionalCacheHitCount++; - BodySpewer bodySpewer = new BodySpewer(cacheData.contentLength); - bodySpewer.cacheResponse = cacheData.candidate; + CachedBodyEmitter bodySpewer = new CachedBodyEmitter(cacheData.candidate, cacheData.contentLength); bodySpewer.setDataEmitter(data.bodyEmitter); data.bodyEmitter = bodySpewer; bodySpewer.spew(); @@ -341,7 +341,7 @@ public class ResponseCacheMiddleware extends SimpleMiddleware { while (!bb.isEmpty()) { ByteBuffer b = bb.remove(); try { - outputStream.write(b.array(), b.arrayOffset() + b.position(), b.remaining()); + ByteBufferList.writeOutputStream(outputStream, b); } finally { copy.add(b); @@ -384,54 +384,62 @@ public class ResponseCacheMiddleware extends SimpleMiddleware { } } - private static class BodySpewer extends FilteredDataEmitter { - long contentLength; + private static class CachedBodyEmitter extends FilteredDataEmitter { EntryCacheResponse cacheResponse; - boolean first = true; ByteBufferList pending = new ByteBufferList(); - boolean paused; + private boolean paused; + private Allocator allocator = new Allocator(); boolean allowEnd; - public BodySpewer(long contentLength) { - this.contentLength = contentLength; + public CachedBodyEmitter(EntryCacheResponse cacheResponse, long contentLength) { + this.cacheResponse = cacheResponse; + allocator.setCurrentAlloc((int)contentLength); } + Runnable spewRunnable = new Runnable() { + @Override + public void run() { + spewInternal(); + } + }; + void spewInternal() { if (pending.remaining() > 0) { - com.koushikdutta.async.Util.emitAllData(BodySpewer.this, pending); + com.koushikdutta.async.Util.emitAllData(CachedBodyEmitter.this, pending); if (pending.remaining() > 0) return; } // fill pending try { - assert first; - if (!first) - return; - first = false; - ByteBuffer buffer = ByteBufferList.obtain((int)contentLength); + ByteBuffer buffer = allocator.allocate(); assert buffer.position() == 0; - DataInputStream din = new DataInputStream(cacheResponse.getBody()); - din.readFully(buffer.array(), buffer.arrayOffset(), (int)contentLength); - buffer.limit((int)contentLength); + FileInputStream din = cacheResponse.getBody(); + int read = din.read(buffer.array(), buffer.arrayOffset(), buffer.capacity()); + if (read == -1) { + ByteBufferList.reclaim(buffer); + allowEnd = true; + report(null); + return; + } + allocator.track(read); + buffer.limit(read); pending.add(buffer); - com.koushikdutta.async.Util.emitAllData(this, pending); - assert din.read() == -1; - allowEnd = true; - report(null); } catch (IOException e) { allowEnd = true; report(e); + return; } + com.koushikdutta.async.Util.emitAllData(this, pending); + if (pending.remaining() > 0) + return; + // this limits max throughput to 256k (aka max alloc) * 100 per second... + // roughly 25MB/s + getServer().postDelayed(spewRunnable, 10); } void spew() { - getServer().post(new Runnable() { - @Override - public void run() { - spewInternal(); - } - }); + getServer().post(spewRunnable); } @Override @@ -447,6 +455,8 @@ public class ResponseCacheMiddleware extends SimpleMiddleware { @Override protected void report(Exception e) { + // a 304 response will immediate call report/end since there is no body. + // prevent this from happening by waiting for the actual body to be spit out. if (!allowEnd) return; StreamUtility.closeQuietly(cacheResponse.getBody()); @@ -671,23 +681,23 @@ public class ResponseCacheMiddleware extends SimpleMiddleware { } @Override + public SSLEngine getSSLEngine() { + return null; + } + + @Override public X509Certificate[] getPeerCertificates() { return null; } } - private class CachedSocket extends DataEmitterBase implements AsyncSocket { - EntryCacheResponse cacheResponse; - long contentLength; - boolean paused; + private class CachedSocket extends CachedBodyEmitter implements AsyncSocket { boolean closed; - boolean first = true; - ByteBufferList pending = new ByteBufferList(); boolean open; CompletedCallback closedCallback; public CachedSocket(EntryCacheResponse cacheResponse, long contentLength) { - this.cacheResponse = cacheResponse; - this.contentLength = contentLength; + super(cacheResponse, contentLength); + allowEnd = true; } @Override @@ -695,19 +705,8 @@ public class ResponseCacheMiddleware extends SimpleMiddleware { } @Override - public boolean isChunked() { - return false; - } - - @Override - public void pause() { - paused = true; - } - - @Override protected void report(Exception e) { super.report(e); - StreamUtility.closeQuietly(cacheResponse.getBody()); if (closed) return; closed = true; @@ -715,54 +714,6 @@ public class ResponseCacheMiddleware extends SimpleMiddleware { closedCallback.onCompleted(e); } - void spewInternal() { - if (pending.remaining() > 0) { - com.koushikdutta.async.Util.emitAllData(CachedSocket.this, pending); - if (pending.remaining() > 0) - return; - } - - // fill pending - try { - assert first; - if (!first) - return; - first = false; - ByteBuffer buffer = ByteBufferList.obtain((int)contentLength); - assert buffer.position() == 0; - DataInputStream din = new DataInputStream(cacheResponse.getBody()); - din.readFully(buffer.array(), buffer.arrayOffset(), (int)contentLength); - buffer.limit((int)contentLength); - pending.add(buffer); - com.koushikdutta.async.Util.emitAllData(CachedSocket.this, pending); - assert din.read() == -1; - report(null); - } - catch (IOException e) { - report(e); - } - } - - void spew() { - getServer().post(new Runnable() { - @Override - public void run() { - spewInternal(); - } - }); - } - - @Override - public void resume() { - paused = false; - spew(); - } - - @Override - public boolean isPaused() { - return paused; - } - @Override public void write(ByteBuffer bb) { // it's gonna write headers and stuff... whatever diff --git a/AndroidAsync/src/com/koushikdutta/async/http/WebSocket.java b/AndroidAsync/src/com/koushikdutta/async/http/WebSocket.java index 8242381..1aaafa6 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/WebSocket.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/WebSocket.java @@ -7,14 +7,21 @@ public interface WebSocket extends AsyncSocket { static public interface StringCallback { public void onStringAvailable(String s); } + static public interface PongCallback { + public void onPongReceived(String s); + } public void send(byte[] bytes); public void send(String string); public void send(byte [] bytes, int offset, int len); + public void ping(String message); public void setStringCallback(StringCallback callback); public StringCallback getStringCallback(); - + + public void setPongCallback(PongCallback callback); + public PongCallback getPongCallback(); + public boolean isBuffering(); public AsyncSocket getSocket(); diff --git a/AndroidAsync/src/com/koushikdutta/async/http/WebSocketImpl.java b/AndroidAsync/src/com/koushikdutta/async/http/WebSocketImpl.java index 96e724a..e4a2787 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/WebSocketImpl.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/WebSocketImpl.java @@ -98,6 +98,12 @@ public class WebSocketImpl implements WebSocket { protected void sendFrame(byte[] frame) { mSink.write(ByteBuffer.wrap(frame)); } + + @Override + protected void onPong(String payload) { + if (WebSocketImpl.this.mPongCallback != null) + WebSocketImpl.this.mPongCallback.onPongReceived(payload); + } }; mParser.setMasking(masking); mParser.setDeflate(deflate); @@ -228,6 +234,11 @@ public class WebSocketImpl implements WebSocket { mSink.write(ByteBuffer.wrap(mParser.frame(string))); } + @Override + public void ping(String string) { + mSink.write(ByteBuffer.wrap(mParser.pingFrame(string))); + } + private StringCallback mStringCallback; @Override public void setStringCallback(StringCallback callback) { @@ -245,6 +256,17 @@ public class WebSocketImpl implements WebSocket { return mStringCallback; } + private PongCallback mPongCallback; + @Override + public void setPongCallback(PongCallback callback) { + mPongCallback = callback; + } + + @Override + public PongCallback getPongCallback() { + return mPongCallback; + } + @Override public DataCallback getDataCallback() { return mDataCallback; @@ -314,4 +336,9 @@ public class WebSocketImpl implements WebSocket { public boolean isPaused() { return mSocket.isPaused(); } + + @Override + public String charset() { + return null; + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/http/body/MultipartFormDataBody.java b/AndroidAsync/src/com/koushikdutta/async/http/body/MultipartFormDataBody.java index 8fdd378..c61a9d9 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/body/MultipartFormDataBody.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/body/MultipartFormDataBody.java @@ -100,7 +100,8 @@ public class MultipartFormDataBody extends BoundaryEmitter implements AsyncHttpR } public static final String CONTENT_TYPE = "multipart/form-data"; - public MultipartFormDataBody(String contentType, String[] values) { + String contentType = CONTENT_TYPE; + public MultipartFormDataBody(String[] values) { for (String value: values) { String[] splits = value.split("="); if (splits.length != 2) @@ -154,7 +155,9 @@ public class MultipartFormDataBody extends BoundaryEmitter implements AsyncHttpR .add(new ContinuationCallback() { @Override public void onContinue(Continuation continuation, CompletedCallback next) throws Exception { - written += part.length(); + long partLength = part.length(); + if (partLength >= 0) + written += partLength; part.write(sink, next); } }) @@ -213,7 +216,11 @@ public class MultipartFormDataBody extends BoundaryEmitter implements AsyncHttpR public MultipartFormDataBody() { } - + + public void setContentType(String contentType) { + this.contentType = contentType; + } + public void addFilePart(String name, File file) { addPart(new FilePart(name, file)); } diff --git a/AndroidAsync/src/com/koushikdutta/async/http/body/Part.java b/AndroidAsync/src/com/koushikdutta/async/http/body/Part.java index aacf5c2..cfe4993 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/body/Part.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/body/Part.java @@ -24,8 +24,8 @@ public class Part { return mContentDisposition.getString("name"); } - private int length = -1; - public Part(String name, int length, List<NameValuePair> contentDisposition) { + private long length = -1; + public Part(String name, long length, List<NameValuePair> contentDisposition) { this.length = length; mHeaders = new RawHeaders(); StringBuilder builder = new StringBuilder(String.format("form-data; name=\"%s\"", name)); @@ -61,7 +61,7 @@ public class Part { return mContentDisposition.containsKey("filename"); } - public int length() { + public long length() { return length; } diff --git a/AndroidAsync/src/com/koushikdutta/async/http/body/StreamPart.java b/AndroidAsync/src/com/koushikdutta/async/http/body/StreamPart.java index 0a3bb0f..d48704b 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/body/StreamPart.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/body/StreamPart.java @@ -10,7 +10,7 @@ import com.koushikdutta.async.DataSink; import com.koushikdutta.async.callback.CompletedCallback; public abstract class StreamPart extends Part { - public StreamPart(String name, int length, List<NameValuePair> contentDisposition) { + public StreamPart(String name, long length, List<NameValuePair> contentDisposition) { super(name, length, contentDisposition); } diff --git a/AndroidAsync/src/com/koushikdutta/async/http/body/UrlEncodedFormBody.java b/AndroidAsync/src/com/koushikdutta/async/http/body/UrlEncodedFormBody.java index b50b7c7..7bceceb 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/body/UrlEncodedFormBody.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/body/UrlEncodedFormBody.java @@ -8,6 +8,7 @@ import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.callback.DataCallback; import com.koushikdutta.async.http.AsyncHttpRequest; import com.koushikdutta.async.http.Multimap; +import com.koushikdutta.async.util.Charsets; import org.apache.http.NameValuePair; @@ -42,7 +43,7 @@ public class UrlEncodedFormBody implements AsyncHttpRequestBody<Multimap> { b.append('='); b.append(URLEncoder.encode(pair.getValue(), "UTF-8")); } - mBodyBytes = b.toString().getBytes("ISO-8859-1"); + mBodyBytes = b.toString().getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { } @@ -58,7 +59,7 @@ public class UrlEncodedFormBody implements AsyncHttpRequestBody<Multimap> { public static final String CONTENT_TYPE = "application/x-www-form-urlencoded"; @Override public String getContentType() { - return CONTENT_TYPE; + return CONTENT_TYPE + "; charset=utf8"; } @Override diff --git a/AndroidAsync/src/com/koushikdutta/async/http/filter/GZIPInputFilter.java b/AndroidAsync/src/com/koushikdutta/async/http/filter/GZIPInputFilter.java index e1a23d0..04eb9c9 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/filter/GZIPInputFilter.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/filter/GZIPInputFilter.java @@ -89,14 +89,23 @@ public class GZIPInputFilter extends InflaterInputFilter { ByteBufferList.reclaim(b); } } + bb.recycle(); + done(); } }; if ((flags & FNAME) != 0) { parser.until((byte) 0, summer); + return; } if ((flags & FCOMMENT) != 0) { parser.until((byte) 0, summer); + return; } + + done(); + } + + private void done() { if (hcrc) { parser.readByteArray(2, new ParseCallback<byte[]>() { public void parsed(byte[] header) { diff --git a/AndroidAsync/src/com/koushikdutta/async/http/filter/InflaterInputFilter.java b/AndroidAsync/src/com/koushikdutta/async/http/filter/InflaterInputFilter.java index e96f284..3ae1363 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/filter/InflaterInputFilter.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/filter/InflaterInputFilter.java @@ -14,6 +14,7 @@ public class InflaterInputFilter extends FilteredDataEmitter { @Override protected void report(Exception e) { + mInflater.end(); if (e != null && mInflater.getRemaining() > 0) { e = new DataRemainingException("data still remaining in inflater", e); } @@ -35,8 +36,7 @@ public class InflaterInputFilter extends FilteredDataEmitter { int inflated = mInflater.inflate(output.array(), output.arrayOffset() + output.position(), output.remaining()); output.position(output.position() + inflated); if (!output.hasRemaining()) { - output.limit(output.position()); - output.position(0); + output.flip(); transformed.add(output); assert totalRead != 0; int newSize = output.capacity() * 2; @@ -47,8 +47,7 @@ public class InflaterInputFilter extends FilteredDataEmitter { } ByteBufferList.reclaim(b); } - output.limit(output.position()); - output.position(0); + output.flip(); transformed.add(output); Util.emitAllData(this, transformed); diff --git a/AndroidAsync/src/com/koushikdutta/async/http/libcore/RequestHeaders.java b/AndroidAsync/src/com/koushikdutta/async/http/libcore/RequestHeaders.java index 3216bd5..b5180b4 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/libcore/RequestHeaders.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/libcore/RequestHeaders.java @@ -212,7 +212,9 @@ public final class RequestHeaders { if (this.contentLength != -1) { headers.removeAll("Content-Length"); } - headers.add("Content-Length", Integer.toString(contentLength)); + if (contentLength != -1) { + headers.add("Content-Length", Integer.toString(contentLength)); + } this.contentLength = contentLength; } diff --git a/AndroidAsync/src/com/koushikdutta/async/http/libcore/StrictLineReader.java b/AndroidAsync/src/com/koushikdutta/async/http/libcore/StrictLineReader.java index 6c6308f..d1cb5d9 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/libcore/StrictLineReader.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/libcore/StrictLineReader.java @@ -16,6 +16,8 @@ package com.koushikdutta.async.http.libcore; +import com.koushikdutta.async.util.Charsets; + import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.EOFException; diff --git a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServer.java b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServer.java index 331c1be..4259d1d 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServer.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServer.java @@ -1,9 +1,12 @@ package com.koushikdutta.async.http.server; +import android.annotation.TargetApi; import android.content.Context; import android.content.res.AssetManager; +import android.os.Build; import android.text.TextUtils; +import com.koushikdutta.async.AsyncSSLSocket; import com.koushikdutta.async.AsyncSSLSocketWrapper; import com.koushikdutta.async.AsyncServer; import com.koushikdutta.async.AsyncServerSocket; @@ -39,6 +42,7 @@ import java.util.regex.Pattern; import javax.net.ssl.SSLContext; +@TargetApi(Build.VERSION_CODES.ECLAIR) public class AsyncHttpServer { ArrayList<AsyncServerSocket> mListeners = new ArrayList<AsyncServerSocket>(); public void stop() { @@ -223,8 +227,14 @@ public class AsyncHttpServer { AsyncServer.getDefault().listen(null, port, new ListenCallback() { @Override public void onAccepted(AsyncSocket socket) { - AsyncSSLSocketWrapper sslSocket = new AsyncSSLSocketWrapper(socket, null, port, sslContext, null, null, false); - mListenCallback.onAccepted(sslSocket); + AsyncSSLSocketWrapper.handshake(socket, null, port, sslContext.createSSLEngine(), null, null, false, + new AsyncSSLSocketWrapper.HandshakeCallback() { + @Override + public void onHandshakeCompleted(Exception e, AsyncSSLSocket socket) { + if (socket != null) + mListenCallback.onAccepted(socket); + } + }); } @Override @@ -308,11 +318,15 @@ public class AsyncHttpServer { response.end(); return; } - callback.onConnected(new WebSocketImpl(request, response), request.getHeaders()); + callback.onConnected(createWebSocket(request, response), request.getHeaders()); } }); } + protected WebSocket createWebSocket(final AsyncHttpServerRequest request, final AsyncHttpServerResponse response) { + return new WebSocketImpl(request, response); + } + public void get(String regex, HttpServerRequestCallback callback) { addAction(AsyncHttpGet.METHOD, regex, callback); } diff --git a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java index 4689bec..bd7211b 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java @@ -318,11 +318,7 @@ public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse { @Override public void close() { - end(); - if (mSink != null) - mSink.close(); - else - mSocket.close(); + mSocket.close(); } @Override diff --git a/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOClient.java b/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOClient.java index 344aab8..af469b6 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOClient.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/socketio/SocketIOClient.java @@ -1,9 +1,7 @@ package com.koushikdutta.async.http.socketio; -import android.os.Handler; import android.text.TextUtils; -import com.koushikdutta.async.AsyncServer; import com.koushikdutta.async.future.Future; import com.koushikdutta.async.future.SimpleFuture; import com.koushikdutta.async.http.AsyncHttpClient; @@ -12,6 +10,7 @@ import com.koushikdutta.async.http.socketio.transport.SocketIOTransport; import org.json.JSONArray; import org.json.JSONObject; +@Deprecated public class SocketIOClient extends EventEmitter { boolean connected; boolean disconnected; diff --git a/AndroidAsync/src/com/koushikdutta/async/parser/StringParser.java b/AndroidAsync/src/com/koushikdutta/async/parser/StringParser.java index 7886662..89a611c 100644 --- a/AndroidAsync/src/com/koushikdutta/async/parser/StringParser.java +++ b/AndroidAsync/src/com/koushikdutta/async/parser/StringParser.java @@ -7,17 +7,20 @@ import com.koushikdutta.async.callback.CompletedCallback; import com.koushikdutta.async.future.Future; import com.koushikdutta.async.future.TransformFuture; +import java.nio.charset.Charset; + /** * Created by koush on 5/27/13. */ public class StringParser implements AsyncParser<String> { @Override public Future<String> parse(DataEmitter emitter) { + final String charset = emitter.charset(); return new ByteBufferListParser().parse(emitter) .then(new TransformFuture<String, ByteBufferList>() { @Override protected void transform(ByteBufferList result) throws Exception { - setComplete(result.readString()); + setComplete(result.readString(charset != null ? Charset.forName(charset) : null)); } }); } diff --git a/AndroidAsync/src/com/koushikdutta/async/stream/InputStreamDataEmitter.java b/AndroidAsync/src/com/koushikdutta/async/stream/InputStreamDataEmitter.java index bdf7993..c63891c 100644 --- a/AndroidAsync/src/com/koushikdutta/async/stream/InputStreamDataEmitter.java +++ b/AndroidAsync/src/com/koushikdutta/async/stream/InputStreamDataEmitter.java @@ -146,4 +146,9 @@ public class InputStreamDataEmitter implements DataEmitter { catch (Exception e) { } } + + @Override + public String charset() { + return null; + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/stream/OutputStreamDataSink.java b/AndroidAsync/src/com/koushikdutta/async/stream/OutputStreamDataSink.java index d4def38..13bb419 100644 --- a/AndroidAsync/src/com/koushikdutta/async/stream/OutputStreamDataSink.java +++ b/AndroidAsync/src/com/koushikdutta/async/stream/OutputStreamDataSink.java @@ -17,7 +17,19 @@ public class OutputStreamDataSink implements DataSink { @Override public void end() { - close(); + try { + if (mStream != null) + mStream.close(); + reportClose(null); + } + catch (IOException e) { + reportClose(e); + } + } + + @Override + public void close() { + end(); } AsyncServer server; @@ -35,31 +47,6 @@ public class OutputStreamDataSink implements DataSink { return mStream; } - private boolean doPending() { - try { - while (pending.size() > 0) { - ByteBuffer b; - synchronized (pending) { - b = pending.remove(); - } - int rem = b.remaining(); - getOutputStream().write(b.array(), b.arrayOffset() + b.position(), b.remaining()); - totalWritten += rem; - ByteBufferList.reclaim(b); - } - return true; - } - catch (Exception e) { - pending.recycle(); - closeReported = true; - closeException = e; - return false; - } - } - - final ByteBufferList pending = new ByteBufferList(); - int totalWritten; - @Override public void write(final ByteBuffer bb) { try { @@ -104,18 +91,6 @@ public class OutputStreamDataSink implements DataSink { public boolean isOpen() { return closeReported; } - - @Override - public void close() { - try { - if (mStream != null) - mStream.close(); - reportClose(null); - } - catch (IOException e) { - reportClose(e); - } - } boolean closeReported; Exception closeException; diff --git a/AndroidAsync/src/com/koushikdutta/async/util/Allocator.java b/AndroidAsync/src/com/koushikdutta/async/util/Allocator.java new file mode 100644 index 0000000..608026d --- /dev/null +++ b/AndroidAsync/src/com/koushikdutta/async/util/Allocator.java @@ -0,0 +1,48 @@ +package com.koushikdutta.async.util; + +import com.koushikdutta.async.ByteBufferList; + +import java.nio.ByteBuffer; + +/** + * Created by koush on 6/28/14. + */ +public class Allocator { + final int maxAlloc; + int currentAlloc = 0; + int minAlloc = 2 << 11; + + public Allocator(int maxAlloc) { + this.maxAlloc = maxAlloc; + } + + public Allocator() { + maxAlloc = ByteBufferList.MAX_ITEM_SIZE; + } + + public ByteBuffer allocate() { + return ByteBufferList.obtain(Math.min(Math.max(currentAlloc, minAlloc), maxAlloc)); + } + + public void track(long read) { + currentAlloc = (int)read * 2; + } + + public int getMaxAlloc() { + return maxAlloc; + } + + public void setCurrentAlloc(int currentAlloc) { + this.currentAlloc = currentAlloc; + } + + public int getMinAlloc() { + return minAlloc; + } + + public Allocator setMinAlloc(int minAlloc ) { + this.minAlloc = minAlloc; + return this; + } +} + diff --git a/AndroidAsync/src/com/koushikdutta/async/http/libcore/Charsets.java b/AndroidAsync/src/com/koushikdutta/async/util/Charsets.java index 575b147..c3a1e44 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/libcore/Charsets.java +++ b/AndroidAsync/src/com/koushikdutta/async/util/Charsets.java @@ -1,4 +1,4 @@ -package com.koushikdutta.async.http.libcore; +package com.koushikdutta.async.util; import java.nio.charset.Charset; diff --git a/AndroidAsync/src/com/koushikdutta/async/util/FileCache.java b/AndroidAsync/src/com/koushikdutta/async/util/FileCache.java index 3ce4b97..d117108 100644 --- a/AndroidAsync/src/com/koushikdutta/async/util/FileCache.java +++ b/AndroidAsync/src/com/koushikdutta/async/util/FileCache.java @@ -6,6 +6,8 @@ import java.io.IOException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -41,17 +43,42 @@ public class FileCache { } } + private static String hashAlgorithm = "MD5"; + + private static MessageDigest findAlternativeMessageDigest() { + if ("MD5".equals(hashAlgorithm)) { + for (Provider provider : Security.getProviders()) { + for (Provider.Service service : provider.getServices()) { + hashAlgorithm = service.getAlgorithm(); + try { + MessageDigest messageDigest = MessageDigest.getInstance(hashAlgorithm); + if (messageDigest != null) + return messageDigest; + } catch (NoSuchAlgorithmException ignored) { + } + } + } + } + return null; + } + public static String toKeyString(Object... parts) { - try { - MessageDigest messageDigest = MessageDigest.getInstance("MD5"); - for (Object part: parts) { - messageDigest.update(part.toString().getBytes()); + MessageDigest messageDigest; + synchronized (FileCache.class) { + try { + messageDigest = MessageDigest.getInstance(hashAlgorithm); + } catch (NoSuchAlgorithmException e) { + messageDigest = findAlternativeMessageDigest(); + if (null == messageDigest) + throw new RuntimeException(e); } - byte[] md5bytes = messageDigest.digest(); - return new BigInteger(1, md5bytes).toString(16); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); } + + for (Object part : parts) { + messageDigest.update(part.toString().getBytes()); + } + byte[] md5bytes = messageDigest.digest(); + return new BigInteger(1, md5bytes).toString(16); } boolean loadAsync; diff --git a/AndroidAsync/src/com/koushikdutta/async/util/StreamUtility.java b/AndroidAsync/src/com/koushikdutta/async/util/StreamUtility.java index 95911dd..4d55e2f 100644 --- a/AndroidAsync/src/com/koushikdutta/async/util/StreamUtility.java +++ b/AndroidAsync/src/com/koushikdutta/async/util/StreamUtility.java @@ -102,5 +102,10 @@ public class StreamUtility { } } } + + public static void eat(InputStream input) throws IOException { + byte[] stuff = new byte[1024]; + while (input.read(stuff) != -1); + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/util/UntypedHashtable.java b/AndroidAsync/src/com/koushikdutta/async/util/UntypedHashtable.java new file mode 100644 index 0000000..a159d97 --- /dev/null +++ b/AndroidAsync/src/com/koushikdutta/async/util/UntypedHashtable.java @@ -0,0 +1,26 @@ +package com.koushikdutta.async.util; + +import java.util.Hashtable; + +public class UntypedHashtable { + private Hashtable<String, Object> hash = new Hashtable<String, Object>(); + + public void put(String key, Object value) { + hash.put(key, value); + } + + public void remove(String key) { + hash.remove(key); + } + + public <T> T get(String key, T defaultValue) { + T ret = get(key); + if (ret == null) + return defaultValue; + return ret; + } + + public <T> T get(String key) { + return (T)hash.get(key); + } +}
\ No newline at end of file diff --git a/AndroidAsyncTest/testdata/6691924d7d24237d3b3679310157d640 b/AndroidAsync/test/assets/6691924d7d24237d3b3679310157d640 Binary files differindex cdd924a..cdd924a 100644 --- a/AndroidAsyncTest/testdata/6691924d7d24237d3b3679310157d640 +++ b/AndroidAsync/test/assets/6691924d7d24237d3b3679310157d640 diff --git a/AndroidAsyncTest/testdata/hello.txt b/AndroidAsync/test/assets/hello.txt index 95d09f2..95d09f2 100644 --- a/AndroidAsyncTest/testdata/hello.txt +++ b/AndroidAsync/test/assets/hello.txt diff --git a/AndroidAsyncTest/testdata/test.json b/AndroidAsync/test/assets/test.json index b42f309..b42f309 100644 --- a/AndroidAsyncTest/testdata/test.json +++ b/AndroidAsync/test/assets/test.json diff --git a/AndroidAsyncTest/res/drawable-hdpi/ic_launcher.png b/AndroidAsync/test/res/drawable-hdpi/ic_launcher.png Binary files differindex 96a442e..96a442e 100644 --- a/AndroidAsyncTest/res/drawable-hdpi/ic_launcher.png +++ b/AndroidAsync/test/res/drawable-hdpi/ic_launcher.png diff --git a/AndroidAsyncTest/res/drawable-ldpi/ic_launcher.png b/AndroidAsync/test/res/drawable-ldpi/ic_launcher.png Binary files differindex 9923872..9923872 100644 --- a/AndroidAsyncTest/res/drawable-ldpi/ic_launcher.png +++ b/AndroidAsync/test/res/drawable-ldpi/ic_launcher.png diff --git a/AndroidAsyncTest/res/drawable-mdpi/ic_launcher.png b/AndroidAsync/test/res/drawable-mdpi/ic_launcher.png Binary files differindex 359047d..359047d 100644 --- a/AndroidAsyncTest/res/drawable-mdpi/ic_launcher.png +++ b/AndroidAsync/test/res/drawable-mdpi/ic_launcher.png diff --git a/AndroidAsyncTest/res/drawable-xhdpi/ic_launcher.png b/AndroidAsync/test/res/drawable-xhdpi/ic_launcher.png Binary files differindex 71c6d76..71c6d76 100644 --- a/AndroidAsyncTest/res/drawable-xhdpi/ic_launcher.png +++ b/AndroidAsync/test/res/drawable-xhdpi/ic_launcher.png diff --git a/AndroidAsyncTest/res/raw/keystore.bks b/AndroidAsync/test/res/raw/keystore.bks Binary files differindex acf703c..acf703c 100644 --- a/AndroidAsyncTest/res/raw/keystore.bks +++ b/AndroidAsync/test/res/raw/keystore.bks diff --git a/AndroidAsyncTest/res/values/strings.xml b/AndroidAsync/test/res/values/strings.xml index ef0d9f7..ef0d9f7 100644 --- a/AndroidAsyncTest/res/values/strings.xml +++ b/AndroidAsync/test/res/values/strings.xml diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/BodyTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/BodyTests.java index e803849..e803849 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/BodyTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/BodyTests.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/ByteUtilTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/ByteUtilTests.java index 0d2f5df..0d2f5df 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/ByteUtilTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/ByteUtilTests.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/CacheTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/CacheTests.java index bffc34c..bffc34c 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/CacheTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/CacheTests.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/DnsTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/DnsTests.java index 4c8f487..4c8f487 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/DnsTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/DnsTests.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/FileCacheTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/FileCacheTests.java index ef07e27..ef07e27 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/FileCacheTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/FileCacheTests.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/FileTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/FileTests.java index 1d37239..1d37239 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/FileTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/FileTests.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/FutureTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/FutureTests.java index c453db9..c453db9 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/FutureTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/FutureTests.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/HttpClientTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/HttpClientTests.java index e238f66..6809880 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/HttpClientTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/HttpClientTests.java @@ -17,9 +17,11 @@ import com.koushikdutta.async.http.AsyncHttpClient; import com.koushikdutta.async.http.AsyncHttpClient.StringCallback; import com.koushikdutta.async.http.AsyncHttpGet; import com.koushikdutta.async.http.AsyncHttpHead; +import com.koushikdutta.async.http.AsyncHttpPost; import com.koushikdutta.async.http.AsyncHttpRequest; import com.koushikdutta.async.http.AsyncHttpResponse; import com.koushikdutta.async.http.ResponseCacheMiddleware; +import com.koushikdutta.async.http.body.JSONObjectBody; import com.koushikdutta.async.http.callback.HttpConnectCallback; import com.koushikdutta.async.http.server.AsyncHttpServer; import com.koushikdutta.async.http.server.AsyncHttpServerRequest; @@ -29,8 +31,9 @@ import com.koushikdutta.async.http.server.HttpServerRequestCallback; import junit.framework.Assert; import junit.framework.TestCase; +import org.json.JSONObject; + import java.io.File; -import java.net.URI; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; @@ -134,9 +137,8 @@ public class HttpClientTests extends TestCase { // this testdata file was generated using /dev/random. filename is also the md5 of the file. final static String dataNameAndHash = "6691924d7d24237d3b3679310157d640"; - final static String githubPath = "github.com/koush/AndroidAsync/raw/master/AndroidAsyncTest/testdata/"; + final static String githubPath = "raw.githubusercontent.com/koush/AndroidAsync/master/AndroidAsync/test/assets/"; final static String github = "https://" + githubPath + dataNameAndHash; - final static String githubInsecure = "http://" + githubPath + dataNameAndHash; public void testGithubRandomData() throws Exception { final Semaphore semaphore = new Semaphore(0); final Md5 md5 = Md5.createInstance(); @@ -176,27 +178,6 @@ public class HttpClientTests extends TestCase { assertEquals(md5.digest(), dataNameAndHash); } - public void testInsecureGithubRandomDataWithFuture() throws Exception { - final Md5 md5 = Md5.createInstance(); - Future<ByteBufferList> bb = client.executeByteBufferList(new AsyncHttpGet(githubInsecure), null); - md5.update(bb.get(TIMEOUT, TimeUnit.MILLISECONDS)); - assertEquals(md5.digest(), dataNameAndHash); - } - - public void testInsecureGithubRandomDataWithFutureCallback() throws Exception { - final Semaphore semaphore = new Semaphore(0); - final Md5 md5 = Md5.createInstance(); - client.executeByteBufferList(new AsyncHttpGet(githubInsecure), null).setCallback(new FutureCallback<ByteBufferList>() { - @Override - public void onCompleted(Exception e, ByteBufferList bb) { - md5.update(bb); - semaphore.release(); - } - }); - assertTrue("timeout", semaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)); - assertEquals(md5.digest(), dataNameAndHash); - } - public void testGithubHelloWithFuture() throws Exception { Future<String> string = client.executeString(new AsyncHttpGet("https://" + githubPath + "hello.txt"), null); assertEquals(string.get(TIMEOUT, TimeUnit.MILLISECONDS), "hello world"); @@ -347,4 +328,13 @@ public class HttpClientTests extends TestCase { Future<String> str = AsyncHttpClient.getDefaultInstance().executeString(req, null); assertTrue(TextUtils.isEmpty(str.get(TIMEOUT, TimeUnit.MILLISECONDS))); } + + public void testPostJsonObject() throws Exception { + JSONObject post = new JSONObject(); + post.put("ping", "pong"); + AsyncHttpPost p = new AsyncHttpPost("https://koush.clockworkmod.com/test/echo"); + p.setBody(new JSONObjectBody(post)); + JSONObject ret = AsyncHttpClient.getDefaultInstance().executeJSONObject(p, null).get(); + assertEquals("pong", ret.getString("ping")); + } } diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/HttpServerTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/HttpServerTests.java index df117e6..df117e6 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/HttpServerTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/HttpServerTests.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/Issue59.java b/AndroidAsync/test/src/com/koushikdutta/async/test/Issue59.java index 44b44fc..44b44fc 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/Issue59.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/Issue59.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/Md5.java b/AndroidAsync/test/src/com/koushikdutta/async/test/Md5.java index a6c4b18..a6c4b18 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/Md5.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/Md5.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/MultipartTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/MultipartTests.java index cace2a5..cace2a5 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/MultipartTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/MultipartTests.java diff --git a/AndroidAsync/test/src/com/koushikdutta/async/test/ParserTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/ParserTests.java new file mode 100644 index 0000000..3374eff --- /dev/null +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/ParserTests.java @@ -0,0 +1,56 @@ +package com.koushikdutta.async.test; + +import com.koushikdutta.async.ByteBufferList; +import com.koushikdutta.async.FilteredDataEmitter; +import com.koushikdutta.async.future.Future; +import com.koushikdutta.async.parser.StringParser; +import com.koushikdutta.async.util.Charsets; + +import junit.framework.TestCase; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +/** + * Created by koush on 7/10/14. + */ +public class ParserTests extends TestCase { + public void testString() throws Exception { + StringParser p = new StringParser(); + FilteredDataEmitter f = new FilteredDataEmitter() { + @Override + public boolean isPaused() { + return false; + } + }; + Future<String> ret = p.parse(f); + ByteBufferList l = new ByteBufferList(); + l.add(ByteBuffer.wrap("foo".getBytes(Charsets.US_ASCII.name()))); + f.onDataAvailable(f, l); + f.getEndCallback().onCompleted(null); + String s = ret.get(); + assertEquals(s, "foo"); + } + + public void testUtf8String() throws Exception { + StringParser p = new StringParser(); + FilteredDataEmitter f = new FilteredDataEmitter() { + @Override + public String charset() { + return Charsets.UTF_8.name(); + } + + @Override + public boolean isPaused() { + return false; + } + }; + Future<String> ret = p.parse(f); + ByteBufferList l = new ByteBufferList(); + l.add(ByteBuffer.wrap("æææ".getBytes(Charsets.UTF_8.name()))); + f.onDataAvailable(f, l); + f.getEndCallback().onCompleted(null); + String s = ret.get(); + assertEquals(s, "æææ"); + } +} diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/ProxyTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/ProxyTests.java index f7243d0..f7243d0 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/ProxyTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/ProxyTests.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/RedirectTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/RedirectTests.java index def3e33..def3e33 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/RedirectTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/RedirectTests.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/SSLTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/SSLTests.java index 4e89b37..fbdc6cf 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/SSLTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/SSLTests.java @@ -3,7 +3,6 @@ package com.koushikdutta.async.test; import android.test.AndroidTestCase; import com.koushikdutta.async.AsyncServer; -import com.koushikdutta.async.future.FutureCallback; import com.koushikdutta.async.http.AsyncHttpClient; import com.koushikdutta.async.http.AsyncHttpGet; import com.koushikdutta.async.http.server.AsyncHttpServer; diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/SanityChecks.java b/AndroidAsync/test/src/com/koushikdutta/async/test/SanityChecks.java index 69cc566..69cc566 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/SanityChecks.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/SanityChecks.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/SocketIOTests.java index 4c8ec66..4c8ec66 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/SocketIOTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/SocketIOTests.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/TimeoutTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/TimeoutTests.java index 2eb13b4..2eb13b4 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/TimeoutTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/TimeoutTests.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/TriggerFuture.java b/AndroidAsync/test/src/com/koushikdutta/async/test/TriggerFuture.java index eab00ed..eab00ed 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/TriggerFuture.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/TriggerFuture.java diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/WebSocketTests.java b/AndroidAsync/test/src/com/koushikdutta/async/test/WebSocketTests.java index a5a7f92..a5a7f92 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/WebSocketTests.java +++ b/AndroidAsync/test/src/com/koushikdutta/async/test/WebSocketTests.java diff --git a/AndroidAsyncTest/.classpath b/AndroidAsyncTest/.classpath deleted file mode 100644 index 6982030..0000000 --- a/AndroidAsyncTest/.classpath +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<classpath> - <classpathentry combineaccessrules="false" kind="src" path="/AndroidAsyncSample"/> - <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> - <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> - <classpathentry kind="src" path="src"/> - <classpathentry kind="src" path="gen"/> - <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/> - <classpathentry kind="output" path="bin/classes"/> -</classpath> diff --git a/AndroidAsyncTest/.project b/AndroidAsyncTest/.project deleted file mode 100644 index 379c28d..0000000 --- a/AndroidAsyncTest/.project +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<projectDescription> - <name>AndroidAsyncTest</name> - <comment></comment> - <projects> - <project>AndroidAsyncSample</project> - </projects> - <buildSpec> - <buildCommand> - <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>org.eclipse.jdt.core.javabuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>com.android.ide.eclipse.adt.ApkBuilder</name> - <arguments> - </arguments> - </buildCommand> - </buildSpec> - <natures> - <nature>com.android.ide.eclipse.adt.AndroidNature</nature> - <nature>org.eclipse.jdt.core.javanature</nature> - </natures> -</projectDescription> diff --git a/AndroidAsyncTest/AndroidAsyncTest.iml b/AndroidAsyncTest/AndroidAsyncTest.iml deleted file mode 100644 index fb8467f..0000000 --- a/AndroidAsyncTest/AndroidAsyncTest.iml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<module type="JAVA_MODULE" version="4"> - <component name="FacetManager"> - <facet type="android" name="Android"> - <configuration> - <option name="UPDATE_PROPERTY_FILES" value="true" /> - <notImportedProperties> - <property>MANIFEST_FILE_PATH</property> - <property>RESOURCES_DIR_PATH</property> - <property>ASSETS_DIR_PATH</property> - <property>NATIVE_LIBS_DIR_PATH</property> - </notImportedProperties> - </configuration> - </facet> - </component> - <component name="NewModuleRootManager" inherit-compiler-output="true"> - <exclude-output /> - <content url="file://$MODULE_DIR$"> - <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" /> - <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> - </content> - <orderEntry type="inheritedJdk" /> - <orderEntry type="sourceFolder" forTests="false" /> - <orderEntry type="module" module-name="AndroidAsync" /> - </component> -</module> - diff --git a/AndroidAsyncTest/AndroidManifest.xml b/AndroidAsyncTest/AndroidManifest.xml deleted file mode 100644 index 1c36b17..0000000 --- a/AndroidAsyncTest/AndroidManifest.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.koushikdutta.async.test" - android:versionCode="1" - android:versionName="1.0" > - - <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" /> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> - <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> - - <instrumentation - android:name="android.test.InstrumentationTestRunner" - android:targetPackage="com.koushikdutta.async.test" /> - - <application - android:debuggable="true" - android:icon="@drawable/ic_launcher" - android:label="@string/app_name" > - <uses-library android:name="android.test.runner" /> - </application> - -</manifest>
\ No newline at end of file diff --git a/AndroidAsyncTest/proguard-project.txt b/AndroidAsyncTest/proguard-project.txt deleted file mode 100644 index f2fe155..0000000 --- a/AndroidAsyncTest/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# 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 *; -#} diff --git a/AndroidAsyncTest/project.properties b/AndroidAsyncTest/project.properties deleted file mode 100644 index 7e931bf..0000000 --- a/AndroidAsyncTest/project.properties +++ /dev/null @@ -1,15 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-19 -android.library.reference.1=../AndroidAsync @@ -137,7 +137,7 @@ AsyncHttpClient.getDefaultInstance().websocket(get, "my-protocol", new WebSocket ```
-### AndroidAsync also supports socket.io
+### AndroidAsync also supports socket.io (version 0.9.x)
```java
SocketIOClient.connect(AsyncHttpClient.getDefaultInstance(), "http://192.168.1.2:3000", new ConnectCallback() {
|