diff options
author | Koushik Dutta <koushd@gmail.com> | 2012-08-23 23:38:18 -0700 |
---|---|---|
committer | Koushik Dutta <koushd@gmail.com> | 2012-08-23 23:38:18 -0700 |
commit | 4705fa51dcc6f96ce7ef145d836077692d6d8a80 (patch) | |
tree | ceaf08f5e8f78e2f9da21547d240c347dec5d6a0 | |
download | AndroidAsync-4705fa51dcc6f96ce7ef145d836077692d6d8a80.tar.gz AndroidAsync-4705fa51dcc6f96ce7ef145d836077692d6d8a80.tar.bz2 AndroidAsync-4705fa51dcc6f96ce7ef145d836077692d6d8a80.zip |
initial
179 files changed, 4562 insertions, 0 deletions
diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..a4763d1 --- /dev/null +++ b/.classpath @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="gen"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> + <classpathentry kind="output" path="bin/classes"/> +</classpath> diff --git a/.project b/.project new file mode 100644 index 0000000..4e6d9d1 --- /dev/null +++ b/.project @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>AndroidAsync</name> + <comment></comment> + <projects> + </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/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..d89e9aa --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,25 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.koushikdutta.async" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk + android:minSdkVersion="8" + android:targetSdkVersion="15" /> + <uses-permission android:name="android.permission.INTERNET"/> + + <application + android:icon="@drawable/ic_launcher" + android:label="@string/name" > + <activity + android:name="com.koushikdutta.test.TestActivity" + android:label="@string/name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest>
\ No newline at end of file diff --git a/bin/AndroidAsync.apk b/bin/AndroidAsync.apk Binary files differnew file mode 100644 index 0000000..ab9fcf6 --- /dev/null +++ b/bin/AndroidAsync.apk diff --git a/bin/AndroidManifest.xml b/bin/AndroidManifest.xml new file mode 100644 index 0000000..d89e9aa --- /dev/null +++ b/bin/AndroidManifest.xml @@ -0,0 +1,25 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.koushikdutta.async" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk + android:minSdkVersion="8" + android:targetSdkVersion="15" /> + <uses-permission android:name="android.permission.INTERNET"/> + + <application + android:icon="@drawable/ic_launcher" + android:label="@string/name" > + <activity + android:name="com.koushikdutta.test.TestActivity" + android:label="@string/name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest>
\ No newline at end of file diff --git a/bin/classes.dex b/bin/classes.dex Binary files differnew file mode 100644 index 0000000..6aec072 --- /dev/null +++ b/bin/classes.dex diff --git a/bin/classes/com/koushikdutta/async/AsyncInputStream.class b/bin/classes/com/koushikdutta/async/AsyncInputStream.class Binary files differnew file mode 100644 index 0000000..5b15e44 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/AsyncInputStream.class diff --git a/bin/classes/com/koushikdutta/async/AsyncServer$1.class b/bin/classes/com/koushikdutta/async/AsyncServer$1.class Binary files differnew file mode 100644 index 0000000..f5964d1 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/AsyncServer$1.class diff --git a/bin/classes/com/koushikdutta/async/AsyncServer$2.class b/bin/classes/com/koushikdutta/async/AsyncServer$2.class Binary files differnew file mode 100644 index 0000000..289a152 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/AsyncServer$2.class diff --git a/bin/classes/com/koushikdutta/async/AsyncServer$3.class b/bin/classes/com/koushikdutta/async/AsyncServer$3.class Binary files differnew file mode 100644 index 0000000..bf3e839 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/AsyncServer$3.class diff --git a/bin/classes/com/koushikdutta/async/AsyncServer$4.class b/bin/classes/com/koushikdutta/async/AsyncServer$4.class Binary files differnew file mode 100644 index 0000000..9344f51 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/AsyncServer$4.class diff --git a/bin/classes/com/koushikdutta/async/AsyncServer$5.class b/bin/classes/com/koushikdutta/async/AsyncServer$5.class Binary files differnew file mode 100644 index 0000000..0192c2d --- /dev/null +++ b/bin/classes/com/koushikdutta/async/AsyncServer$5.class diff --git a/bin/classes/com/koushikdutta/async/AsyncServer$6.class b/bin/classes/com/koushikdutta/async/AsyncServer$6.class Binary files differnew file mode 100644 index 0000000..438b84c --- /dev/null +++ b/bin/classes/com/koushikdutta/async/AsyncServer$6.class diff --git a/bin/classes/com/koushikdutta/async/AsyncServer.class b/bin/classes/com/koushikdutta/async/AsyncServer.class Binary files differnew file mode 100644 index 0000000..81ad283 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/AsyncServer.class diff --git a/bin/classes/com/koushikdutta/async/AsyncSocket.class b/bin/classes/com/koushikdutta/async/AsyncSocket.class Binary files differnew file mode 100644 index 0000000..35cbe76 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/AsyncSocket.class diff --git a/bin/classes/com/koushikdutta/async/AsyncSocketImpl.class b/bin/classes/com/koushikdutta/async/AsyncSocketImpl.class Binary files differnew file mode 100644 index 0000000..b7ed6bb --- /dev/null +++ b/bin/classes/com/koushikdutta/async/AsyncSocketImpl.class diff --git a/bin/classes/com/koushikdutta/async/BufferedDataEmitter.class b/bin/classes/com/koushikdutta/async/BufferedDataEmitter.class Binary files differnew file mode 100644 index 0000000..89e2348 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/BufferedDataEmitter.class diff --git a/bin/classes/com/koushikdutta/async/BufferedDataSink$1.class b/bin/classes/com/koushikdutta/async/BufferedDataSink$1.class Binary files differnew file mode 100644 index 0000000..11ec0b7 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/BufferedDataSink$1.class diff --git a/bin/classes/com/koushikdutta/async/BufferedDataSink.class b/bin/classes/com/koushikdutta/async/BufferedDataSink.class Binary files differnew file mode 100644 index 0000000..6eaf50a --- /dev/null +++ b/bin/classes/com/koushikdutta/async/BufferedDataSink.class diff --git a/bin/classes/com/koushikdutta/async/BuildConfig.class b/bin/classes/com/koushikdutta/async/BuildConfig.class Binary files differnew file mode 100644 index 0000000..7612af6 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/BuildConfig.class diff --git a/bin/classes/com/koushikdutta/async/ByteBufferList.class b/bin/classes/com/koushikdutta/async/ByteBufferList.class Binary files differnew file mode 100644 index 0000000..fe062e7 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/ByteBufferList.class diff --git a/bin/classes/com/koushikdutta/async/ChannelWrapper.class b/bin/classes/com/koushikdutta/async/ChannelWrapper.class Binary files differnew file mode 100644 index 0000000..41616b7 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/ChannelWrapper.class diff --git a/bin/classes/com/koushikdutta/async/CloseableData.class b/bin/classes/com/koushikdutta/async/CloseableData.class Binary files differnew file mode 100644 index 0000000..cd7e9e0 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/CloseableData.class diff --git a/bin/classes/com/koushikdutta/async/DataEmitter.class b/bin/classes/com/koushikdutta/async/DataEmitter.class Binary files differnew file mode 100644 index 0000000..4dbb832 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/DataEmitter.class diff --git a/bin/classes/com/koushikdutta/async/DataEmitterStream$1.class b/bin/classes/com/koushikdutta/async/DataEmitterStream$1.class Binary files differnew file mode 100644 index 0000000..8ffd61e --- /dev/null +++ b/bin/classes/com/koushikdutta/async/DataEmitterStream$1.class diff --git a/bin/classes/com/koushikdutta/async/DataEmitterStream.class b/bin/classes/com/koushikdutta/async/DataEmitterStream.class Binary files differnew file mode 100644 index 0000000..da0dab4 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/DataEmitterStream.class diff --git a/bin/classes/com/koushikdutta/async/DataExchange.class b/bin/classes/com/koushikdutta/async/DataExchange.class Binary files differnew file mode 100644 index 0000000..cde8f81 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/DataExchange.class diff --git a/bin/classes/com/koushikdutta/async/DataSink.class b/bin/classes/com/koushikdutta/async/DataSink.class Binary files differnew file mode 100644 index 0000000..a9d9642 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/DataSink.class diff --git a/bin/classes/com/koushikdutta/async/DataTransformer.class b/bin/classes/com/koushikdutta/async/DataTransformer.class Binary files differnew file mode 100644 index 0000000..90afdd4 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/DataTransformer.class diff --git a/bin/classes/com/koushikdutta/async/DataTransformerBase.class b/bin/classes/com/koushikdutta/async/DataTransformerBase.class Binary files differnew file mode 100644 index 0000000..d826fed --- /dev/null +++ b/bin/classes/com/koushikdutta/async/DataTransformerBase.class diff --git a/bin/classes/com/koushikdutta/async/DatagramChannelWrapper.class b/bin/classes/com/koushikdutta/async/DatagramChannelWrapper.class Binary files differnew file mode 100644 index 0000000..0cec06f --- /dev/null +++ b/bin/classes/com/koushikdutta/async/DatagramChannelWrapper.class diff --git a/bin/classes/com/koushikdutta/async/ExceptionCallback.class b/bin/classes/com/koushikdutta/async/ExceptionCallback.class Binary files differnew file mode 100644 index 0000000..89aba7a --- /dev/null +++ b/bin/classes/com/koushikdutta/async/ExceptionCallback.class diff --git a/bin/classes/com/koushikdutta/async/ExceptionEmitter.class b/bin/classes/com/koushikdutta/async/ExceptionEmitter.class Binary files differnew file mode 100644 index 0000000..d240496 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/ExceptionEmitter.class diff --git a/bin/classes/com/koushikdutta/async/LineEmitter$1.class b/bin/classes/com/koushikdutta/async/LineEmitter$1.class Binary files differnew file mode 100644 index 0000000..8cfd840 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/LineEmitter$1.class diff --git a/bin/classes/com/koushikdutta/async/LineEmitter$StringCallback.class b/bin/classes/com/koushikdutta/async/LineEmitter$StringCallback.class Binary files differnew file mode 100644 index 0000000..4ae606c --- /dev/null +++ b/bin/classes/com/koushikdutta/async/LineEmitter$StringCallback.class diff --git a/bin/classes/com/koushikdutta/async/LineEmitter.class b/bin/classes/com/koushikdutta/async/LineEmitter.class Binary files differnew file mode 100644 index 0000000..086518b --- /dev/null +++ b/bin/classes/com/koushikdutta/async/LineEmitter.class diff --git a/bin/classes/com/koushikdutta/async/NullDataCallback.class b/bin/classes/com/koushikdutta/async/NullDataCallback.class Binary files differnew file mode 100644 index 0000000..7b41382 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/NullDataCallback.class diff --git a/bin/classes/com/koushikdutta/async/PushParser$1.class b/bin/classes/com/koushikdutta/async/PushParser$1.class Binary files differnew file mode 100644 index 0000000..e9dc8ba --- /dev/null +++ b/bin/classes/com/koushikdutta/async/PushParser$1.class diff --git a/bin/classes/com/koushikdutta/async/PushParser$BufferWaiter.class b/bin/classes/com/koushikdutta/async/PushParser$BufferWaiter.class Binary files differnew file mode 100644 index 0000000..18abbde --- /dev/null +++ b/bin/classes/com/koushikdutta/async/PushParser$BufferWaiter.class diff --git a/bin/classes/com/koushikdutta/async/PushParser$UntilWaiter.class b/bin/classes/com/koushikdutta/async/PushParser$UntilWaiter.class Binary files differnew file mode 100644 index 0000000..2c61012 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/PushParser$UntilWaiter.class diff --git a/bin/classes/com/koushikdutta/async/PushParser.class b/bin/classes/com/koushikdutta/async/PushParser.class Binary files differnew file mode 100644 index 0000000..7653a65 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/PushParser.class diff --git a/bin/classes/com/koushikdutta/async/R$attr.class b/bin/classes/com/koushikdutta/async/R$attr.class Binary files differnew file mode 100644 index 0000000..02e07ae --- /dev/null +++ b/bin/classes/com/koushikdutta/async/R$attr.class diff --git a/bin/classes/com/koushikdutta/async/R$drawable.class b/bin/classes/com/koushikdutta/async/R$drawable.class Binary files differnew file mode 100644 index 0000000..ee06b8a --- /dev/null +++ b/bin/classes/com/koushikdutta/async/R$drawable.class diff --git a/bin/classes/com/koushikdutta/async/R$string.class b/bin/classes/com/koushikdutta/async/R$string.class Binary files differnew file mode 100644 index 0000000..99b8bac --- /dev/null +++ b/bin/classes/com/koushikdutta/async/R$string.class diff --git a/bin/classes/com/koushikdutta/async/R.class b/bin/classes/com/koushikdutta/async/R.class Binary files differnew file mode 100644 index 0000000..6638f8a --- /dev/null +++ b/bin/classes/com/koushikdutta/async/R.class diff --git a/bin/classes/com/koushikdutta/async/SSLDataExchange$1.class b/bin/classes/com/koushikdutta/async/SSLDataExchange$1.class Binary files differnew file mode 100644 index 0000000..4572273 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/SSLDataExchange$1.class diff --git a/bin/classes/com/koushikdutta/async/SSLDataExchange.class b/bin/classes/com/koushikdutta/async/SSLDataExchange.class Binary files differnew file mode 100644 index 0000000..e8f662c --- /dev/null +++ b/bin/classes/com/koushikdutta/async/SSLDataExchange.class diff --git a/bin/classes/com/koushikdutta/async/ServerSocketChannelWrapper.class b/bin/classes/com/koushikdutta/async/ServerSocketChannelWrapper.class Binary files differnew file mode 100644 index 0000000..87517dd --- /dev/null +++ b/bin/classes/com/koushikdutta/async/ServerSocketChannelWrapper.class diff --git a/bin/classes/com/koushikdutta/async/SocketChannelWrapper.class b/bin/classes/com/koushikdutta/async/SocketChannelWrapper.class Binary files differnew file mode 100644 index 0000000..a617cc7 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/SocketChannelWrapper.class diff --git a/bin/classes/com/koushikdutta/async/TapCallback.class b/bin/classes/com/koushikdutta/async/TapCallback.class Binary files differnew file mode 100644 index 0000000..63f4ef7 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/TapCallback.class diff --git a/bin/classes/com/koushikdutta/async/Util.class b/bin/classes/com/koushikdutta/async/Util.class Binary files differnew file mode 100644 index 0000000..28c895a --- /dev/null +++ b/bin/classes/com/koushikdutta/async/Util.class diff --git a/bin/classes/com/koushikdutta/async/callback/ClosedCallback.class b/bin/classes/com/koushikdutta/async/callback/ClosedCallback.class Binary files differnew file mode 100644 index 0000000..aaa3138 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/callback/ClosedCallback.class diff --git a/bin/classes/com/koushikdutta/async/callback/CompletedCallback.class b/bin/classes/com/koushikdutta/async/callback/CompletedCallback.class Binary files differnew file mode 100644 index 0000000..a36f8d8 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/callback/CompletedCallback.class diff --git a/bin/classes/com/koushikdutta/async/callback/ConnectCallback.class b/bin/classes/com/koushikdutta/async/callback/ConnectCallback.class Binary files differnew file mode 100644 index 0000000..9b8d452 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/callback/ConnectCallback.class diff --git a/bin/classes/com/koushikdutta/async/callback/DataCallback.class b/bin/classes/com/koushikdutta/async/callback/DataCallback.class Binary files differnew file mode 100644 index 0000000..d5f1ddd --- /dev/null +++ b/bin/classes/com/koushikdutta/async/callback/DataCallback.class diff --git a/bin/classes/com/koushikdutta/async/callback/ListenCallback.class b/bin/classes/com/koushikdutta/async/callback/ListenCallback.class Binary files differnew file mode 100644 index 0000000..7bfcfeb --- /dev/null +++ b/bin/classes/com/koushikdutta/async/callback/ListenCallback.class diff --git a/bin/classes/com/koushikdutta/async/callback/ResultCallback.class b/bin/classes/com/koushikdutta/async/callback/ResultCallback.class Binary files differnew file mode 100644 index 0000000..c36abf3 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/callback/ResultCallback.class diff --git a/bin/classes/com/koushikdutta/async/callback/WritableCallback.class b/bin/classes/com/koushikdutta/async/callback/WritableCallback.class Binary files differnew file mode 100644 index 0000000..33136c8 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/callback/WritableCallback.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$1$1.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$1$1.class Binary files differnew file mode 100644 index 0000000..4243fb0 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$1$1.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$1.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$1.class Binary files differnew file mode 100644 index 0000000..93b189c --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$1.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$2.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$2.class Binary files differnew file mode 100644 index 0000000..346c8e5 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$2.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$3.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$3.class Binary files differnew file mode 100644 index 0000000..19a2e30 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$3.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$4$1.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$4$1.class Binary files differnew file mode 100644 index 0000000..189030d --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$4$1.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$4$2.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$4$2.class Binary files differnew file mode 100644 index 0000000..a22501b --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$4$2.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$4.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$4.class Binary files differnew file mode 100644 index 0000000..878e233 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$4.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$DownloadCallback.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$DownloadCallback.class Binary files differnew file mode 100644 index 0000000..fddb341 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$DownloadCallback.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$ResultConvert.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$ResultConvert.class Binary files differnew file mode 100644 index 0000000..e9e9faf --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$ResultConvert.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$StringCallback.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$StringCallback.class Binary files differnew file mode 100644 index 0000000..cf8f81f --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient$StringCallback.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpClient.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient.class Binary files differnew file mode 100644 index 0000000..f22c869 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpClient.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpGet.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpGet.class Binary files differnew file mode 100644 index 0000000..c19161c --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpGet.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpRequest.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpRequest.class Binary files differnew file mode 100644 index 0000000..9b2ef05 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpRequest.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpResponse.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponse.class Binary files differnew file mode 100644 index 0000000..533e9ed --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponse.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$1.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$1.class Binary files differnew file mode 100644 index 0000000..6add408 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$1.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$2.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$2.class Binary files differnew file mode 100644 index 0000000..71eaf0c --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$2.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$3.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$3.class Binary files differnew file mode 100644 index 0000000..e85567f --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$3.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$4.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$4.class Binary files differnew file mode 100644 index 0000000..09dd52c --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$4.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$5.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$5.class Binary files differnew file mode 100644 index 0000000..f94ceee --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$5.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$6.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$6.class Binary files differnew file mode 100644 index 0000000..d51fd42 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl$6.class diff --git a/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl.class b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl.class Binary files differnew file mode 100644 index 0000000..bfefb33 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/AsyncHttpResponseImpl.class diff --git a/bin/classes/com/koushikdutta/async/http/callback/HttpConnectCallback.class b/bin/classes/com/koushikdutta/async/http/callback/HttpConnectCallback.class Binary files differnew file mode 100644 index 0000000..0ed419c --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/callback/HttpConnectCallback.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/HeaderParser$CacheControlHandler.class b/bin/classes/com/koushikdutta/async/http/libcore/HeaderParser$CacheControlHandler.class Binary files differnew file mode 100644 index 0000000..805b7e2 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/HeaderParser$CacheControlHandler.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/HeaderParser.class b/bin/classes/com/koushikdutta/async/http/libcore/HeaderParser.class Binary files differnew file mode 100644 index 0000000..f8f1d3e --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/HeaderParser.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/HttpDate$1.class b/bin/classes/com/koushikdutta/async/http/libcore/HttpDate$1.class Binary files differnew file mode 100644 index 0000000..7f8cfa0 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/HttpDate$1.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/HttpDate.class b/bin/classes/com/koushikdutta/async/http/libcore/HttpDate.class Binary files differnew file mode 100644 index 0000000..6bcf984 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/HttpDate.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/Memory.class b/bin/classes/com/koushikdutta/async/http/libcore/Memory.class Binary files differnew file mode 100644 index 0000000..46f1152 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/Memory.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/Objects.class b/bin/classes/com/koushikdutta/async/http/libcore/Objects.class Binary files differnew file mode 100644 index 0000000..3095a54 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/Objects.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/RawHeaders$1.class b/bin/classes/com/koushikdutta/async/http/libcore/RawHeaders$1.class Binary files differnew file mode 100644 index 0000000..0a2ca76 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/RawHeaders$1.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/RawHeaders.class b/bin/classes/com/koushikdutta/async/http/libcore/RawHeaders.class Binary files differnew file mode 100644 index 0000000..ff9146f --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/RawHeaders.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/RequestHeaders$1.class b/bin/classes/com/koushikdutta/async/http/libcore/RequestHeaders$1.class Binary files differnew file mode 100644 index 0000000..47190b7 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/RequestHeaders$1.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/RequestHeaders.class b/bin/classes/com/koushikdutta/async/http/libcore/RequestHeaders.class Binary files differnew file mode 100644 index 0000000..93207ad --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/RequestHeaders.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/ResponseHeaders$1.class b/bin/classes/com/koushikdutta/async/http/libcore/ResponseHeaders$1.class Binary files differnew file mode 100644 index 0000000..a009a64 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/ResponseHeaders$1.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/ResponseHeaders.class b/bin/classes/com/koushikdutta/async/http/libcore/ResponseHeaders.class Binary files differnew file mode 100644 index 0000000..a1fa52d --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/ResponseHeaders.class diff --git a/bin/classes/com/koushikdutta/async/http/libcore/ResponseSource.class b/bin/classes/com/koushikdutta/async/http/libcore/ResponseSource.class Binary files differnew file mode 100644 index 0000000..c67dee9 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/libcore/ResponseSource.class diff --git a/bin/classes/com/koushikdutta/async/http/transform/ChunkedTransformer$State.class b/bin/classes/com/koushikdutta/async/http/transform/ChunkedTransformer$State.class Binary files differnew file mode 100644 index 0000000..b2942da --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/transform/ChunkedTransformer$State.class diff --git a/bin/classes/com/koushikdutta/async/http/transform/ChunkedTransformer.class b/bin/classes/com/koushikdutta/async/http/transform/ChunkedTransformer.class Binary files differnew file mode 100644 index 0000000..176eaea --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/transform/ChunkedTransformer.class diff --git a/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1$1$1.class b/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1$1$1.class Binary files differnew file mode 100644 index 0000000..8c06161 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1$1$1.class diff --git a/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1$1.class b/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1$1.class Binary files differnew file mode 100644 index 0000000..6b6c31e --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1$1.class diff --git a/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1$2.class b/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1$2.class Binary files differnew file mode 100644 index 0000000..3be4915 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1$2.class diff --git a/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1$3.class b/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1$3.class Binary files differnew file mode 100644 index 0000000..f215476 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1$3.class diff --git a/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1.class b/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1.class Binary files differnew file mode 100644 index 0000000..c870c93 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer$1.class diff --git a/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer.class b/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer.class Binary files differnew file mode 100644 index 0000000..3f1df22 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/transform/GZIPTransformer.class diff --git a/bin/classes/com/koushikdutta/async/http/transform/InflaterTransformer.class b/bin/classes/com/koushikdutta/async/http/transform/InflaterTransformer.class Binary files differnew file mode 100644 index 0000000..fe5eba3 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/http/transform/InflaterTransformer.class diff --git a/bin/classes/com/koushikdutta/async/stream/OutputStreamDataCallback.class b/bin/classes/com/koushikdutta/async/stream/OutputStreamDataCallback.class Binary files differnew file mode 100644 index 0000000..80e3a21 --- /dev/null +++ b/bin/classes/com/koushikdutta/async/stream/OutputStreamDataCallback.class diff --git a/bin/classes/com/koushikdutta/test/TestActivity$1.class b/bin/classes/com/koushikdutta/test/TestActivity$1.class Binary files differnew file mode 100644 index 0000000..b84eba5 --- /dev/null +++ b/bin/classes/com/koushikdutta/test/TestActivity$1.class diff --git a/bin/classes/com/koushikdutta/test/TestActivity.class b/bin/classes/com/koushikdutta/test/TestActivity.class Binary files differnew file mode 100644 index 0000000..08c0d49 --- /dev/null +++ b/bin/classes/com/koushikdutta/test/TestActivity.class diff --git a/bin/res/drawable-hdpi/ic_action_search.png b/bin/res/drawable-hdpi/ic_action_search.png Binary files differnew file mode 100644 index 0000000..ffec9be --- /dev/null +++ b/bin/res/drawable-hdpi/ic_action_search.png diff --git a/bin/res/drawable-hdpi/ic_launcher.png b/bin/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..2642d5d --- /dev/null +++ b/bin/res/drawable-hdpi/ic_launcher.png diff --git a/bin/res/drawable-ldpi/ic_launcher.png b/bin/res/drawable-ldpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..28424c3 --- /dev/null +++ b/bin/res/drawable-ldpi/ic_launcher.png diff --git a/bin/res/drawable-mdpi/ic_action_search.png b/bin/res/drawable-mdpi/ic_action_search.png Binary files differnew file mode 100644 index 0000000..ce9399c --- /dev/null +++ b/bin/res/drawable-mdpi/ic_action_search.png diff --git a/bin/res/drawable-mdpi/ic_launcher.png b/bin/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..312d96f --- /dev/null +++ b/bin/res/drawable-mdpi/ic_launcher.png diff --git a/bin/res/drawable-xhdpi/ic_action_search.png b/bin/res/drawable-xhdpi/ic_action_search.png Binary files differnew file mode 100644 index 0000000..b2d7b2d --- /dev/null +++ b/bin/res/drawable-xhdpi/ic_action_search.png diff --git a/bin/res/drawable-xhdpi/ic_launcher.png b/bin/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..2a66d4d --- /dev/null +++ b/bin/res/drawable-xhdpi/ic_launcher.png diff --git a/bin/resources.ap_ b/bin/resources.ap_ Binary files differnew file mode 100644 index 0000000..c94e2c9 --- /dev/null +++ b/bin/resources.ap_ diff --git a/gen/com/koushikdutta/async/BuildConfig.java b/gen/com/koushikdutta/async/BuildConfig.java new file mode 100644 index 0000000..dd94c94 --- /dev/null +++ b/gen/com/koushikdutta/async/BuildConfig.java @@ -0,0 +1,6 @@ +/** Automatically generated file. DO NOT MODIFY */ +package com.koushikdutta.async; + +public final class BuildConfig { + public final static boolean DEBUG = true; +}
\ No newline at end of file diff --git a/gen/com/koushikdutta/async/R.java b/gen/com/koushikdutta/async/R.java new file mode 100644 index 0000000..7761246 --- /dev/null +++ b/gen/com/koushikdutta/async/R.java @@ -0,0 +1,20 @@ +/* AUTO-GENERATED FILE. DO NOT MODIFY. + * + * This class was automatically generated by the + * aapt tool from the resource data it found. It + * should not be modified by hand. + */ + +package com.koushikdutta.async; + +public final class R { + public static final class attr { + } + public static final class drawable { + public static final int ic_action_search=0x7f020000; + public static final int ic_launcher=0x7f020001; + } + public static final class string { + public static final int name=0x7f030000; + } +} diff --git a/ic_launcher-web.png b/ic_launcher-web.png Binary files differnew file mode 100644 index 0000000..c37372a --- /dev/null +++ b/ic_launcher-web.png diff --git a/libs/android-support-v4.jar b/libs/android-support-v4.jar Binary files differnew file mode 100644 index 0000000..feaf44f --- /dev/null +++ b/libs/android-support-v4.jar diff --git a/proguard-project.txt b/proguard-project.txt new file mode 100644 index 0000000..f2fe155 --- /dev/null +++ b/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/project.properties b/project.properties new file mode 100644 index 0000000..9b84a6b --- /dev/null +++ b/project.properties @@ -0,0 +1,14 @@ +# 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-16 diff --git a/res/drawable-hdpi/ic_action_search.png b/res/drawable-hdpi/ic_action_search.png Binary files differnew file mode 100644 index 0000000..67de12d --- /dev/null +++ b/res/drawable-hdpi/ic_action_search.png diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..a301d57 --- /dev/null +++ b/res/drawable-hdpi/ic_launcher.png diff --git a/res/drawable-ldpi/ic_launcher.png b/res/drawable-ldpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..2c2a58b --- /dev/null +++ b/res/drawable-ldpi/ic_launcher.png diff --git a/res/drawable-mdpi/ic_action_search.png b/res/drawable-mdpi/ic_action_search.png Binary files differnew file mode 100644 index 0000000..134d549 --- /dev/null +++ b/res/drawable-mdpi/ic_action_search.png diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..f91f736 --- /dev/null +++ b/res/drawable-mdpi/ic_launcher.png diff --git a/res/drawable-xhdpi/ic_action_search.png b/res/drawable-xhdpi/ic_action_search.png Binary files differnew file mode 100644 index 0000000..d699c6b --- /dev/null +++ b/res/drawable-xhdpi/ic_action_search.png diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..96095ec --- /dev/null +++ b/res/drawable-xhdpi/ic_launcher.png diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..fd9382d --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="name">Async</string> + +</resources>
\ No newline at end of file diff --git a/src/com/koushikdutta/async/AsyncInputStream.java b/src/com/koushikdutta/async/AsyncInputStream.java new file mode 100644 index 0000000..20bb5ad --- /dev/null +++ b/src/com/koushikdutta/async/AsyncInputStream.java @@ -0,0 +1,7 @@ +package com.koushikdutta.async; + +import com.koushikdutta.async.callback.DataCallback; + +public interface AsyncInputStream { + public void read(int count, DataCallback callback); +} diff --git a/src/com/koushikdutta/async/AsyncServer.java b/src/com/koushikdutta/async/AsyncServer.java new file mode 100644 index 0000000..18883cd --- /dev/null +++ b/src/com/koushikdutta/async/AsyncServer.java @@ -0,0 +1,306 @@ +package com.koushikdutta.async; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; +import java.util.LinkedList; +import java.util.Set; +import java.util.concurrent.Semaphore; + +import com.koushikdutta.async.callback.ConnectCallback; +import com.koushikdutta.async.callback.ListenCallback; + +import junit.framework.Assert; +import android.util.Log; + +public class AsyncServer { + private static final String LOGTAG = "NIO"; + + static AsyncServer mInstance; + public static AsyncServer getDefault() { + if (mInstance == null) + mInstance = new AsyncServer(); + + if (!mInstance.mRun) { + try { + mInstance.initialize(); + } + catch (IOException e) { + e.printStackTrace(); + } + + new Thread() { + @Override + public void run() { + mInstance.run(); + } + }.start(); + } + + return mInstance; + } + + Selector mSelector; + + public AsyncServer() { + } + + private void handleSocket(final AsyncSocketImpl handler) throws ClosedChannelException { + final ChannelWrapper sc = handler.getChannel(); + SelectionKey ckey = sc.register(mSelector); + ckey.attach(handler); + handler.mKey = ckey; + } + + public void post(Runnable runnable) { + synchronized (mQueue) { + mQueue.add(runnable); + } + if (Thread.currentThread() == mAffinity) { + runQueue(); + } + else { + mSelector.wakeup(); + } + } + + public void run(final Runnable runnable) { + final Semaphore semaphore = new Semaphore(0); + post(new Runnable() { + @Override + public void run() { + runnable.run(); + semaphore.release(); + } + }); + try { + semaphore.acquire(); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private void runQueue() { + Assert.assertEquals(Thread.currentThread(), mAffinity); + synchronized (mQueue) { + while (mQueue.size() > 0) { + Runnable run = mQueue.remove(); + run.run(); + } + } + } + + boolean mRun = false; + boolean mShuttingDown = false; + LinkedList<Runnable> mQueue = new LinkedList<Runnable>(); + + public void stop() { + mRun = false; + mShuttingDown = true; + mSelector.wakeup(); + } + + protected void onDataTransmitted(int transmitted) { + } + + public void listen(InetAddress host, int port, final ListenCallback handler) throws IOException { + ServerSocketChannel server = ServerSocketChannel.open(); + final ServerSocketChannelWrapper wrapper = new ServerSocketChannelWrapper(server); + InetSocketAddress isa = new InetSocketAddress(host, port); + server.socket().bind(isa); + run(new Runnable() { + @Override + public void run() { + try { + SelectionKey key = wrapper.register(mSelector); + key.attach(handler); + } + catch (ClosedChannelException e) { + e.printStackTrace(); + } + } + }); + } + + public void connectSocket(final SocketAddress remote, final ConnectCallback handler) { + try { + final SocketChannel socket = SocketChannel.open(); + final ChannelWrapper sc = new SocketChannelWrapper(socket); + post(new Runnable() { + @Override + public void run() { + try { + SelectionKey ckey = sc.register(mSelector); + ckey.attach(handler); + socket.connect(remote); + } + catch (Exception e) { + handler.onConnectCompleted(e, null); + } + } + }); + } + catch (Exception e) { + handler.onConnectCompleted(e, null); + } + } + + public void connectSocket(final String host, final int port, final ConnectCallback handler) { + try { + final SocketChannel socket = SocketChannel.open(); + final ChannelWrapper sc = new SocketChannelWrapper(socket); + post(new Runnable() { + @Override + public void run() { + try { + SocketAddress remote = new InetSocketAddress(host, port); + socket.connect(remote); + SelectionKey ckey = sc.register(mSelector); + ckey.attach(handler); + } + catch (Exception e) { + handler.onConnectCompleted(e, null); + } + } + }); + } + catch (Exception e) { + handler.onConnectCompleted(e, null); + } + } + + public AsyncSocket connectDatagram(final SocketAddress remote) throws IOException { + final DatagramChannel socket = DatagramChannel.open(); + final AsyncSocketImpl handler = new AsyncSocketImpl(); + handler.attach(socket); + // ugh.. this should really be post to make it nonblocking... + // but i want datagrams to be immediately writreable. + // they're not really used anyways. + run(new Runnable() { + @Override + public void run() { + try { + handleSocket(handler); + socket.connect(remote); + } + catch (Exception e) { + } + } + }); + return handler; + } + + public void initialize() throws IOException { + synchronized (this) { + if (mSelector == null) + mSelector = SelectorProvider.provider().openSelector(); + } + } + + Thread mAffinity; + public void run() { + synchronized (this) { + if (mRun) { + Log.i(LOGTAG, "Already running."); + return; + } + if (mShuttingDown) { + Log.i(LOGTAG, "Shutdown in progress."); + return; + } + mRun = true; + mAffinity = Thread.currentThread(); + } + + while (mRun) { + try { + runLoop(); + } + catch (Exception e) { + Log.i(LOGTAG, "exception?"); + e.printStackTrace(); + } + } + + // SHUT. DOWN. EVERYTHING. + for (SelectionKey key : mSelector.keys()) { + try { + key.channel().close(); + } + catch (IOException e) { + } + } + + try { + mSelector.close(); + } + catch (IOException e) { + } + mSelector = null; + mShuttingDown = false; + mAffinity = null; + } + + private void runLoop() throws IOException { + mSelector.select(); + runQueue(); + Set<SelectionKey> readyKeys = mSelector.selectedKeys(); + for (SelectionKey key : readyKeys) { + if (key.isAcceptable()) { + ServerSocketChannel nextReady = (ServerSocketChannel) key.channel(); + SocketChannel sc = nextReady.accept(); + if (sc == null) + continue; + sc.configureBlocking(false); + SelectionKey ckey = sc.register(mSelector, SelectionKey.OP_READ); + ListenCallback serverHandler = (ListenCallback) key.attachment(); + AsyncSocketImpl handler = new AsyncSocketImpl(); + handler.attach(sc); + handler.mKey = ckey; + ckey.attach(handler); + serverHandler.onAccepted(handler); + } + else if (key.isReadable()) { + AsyncSocketImpl handler = (AsyncSocketImpl) key.attachment(); + int transmitted = handler.onReadable(); + onDataTransmitted(transmitted); + } + else if (key.isWritable()) { + AsyncSocketImpl handler = (AsyncSocketImpl) key.attachment(); + handler.onDataWritable(); + } + else if (key.isConnectable()) { + ConnectCallback handler = (ConnectCallback) key.attachment(); + SocketChannel sc = (SocketChannel) key.channel(); + key.interestOps(SelectionKey.OP_READ); + try { + sc.finishConnect(); + AsyncSocketImpl newHandler = new AsyncSocketImpl(); + newHandler.mKey = key; + newHandler.attach(sc); + key.attach(newHandler); + handler.onConnectCompleted(null, newHandler); + } + catch (Exception ex) { + key.cancel(); + sc.close(); + handler.onConnectCompleted(ex, null); + } + } + else { + Log.i(LOGTAG, "wtf"); + Assert.fail(); + } + } + readyKeys.clear(); + } +} diff --git a/src/com/koushikdutta/async/AsyncSocket.java b/src/com/koushikdutta/async/AsyncSocket.java new file mode 100644 index 0000000..6ff2a5f --- /dev/null +++ b/src/com/koushikdutta/async/AsyncSocket.java @@ -0,0 +1,6 @@ +package com.koushikdutta.async; + + +public interface AsyncSocket extends DataExchange, CloseableData, ExceptionEmitter { + +} diff --git a/src/com/koushikdutta/async/AsyncSocketImpl.java b/src/com/koushikdutta/async/AsyncSocketImpl.java new file mode 100644 index 0000000..b97df3b --- /dev/null +++ b/src/com/koushikdutta/async/AsyncSocketImpl.java @@ -0,0 +1,205 @@ +package com.koushikdutta.async; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; + +import junit.framework.Assert; + +import com.koushikdutta.async.callback.ClosedCallback; +import com.koushikdutta.async.callback.DataCallback; +import com.koushikdutta.async.callback.WritableCallback; + +class AsyncSocketImpl implements AsyncSocket { + AsyncSocketImpl() { + } + + public boolean isChunked() { + return mChannel.isChunked(); + } + + void attach(SocketChannel channel) throws IOException { + mChannel = new SocketChannelWrapper(channel); + } + + void attach(DatagramChannel channel) throws IOException { + mChannel = new DatagramChannelWrapper(channel); + } + + ChannelWrapper getChannel() { + return mChannel; + } + + public void onDataWritable() { + Assert.assertNotNull(mWriteableHandler); + mWriteableHandler.onWriteable(); + } + + private ChannelWrapper mChannel; + SelectionKey mKey; + + @Override + public void write(ByteBufferList list) { + if (!mChannel.isConnected()) { + Assert.assertFalse(mChannel.isChunked()); + return; + } + + try { + mChannel.write(list.toArray()); + handleRemaining(list.remaining()); + } + catch (IOException e) { + } + } + + private void handleRemaining(int remaining) { + if (remaining > 0) { + // chunked channels should not fail + Assert.assertFalse(mChannel.isChunked()); + // register for a write notification if a write fails + mKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); + } + else { + mKey.interestOps(SelectionKey.OP_READ); + } + } + + @Override + public void write(ByteBuffer b) { + try { + if (!mChannel.isConnected()) { + Assert.assertFalse(mChannel.isChunked()); + return; + } + + // keep writing until the the socket can't write any more, or the + // data is exhausted. + mChannel.write(b); + handleRemaining(b.remaining()); + } + catch (IOException ex) { + ex.printStackTrace(); + } + } + + int onReadable() { + int total = 0; + try { + boolean closed = false; + ByteBufferList list = new ByteBufferList(); + while (true) { + ByteBuffer b = ByteBuffer.allocate(2 << 10); + list.add(b); + int read = mChannel.read(b); + b.limit(b.position()); + b.position(0); + if (read < 0) { + close(); + closed = true; + } + else { + total += read; + } + if (read <= 0) + break; + if (mChannel.isChunked()) { + Assert.assertNotNull(mDataHandler); + mDataHandler.onDataAvailable(this, list); + list = new ByteBufferList(); + } + } + if (!mChannel.isChunked()) { +// Util.emitAllData(this, list); + int remaining; + while (mDataHandler != null && (remaining = list.remaining()) > 0) { + DataCallback handler = mDataHandler; + mDataHandler.onDataAvailable(this, list); + if (remaining == list.remaining() && handler == mDataHandler) { + Assert.fail("mDataHandler failed to consume data, yet remains the mDataHandler."); + break; + } + } + Assert.assertEquals(list.remaining(), 0); + } + + if (closed) + reportClose(); + } + catch (Exception e) { + close(); + report(e); + reportClose(); + } + + return total; + } + + private void reportClose() { + if (mClosedHander != null) + mClosedHander.onClosed(); + } + + @Override + public void close() { + mKey.cancel(); + try { + mChannel.close(); + } + catch (IOException e) { + } + } + + WritableCallback mWriteableHandler; + @Override + public void setWriteableCallback(WritableCallback handler) { + mWriteableHandler = handler; + } + + DataCallback mDataHandler; + @Override + public void setDataCallback(DataCallback callback) { + mDataHandler = callback; + } + + @Override + public DataCallback getDataCallback() { + return mDataHandler; + } + + ClosedCallback mClosedHander; + @Override + public void setClosedCallback(ClosedCallback handler) { + mClosedHander = handler; + } + + @Override + public ClosedCallback getCloseHandler() { + return mClosedHander; + } + + @Override + public WritableCallback getWriteableCallback() { + return mWriteableHandler; + } + + void report(Exception e) { + if (mExceptionCallback != null) + mExceptionCallback.onException(e); + else + e.printStackTrace(); + } + + private ExceptionCallback mExceptionCallback; + @Override + public void setExceptionCallback(ExceptionCallback callback) { + mExceptionCallback = callback; + } + + @Override + public ExceptionCallback getExceptionCallback() { + return mExceptionCallback; + } +} diff --git a/src/com/koushikdutta/async/BufferedDataEmitter.java b/src/com/koushikdutta/async/BufferedDataEmitter.java new file mode 100644 index 0000000..50e1289 --- /dev/null +++ b/src/com/koushikdutta/async/BufferedDataEmitter.java @@ -0,0 +1,40 @@ +package com.koushikdutta.async; + +import com.koushikdutta.async.callback.DataCallback; + +public class BufferedDataEmitter implements DataEmitter, DataCallback { + public BufferedDataEmitter() { + } + + public void onDataAvailable() { + if (mDataCallback != null) + mDataCallback.onDataAvailable(this, mBuffers); + } + + ByteBufferList mBuffers = new ByteBufferList(); + + DataCallback mDataCallback; + @Override + public void setDataCallback(DataCallback callback) { + mDataCallback = callback; + } + + @Override + public DataCallback getDataCallback() { + return mDataCallback; + } + + @Override + public boolean isChunked() { + return false; + } + + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + mBuffers.add(bb); + bb.clear(); + + onDataAvailable(); + } + +} diff --git a/src/com/koushikdutta/async/BufferedDataSink.java b/src/com/koushikdutta/async/BufferedDataSink.java new file mode 100644 index 0000000..4b176b7 --- /dev/null +++ b/src/com/koushikdutta/async/BufferedDataSink.java @@ -0,0 +1,56 @@ +package com.koushikdutta.async; + +import java.nio.ByteBuffer; + +import junit.framework.Assert; + +import com.koushikdutta.async.callback.WritableCallback; + +public class BufferedDataSink implements DataSink { + DataSink mDataSink; + public BufferedDataSink(DataSink datasink) { + mDataSink = datasink; + mDataSink.setWriteableCallback(new WritableCallback() { + @Override + public void onWriteable() { + writePending(); + } + }); + } + + public DataSink getDataSink() { + return mDataSink; + } + + private void writePending() { + mDataSink.write(mPendingWrites); + } + + ByteBufferList mPendingWrites = new ByteBufferList(); + + @Override + public void write(ByteBuffer bb) { + mPendingWrites.add(ByteBuffer.wrap(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining())); + bb.position(0); + bb.limit(0); + writePending(); + } + + @Override + public void write(ByteBufferList bb) { + mPendingWrites.add(bb); + bb.clear(); + writePending(); + } + + @Override + public void setWriteableCallback(WritableCallback handler) { + Assert.fail("BufferingDataSink is always writeable."); + } + + @Override + public WritableCallback getWriteableCallback() { + Assert.fail("BufferingDataSink is always writeable."); + return null; + } +} diff --git a/src/com/koushikdutta/async/ByteBufferList.java b/src/com/koushikdutta/async/ByteBufferList.java new file mode 100644 index 0000000..6995b61 --- /dev/null +++ b/src/com/koushikdutta/async/ByteBufferList.java @@ -0,0 +1,171 @@ +package com.koushikdutta.async; + +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.LinkedList; + +import junit.framework.Assert; + +public class ByteBufferList implements Iterable<ByteBuffer> { + LinkedList<ByteBuffer> mBuffers = new LinkedList<ByteBuffer>(); + + public ByteBuffer peek() { + return mBuffers.peek(); + } + + public ByteBufferList() { + } + + public ByteBuffer[] toArray() { + ByteBuffer[] ret = new ByteBuffer[mBuffers.size()]; + ret = mBuffers.toArray(ret); + return ret; + } + + public int remaining() { + int ret = 0; + for (ByteBuffer bb: mBuffers) { + ret += bb.remaining(); + } + return ret; + } + + public int getInt() { + return read(4).getInt(); + } + + public char getByteChar() { + return (char)read(1).get(); + } + + public int getShort() { + return read(2).getShort(); + } + + public byte get() { + return read(1).get(); + } + + public long getLong() { + return read(8).getLong(); + } + + public void get(byte[] bytes) { + read(bytes.length).get(bytes); + } + + public ByteBufferList get(int length) { + Assert.assertTrue(remaining() >= length); + ByteBufferList ret = new ByteBufferList(); + int offset = 0; + for (ByteBuffer b: mBuffers) { + int remaining = b.remaining(); + + if (remaining == 0) + continue; + // done + if (offset > length) + break; + + if (offset + remaining > length) { + int need = length - offset; + // this is shared between both + ret.add(ByteBuffer.wrap(b.array(), b.arrayOffset() + b.position(), need)); + b.position(b.position() + need); + } + else { + // this belongs to the new list + ret.add(ByteBuffer.wrap(b.array(), b.arrayOffset() + b.position(), remaining)); + b.position(b.limit()); + } + + offset += remaining; + } + + return ret; + } + + public ByteBuffer read(int count) { + Assert.assertTrue(count <= remaining()); + + ByteBuffer first = mBuffers.peek(); + while (first.position() == first.limit()) { + mBuffers.remove(); + first = mBuffers.peek(); + } + + if (first.remaining() >= count) { + return first; + } + else { + // reallocate the count into a single buffer, and return it + byte[] bytes = new byte[count]; + int offset = 0; + ByteBuffer bb = null; + while (offset < count) { + bb = mBuffers.remove(); + int toRead = Math.min(count - offset, bb.remaining()); + bb.get(bytes, offset, toRead); + offset += toRead; + } + Assert.assertNotNull(bb); + // if there was still data left in the last buffer we popped + // toss it back into the head + if (bb.position() < bb.limit()) + mBuffers.add(0, bb); + ByteBuffer ret = ByteBuffer.wrap(bytes); + mBuffers.add(0, ret); + return ret; + } + } + + public void trim() { + // this clears out buffers that are empty in the beginning of the list + if (size() > 0) + read(0); + } + + public void add(ByteBuffer b) { + if (b.remaining() <= 0) + return; + mBuffers.add(b); + trim(); + } + + public void add(ByteBufferList b) { + if (b.remaining() <= 0) + return; + mBuffers.addAll(b.mBuffers); + trim(); + } + + public void clear() { + mBuffers.clear(); + } + + public ByteBuffer remove() { + return mBuffers.remove(); + } + + public int size() { + return mBuffers.size(); + } + + @Override + public Iterator<ByteBuffer> iterator() { + return mBuffers.iterator(); + } + + public void spewString() { + for (ByteBuffer bb: mBuffers) { + try { + String s = new String(bb.array(), bb.arrayOffset() + bb.position(), bb.limit()); + System.out.println(s); + } + catch (Exception e) { + e.printStackTrace(); + + } + } + } +} diff --git a/src/com/koushikdutta/async/ChannelWrapper.java b/src/com/koushikdutta/async/ChannelWrapper.java new file mode 100644 index 0000000..68b9ed2 --- /dev/null +++ b/src/com/koushikdutta/async/ChannelWrapper.java @@ -0,0 +1,43 @@ +package com.koushikdutta.async; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.spi.AbstractSelectableChannel; + +abstract class ChannelWrapper implements ReadableByteChannel { + private AbstractSelectableChannel mChannel; + ChannelWrapper(AbstractSelectableChannel channel) throws IOException { + channel.configureBlocking(false); + mChannel = channel; + } + + public abstract boolean isConnected(); + + public abstract int write(ByteBuffer src) throws IOException; + public abstract int write(ByteBuffer[] src) throws IOException; + + // register for default events appropriate for this channel + public abstract SelectionKey register(Selector sel) throws ClosedChannelException; + + public SelectionKey register(Selector sel, int ops) throws ClosedChannelException { + return mChannel.register(sel, ops); + } + + public boolean isChunked() { + return false; + } + + @Override + public boolean isOpen() { + return mChannel.isOpen(); + } + + @Override + public void close() throws IOException { + mChannel.close(); + } +} diff --git a/src/com/koushikdutta/async/CloseableData.java b/src/com/koushikdutta/async/CloseableData.java new file mode 100644 index 0000000..56563c5 --- /dev/null +++ b/src/com/koushikdutta/async/CloseableData.java @@ -0,0 +1,9 @@ +package com.koushikdutta.async; + +import com.koushikdutta.async.callback.ClosedCallback; + +public interface CloseableData { + public void close(); + public void setClosedCallback(ClosedCallback handler); + public ClosedCallback getCloseHandler(); +} diff --git a/src/com/koushikdutta/async/DataEmitter.java b/src/com/koushikdutta/async/DataEmitter.java new file mode 100644 index 0000000..c868be6 --- /dev/null +++ b/src/com/koushikdutta/async/DataEmitter.java @@ -0,0 +1,9 @@ +package com.koushikdutta.async; + +import com.koushikdutta.async.callback.DataCallback; + +public interface DataEmitter { + public void setDataCallback(DataCallback callback); + public DataCallback getDataCallback(); + public boolean isChunked(); +} diff --git a/src/com/koushikdutta/async/DataEmitterStream.java b/src/com/koushikdutta/async/DataEmitterStream.java new file mode 100644 index 0000000..37bc496 --- /dev/null +++ b/src/com/koushikdutta/async/DataEmitterStream.java @@ -0,0 +1,49 @@ +package com.koushikdutta.async; + +import junit.framework.Assert; + +import com.koushikdutta.async.callback.DataCallback; + +public class DataEmitterStream implements AsyncInputStream { + DataCallback mPendingRead; + int mPendingReadLength; + ByteBufferList mPendingData = new ByteBufferList(); + @Override + public void read(int count, DataCallback callback) { + Assert.assertNull(mPendingRead); + mPendingReadLength = count; + mPendingRead = callback; + mPendingData = new ByteBufferList(); + mEmitter.setDataCallback(mMyHandler); + } + + DataCallback mMyHandler = new DataCallback() { + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + // if we're registered for data, we must be waiting for a read + Assert.assertNotNull(mPendingRead); + do { + int need = Math.min(bb.remaining(), mPendingReadLength - mPendingData.remaining()); + mPendingData.add(bb.get(need)); + } + while (handlePendingData() && mPendingRead != null); + } + }; + + private boolean handlePendingData() { + if (mPendingReadLength > mPendingData.remaining()) + return false; + + DataCallback pendingRead = mPendingRead; + mPendingRead = null; + pendingRead.onDataAvailable(mEmitter, mPendingData); + + return true; + } + + DataEmitter mEmitter; + public DataEmitterStream(DataEmitter emitter) { + Assert.assertFalse(emitter.isChunked()); + mEmitter = emitter; + } +} diff --git a/src/com/koushikdutta/async/DataExchange.java b/src/com/koushikdutta/async/DataExchange.java new file mode 100644 index 0000000..3de062d --- /dev/null +++ b/src/com/koushikdutta/async/DataExchange.java @@ -0,0 +1,5 @@ +package com.koushikdutta.async; + +public interface DataExchange extends DataEmitter, DataSink { + +} diff --git a/src/com/koushikdutta/async/DataSink.java b/src/com/koushikdutta/async/DataSink.java new file mode 100644 index 0000000..fbd2ca6 --- /dev/null +++ b/src/com/koushikdutta/async/DataSink.java @@ -0,0 +1,12 @@ +package com.koushikdutta.async; + +import java.nio.ByteBuffer; + +import com.koushikdutta.async.callback.WritableCallback; + +public interface DataSink { + public void write(ByteBuffer bb); + public void write(ByteBufferList bb); + public void setWriteableCallback(WritableCallback handler); + public WritableCallback getWriteableCallback(); +} diff --git a/src/com/koushikdutta/async/DataTransformer.java b/src/com/koushikdutta/async/DataTransformer.java new file mode 100644 index 0000000..d33e822 --- /dev/null +++ b/src/com/koushikdutta/async/DataTransformer.java @@ -0,0 +1,6 @@ +package com.koushikdutta.async; + +import com.koushikdutta.async.callback.DataCallback; + +public interface DataTransformer extends DataEmitter, DataCallback, ExceptionCallback { +} diff --git a/src/com/koushikdutta/async/DataTransformerBase.java b/src/com/koushikdutta/async/DataTransformerBase.java new file mode 100644 index 0000000..0469ba4 --- /dev/null +++ b/src/com/koushikdutta/async/DataTransformerBase.java @@ -0,0 +1,37 @@ +package com.koushikdutta.async; + +import junit.framework.Assert; + +import com.koushikdutta.async.callback.DataCallback; + +public class DataTransformerBase implements DataTransformer { + public DataTransformerBase() { + } + + private DataCallback mDataCallback; + @Override + public void setDataCallback(DataCallback callback) { + mDataCallback = callback; + } + + @Override + public DataCallback getDataCallback() { + return mDataCallback; + } + + @Override + public boolean isChunked() { + return false; + } + + @Override + public void onException(Exception error) { + error.printStackTrace(); + } + + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + Assert.assertNotNull(mDataCallback); + Util.emitAllData(this, bb); + } +} diff --git a/src/com/koushikdutta/async/DatagramChannelWrapper.java b/src/com/koushikdutta/async/DatagramChannelWrapper.java new file mode 100644 index 0000000..4bc5f6b --- /dev/null +++ b/src/com/koushikdutta/async/DatagramChannelWrapper.java @@ -0,0 +1,45 @@ +package com.koushikdutta.async; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; + +class DatagramChannelWrapper extends ChannelWrapper { + DatagramChannel mChannel; + + DatagramChannelWrapper(DatagramChannel channel) throws IOException { + super(channel); + mChannel = channel; + } + @Override + public int read(ByteBuffer buffer) throws IOException { + return mChannel.read(buffer); + } + @Override + public boolean isConnected() { + return mChannel.isConnected(); + } + @Override + public int write(ByteBuffer src) throws IOException { + return mChannel.write(src); + } + @Override + public int write(ByteBuffer[] src) throws IOException { + return (int)mChannel.write(src); + } + @Override + public SelectionKey register(Selector sel, int ops) throws ClosedChannelException { + return mChannel.register(sel, ops); + } + @Override + public boolean isChunked() { + return true; + } + @Override + public SelectionKey register(Selector sel) throws ClosedChannelException { + return register(sel, SelectionKey.OP_READ); + } +} diff --git a/src/com/koushikdutta/async/ExceptionCallback.java b/src/com/koushikdutta/async/ExceptionCallback.java new file mode 100644 index 0000000..f33dcac --- /dev/null +++ b/src/com/koushikdutta/async/ExceptionCallback.java @@ -0,0 +1,5 @@ +package com.koushikdutta.async; + +public interface ExceptionCallback { + public void onException(Exception error); +} diff --git a/src/com/koushikdutta/async/ExceptionEmitter.java b/src/com/koushikdutta/async/ExceptionEmitter.java new file mode 100644 index 0000000..21d5e2f --- /dev/null +++ b/src/com/koushikdutta/async/ExceptionEmitter.java @@ -0,0 +1,6 @@ +package com.koushikdutta.async; + +public interface ExceptionEmitter { + public void setExceptionCallback(ExceptionCallback callback); + public ExceptionCallback getExceptionCallback(); +} diff --git a/src/com/koushikdutta/async/LineEmitter.java b/src/com/koushikdutta/async/LineEmitter.java new file mode 100644 index 0000000..9694d5b --- /dev/null +++ b/src/com/koushikdutta/async/LineEmitter.java @@ -0,0 +1,43 @@ +package com.koushikdutta.async; + +import junit.framework.Assert; + +import com.koushikdutta.async.callback.DataCallback; + +public class LineEmitter { + static public interface StringCallback { + public void onStringAvailable(String s); + } + + StringBuilder data = new StringBuilder(); + + public LineEmitter(final DataEmitter emitter) { + emitter.setDataCallback(new DataCallback() { + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + while (bb.remaining() > 0) { + byte b = bb.get(); + if (b == '\n') { + Assert.assertNotNull(mLineCallback); + mLineCallback.onStringAvailable(data.toString()); + if (emitter.getDataCallback() != this) + return; + data = new StringBuilder(); + } + else { + data.append((char)b); + } + } + } + }); + } + + StringCallback mLineCallback; + public void setLineCallback(StringCallback callback) { + mLineCallback = callback; + } + + public StringCallback getLineCallback() { + return mLineCallback; + } +} diff --git a/src/com/koushikdutta/async/NullDataCallback.java b/src/com/koushikdutta/async/NullDataCallback.java new file mode 100644 index 0000000..3911286 --- /dev/null +++ b/src/com/koushikdutta/async/NullDataCallback.java @@ -0,0 +1,10 @@ +package com.koushikdutta.async; + +import com.koushikdutta.async.callback.DataCallback; + +public class NullDataCallback implements DataCallback { + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + bb.clear(); + } +} diff --git a/src/com/koushikdutta/async/PushParser.java b/src/com/koushikdutta/async/PushParser.java new file mode 100644 index 0000000..60e9639 --- /dev/null +++ b/src/com/koushikdutta/async/PushParser.java @@ -0,0 +1,217 @@ +package com.koushikdutta.async; + +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedList; + +import junit.framework.Assert; + +import com.koushikdutta.async.callback.DataCallback; + +public class PushParser { + private LinkedList<Object> mWaiting = new LinkedList<Object>(); + + static class BufferWaiter { + int length; + } + + static class UntilWaiter { + byte value; + DataCallback callback; + } + + int mNeeded = 0; + public PushParser readInt() { + mNeeded += 4; + mWaiting.add(int.class); + return this; + } + + public PushParser readByte() { + mNeeded += 1; + mWaiting.add(byte.class); + return this; + } + + public PushParser readShort() { + mNeeded += 2; + mWaiting.add(short.class); + return this; + } + + public PushParser readLong() { + mNeeded += 8; + mWaiting.add(long.class); + return this; + } + + public PushParser readBuffer(int length) { + if (length != -1) + mNeeded += length; + BufferWaiter bw = new BufferWaiter(); + bw.length = length; + mWaiting.add(bw); + return this; + } + + public PushParser readLenBuffer() { + readInt(); + BufferWaiter bw = new BufferWaiter(); + bw.length = -1; + mWaiting.add(bw); + return this; + } + + public PushParser until(byte b, DataCallback callback) { + UntilWaiter waiter = new UntilWaiter(); + waiter.value = b; + waiter.callback = callback; + mWaiting.add(b); + return this; + } + + public PushParser noop() { + mWaiting.add(Object.class); + return this; + } + + AsyncInputStream mReader; + DataEmitter mEmitter; + public PushParser(DataEmitter s) { + mEmitter = s; + mReader = new DataEmitterStream(s); + } + + private ArrayList<Object> mArgs = new ArrayList<Object>(); + private TapCallback mCallback; + + Exception stack() { + try { + throw new Exception(); + } + catch (Exception e) { + return e; + } + } + + public void tap(TapCallback callback) { + Assert.assertNull(mCallback); + Assert.assertTrue(mWaiting.size() > 0); + + mCallback = callback; + + new DataCallback() { + { + onDataAvailable(mEmitter, null); + } + + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + try { + while (mWaiting.size() > 0) { + Object waiting = mWaiting.peek(); + if (waiting == null) + break; +// System.out.println("Remaining: " + bb.remaining()); + if (waiting == int.class) { + mArgs.add(bb.getInt()); + mNeeded -= 4; + } + else if (waiting == short.class) { + mArgs.add(bb.getShort()); + mNeeded -= 2; + } + else if (waiting == byte.class) { + mArgs.add(bb.get()); + mNeeded -= 1; + } + else if (waiting == long.class) { + mArgs.add(bb.getLong()); + mNeeded -= 8; + } + else if (waiting == Object.class) { + mArgs.add(null); + } + else if (waiting instanceof UntilWaiter) { + UntilWaiter uw = (UntilWaiter)waiting; + boolean found = false; + ByteBufferList cb = new ByteBufferList(); + ByteBuffer lastBuffer = null; + do { + if (lastBuffer != bb.peek()) { + lastBuffer.mark(); + if (lastBuffer != null) { + lastBuffer.reset(); + cb.add(lastBuffer); + } + lastBuffer = bb.peek(); + } + } + while (bb.remaining() > 0 && (found = (bb.get() != uw.value))); + + int mark = lastBuffer.position(); + lastBuffer.reset(); + ByteBuffer add = ByteBuffer.wrap(lastBuffer.array(), lastBuffer.arrayOffset() + lastBuffer.position(), mark - lastBuffer.position()); + cb.add(add); + lastBuffer.position(mark); + + if (!found) { + if (uw.callback != null) + uw.callback.onDataAvailable(emitter, cb); + throw new Exception(); + } + } + else if (waiting instanceof BufferWaiter) { + BufferWaiter bw = (BufferWaiter)waiting; + int length = bw.length; + if (length == -1) { + length = (Integer)mArgs.get(mArgs.size() - 1); + mArgs.remove(mArgs.size() - 1); + bw.length = length; + mNeeded += length; + } + if (bb.remaining() < length) { +// System.out.print("imminient feilure detected"); + throw new Exception(); + } + +// e.printStackTrace(); +// System.out.println("Buffer length: " + length); + byte[] bytes = null; + if (length > 0) { + bytes = new byte[length]; + bb.get(bytes); + } + mNeeded -= length; + mArgs.add(bytes); + } + else { + Assert.fail(); + } +// System.out.println("Parsed: " + mArgs.get(0)); + mWaiting.remove(); + } + } + catch (Exception ex) { + Assert.assertTrue(mNeeded != 0); +// ex.printStackTrace(); + mReader.read(mNeeded, this); + return; + } + + try { + Object[] args = mArgs.toArray(); + mArgs.clear(); + TapCallback callback = mCallback; + mCallback = null; + Method method = callback.getTap(); + method.invoke(callback, args); + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + }; + } +} diff --git a/src/com/koushikdutta/async/SSLDataExchange.java b/src/com/koushikdutta/async/SSLDataExchange.java new file mode 100644 index 0000000..93f48d6 --- /dev/null +++ b/src/com/koushikdutta/async/SSLDataExchange.java @@ -0,0 +1,289 @@ +package com.koushikdutta.async; + +import java.nio.ByteBuffer; +import java.security.KeyStore; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLEngineResult.Status; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import junit.framework.Assert; + +import org.apache.http.conn.ssl.StrictHostnameVerifier; + +import com.koushikdutta.async.callback.DataCallback; +import com.koushikdutta.async.callback.WritableCallback; + +public class SSLDataExchange implements DataTransformer, DataExchange, ExceptionEmitter { + DataExchange mExchange; + BufferedDataEmitter mEmitter = new BufferedDataEmitter(); + BufferedDataSink mSink; + ByteBuffer mReadTmp = ByteBuffer.allocate(8192); + boolean mUnwrapping = false; + public SSLDataExchange(DataExchange exchange) { + mExchange = exchange; + + engine.setUseClientMode(true); + mSink = new BufferedDataSink(exchange); + + // SSL needs buffering of data written during handshake. + // aka exhcange.setDatacallback +// mEmitter.buffer(exchange); + + mEmitter.setDataCallback(new DataCallback() { + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + try { + if (mUnwrapping) + return; + mUnwrapping = true; + + ByteBufferList out = new ByteBufferList(); + + mReadTmp.position(0); + mReadTmp.limit(mReadTmp.capacity()); + ByteBuffer b; + if (bb.size() > 1) + b = bb.read(bb.remaining()); + else if (bb.size() == 1) + b = bb.peek(); + else { + b = ByteBuffer.allocate(0); + } + + while (true) { + int remaining = b.remaining(); + + SSLEngineResult res = engine.unwrap(b, mReadTmp); + if (res.getStatus() == Status.BUFFER_OVERFLOW) { + addToPending(out); + mReadTmp = ByteBuffer.allocate(mReadTmp.remaining() * 2); + remaining = -1; + } + handleResult(res); + if (b.remaining() == remaining) + break; + } + + addToPending(out); + Util.emitAllData(SSLDataExchange.this, out); + } + catch (Exception ex) { + report(ex); + } + finally { + mUnwrapping = false; + } + } + }); + } + + void addToPending(ByteBufferList out) { + if (mReadTmp.position() > 0) { + mReadTmp.limit(mReadTmp.position()); + mReadTmp.position(0); + out.add(mReadTmp); + mReadTmp = ByteBuffer.allocate(mReadTmp.capacity()); + } + } + + static { + try { + ctx = SSLContext.getInstance("Default"); + } + catch (Exception ex) { + } + } + static SSLContext ctx; + + SSLEngine engine = ctx.createSSLEngine(); + boolean finishedHandshake = false; + + DataCallback mDataCallback; + @Override + public void setDataCallback(DataCallback callback) { + mDataCallback = callback; + } + @Override + public DataCallback getDataCallback() { + return mDataCallback; + } + @Override + public boolean isChunked() { + return false; + } + + private String mHost; + public String getHost() { + return mHost; + } + public void setHost(String host) { + mHost = host; + } + + private int mPort; + public int getPort() { + return mPort; + } + public void setPort(int port) { + mPort = port; + } + + private void handleResult(SSLEngineResult res) { + if (res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { + final Runnable task = engine.getDelegatedTask(); + task.run(); + } + + if (res.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) { + write(ByteBuffer.allocate(0)); + } + + if (res.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP) { + mEmitter.onDataAvailable(); + } + + try { + if (!finishedHandshake && (engine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING || engine.getHandshakeStatus() == HandshakeStatus.FINISHED)) { + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + boolean trusted = false; + for (TrustManager tm : tmf.getTrustManagers()) { + try { + X509TrustManager xtm = (X509TrustManager) tm; + X509Certificate[] certs = (X509Certificate[]) engine.getSession().getPeerCertificates(); + xtm.checkServerTrusted(certs, "SSL"); + if (mHost != null) { + StrictHostnameVerifier verifier = new StrictHostnameVerifier(); + verifier.verify(mHost, StrictHostnameVerifier.getCNs(certs[0]), StrictHostnameVerifier.getDNSSubjectAlts(certs[0])); + } + trusted = true; + break; + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + finishedHandshake = true; + if (!trusted) + throw new SSLPeerUnverifiedException("Not trusted by any of the system trust managers."); + Assert.assertNotNull(mWriteableCallback); + mWriteableCallback.onWriteable(); + mEmitter.onDataAvailable(); + } + } + catch (Exception ex) { + report(ex); + } + } + + private void report(Exception e) { + if (mErrorCallback != null) + mErrorCallback.onException(e); + } + + private void writeTmp() { + mWriteTmp.limit(mWriteTmp.position()); + mWriteTmp.position(0); + if (mWriteTmp.remaining() > 0) + mSink.write(mWriteTmp); + } + + boolean checkWrapResult(SSLEngineResult res) { + if (res.getStatus() == Status.BUFFER_OVERFLOW) { + mWriteTmp = ByteBuffer.allocate(mWriteTmp.remaining() * 2); + return false; + } + return true; + } + + private boolean mWrapping = false; + ByteBuffer mWriteTmp = ByteBuffer.allocate(8192); + @Override + public void write(ByteBuffer bb) { + mWrapping = true; + int remaining; + SSLEngineResult res = null; + do { + remaining = bb.remaining(); + mWriteTmp.position(0); + mWriteTmp.limit(mWriteTmp.capacity()); + try { + res = engine.wrap(bb, mWriteTmp); + if (!checkWrapResult(res)) + remaining = -1; + writeTmp(); + handleResult(res); + } + catch (SSLException e) { + report(e); + } + } + while (remaining != bb.remaining() || (res != null && res.getHandshakeStatus() == HandshakeStatus.NEED_WRAP)); + mWrapping = false; + } + + @Override + public void write(ByteBufferList bb) { + if (mWrapping) + return; + mWrapping = true; + int remaining; + SSLEngineResult res = null; + do { + remaining = bb.remaining(); + mWriteTmp.position(0); + mWriteTmp.limit(mWriteTmp.capacity()); + try { + res = engine.wrap(bb.toArray(), mWriteTmp); + if (!checkWrapResult(res)) + remaining = -1; + writeTmp(); + handleResult(res); + } + catch (SSLException e) { + report(e); + } + } + while (remaining != bb.remaining() || (res != null && res.getHandshakeStatus() == HandshakeStatus.NEED_WRAP)); + mWrapping = false; + } + + WritableCallback mWriteableCallback; + @Override + public void setWriteableCallback(WritableCallback handler) { + mWriteableCallback = handler; + } + + private ExceptionCallback mErrorCallback; + @Override + public void setExceptionCallback(ExceptionCallback callback) { + mErrorCallback = callback; + } + @Override + public ExceptionCallback getExceptionCallback() { + return mErrorCallback; + } + @Override + public WritableCallback getWriteableCallback() { + return mWriteableCallback; + } + + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + mEmitter.onDataAvailable(emitter, bb); + } + + @Override + public void onException(Exception error) { + report(error); + } +} diff --git a/src/com/koushikdutta/async/ServerSocketChannelWrapper.java b/src/com/koushikdutta/async/ServerSocketChannelWrapper.java new file mode 100644 index 0000000..ee31298 --- /dev/null +++ b/src/com/koushikdutta/async/ServerSocketChannelWrapper.java @@ -0,0 +1,52 @@ +package com.koushikdutta.async; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; + +import junit.framework.Assert; + +class ServerSocketChannelWrapper extends ChannelWrapper { + ServerSocketChannel mChannel; + + ServerSocketChannelWrapper(ServerSocketChannel channel) throws IOException { + super(channel); + mChannel = channel; + } + + @Override + public int read(ByteBuffer buffer) throws IOException { + final String msg = "Can't read ServerSocketChannel"; + Assert.fail(msg); + throw new IOException(msg); + } + + @Override + public boolean isConnected() { + Assert.fail("ServerSocketChannel is never connected"); + return false; + } + + @Override + public int write(ByteBuffer src) throws IOException { + final String msg = "Can't write ServerSocketChannel"; + Assert.fail(msg); + throw new IOException(msg); + + } + + @Override + public SelectionKey register(Selector sel) throws ClosedChannelException { + return mChannel.register(sel, SelectionKey.OP_ACCEPT); + } + + @Override + public int write(ByteBuffer[] src) throws IOException { + final String msg = "Can't write ServerSocketChannel"; + Assert.fail(msg); + throw new IOException(msg); + } +} diff --git a/src/com/koushikdutta/async/SocketChannelWrapper.java b/src/com/koushikdutta/async/SocketChannelWrapper.java new file mode 100644 index 0000000..47feef6 --- /dev/null +++ b/src/com/koushikdutta/async/SocketChannelWrapper.java @@ -0,0 +1,37 @@ +package com.koushikdutta.async; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; + +class SocketChannelWrapper extends ChannelWrapper { + SocketChannel mChannel; + + SocketChannelWrapper(SocketChannel channel) throws IOException { + super(channel); + mChannel = channel; + } + @Override + public int read(ByteBuffer buffer) throws IOException { + return mChannel.read(buffer); + } + @Override + public boolean isConnected() { + return mChannel.isConnected(); + } + @Override + public int write(ByteBuffer src) throws IOException { + return mChannel.write(src); + } + @Override + public int write(ByteBuffer[] src) throws IOException { + return (int)mChannel.write(src); + } + @Override + public SelectionKey register(Selector sel) throws ClosedChannelException { + return register(sel, SelectionKey.OP_CONNECT); + } +} diff --git a/src/com/koushikdutta/async/TapCallback.java b/src/com/koushikdutta/async/TapCallback.java new file mode 100644 index 0000000..a70b689 --- /dev/null +++ b/src/com/koushikdutta/async/TapCallback.java @@ -0,0 +1,21 @@ +package com.koushikdutta.async; + +import java.lang.reflect.Method; +import java.util.Hashtable; + +public class TapCallback { + static Hashtable<Class, Method> mTable = new Hashtable<Class, Method>(); + + Method getTap() { + Method found = mTable.get(getClass()); + if (found != null) + return found; + for (Method method : getClass().getMethods()) { + if ("tap".equals(method.getName())) { + mTable.put(getClass(), method); + return method; + } + } + return null; + } +} diff --git a/src/com/koushikdutta/async/Util.java b/src/com/koushikdutta/async/Util.java new file mode 100644 index 0000000..5a7c09d --- /dev/null +++ b/src/com/koushikdutta/async/Util.java @@ -0,0 +1,20 @@ +package com.koushikdutta.async; + +import junit.framework.Assert; + +import com.koushikdutta.async.callback.DataCallback; + +public class Util { + public static void emitAllData(DataEmitter emitter, ByteBufferList list) { + int remaining; + while (emitter.getDataCallback() != null && (remaining = list.remaining()) > 0) { + DataCallback handler = emitter.getDataCallback(); + handler.onDataAvailable(emitter, list); + if (remaining == list.remaining() && handler == emitter.getDataCallback()) { + Assert.fail("mDataHandler failed to consume data, yet remains the mDataHandler."); + break; + } + } + Assert.assertEquals(list.remaining(), 0); + } +} diff --git a/src/com/koushikdutta/async/callback/ClosedCallback.java b/src/com/koushikdutta/async/callback/ClosedCallback.java new file mode 100644 index 0000000..9ae034e --- /dev/null +++ b/src/com/koushikdutta/async/callback/ClosedCallback.java @@ -0,0 +1,5 @@ +package com.koushikdutta.async.callback; + +public interface ClosedCallback { + public void onClosed(); +} diff --git a/src/com/koushikdutta/async/callback/CompletedCallback.java b/src/com/koushikdutta/async/callback/CompletedCallback.java new file mode 100644 index 0000000..d6c0342 --- /dev/null +++ b/src/com/koushikdutta/async/callback/CompletedCallback.java @@ -0,0 +1,5 @@ +package com.koushikdutta.async.callback; + +public interface CompletedCallback { + public void onCompleted(Exception ex); +} diff --git a/src/com/koushikdutta/async/callback/ConnectCallback.java b/src/com/koushikdutta/async/callback/ConnectCallback.java new file mode 100644 index 0000000..fdaa838 --- /dev/null +++ b/src/com/koushikdutta/async/callback/ConnectCallback.java @@ -0,0 +1,7 @@ +package com.koushikdutta.async.callback; + +import com.koushikdutta.async.AsyncSocket; + +public interface ConnectCallback { + public void onConnectCompleted(Exception ex, AsyncSocket socket); +} diff --git a/src/com/koushikdutta/async/callback/DataCallback.java b/src/com/koushikdutta/async/callback/DataCallback.java new file mode 100644 index 0000000..564e48b --- /dev/null +++ b/src/com/koushikdutta/async/callback/DataCallback.java @@ -0,0 +1,9 @@ +package com.koushikdutta.async.callback; + +import com.koushikdutta.async.ByteBufferList; +import com.koushikdutta.async.DataEmitter; + + +public interface DataCallback { + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb); +} diff --git a/src/com/koushikdutta/async/callback/ListenCallback.java b/src/com/koushikdutta/async/callback/ListenCallback.java new file mode 100644 index 0000000..f3b4967 --- /dev/null +++ b/src/com/koushikdutta/async/callback/ListenCallback.java @@ -0,0 +1,8 @@ +package com.koushikdutta.async.callback; + +import com.koushikdutta.async.AsyncSocket; + + +public interface ListenCallback { + public void onAccepted(AsyncSocket handler); +} diff --git a/src/com/koushikdutta/async/callback/ResultCallback.java b/src/com/koushikdutta/async/callback/ResultCallback.java new file mode 100644 index 0000000..6d4ce7b --- /dev/null +++ b/src/com/koushikdutta/async/callback/ResultCallback.java @@ -0,0 +1,5 @@ +package com.koushikdutta.async.callback; + +public interface ResultCallback<T> { + public void onCompleted(Exception e, T result); +} diff --git a/src/com/koushikdutta/async/callback/WritableCallback.java b/src/com/koushikdutta/async/callback/WritableCallback.java new file mode 100644 index 0000000..edc4baf --- /dev/null +++ b/src/com/koushikdutta/async/callback/WritableCallback.java @@ -0,0 +1,5 @@ +package com.koushikdutta.async.callback; + +public interface WritableCallback { + public void onWriteable(); +} diff --git a/src/com/koushikdutta/async/http/AsyncHttpClient.java b/src/com/koushikdutta/async/http/AsyncHttpClient.java new file mode 100644 index 0000000..36e91c4 --- /dev/null +++ b/src/com/koushikdutta/async/http/AsyncHttpClient.java @@ -0,0 +1,147 @@ +package com.koushikdutta.async.http; + +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; + +import com.koushikdutta.async.AsyncServer; +import com.koushikdutta.async.AsyncSocket; +import com.koushikdutta.async.ByteBufferList; +import com.koushikdutta.async.DataEmitter; +import com.koushikdutta.async.NullDataCallback; +import com.koushikdutta.async.callback.CompletedCallback; +import com.koushikdutta.async.callback.ConnectCallback; +import com.koushikdutta.async.callback.DataCallback; +import com.koushikdutta.async.callback.ResultCallback; +import com.koushikdutta.async.http.callback.HttpConnectCallback; +import com.koushikdutta.async.http.libcore.RawHeaders; + +public class AsyncHttpClient { + public static void connect(final AsyncHttpRequest request, final HttpConnectCallback callback) { + connect(AsyncServer.getDefault(), request, callback); + } + public static void connect(final AsyncServer server, final AsyncHttpRequest request, final HttpConnectCallback callback) { + connect(server, request, callback, 0); + } + + public static void connect(final AsyncServer server, final AsyncHttpRequest request, final HttpConnectCallback callback, int redirectCount) { + if (redirectCount > 5) { + callback.onConnectCompleted(new Exception("too many redirects"), null); + return; + } + final URI uri = request.getUri(); + int port = uri.getPort(); + if (port == -1) { + if (uri.getScheme().equals("http")) + port = 80; + else if (uri.getScheme().equals("https")) + port = 443; + else { + callback.onConnectCompleted(new Exception("invalid uri scheme"), null); + return; + } + } + server.connectSocket(uri.getHost(), port, new ConnectCallback() { + @Override + public void onConnectCompleted(Exception ex, AsyncSocket socket) { + if (ex != null) { + callback.onConnectCompleted(ex, null); + return; + } + final AsyncHttpResponseImpl ret = new AsyncHttpResponseImpl(request) { + protected void onHeadersReceived() { + super.onHeadersReceived(); + + try { + RawHeaders headers = getRawHeaders(); + if ((headers.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM || headers.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP) && request.getFollowRedirect()) { + AsyncHttpRequest newReq = new AsyncHttpRequest(new URI(headers.get("Location")), request.getMethod()); + connect(server, newReq, callback); + + setDataCallback(new NullDataCallback()); + } + } + catch (Exception ex) { + callback.onConnectCompleted(ex, null); + } + }; + }; + ret.setSocket(socket); + callback.onConnectCompleted(null, ret); + } + }); + } + + public static void connect(URI uri, final HttpConnectCallback callback) { + connect(AsyncServer.getDefault(), new AsyncHttpGet(uri), callback); + } + + public static void connect(String uri, final HttpConnectCallback callback) { + try { + connect(AsyncServer.getDefault(), new AsyncHttpGet(new URI(uri)), callback); + } + catch (URISyntaxException e) { + callback.onConnectCompleted(e, null); + } + } + + public static interface DownloadCallback extends ResultCallback<ByteBufferList> { + } + + public static interface StringCallback extends ResultCallback<String> { + } + + private interface ResultConvert { + public Object convert(ByteBufferList bb); + } + + public static void download(String uri, final DownloadCallback callback) { + download(uri, callback, new ResultConvert() { + @Override + public Object convert(ByteBufferList b) { + return b; + } + }); + } + + public static void download(String uri, final StringCallback callback) { + download(uri, callback, new ResultConvert() { + @Override + public Object convert(ByteBufferList bb) { + StringBuilder builder = new StringBuilder(); + for (ByteBuffer b: bb) { + builder.append(new String(b.array(), b.arrayOffset() + b.position(), b.remaining())); + } + return builder.toString(); + } + }); + } + + private static void download(String uri, final ResultCallback callback, final ResultConvert convert) { + connect(uri, new HttpConnectCallback() { + ByteBufferList buffer = new ByteBufferList(); + @Override + public void onConnectCompleted(Exception ex, AsyncHttpResponse response) { + if (ex != null) { + callback.onCompleted(ex, null); + return; + } + + response.setDataCallback(new DataCallback() { + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + buffer.add(bb); + bb.clear(); + } + }); + response.setCompletedCallback(new CompletedCallback() { + @Override + public void onCompleted(Exception ex) { + callback.onCompleted(ex, buffer != null ? convert.convert(buffer) : null); + } + }); + } + }); + } +} diff --git a/src/com/koushikdutta/async/http/AsyncHttpGet.java b/src/com/koushikdutta/async/http/AsyncHttpGet.java new file mode 100644 index 0000000..06f679d --- /dev/null +++ b/src/com/koushikdutta/async/http/AsyncHttpGet.java @@ -0,0 +1,16 @@ +package com.koushikdutta.async.http; + +import java.net.URI; +import java.net.URISyntaxException; + +public class AsyncHttpGet extends AsyncHttpRequest { + public static final String METHOD = "GET"; + + public AsyncHttpGet(String uri) throws URISyntaxException { + super(new URI(uri), METHOD); + } + + public AsyncHttpGet(URI uri) { + super(uri, METHOD); + } +} diff --git a/src/com/koushikdutta/async/http/AsyncHttpRequest.java b/src/com/koushikdutta/async/http/AsyncHttpRequest.java new file mode 100644 index 0000000..b0fb5cc --- /dev/null +++ b/src/com/koushikdutta/async/http/AsyncHttpRequest.java @@ -0,0 +1,56 @@ +package com.koushikdutta.async.http; + +import java.net.URI; + +import junit.framework.Assert; + +import com.koushikdutta.async.http.libcore.RawHeaders; +import com.koushikdutta.async.http.libcore.RequestHeaders; + +public class AsyncHttpRequest { + public String getRequestLine() { + String path = getUri().getPath(); + if (path.length() == 0) + path = "/"; + return String.format("%s %s HTTP/1.1", mMethod, path); + } + + protected final String getDefaultUserAgent() { + String agent = System.getProperty("http.agent"); + return agent != null ? agent : ("Java" + System.getProperty("java.version")); + } + + private String mMethod; + public String getMethod() { + return mMethod; + } + + public AsyncHttpRequest(URI uri, String method) { + Assert.assertNotNull(uri); + mMethod = method; + mHeaders = new RequestHeaders(uri, mRawHeaders); + mRawHeaders.setStatusLine(getRequestLine()); + mHeaders.setHost(uri.getHost()); + mHeaders.setUserAgent(getDefaultUserAgent()); + mHeaders.setAcceptEncoding("gzip"); + } + + public URI getUri() { + return mHeaders.getUri(); + } + + private RawHeaders mRawHeaders = new RawHeaders(); + private RequestHeaders mHeaders; + + public String getRequestString() { + return mRawHeaders.toHeaderString(); + } + + private boolean mFollowRedirect = true; + public boolean getFollowRedirect() { + return mFollowRedirect; + } + public void setFollowRedirect(boolean follow) { + mFollowRedirect = follow; + } +} diff --git a/src/com/koushikdutta/async/http/AsyncHttpResponse.java b/src/com/koushikdutta/async/http/AsyncHttpResponse.java new file mode 100644 index 0000000..0aa61ea --- /dev/null +++ b/src/com/koushikdutta/async/http/AsyncHttpResponse.java @@ -0,0 +1,10 @@ +package com.koushikdutta.async.http; + +import com.koushikdutta.async.ExceptionEmitter; +import com.koushikdutta.async.DataEmitter; +import com.koushikdutta.async.callback.CompletedCallback; + +public interface AsyncHttpResponse extends DataEmitter, ExceptionEmitter { + public void setCompletedCallback(CompletedCallback handler); + public CompletedCallback getCloseHandler(); +} diff --git a/src/com/koushikdutta/async/http/AsyncHttpResponseImpl.java b/src/com/koushikdutta/async/http/AsyncHttpResponseImpl.java new file mode 100644 index 0000000..fbfb69a --- /dev/null +++ b/src/com/koushikdutta/async/http/AsyncHttpResponseImpl.java @@ -0,0 +1,192 @@ +package com.koushikdutta.async.http; + +import java.nio.ByteBuffer; + +import junit.framework.Assert; + +import com.koushikdutta.async.AsyncSocket; +import com.koushikdutta.async.BufferedDataSink; +import com.koushikdutta.async.ByteBufferList; +import com.koushikdutta.async.DataEmitter; +import com.koushikdutta.async.DataExchange; +import com.koushikdutta.async.DataTransformerBase; +import com.koushikdutta.async.ExceptionCallback; +import com.koushikdutta.async.LineEmitter; +import com.koushikdutta.async.LineEmitter.StringCallback; +import com.koushikdutta.async.SSLDataExchange; +import com.koushikdutta.async.callback.ClosedCallback; +import com.koushikdutta.async.callback.CompletedCallback; +import com.koushikdutta.async.callback.DataCallback; +import com.koushikdutta.async.http.libcore.RawHeaders; +import com.koushikdutta.async.http.libcore.ResponseHeaders; +import com.koushikdutta.async.http.transform.ChunkedTransformer; +import com.koushikdutta.async.http.transform.GZIPTransformer; +import com.koushikdutta.async.http.transform.InflaterTransformer; + +public class AsyncHttpResponseImpl extends DataTransformerBase implements AsyncHttpResponse { + private RawHeaders mRawHeaders = new RawHeaders(); + RawHeaders getRawHeaders() { + return mRawHeaders; + } + + void setSocket(AsyncSocket socket) { + mSocket = socket; + // socket and exchange are the same for regular http + // but different for https (ssl) + // the exchange will be a wrapper around socket that does + // ssl translation. + DataExchange exchange = socket; + if (mRequest.getUri().getScheme().equals("https")) { + SSLDataExchange ssl = new SSLDataExchange(socket); + exchange = ssl; + socket.setDataCallback(ssl); + } + mExchange = exchange; + + mWriter = new BufferedDataSink(exchange); + String rs = mRequest.getRequestString(); + mWriter.write(ByteBuffer.wrap(rs.getBytes())); + + LineEmitter liner = new LineEmitter(exchange); + liner.setLineCallback(mHeaderCallback); + + mSocket.setClosedCallback(new ClosedCallback() { + @Override + public void onClosed() { + if (!mCompleted) { + report(new Exception("connection closed before response completed.")); + } + } + }); + } + + protected void onHeadersReceived() { + mHeaders = new ResponseHeaders(mRequest.getUri(), mRawHeaders); + + DataCallback callback = this; + + if ("gzip".equals(mHeaders.getContentEncoding())) { + GZIPTransformer gunzipper = new GZIPTransformer() { + @Override + public void onException(Exception error) { + report(error); + } + }; + gunzipper.setDataCallback(callback); + callback = gunzipper; + } + else if ("deflate".equals(mHeaders.getContentEncoding())) { + InflaterTransformer inflater = new InflaterTransformer() { + @Override + public void onException(Exception error) { + report(error); + } + }; + inflater.setDataCallback(callback); + callback = inflater; + } + + if (!mHeaders.isChunked()) { + Assert.assertTrue(mHeaders.getContentLength() > 0); + DataTransformerBase contentLengthWatcher = new DataTransformerBase() { + int totalRead = 0; + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + totalRead += bb.remaining(); + Assert.assertTrue(totalRead <= mHeaders.getContentLength()); + super.onDataAvailable(emitter, bb); + if (totalRead == mHeaders.getContentLength()) + report(null); + } + }; + contentLengthWatcher.setDataCallback(callback); + callback = contentLengthWatcher; + } + else { + ChunkedTransformer chunker = new ChunkedTransformer() { + @Override + public void onCompleted(Exception ex) { + AsyncHttpResponseImpl.this.report(ex); + } + + @Override + public void onException(Exception error) { + report(error); + } + }; + chunker.setDataCallback(callback); + callback = chunker; + } + mExchange.setDataCallback(callback); + } + + StringCallback mHeaderCallback = new StringCallback() { + @Override + public void onStringAvailable(String s) { + try { + if (mRawHeaders.getStatusLine() == null) { + mRawHeaders.setStatusLine(s); + } + else if (!"\r".equals(s)){ + mRawHeaders.addLine(s); + } + else { + onHeadersReceived(); +// System.out.println(mRawHeaders.toHeaderString()); + } + } + catch (Exception ex) { + report(ex); + } + } + }; + + void report(Exception ex) { + if (ex != null) { + if (mErrorCallback != null) + mErrorCallback.onException(ex); + } + onCompleted(ex); + } + + boolean hasParsedStatusLine = false; + BufferedDataSink mWriter; + AsyncSocket mSocket; + AsyncHttpRequest mRequest; + DataExchange mExchange; + ResponseHeaders mHeaders; + public AsyncHttpResponseImpl(AsyncHttpRequest request) { + mRequest = request; + } + + boolean mCompleted = false; + private void onCompleted(Exception ex) { + mCompleted = true; + mSocket.close(); +// System.out.println("closing up shop"); + if (mCompletedCallback != null) + mCompletedCallback.onCompleted(ex); + } + + CompletedCallback mCompletedCallback; + @Override + public void setCompletedCallback(CompletedCallback handler) { + mCompletedCallback = handler; + } + + @Override + public CompletedCallback getCloseHandler() { + return mCompletedCallback; + } + + ExceptionCallback mErrorCallback; + @Override + public void setExceptionCallback(ExceptionCallback callback) { + mErrorCallback = callback; + } + + @Override + public ExceptionCallback getExceptionCallback() { + return mErrorCallback; + } +} diff --git a/src/com/koushikdutta/async/http/callback/HttpConnectCallback.java b/src/com/koushikdutta/async/http/callback/HttpConnectCallback.java new file mode 100644 index 0000000..3f85e18 --- /dev/null +++ b/src/com/koushikdutta/async/http/callback/HttpConnectCallback.java @@ -0,0 +1,7 @@ +package com.koushikdutta.async.http.callback; + +import com.koushikdutta.async.http.AsyncHttpResponse; + +public interface HttpConnectCallback { + public void onConnectCompleted(Exception ex, AsyncHttpResponse response); +} diff --git a/src/com/koushikdutta/async/http/libcore/HeaderParser.java b/src/com/koushikdutta/async/http/libcore/HeaderParser.java new file mode 100644 index 0000000..a91be67 --- /dev/null +++ b/src/com/koushikdutta/async/http/libcore/HeaderParser.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.koushikdutta.async.http.libcore; + +final class HeaderParser { + + public interface CacheControlHandler { + void handle(String directive, String parameter); + } + + /** + * Parse a comma-separated list of cache control header values. + */ + public static void parseCacheControl(String value, CacheControlHandler handler) { + int pos = 0; + while (pos < value.length()) { + int tokenStart = pos; + pos = skipUntil(value, pos, "=,"); + String directive = value.substring(tokenStart, pos).trim(); + + if (pos == value.length() || value.charAt(pos) == ',') { + pos++; // consume ',' (if necessary) + handler.handle(directive, null); + continue; + } + + pos++; // consume '=' + pos = skipWhitespace(value, pos); + + String parameter; + + // quoted string + if (pos < value.length() && value.charAt(pos) == '\"') { + pos++; // consume '"' open quote + int parameterStart = pos; + pos = skipUntil(value, pos, "\""); + parameter = value.substring(parameterStart, pos); + pos++; // consume '"' close quote (if necessary) + + // unquoted string + } else { + int parameterStart = pos; + pos = skipUntil(value, pos, ","); + parameter = value.substring(parameterStart, pos).trim(); + } + + handler.handle(directive, parameter); + } + } + + /** + * Returns the next index in {@code input} at or after {@code pos} that + * contains a character from {@code characters}. Returns the input length if + * none of the requested characters can be found. + */ + private static int skipUntil(String input, int pos, String characters) { + for (; pos < input.length(); pos++) { + if (characters.indexOf(input.charAt(pos)) != -1) { + break; + } + } + return pos; + } + + /** + * Returns the next non-whitespace character in {@code input} that is white + * space. Result is undefined if input contains newline characters. + */ + private static int skipWhitespace(String input, int pos) { + for (; pos < input.length(); pos++) { + char c = input.charAt(pos); + if (c != ' ' && c != '\t') { + break; + } + } + return pos; + } + + /** + * Returns {@code value} as a positive integer, or 0 if it is negative, or + * -1 if it cannot be parsed. + */ + public static int parseSeconds(String value) { + try { + long seconds = Long.parseLong(value); + if (seconds > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } else if (seconds < 0) { + return 0; + } else { + return (int) seconds; + } + } catch (NumberFormatException e) { + return -1; + } + } +} diff --git a/src/com/koushikdutta/async/http/libcore/HttpDate.java b/src/com/koushikdutta/async/http/libcore/HttpDate.java new file mode 100644 index 0000000..59e4929 --- /dev/null +++ b/src/com/koushikdutta/async/http/libcore/HttpDate.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.koushikdutta.async.http.libcore; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Best-effort parser for HTTP dates. + */ +public final class HttpDate { + + /** + * Most websites serve cookies in the blessed format. Eagerly create the parser to ensure such + * cookies are on the fast path. + */ + private static final ThreadLocal<DateFormat> STANDARD_DATE_FORMAT + = new ThreadLocal<DateFormat>() { + @Override protected DateFormat initialValue() { + DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); + rfc1123.setTimeZone(TimeZone.getTimeZone("UTC")); + return rfc1123; + } + }; + + /** + * If we fail to parse a date in a non-standard format, try each of these formats in sequence. + */ + private static final String[] BROWSER_COMPATIBLE_DATE_FORMATS = new String[] { + /* This list comes from {@code org.apache.http.impl.cookie.BrowserCompatSpec}. */ + "EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 1036 + "EEE MMM d HH:mm:ss yyyy", // ANSI C asctime() + "EEE, dd-MMM-yyyy HH:mm:ss z", + "EEE, dd-MMM-yyyy HH-mm-ss z", + "EEE, dd MMM yy HH:mm:ss z", + "EEE dd-MMM-yyyy HH:mm:ss z", + "EEE dd MMM yyyy HH:mm:ss z", + "EEE dd-MMM-yyyy HH-mm-ss z", + "EEE dd-MMM-yy HH:mm:ss z", + "EEE dd MMM yy HH:mm:ss z", + "EEE,dd-MMM-yy HH:mm:ss z", + "EEE,dd-MMM-yyyy HH:mm:ss z", + "EEE, dd-MM-yyyy HH:mm:ss z", + + /* RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com */ + "EEE MMM d yyyy HH:mm:ss z", + }; + + /** + * Returns the date for {@code value}. Returns null if the value couldn't be + * parsed. + */ + public static Date parse(String value) { + try { + return STANDARD_DATE_FORMAT.get().parse(value); + } catch (ParseException ignore) { + } + for (String formatString : BROWSER_COMPATIBLE_DATE_FORMATS) { + try { + return new SimpleDateFormat(formatString, Locale.US).parse(value); + } catch (ParseException ignore) { + } + } + return null; + } + + /** + * Returns the string for {@code value}. + */ + public static String format(Date value) { + return STANDARD_DATE_FORMAT.get().format(value); + } +} diff --git a/src/com/koushikdutta/async/http/libcore/Memory.java b/src/com/koushikdutta/async/http/libcore/Memory.java new file mode 100644 index 0000000..60a56c4 --- /dev/null +++ b/src/com/koushikdutta/async/http/libcore/Memory.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.koushikdutta.async.http.libcore; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Unsafe access to memory. + */ +public final class Memory { + private Memory() { } + + /** + * Used to optimize nio heap buffer bulk get operations. 'dst' must be a primitive array. + * 'dstOffset' is measured in units of 'sizeofElements' bytes. + */ + public static native void unsafeBulkGet(Object dst, int dstOffset, int byteCount, + byte[] src, int srcOffset, int sizeofElements, boolean swap); + + /** + * Used to optimize nio heap buffer bulk put operations. 'src' must be a primitive array. + * 'srcOffset' is measured in units of 'sizeofElements' bytes. + */ + public static native void unsafeBulkPut(byte[] dst, int dstOffset, int byteCount, + Object src, int srcOffset, int sizeofElements, boolean swap); + + public static int peekInt(byte[] src, int offset, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + return (((src[offset++] & 0xff) << 24) | + ((src[offset++] & 0xff) << 16) | + ((src[offset++] & 0xff) << 8) | + ((src[offset ] & 0xff) << 0)); + } else { + return (((src[offset++] & 0xff) << 0) | + ((src[offset++] & 0xff) << 8) | + ((src[offset++] & 0xff) << 16) | + ((src[offset ] & 0xff) << 24)); + } + } + + public static long peekLong(byte[] src, int offset, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + int h = ((src[offset++] & 0xff) << 24) | + ((src[offset++] & 0xff) << 16) | + ((src[offset++] & 0xff) << 8) | + ((src[offset++] & 0xff) << 0); + int l = ((src[offset++] & 0xff) << 24) | + ((src[offset++] & 0xff) << 16) | + ((src[offset++] & 0xff) << 8) | + ((src[offset ] & 0xff) << 0); + return (((long) h) << 32L) | ((long) l) & 0xffffffffL; + } else { + int l = ((src[offset++] & 0xff) << 0) | + ((src[offset++] & 0xff) << 8) | + ((src[offset++] & 0xff) << 16) | + ((src[offset++] & 0xff) << 24); + int h = ((src[offset++] & 0xff) << 0) | + ((src[offset++] & 0xff) << 8) | + ((src[offset++] & 0xff) << 16) | + ((src[offset ] & 0xff) << 24); + return (((long) h) << 32L) | ((long) l) & 0xffffffffL; + } + } + + public static short peekShort(byte[] src, int offset, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + return (short) ((src[offset] << 8) | (src[offset + 1] & 0xff)); + } else { + return (short) ((src[offset + 1] << 8) | (src[offset] & 0xff)); + } + } + + public static void pokeInt(byte[] dst, int offset, int value, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + dst[offset++] = (byte) ((value >> 24) & 0xff); + dst[offset++] = (byte) ((value >> 16) & 0xff); + dst[offset++] = (byte) ((value >> 8) & 0xff); + dst[offset ] = (byte) ((value >> 0) & 0xff); + } else { + dst[offset++] = (byte) ((value >> 0) & 0xff); + dst[offset++] = (byte) ((value >> 8) & 0xff); + dst[offset++] = (byte) ((value >> 16) & 0xff); + dst[offset ] = (byte) ((value >> 24) & 0xff); + } + } + + public static void pokeLong(byte[] dst, int offset, long value, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + int i = (int) (value >> 32); + dst[offset++] = (byte) ((i >> 24) & 0xff); + dst[offset++] = (byte) ((i >> 16) & 0xff); + dst[offset++] = (byte) ((i >> 8) & 0xff); + dst[offset++] = (byte) ((i >> 0) & 0xff); + i = (int) value; + dst[offset++] = (byte) ((i >> 24) & 0xff); + dst[offset++] = (byte) ((i >> 16) & 0xff); + dst[offset++] = (byte) ((i >> 8) & 0xff); + dst[offset ] = (byte) ((i >> 0) & 0xff); + } else { + int i = (int) value; + dst[offset++] = (byte) ((i >> 0) & 0xff); + dst[offset++] = (byte) ((i >> 8) & 0xff); + dst[offset++] = (byte) ((i >> 16) & 0xff); + dst[offset++] = (byte) ((i >> 24) & 0xff); + i = (int) (value >> 32); + dst[offset++] = (byte) ((i >> 0) & 0xff); + dst[offset++] = (byte) ((i >> 8) & 0xff); + dst[offset++] = (byte) ((i >> 16) & 0xff); + dst[offset ] = (byte) ((i >> 24) & 0xff); + } + } + + public static void pokeShort(byte[] dst, int offset, short value, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + dst[offset++] = (byte) ((value >> 8) & 0xff); + dst[offset ] = (byte) ((value >> 0) & 0xff); + } else { + dst[offset++] = (byte) ((value >> 0) & 0xff); + dst[offset ] = (byte) ((value >> 8) & 0xff); + } + } + + /** + * Copies 'byteCount' bytes from the source to the destination. The objects are either + * instances of DirectByteBuffer or byte[]. The offsets in the byte[] case must include + * the Buffer.arrayOffset if the array came from a Buffer.array call. We could make this + * private and provide the four type-safe variants, but then ByteBuffer.put(ByteBuffer) + * would need to work out which to call based on whether the source and destination buffers + * are direct or not. + * + * @hide make type-safe before making public? + */ + public static native void memmove(Object dstObject, int dstOffset, Object srcObject, int srcOffset, long byteCount); + + public static native byte peekByte(int address); + public static native int peekInt(int address, boolean swap); + public static native long peekLong(int address, boolean swap); + public static native short peekShort(int address, boolean swap); + + public static native void peekByteArray(int address, byte[] dst, int dstOffset, int byteCount); + public static native void peekCharArray(int address, char[] dst, int dstOffset, int charCount, boolean swap); + public static native void peekDoubleArray(int address, double[] dst, int dstOffset, int doubleCount, boolean swap); + public static native void peekFloatArray(int address, float[] dst, int dstOffset, int floatCount, boolean swap); + public static native void peekIntArray(int address, int[] dst, int dstOffset, int intCount, boolean swap); + public static native void peekLongArray(int address, long[] dst, int dstOffset, int longCount, boolean swap); + public static native void peekShortArray(int address, short[] dst, int dstOffset, int shortCount, boolean swap); + + public static native void pokeByte(int address, byte value); + public static native void pokeInt(int address, int value, boolean swap); + public static native void pokeLong(int address, long value, boolean swap); + public static native void pokeShort(int address, short value, boolean swap); + + public static native void pokeByteArray(int address, byte[] src, int offset, int count); + public static native void pokeCharArray(int address, char[] src, int offset, int count, boolean swap); + public static native void pokeDoubleArray(int address, double[] src, int offset, int count, boolean swap); + public static native void pokeFloatArray(int address, float[] src, int offset, int count, boolean swap); + public static native void pokeIntArray(int address, int[] src, int offset, int count, boolean swap); + public static native void pokeLongArray(int address, long[] src, int offset, int count, boolean swap); + public static native void pokeShortArray(int address, short[] src, int offset, int count, boolean swap); +} diff --git a/src/com/koushikdutta/async/http/libcore/Objects.java b/src/com/koushikdutta/async/http/libcore/Objects.java new file mode 100644 index 0000000..a0bfbad --- /dev/null +++ b/src/com/koushikdutta/async/http/libcore/Objects.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.koushikdutta.async.http.libcore; + +public final class Objects { + private Objects() {} + + /** + * Returns true if two possibly-null objects are equal. + */ + public static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + public static int hashCode(Object o) { + return (o == null) ? 0 : o.hashCode(); + } +} diff --git a/src/com/koushikdutta/async/http/libcore/RawHeaders.java b/src/com/koushikdutta/async/http/libcore/RawHeaders.java new file mode 100644 index 0000000..79be2e1 --- /dev/null +++ b/src/com/koushikdutta/async/http/libcore/RawHeaders.java @@ -0,0 +1,296 @@ +package com.koushikdutta.async.http.libcore; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; + +/** + * The HTTP status and unparsed header fields of a single HTTP message. Values + * are represented as uninterpreted strings; use {@link RequestHeaders} and + * {@link ResponseHeaders} for interpreted headers. This class maintains the + * order of the header fields within the HTTP message. + * + * <p>This class tracks fields line-by-line. A field with multiple comma- + * separated values on the same line will be treated as a field with a single + * value by this class. It is the caller's responsibility to detect and split + * on commas if their field permits multiple values. This simplifies use of + * single-valued fields whose values routinely contain commas, such as cookies + * or dates. + * + * <p>This class trims whitespace from values. It never returns values with + * leading or trailing whitespace. + */ +public final class RawHeaders { + private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() { + @Override public int compare(String a, String b) { + if (a == b) { + return 0; + } else if (a == null) { + return -1; + } else if (b == null) { + return 1; + } else { + return String.CASE_INSENSITIVE_ORDER.compare(a, b); + } + } + }; + + private final List<String> namesAndValues = new ArrayList<String>(20); + private String statusLine; + private int httpMinorVersion = 1; + private int responseCode = -1; + private String responseMessage; + + public RawHeaders() {} + + public RawHeaders(RawHeaders copyFrom) { + namesAndValues.addAll(copyFrom.namesAndValues); + statusLine = copyFrom.statusLine; + httpMinorVersion = copyFrom.httpMinorVersion; + responseCode = copyFrom.responseCode; + responseMessage = copyFrom.responseMessage; + } + + /** + * Sets the response status line (like "HTTP/1.0 200 OK") or request line + * (like "GET / HTTP/1.1"). + */ + public void setStatusLine(String statusLine) { + statusLine = statusLine.trim(); + this.statusLine = statusLine; + + if (statusLine == null || !statusLine.startsWith("HTTP/")) { + return; + } + statusLine = statusLine.trim(); + int mark = statusLine.indexOf(" ") + 1; + if (mark == 0) { + return; + } + if (statusLine.charAt(mark - 2) != '1') { + this.httpMinorVersion = 0; + } + int last = mark + 3; + if (last > statusLine.length()) { + last = statusLine.length(); + } + this.responseCode = Integer.parseInt(statusLine.substring(mark, last)); + if (last + 1 <= statusLine.length()) { + this.responseMessage = statusLine.substring(last + 1); + } + } + + public String getStatusLine() { + return statusLine; + } + + /** + * Returns the status line's HTTP minor version. This returns 0 for HTTP/1.0 + * and 1 for HTTP/1.1. This returns 1 if the HTTP version is unknown. + */ + public int getHttpMinorVersion() { + return httpMinorVersion != -1 ? httpMinorVersion : 1; + } + + /** + * Returns the HTTP status code or -1 if it is unknown. + */ + public int getResponseCode() { + return responseCode; + } + + /** + * Returns the HTTP status message or null if it is unknown. + */ + public String getResponseMessage() { + return responseMessage; + } + + /** + * Add an HTTP header line containing a field name, a literal colon, and a + * value. + */ + public void addLine(String line) { + int index = line.indexOf(":"); + if (index == -1) { + add("", line); + } else { + add(line.substring(0, index), line.substring(index + 1)); + } + } + + /** + * Add a field with the specified value. + */ + public void add(String fieldName, String value) { + if (fieldName == null) { + throw new IllegalArgumentException("fieldName == null"); + } + if (value == null) { + /* + * Given null values, the RI sends a malformed field line like + * "Accept\r\n". For platform compatibility and HTTP compliance, we + * print a warning and ignore null values. + */ + System.err.println("Ignoring HTTP header field '" + fieldName + "' because its value is null"); + return; + } + namesAndValues.add(fieldName); + namesAndValues.add(value.trim()); + } + + public void removeAll(String fieldName) { + for (int i = 0; i < namesAndValues.size(); i += 2) { + if (fieldName.equalsIgnoreCase(namesAndValues.get(i))) { + namesAndValues.remove(i); // field name + namesAndValues.remove(i); // value + } + } + } + + public void addAll(String fieldName, List<String> headerFields) { + for (String value : headerFields) { + add(fieldName, value); + } + } + + /** + * Set a field with the specified value. If the field is not found, it is + * added. If the field is found, the existing values are replaced. + */ + public void set(String fieldName, String value) { + removeAll(fieldName); + add(fieldName, value); + } + + /** + * Returns the number of field values. + */ + public int length() { + return namesAndValues.size() / 2; + } + + /** + * Returns the field at {@code position} or null if that is out of range. + */ + public String getFieldName(int index) { + int fieldNameIndex = index * 2; + if (fieldNameIndex < 0 || fieldNameIndex >= namesAndValues.size()) { + return null; + } + return namesAndValues.get(fieldNameIndex); + } + + /** + * Returns the value at {@code index} or null if that is out of range. + */ + public String getValue(int index) { + int valueIndex = index * 2 + 1; + if (valueIndex < 0 || valueIndex >= namesAndValues.size()) { + return null; + } + return namesAndValues.get(valueIndex); + } + + /** + * Returns the last value corresponding to the specified field, or null. + */ + public String get(String fieldName) { + for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) { + if (fieldName.equalsIgnoreCase(namesAndValues.get(i))) { + return namesAndValues.get(i + 1); + } + } + return null; + } + + /** + * @param fieldNames a case-insensitive set of HTTP header field names. + */ + public RawHeaders getAll(Set<String> fieldNames) { + RawHeaders result = new RawHeaders(); + for (int i = 0; i < namesAndValues.size(); i += 2) { + String fieldName = namesAndValues.get(i); + if (fieldNames.contains(fieldName)) { + result.add(fieldName, namesAndValues.get(i + 1)); + } + } + return result; + } + + public String toHeaderString() { + StringBuilder result = new StringBuilder(256); + result.append(statusLine).append("\r\n"); + for (int i = 0; i < namesAndValues.size(); i += 2) { + result.append(namesAndValues.get(i)).append(": ") + .append(namesAndValues.get(i + 1)).append("\r\n"); + } + result.append("\r\n"); + return result.toString(); + } + + /** + * Returns an immutable map containing each field to its list of values. The + * status line is mapped to null. + */ + public Map<String, List<String>> toMultimap() { + Map<String, List<String>> result = new TreeMap<String, List<String>>(FIELD_NAME_COMPARATOR); + for (int i = 0; i < namesAndValues.size(); i += 2) { + String fieldName = namesAndValues.get(i); + String value = namesAndValues.get(i + 1); + + List<String> allValues = new ArrayList<String>(); + List<String> otherValues = result.get(fieldName); + if (otherValues != null) { + allValues.addAll(otherValues); + } + allValues.add(value); + result.put(fieldName, Collections.unmodifiableList(allValues)); + } + if (statusLine != null) { + result.put(null, Collections.unmodifiableList(Collections.singletonList(statusLine))); + } + return Collections.unmodifiableMap(result); + } + + /** + * Creates a new instance from the given map of fields to values. If + * present, the null field's last element will be used to set the status + * line. + */ + public static RawHeaders fromMultimap(Map<String, List<String>> map) { + RawHeaders result = new RawHeaders(); + for (Entry<String, List<String>> entry : map.entrySet()) { + String fieldName = entry.getKey(); + List<String> values = entry.getValue(); + if (fieldName != null) { + result.addAll(fieldName, values); + } else if (!values.isEmpty()) { + result.setStatusLine(values.get(values.size() - 1)); + } + } + return result; + } +} diff --git a/src/com/koushikdutta/async/http/libcore/RequestHeaders.java b/src/com/koushikdutta/async/http/libcore/RequestHeaders.java new file mode 100644 index 0000000..4e29e71 --- /dev/null +++ b/src/com/koushikdutta/async/http/libcore/RequestHeaders.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.koushikdutta.async.http.libcore; + +import java.net.URI; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * Parsed HTTP request headers. + */ +public final class RequestHeaders { + private final URI uri; + private final RawHeaders headers; + + /** Don't use a cache to satisfy this request. */ + private boolean noCache; + private int maxAgeSeconds = -1; + private int maxStaleSeconds = -1; + private int minFreshSeconds = -1; + + /** + * This field's name "only-if-cached" is misleading. It actually means "do + * not use the network". It is set by a client who only wants to make a + * request if it can be fully satisfied by the cache. Cached responses that + * would require validation (ie. conditional gets) are not permitted if this + * header is set. + */ + private boolean onlyIfCached; + + /** + * True if the request contains an authorization field. Although this isn't + * necessarily a shared cache, it follows the spec's strict requirements for + * shared caches. + */ + private boolean hasAuthorization; + + private int contentLength = -1; + private String transferEncoding; + private String userAgent; + private String host; + private String connection; + private String acceptEncoding; + private String contentType; + private String ifModifiedSince; + private String ifNoneMatch; + private String proxyAuthorization; + + public RequestHeaders(URI uri, RawHeaders headers) { + this.uri = uri; + this.headers = headers; + + HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() { + @Override public void handle(String directive, String parameter) { + if (directive.equalsIgnoreCase("no-cache")) { + noCache = true; + } else if (directive.equalsIgnoreCase("max-age")) { + maxAgeSeconds = HeaderParser.parseSeconds(parameter); + } else if (directive.equalsIgnoreCase("max-stale")) { + maxStaleSeconds = HeaderParser.parseSeconds(parameter); + } else if (directive.equalsIgnoreCase("min-fresh")) { + minFreshSeconds = HeaderParser.parseSeconds(parameter); + } else if (directive.equalsIgnoreCase("only-if-cached")) { + onlyIfCached = true; + } + } + }; + + for (int i = 0; i < headers.length(); i++) { + String fieldName = headers.getFieldName(i); + String value = headers.getValue(i); + if ("Cache-Control".equalsIgnoreCase(fieldName)) { + HeaderParser.parseCacheControl(value, handler); + } else if ("Pragma".equalsIgnoreCase(fieldName)) { + if (value.equalsIgnoreCase("no-cache")) { + noCache = true; + } + } else if ("If-None-Match".equalsIgnoreCase(fieldName)) { + ifNoneMatch = value; + } else if ("If-Modified-Since".equalsIgnoreCase(fieldName)) { + ifModifiedSince = value; + } else if ("Authorization".equalsIgnoreCase(fieldName)) { + hasAuthorization = true; + } else if ("Content-Length".equalsIgnoreCase(fieldName)) { + try { + contentLength = Integer.parseInt(value); + } catch (NumberFormatException ignored) { + } + } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) { + transferEncoding = value; + } else if ("User-Agent".equalsIgnoreCase(fieldName)) { + userAgent = value; + } else if ("Host".equalsIgnoreCase(fieldName)) { + host = value; + } else if ("Connection".equalsIgnoreCase(fieldName)) { + connection = value; + } else if ("Accept-Encoding".equalsIgnoreCase(fieldName)) { + acceptEncoding = value; + } else if ("Content-Type".equalsIgnoreCase(fieldName)) { + contentType = value; + } else if ("Proxy-Authorization".equalsIgnoreCase(fieldName)) { + proxyAuthorization = value; + } + } + } + + public boolean isChunked() { + return "chunked".equalsIgnoreCase(transferEncoding); + } + + public boolean hasConnectionClose() { + return "close".equalsIgnoreCase(connection); + } + + public URI getUri() { + return uri; + } + + public RawHeaders getHeaders() { + return headers; + } + + public boolean isNoCache() { + return noCache; + } + + public int getMaxAgeSeconds() { + return maxAgeSeconds; + } + + public int getMaxStaleSeconds() { + return maxStaleSeconds; + } + + public int getMinFreshSeconds() { + return minFreshSeconds; + } + + public boolean isOnlyIfCached() { + return onlyIfCached; + } + + public boolean hasAuthorization() { + return hasAuthorization; + } + + public int getContentLength() { + return contentLength; + } + + public String getTransferEncoding() { + return transferEncoding; + } + + public String getUserAgent() { + return userAgent; + } + + public String getHost() { + return host; + } + + public String getConnection() { + return connection; + } + + public String getAcceptEncoding() { + return acceptEncoding; + } + + public String getContentType() { + return contentType; + } + + public String getIfModifiedSince() { + return ifModifiedSince; + } + + public String getIfNoneMatch() { + return ifNoneMatch; + } + + public String getProxyAuthorization() { + return proxyAuthorization; + } + + public void setChunked() { + if (this.transferEncoding != null) { + headers.removeAll("Transfer-Encoding"); + } + headers.add("Transfer-Encoding", "chunked"); + this.transferEncoding = "chunked"; + } + + public void setContentLength(int contentLength) { + if (this.contentLength != -1) { + headers.removeAll("Content-Length"); + } + headers.add("Content-Length", Integer.toString(contentLength)); + this.contentLength = contentLength; + } + + public void setUserAgent(String userAgent) { + if (this.userAgent != null) { + headers.removeAll("User-Agent"); + } + headers.add("User-Agent", userAgent); + this.userAgent = userAgent; + } + + public void setHost(String host) { + if (this.host != null) { + headers.removeAll("Host"); + } + headers.add("Host", host); + this.host = host; + } + + public void setConnection(String connection) { + if (this.connection != null) { + headers.removeAll("Connection"); + } + headers.add("Connection", connection); + this.connection = connection; + } + + public void setAcceptEncoding(String acceptEncoding) { + if (this.acceptEncoding != null) { + headers.removeAll("Accept-Encoding"); + } + headers.add("Accept-Encoding", acceptEncoding); + this.acceptEncoding = acceptEncoding; + } + + public void setContentType(String contentType) { + if (this.contentType != null) { + headers.removeAll("Content-Type"); + } + headers.add("Content-Type", contentType); + this.contentType = contentType; + } + + public void setIfModifiedSince(Date date) { + if (ifModifiedSince != null) { + headers.removeAll("If-Modified-Since"); + } + String formattedDate = HttpDate.format(date); + headers.add("If-Modified-Since", formattedDate); + ifModifiedSince = formattedDate; + } + + public void setIfNoneMatch(String ifNoneMatch) { + if (this.ifNoneMatch != null) { + headers.removeAll("If-None-Match"); + } + headers.add("If-None-Match", ifNoneMatch); + this.ifNoneMatch = ifNoneMatch; + } + + /** + * Returns true if the request contains conditions that save the server from + * sending a response that the client has locally. When the caller adds + * conditions, this cache won't participate in the request. + */ + public boolean hasConditions() { + return ifModifiedSince != null || ifNoneMatch != null; + } + + public void addCookies(Map<String, List<String>> allCookieHeaders) { + for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) { + String key = entry.getKey(); + if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) { + headers.addAll(key, entry.getValue()); + } + } + } +} diff --git a/src/com/koushikdutta/async/http/libcore/ResponseHeaders.java b/src/com/koushikdutta/async/http/libcore/ResponseHeaders.java new file mode 100644 index 0000000..8f7bb65 --- /dev/null +++ b/src/com/koushikdutta/async/http/libcore/ResponseHeaders.java @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.koushikdutta.async.http.libcore; + +import java.net.HttpURLConnection; +import java.net.URI; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.TimeUnit; + +/** + * Parsed HTTP response headers. + */ +public final class ResponseHeaders { + + /** HTTP header name for the local time when the request was sent. */ + private static final String SENT_MILLIS = "X-Android-Sent-Millis"; + + /** HTTP header name for the local time when the response was received. */ + private static final String RECEIVED_MILLIS = "X-Android-Received-Millis"; + + private final URI uri; + private final RawHeaders headers; + + /** The server's time when this response was served, if known. */ + private Date servedDate; + + /** The last modified date of the response, if known. */ + private Date lastModified; + + /** + * The expiration date of the response, if known. If both this field and the + * max age are set, the max age is preferred. + */ + private Date expires; + + /** + * Extension header set by HttpURLConnectionImpl specifying the timestamp + * when the HTTP request was first initiated. + */ + private long sentRequestMillis; + + /** + * Extension header set by HttpURLConnectionImpl specifying the timestamp + * when the HTTP response was first received. + */ + private long receivedResponseMillis; + + /** + * In the response, this field's name "no-cache" is misleading. It doesn't + * prevent us from caching the response; it only means we have to validate + * the response with the origin server before returning it. We can do this + * with a conditional get. + */ + private boolean noCache; + + /** If true, this response should not be cached. */ + private boolean noStore; + + /** + * The duration past the response's served date that it can be served + * without validation. + */ + private int maxAgeSeconds = -1; + + /** + * The "s-maxage" directive is the max age for shared caches. Not to be + * confused with "max-age" for non-shared caches, As in Firefox and Chrome, + * this directive is not honored by this cache. + */ + private int sMaxAgeSeconds = -1; + + /** + * This request header field's name "only-if-cached" is misleading. It + * actually means "do not use the network". It is set by a client who only + * wants to make a request if it can be fully satisfied by the cache. + * Cached responses that would require validation (ie. conditional gets) are + * not permitted if this header is set. + */ + private boolean isPublic; + private boolean mustRevalidate; + private String etag; + private int ageSeconds = -1; + + /** Case-insensitive set of field names. */ + private Set<String> varyFields = Collections.emptySet(); + + private String contentEncoding; + private String transferEncoding; + private int contentLength = -1; + private String connection; + private String proxyAuthenticate; + private String wwwAuthenticate; + + public ResponseHeaders(URI uri, RawHeaders headers) { + this.uri = uri; + this.headers = headers; + + HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() { + @Override public void handle(String directive, String parameter) { + if (directive.equalsIgnoreCase("no-cache")) { + noCache = true; + } else if (directive.equalsIgnoreCase("no-store")) { + noStore = true; + } else if (directive.equalsIgnoreCase("max-age")) { + maxAgeSeconds = HeaderParser.parseSeconds(parameter); + } else if (directive.equalsIgnoreCase("s-maxage")) { + sMaxAgeSeconds = HeaderParser.parseSeconds(parameter); + } else if (directive.equalsIgnoreCase("public")) { + isPublic = true; + } else if (directive.equalsIgnoreCase("must-revalidate")) { + mustRevalidate = true; + } + } + }; + + for (int i = 0; i < headers.length(); i++) { + String fieldName = headers.getFieldName(i); + String value = headers.getValue(i); + if ("Cache-Control".equalsIgnoreCase(fieldName)) { + HeaderParser.parseCacheControl(value, handler); + } else if ("Date".equalsIgnoreCase(fieldName)) { + servedDate = HttpDate.parse(value); + } else if ("Expires".equalsIgnoreCase(fieldName)) { + expires = HttpDate.parse(value); + } else if ("Last-Modified".equalsIgnoreCase(fieldName)) { + lastModified = HttpDate.parse(value); + } else if ("ETag".equalsIgnoreCase(fieldName)) { + etag = value; + } else if ("Pragma".equalsIgnoreCase(fieldName)) { + if (value.equalsIgnoreCase("no-cache")) { + noCache = true; + } + } else if ("Age".equalsIgnoreCase(fieldName)) { + ageSeconds = HeaderParser.parseSeconds(value); + } else if ("Vary".equalsIgnoreCase(fieldName)) { + // Replace the immutable empty set with something we can mutate. + if (varyFields.isEmpty()) { + varyFields = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); + } + for (String varyField : value.split(",")) { + varyFields.add(varyField.trim()); + } + } else if ("Content-Encoding".equalsIgnoreCase(fieldName)) { + contentEncoding = value; + } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) { + transferEncoding = value; + } else if ("Content-Length".equalsIgnoreCase(fieldName)) { + try { + contentLength = Integer.parseInt(value); + } catch (NumberFormatException ignored) { + } + } else if ("Connection".equalsIgnoreCase(fieldName)) { + connection = value; + } else if ("Proxy-Authenticate".equalsIgnoreCase(fieldName)) { + proxyAuthenticate = value; + } else if ("WWW-Authenticate".equalsIgnoreCase(fieldName)) { + wwwAuthenticate = value; + } else if (SENT_MILLIS.equalsIgnoreCase(fieldName)) { + sentRequestMillis = Long.parseLong(value); + } else if (RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) { + receivedResponseMillis = Long.parseLong(value); + } + } + } + + public boolean isContentEncodingGzip() { + return "gzip".equalsIgnoreCase(contentEncoding); + } + + public void stripContentEncoding() { + contentEncoding = null; + headers.removeAll("Content-Encoding"); + } + + public boolean isChunked() { + return "chunked".equalsIgnoreCase(transferEncoding); + } + + public boolean hasConnectionClose() { + return "close".equalsIgnoreCase(connection); + } + + public URI getUri() { + return uri; + } + + public RawHeaders getHeaders() { + return headers; + } + + public Date getServedDate() { + return servedDate; + } + + public Date getLastModified() { + return lastModified; + } + + public Date getExpires() { + return expires; + } + + public boolean isNoCache() { + return noCache; + } + + public boolean isNoStore() { + return noStore; + } + + public int getMaxAgeSeconds() { + return maxAgeSeconds; + } + + public int getSMaxAgeSeconds() { + return sMaxAgeSeconds; + } + + public boolean isPublic() { + return isPublic; + } + + public boolean isMustRevalidate() { + return mustRevalidate; + } + + public String getEtag() { + return etag; + } + + public Set<String> getVaryFields() { + return varyFields; + } + + public String getContentEncoding() { + return contentEncoding; + } + + public int getContentLength() { + return contentLength; + } + + public String getConnection() { + return connection; + } + + public String getProxyAuthenticate() { + return proxyAuthenticate; + } + + public String getWwwAuthenticate() { + return wwwAuthenticate; + } + + public void setLocalTimestamps(long sentRequestMillis, long receivedResponseMillis) { + this.sentRequestMillis = sentRequestMillis; + headers.add(SENT_MILLIS, Long.toString(sentRequestMillis)); + this.receivedResponseMillis = receivedResponseMillis; + headers.add(RECEIVED_MILLIS, Long.toString(receivedResponseMillis)); + } + + /** + * Returns the current age of the response, in milliseconds. The calculation + * is specified by RFC 2616, 13.2.3 Age Calculations. + */ + private long computeAge(long nowMillis) { + long apparentReceivedAge = servedDate != null + ? Math.max(0, receivedResponseMillis - servedDate.getTime()) + : 0; + long receivedAge = ageSeconds != -1 + ? Math.max(apparentReceivedAge, TimeUnit.SECONDS.toMillis(ageSeconds)) + : apparentReceivedAge; + long responseDuration = receivedResponseMillis - sentRequestMillis; + long residentDuration = nowMillis - receivedResponseMillis; + return receivedAge + responseDuration + residentDuration; + } + + /** + * Returns the number of milliseconds that the response was fresh for, + * starting from the served date. + */ + private long computeFreshnessLifetime() { + if (maxAgeSeconds != -1) { + return TimeUnit.SECONDS.toMillis(maxAgeSeconds); + } else if (expires != null) { + long servedMillis = servedDate != null ? servedDate.getTime() : receivedResponseMillis; + long delta = expires.getTime() - servedMillis; + return delta > 0 ? delta : 0; + } else if (lastModified != null && uri.getRawQuery() == null) { + /* + * As recommended by the HTTP RFC and implemented in Firefox, the + * max age of a document should be defaulted to 10% of the + * document's age at the time it was served. Default expiration + * dates aren't used for URIs containing a query. + */ + long servedMillis = servedDate != null ? servedDate.getTime() : sentRequestMillis; + long delta = servedMillis - lastModified.getTime(); + return delta > 0 ? (delta / 10) : 0; + } + return 0; + } + + /** + * Returns true if computeFreshnessLifetime used a heuristic. If we used a + * heuristic to serve a cached response older than 24 hours, we are required + * to attach a warning. + */ + private boolean isFreshnessLifetimeHeuristic() { + return maxAgeSeconds == -1 && expires == null; + } + + /** + * Returns true if this response can be stored to later serve another + * request. + */ + public boolean isCacheable(RequestHeaders request) { + /* + * Always go to network for uncacheable response codes (RFC 2616, 13.4), + * This implementation doesn't support caching partial content. + */ + int responseCode = headers.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_OK + && responseCode != HttpURLConnection.HTTP_NOT_AUTHORITATIVE + && responseCode != HttpURLConnection.HTTP_MULT_CHOICE + && responseCode != HttpURLConnection.HTTP_MOVED_PERM + && responseCode != HttpURLConnection.HTTP_GONE) { + return false; + } + + /* + * Responses to authorized requests aren't cacheable unless they include + * a 'public', 'must-revalidate' or 's-maxage' directive. + */ + if (request.hasAuthorization() + && !isPublic + && !mustRevalidate + && sMaxAgeSeconds == -1) { + return false; + } + + if (noStore) { + return false; + } + + return true; + } + + /** + * Returns true if a Vary header contains an asterisk. Such responses cannot + * be cached. + */ + public boolean hasVaryAll() { + return varyFields.contains("*"); + } + + /** + * Returns true if none of the Vary headers on this response have changed + * between {@code cachedRequest} and {@code newRequest}. + */ + public boolean varyMatches(Map<String, List<String>> cachedRequest, + Map<String, List<String>> newRequest) { + for (String field : varyFields) { + if (!Objects.equal(cachedRequest.get(field), newRequest.get(field))) { + return false; + } + } + return true; + } + + /** + * Returns the source to satisfy {@code request} given this cached response. + */ + public ResponseSource chooseResponseSource(long nowMillis, RequestHeaders request) { + /* + * If this response shouldn't have been stored, it should never be used + * as a response source. This check should be redundant as long as the + * persistence store is well-behaved and the rules are constant. + */ + if (!isCacheable(request)) { + return ResponseSource.NETWORK; + } + + if (request.isNoCache() || request.hasConditions()) { + return ResponseSource.NETWORK; + } + + long ageMillis = computeAge(nowMillis); + long freshMillis = computeFreshnessLifetime(); + + if (request.getMaxAgeSeconds() != -1) { + freshMillis = Math.min(freshMillis, + TimeUnit.SECONDS.toMillis(request.getMaxAgeSeconds())); + } + + long minFreshMillis = 0; + if (request.getMinFreshSeconds() != -1) { + minFreshMillis = TimeUnit.SECONDS.toMillis(request.getMinFreshSeconds()); + } + + long maxStaleMillis = 0; + if (!mustRevalidate && request.getMaxStaleSeconds() != -1) { + maxStaleMillis = TimeUnit.SECONDS.toMillis(request.getMaxStaleSeconds()); + } + + if (!noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { + if (ageMillis + minFreshMillis >= freshMillis) { + headers.add("Warning", "110 HttpURLConnection \"Response is stale\""); + } + /* + * not available in API 8 + if (ageMillis > TimeUnit.HOURS.toMillis(24) && isFreshnessLifetimeHeuristic()) { + */ + if (ageMillis > 24L * 60L * 60L * 1000L && isFreshnessLifetimeHeuristic()) { + headers.add("Warning", "113 HttpURLConnection \"Heuristic expiration\""); + } + return ResponseSource.CACHE; + } + + if (lastModified != null) { + request.setIfModifiedSince(lastModified); + } else if (servedDate != null) { + request.setIfModifiedSince(servedDate); + } + + if (etag != null) { + request.setIfNoneMatch(etag); + } + + return request.hasConditions() + ? ResponseSource.CONDITIONAL_CACHE + : ResponseSource.NETWORK; + } + + /** + * Returns true if this cached response should be used; false if the + * network response should be used. + */ + public boolean validate(ResponseHeaders networkResponse) { + if (networkResponse.headers.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) { + return true; + } + + /* + * The HTTP spec says that if the network's response is older than our + * cached response, we may return the cache's response. Like Chrome (but + * unlike Firefox), this client prefers to return the newer response. + */ + if (lastModified != null + && networkResponse.lastModified != null + && networkResponse.lastModified.getTime() < lastModified.getTime()) { + return true; + } + + return false; + } + + /** + * Combines this cached header with a network header as defined by RFC 2616, + * 13.5.3. + */ + public ResponseHeaders combine(ResponseHeaders network) { + RawHeaders result = new RawHeaders(); + + for (int i = 0; i < headers.length(); i++) { + String fieldName = headers.getFieldName(i); + String value = headers.getValue(i); + if (fieldName.equals("Warning") && value.startsWith("1")) { + continue; // drop 100-level freshness warnings + } + if (!isEndToEnd(fieldName) || network.headers.get(fieldName) == null) { + result.add(fieldName, value); + } + } + + for (int i = 0; i < network.headers.length(); i++) { + String fieldName = network.headers.getFieldName(i); + if (isEndToEnd(fieldName)) { + result.add(fieldName, network.headers.getValue(i)); + } + } + + return new ResponseHeaders(uri, result); + } + + /** + * Returns true if {@code fieldName} is an end-to-end HTTP header, as + * defined by RFC 2616, 13.5.1. + */ + private static boolean isEndToEnd(String fieldName) { + return !fieldName.equalsIgnoreCase("Connection") + && !fieldName.equalsIgnoreCase("Keep-Alive") + && !fieldName.equalsIgnoreCase("Proxy-Authenticate") + && !fieldName.equalsIgnoreCase("Proxy-Authorization") + && !fieldName.equalsIgnoreCase("TE") + && !fieldName.equalsIgnoreCase("Trailers") + && !fieldName.equalsIgnoreCase("Transfer-Encoding") + && !fieldName.equalsIgnoreCase("Upgrade"); + } +} diff --git a/src/com/koushikdutta/async/http/libcore/ResponseSource.java b/src/com/koushikdutta/async/http/libcore/ResponseSource.java new file mode 100644 index 0000000..d1eaed4 --- /dev/null +++ b/src/com/koushikdutta/async/http/libcore/ResponseSource.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.koushikdutta.async.http.libcore; + +enum ResponseSource { + + /** + * Return the response from the cache immediately. + */ + CACHE, + + /** + * Make a conditional request to the host, returning the cache response if + * the cache is valid and the network response otherwise. + */ + CONDITIONAL_CACHE, + + /** + * Return the response from the network. + */ + NETWORK; + + public boolean requiresConnection() { + return this == CONDITIONAL_CACHE || this == NETWORK; + } +} diff --git a/src/com/koushikdutta/async/http/transform/ChunkedTransformer.java b/src/com/koushikdutta/async/http/transform/ChunkedTransformer.java new file mode 100644 index 0000000..2997af2 --- /dev/null +++ b/src/com/koushikdutta/async/http/transform/ChunkedTransformer.java @@ -0,0 +1,118 @@ +package com.koushikdutta.async.http.transform; + +import junit.framework.Assert; + +import com.koushikdutta.async.ByteBufferList; +import com.koushikdutta.async.DataEmitter; +import com.koushikdutta.async.DataTransformerBase; +import com.koushikdutta.async.Util; +import com.koushikdutta.async.callback.CompletedCallback; + +public abstract class ChunkedTransformer extends DataTransformerBase implements CompletedCallback { + private int mChunkLength = 0; + private int mChunkLengthRemaining = 0; + private State mState = State.CHUNK_LEN; + + private static enum State { + CHUNK_LEN, + CHUNK_LEN_CR, + CHUNK_LEN_CRLF, + CHUNK, + CHUNK_CR, + CHUNK_CRLF, + COMPLETE + } + + private boolean checkByte(char b, char value) { + if (b != value) { + onException(new Exception(value + " was expeceted, got " + (char)b)); + return false; + } + return true; + } + + private boolean checkLF(char b) { + return checkByte(b, '\n'); + } + + private boolean checkCR(char b) { + return checkByte(b, '\r'); + } + + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + try { + while (bb.remaining() > 0) { + switch (mState) { + case CHUNK_LEN: + char c = bb.getByteChar(); + if (c == '\r') { + mState = State.CHUNK_LEN_CR; + } + else { + mChunkLength *= 16; + if (c >= 'a' && c <= 'f') + mChunkLength += (c - 'a' + 10); + else if (c >= '0' && c <= '9') + mChunkLength += c - '0'; + else if (c >= 'A' && c <= 'F') + mChunkLength += (c - 'A' + 10); + else { + onException(new Exception("invalid chunk length: " + c)); + return; + } + } + mChunkLengthRemaining = mChunkLength; + break; + case CHUNK_LEN_CR: + if (!checkLF(bb.getByteChar())) + return; + mState = State.CHUNK; + break; + case CHUNK: + int remaining = bb.remaining(); + int reading = Math.min(mChunkLengthRemaining, remaining); + mChunkLengthRemaining -= reading; + if (mChunkLengthRemaining == 0) { + mState = State.CHUNK_CR; + } + if (reading == 0) + break; + ByteBufferList chunk = bb.get(reading); + int newRemaining = bb.remaining(); + Assert.assertEquals(remaining, chunk.remaining() + bb.remaining()); + Assert.assertEquals(reading, chunk.remaining()); + Util.emitAllData(this, chunk); + Assert.assertEquals(newRemaining, bb.remaining()); + break; + case CHUNK_CR: + if (!checkCR(bb.getByteChar())) + return; + mState = State.CHUNK_CRLF; + break; + case CHUNK_CRLF: + if (!checkLF(bb.getByteChar())) + return; + if (mChunkLength > 0) { + mState = State.CHUNK_LEN; + + } + else { + mState = State.COMPLETE; + onCompleted(null); + } + mChunkLength = 0; + break; + case COMPLETE: + Exception fail = new Exception("Continued receiving data after chunk complete"); + onException(fail); + onCompleted(fail); + return; + } + } + } + catch (Exception ex) { + onException(ex); + } + } +} diff --git a/src/com/koushikdutta/async/http/transform/GZIPTransformer.java b/src/com/koushikdutta/async/http/transform/GZIPTransformer.java new file mode 100644 index 0000000..d07c808 --- /dev/null +++ b/src/com/koushikdutta/async/http/transform/GZIPTransformer.java @@ -0,0 +1,133 @@ +package com.koushikdutta.async.http.transform; + +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.zip.CRC32; +import java.util.zip.GZIPInputStream; +import java.util.zip.Inflater; + +import com.koushikdutta.async.ByteBufferList; +import com.koushikdutta.async.DataEmitter; +import com.koushikdutta.async.DataEmitterStream; +import com.koushikdutta.async.PushParser; +import com.koushikdutta.async.TapCallback; +import com.koushikdutta.async.callback.DataCallback; +import com.koushikdutta.async.http.libcore.Memory; + +public abstract class GZIPTransformer extends InflaterTransformer { + private static final int FCOMMENT = 16; + + private static final int FEXTRA = 4; + + private static final int FHCRC = 2; + + private static final int FNAME = 8; + + + + public GZIPTransformer() { + super(new Inflater(true)); + } + + boolean mNeedsHeader = true; + protected CRC32 crc = new CRC32(); + + public static int unsignedToBytes(byte b) { + return b & 0xFF; + } + + DataEmitterStream mHeaderParser; + @Override + public void onDataAvailable(final DataEmitter emitter, ByteBufferList bb) { + if (mNeedsHeader) { + final PushParser parser = new PushParser(emitter); + parser + .readBuffer(10) + .tap(new TapCallback() { + int flags; + boolean hcrc; + public void tap(byte[] header) { + short magic = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN); + if (magic != (short) GZIPInputStream.GZIP_MAGIC) { + onException(new IOException(String.format("unknown format (magic number %x)", magic))); + return; + } + flags = header[3]; + hcrc = (flags & FHCRC) != 0; + if (hcrc) { + crc.update(header, 0, header.length); + } + if ((flags & FEXTRA) != 0) { + parser + .readBuffer(2) + .tap(new TapCallback() { + public void tap(byte[] header) { + if (hcrc) { + crc.update(header, 0, 2); + } + int length = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN) & 0xffff; + parser + .readBuffer(length) + .tap(new TapCallback() { + public void tap(byte[] buf) { + if (hcrc) { + crc.update(buf, 0, buf.length); + } + next(); + } + }); + } + }); + } + + next(); + } + public void next() { + PushParser parser = new PushParser(emitter); + DataCallback summer = new DataCallback() { + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + if (hcrc) { + while (bb.size() > 0) { + ByteBuffer b = bb.remove(); + crc.update(b.array(), b.arrayOffset() + b.position(), b.remaining()); + } + } + } + }; + if ((flags & FNAME) != 0) { + parser.until((byte)0, summer); + } + if ((flags & FCOMMENT) != 0) { + parser.until((byte)0, summer); + } + if (hcrc) { + parser.readBuffer(2); + } + else { + parser.noop(); + } + parser.tap(new TapCallback() { + public void tap(byte[] header) { + if (header != null) { + short crc16 = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN); + if ((short) crc.getValue() != crc16) { + onException(new IOException("CRC mismatch")); + return; + } + crc.reset(); + } + mNeedsHeader = false; + emitter.setDataCallback(GZIPTransformer.this); + } + }); + } + }); + } + else { + super.onDataAvailable(emitter, bb); + } + } +} diff --git a/src/com/koushikdutta/async/http/transform/InflaterTransformer.java b/src/com/koushikdutta/async/http/transform/InflaterTransformer.java new file mode 100644 index 0000000..610bc16 --- /dev/null +++ b/src/com/koushikdutta/async/http/transform/InflaterTransformer.java @@ -0,0 +1,62 @@ +package com.koushikdutta.async.http.transform; + +import java.nio.ByteBuffer; +import java.util.zip.Inflater; + +import junit.framework.Assert; + +import com.koushikdutta.async.ByteBufferList; +import com.koushikdutta.async.DataEmitter; +import com.koushikdutta.async.DataTransformerBase; +import com.koushikdutta.async.Util; + +public abstract class InflaterTransformer extends DataTransformerBase { + private Inflater mInflater; + + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + try { + ByteBufferList transformed = new ByteBufferList(); + ByteBuffer output = ByteBuffer.allocate(bb.remaining() * 2); + int totalInflated = 0; + int totalRead = 0; + while (bb.size() > 0) { + ByteBuffer b = bb.remove(); + if (b.hasRemaining()) { + totalRead =+ b.remaining(); + mInflater.setInput(b.array(), b.arrayOffset() + b.position(), b.remaining()); + do { + int inflated = mInflater.inflate(output.array(), output.arrayOffset() + output.position(), output.remaining()); + totalInflated += inflated; + output.position(output.position() + inflated); + if (!output.hasRemaining()) { + output.limit(output.position()); + output.position(0); + transformed.add(output); + Assert.assertNotSame(totalRead, 0); + int newSize = output.capacity() * 2; + output = ByteBuffer.allocate(newSize); + } + } + while (!mInflater.needsInput() && !mInflater.finished()); + } + } + output.limit(output.position()); + output.position(0); + transformed.add(output); + + Util.emitAllData(this, transformed); + } + catch (Exception ex) { + onException(ex); + } + } + + public InflaterTransformer() { + this(new Inflater()); + } + + public InflaterTransformer(Inflater inflater) { + mInflater = inflater; + } +} diff --git a/src/com/koushikdutta/async/stream/OutputStreamDataCallback.java b/src/com/koushikdutta/async/stream/OutputStreamDataCallback.java new file mode 100644 index 0000000..d6f5747 --- /dev/null +++ b/src/com/koushikdutta/async/stream/OutputStreamDataCallback.java @@ -0,0 +1,44 @@ +package com.koushikdutta.async.stream; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +import com.koushikdutta.async.ByteBufferList; +import com.koushikdutta.async.DataEmitter; +import com.koushikdutta.async.ExceptionCallback; +import com.koushikdutta.async.callback.DataCallback; + +public class OutputStreamDataCallback implements DataCallback, ExceptionCallback { + private OutputStream mOutput; + public OutputStreamDataCallback(OutputStream os) { + mOutput = os; + } + + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + try { + for (ByteBuffer b: bb) { + mOutput.write(b.array(), b.arrayOffset() + b.position(), b.remaining()); + } + } + catch (Exception ex) { + onException(ex); + } + bb.clear(); + } + + public void close() { + try { + mOutput.close(); + } + catch (IOException e) { + onException(e); + } + } + + @Override + public void onException(Exception error) { + error.printStackTrace(); + } +} diff --git a/src/com/koushikdutta/test/TestActivity.java b/src/com/koushikdutta/test/TestActivity.java new file mode 100644 index 0000000..b14d35a --- /dev/null +++ b/src/com/koushikdutta/test/TestActivity.java @@ -0,0 +1,313 @@ +package com.koushikdutta.test; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.util.Enumeration; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.os.Bundle; +import android.os.StrictMode; +import android.os.StrictMode.ThreadPolicy; +import android.os.StrictMode.ThreadPolicy.Builder; +import android.util.Log; + +import com.koushikdutta.async.AsyncServer; +import com.koushikdutta.async.AsyncSocket; +import com.koushikdutta.async.BufferedDataSink; +import com.koushikdutta.async.ByteBufferList; +import com.koushikdutta.async.DataEmitter; +import com.koushikdutta.async.PushParser; +import com.koushikdutta.async.TapCallback; +import com.koushikdutta.async.callback.ConnectCallback; +import com.koushikdutta.async.callback.ListenCallback; +import com.koushikdutta.async.callback.CompletedCallback; +import com.koushikdutta.async.callback.DataCallback; +import com.koushikdutta.async.http.AsyncHttpClient; +import com.koushikdutta.async.http.AsyncHttpClient.StringCallback; +import com.koushikdutta.async.http.AsyncHttpGet; +import com.koushikdutta.async.http.AsyncHttpResponse; +import com.koushikdutta.async.http.callback.HttpConnectCallback; + +@SuppressLint("NewApi") +public class TestActivity extends Activity { + ByteBuffer addBytes(ByteBuffer o, byte[] bytes) { + if (o.remaining() < bytes.length) { + ByteBuffer n = ByteBuffer.allocate(o.capacity() * 2 + bytes.length); + n.mark(); + o.limit(o.position()); + o.reset(); + n.put(o); + o = n; + } + o.put(bytes); + return o; + } + + static final String TAG = "TEST"; + public String getLocalIpAddress() { + try { + for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { + NetworkInterface intf = en.nextElement(); + if (intf.getDisplayName().contains("p2p")) + continue; + for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) { + InetAddress inetAddress = enumIpAddr.nextElement(); + if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) { + return inetAddress.getHostAddress(); + } + } + } + } catch (SocketException ex) { + Log.e(TAG, ex.toString()); + } + return null; + } + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + +// try { +// ByteArrayOutputStream bout = new ByteArrayOutputStream(); +// DataOutputStream os = new DataOutputStream(new GZIPOutputStream(bout)); +// os.write("hello asidj aiosjd oiasjd oiasj doas doasj doijq35r90u83qasoidj oaisjdoi asdiohas ihds ".getBytes()); +// os.flush(); +// os.close(); +// byte[] bytes = bout.toByteArray(); +// +// GZIPTransformer t = new GZIPTransformer() { +// @Override +// public void onException(Exception error) { +// error.printStackTrace(); +// } +// }; +// +// t.setDataCallback(new DataCallback() { +// @Override +// public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { +// bb.spewString(); +// bb.clear(); +// } +// }); +// +// ByteBuffer b = ByteBuffer.wrap(bytes); +// ByteBufferList bb = new ByteBufferList(); +// bb.add(b); +// DataEmitter dummy = new DataEmitter() { +// DataCallback cb; +// @Override +// public void setDataCallback(DataCallback callback) { +// cb = callback; +// } +// +// @Override +// public boolean isChunked() { +// return false; +// } +// +// @Override +// public DataCallback getDataCallback() { +// return cb; +// } +// }; +// dummy.setDataCallback(t); +// Util.emitAllData(dummy, bb); +// System.out.println("done"); +// } +// catch (Exception ex) { +// ex.printStackTrace(); +// } +// +// if (true) +// return; + + ThreadPolicy.Builder b = new Builder(); + StrictMode.setThreadPolicy(b.permitAll().build()); + + + final String host = "builder.clockworkmod.com"; + final int port = 443; + + final String shit = "GET / HTTP/1.1\n" + + "User-Agent: curl/7.25.0 (x86_64-apple-darwin11.3.0) libcurl/7.25.0 OpenSSL/1.0.1c zlib/1.2.7 libidn/1.22\n" + + String.format("Host: %s\n", host) + + "Accept: */*\n" + "\n\n"; + +// final AsyncServer server = new AsyncServer(); + try { +// server.initialize(); +// +// new Thread() { +// public void run() { +// server.run(); +// }; +// }.start(); + + + /* + // sending datagram to localhost that is unreceived causes the nio selector to spin... + // I assume because the kernel does some hackery with piping localhost traffic. + // use an explicit ip address of a network interface. + final AsyncSocket dg = server.connectDatagram(new InetSocketAddress(getLocalIpAddress(), 50000)); + dg.setDataCallback(new DataCallback() { + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + dg.write(bb); + System.out.println("pong"); + } + }); + + new Thread() { + public void run() { + try { + final DatagramSocket s = new DatagramSocket(50000); +// BufferedDataSink sink = new BufferedDataSink(dg); + dg.write(ByteBuffer.wrap(new byte[1000])); + + DatagramPacket p = new DatagramPacket(new byte[2000], 2000); + while (true) { + s.receive(p); + System.out.println("ping"); + Thread.sleep(5000); + s.send(p); + // s.send(p); + } + } + catch (Exception e) { + e.printStackTrace(); + } + } + }.start(); + + if (true) + return; + */ + + AsyncHttpClient.download("http://www.appleapologist.com", new StringCallback() { + @Override + public void onCompleted(Exception e, String result) { + if (e != null) { + e.printStackTrace(); + } + System.out.println(result); + } + }); + + + if (true) + return; + + for (int i = 0; i < 4; i++) { +// AsyncHttpGet get = new AsyncHttpGet("https://soighoisdfoihsiohsdf.com"); +// AsyncHttpGet get = new AsyncHttpGet("http://www.cnn.com"); + AsyncHttpGet get = new AsyncHttpGet("https://builder.clockworkmod.com"); + AsyncHttpClient.connect(get, new HttpConnectCallback() { + @Override + public void onConnectCompleted(Exception ex, AsyncHttpResponse response) { + if (ex != null) { + ex.printStackTrace(); + return; + } + response.setDataCallback(new DataCallback() { + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + System.out.println(bb.remaining()); + bb.clear(); + } + }); + response.setCompletedCallback(new CompletedCallback() { + @Override + public void onCompleted(Exception ex) { + System.out.println("done!"); + if (ex != null) { + System.out.println("Errors:"); + ex.printStackTrace(); + } + } + }); + } + }); + } + + if (true) + return; + + AsyncServer.getDefault().listen(InetAddress.getLocalHost(), 50001, new ListenCallback() { + @Override + public void onAccepted(final AsyncSocket handler) { + final DataCallback dh = new DataCallback() { + @Override + public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + bb.clear(); + System.out.println(bb.remaining()); + } + }; + + final PushParser pp = new PushParser(handler); + pp + .readBuffer(100000) + .tap(new TapCallback() { + public void tap(byte[] buffer) { + System.out.println(buffer.length); + + pp + .readBuffer(15000) + .tap(new TapCallback() { + public void tap(byte[] buffer) { + System.out.println(buffer.length); + + handler.setDataCallback(dh); + } + }); + + } + }); + } + }); + + AsyncServer.getDefault().connectSocket(new InetSocketAddress(InetAddress.getLocalHost(), 50001), new ConnectCallback() { + @Override + public void onConnectCompleted(Exception ex, final AsyncSocket socket) { + if (ex != null) { + System.out.println("connect fail"); + return; + } + + new Thread() { + @Override + public void run() { + try { + BufferedDataSink ds = new BufferedDataSink(socket); + ByteBuffer b = ByteBuffer.allocate(30000); + ds.write(b); + Thread.sleep(1000); + b = ByteBuffer.allocate(30000); + ds.write(b); + Thread.sleep(1000); + b = ByteBuffer.allocate(30000); + ds.write(b); + Thread.sleep(1000); + b = ByteBuffer.allocate(30000); + ds.write(b); + } + catch (InterruptedException e) { + e.printStackTrace(); + } + } + }.start(); + } + }); + + + } + catch (Exception e) { + e.printStackTrace(); + } + } +}
\ No newline at end of file |