aboutsummaryrefslogtreecommitdiffstats
path: root/AndroidAsync/src/com/koushikdutta/async
diff options
context:
space:
mode:
authorKoushik Dutta <koushd@gmail.com>2014-07-23 01:06:25 -0700
committerKoushik Dutta <koushd@gmail.com>2014-07-23 01:06:25 -0700
commit1b08bd6375fca37e5bd41169c2e80b55f0c1f0b3 (patch)
tree9e3f10e5bd69f0c63ae2a241079430e88b21abb6 /AndroidAsync/src/com/koushikdutta/async
parent11b629da6c967fe6b3c588c0d77dbc0fdc53b4c5 (diff)
downloadAndroidAsync-1b08bd6375fca37e5bd41169c2e80b55f0c1f0b3.tar.gz
AndroidAsync-1b08bd6375fca37e5bd41169c2e80b55f0c1f0b3.tar.bz2
AndroidAsync-1b08bd6375fca37e5bd41169c2e80b55f0c1f0b3.zip
All libcore usage hidden. Fix obscure buffering/replay bugs in SSL.
Diffstat (limited to 'AndroidAsync/src/com/koushikdutta/async')
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java33
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/AsyncServer.java7
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/DataEmitterBase.java3
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/FilteredDataEmitter.java6
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/Util.java1
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java19
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClientMiddleware.java55
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java40
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponse.java8
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponseImpl.java67
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLSocketMiddleware.java111
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/AsyncSocketMiddleware.java17
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/Headers.java111
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/HttpUtil.java30
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/Multimap.java79
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/Protocol.java89
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/SimpleMiddleware.java7
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/WebSocketImpl.java9
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/body/MultipartFormDataBody.java17
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/body/Part.java15
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/cache/CacheUtil.java35
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/cache/RawHeaders.java2
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/cache/ResponseCacheMiddleware.java27
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/callback/HeadersCallback.java10
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/filter/ContentLengthFilter.java2
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServer.java82
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerRequest.java4
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerRequestImpl.java32
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponse.java7
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java55
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/server/AsyncProxyServer.java81
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/spdy/AsyncSpdyConnection.java8
-rw-r--r--AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java2
33 files changed, 695 insertions, 376 deletions
diff --git a/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java b/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java
index 7f60999..84d7da1 100644
--- a/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java
+++ b/AndroidAsync/src/com/koushikdutta/async/AsyncSSLSocketWrapper.java
@@ -115,6 +115,10 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket
}
}
+ boolean mEnded;
+ Exception mEndException;
+ final ByteBufferList transformed = new ByteBufferList();
+
private AsyncSSLSocketWrapper(AsyncSocket socket,
String host, int port,
SSLEngine sslEngine,
@@ -140,10 +144,20 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket
// SSL needs buffering of data written during handshake.
// aka exhcange.setDatacallback
mEmitter = new BufferedDataEmitter(socket);
+ mEmitter.setEndCallback(new CompletedCallback() {
+ @Override
+ public void onCompleted(Exception ex) {
+ if (mEnded)
+ return;
+ mEnded = true;
+ mEndException = ex;
+ if (!transformed.hasRemaining() && mEndCallback != null)
+ mEndCallback.onCompleted(ex);
+ }
+ });
final Allocator allocator = new Allocator();
allocator.setMinAlloc(8192);
- final ByteBufferList transformed = new ByteBufferList();
mEmitter.setDataCallback(new DataCallback() {
@Override
public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
@@ -195,7 +209,7 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket
}
}
- Util.emitAllData(AsyncSSLSocketWrapper.this, transformed);
+ AsyncSSLSocketWrapper.this.onDataAvailable();
}
catch (SSLException ex) {
ex.printStackTrace();
@@ -208,6 +222,14 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket
});
}
+ public void onDataAvailable() {
+ Util.emitAllData(this, transformed);
+
+ if (mEnded && !transformed.hasRemaining())
+ mEndCallback.onCompleted(mEndException);
+ }
+
+
@Override
public SSLEngine getSSLEngine() {
return engine;
@@ -432,14 +454,15 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket
return mSocket.getClosedCallback();
}
+ CompletedCallback mEndCallback;
@Override
public void setEndCallback(CompletedCallback callback) {
- mSocket.setEndCallback(callback);
+ mEndCallback = callback;
}
@Override
public CompletedCallback getEndCallback() {
- return mSocket.getEndCallback();
+ return mEndCallback;
}
@Override
@@ -449,6 +472,8 @@ public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket
@Override
public void resume() {
+ onDataAvailable();
+ mEmitter.onDataAvailable();
mSocket.resume();
}
diff --git a/AndroidAsync/src/com/koushikdutta/async/AsyncServer.java b/AndroidAsync/src/com/koushikdutta/async/AsyncServer.java
index a1c7a47..b399362 100644
--- a/AndroidAsync/src/com/koushikdutta/async/AsyncServer.java
+++ b/AndroidAsync/src/com/koushikdutta/async/AsyncServer.java
@@ -126,7 +126,12 @@ public class AsyncServer {
synchronousWorkers.execute(new Runnable() {
@Override
public void run() {
- selector.wakeupOnce();
+ try {
+ selector.wakeupOnce();
+ }
+ catch (Exception e) {
+ Log.i(LOGTAG, "Selector shit the bed.");
+ }
}
});
}
diff --git a/AndroidAsync/src/com/koushikdutta/async/DataEmitterBase.java b/AndroidAsync/src/com/koushikdutta/async/DataEmitterBase.java
index 90e05c7..1c05617 100644
--- a/AndroidAsync/src/com/koushikdutta/async/DataEmitterBase.java
+++ b/AndroidAsync/src/com/koushikdutta/async/DataEmitterBase.java
@@ -8,9 +8,6 @@ import com.koushikdutta.async.callback.DataCallback;
*/
public abstract class DataEmitterBase implements DataEmitter {
private boolean ended;
- protected void resetEnded() {
- ended = false;
- }
protected void report(Exception e) {
if (ended)
return;
diff --git a/AndroidAsync/src/com/koushikdutta/async/FilteredDataEmitter.java b/AndroidAsync/src/com/koushikdutta/async/FilteredDataEmitter.java
index 71bcfa9..6c59def 100644
--- a/AndroidAsync/src/com/koushikdutta/async/FilteredDataEmitter.java
+++ b/AndroidAsync/src/com/koushikdutta/async/FilteredDataEmitter.java
@@ -5,7 +5,7 @@ import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.wrapper.DataEmitterWrapper;
public class FilteredDataEmitter extends DataEmitterBase implements DataEmitter, DataCallback, DataEmitterWrapper, DataTrackingEmitter {
- DataEmitter mEmitter;
+ private DataEmitter mEmitter;
@Override
public DataEmitter getDataEmitter() {
return mEmitter;
@@ -41,8 +41,8 @@ public class FilteredDataEmitter extends DataEmitterBase implements DataEmitter,
this.tracker = tracker;
}
- DataTracker tracker;
- int totalRead;
+ private DataTracker tracker;
+ private int totalRead;
@Override
public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
if (bb != null)
diff --git a/AndroidAsync/src/com/koushikdutta/async/Util.java b/AndroidAsync/src/com/koushikdutta/async/Util.java
index 18c3736..c8df5b7 100644
--- a/AndroidAsync/src/com/koushikdutta/async/Util.java
+++ b/AndroidAsync/src/com/koushikdutta/async/Util.java
@@ -131,7 +131,6 @@ public class Util {
sink.setWriteableCallback(new WritableCallback() {
@Override
public void onWriteable() {
- dataCallback.onDataAvailable(emitter, new ByteBufferList());
emitter.resume();
}
});
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java
index b6654a9..313a9fa 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClient.java
@@ -20,7 +20,6 @@ import com.koushikdutta.async.future.SimpleFuture;
import com.koushikdutta.async.http.AsyncHttpClientMiddleware.OnRequestCompleteData;
import com.koushikdutta.async.http.callback.HttpConnectCallback;
import com.koushikdutta.async.http.callback.RequestCallback;
-import com.koushikdutta.async.http.cache.RawHeaders;
import com.koushikdutta.async.parser.AsyncParser;
import com.koushikdutta.async.parser.ByteBufferListParser;
import com.koushikdutta.async.parser.JSONArrayParser;
@@ -209,6 +208,12 @@ public class AsyncHttpClient {
request.logd("Executing request.");
+ synchronized (mMiddleware) {
+ for (AsyncHttpClientMiddleware middleware: mMiddleware) {
+ middleware.onRequest(data);
+ }
+ }
+
// flow:
// 1) set a connect timeout
// 2) wait for connect
@@ -252,12 +257,6 @@ public class AsyncHttpClient {
mServer.removeAllCallbacks(cancel.scheduled);
data.socket = socket;
- synchronized (mMiddleware) {
- for (AsyncHttpClientMiddleware middleware: mMiddleware) {
- middleware.onSocket(data);
- }
- }
-
cancel.socket = socket;
if (ex != null) {
@@ -294,7 +293,7 @@ public class AsyncHttpClient {
super.setDataEmitter(data.bodyEmitter);
- RawHeaders headers = mHeaders;
+ Headers headers = mHeaders;
int responseCode = code();
if ((responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == 307) && request.getFollowRedirect()) {
String location = headers.get("Location");
@@ -327,7 +326,7 @@ public class AsyncHttpClient {
return;
}
- request.logv("Final (post cache response) headers:\n" + mHeaders.toHeaderString());
+ request.logv("Final (post cache response) headers:\n" + toString());
// at this point the headers are done being modified
reportConnectedCompleted(cancel, null, this, request, callback);
@@ -343,7 +342,7 @@ public class AsyncHttpClient {
mServer.removeAllCallbacks(cancel.scheduled);
// allow the middleware to massage the headers before the body is decoded
- request.logv("Received headers:\n" + mHeaders.toHeaderString());
+ request.logv("Received headers:\n" + toString());
data.headers = mHeaders;
synchronized (mMiddleware) {
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClientMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClientMiddleware.java
index 2404482..c706e53 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClientMiddleware.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpClientMiddleware.java
@@ -5,27 +5,31 @@ import com.koushikdutta.async.DataEmitter;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.ConnectCallback;
import com.koushikdutta.async.future.Cancellable;
-import com.koushikdutta.async.http.cache.RawHeaders;
import com.koushikdutta.async.util.UntypedHashtable;
+/**
+ * AsyncHttpClientMiddleware is used by AsyncHttpClient to
+ * inspect, manipulate, and handle http requests.
+ */
public interface AsyncHttpClientMiddleware {
- public static class GetSocketData {
+ public static class OnRequestData {
public UntypedHashtable state = new UntypedHashtable();
public AsyncHttpRequest request;
+ }
+
+ public static class GetSocketData extends OnRequestData {
public ConnectCallback connectCallback;
public Cancellable socketCancellable;
- }
-
- public static class OnSocketData extends GetSocketData {
- public AsyncSocket socket;
+ public String protocol;
}
- public static class SendHeaderData extends OnSocketData {
- CompletedCallback sendHeadersCallback;
+ public static class SendHeaderData extends GetSocketData {
+ public AsyncSocket socket;
+ public CompletedCallback sendHeadersCallback;
}
public static class OnHeadersReceivedData extends SendHeaderData {
- public RawHeaders headers;
+ public Headers headers;
}
public static class OnBodyData extends OnHeadersReceivedData {
@@ -37,10 +41,41 @@ public interface AsyncHttpClientMiddleware {
public Exception exception;
}
+ /**
+ * Called immediately upon request execution
+ * @param data
+ */
+ public void onRequest(OnRequestData data);
+
+ /**
+ * Called to retrieve the socket that will fulfill this request
+ * @param data
+ * @return
+ */
public Cancellable getSocket(GetSocketData data);
- public void onSocket(OnSocketData data);
+
+ /**
+ * Called before the headers are sent via the socket
+ * @param data
+ * @return
+ */
public boolean sendHeaders(SendHeaderData data);
+
+ /**
+ * Called once the headers have been received via the socket
+ * @param data
+ */
public void onHeadersReceived(OnHeadersReceivedData data);
+
+ /**
+ * Called before the body is decoded
+ * @param data
+ */
public void onBodyDecoder(OnBodyData data);
+
+ /**
+ * Called once the request is complete
+ * @param data
+ */
public void onRequestComplete(OnRequestCompleteData data);
}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java
index fa50c20..95dd21d 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpRequest.java
@@ -5,7 +5,6 @@ import android.util.Log;
import com.koushikdutta.async.AsyncSSLException;
import com.koushikdutta.async.http.body.AsyncHttpRequestBody;
-import com.koushikdutta.async.http.cache.RawHeaders;
import org.apache.http.Header;
import org.apache.http.HeaderIterator;
@@ -35,7 +34,7 @@ public class AsyncHttpRequest {
public String getMethod() {
return mMethod;
}
-
+
@Override
public String toString() {
String path = AsyncHttpRequest.this.getUri().getEncodedPath();
@@ -88,7 +87,6 @@ public class AsyncHttpRequest {
if (getClass() != AsyncHttpRequest.class)
throw new UnsupportedOperationException("can't change method on a subclass of AsyncHttpRequest");
mMethod = method;
- mRawHeaders.setStatusLine(getRequestLine().toString());
return this;
}
@@ -96,7 +94,7 @@ public class AsyncHttpRequest {
this(uri, method, null);
}
- public static void setDefaultHeaders(RawHeaders ret, Uri uri) {
+ public static void setDefaultHeaders(Headers ret, Uri uri) {
if (uri != null) {
String host = uri.getHost();
if (uri.getPort() != -1)
@@ -110,17 +108,16 @@ public class AsyncHttpRequest {
ret.set("Accept", "*/*");
}
- public AsyncHttpRequest(Uri uri, String method, RawHeaders headers) {
+ public AsyncHttpRequest(Uri uri, String method, Headers headers) {
assert uri != null;
mMethod = method;
this.uri = uri;
if (headers == null)
- mRawHeaders = new RawHeaders();
+ mRawHeaders = new Headers();
else
mRawHeaders = headers;
if (headers == null)
setDefaultHeaders(mRawHeaders, uri);
- mRawHeaders.setStatusLine(getRequestLine().toString());
}
Uri uri;
@@ -128,16 +125,12 @@ public class AsyncHttpRequest {
return uri;
}
- private RawHeaders mRawHeaders = new RawHeaders();
+ private Headers mRawHeaders = new Headers();
- public RawHeaders getHeaders() {
+ public Headers getHeaders() {
return mRawHeaders;
}
- public String getRequestString() {
- return mRawHeaders.toHeaderString();
- }
-
private boolean mFollowRedirect = true;
public boolean getFollowRedirect() {
return mFollowRedirect;
@@ -208,13 +201,7 @@ public class AsyncHttpRequest {
@Override
public Header[] getAllHeaders() {
- Header[] ret = new Header[request.getHeaders().length()];
- for (int i = 0; i < ret.length; i++) {
- String name = request.getHeaders().getFieldName(i);
- String value = request.getHeaders().getValue(i);
- ret[i] = new BasicHeader(name, value);
- }
- return ret;
+ return request.getHeaders().toHeaderArray();
}
@Override
@@ -227,7 +214,7 @@ public class AsyncHttpRequest {
@Override
public Header[] getHeaders(String name) {
- Map<String, List<String>> map = request.getHeaders().toMultimap();
+ Map<String, List<String>> map = request.getHeaders().getMultiMap();
List<String> vals = map.get(name);
if (vals == null)
return new Header[0];
@@ -270,12 +257,12 @@ public class AsyncHttpRequest {
@Override
public void removeHeader(Header header) {
- request.getHeaders().removeAll(header.getName());
+ request.getHeaders().remove(header.getName());
}
@Override
public void removeHeaders(String name) {
- request.getHeaders().removeAll(name);
+ request.getHeaders().remove(name);
}
@Override
@@ -334,6 +321,13 @@ public class AsyncHttpRequest {
return proxyPort;
}
+ @Override
+ public String toString() {
+ if (mRawHeaders == null)
+ return super.toString();
+ return mRawHeaders.toPrefixString(uri.toString());
+ }
+
public void setLogging(String tag, int level) {
LOGTAG = tag;
logLevel = level;
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponse.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponse.java
index f44ba8e..e771395 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponse.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponse.java
@@ -3,15 +3,15 @@ package com.koushikdutta.async.http;
import com.koushikdutta.async.AsyncSocket;
import com.koushikdutta.async.DataEmitter;
import com.koushikdutta.async.callback.CompletedCallback;
-import com.koushikdutta.async.http.cache.RawHeaders;
public interface AsyncHttpResponse extends DataEmitter {
- public void setEndCallback(CompletedCallback handler);
public String protocol();
public String message();
public int code();
- public RawHeaders headers();
- public void end();
+ public AsyncHttpResponse protocol(String protocol);
+ public AsyncHttpResponse message(String message);
+ public AsyncHttpResponse code(int code);
+ public Headers headers();
public AsyncSocket detachSocket();
public AsyncHttpRequest getRequest();
}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponseImpl.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponseImpl.java
index e1b2399..fca0473 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponseImpl.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncHttpResponseImpl.java
@@ -9,12 +9,13 @@ import com.koushikdutta.async.FilteredDataEmitter;
import com.koushikdutta.async.LineEmitter;
import com.koushikdutta.async.LineEmitter.StringCallback;
import com.koushikdutta.async.NullDataCallback;
+import com.koushikdutta.async.Util;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.WritableCallback;
import com.koushikdutta.async.http.body.AsyncHttpRequestBody;
import com.koushikdutta.async.http.filter.ChunkedOutputFilter;
-import com.koushikdutta.async.http.cache.RawHeaders;
+import java.io.IOException;
import java.nio.charset.Charset;
abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements AsyncSocket, AsyncHttpResponse {
@@ -60,9 +61,10 @@ abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements Asyn
}
});
- String rs = mRequest.getRequestString();
+ String rl = mRequest.getRequestLine().toString();
+ String rs = mRequest.getHeaders().toPrefixString(rl);
mRequest.logv("\n" + rs);
- com.koushikdutta.async.Util.writeAll(exchange, rs.getBytes(), new CompletedCallback() {
+ Util.writeAll(exchange, rs.getBytes(), new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
if (mWriter != null) {
@@ -72,8 +74,7 @@ abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements Asyn
onRequestCompleted(ex);
}
});
- }
- else {
+ } else {
onRequestCompleted(null);
}
}
@@ -102,17 +103,25 @@ abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements Asyn
protected abstract void onHeadersReceived();
StringCallback mHeaderCallback = new StringCallback() {
- private RawHeaders mRawHeaders = new RawHeaders();
+ private Headers mRawHeaders = new Headers();
+ private String statusLine;
@Override
public void onStringAvailable(String s) {
try {
- if (mRawHeaders.getStatusLine() == null) {
- mRawHeaders.setStatusLine(s);
+ if (statusLine == null) {
+ statusLine = s;
}
else if (!"\r".equals(s)) {
mRawHeaders.addLine(s);
}
else {
+ String[] parts = statusLine.split(" ", 3);
+ if (parts.length != 3)
+ throw new Exception(new IOException("Not HTTP"));
+
+ protocol = parts[0];
+ code = Integer.parseInt(parts[1]);
+ message = parts[2];
mHeaders = mRawHeaders;
onHeadersReceived();
// socket may get detached after headers (websocket)
@@ -125,7 +134,7 @@ abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements Asyn
emitter = HttpUtil.EndEmitter.create(getServer(), null);
}
else {
- emitter = HttpUtil.getBodyDecoder(mSocket, mRawHeaders, false);
+ emitter = HttpUtil.getBodyDecoder(mSocket, Protocol.get(protocol), mHeaders, false);
}
setDataEmitter(emitter);
}
@@ -158,7 +167,7 @@ abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements Asyn
private AsyncHttpRequest mRequest;
private AsyncSocket mSocket;
- protected RawHeaders mHeaders;
+ protected Headers mHeaders;
public AsyncHttpResponseImpl(AsyncHttpRequest request) {
mRequest = request;
}
@@ -166,23 +175,51 @@ abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements Asyn
boolean mCompleted = false;
@Override
- public RawHeaders headers() {
+ public Headers headers() {
return mHeaders;
}
+ int code;
@Override
public int code() {
- return headers().getResponseCode();
+ return code;
+ }
+
+ @Override
+ public AsyncHttpResponse code(int code) {
+ this.code = code;
+ return this;
}
@Override
+ public AsyncHttpResponse protocol(String protocol) {
+ this.protocol = protocol;
+ return this;
+ }
+
+ @Override
+ public AsyncHttpResponse message(String message) {
+ this.message = message;
+ return this;
+ }
+
+ String protocol;
+ @Override
public String protocol() {
- return "HTTP/1." + headers().getHttpMinorVersion();
+ return protocol;
}
+ String message;
@Override
public String message() {
- return headers().getResponseMessage();
+ return message;
+ }
+
+ @Override
+ public String toString() {
+ if (mHeaders == null)
+ return super.toString();
+ return mHeaders.toPrefixString(protocol + " " + code + " " + message);
}
private boolean mFirstWrite = true;
@@ -240,7 +277,7 @@ abstract class AsyncHttpResponseImpl extends FilteredDataEmitter implements Asyn
@Override
public String charset() {
- Multimap mm = Multimap.parseHeader(headers(), "Content-Type");
+ Multimap mm = Multimap.parseSemicolonDelimited(headers().get("Content-Type"));
String cs;
if (mm != null && null != (cs = mm.getString("charset")) && Charset.isSupported(cs)) {
return cs;
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLSocketMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLSocketMiddleware.java
index 4ec7d80..59fa0f7 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLSocketMiddleware.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncSSLSocketMiddleware.java
@@ -10,7 +10,6 @@ import com.koushikdutta.async.LineEmitter;
import com.koushikdutta.async.Util;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.ConnectCallback;
-import com.koushikdutta.async.http.cache.RawHeaders;
import java.io.IOException;
import java.util.ArrayList;
@@ -86,75 +85,69 @@ public class AsyncSSLSocketMiddleware extends AsyncSocketMiddleware {
}
@Override
- protected ConnectCallback wrapCallback(final ConnectCallback callback, final Uri uri, final int port, final boolean proxied) {
+ protected ConnectCallback wrapCallback(GetSocketData data, final Uri uri, final int port, final boolean proxied, final ConnectCallback callback) {
return new ConnectCallback() {
@Override
public void onConnectCompleted(Exception ex, final AsyncSocket socket) {
- if (ex == null) {
- if (!proxied) {
- tryHandshake(callback, socket, uri, port);
- }
- else {
- // this SSL connection is proxied, must issue a CONNECT request to the proxy server
- // http://stackoverflow.com/a/6594880/704837
- RawHeaders connect = new RawHeaders();
- connect.setStatusLine(String.format("CONNECT %s:%s HTTP/1.1", uri.getHost(), port));
- Util.writeAll(socket, connect.toHeaderString().getBytes(), new CompletedCallback() {
+ if (ex != null) {
+ callback.onConnectCompleted(ex, socket);
+ return;
+ }
+
+ if (!proxied) {
+ tryHandshake(callback, socket, uri, port);
+ return;
+ }
+
+ // this SSL connection is proxied, must issue a CONNECT request to the proxy server
+ // http://stackoverflow.com/a/6594880/704837
+ String connect = String.format("CONNECT %s:%s HTTP/1.1\r\n\r\n", uri.getHost(), port);
+ Util.writeAll(socket, connect.getBytes(), new CompletedCallback() {
+ @Override
+ public void onCompleted(Exception ex) {
+ if (ex != null) {
+ callback.onConnectCompleted(ex, socket);
+ return;
+ }
+
+ LineEmitter liner = new LineEmitter();
+ liner.setLineCallback(new LineEmitter.StringCallback() {
+ String statusLine;
@Override
- public void onCompleted(Exception ex) {
- if (ex != null) {
- callback.onConnectCompleted(ex, socket);
- return;
+ public void onStringAvailable(String s) {
+ if (statusLine == null) {
+ statusLine = s;
+ if (statusLine.length() > 128 || !statusLine.contains("200")) {
+ socket.setDataCallback(null);
+ socket.setEndCallback(null);
+ callback.onConnectCompleted(new IOException("non 200 status line"), socket);
+ }
}
-
- LineEmitter liner = new LineEmitter();
- liner.setLineCallback(new LineEmitter.StringCallback() {
- String statusLine;
- @Override
- public void onStringAvailable(String s) {
- if (statusLine == null) {
- statusLine = s;
- if (statusLine.length() > 128 || !statusLine.contains("200")) {
- socket.setDataCallback(null);
- socket.setEndCallback(null);
- callback.onConnectCompleted(new IOException("non 200 status line"), socket);
- }
- }
- else {
- socket.setDataCallback(null);
- socket.setEndCallback(null);
- if (TextUtils.isEmpty(s.trim())) {
- tryHandshake(callback, socket, uri, port);
- }
- else {
- callback.onConnectCompleted(new IOException("unknown second status line"), socket);
- }
- }
+ else {
+ socket.setDataCallback(null);
+ socket.setEndCallback(null);
+ if (TextUtils.isEmpty(s.trim())) {
+ tryHandshake(callback, socket, uri, port);
}
- });
-
- socket.setDataCallback(liner);
-
- socket.setEndCallback(new CompletedCallback() {
- @Override
- public void onCompleted(Exception ex) {
- if (!socket.isOpen() && ex == null)
- ex = new IOException("socket closed before proxy connect response");
- callback.onConnectCompleted(ex, socket);
+ else {
+ callback.onConnectCompleted(new IOException("unknown second status line"), socket);
}
- });
+ }
+ }
+ });
+
+ socket.setDataCallback(liner);
-// AsyncSocket wrapper = socket;
-// if (ex == null)
-// wrapper = new AsyncSSLSocketWrapper(socket, uri.getHost(), port, sslContext, trustManagers, hostnameVerifier, true);
-// callback.onConnectCompleted(ex, wrapper);
+ socket.setEndCallback(new CompletedCallback() {
+ @Override
+ public void onCompleted(Exception ex) {
+ if (!socket.isOpen() && ex == null)
+ ex = new IOException("socket closed before proxy connect response");
+ callback.onConnectCompleted(ex, socket);
}
});
}
- }
- else {
- callback.onConnectCompleted(ex, socket);
- }
+ });
}
};
}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/AsyncSocketMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/AsyncSocketMiddleware.java
index 6e7d692..37ffe5c 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/AsyncSocketMiddleware.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/AsyncSocketMiddleware.java
@@ -52,7 +52,7 @@ public class AsyncSocketMiddleware extends SimpleMiddleware {
protected AsyncHttpClient mClient;
- protected ConnectCallback wrapCallback(ConnectCallback callback, Uri uri, int port, boolean proxied) {
+ protected ConnectCallback wrapCallback(GetSocketData data, Uri uri, int port, boolean proxied, ConnectCallback callback) {
return callback;
}
@@ -170,22 +170,19 @@ public class AsyncSocketMiddleware extends SimpleMiddleware {
if (data.request.getProxyHost() != null) {
unresolvedHost = data.request.getProxyHost();
unresolvedPort = data.request.getProxyPort();
- // set the host and port explicitly for proxied connections
- data.request.getHeaders().setStatusLine(data.request.getProxyRequestLine().toString());
proxied = true;
}
else if (proxyHost != null) {
unresolvedHost = proxyHost;
unresolvedPort = proxyPort;
- // set the host and port explicitly for proxied connections
- data.request.getHeaders().setStatusLine(data.request.getProxyRequestLine().toString());
proxied = true;
}
else {
unresolvedHost = uri.getHost();
unresolvedPort = port;
}
- return mClient.getServer().connectSocket(unresolvedHost, unresolvedPort, wrapCallback(data.connectCallback, uri, port, proxied));
+ return mClient.getServer().connectSocket(unresolvedHost, unresolvedPort,
+ wrapCallback(data, uri, port, proxied, data.connectCallback));
}
// try to connect to everything...
@@ -216,7 +213,8 @@ public class AsyncSocketMiddleware extends SimpleMiddleware {
keepTrying.add(new ContinuationCallback() {
@Override
public void onContinue(Continuation continuation, final CompletedCallback next) throws Exception {
- mClient.getServer().connectSocket(new InetSocketAddress(address, port), wrapCallback(new ConnectCallback() {
+ mClient.getServer().connectSocket(new InetSocketAddress(address, port),
+ wrapCallback(data, uri, port, false, new ConnectCallback() {
@Override
public void onConnectCompleted(Exception ex, AsyncSocket socket) {
if (isDone()) {
@@ -244,7 +242,7 @@ public class AsyncSocketMiddleware extends SimpleMiddleware {
data.connectCallback.onConnectCompleted(ex, socket);
}
}
- }, uri, port, false));
+ }));
}
});
}
@@ -360,7 +358,8 @@ public class AsyncSocketMiddleware extends SimpleMiddleware {
data.socket.close();
return;
}
- if (!HttpUtil.isKeepAlive(data.headers) || !HttpUtil.isKeepAlive(data.request.getHeaders())) {
+ if (!HttpUtil.isKeepAlive(data.response.protocol(), data.headers)
+ || !HttpUtil.isKeepAlive(Protocol.HTTP_1_1, data.request.getHeaders())) {
data.request.logv("closing out socket (not keep alive)");
data.socket.close();
return;
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/Headers.java b/AndroidAsync/src/com/koushikdutta/async/http/Headers.java
index 8a2146b..8fb7025 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/Headers.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/Headers.java
@@ -1,11 +1,28 @@
package com.koushikdutta.async.http;
+
+import android.text.TextUtils;
+
+import com.koushikdutta.async.http.server.AsyncHttpServer;
+
+import org.apache.http.Header;
+import org.apache.http.message.BasicHeader;
+
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Created by koush on 7/21/14.
*/
public class Headers {
+ public Headers() {
+ }
+
+ public Headers(Map<String, List<String>> mm) {
+ map.putAll(mm);
+ }
+
Multimap map = new Multimap();
public Multimap getMultiMap() {
return map;
@@ -29,33 +46,93 @@ public class Headers {
return this;
}
- public List<String> remove(String header) {
- return map.remove(header);
+ public Headers addLine(String line) {
+ if (line != null) {
+ line = line.trim();
+ String[] parts = line.split(":", 2);
+ if (parts.length == 2)
+ add(parts[0].trim(), parts[1].trim());
+ else
+ add(parts[0].trim(), "");
+ }
+ return this;
}
- int responseCode;
- public int getResponseCode() {
- return responseCode;
+ public Headers addAll(String header, List<String> values) {
+ for (String v: values) {
+ add(header, v);
+ }
+ return this;
}
- public void setResponseCode(int responseCode) {
- this.responseCode = responseCode;
+
+ public Headers addAll(Map<String, List<String>> m) {
+ map.putAll(m);
+ return this;
}
- String protocol;
- public String getProtocol() {
- return protocol;
+ public Headers addAll(Headers headers) {
+ map.putAll(headers.map);
+ return this;
+ }
+
+ public List<String> removeAll(String header) {
+ return map.remove(header);
+ }
+
+ public String remove(String header) {
+ List<String> r = removeAll(header);
+ if (r == null || r.size() == 0)
+ return null;
+ return r.get(0);
}
- public void setProtocol(String protocol) {
- this.protocol = protocol;
+
+ public Header[] toHeaderArray() {
+ ArrayList<Header> ret = new ArrayList<Header>();
+ for (String key: map.keySet()) {
+ for (String v: map.get(key)) {
+ ret.add(new BasicHeader(key, v));
+ }
+ }
+ return ret.toArray(new Header[ret.size()]);
+ }
+
+ public StringBuilder toStringBuilder() {
+ StringBuilder result = new StringBuilder(256);
+ for (String key: map.keySet()) {
+ for (String v: map.get(key)) {
+ result.append(key)
+ .append(": ")
+ .append(v)
+ .append("\r\n");
+ }
+ }
+ result.append("\r\n");
+ return result;
}
- String responseMessage;
+ @Override
+ public String toString() {
+ return toStringBuilder().toString();
+ }
- public String getResponseMessage() {
- return responseMessage;
+ public String toPrefixString(String prefix) {
+ return
+ toStringBuilder()
+ .insert(0, prefix + "\r\n")
+ .toString();
}
- public void setResponseMessage(String responseMessage) {
- this.responseMessage = responseMessage;
+ public static Headers parse(String payload) {
+ String[] lines = payload.split("\n");
+
+ Headers headers = new Headers();
+ for (String line: lines) {
+ line = line.trim();
+ if (TextUtils.isEmpty(line))
+ continue;
+
+ headers.addLine(line);
+ }
+ return headers;
}
}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/HttpUtil.java b/AndroidAsync/src/com/koushikdutta/async/http/HttpUtil.java
index bbcdf8f..b7178ee 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/HttpUtil.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/HttpUtil.java
@@ -9,14 +9,13 @@ import com.koushikdutta.async.http.body.JSONObjectBody;
import com.koushikdutta.async.http.body.MultipartFormDataBody;
import com.koushikdutta.async.http.body.StringBody;
import com.koushikdutta.async.http.body.UrlEncodedFormBody;
-import com.koushikdutta.async.http.cache.RawHeaders;
import com.koushikdutta.async.http.filter.ChunkedInputFilter;
import com.koushikdutta.async.http.filter.ContentLengthFilter;
import com.koushikdutta.async.http.filter.GZIPInputFilter;
import com.koushikdutta.async.http.filter.InflaterInputFilter;
public class HttpUtil {
- public static AsyncHttpRequestBody getBody(DataEmitter emitter, CompletedCallback reporter, RawHeaders headers) {
+ public static AsyncHttpRequestBody getBody(DataEmitter emitter, CompletedCallback reporter, Headers headers) {
String contentType = headers.get("Content-Type");
if (contentType != null) {
String[] values = contentType.split(";");
@@ -60,7 +59,7 @@ public class HttpUtil {
}
}
- public static DataEmitter getBodyDecoder(DataEmitter emitter, RawHeaders headers, boolean server) {
+ public static DataEmitter getBodyDecoder(DataEmitter emitter, Protocol protocol, Headers headers, boolean server) {
long _contentLength;
try {
_contentLength = Long.parseLong(headers.get("Content-Length"));
@@ -92,7 +91,7 @@ public class HttpUtil {
emitter = chunker;
}
else {
- if ((server || headers.getStatusLine().contains("HTTP/1.1")) && !"close".equalsIgnoreCase(headers.get("Connection"))) {
+ if ((server || protocol == Protocol.HTTP_1_1) && !"close".equalsIgnoreCase(headers.get("Connection"))) {
// if this is the server, and the client has not indicated a request body, the client is done
EndEmitter ender = EndEmitter.create(emitter.getServer(), null);
ender.setDataEmitter(emitter);
@@ -117,20 +116,23 @@ public class HttpUtil {
return emitter;
}
- public static boolean isKeepAlive(RawHeaders headers) {
- boolean keepAlive;
+ public static boolean isKeepAlive(Protocol protocol, Headers headers) {
+ // connection is always keep alive as this is an http/1.1 client
String connection = headers.get("Connection");
- if (connection != null) {
- keepAlive = "keep-alive".equalsIgnoreCase(connection);
- }
- else {
- keepAlive = headers.getHttpMinorVersion() >= 1;
- }
+ if (connection == null)
+ return protocol == Protocol.HTTP_1_1;
+ return "keep-alive".equalsIgnoreCase(connection);
+ }
- return keepAlive;
+ public static boolean isKeepAlive(String protocol, Headers headers) {
+ // connection is always keep alive as this is an http/1.1 client
+ String connection = headers.get("Connection");
+ if (connection == null)
+ return Protocol.get(protocol) == Protocol.HTTP_1_1;
+ return "keep-alive".equalsIgnoreCase(connection);
}
- public static int contentLength(RawHeaders headers) {
+ public static int contentLength(Headers headers) {
String cl = headers.get("Content-Length");
if (cl == null)
return -1;
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/Multimap.java b/AndroidAsync/src/com/koushikdutta/async/http/Multimap.java
index 5223fc8..913532e 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/Multimap.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/Multimap.java
@@ -2,14 +2,11 @@ package com.koushikdutta.async.http;
import android.net.Uri;
-import com.koushikdutta.async.http.cache.RawHeaders;
-
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import java.net.URLDecoder;
import java.util.ArrayList;
-import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
@@ -43,67 +40,69 @@ public class Multimap extends LinkedHashMap<String, List<String>> implements Ite
put(name, ret);
}
- public Multimap(RawHeaders headers) {
- headers.toMultimap().putAll(this);
- }
-
public Multimap(List<NameValuePair> pairs) {
for (NameValuePair pair: pairs)
add(pair.getName(), pair.getValue());
}
- public static Multimap parseHeader(String header) {
- if (header == null)
- return null;
+ public Multimap(Multimap m) {
+ putAll(m);
+ }
+
+ public interface StringDecoder {
+ public String decode(String s);
+ }
+
+ public static Multimap parse(String value, String delimiter, boolean unquote, StringDecoder decoder) {
Multimap map = new Multimap();
- String[] parts = header.split(";");
+ if (value == null)
+ return map;
+ String[] parts = value.split(delimiter);
for (String part: parts) {
String[] pair = part.split("=", 2);
String key = pair[0].trim();
String v = null;
if (pair.length > 1)
v = pair[1];
- if (v != null && v.endsWith("\"") && v.startsWith("\""))
+ if (unquote && v != null && v.endsWith("\"") && v.startsWith("\""))
v = v.substring(1, v.length() - 1);
+ if (decoder != null) {
+ key = decoder.decode(key);
+ v = decoder.decode(v);
+ }
map.add(key, v);
}
return map;
}
- public static Multimap parseHeader(RawHeaders headers, String header) {
- return parseHeader(headers.get(header));
+ public static Multimap parseSemicolonDelimited(String header) {
+ return parse(header, ";", true, null);
}
- public static Multimap parseQuery(String query) {
- Multimap map = new Multimap();
- String[] pairs = query.split("&");
- for (String p : pairs) {
- String[] pair = p.split("=", 2);
- if (pair.length == 0)
- continue;
- String name = Uri.decode(pair[0]);
- String value = null;
- if (pair.length == 2)
- value = Uri.decode(pair[1]);
- map.add(name, value);
+ public static Multimap parseCommaDelimited(String header) {
+ return parse(header, ",", true, null);
+ }
+
+ private static final StringDecoder QUERY_DECODER = new StringDecoder() {
+ @Override
+ public String decode(String s) {
+ return Uri.decode(s);
}
- return map;
+ };
+
+ public static Multimap parseQuery(String query) {
+ return parse(query, "&", false, QUERY_DECODER);
}
- public static Multimap parseUrlEncoded(String query) {
- Multimap map = new Multimap();
- String[] pairs = query.split("&");
- for (String p : pairs) {
- String[] pair = p.split("=", 2);
- if (pair.length == 0)
- continue;
- String name = URLDecoder.decode(pair[0]);
- String value = null;
- if (pair.length == 2)
- value = URLDecoder.decode(pair[1]);
- map.add(name, value);
+ private static final StringDecoder URL_DECODER = new StringDecoder() {
+ @Override
+ public String decode(String s) {
+ return URLDecoder.decode(s);
}
- return map;
+ };
+
+ public static Multimap parseUrlEncoded(String query) {
+ return parse(query, "&", false, URL_DECODER);
}
@Override
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/Protocol.java b/AndroidAsync/src/com/koushikdutta/async/http/Protocol.java
new file mode 100644
index 0000000..8e5a46c
--- /dev/null
+++ b/AndroidAsync/src/com/koushikdutta/async/http/Protocol.java
@@ -0,0 +1,89 @@
+package com.koushikdutta.async.http;
+
+import java.util.Hashtable;
+
+/**
+ * Protocols that OkHttp implements for <a
+ * href="http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04">NPN</a> and
+ * <a href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg">ALPN</a>
+ * selection.
+ * <p/>
+ * <h3>Protocol vs Scheme</h3>
+ * Despite its name, {@link java.net.URL#getProtocol()} returns the
+ * {@linkplain java.net.URI#getScheme() scheme} (http, https, etc.) of the URL, not
+ * the protocol (http/1.1, spdy/3.1, etc.). OkHttp uses the word <i>protocol</i>
+ * to identify how HTTP messages are framed.
+ */
+public enum Protocol {
+ /**
+ * An obsolete plaintext framing that does not use persistent sockets by
+ * default.
+ */
+ HTTP_1_0("http/1.0"),
+
+ /**
+ * A plaintext framing that includes persistent connections.
+ * <p/>
+ * <p>This version of OkHttp implements <a
+ * href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</a>, and tracks
+ * revisions to that spec.
+ */
+ HTTP_1_1("http/1.1"),
+
+ /**
+ * Chromium's binary-framed protocol that includes header compression,
+ * multiplexing multiple requests on the same socket, and server-push.
+ * HTTP/1.1 semantics are layered on SPDY/3.
+ * <p/>
+ * <p>This version of OkHttp implements SPDY 3 <a
+ * href="http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1">draft
+ * 3.1</a>. Future releases of OkHttp may use this identifier for a newer draft
+ * of the SPDY spec.
+ */
+ SPDY_3("spdy/3.1"),
+
+ /**
+ * The IETF's binary-framed protocol that includes header compression,
+ * multiplexing multiple requests on the same socket, and server-push.
+ * HTTP/1.1 semantics are layered on HTTP/2.
+ * <p/>
+ * <p>This version of OkHttp implements HTTP/2 <a
+ * href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-13">draft 12</a>
+ * with HPACK <a
+ * href="http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-08">draft
+ * 6</a>. Future releases of OkHttp may use this identifier for a newer draft
+ * of these specs.
+ */
+ HTTP_2("h2-13");
+
+ private final String protocol;
+ private static final Hashtable<String, Protocol> protocols = new Hashtable<String, Protocol>();
+
+ static {
+ protocols.put(HTTP_1_0.toString(), HTTP_1_0);
+ protocols.put(HTTP_1_1.toString(), HTTP_1_1);
+ protocols.put(SPDY_3.toString(), SPDY_3);
+ protocols.put(HTTP_2.toString(), HTTP_2);
+ }
+
+
+ Protocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ /**
+ * Returns the protocol identified by {@code protocol}.
+ */
+ public static Protocol get(String protocol) {
+ return protocols.get(protocol.toLowerCase());
+ }
+
+ /**
+ * Returns the string used to identify this protocol for ALPN and NPN, like
+ * "http/1.1", "spdy/3.1" or "h2-13".
+ */
+ @Override
+ public String toString() {
+ return protocol;
+ }
+}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/SimpleMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/SimpleMiddleware.java
index dba645a..7fe545d 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/SimpleMiddleware.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/SimpleMiddleware.java
@@ -3,14 +3,13 @@ package com.koushikdutta.async.http;
import com.koushikdutta.async.future.Cancellable;
public class SimpleMiddleware implements AsyncHttpClientMiddleware {
-
@Override
- public Cancellable getSocket(GetSocketData data) {
- return null;
+ public void onRequest(OnRequestData data) {
}
@Override
- public void onSocket(OnSocketData data) {
+ public Cancellable getSocket(GetSocketData data) {
+ return null;
}
@Override
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/WebSocketImpl.java b/AndroidAsync/src/com/koushikdutta/async/http/WebSocketImpl.java
index 4af0b99..ddbd62c 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/WebSocketImpl.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/WebSocketImpl.java
@@ -11,7 +11,6 @@ import com.koushikdutta.async.Util;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.callback.WritableCallback;
-import com.koushikdutta.async.http.cache.RawHeaders;
import com.koushikdutta.async.http.server.AsyncHttpServerRequest;
import com.koushikdutta.async.http.server.AsyncHttpServerResponse;
@@ -115,7 +114,7 @@ public class WebSocketImpl implements WebSocket {
String sha1 = SHA1(concat);
String origin = request.getHeaders().get("Origin");
- response.responseCode(101);
+ response.code(101);
response.getHeaders().set("Upgrade", "WebSocket");
response.getHeaders().set("Connection", "Upgrade");
response.getHeaders().set("Sec-WebSocket-Accept", sha1);
@@ -131,7 +130,7 @@ public class WebSocketImpl implements WebSocket {
}
public static void addWebSocketUpgradeHeaders(AsyncHttpRequest req, String protocol) {
- RawHeaders headers = req.getHeaders();
+ Headers headers = req.getHeaders();
final String key = Base64.encodeToString(toByteArray(UUID.randomUUID()),Base64.NO_WRAP);
headers.set("Sec-WebSocket-Version", "13");
headers.set("Sec-WebSocket-Key", key);
@@ -151,10 +150,10 @@ public class WebSocketImpl implements WebSocket {
mSink = new BufferedDataSink(mSocket);
}
- public static WebSocket finishHandshake(RawHeaders requestHeaders, AsyncHttpResponse response) {
+ public static WebSocket finishHandshake(Headers requestHeaders, AsyncHttpResponse response) {
if (response == null)
return null;
- if (response.headers().getResponseCode() != 101)
+ if (response.code() != 101)
return null;
if (!"websocket".equalsIgnoreCase(response.headers().get("Upgrade")))
return null;
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/body/MultipartFormDataBody.java b/AndroidAsync/src/com/koushikdutta/async/http/body/MultipartFormDataBody.java
index 1872e19..309d9c3 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/body/MultipartFormDataBody.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/body/MultipartFormDataBody.java
@@ -11,8 +11,8 @@ import com.koushikdutta.async.callback.ContinuationCallback;
import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.future.Continuation;
import com.koushikdutta.async.http.AsyncHttpRequest;
+import com.koushikdutta.async.http.Headers;
import com.koushikdutta.async.http.Multimap;
-import com.koushikdutta.async.http.cache.RawHeaders;
import com.koushikdutta.async.http.server.BoundaryEmitter;
import java.io.File;
@@ -21,7 +21,7 @@ import java.util.UUID;
public class MultipartFormDataBody extends BoundaryEmitter implements AsyncHttpRequestBody<Multimap> {
LineEmitter liner;
- RawHeaders formData;
+ Headers formData;
ByteBufferList last;
String lastName;
@@ -40,7 +40,7 @@ public class MultipartFormDataBody extends BoundaryEmitter implements AsyncHttpR
return;
if (formData == null)
- formData = new RawHeaders();
+ formData = new Headers();
formData.add(lastName, last.peekString());
@@ -62,7 +62,7 @@ public class MultipartFormDataBody extends BoundaryEmitter implements AsyncHttpR
@Override
protected void onBoundaryStart() {
- final RawHeaders headers = new RawHeaders();
+ final Headers headers = new Headers();
liner = new LineEmitter();
liner.setLineCallback(new StringCallback() {
@Override
@@ -146,8 +146,7 @@ public class MultipartFormDataBody extends BoundaryEmitter implements AsyncHttpR
c.add(new ContinuationCallback() {
@Override
public void onContinue(Continuation continuation, CompletedCallback next) throws Exception {
- part.getRawHeaders().setStatusLine(getBoundaryStart());
- byte[] bytes = part.getRawHeaders().toHeaderString().getBytes();
+ byte[] bytes = part.getRawHeaders().toPrefixString(getBoundaryStart()).getBytes();
com.koushikdutta.async.Util.writeAll(sink, bytes, next);
written += bytes.length;
}
@@ -205,10 +204,10 @@ public class MultipartFormDataBody extends BoundaryEmitter implements AsyncHttpR
int length = 0;
for (final Part part: mParts) {
- part.getRawHeaders().setStatusLine(getBoundaryStart());
+ String partHeader = part.getRawHeaders().toPrefixString(getBoundaryStart());
if (part.length() == -1)
return -1;
- length += part.length() + part.getRawHeaders().toHeaderString().getBytes().length + "\r\n".length();
+ length += part.length() + partHeader.getBytes().length + "\r\n".length();
}
length += (getBoundaryEnd()).getBytes().length;
return totalToWrite = length;
@@ -238,6 +237,6 @@ public class MultipartFormDataBody extends BoundaryEmitter implements AsyncHttpR
@Override
public Multimap get() {
- return new Multimap(formData);
+ return new Multimap(formData.getMultiMap());
}
}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/body/Part.java b/AndroidAsync/src/com/koushikdutta/async/http/body/Part.java
index 56fcecb..b66c711 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/body/Part.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/body/Part.java
@@ -2,8 +2,9 @@ package com.koushikdutta.async.http.body;
import com.koushikdutta.async.DataSink;
import com.koushikdutta.async.callback.CompletedCallback;
+import com.koushikdutta.async.http.Headers;
import com.koushikdutta.async.http.Multimap;
-import com.koushikdutta.async.http.cache.RawHeaders;
+
import org.apache.http.NameValuePair;
import java.io.File;
@@ -12,11 +13,11 @@ import java.util.List;
public class Part {
public static final String CONTENT_DISPOSITION = "Content-Disposition";
- RawHeaders mHeaders;
+ Headers mHeaders;
Multimap mContentDisposition;
- public Part(RawHeaders headers) {
+ public Part(Headers headers) {
mHeaders = headers;
- mContentDisposition = Multimap.parseHeader(mHeaders, CONTENT_DISPOSITION);
+ mContentDisposition = Multimap.parseSemicolonDelimited(mHeaders.get(CONTENT_DISPOSITION));
}
public String getName() {
@@ -26,7 +27,7 @@ public class Part {
private long length = -1;
public Part(String name, long length, List<NameValuePair> contentDisposition) {
this.length = length;
- mHeaders = new RawHeaders();
+ mHeaders = new Headers();
StringBuilder builder = new StringBuilder(String.format("form-data; name=\"%s\"", name));
if (contentDisposition != null) {
for (NameValuePair pair: contentDisposition) {
@@ -34,10 +35,10 @@ public class Part {
}
}
mHeaders.set(CONTENT_DISPOSITION, builder.toString());
- mContentDisposition = Multimap.parseHeader(mHeaders, CONTENT_DISPOSITION);
+ mContentDisposition = Multimap.parseSemicolonDelimited(mHeaders.get(CONTENT_DISPOSITION));
}
- public RawHeaders getRawHeaders() {
+ public Headers getRawHeaders() {
return mHeaders;
}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/cache/CacheUtil.java b/AndroidAsync/src/com/koushikdutta/async/http/cache/CacheUtil.java
deleted file mode 100644
index caadf8f..0000000
--- a/AndroidAsync/src/com/koushikdutta/async/http/cache/CacheUtil.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.koushikdutta.async.http.cache;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Created by koush on 7/21/14.
- */
-class CacheUtil {
- static Set<String> varyFields(RawHeaders headers) {
- HashSet<String> ret = new HashSet<String>();
- String value = headers.get("Vary");
- if (value == null)
- return ret;
- for (String varyField : value.split(",")) {
- ret.add(varyField.trim());
- }
- return ret;
- }
-
- static boolean isCacheable(RawHeaders requestHeaders, RawHeaders responseHeaders) {
- ResponseHeaders r = new ResponseHeaders(null, responseHeaders);
- return r.isCacheable(new RequestHeaders(null, requestHeaders));
- }
-
- static boolean isNoCache(RawHeaders headers) {
- return new RequestHeaders(null, headers).isNoCache();
- }
-
- ResponseSource chooseResponseSource(long nowMillis, RawHeaders request, RawHeaders response) {
- RequestHeaders requestHeaders = new RequestHeaders(null, request);
- ResponseHeaders responseHeaders = new ResponseHeaders(null, response);
- return responseHeaders.chooseResponseSource(nowMillis, requestHeaders);
- }
-}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/cache/RawHeaders.java b/AndroidAsync/src/com/koushikdutta/async/http/cache/RawHeaders.java
index 53be340..9274a54 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/cache/RawHeaders.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/cache/RawHeaders.java
@@ -44,7 +44,7 @@ import java.util.TreeMap;
* <p>This class trims whitespace from values. It never returns values with
* leading or trailing whitespace.
*/
-public final class RawHeaders {
+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) {
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/cache/ResponseCacheMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/cache/ResponseCacheMiddleware.java
index 22f0ac2..447c81b 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/cache/ResponseCacheMiddleware.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/cache/ResponseCacheMiddleware.java
@@ -18,6 +18,7 @@ import com.koushikdutta.async.http.AsyncHttpClient;
import com.koushikdutta.async.http.AsyncHttpClientMiddleware;
import com.koushikdutta.async.http.AsyncHttpGet;
import com.koushikdutta.async.http.AsyncHttpRequest;
+import com.koushikdutta.async.http.Headers;
import com.koushikdutta.async.http.SimpleMiddleware;
import com.koushikdutta.async.util.Allocator;
import com.koushikdutta.async.util.Charsets;
@@ -95,7 +96,10 @@ public class ResponseCacheMiddleware extends SimpleMiddleware {
// also see if this can be turned into a conditional cache request.
@Override
public Cancellable getSocket(final GetSocketData data) {
- if (cache == null || !caching || CacheUtil.isNoCache(data.request.getHeaders())) {
+ RequestHeaders requestHeaders = new RequestHeaders(data.request.getUri(), RawHeaders.fromMultimap(data.request.getHeaders().getMultiMap()));
+ data.state.put("request-headers", requestHeaders);
+
+ if (cache == null || !caching || requestHeaders.isNoCache()) {
networkCount++;
return null;
}
@@ -121,7 +125,7 @@ public class ResponseCacheMiddleware extends SimpleMiddleware {
}
// verify the entry matches
- if (!entry.matches(data.request.getUri(), data.request.getMethod(), data.request.getHeaders().toMultimap())) {
+ if (!entry.matches(data.request.getUri(), data.request.getMethod(), data.request.getHeaders().getMultiMap())) {
networkCount++;
StreamUtility.closeQuietly(snapshot);
return null;
@@ -154,7 +158,6 @@ public class ResponseCacheMiddleware extends SimpleMiddleware {
cachedResponseHeaders.setLocalTimestamps(System.currentTimeMillis(), System.currentTimeMillis());
long now = System.currentTimeMillis();
- RequestHeaders requestHeaders = new RequestHeaders(null, data.request.getHeaders());
ResponseSource responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders);
if (responseSource == ResponseSource.CACHE) {
@@ -218,13 +221,18 @@ public class ResponseCacheMiddleware extends SimpleMiddleware {
}
CacheData cacheData = data.state.get("cache-data");
+ RawHeaders rh = RawHeaders.fromMultimap(data.headers.getMultiMap());
+ rh.setStatusLine(String.format("%s %s %s", data.response.protocol(), data.response.code(), data.response.message()));
+ ResponseHeaders networkResponse = new ResponseHeaders(data.request.getUri(), rh);
+ data.state.put("response-headers", networkResponse);
if (cacheData != null) {
- ResponseHeaders networkResponse = new ResponseHeaders(null, data.headers);
if (cacheData.cachedResponseHeaders.validate(networkResponse)) {
data.request.logi("Serving response from conditional cache");
data.headers.removeAll("Content-Length");
- data.headers = cacheData.cachedResponseHeaders.combine(networkResponse).getHeaders();
- data.headers.setStatusLine(cacheData.cachedResponseHeaders.getHeaders().getStatusLine());
+ ResponseHeaders combined = cacheData.cachedResponseHeaders.combine(networkResponse);
+ data.headers = new Headers(combined.getHeaders().toMultimap());
+ data.response.code(combined.getHeaders().getResponseCode());
+ data.response.message(combined.getHeaders().getResponseMessage());
data.headers.set(SERVED_FROM, CONDITIONAL_CACHE);
conditionalCacheHitCount++;
@@ -244,7 +252,8 @@ public class ResponseCacheMiddleware extends SimpleMiddleware {
if (!caching)
return;
- if (!CacheUtil.isCacheable(data.request.getHeaders(), data.headers) || !data.request.getMethod().equals(AsyncHttpGet.METHOD)) {
+ RequestHeaders requestHeaders = data.state.get("request-headers");
+ if (requestHeaders == null || !networkResponse.isCacheable(requestHeaders) || !data.request.getMethod().equals(AsyncHttpGet.METHOD)) {
/*
* Don't cache non-GET responses. We're technically allowed to cache
* HEAD requests and some POST requests, but the complexity of doing
@@ -256,8 +265,8 @@ public class ResponseCacheMiddleware extends SimpleMiddleware {
}
String key = FileCache.toKeyString(data.request.getUri());
- RawHeaders varyHeaders = data.request.getHeaders().getAll(CacheUtil.varyFields(data.headers));
- Entry entry = new Entry(data.request.getUri(), varyHeaders, data.request, data.headers);
+ RawHeaders varyHeaders = requestHeaders.getHeaders().getAll(networkResponse.getVaryFields());
+ Entry entry = new Entry(data.request.getUri(), varyHeaders, data.request, networkResponse.getHeaders());
BodyCacher cacher = new BodyCacher();
EntryEditor editor = new EntryEditor(key);
try {
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/callback/HeadersCallback.java b/AndroidAsync/src/com/koushikdutta/async/http/callback/HeadersCallback.java
deleted file mode 100644
index 68db69f..0000000
--- a/AndroidAsync/src/com/koushikdutta/async/http/callback/HeadersCallback.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.koushikdutta.async.http.callback;
-
-import com.koushikdutta.async.http.cache.RawHeaders;
-
-/**
- * Created by koush on 6/30/13.
- */
-public interface HeadersCallback {
- public void onHeaders(RawHeaders headers);
-}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/filter/ContentLengthFilter.java b/AndroidAsync/src/com/koushikdutta/async/http/filter/ContentLengthFilter.java
index d1b05a2..41aa217 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/filter/ContentLengthFilter.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/filter/ContentLengthFilter.java
@@ -12,7 +12,7 @@ public class ContentLengthFilter extends FilteredDataEmitter {
@Override
protected void report(Exception e) {
if (e == null && totalRead != contentLength)
- e = new PrematureDataEndException("End of data reached before content length was read");
+ e = new PrematureDataEndException("End of data reached before content length was read: " + totalRead + "/" + contentLength + " Paused: " + isPaused());
super.report(e);
}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServer.java b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServer.java
index 1c981b7..3d65669 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServer.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServer.java
@@ -20,16 +20,18 @@ import com.koushikdutta.async.callback.ListenCallback;
import com.koushikdutta.async.http.AsyncHttpGet;
import com.koushikdutta.async.http.AsyncHttpHead;
import com.koushikdutta.async.http.AsyncHttpPost;
+import com.koushikdutta.async.http.Headers;
import com.koushikdutta.async.http.HttpUtil;
import com.koushikdutta.async.http.Multimap;
+import com.koushikdutta.async.http.Protocol;
import com.koushikdutta.async.http.WebSocket;
import com.koushikdutta.async.http.WebSocketImpl;
import com.koushikdutta.async.http.body.AsyncHttpRequestBody;
-import com.koushikdutta.async.http.cache.RawHeaders;
import com.koushikdutta.async.util.StreamUtility;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -52,10 +54,16 @@ public class AsyncHttpServer {
}
}
- protected void onRequest(AsyncHttpServerRequest request, AsyncHttpServerResponse response) {
+ protected boolean onRequest(AsyncHttpServerRequest request, AsyncHttpServerResponse response) {
+ return false;
}
- protected AsyncHttpRequestBody onUnknownBody(RawHeaders headers) {
+ protected void onRequest(HttpServerRequestCallback callback, AsyncHttpServerRequest request, AsyncHttpServerResponse response) {
+ if (callback != null)
+ callback.onRequest(request, response);
+ }
+
+ protected AsyncHttpRequestBody onUnknownBody(Headers headers) {
return new UnknownRequestBody(headers.get("Content-Type"));
}
@@ -63,7 +71,7 @@ public class AsyncHttpServer {
@Override
public void onAccepted(final AsyncSocket socket) {
AsyncHttpServerRequestImpl req = new AsyncHttpServerRequestImpl() {
- Pair match;
+ HttpServerRequestCallback match;
String fullPath;
String path;
boolean responseComplete;
@@ -72,13 +80,13 @@ public class AsyncHttpServer {
boolean hasContinued;
@Override
- protected AsyncHttpRequestBody onUnknownBody(RawHeaders headers) {
+ protected AsyncHttpRequestBody onUnknownBody(Headers headers) {
return AsyncHttpServer.this.onUnknownBody(headers);
}
@Override
protected void onHeadersReceived() {
- RawHeaders headers = getHeaders();
+ Headers headers = getHeaders();
// should the negotiation of 100 continue be here, or in the request impl?
// probably here, so AsyncResponse can negotiate a 100 continue.
@@ -101,7 +109,7 @@ public class AsyncHttpServer {
}
// System.out.println(headers.toHeaderString());
- String statusLine = headers.getStatusLine();
+ String statusLine = getStatusLine();
String[] parts = statusLine.split(" ");
fullPath = parts[1];
path = fullPath.split("\\?")[0];
@@ -113,7 +121,7 @@ public class AsyncHttpServer {
Matcher m = p.regex.matcher(path);
if (m.matches()) {
mMatcher = m;
- match = p;
+ match = p.callback;
break;
}
}
@@ -130,26 +138,26 @@ public class AsyncHttpServer {
}
};
- onRequest(this, res);
-
- if (match == null) {
- res.responseCode(404);
+ boolean handled = onRequest(this, res);
+
+ if (match == null && !handled) {
+ res.code(404);
res.end();
return;
}
if (!getBody().readFullyOnRequest()) {
- match.callback.onRequest(this, res);
+ onRequest(match, this, res);
}
else if (requestComplete) {
- match.callback.onRequest(this, res);
+ onRequest(match, this, res);
}
}
@Override
public void onCompleted(Exception e) {
// if the protocol was switched off http, ignore this request/response.
- if (res.getHeaders().getResponseCode() == 101)
+ if (res.code() == 101)
return;
requestComplete = true;
super.onCompleted(e);
@@ -165,14 +173,13 @@ public class AsyncHttpServer {
handleOnCompleted();
if (getBody().readFullyOnRequest()) {
- if (match != null)
- match.callback.onRequest(this, res);
+ onRequest(match, this, res);
}
}
private void handleOnCompleted() {
if (requestComplete && responseComplete) {
- if (HttpUtil.isKeepAlive(getHeaders())) {
+ if (HttpUtil.isKeepAlive(Protocol.HTTP_1_1, getHeaders())) {
onAccepted(socket);
}
else {
@@ -266,7 +273,7 @@ public class AsyncHttpServer {
HttpServerRequestCallback callback;
}
- Hashtable<String, ArrayList<Pair>> mActions = new Hashtable<String, ArrayList<Pair>>();
+ final Hashtable<String, ArrayList<Pair>> mActions = new Hashtable<String, ArrayList<Pair>>();
public void addAction(String action, String regex, HttpServerRequestCallback callback) {
Pair p = new Pair();
@@ -284,7 +291,7 @@ public class AsyncHttpServer {
}
public static interface WebSocketRequestCallback {
- public void onConnected(WebSocket webSocket, RawHeaders headers);
+ public void onConnected(WebSocket webSocket, Headers headers);
}
public void websocket(String regex, final WebSocketRequestCallback callback) {
@@ -307,13 +314,13 @@ public class AsyncHttpServer {
}
}
if (!"websocket".equalsIgnoreCase(request.getHeaders().get("Upgrade")) || !hasUpgrade) {
- response.responseCode(404);
+ response.code(404);
response.end();
return;
}
String peerProtocol = request.getHeaders().get("Sec-WebSocket-Protocol");
if (!TextUtils.equals(protocol, peerProtocol)) {
- response.responseCode(404);
+ response.code(404);
response.end();
return;
}
@@ -379,14 +386,14 @@ public class AsyncHttpServer {
public void onRequest(AsyncHttpServerRequest request, final AsyncHttpServerResponse response) {
String path = request.getMatcher().replaceAll("");
android.util.Pair<Integer, InputStream> pair = getAssetStream(_context, assetPath + path);
- final InputStream is = pair.second;
- response.getHeaders().set("Content-Length", String.valueOf(pair.first));
- if (is == null) {
- response.responseCode(404);
+ if (pair == null || pair.second == null) {
+ response.code(404);
response.end();
return;
}
- response.responseCode(200);
+ final InputStream is = pair.second;
+ response.getHeaders().set("Content-Length", String.valueOf(pair.first));
+ response.code(200);
response.getHeaders().add("Content-Type", getContentType(assetPath + path));
Util.pump(is, response, new CompletedCallback() {
@Override
@@ -402,15 +409,15 @@ public class AsyncHttpServer {
public void onRequest(AsyncHttpServerRequest request, final AsyncHttpServerResponse response) {
String path = request.getMatcher().replaceAll("");
android.util.Pair<Integer, InputStream> pair = getAssetStream(_context, assetPath + path);
- final InputStream is = pair.second;
- StreamUtility.closeQuietly(is);
- response.getHeaders().set("Content-Length", String.valueOf(pair.first));
- if (is == null) {
- response.responseCode(404);
+ if (pair == null || pair.second == null) {
+ response.code(404);
response.end();
return;
}
- response.responseCode(200);
+ final InputStream is = pair.second;
+ StreamUtility.closeQuietly(is);
+ response.getHeaders().set("Content-Length", String.valueOf(pair.first));
+ response.code(200);
response.getHeaders().add("Content-Type", getContentType(assetPath + path));
response.writeHead();
response.end();
@@ -455,13 +462,13 @@ public class AsyncHttpServer {
return;
}
if (!file.isFile()) {
- response.responseCode(404);
+ response.code(404);
response.end();
return;
}
try {
FileInputStream is = new FileInputStream(file);
- response.responseCode(200);
+ response.code(200);
Util.pump(is, response, new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
@@ -469,10 +476,9 @@ public class AsyncHttpServer {
}
});
}
- catch (Exception ex) {
- response.responseCode(404);
+ catch (FileNotFoundException ex) {
+ response.code(404);
response.end();
- return;
}
}
});
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerRequest.java b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerRequest.java
index 77ce875..1b0292f 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerRequest.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerRequest.java
@@ -2,14 +2,14 @@ package com.koushikdutta.async.http.server;
import com.koushikdutta.async.AsyncSocket;
import com.koushikdutta.async.DataEmitter;
+import com.koushikdutta.async.http.Headers;
import com.koushikdutta.async.http.Multimap;
import com.koushikdutta.async.http.body.AsyncHttpRequestBody;
-import com.koushikdutta.async.http.cache.RawHeaders;
import java.util.regex.Matcher;
public interface AsyncHttpServerRequest extends DataEmitter {
- public RawHeaders getHeaders();
+ public Headers getHeaders();
public Matcher getMatcher();
public AsyncHttpRequestBody getBody();
public AsyncSocket getSocket();
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerRequestImpl.java b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerRequestImpl.java
index ba0576f..15bed10 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerRequestImpl.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerRequestImpl.java
@@ -7,17 +7,23 @@ import com.koushikdutta.async.LineEmitter;
import com.koushikdutta.async.LineEmitter.StringCallback;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.DataCallback;
+import com.koushikdutta.async.http.Headers;
import com.koushikdutta.async.http.HttpUtil;
+import com.koushikdutta.async.http.Protocol;
import com.koushikdutta.async.http.body.AsyncHttpRequestBody;
-import com.koushikdutta.async.http.cache.RawHeaders;
import java.util.regex.Matcher;
public abstract class AsyncHttpServerRequestImpl extends FilteredDataEmitter implements AsyncHttpServerRequest, CompletedCallback {
- private RawHeaders mRawHeaders = new RawHeaders();
+ private String statusLine;
+ private Headers mRawHeaders = new Headers();
AsyncSocket mSocket;
Matcher mMatcher;
+ public String getStatusLine() {
+ return statusLine;
+ }
+
private CompletedCallback mReporter = new CompletedCallback() {
@Override
public void onCompleted(Exception error) {
@@ -35,11 +41,10 @@ public abstract class AsyncHttpServerRequestImpl extends FilteredDataEmitter imp
abstract protected void onHeadersReceived();
protected void onNotHttp() {
- System.out.println("not http: " + mRawHeaders.getStatusLine());
- System.out.println("not http: " + mRawHeaders.getStatusLine().length());
+ System.out.println("not http!");
}
- protected AsyncHttpRequestBody onUnknownBody(RawHeaders headers) {
+ protected AsyncHttpRequestBody onUnknownBody(Headers headers) {
return null;
}
@@ -47,9 +52,9 @@ public abstract class AsyncHttpServerRequestImpl extends FilteredDataEmitter imp
@Override
public void onStringAvailable(String s) {
try {
- if (mRawHeaders.getStatusLine() == null) {
- mRawHeaders.setStatusLine(s);
- if (!mRawHeaders.getStatusLine().contains("HTTP/")) {
+ if (statusLine == null) {
+ statusLine = s;
+ if (!statusLine.contains("HTTP/")) {
onNotHttp();
mSocket.setDataCallback(null);
}
@@ -58,7 +63,7 @@ public abstract class AsyncHttpServerRequestImpl extends FilteredDataEmitter imp
mRawHeaders.addLine(s);
}
else {
- DataEmitter emitter = HttpUtil.getBodyDecoder(mSocket, mRawHeaders, true);
+ DataEmitter emitter = HttpUtil.getBodyDecoder(mSocket, Protocol.HTTP_1_1, mRawHeaders, true);
// emitter.setEndCallback(mReporter);
mBody = HttpUtil.getBody(emitter, mReporter, mRawHeaders);
if (mBody == null) {
@@ -96,7 +101,7 @@ public abstract class AsyncHttpServerRequestImpl extends FilteredDataEmitter imp
}
@Override
- public RawHeaders getHeaders() {
+ public Headers getHeaders() {
return mRawHeaders;
}
@@ -140,4 +145,11 @@ public abstract class AsyncHttpServerRequestImpl extends FilteredDataEmitter imp
public boolean isPaused() {
return mSocket.isPaused();
}
+
+ @Override
+ public String toString() {
+ if (mRawHeaders == null)
+ return super.toString();
+ return mRawHeaders.toPrefixString(statusLine);
+ }
}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponse.java b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponse.java
index 87fbc5e..bc6e332 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponse.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponse.java
@@ -3,7 +3,7 @@ package com.koushikdutta.async.http.server;
import com.koushikdutta.async.AsyncSocket;
import com.koushikdutta.async.DataSink;
import com.koushikdutta.async.callback.CompletedCallback;
-import com.koushikdutta.async.http.cache.RawHeaders;
+import com.koushikdutta.async.http.Headers;
import org.json.JSONObject;
@@ -17,8 +17,9 @@ public interface AsyncHttpServerResponse extends DataSink, CompletedCallback {
public void send(JSONObject json);
public void sendFile(File file);
public void sendStream(InputStream inputStream, long totalLength);
- public void responseCode(int code);
- public RawHeaders getHeaders();
+ public AsyncHttpServerResponse code(int code);
+ public int code();
+ public Headers getHeaders();
public void writeHead();
public void setContentType(String contentType);
public void redirect(String location);
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java
index 0fe4e10..3cc5d63 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncHttpServerResponseImpl.java
@@ -11,8 +11,10 @@ import com.koushikdutta.async.Util;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.WritableCallback;
import com.koushikdutta.async.http.AsyncHttpHead;
+import com.koushikdutta.async.http.AsyncHttpResponse;
+import com.koushikdutta.async.http.Headers;
import com.koushikdutta.async.http.HttpUtil;
-import com.koushikdutta.async.http.cache.RawHeaders;
+import com.koushikdutta.async.http.Protocol;
import com.koushikdutta.async.http.filter.ChunkedOutputFilter;
import com.koushikdutta.async.util.StreamUtility;
@@ -21,15 +23,16 @@ import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse {
- private RawHeaders mRawHeaders = new RawHeaders();
+ private Headers mRawHeaders = new Headers();
private long mContentLength = -1;
@Override
- public RawHeaders getHeaders() {
+ public Headers getHeaders() {
return mRawHeaders;
}
@@ -42,7 +45,7 @@ public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse {
AsyncHttpServerResponseImpl(AsyncSocket socket, AsyncHttpServerRequestImpl req) {
mSocket = socket;
mRequest = req;
- if (HttpUtil.isKeepAlive(req.getHeaders()))
+ if (HttpUtil.isKeepAlive(Protocol.HTTP_1_1, req.getHeaders()))
mRawHeaders.set("Connection", "Keep-Alive");
}
@@ -69,7 +72,6 @@ public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse {
return;
mHasWritten = true;
- assert null != mRawHeaders.getStatusLine();
String currentEncoding = mRawHeaders.get("Transfer-Encoding");
if ("".equals(currentEncoding))
mRawHeaders.removeAll("Transfer-Encoding");
@@ -129,7 +131,9 @@ public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse {
private void writeHeadInternal() {
assert !mHeadWritten;
mHeadWritten = true;
- Util.writeAll(mSocket, mRawHeaders.toHeaderString().getBytes(), new CompletedCallback() {
+ String statusLine = String.format("HTTP/1.1 %s %s", code, AsyncHttpServer.getResponseCodeDescription(code));
+ String rh = mRawHeaders.toPrefixString(statusLine);
+ Util.writeAll(mSocket, rh.getBytes(), new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
// TODO: HACK!!!
@@ -153,8 +157,6 @@ public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse {
@Override
public void send(String contentType, final String string) {
try {
- if (mRawHeaders.getStatusLine() == null)
- responseCode(200);
assert mContentLength < 0;
byte[] bytes = string.getBytes("UTF-8");
mContentLength = bytes.length;
@@ -184,7 +186,6 @@ public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse {
@Override
public void send(String string) {
- responseCode(200);
String contentType = mRawHeaders.get("Content-Type");
if (contentType == null)
contentType = "text/html; charset=utf8";
@@ -206,7 +207,7 @@ public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse {
String[] parts = range.split("=");
if (parts.length != 2 || !"bytes".equals(parts[0])) {
// Requested range not satisfiable
- responseCode(416);
+ code(416);
end();
return;
}
@@ -222,11 +223,11 @@ public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse {
else
end = totalLength - 1;
- responseCode(206);
+ code(206);
getHeaders().set("Content-Range", String.format("bytes %d-%d/%d", start, end, totalLength));
}
catch (Exception e) {
- responseCode(416);
+ code(416);
end();
return;
}
@@ -237,8 +238,6 @@ public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse {
mContentLength = end - start + 1;
mRawHeaders.set("Content-Length", String.valueOf(mContentLength));
mRawHeaders.set("Accept-Ranges", "bytes");
- if (getHeaders().getStatusLine() == null)
- responseCode(200);
if (mRequest.getMethod().equals(AsyncHttpHead.METHOD)) {
writeHead();
onEnd();
@@ -253,7 +252,7 @@ public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse {
});
}
catch (Exception e) {
- responseCode(404);
+ code(500);
end();
}
}
@@ -266,21 +265,27 @@ public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse {
FileInputStream fin = new FileInputStream(file);
sendStream(new BufferedInputStream(fin, 64000), file.length());
}
- catch (Exception e) {
- responseCode(404);
+ catch (FileNotFoundException e) {
+ code(404);
end();
}
}
+ int code = 200;
+ @Override
+ public AsyncHttpServerResponse code(int code) {
+ this.code = code;
+ return this;
+ }
+
@Override
- public void responseCode(int code) {
- String status = AsyncHttpServer.getResponseCodeDescription(code);
- mRawHeaders.setStatusLine(String.format("HTTP/1.1 %d %s", code, status));
+ public int code() {
+ return code;
}
@Override
public void redirect(String location) {
- responseCode(302);
+ code(302);
mRawHeaders.set("Location", location);
end();
}
@@ -311,4 +316,12 @@ public class AsyncHttpServerResponseImpl implements AsyncHttpServerResponse {
public AsyncServer getServer() {
return mSocket.getServer();
}
+
+ @Override
+ public String toString() {
+ if (mRawHeaders == null)
+ return super.toString();
+ String statusLine = String.format("HTTP/1.1 %s %s", code, AsyncHttpServer.getResponseCodeDescription(code));
+ return mRawHeaders.toPrefixString(statusLine);
+ }
}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncProxyServer.java b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncProxyServer.java
new file mode 100644
index 0000000..8a16c09
--- /dev/null
+++ b/AndroidAsync/src/com/koushikdutta/async/http/server/AsyncProxyServer.java
@@ -0,0 +1,81 @@
+package com.koushikdutta.async.http.server;
+
+import android.net.Uri;
+
+import com.koushikdutta.async.AsyncServer;
+import com.koushikdutta.async.Util;
+import com.koushikdutta.async.callback.CompletedCallback;
+import com.koushikdutta.async.http.AsyncHttpClient;
+import com.koushikdutta.async.http.AsyncHttpRequest;
+import com.koushikdutta.async.http.AsyncHttpResponse;
+import com.koushikdutta.async.http.callback.HttpConnectCallback;
+
+/**
+ * Created by koush on 7/22/14.
+ */
+public class AsyncProxyServer extends AsyncHttpServer {
+ AsyncHttpClient proxyClient;
+ public AsyncProxyServer(AsyncServer server) {
+ proxyClient = new AsyncHttpClient(server);
+ }
+
+ @Override
+ protected void onRequest(HttpServerRequestCallback callback, AsyncHttpServerRequest request, final AsyncHttpServerResponse response) {
+ super.onRequest(callback, request, response);
+
+ if (callback != null)
+ return;
+
+ try {
+ Uri uri;
+
+ try {
+ uri = Uri.parse(request.getPath());
+ if (uri.getScheme() == null)
+ throw new Exception("no host or full uri provided");
+ }
+ catch (Exception e) {
+ String host = request.getHeaders().get("Host");
+ int port = 80;
+ if (host != null) {
+ String[] splits = host.split(":", 2);
+ if (splits.length == 2) {
+ host = splits[0];
+ port = Integer.parseInt(splits[1]);
+ }
+ }
+ uri = Uri.parse("http://" + host + ":" + port + request.getPath());
+ }
+
+ proxyClient.execute(new AsyncHttpRequest(uri, request.getMethod(), request.getHeaders()), new HttpConnectCallback() {
+ @Override
+ public void onConnectCompleted(Exception ex, AsyncHttpResponse remoteResponse) {
+ if (ex != null) {
+ response.code(500);
+ response.send(ex.getMessage());
+ return;
+ }
+ response.code(remoteResponse.code());
+ response.getHeaders().addAll(remoteResponse.headers());
+ response.getHeaders().removeAll("Transfer-Encoding");
+ response.getHeaders().removeAll("Content-Encoding");
+ Util.pump(remoteResponse, response, new CompletedCallback() {
+ @Override
+ public void onCompleted(Exception ex) {
+ response.end();
+ }
+ });
+ }
+ });
+ }
+ catch (Exception e) {
+ response.code(500);
+ response.send(e.getMessage());
+ }
+ }
+
+ @Override
+ protected boolean onRequest(AsyncHttpServerRequest request, AsyncHttpServerResponse response) {
+ return true;
+ }
+}
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/AsyncSpdyConnection.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/AsyncSpdyConnection.java
index 484b6d4..18e856c 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/AsyncSpdyConnection.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/AsyncSpdyConnection.java
@@ -1,7 +1,5 @@
package com.koushikdutta.async.http.spdy;
-import android.text.TextUtils;
-
import com.koushikdutta.async.AsyncServer;
import com.koushikdutta.async.AsyncSocket;
import com.koushikdutta.async.BufferedDataEmitter;
@@ -11,8 +9,7 @@ import com.koushikdutta.async.Util;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.callback.WritableCallback;
-import com.koushikdutta.async.http.spdy.okhttp.Protocol;
-import com.koushikdutta.async.http.spdy.okhttp.internal.NamedRunnable;
+import com.koushikdutta.async.http.Protocol;
import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.ErrorCode;
import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.FrameReader;
import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.FrameWriter;
@@ -22,14 +19,11 @@ import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Http20Draft13;
import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Ping;
import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Settings;
import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Spdy3;
-import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.SpdyConnection;
-import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.SpdyStream;
import com.koushikdutta.async.http.spdy.okhttp.internal.spdy.Variant;
import com.koushikdutta.async.http.spdy.okio.BufferedSource;
import com.koushikdutta.async.http.spdy.okio.ByteString;
import java.io.IOException;
-import java.nio.ByteBuffer;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java
index 837a126..35d4257 100644
--- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java
+++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java
@@ -8,7 +8,7 @@ import com.koushikdutta.async.future.Cancellable;
import com.koushikdutta.async.http.AsyncHttpClient;
import com.koushikdutta.async.http.AsyncSSLEngineConfigurator;
import com.koushikdutta.async.http.AsyncSSLSocketMiddleware;
-import com.koushikdutta.async.http.spdy.okhttp.Protocol;
+import com.koushikdutta.async.http.Protocol;
import com.koushikdutta.async.util.Charsets;
import java.lang.reflect.Field;