Return-Path: X-Original-To: apmail-cordova-commits-archive@www.apache.org Delivered-To: apmail-cordova-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 4986510C42 for ; Mon, 25 Nov 2013 23:43:38 +0000 (UTC) Received: (qmail 42523 invoked by uid 500); 25 Nov 2013 23:43:37 -0000 Delivered-To: apmail-cordova-commits-archive@cordova.apache.org Received: (qmail 42321 invoked by uid 500); 25 Nov 2013 23:43:36 -0000 Mailing-List: contact commits-help@cordova.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cordova.apache.org Delivered-To: mailing list commits@cordova.apache.org Received: (qmail 41733 invoked by uid 99); 25 Nov 2013 23:43:35 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 25 Nov 2013 23:43:35 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 4F8839106D7; Mon, 25 Nov 2013 23:43:35 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: steven@apache.org To: commits@cordova.apache.org Date: Mon, 25 Nov 2013 23:43:46 -0000 Message-Id: <66359cf904114be18f5f2309416765ea@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [13/17] added fireos code from https://github.com/archananaik/cordova-amazon-fireos sans history http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/Connection.java ---------------------------------------------------------------------- diff --git a/framework/src/com/squareup/okhttp/Connection.java b/framework/src/com/squareup/okhttp/Connection.java new file mode 100644 index 0000000..6a6c84d --- /dev/null +++ b/framework/src/com/squareup/okhttp/Connection.java @@ -0,0 +1,291 @@ +/* + * 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.squareup.okhttp; + +import com.squareup.okhttp.internal.Platform; +import com.squareup.okhttp.internal.http.HttpAuthenticator; +import com.squareup.okhttp.internal.http.HttpEngine; +import com.squareup.okhttp.internal.http.HttpTransport; +import com.squareup.okhttp.internal.http.RawHeaders; +import com.squareup.okhttp.internal.http.SpdyTransport; +import com.squareup.okhttp.internal.spdy.SpdyConnection; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Proxy; +import java.net.Socket; +import java.net.URL; +import java.util.Arrays; +import javax.net.ssl.SSLSocket; + +import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; + +/** + * Holds the sockets and streams of an HTTP, HTTPS, or HTTPS+SPDY connection, + * which may be used for multiple HTTP request/response exchanges. Connections + * may be direct to the origin server or via a proxy. + * + *

Typically instances of this class are created, connected and exercised + * automatically by the HTTP client. Applications may use this class to monitor + * HTTP connections as members of a {@link ConnectionPool connection pool}. + * + *

Do not confuse this class with the misnamed {@code HttpURLConnection}, + * which isn't so much a connection as a single request/response exchange. + * + *

Modern TLS

+ * There are tradeoffs when selecting which options to include when negotiating + * a secure connection to a remote host. Newer TLS options are quite useful: + *
    + *
  • Server Name Indication (SNI) enables one IP address to negotiate secure + * connections for multiple domain names. + *
  • Next Protocol Negotiation (NPN) enables the HTTPS port (443) to be used + * for both HTTP and SPDY transports. + *
+ * Unfortunately, older HTTPS servers refuse to connect when such options are + * presented. Rather than avoiding these options entirely, this class allows a + * connection to be attempted with modern options and then retried without them + * should the attempt fail. + */ +public final class Connection implements Closeable { + private static final byte[] NPN_PROTOCOLS = new byte[] { + 6, 's', 'p', 'd', 'y', '/', '3', + 8, 'h', 't', 't', 'p', '/', '1', '.', '1' + }; + private static final byte[] SPDY3 = new byte[] { + 's', 'p', 'd', 'y', '/', '3' + }; + private static final byte[] HTTP_11 = new byte[] { + 'h', 't', 't', 'p', '/', '1', '.', '1' + }; + + private final Route route; + + private Socket socket; + private InputStream in; + private OutputStream out; + private boolean connected = false; + private SpdyConnection spdyConnection; + private int httpMinorVersion = 1; // Assume HTTP/1.1 + private long idleStartTimeNs; + + public Connection(Route route) { + this.route = route; + } + + public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest) + throws IOException { + if (connected) { + throw new IllegalStateException("already connected"); + } + connected = true; + socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket(); + socket.connect(route.inetSocketAddress, connectTimeout); + socket.setSoTimeout(readTimeout); + in = socket.getInputStream(); + out = socket.getOutputStream(); + + if (route.address.sslSocketFactory != null) { + upgradeToTls(tunnelRequest); + } + + // Use MTU-sized buffers to send fewer packets. + int mtu = Platform.get().getMtu(socket); + in = new BufferedInputStream(in, mtu); + out = new BufferedOutputStream(out, mtu); + } + + /** + * Create an {@code SSLSocket} and perform the TLS handshake and certificate + * validation. + */ + private void upgradeToTls(TunnelRequest tunnelRequest) throws IOException { + Platform platform = Platform.get(); + + // Make an SSL Tunnel on the first message pair of each SSL + proxy connection. + if (requiresTunnel()) { + makeTunnel(tunnelRequest); + } + + // Create the wrapper over connected socket. + socket = route.address.sslSocketFactory + .createSocket(socket, route.address.uriHost, route.address.uriPort, true /* autoClose */); + SSLSocket sslSocket = (SSLSocket) socket; + if (route.modernTls) { + platform.enableTlsExtensions(sslSocket, route.address.uriHost); + } else { + platform.supportTlsIntolerantServer(sslSocket); + } + + if (route.modernTls) { + platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS); + } + + // Force handshake. This can throw! + sslSocket.startHandshake(); + + // Verify that the socket's certificates are acceptable for the target host. + if (!route.address.hostnameVerifier.verify(route.address.uriHost, sslSocket.getSession())) { + throw new IOException("Hostname '" + route.address.uriHost + "' was not verified"); + } + + out = sslSocket.getOutputStream(); + in = sslSocket.getInputStream(); + + byte[] selectedProtocol; + if (route.modernTls + && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) { + if (Arrays.equals(selectedProtocol, SPDY3)) { + sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream. + spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out) + .build(); + } else if (!Arrays.equals(selectedProtocol, HTTP_11)) { + throw new IOException( + "Unexpected NPN transport " + new String(selectedProtocol, "ISO-8859-1")); + } + } + } + + /** Returns true if {@link #connect} has been attempted on this connection. */ + public boolean isConnected() { + return connected; + } + + @Override public void close() throws IOException { + socket.close(); + } + + /** Returns the route used by this connection. */ + public Route getRoute() { + return route; + } + + /** + * Returns the socket that this connection uses, or null if the connection + * is not currently connected. + */ + public Socket getSocket() { + return socket; + } + + /** Returns true if this connection is alive. */ + public boolean isAlive() { + return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown(); + } + + public void resetIdleStartTime() { + if (spdyConnection != null) { + throw new IllegalStateException("spdyConnection != null"); + } + this.idleStartTimeNs = System.nanoTime(); + } + + /** Returns true if this connection is idle. */ + public boolean isIdle() { + return spdyConnection == null || spdyConnection.isIdle(); + } + + /** + * Returns true if this connection has been idle for longer than + * {@code keepAliveDurationNs}. + */ + public boolean isExpired(long keepAliveDurationNs) { + return isIdle() && System.nanoTime() - getIdleStartTimeNs() > keepAliveDurationNs; + } + + /** + * Returns the time in ns when this connection became idle. Undefined if + * this connection is not idle. + */ + public long getIdleStartTimeNs() { + return spdyConnection == null ? idleStartTimeNs : spdyConnection.getIdleStartTimeNs(); + } + + /** Returns the transport appropriate for this connection. */ + public Object newTransport(HttpEngine httpEngine) throws IOException { + return (spdyConnection != null) ? new SpdyTransport(httpEngine, spdyConnection) + : new HttpTransport(httpEngine, out, in); + } + + /** + * Returns true if this is a SPDY connection. Such connections can be used + * in multiple HTTP requests simultaneously. + */ + public boolean isSpdy() { + return spdyConnection != null; + } + + public SpdyConnection getSpdyConnection() { + return spdyConnection; + } + + /** + * Returns the minor HTTP version that should be used for future requests on + * this connection. Either 0 for HTTP/1.0, or 1 for HTTP/1.1. The default + * value is 1 for new connections. + */ + public int getHttpMinorVersion() { + return httpMinorVersion; + } + + public void setHttpMinorVersion(int httpMinorVersion) { + this.httpMinorVersion = httpMinorVersion; + } + + /** + * Returns true if the HTTP connection needs to tunnel one protocol over + * another, such as when using HTTPS through an HTTP proxy. When doing so, + * we must avoid buffering bytes intended for the higher-level protocol. + */ + public boolean requiresTunnel() { + return route.address.sslSocketFactory != null && route.proxy.type() == Proxy.Type.HTTP; + } + + /** + * To make an HTTPS connection over an HTTP proxy, send an unencrypted + * CONNECT request to create the proxy connection. This may need to be + * retried if the proxy requires authorization. + */ + private void makeTunnel(TunnelRequest tunnelRequest) throws IOException { + RawHeaders requestHeaders = tunnelRequest.getRequestHeaders(); + while (true) { + out.write(requestHeaders.toBytes()); + RawHeaders responseHeaders = RawHeaders.fromBytes(in); + + switch (responseHeaders.getResponseCode()) { + case HTTP_OK: + return; + case HTTP_PROXY_AUTH: + requestHeaders = new RawHeaders(requestHeaders); + URL url = new URL("https", tunnelRequest.host, tunnelRequest.port, "/"); + boolean credentialsFound = HttpAuthenticator.processAuthHeader(HTTP_PROXY_AUTH, + responseHeaders, requestHeaders, route.proxy, url); + if (credentialsFound) { + continue; + } else { + throw new IOException("Failed to authenticate with proxy"); + } + default: + throw new IOException( + "Unexpected response code for CONNECT: " + responseHeaders.getResponseCode()); + } + } + } +} http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/ConnectionPool.java ---------------------------------------------------------------------- diff --git a/framework/src/com/squareup/okhttp/ConnectionPool.java b/framework/src/com/squareup/okhttp/ConnectionPool.java new file mode 100644 index 0000000..933bd73 --- /dev/null +++ b/framework/src/com/squareup/okhttp/ConnectionPool.java @@ -0,0 +1,273 @@ +/* + * 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.squareup.okhttp; + +import com.squareup.okhttp.internal.Platform; +import com.squareup.okhttp.internal.Util; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP + * requests that share the same {@link com.squareup.okhttp.Address} may share a + * {@link com.squareup.okhttp.Connection}. This class implements the policy of + * which connections to keep open for future use. + * + *

The {@link #getDefault() system-wide default} uses system properties for + * tuning parameters: + *

    + *
  • {@code http.keepAlive} true if HTTP and SPDY connections should be + * pooled at all. Default is true. + *
  • {@code http.maxConnections} maximum number of idle connections to + * each to keep in the pool. Default is 5. + *
  • {@code http.keepAliveDuration} Time in milliseconds to keep the + * connection alive in the pool before closing it. Default is 5 minutes. + * This property isn't used by {@code HttpURLConnection}. + *
+ * + *

The default instance doesn't adjust its configuration as system + * properties are changed. This assumes that the applications that set these + * parameters do so before making HTTP connections, and that this class is + * initialized lazily. + */ +public class ConnectionPool { + private static final int MAX_CONNECTIONS_TO_CLEANUP = 2; + private static final long DEFAULT_KEEP_ALIVE_DURATION_MS = 5 * 60 * 1000; // 5 min + + private static final ConnectionPool systemDefault; + + static { + String keepAlive = System.getProperty("http.keepAlive"); + String keepAliveDuration = System.getProperty("http.keepAliveDuration"); + String maxIdleConnections = System.getProperty("http.maxConnections"); + long keepAliveDurationMs = keepAliveDuration != null ? Long.parseLong(keepAliveDuration) + : DEFAULT_KEEP_ALIVE_DURATION_MS; + if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) { + systemDefault = new ConnectionPool(0, keepAliveDurationMs); + } else if (maxIdleConnections != null) { + systemDefault = new ConnectionPool(Integer.parseInt(maxIdleConnections), keepAliveDurationMs); + } else { + systemDefault = new ConnectionPool(5, keepAliveDurationMs); + } + } + + /** The maximum number of idle connections for each address. */ + private final int maxIdleConnections; + private final long keepAliveDurationNs; + + private final LinkedList connections = new LinkedList(); + + /** We use a single background thread to cleanup expired connections. */ + private final ExecutorService executorService = + new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); + private final Callable connectionsCleanupCallable = new Callable() { + @Override public Void call() throws Exception { + List expiredConnections = new ArrayList(MAX_CONNECTIONS_TO_CLEANUP); + int idleConnectionCount = 0; + synchronized (ConnectionPool.this) { + for (ListIterator i = connections.listIterator(connections.size()); + i.hasPrevious(); ) { + Connection connection = i.previous(); + if (!connection.isAlive() || connection.isExpired(keepAliveDurationNs)) { + i.remove(); + expiredConnections.add(connection); + if (expiredConnections.size() == MAX_CONNECTIONS_TO_CLEANUP) break; + } else if (connection.isIdle()) { + idleConnectionCount++; + } + } + + for (ListIterator i = connections.listIterator(connections.size()); + i.hasPrevious() && idleConnectionCount > maxIdleConnections; ) { + Connection connection = i.previous(); + if (connection.isIdle()) { + expiredConnections.add(connection); + i.remove(); + --idleConnectionCount; + } + } + } + for (Connection expiredConnection : expiredConnections) { + Util.closeQuietly(expiredConnection); + } + return null; + } + }; + + public ConnectionPool(int maxIdleConnections, long keepAliveDurationMs) { + this.maxIdleConnections = maxIdleConnections; + this.keepAliveDurationNs = keepAliveDurationMs * 1000 * 1000; + } + + /** + * Returns a snapshot of the connections in this pool, ordered from newest to + * oldest. Waits for the cleanup callable to run if it is currently scheduled. + */ + List getConnections() { + waitForCleanupCallableToRun(); + synchronized (this) { + return new ArrayList(connections); + } + } + + /** + * Blocks until the executor service has processed all currently enqueued + * jobs. + */ + private void waitForCleanupCallableToRun() { + try { + executorService.submit(new Runnable() { + @Override public void run() { + } + }).get(); + } catch (Exception e) { + throw new AssertionError(); + } + } + + public static ConnectionPool getDefault() { + return systemDefault; + } + + /** Returns total number of connections in the pool. */ + public synchronized int getConnectionCount() { + return connections.size(); + } + + /** Returns total number of spdy connections in the pool. */ + public synchronized int getSpdyConnectionCount() { + int total = 0; + for (Connection connection : connections) { + if (connection.isSpdy()) total++; + } + return total; + } + + /** Returns total number of http connections in the pool. */ + public synchronized int getHttpConnectionCount() { + int total = 0; + for (Connection connection : connections) { + if (!connection.isSpdy()) total++; + } + return total; + } + + /** Returns a recycled connection to {@code address}, or null if no such connection exists. */ + public synchronized Connection get(Address address) { + Connection foundConnection = null; + for (ListIterator i = connections.listIterator(connections.size()); + i.hasPrevious(); ) { + Connection connection = i.previous(); + if (!connection.getRoute().getAddress().equals(address) + || !connection.isAlive() + || System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) { + continue; + } + i.remove(); + if (!connection.isSpdy()) { + try { + Platform.get().tagSocket(connection.getSocket()); + } catch (SocketException e) { + Util.closeQuietly(connection); + // When unable to tag, skip recycling and close + Platform.get().logW("Unable to tagSocket(): " + e); + continue; + } + } + foundConnection = connection; + break; + } + + if (foundConnection != null && foundConnection.isSpdy()) { + connections.addFirst(foundConnection); // Add it back after iteration. + } + + executorService.submit(connectionsCleanupCallable); + return foundConnection; + } + + /** + * Gives {@code connection} to the pool. The pool may store the connection, + * or close it, as its policy describes. + * + *

It is an error to use {@code connection} after calling this method. + */ + public void recycle(Connection connection) { + executorService.submit(connectionsCleanupCallable); + + if (connection.isSpdy()) { + return; + } + + if (!connection.isAlive()) { + Util.closeQuietly(connection); + return; + } + + try { + Platform.get().untagSocket(connection.getSocket()); + } catch (SocketException e) { + // When unable to remove tagging, skip recycling and close. + Platform.get().logW("Unable to untagSocket(): " + e); + Util.closeQuietly(connection); + return; + } + + synchronized (this) { + connections.addFirst(connection); + connection.resetIdleStartTime(); + } + } + + /** + * Shares the SPDY connection with the pool. Callers to this method may + * continue to use {@code connection}. + */ + public void maybeShare(Connection connection) { + executorService.submit(connectionsCleanupCallable); + if (!connection.isSpdy()) { + // Only SPDY connections are sharable. + return; + } + if (connection.isAlive()) { + synchronized (this) { + connections.addFirst(connection); + } + } + } + + /** Close and remove all connections in the pool. */ + public void evictAll() { + List connections; + synchronized (this) { + connections = new ArrayList(this.connections); + this.connections.clear(); + } + + for (Connection connection : connections) { + Util.closeQuietly(connection); + } + } +} http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/HttpResponseCache.java ---------------------------------------------------------------------- diff --git a/framework/src/com/squareup/okhttp/HttpResponseCache.java b/framework/src/com/squareup/okhttp/HttpResponseCache.java new file mode 100644 index 0000000..a6d380a --- /dev/null +++ b/framework/src/com/squareup/okhttp/HttpResponseCache.java @@ -0,0 +1,693 @@ +/* + * 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.squareup.okhttp; + +import com.squareup.okhttp.internal.Base64; +import com.squareup.okhttp.internal.DiskLruCache; +import com.squareup.okhttp.internal.StrictLineReader; +import com.squareup.okhttp.internal.Util; +import com.squareup.okhttp.internal.http.HttpEngine; +import com.squareup.okhttp.internal.http.HttpURLConnectionImpl; +import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl; +import com.squareup.okhttp.internal.http.OkResponseCache; +import com.squareup.okhttp.internal.http.RawHeaders; +import com.squareup.okhttp.internal.http.ResponseHeaders; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.CacheRequest; +import java.net.CacheResponse; +import java.net.HttpURLConnection; +import java.net.ResponseCache; +import java.net.SecureCacheResponse; +import java.net.URI; +import java.net.URLConnection; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLPeerUnverifiedException; + +import static com.squareup.okhttp.internal.Util.US_ASCII; +import static com.squareup.okhttp.internal.Util.UTF_8; + +/** + * Caches HTTP and HTTPS responses to the filesystem so they may be reused, + * saving time and bandwidth. + * + *

Cache Optimization

+ * To measure cache effectiveness, this class tracks three statistics: + *
    + *
  • {@link #getRequestCount() Request Count:} the number + * of HTTP requests issued since this cache was created. + *
  • {@link #getNetworkCount() Network Count:} the + * number of those requests that required network use. + *
  • {@link #getHitCount() Hit Count:} the number of + * those requests whose responses were served by the cache. + *
+ * Sometimes a request will result in a conditional cache hit. If the cache + * contains a stale copy of the response, the client will issue a conditional + * {@code GET}. The server will then send either the updated response if it has + * changed, or a short 'not modified' response if the client's copy is still + * valid. Such responses increment both the network count and hit count. + * + *

The best way to improve the cache hit rate is by configuring the web + * server to return cacheable responses. Although this client honors all HTTP/1.1 (RFC 2068) cache + * headers, it doesn't cache partial responses. + * + *

Force a Network Response

+ * In some situations, such as after a user clicks a 'refresh' button, it may be + * necessary to skip the cache, and fetch data directly from the server. To force + * a full refresh, add the {@code no-cache} directive:
   {@code
+ *         connection.addRequestProperty("Cache-Control", "no-cache");
+ * }
+ * If it is only necessary to force a cached response to be validated by the + * server, use the more efficient {@code max-age=0} instead:
   {@code
+ *         connection.addRequestProperty("Cache-Control", "max-age=0");
+ * }
+ * + *

Force a Cache Response

+ * Sometimes you'll want to show resources if they are available immediately, + * but not otherwise. This can be used so your application can show + * something while waiting for the latest data to be downloaded. To + * restrict a request to locally-cached resources, add the {@code + * only-if-cached} directive:
   {@code
+ *     try {
+ *         connection.addRequestProperty("Cache-Control", "only-if-cached");
+ *         InputStream cached = connection.getInputStream();
+ *         // the resource was cached! show it
+ *     } catch (FileNotFoundException e) {
+ *         // the resource was not cached
+ *     }
+ * }
+ * This technique works even better in situations where a stale response is + * better than no response. To permit stale cached responses, use the {@code + * max-stale} directive with the maximum staleness in seconds:
   {@code
+ *         int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
+ *         connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale);
+ * }
+ */ +public final class HttpResponseCache extends ResponseCache { + private static final char[] DIGITS = + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + // TODO: add APIs to iterate the cache? + private static final int VERSION = 201105; + private static final int ENTRY_METADATA = 0; + private static final int ENTRY_BODY = 1; + private static final int ENTRY_COUNT = 2; + + private final DiskLruCache cache; + + /* read and write statistics, all guarded by 'this' */ + private int writeSuccessCount; + private int writeAbortCount; + private int networkCount; + private int hitCount; + private int requestCount; + + /** + * Although this class only exposes the limited ResponseCache API, it + * implements the full OkResponseCache interface. This field is used as a + * package private handle to the complete implementation. It delegates to + * public and private members of this type. + */ + final OkResponseCache okResponseCache = new OkResponseCache() { + @Override public CacheResponse get(URI uri, String requestMethod, + Map> requestHeaders) throws IOException { + return HttpResponseCache.this.get(uri, requestMethod, requestHeaders); + } + + @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { + return HttpResponseCache.this.put(uri, connection); + } + + @Override public void update( + CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException { + HttpResponseCache.this.update(conditionalCacheHit, connection); + } + + @Override public void trackConditionalCacheHit() { + HttpResponseCache.this.trackConditionalCacheHit(); + } + + @Override public void trackResponse(ResponseSource source) { + HttpResponseCache.this.trackResponse(source); + } + }; + + public HttpResponseCache(File directory, long maxSize) throws IOException { + cache = DiskLruCache.open(directory, VERSION, ENTRY_COUNT, maxSize); + } + + private String uriToKey(URI uri) { + try { + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + byte[] md5bytes = messageDigest.digest(uri.toString().getBytes("UTF-8")); + return bytesToHexString(md5bytes); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } + + private static String bytesToHexString(byte[] bytes) { + char[] digits = DIGITS; + char[] buf = new char[bytes.length * 2]; + int c = 0; + for (byte b : bytes) { + buf[c++] = digits[(b >> 4) & 0xf]; + buf[c++] = digits[b & 0xf]; + } + return new String(buf); + } + + @Override public CacheResponse get(URI uri, String requestMethod, + Map> requestHeaders) { + String key = uriToKey(uri); + DiskLruCache.Snapshot snapshot; + Entry entry; + try { + snapshot = cache.get(key); + if (snapshot == null) { + return null; + } + entry = new Entry(snapshot.getInputStream(ENTRY_METADATA)); + } catch (IOException e) { + // Give up because the cache cannot be read. + return null; + } + + if (!entry.matches(uri, requestMethod, requestHeaders)) { + snapshot.close(); + return null; + } + + return entry.isHttps() ? new EntrySecureCacheResponse(entry, snapshot) + : new EntryCacheResponse(entry, snapshot); + } + + @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { + if (!(urlConnection instanceof HttpURLConnection)) { + return null; + } + + HttpURLConnection httpConnection = (HttpURLConnection) urlConnection; + String requestMethod = httpConnection.getRequestMethod(); + String key = uriToKey(uri); + + if (requestMethod.equals("POST") || requestMethod.equals("PUT") || requestMethod.equals( + "DELETE")) { + try { + cache.remove(key); + } catch (IOException ignored) { + // The cache cannot be written. + } + return null; + } else if (!requestMethod.equals("GET")) { + // Don't cache non-GET responses. We're technically allowed to cache + // HEAD requests and some POST requests, but the complexity of doing + // so is high and the benefit is low. + return null; + } + + HttpEngine httpEngine = getHttpEngine(httpConnection); + if (httpEngine == null) { + // Don't cache unless the HTTP implementation is ours. + return null; + } + + ResponseHeaders response = httpEngine.getResponseHeaders(); + if (response.hasVaryAll()) { + return null; + } + + RawHeaders varyHeaders = + httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields()); + Entry entry = new Entry(uri, varyHeaders, httpConnection); + DiskLruCache.Editor editor = null; + try { + editor = cache.edit(key); + if (editor == null) { + return null; + } + entry.writeTo(editor); + return new CacheRequestImpl(editor); + } catch (IOException e) { + abortQuietly(editor); + return null; + } + } + + private void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection) + throws IOException { + HttpEngine httpEngine = getHttpEngine(httpConnection); + URI uri = httpEngine.getUri(); + ResponseHeaders response = httpEngine.getResponseHeaders(); + RawHeaders varyHeaders = + httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields()); + Entry entry = new Entry(uri, varyHeaders, httpConnection); + DiskLruCache.Snapshot snapshot = (conditionalCacheHit instanceof EntryCacheResponse) + ? ((EntryCacheResponse) conditionalCacheHit).snapshot + : ((EntrySecureCacheResponse) conditionalCacheHit).snapshot; + DiskLruCache.Editor editor = null; + try { + editor = snapshot.edit(); // returns null if snapshot is not current + if (editor != null) { + entry.writeTo(editor); + editor.commit(); + } + } catch (IOException e) { + abortQuietly(editor); + } + } + + private void abortQuietly(DiskLruCache.Editor editor) { + // Give up because the cache cannot be written. + try { + if (editor != null) { + editor.abort(); + } + } catch (IOException ignored) { + } + } + + private HttpEngine getHttpEngine(URLConnection httpConnection) { + if (httpConnection instanceof HttpURLConnectionImpl) { + return ((HttpURLConnectionImpl) httpConnection).getHttpEngine(); + } else if (httpConnection instanceof HttpsURLConnectionImpl) { + return ((HttpsURLConnectionImpl) httpConnection).getHttpEngine(); + } else { + return null; + } + } + + /** + * Closes the cache and deletes all of its stored values. This will delete + * all files in the cache directory including files that weren't created by + * the cache. + */ + public void delete() throws IOException { + cache.delete(); + } + + public synchronized int getWriteAbortCount() { + return writeAbortCount; + } + + public synchronized int getWriteSuccessCount() { + return writeSuccessCount; + } + + private synchronized void trackResponse(ResponseSource source) { + requestCount++; + + switch (source) { + case CACHE: + hitCount++; + break; + case CONDITIONAL_CACHE: + case NETWORK: + networkCount++; + break; + } + } + + private synchronized void trackConditionalCacheHit() { + hitCount++; + } + + public synchronized int getNetworkCount() { + return networkCount; + } + + public synchronized int getHitCount() { + return hitCount; + } + + public synchronized int getRequestCount() { + return requestCount; + } + + private final class CacheRequestImpl extends CacheRequest { + private final DiskLruCache.Editor editor; + private OutputStream cacheOut; + private boolean done; + private OutputStream body; + + public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException { + this.editor = editor; + this.cacheOut = editor.newOutputStream(ENTRY_BODY); + this.body = new FilterOutputStream(cacheOut) { + @Override public void close() throws IOException { + synchronized (HttpResponseCache.this) { + if (done) { + return; + } + done = true; + writeSuccessCount++; + } + super.close(); + editor.commit(); + } + + @Override + public void write(byte[] buffer, int offset, int length) throws IOException { + // Since we don't override "write(int oneByte)", we can write directly to "out" + // and avoid the inefficient implementation from the FilterOutputStream. + out.write(buffer, offset, length); + } + }; + } + + @Override public void abort() { + synchronized (HttpResponseCache.this) { + if (done) { + return; + } + done = true; + writeAbortCount++; + } + Util.closeQuietly(cacheOut); + try { + editor.abort(); + } catch (IOException ignored) { + } + } + + @Override public OutputStream getBody() throws IOException { + return body; + } + } + + private static final class Entry { + private final String uri; + private final RawHeaders varyHeaders; + private final String requestMethod; + private final RawHeaders responseHeaders; + private final String cipherSuite; + private final Certificate[] peerCertificates; + private final Certificate[] localCertificates; + + /** + * Reads an entry from an input stream. A typical entry looks like this: + *
{@code
+     *   http://google.com/foo
+     *   GET
+     *   2
+     *   Accept-Language: fr-CA
+     *   Accept-Charset: UTF-8
+     *   HTTP/1.1 200 OK
+     *   3
+     *   Content-Type: image/png
+     *   Content-Length: 100
+     *   Cache-Control: max-age=600
+     * }
+ * + *

A typical HTTPS file looks like this: + *

{@code
+     *   https://google.com/foo
+     *   GET
+     *   2
+     *   Accept-Language: fr-CA
+     *   Accept-Charset: UTF-8
+     *   HTTP/1.1 200 OK
+     *   3
+     *   Content-Type: image/png
+     *   Content-Length: 100
+     *   Cache-Control: max-age=600
+     *
+     *   AES_256_WITH_MD5
+     *   2
+     *   base64-encoded peerCertificate[0]
+     *   base64-encoded peerCertificate[1]
+     *   -1
+     * }
+ * The file is newline separated. The first two lines are the URL and + * the request method. Next is the number of HTTP Vary request header + * lines, followed by those lines. + * + *

Next is the response status line, followed by the number of HTTP + * response header lines, followed by those lines. + * + *

HTTPS responses also contain SSL session information. This begins + * with a blank line, and then a line containing the cipher suite. Next + * is the length of the peer certificate chain. These certificates are + * base64-encoded and appear each on their own line. The next line + * contains the length of the local certificate chain. These + * certificates are also base64-encoded and appear each on their own + * line. A length of -1 is used to encode a null array. + */ + public Entry(InputStream in) throws IOException { + try { + StrictLineReader reader = new StrictLineReader(in, US_ASCII); + uri = reader.readLine(); + requestMethod = reader.readLine(); + varyHeaders = new RawHeaders(); + int varyRequestHeaderLineCount = reader.readInt(); + for (int i = 0; i < varyRequestHeaderLineCount; i++) { + varyHeaders.addLine(reader.readLine()); + } + + responseHeaders = new RawHeaders(); + responseHeaders.setStatusLine(reader.readLine()); + int responseHeaderLineCount = reader.readInt(); + for (int i = 0; i < responseHeaderLineCount; i++) { + responseHeaders.addLine(reader.readLine()); + } + + if (isHttps()) { + String blank = reader.readLine(); + if (blank.length() > 0) { + throw new IOException("expected \"\" but was \"" + blank + "\""); + } + cipherSuite = reader.readLine(); + peerCertificates = readCertArray(reader); + localCertificates = readCertArray(reader); + } else { + cipherSuite = null; + peerCertificates = null; + localCertificates = null; + } + } finally { + in.close(); + } + } + + public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection) + throws IOException { + this.uri = uri.toString(); + this.varyHeaders = varyHeaders; + this.requestMethod = httpConnection.getRequestMethod(); + this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields(), true); + + if (isHttps()) { + HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection; + cipherSuite = httpsConnection.getCipherSuite(); + Certificate[] peerCertificatesNonFinal = null; + try { + peerCertificatesNonFinal = httpsConnection.getServerCertificates(); + } catch (SSLPeerUnverifiedException ignored) { + } + peerCertificates = peerCertificatesNonFinal; + localCertificates = httpsConnection.getLocalCertificates(); + } else { + cipherSuite = null; + peerCertificates = null; + localCertificates = null; + } + } + + public void writeTo(DiskLruCache.Editor editor) throws IOException { + OutputStream out = editor.newOutputStream(ENTRY_METADATA); + Writer writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8)); + + writer.write(uri + '\n'); + writer.write(requestMethod + '\n'); + writer.write(Integer.toString(varyHeaders.length()) + '\n'); + for (int i = 0; i < varyHeaders.length(); i++) { + writer.write(varyHeaders.getFieldName(i) + ": " + varyHeaders.getValue(i) + '\n'); + } + + writer.write(responseHeaders.getStatusLine() + '\n'); + writer.write(Integer.toString(responseHeaders.length()) + '\n'); + for (int i = 0; i < responseHeaders.length(); i++) { + writer.write(responseHeaders.getFieldName(i) + ": " + responseHeaders.getValue(i) + '\n'); + } + + if (isHttps()) { + writer.write('\n'); + writer.write(cipherSuite + '\n'); + writeCertArray(writer, peerCertificates); + writeCertArray(writer, localCertificates); + } + writer.close(); + } + + private boolean isHttps() { + return uri.startsWith("https://"); + } + + private Certificate[] readCertArray(StrictLineReader reader) throws IOException { + int length = reader.readInt(); + if (length == -1) { + return null; + } + try { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + Certificate[] result = new Certificate[length]; + for (int i = 0; i < result.length; i++) { + String line = reader.readLine(); + byte[] bytes = Base64.decode(line.getBytes("US-ASCII")); + result[i] = certificateFactory.generateCertificate(new ByteArrayInputStream(bytes)); + } + return result; + } catch (CertificateException e) { + throw new IOException(e.getMessage()); + } + } + + private void writeCertArray(Writer writer, Certificate[] certificates) throws IOException { + if (certificates == null) { + writer.write("-1\n"); + return; + } + try { + writer.write(Integer.toString(certificates.length) + '\n'); + for (Certificate certificate : certificates) { + byte[] bytes = certificate.getEncoded(); + String line = Base64.encode(bytes); + writer.write(line + '\n'); + } + } catch (CertificateEncodingException e) { + throw new IOException(e.getMessage()); + } + } + + public boolean matches(URI uri, String requestMethod, + Map> requestHeaders) { + return this.uri.equals(uri.toString()) + && this.requestMethod.equals(requestMethod) + && new ResponseHeaders(uri, responseHeaders).varyMatches(varyHeaders.toMultimap(false), + requestHeaders); + } + } + + /** + * Returns an input stream that reads the body of a snapshot, closing the + * snapshot when the stream is closed. + */ + private static InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) { + return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) { + @Override public void close() throws IOException { + snapshot.close(); + super.close(); + } + }; + } + + static class EntryCacheResponse extends CacheResponse { + private final Entry entry; + private final DiskLruCache.Snapshot snapshot; + private final InputStream in; + + public EntryCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) { + this.entry = entry; + this.snapshot = snapshot; + this.in = newBodyInputStream(snapshot); + } + + @Override public Map> getHeaders() { + return entry.responseHeaders.toMultimap(true); + } + + @Override public InputStream getBody() { + return in; + } + } + + static class EntrySecureCacheResponse extends SecureCacheResponse { + private final Entry entry; + private final DiskLruCache.Snapshot snapshot; + private final InputStream in; + + public EntrySecureCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) { + this.entry = entry; + this.snapshot = snapshot; + this.in = newBodyInputStream(snapshot); + } + + @Override public Map> getHeaders() { + return entry.responseHeaders.toMultimap(true); + } + + @Override public InputStream getBody() { + return in; + } + + @Override public String getCipherSuite() { + return entry.cipherSuite; + } + + @Override public List getServerCertificateChain() + throws SSLPeerUnverifiedException { + if (entry.peerCertificates == null || entry.peerCertificates.length == 0) { + throw new SSLPeerUnverifiedException(null); + } + return Arrays.asList(entry.peerCertificates.clone()); + } + + @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + if (entry.peerCertificates == null || entry.peerCertificates.length == 0) { + throw new SSLPeerUnverifiedException(null); + } + return ((X509Certificate) entry.peerCertificates[0]).getSubjectX500Principal(); + } + + @Override public List getLocalCertificateChain() { + if (entry.localCertificates == null || entry.localCertificates.length == 0) { + return null; + } + return Arrays.asList(entry.localCertificates.clone()); + } + + @Override public Principal getLocalPrincipal() { + if (entry.localCertificates == null || entry.localCertificates.length == 0) { + return null; + } + return ((X509Certificate) entry.localCertificates[0]).getSubjectX500Principal(); + } + } +} http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/OkHttpClient.java ---------------------------------------------------------------------- diff --git a/framework/src/com/squareup/okhttp/OkHttpClient.java b/framework/src/com/squareup/okhttp/OkHttpClient.java new file mode 100644 index 0000000..7834bd6 --- /dev/null +++ b/framework/src/com/squareup/okhttp/OkHttpClient.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2012 Square, Inc. + * + * 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.squareup.okhttp; + +import com.squareup.okhttp.internal.http.HttpURLConnectionImpl; +import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl; +import com.squareup.okhttp.internal.http.OkResponseCache; +import com.squareup.okhttp.internal.http.OkResponseCacheAdapter; +import java.net.CookieHandler; +import java.net.HttpURLConnection; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.ResponseCache; +import java.net.URL; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; + +/** Configures and creates HTTP connections. */ +public final class OkHttpClient { + private Proxy proxy; + private Set failedRoutes = Collections.synchronizedSet(new LinkedHashSet()); + private ProxySelector proxySelector; + private CookieHandler cookieHandler; + private ResponseCache responseCache; + private SSLSocketFactory sslSocketFactory; + private HostnameVerifier hostnameVerifier; + private ConnectionPool connectionPool; + private boolean followProtocolRedirects = true; + + /** + * Sets the HTTP proxy that will be used by connections created by this + * client. This takes precedence over {@link #setProxySelector}, which is + * only honored when this proxy is null (which it is by default). To disable + * proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}. + */ + public OkHttpClient setProxy(Proxy proxy) { + this.proxy = proxy; + return this; + } + + public Proxy getProxy() { + return proxy; + } + + /** + * Sets the proxy selection policy to be used if no {@link #setProxy proxy} + * is specified explicitly. The proxy selector may return multiple proxies; + * in that case they will be tried in sequence until a successful connection + * is established. + * + *

If unset, the {@link ProxySelector#getDefault() system-wide default} + * proxy selector will be used. + */ + public OkHttpClient setProxySelector(ProxySelector proxySelector) { + this.proxySelector = proxySelector; + return this; + } + + public ProxySelector getProxySelector() { + return proxySelector; + } + + /** + * Sets the cookie handler to be used to read outgoing cookies and write + * incoming cookies. + * + *

If unset, the {@link CookieHandler#getDefault() system-wide default} + * cookie handler will be used. + */ + public OkHttpClient setCookieHandler(CookieHandler cookieHandler) { + this.cookieHandler = cookieHandler; + return this; + } + + public CookieHandler getCookieHandler() { + return cookieHandler; + } + + /** + * Sets the response cache to be used to read and write cached responses. + * + *

If unset, the {@link ResponseCache#getDefault() system-wide default} + * response cache will be used. + */ + public OkHttpClient setResponseCache(ResponseCache responseCache) { + this.responseCache = responseCache; + return this; + } + + public ResponseCache getResponseCache() { + return responseCache; + } + + private OkResponseCache okResponseCache() { + if (responseCache instanceof HttpResponseCache) { + return ((HttpResponseCache) responseCache).okResponseCache; + } else if (responseCache != null) { + return new OkResponseCacheAdapter(responseCache); + } else { + return null; + } + } + + /** + * Sets the socket factory used to secure HTTPS connections. + * + *

If unset, the {@link HttpsURLConnection#getDefaultSSLSocketFactory() + * system-wide default} SSL socket factory will be used. + */ + public OkHttpClient setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + return this; + } + + public SSLSocketFactory getSslSocketFactory() { + return sslSocketFactory; + } + + /** + * Sets the verifier used to confirm that response certificates apply to + * requested hostnames for HTTPS connections. + * + *

If unset, the {@link HttpsURLConnection#getDefaultHostnameVerifier() + * system-wide default} hostname verifier will be used. + */ + public OkHttpClient setHostnameVerifier(HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + return this; + } + + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; + } + + /** + * Sets the connection pool used to recycle HTTP and HTTPS connections. + * + *

If unset, the {@link ConnectionPool#getDefault() system-wide + * default} connection pool will be used. + */ + public OkHttpClient setConnectionPool(ConnectionPool connectionPool) { + this.connectionPool = connectionPool; + return this; + } + + public ConnectionPool getConnectionPool() { + return connectionPool; + } + + /** + * Configure this client to follow redirects from HTTPS to HTTP and from HTTP + * to HTTPS. + * + *

If unset, protocol redirects will be followed. This is different than + * the built-in {@code HttpURLConnection}'s default. + */ + public OkHttpClient setFollowProtocolRedirects(boolean followProtocolRedirects) { + this.followProtocolRedirects = followProtocolRedirects; + return this; + } + + public boolean getFollowProtocolRedirects() { + return followProtocolRedirects; + } + + public HttpURLConnection open(URL url) { + String protocol = url.getProtocol(); + OkHttpClient copy = copyWithDefaults(); + if (protocol.equals("http")) { + return new HttpURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes); + } else if (protocol.equals("https")) { + return new HttpsURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes); + } else { + throw new IllegalArgumentException("Unexpected protocol: " + protocol); + } + } + + /** + * Returns a shallow copy of this OkHttpClient that uses the system-wide default for + * each field that hasn't been explicitly configured. + */ + private OkHttpClient copyWithDefaults() { + OkHttpClient result = new OkHttpClient(); + result.proxy = proxy; + result.failedRoutes = failedRoutes; + result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault(); + result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault(); + result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault(); + result.sslSocketFactory = sslSocketFactory != null + ? sslSocketFactory + : HttpsURLConnection.getDefaultSSLSocketFactory(); + result.hostnameVerifier = hostnameVerifier != null + ? hostnameVerifier + : HttpsURLConnection.getDefaultHostnameVerifier(); + result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault(); + result.followProtocolRedirects = followProtocolRedirects; + return result; + } +} http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/OkResponseCache.java ---------------------------------------------------------------------- diff --git a/framework/src/com/squareup/okhttp/OkResponseCache.java b/framework/src/com/squareup/okhttp/OkResponseCache.java new file mode 100644 index 0000000..b7e3801 --- /dev/null +++ b/framework/src/com/squareup/okhttp/OkResponseCache.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012 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.squareup.okhttp; + +import java.io.IOException; +import java.net.CacheResponse; +import java.net.HttpURLConnection; + +/** + * A response cache that supports statistics tracking and updating stored + * responses. Implementations of {@link java.net.ResponseCache} should implement + * this interface to receive additional support from the HTTP engine. + */ +public interface OkResponseCache { + + /** Track an HTTP response being satisfied by {@code source}. */ + void trackResponse(ResponseSource source); + + /** Track an conditional GET that was satisfied by this cache. */ + void trackConditionalCacheHit(); + + /** Updates stored HTTP headers using a hit on a conditional GET. */ + void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection) + throws IOException; +} http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/ResponseSource.java ---------------------------------------------------------------------- diff --git a/framework/src/com/squareup/okhttp/ResponseSource.java b/framework/src/com/squareup/okhttp/ResponseSource.java new file mode 100644 index 0000000..4eca172 --- /dev/null +++ b/framework/src/com/squareup/okhttp/ResponseSource.java @@ -0,0 +1,37 @@ +/* + * 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.squareup.okhttp; + +/** The source of an HTTP response. */ +public enum ResponseSource { + + /** The response was returned from the local cache. */ + CACHE, + + /** + * The response is available in the cache but must be validated with the + * network. The cache result will be used if it is still valid; otherwise + * the network's response will be used. + */ + CONDITIONAL_CACHE, + + /** The response was returned from the network. */ + NETWORK; + + public boolean requiresConnection() { + return this == CONDITIONAL_CACHE || this == NETWORK; + } +} http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/Route.java ---------------------------------------------------------------------- diff --git a/framework/src/com/squareup/okhttp/Route.java b/framework/src/com/squareup/okhttp/Route.java new file mode 100644 index 0000000..6968c60 --- /dev/null +++ b/framework/src/com/squareup/okhttp/Route.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2013 Square, Inc. + * + * 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.squareup.okhttp; + +import java.net.InetSocketAddress; +import java.net.Proxy; + +/** Represents the route used by a connection to reach an endpoint. */ +public class Route { + final Address address; + final Proxy proxy; + final InetSocketAddress inetSocketAddress; + final boolean modernTls; + + public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress, + boolean modernTls) { + if (address == null) throw new NullPointerException("address == null"); + if (proxy == null) throw new NullPointerException("proxy == null"); + if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null"); + this.address = address; + this.proxy = proxy; + this.inetSocketAddress = inetSocketAddress; + this.modernTls = modernTls; + } + + /** Returns the {@link Address} of this route. */ + public Address getAddress() { + return address; + } + + /** + * Returns the {@link Proxy} of this route. + * + * Warning: This may be different than the proxy returned + * by {@link #getAddress}! That is the proxy that the user asked to be + * connected to; this returns the proxy that they were actually connected + * to. The two may disagree when a proxy selector selects a different proxy + * for a connection. + */ + public Proxy getProxy() { + return proxy; + } + + /** Returns the {@link InetSocketAddress} of this route. */ + public InetSocketAddress getSocketAddress() { + return inetSocketAddress; + } + + /** Returns true if this route uses modern tls. */ + public boolean isModernTls() { + return modernTls; + } + + /** Returns a copy of this route with flipped tls mode. */ + public Route flipTlsMode() { + return new Route(address, proxy, inetSocketAddress, !modernTls); + } + + @Override public boolean equals(Object obj) { + if (obj instanceof Route) { + Route other = (Route) obj; + return (address.equals(other.address) + && proxy.equals(other.proxy) + && inetSocketAddress.equals(other.inetSocketAddress) + && modernTls == other.modernTls); + } + return false; + } + + @Override public int hashCode() { + int result = 17; + result = 31 * result + address.hashCode(); + result = 31 * result + proxy.hashCode(); + result = 31 * result + inetSocketAddress.hashCode(); + result = result + (modernTls ? (31 * result) : 0); + return result; + } +} http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/TunnelRequest.java ---------------------------------------------------------------------- diff --git a/framework/src/com/squareup/okhttp/TunnelRequest.java b/framework/src/com/squareup/okhttp/TunnelRequest.java new file mode 100644 index 0000000..5260b87 --- /dev/null +++ b/framework/src/com/squareup/okhttp/TunnelRequest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 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.squareup.okhttp; + +import com.squareup.okhttp.internal.http.RawHeaders; + +import static com.squareup.okhttp.internal.Util.getDefaultPort; + +/** + * Routing and authentication information sent to an HTTP proxy to create a + * HTTPS to an origin server. Everything in the tunnel request is sent + * unencrypted to the proxy server. + * + *

See RFC 2817, Section + * 5.2. + */ +public final class TunnelRequest { + final String host; + final int port; + final String userAgent; + final String proxyAuthorization; + + /** + * @param host the origin server's hostname. Not null. + * @param port the origin server's port, like 80 or 443. + * @param userAgent the client's user-agent. Not null. + * @param proxyAuthorization proxy authorization, or null if the proxy is + * used without an authorization header. + */ + public TunnelRequest(String host, int port, String userAgent, String proxyAuthorization) { + if (host == null) throw new NullPointerException("host == null"); + if (userAgent == null) throw new NullPointerException("userAgent == null"); + this.host = host; + this.port = port; + this.userAgent = userAgent; + this.proxyAuthorization = proxyAuthorization; + } + + /** + * If we're creating a TLS tunnel, send only the minimum set of headers. + * This avoids sending potentially sensitive data like HTTP cookies to + * the proxy unencrypted. + */ + RawHeaders getRequestHeaders() { + RawHeaders result = new RawHeaders(); + result.setRequestLine("CONNECT " + host + ":" + port + " HTTP/1.1"); + + // Always set Host and User-Agent. + result.set("Host", port == getDefaultPort("https") ? host : (host + ":" + port)); + result.set("User-Agent", userAgent); + + // Copy over the Proxy-Authorization header if it exists. + if (proxyAuthorization != null) { + result.set("Proxy-Authorization", proxyAuthorization); + } + + // Always set the Proxy-Connection to Keep-Alive for the benefit of + // HTTP/1.0 proxies like Squid. + result.set("Proxy-Connection", "Keep-Alive"); + return result; + } +} http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java ---------------------------------------------------------------------- diff --git a/framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java b/framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java new file mode 100644 index 0000000..78c9691 --- /dev/null +++ b/framework/src/com/squareup/okhttp/internal/AbstractOutputStream.java @@ -0,0 +1,45 @@ +/* + * 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.squareup.okhttp.internal; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * An output stream for an HTTP request body. + * + *

Since a single socket's output stream may be used to write multiple HTTP + * requests to the same server, subclasses should not close the socket stream. + */ +public abstract class AbstractOutputStream extends OutputStream { + protected boolean closed; + + @Override public final void write(int data) throws IOException { + write(new byte[] { (byte) data }); + } + + protected final void checkNotClosed() throws IOException { + if (closed) { + throw new IOException("stream closed"); + } + } + + /** Returns true if this stream was closed locally. */ + public boolean isClosed() { + return closed; + } +} http://git-wip-us.apache.org/repos/asf/cordova-amazon-fireos/blob/b3b7c0b9/framework/src/com/squareup/okhttp/internal/Base64.java ---------------------------------------------------------------------- diff --git a/framework/src/com/squareup/okhttp/internal/Base64.java b/framework/src/com/squareup/okhttp/internal/Base64.java new file mode 100644 index 0000000..79cd020 --- /dev/null +++ b/framework/src/com/squareup/okhttp/internal/Base64.java @@ -0,0 +1,164 @@ +/* + * 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. + */ + +/** + * @author Alexander Y. Kleymenov + */ + +package com.squareup.okhttp.internal; + +import java.io.UnsupportedEncodingException; + +import static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY; + +/** + * Base64 encoder/decoder. + * In violation of the RFC, this encoder doesn't wrap lines at 76 columns. + */ +public final class Base64 { + private Base64() { + } + + public static byte[] decode(byte[] in) { + return decode(in, in.length); + } + + public static byte[] decode(byte[] in, int len) { + // approximate output length + int length = len / 4 * 3; + // return an empty array on empty or short input without padding + if (length == 0) { + return EMPTY_BYTE_ARRAY; + } + // temporary array + byte[] out = new byte[length]; + // number of padding characters ('=') + int pad = 0; + byte chr; + // compute the number of the padding characters + // and adjust the length of the input + for (; ; len--) { + chr = in[len - 1]; + // skip the neutral characters + if ((chr == '\n') || (chr == '\r') || (chr == ' ') || (chr == '\t')) { + continue; + } + if (chr == '=') { + pad++; + } else { + break; + } + } + // index in the output array + int outIndex = 0; + // index in the input array + int inIndex = 0; + // holds the value of the input character + int bits = 0; + // holds the value of the input quantum + int quantum = 0; + for (int i = 0; i < len; i++) { + chr = in[i]; + // skip the neutral characters + if ((chr == '\n') || (chr == '\r') || (chr == ' ') || (chr == '\t')) { + continue; + } + if ((chr >= 'A') && (chr <= 'Z')) { + // char ASCII value + // A 65 0 + // Z 90 25 (ASCII - 65) + bits = chr - 65; + } else if ((chr >= 'a') && (chr <= 'z')) { + // char ASCII value + // a 97 26 + // z 122 51 (ASCII - 71) + bits = chr - 71; + } else if ((chr >= '0') && (chr <= '9')) { + // char ASCII value + // 0 48 52 + // 9 57 61 (ASCII + 4) + bits = chr + 4; + } else if (chr == '+') { + bits = 62; + } else if (chr == '/') { + bits = 63; + } else { + return null; + } + // append the value to the quantum + quantum = (quantum << 6) | (byte) bits; + if (inIndex % 4 == 3) { + // 4 characters were read, so make the output: + out[outIndex++] = (byte) (quantum >> 16); + out[outIndex++] = (byte) (quantum >> 8); + out[outIndex++] = (byte) quantum; + } + inIndex++; + } + if (pad > 0) { + // adjust the quantum value according to the padding + quantum = quantum << (6 * pad); + // make output + out[outIndex++] = (byte) (quantum >> 16); + if (pad == 1) { + out[outIndex++] = (byte) (quantum >> 8); + } + } + // create the resulting array + byte[] result = new byte[outIndex]; + System.arraycopy(out, 0, result, 0, outIndex); + return result; + } + + private static final byte[] MAP = new byte[] { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', '+', '/' + }; + + public static String encode(byte[] in) { + int length = (in.length + 2) * 4 / 3; + byte[] out = new byte[length]; + int index = 0, end = in.length - in.length % 3; + for (int i = 0; i < end; i += 3) { + out[index++] = MAP[(in[i] & 0xff) >> 2]; + out[index++] = MAP[((in[i] & 0x03) << 4) | ((in[i + 1] & 0xff) >> 4)]; + out[index++] = MAP[((in[i + 1] & 0x0f) << 2) | ((in[i + 2] & 0xff) >> 6)]; + out[index++] = MAP[(in[i + 2] & 0x3f)]; + } + switch (in.length % 3) { + case 1: + out[index++] = MAP[(in[end] & 0xff) >> 2]; + out[index++] = MAP[(in[end] & 0x03) << 4]; + out[index++] = '='; + out[index++] = '='; + break; + case 2: + out[index++] = MAP[(in[end] & 0xff) >> 2]; + out[index++] = MAP[((in[end] & 0x03) << 4) | ((in[end + 1] & 0xff) >> 4)]; + out[index++] = MAP[((in[end + 1] & 0x0f) << 2)]; + out[index++] = '='; + break; + } + try { + return new String(out, 0, index, "US-ASCII"); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + } +}