@@ -0,0 +1,54 @@
/*
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.
*/



buildscript {
repositories {
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:0.10.+'
}
}

apply plugin: 'android-library'

android {
compileSdkVersion 19
buildToolsVersion "19.0.0"

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}

sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
}
@@ -0,0 +1,16 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "ant.properties", and override values to adapt the script to your
# project structure.

# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
target=android-19
apk-configurations=
renderscript.opt.level=O0
android.library=true
@@ -0,0 +1,140 @@
/*
* 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.Util;
import java.net.Proxy;
import java.net.UnknownHostException;
import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;

import static com.squareup.okhttp.internal.Util.equal;

/**
* A specification for a connection to an origin server. For simple connections,
* this is the server's hostname and port. If an explicit proxy is requested (or
* {@link Proxy#NO_PROXY no proxy} is explicitly requested), this also includes
* that proxy information. For secure connections the address also includes the
* SSL socket factory and hostname verifier.
*
* <p>HTTP requests that share the same {@code Address} may also share the same
* {@link Connection}.
*/
public final class Address {
final Proxy proxy;
final String uriHost;
final int uriPort;
final SSLSocketFactory sslSocketFactory;
final HostnameVerifier hostnameVerifier;
final OkAuthenticator authenticator;
final List<String> transports;

public Address(String uriHost, int uriPort, SSLSocketFactory sslSocketFactory,
HostnameVerifier hostnameVerifier, OkAuthenticator authenticator, Proxy proxy,
List<String> transports) throws UnknownHostException {
if (uriHost == null) throw new NullPointerException("uriHost == null");
if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort);
if (authenticator == null) throw new IllegalArgumentException("authenticator == null");
if (transports == null) throw new IllegalArgumentException("transports == null");
this.proxy = proxy;
this.uriHost = uriHost;
this.uriPort = uriPort;
this.sslSocketFactory = sslSocketFactory;
this.hostnameVerifier = hostnameVerifier;
this.authenticator = authenticator;
this.transports = Util.immutableList(transports);
}

/** Returns the hostname of the origin server. */
public String getUriHost() {
return uriHost;
}

/**
* Returns the port of the origin server; typically 80 or 443. Unlike
* may {@code getPort()} accessors, this method never returns -1.
*/
public int getUriPort() {
return uriPort;
}

/**
* Returns the SSL socket factory, or null if this is not an HTTPS
* address.
*/
public SSLSocketFactory getSslSocketFactory() {
return sslSocketFactory;
}

/**
* Returns the hostname verifier, or null if this is not an HTTPS
* address.
*/
public HostnameVerifier getHostnameVerifier() {
return hostnameVerifier;
}


/**
* Returns the client's authenticator. This method never returns null.
*/
public OkAuthenticator getAuthenticator() {
return authenticator;
}

/**
* Returns the client's transports. This method always returns a non-null list
* that contains "http/1.1", possibly among other transports.
*/
public List<String> getTransports() {
return transports;
}

/**
* Returns this address's explicitly-specified HTTP proxy, or null to
* delegate to the HTTP client's proxy selector.
*/
public Proxy getProxy() {
return proxy;
}

@Override public boolean equals(Object other) {
if (other instanceof Address) {
Address that = (Address) other;
return equal(this.proxy, that.proxy)
&& this.uriHost.equals(that.uriHost)
&& this.uriPort == that.uriPort
&& equal(this.sslSocketFactory, that.sslSocketFactory)
&& equal(this.hostnameVerifier, that.hostnameVerifier)
&& equal(this.authenticator, that.authenticator)
&& equal(this.transports, that.transports);
}
return false;
}

@Override public int hashCode() {
int result = 17;
result = 31 * result + uriHost.hashCode();
result = 31 * result + uriPort;
result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
result = 31 * result + (authenticator != null ? authenticator.hashCode() : 0);
result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
result = 31 * result + transports.hashCode();
return result;
}
}
@@ -0,0 +1,335 @@
/*
* 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.SocketTimeoutException;
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.
*
* <p>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}.
*
* <p>Do not confuse this class with the misnamed {@code HttpURLConnection},
* which isn't so much a connection as a single request/response exchange.
*
* <h3>Modern TLS</h3>
* There are tradeoffs when selecting which options to include when negotiating
* a secure connection to a remote host. Newer TLS options are quite useful:
* <ul>
* <li>Server Name Indication (SNI) enables one IP address to negotiate secure
* connections for multiple domain names.
* <li>Next Protocol Negotiation (NPN) enables the HTTPS port (443) to be used
* for both HTTP and SPDY transports.
* </ul>
* 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");

socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();
Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout);
socket.setSoTimeout(readTimeout);
in = socket.getInputStream();
out = socket.getOutputStream();

if (route.address.sslSocketFactory != null) {
upgradeToTls(tunnelRequest);
} else {
streamWrapper();
}
connected = true;
}

/**
* 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);
}

boolean useNpn = route.modernTls && route.address.transports.contains("spdy/3");
if (useNpn) {
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();
streamWrapper();

byte[] selectedProtocol;
if (useNpn && (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();
spdyConnection.sendConnectionHeader();
} 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();
}

/**
* Returns true if we are confident that we can read data from this
* connection. This is more expensive and more accurate than {@link
* #isAlive()}; callers should check {@link #isAlive()} first.
*/
public boolean isReadable() {
if (!(in instanceof BufferedInputStream)) {
return true; // Optimistic.
}
if (isSpdy()) {
return true; // Optimistic. We can't test SPDY because its streams are in use.
}
BufferedInputStream bufferedInputStream = (BufferedInputStream) in;
try {
int readTimeout = socket.getSoTimeout();
try {
socket.setSoTimeout(1);
bufferedInputStream.mark(1);
if (bufferedInputStream.read() == -1) {
return false; // Stream is exhausted; socket is closed.
}
bufferedInputStream.reset();
return true;
} finally {
socket.setSoTimeout(readTimeout);
}
} catch (SocketTimeoutException ignored) {
return true; // Read timed out; socket is good.
} catch (IOException e) {
return false; // Couldn't read; socket is closed.
}
}

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 getIdleStartTimeNs() < System.nanoTime() - 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;
}

public void updateReadTimeout(int newTimeout) throws IOException {
if (!connected) throw new IllegalStateException("updateReadTimeout - not connected");
socket.setSoTimeout(newTimeout);
}

/**
* 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(
route.address.authenticator, 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());
}
}
}

private void streamWrapper() throws IOException {
in = new BufferedInputStream(in, 4096);
out = new BufferedOutputStream(out, 256);
}
}
@@ -0,0 +1,274 @@
/*
* 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.
*
* <p>The {@link #getDefault() system-wide default} uses system properties for
* tuning parameters:
* <ul>
* <li>{@code http.keepAlive} true if HTTP and SPDY connections should be
* pooled at all. Default is true.
* <li>{@code http.maxConnections} maximum number of idle connections to
* each to keep in the pool. Default is 5.
* <li>{@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}.
* </ul>
*
* <p>The default instance <i>doesn't</i> 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<Connection> connections = new LinkedList<Connection>();

/** We use a single background thread to cleanup expired connections. */
private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
Util.daemonThreadFactory("OkHttp ConnectionPool"));
private final Callable<Void> connectionsCleanupCallable = new Callable<Void>() {
@Override public Void call() throws Exception {
List<Connection> expiredConnections = new ArrayList<Connection>(MAX_CONNECTIONS_TO_CLEANUP);
int idleConnectionCount = 0;
synchronized (ConnectionPool.this) {
for (ListIterator<Connection> 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<Connection> 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<Connection> getConnections() {
waitForCleanupCallableToRun();
synchronized (this) {
return new ArrayList<Connection>(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<Connection> 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.
*
* <p>It is an error to use {@code connection} after calling this method.
*/
public void recycle(Connection connection) {
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();
}

executorService.submit(connectionsCleanupCallable);
}

/**
* 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<Connection> connections;
synchronized (this) {
connections = new ArrayList<Connection>(this.connections);
this.connections.clear();
}

for (Connection connection : connections) {
Util.closeQuietly(connection);
}
}
}
@@ -0,0 +1,86 @@
/*
* 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 com.squareup.okhttp.internal.http.ResponseHeaders;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

final class Dispatcher {
// TODO: thread pool size should be configurable; possibly configurable per host.
private final ThreadPoolExecutor executorService = new ThreadPoolExecutor(
8, 8, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
private final Map<Object, List<Job>> enqueuedJobs = new LinkedHashMap<Object, List<Job>>();

public synchronized void enqueue(
OkHttpClient client, Request request, Response.Receiver responseReceiver) {
Job job = new Job(this, client, request, responseReceiver);
List<Job> jobsForTag = enqueuedJobs.get(request.tag());
if (jobsForTag == null) {
jobsForTag = new ArrayList<Job>(2);
enqueuedJobs.put(request.tag(), jobsForTag);
}
jobsForTag.add(job);
executorService.execute(job);
}

public synchronized void cancel(Object tag) {
List<Job> jobs = enqueuedJobs.remove(tag);
if (jobs == null) return;
for (Job job : jobs) {
executorService.remove(job);
}
}

synchronized void finished(Job job) {
List<Job> jobs = enqueuedJobs.get(job.tag());
if (jobs != null) jobs.remove(job);
}

static class RealResponseBody extends Response.Body {
private final ResponseHeaders responseHeaders;
private final InputStream in;

RealResponseBody(ResponseHeaders responseHeaders, InputStream in) {
this.responseHeaders = responseHeaders;
this.in = in;
}

@Override public boolean ready() throws IOException {
return true;
}

@Override public MediaType contentType() {
String contentType = responseHeaders.getContentType();
return contentType != null ? MediaType.parse(contentType) : null;
}

@Override public long contentLength() {
return responseHeaders.getContentLength();
}

@Override public InputStream byteStream() throws IOException {
return in;
}
}
}
@@ -0,0 +1,59 @@
/*
* 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;

/**
* A failure attempting to retrieve an HTTP response.
*
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
* This class is in beta. APIs are subject to change!
*/
/* OkHttp 2.0: public */ class Failure {
private final Request request;
private final Throwable exception;

private Failure(Builder builder) {
this.request = builder.request;
this.exception = builder.exception;
}

public Request request() {
return request;
}

public Throwable exception() {
return exception;
}

public static class Builder {
private Request request;
private Throwable exception;

public Builder request(Request request) {
this.request = request;
return this;
}

public Builder exception(Throwable exception) {
this.exception = exception;
return this;
}

public Failure build() {
return new Failure(this);
}
}
}

Large diffs are not rendered by default.

@@ -0,0 +1,232 @@
/*
* 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 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.HttpsEngine;
import com.squareup.okhttp.internal.http.Policy;
import com.squareup.okhttp.internal.http.RawHeaders;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.URL;

import static com.squareup.okhttp.internal.Util.getEffectivePort;
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_MOVED_PERM;
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_MOVED_TEMP;
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_MULT_CHOICE;
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_PROXY_AUTH;
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_SEE_OTHER;
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_TEMP_REDIRECT;
import static com.squareup.okhttp.internal.http.HttpURLConnectionImpl.HTTP_UNAUTHORIZED;

final class Job implements Runnable, Policy {
private final Dispatcher dispatcher;
private final OkHttpClient client;
private final Response.Receiver responseReceiver;

/** The request; possibly a consequence of redirects or auth headers. */
private Request request;

public Job(Dispatcher dispatcher, OkHttpClient client, Request request,
Response.Receiver responseReceiver) {
this.dispatcher = dispatcher;
this.client = client;
this.request = request;
this.responseReceiver = responseReceiver;
}

@Override public int getChunkLength() {
return request.body().contentLength() == -1 ? HttpTransport.DEFAULT_CHUNK_LENGTH : -1;
}

@Override public long getFixedContentLength() {
return request.body().contentLength();
}

@Override public boolean getUseCaches() {
return false; // TODO.
}

@Override public HttpURLConnection getHttpConnectionToCache() {
return null;
}

@Override public URL getURL() {
return request.url();
}

@Override public long getIfModifiedSince() {
return 0; // For HttpURLConnection only. We let the cache drive this.
}

@Override public boolean usingProxy() {
return false; // We let the connection decide this.
}

@Override public void setSelectedProxy(Proxy proxy) {
// Do nothing.
}

Object tag() {
return request.tag();
}

@Override public void run() {
try {
Response response = execute();
responseReceiver.onResponse(response);
} catch (IOException e) {
responseReceiver.onFailure(new Failure.Builder()
.request(request)
.exception(e)
.build());
} finally {
// TODO: close the response body
// TODO: release the HTTP engine (potentially multiple!)
dispatcher.finished(this);
}
}

private Response execute() throws IOException {
Connection connection = null;
Response redirectedBy = null;

while (true) {
HttpEngine engine = newEngine(connection);

Request.Body body = request.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType == null) throw new IllegalStateException("contentType == null");
if (engine.getRequestHeaders().getContentType() == null) {
engine.getRequestHeaders().setContentType(contentType.toString());
}
}

engine.sendRequest();

if (body != null) {
body.writeTo(engine.getRequestBody());
}

engine.readResponse();

int responseCode = engine.getResponseCode();
Dispatcher.RealResponseBody responseBody = new Dispatcher.RealResponseBody(
engine.getResponseHeaders(), engine.getResponseBody());

Response response = new Response.Builder(request, responseCode)
.rawHeaders(engine.getResponseHeaders().getHeaders())
.body(responseBody)
.redirectedBy(redirectedBy)
.build();

Request redirect = processResponse(engine, response);

if (redirect == null) {
engine.automaticallyReleaseConnectionToPool();
return response;
}

// TODO: fail if too many redirects
// TODO: fail if not following redirects
// TODO: release engine

connection = sameConnection(request, redirect) ? engine.getConnection() : null;
redirectedBy = response;
request = redirect;
}
}

HttpEngine newEngine(Connection connection) throws IOException {
String protocol = request.url().getProtocol();
RawHeaders requestHeaders = request.rawHeaders();
if (protocol.equals("http")) {
return new HttpEngine(client, this, request.method(), requestHeaders, connection, null);
} else if (protocol.equals("https")) {
return new HttpsEngine(client, this, request.method(), requestHeaders, connection, null);
} else {
throw new AssertionError();
}
}

/**
* Figures out the HTTP request to make in response to receiving {@code
* response}. This will either add authentication headers or follow
* redirects. If a follow-up is either unnecessary or not applicable, this
* returns null.
*/
private Request processResponse(HttpEngine engine, Response response) throws IOException {
Request request = response.request();
Proxy selectedProxy = engine.getConnection() != null
? engine.getConnection().getRoute().getProxy()
: client.getProxy();
int responseCode = response.code();

switch (responseCode) {
case HTTP_PROXY_AUTH:
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
}
// fall-through
case HTTP_UNAUTHORIZED:
RawHeaders successorRequestHeaders = request.rawHeaders();
boolean credentialsFound = HttpAuthenticator.processAuthHeader(client.getAuthenticator(),
response.code(), response.rawHeaders(), successorRequestHeaders, selectedProxy,
this.request.url());
return credentialsFound
? request.newBuilder().rawHeaders(successorRequestHeaders).build()
: null;

case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
case HTTP_TEMP_REDIRECT:
String method = request.method();
if (responseCode == HTTP_TEMP_REDIRECT && !method.equals("GET") && !method.equals("HEAD")) {
// "If the 307 status code is received in response to a request other than GET or HEAD,
// the user agent MUST NOT automatically redirect the request"
return null;
}

String location = response.header("Location");
if (location == null) {
return null;
}

URL url = new URL(request.url(), location);
if (!url.getProtocol().equals("https") && !url.getProtocol().equals("http")) {
return null; // Don't follow redirects to unsupported protocols.
}

return this.request.newBuilder().url(url).build();

default:
return null;
}
}

private boolean sameConnection(Request a, Request b) {
return a.url().getHost().equals(b.url().getHost())
&& getEffectivePort(a.url()) == getEffectivePort(b.url())
&& a.url().getProtocol().equals(b.url().getProtocol());
}
}
@@ -0,0 +1,120 @@
/*
* 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.nio.charset.Charset;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* An <a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a> Media Type,
* appropriate to describe the content type of an HTTP request or response body.
*/
public final class MediaType {
private static final String TOKEN = "([a-zA-Z0-9-!#$%&'*+.^_`{|}~]+)";
private static final String QUOTED = "\"([^\"]*)\"";
private static final Pattern TYPE_SUBTYPE = Pattern.compile(TOKEN + "/" + TOKEN);
private static final Pattern PARAMETER = Pattern.compile(
";\\s*" + TOKEN + "=(?:" + TOKEN + "|" + QUOTED + ")");

private final String mediaType;
private final String type;
private final String subtype;
private final String charset;

private MediaType(String mediaType, String type, String subtype, String charset) {
this.mediaType = mediaType;
this.type = type;
this.subtype = subtype;
this.charset = charset;
}

/**
* Returns a media type for {@code string}, or null if {@code string} is not a
* well-formed media type.
*/
public static MediaType parse(String string) {
Matcher typeSubtype = TYPE_SUBTYPE.matcher(string);
if (!typeSubtype.lookingAt()) return null;
String type = typeSubtype.group(1).toLowerCase(Locale.US);
String subtype = typeSubtype.group(2).toLowerCase(Locale.US);

String charset = null;
Matcher parameter = PARAMETER.matcher(string);
for (int s = typeSubtype.end(); s < string.length(); s = parameter.end()) {
parameter.region(s, string.length());
if (!parameter.lookingAt()) return null; // This is not a well-formed media type.

String name = parameter.group(1);
if (name == null || !name.equalsIgnoreCase("charset")) continue;
if (charset != null) throw new IllegalArgumentException("Multiple charsets: " + string);
charset = parameter.group(2) != null
? parameter.group(2) // Value is a token.
: parameter.group(3); // Value is a quoted string.
}

return new MediaType(string, type, subtype, charset);
}

/**
* Returns the high-level media type, such as "text", "image", "audio",
* "video", or "application".
*/
public String type() {
return type;
}

/**
* Returns a specific media subtype, such as "plain" or "png", "mpeg",
* "mp4" or "xml".
*/
public String subtype() {
return subtype;
}

/**
* Returns the charset of this media type, or null if this media type doesn't
* specify a charset.
*/
public Charset charset() {
return charset != null ? Charset.forName(charset) : null;
}

/**
* Returns the charset of this media type, or {@code defaultValue} if this
* media type doesn't specify a charset.
*/
public Charset charset(Charset defaultValue) {
return charset != null ? Charset.forName(charset) : defaultValue;
}

/**
* Returns the encoded media type, like "text/plain; charset=utf-8",
* appropriate for use in a Content-Type header.
*/
@Override public String toString() {
return mediaType;
}

@Override public boolean equals(Object o) {
return o instanceof MediaType && ((MediaType) o).mediaType.equals(mediaType);
}

@Override public int hashCode() {
return mediaType.hashCode();
}
}
@@ -0,0 +1,123 @@
/*
* 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 com.squareup.okhttp.internal.Base64;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Proxy;
import java.net.URL;
import java.util.List;

/**
* Responds to authentication challenges from the remote web or proxy server by
* returning credentials.
*/
public interface OkAuthenticator {
/**
* Returns a credential that satisfies the authentication challenge made by
* {@code url}. Returns null if the challenge cannot be satisfied. This method
* is called in response to an HTTP 401 unauthorized status code sent by the
* origin server.
*
* @param challenges parsed "WWW-Authenticate" challenge headers from the HTTP
* response.
*/
Credential authenticate(Proxy proxy, URL url, List<Challenge> challenges) throws IOException;

/**
* Returns a credential that satisfies the authentication challenge made by
* {@code proxy}. Returns null if the challenge cannot be satisfied. This
* method is called in response to an HTTP 401 unauthorized status code sent
* by the proxy server.
*
* @param challenges parsed "Proxy-Authenticate" challenge headers from the
* HTTP response.
*/
Credential authenticateProxy(Proxy proxy, URL url, List<Challenge> challenges) throws IOException;

/** An RFC 2617 challenge. */
public final class Challenge {
private final String scheme;
private final String realm;

public Challenge(String scheme, String realm) {
this.scheme = scheme;
this.realm = realm;
}

/** Returns the authentication scheme, like {@code Basic}. */
public String getScheme() {
return scheme;
}

/** Returns the protection space. */
public String getRealm() {
return realm;
}

@Override public boolean equals(Object o) {
return o instanceof Challenge
&& ((Challenge) o).scheme.equals(scheme)
&& ((Challenge) o).realm.equals(realm);
}

@Override public int hashCode() {
return scheme.hashCode() + 31 * realm.hashCode();
}

@Override public String toString() {
return scheme + " realm=\"" + realm + "\"";
}
}

/** An RFC 2617 credential. */
public final class Credential {
private final String headerValue;

private Credential(String headerValue) {
this.headerValue = headerValue;
}

/** Returns an auth credential for the Basic scheme. */
public static Credential basic(String userName, String password) {
try {
String usernameAndPassword = userName + ":" + password;
byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1");
String encoded = Base64.encode(bytes);
return new Credential("Basic " + encoded);
} catch (UnsupportedEncodingException e) {
throw new AssertionError();
}
}

public String getHeaderValue() {
return headerValue;
}

@Override public boolean equals(Object o) {
return o instanceof Credential && ((Credential) o).headerValue.equals(headerValue);
}

@Override public int hashCode() {
return headerValue.hashCode();
}

@Override public String toString() {
return headerValue;
}
}
}

Large diffs are not rendered by default.

@@ -0,0 +1,56 @@
/*
* 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.io.IOException;
import java.net.CacheRequest;
import java.net.CacheResponse;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;

/**
* An extended response cache API. Unlike {@link java.net.ResponseCache}, this
* interface supports conditional caching and statistics.
*
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
* This class is in beta. APIs are subject to change!
*/
public interface OkResponseCache {
CacheResponse get(URI uri, String requestMethod, Map<String, List<String>> requestHeaders)
throws IOException;

CacheRequest put(URI uri, URLConnection urlConnection) throws IOException;

/** Remove any cache entries for the supplied {@code uri} if the request method invalidates. */
void maybeRemove(String requestMethod, URI uri) throws IOException;

/**
* Handles a conditional request hit by updating the stored cache response
* with the headers from {@code httpConnection}. The cached response body is
* not updated. If the stored response has changed since {@code
* conditionalCacheHit} was returned, this does nothing.
*/
void update(CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException;

/** Track an conditional GET that was satisfied by this cache. */
void trackConditionalCacheHit();

/** Track an HTTP response being satisfied by {@code source}. */
void trackResponse(ResponseSource source);
}
@@ -0,0 +1,284 @@
/*
* 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 com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.http.RawHeaders;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Set;

/**
* An HTTP request. Instances of this class are immutable if their {@link #body}
* is null or itself immutable.
*
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
* This class is in beta. APIs are subject to change!
*/
/* OkHttp 2.0: public */ final class Request {
private final URL url;
private final String method;
private final RawHeaders headers;
private final Body body;
private final Object tag;

private Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = new RawHeaders(builder.headers);
this.body = builder.body;
this.tag = builder.tag != null ? builder.tag : this;
}

public URL url() {
return url;
}

public String urlString() {
return url.toString();
}

public String method() {
return method;
}

public String header(String name) {
return headers.get(name);
}

public List<String> headers(String name) {
return headers.values(name);
}

public Set<String> headerNames() {
return headers.names();
}

RawHeaders rawHeaders() {
return new RawHeaders(headers);
}

public int headerCount() {
return headers.length();
}

public String headerName(int index) {
return headers.getFieldName(index);
}

public String headerValue(int index) {
return headers.getValue(index);
}

public Body body() {
return body;
}

public Object tag() {
return tag;
}

Builder newBuilder() {
return new Builder(url)
.method(method, body)
.rawHeaders(headers)
.tag(tag);
}

public abstract static class Body {
/** Returns the Content-Type header for this body. */
public abstract MediaType contentType();

/**
* Returns the number of bytes that will be written to {@code out} in a call
* to {@link #writeTo}, or -1 if that count is unknown.
*/
public long contentLength() {
return -1;
}

/** Writes the content of this request to {@code out}. */
public abstract void writeTo(OutputStream out) throws IOException;

/**
* Returns a new request body that transmits {@code content}. If {@code
* contentType} lacks a charset, this will use UTF-8.
*/
public static Body create(MediaType contentType, String content) {
contentType = contentType.charset() != null
? contentType
: MediaType.parse(contentType + "; charset=utf-8");
try {
byte[] bytes = content.getBytes(contentType.charset().name());
return create(contentType, bytes);
} catch (UnsupportedEncodingException e) {
throw new AssertionError();
}
}

/** Returns a new request body that transmits {@code content}. */
public static Body create(final MediaType contentType, final byte[] content) {
if (contentType == null) throw new NullPointerException("contentType == null");
if (content == null) throw new NullPointerException("content == null");

return new Body() {
@Override public MediaType contentType() {
return contentType;
}

@Override public long contentLength() {
return content.length;
}

@Override public void writeTo(OutputStream out) throws IOException {
out.write(content);
}
};
}

/** Returns a new request body that transmits the content of {@code file}. */
public static Body create(final MediaType contentType, final File file) {
if (contentType == null) throw new NullPointerException("contentType == null");
if (file == null) throw new NullPointerException("content == null");

return new Body() {
@Override public MediaType contentType() {
return contentType;
}

@Override public long contentLength() {
return file.length();
}

@Override public void writeTo(OutputStream out) throws IOException {
long length = contentLength();
if (length == 0) return;

InputStream in = null;
try {
in = new FileInputStream(file);
byte[] buffer = new byte[(int) Math.min(8192, length)];
for (int c; (c = in.read(buffer)) != -1; ) {
out.write(buffer, 0, c);
}
} finally {
Util.closeQuietly(in);
}
}
};
}
}

public static class Builder {
private URL url;
private String method = "GET";
private RawHeaders headers = new RawHeaders();
private Body body;
private Object tag;

public Builder(String url) {
url(url);
}

public Builder(URL url) {
url(url);
}

public Builder url(String url) {
try {
this.url = new URL(url);
return this;
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Malformed URL: " + url);
}
}

public Builder url(URL url) {
if (url == null) throw new IllegalStateException("url == null");
this.url = url;
return this;
}

/**
* Sets the header named {@code name} to {@code value}. If this request
* already has any headers with that name, they are all replaced.
*/
public Builder header(String name, String value) {
headers.set(name, value);
return this;
}

/**
* Adds a header with {@code name} and {@code value}. Prefer this method for
* multiply-valued headers like "Cookie".
*/
public Builder addHeader(String name, String value) {
headers.add(name, value);
return this;
}

Builder rawHeaders(RawHeaders rawHeaders) {
headers = new RawHeaders(rawHeaders);
return this;
}

public Builder get() {
return method("GET", null);
}

public Builder head() {
return method("HEAD", null);
}

public Builder post(Body body) {
return method("POST", body);
}

public Builder put(Body body) {
return method("PUT", body);
}

public Builder method(String method, Body body) {
if (method == null || method.length() == 0) {
throw new IllegalArgumentException("method == null || method.length() == 0");
}
this.method = method;
this.body = body;
return this;
}

/**
* Attaches {@code tag} to the request. It can be used later to cancel the
* request. If the tag is unspecified or null, the request is canceled by
* using the request itself as the tag.
*/
public Builder tag(Object tag) {
this.tag = tag;
return this;
}

public Request build() {
return new Request(this);
}
}
}
@@ -0,0 +1,290 @@
/*
* 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 com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.http.RawHeaders;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Set;

import static com.squareup.okhttp.internal.Util.UTF_8;

/**
* An HTTP response. Instances of this class are not immutable: the response
* body is a one-shot value that may be consumed only once. All other properties
* are immutable.
*
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
* This class is in beta. APIs are subject to change!
*/
/* OkHttp 2.0: public */ final class Response {
private final Request request;
private final int code;
private final RawHeaders headers;
private final Body body;
private final Response redirectedBy;

private Response(Builder builder) {
this.request = builder.request;
this.code = builder.code;
this.headers = new RawHeaders(builder.headers);
this.body = builder.body;
this.redirectedBy = builder.redirectedBy;
}

/**
* The wire-level request that initiated this HTTP response. This is usually
* <strong>not</strong> the same request instance provided to the HTTP client:
* <ul>
* <li>It may be transformed by the HTTP client. For example, the client
* may have added its own {@code Content-Encoding} header to enable
* response compression.
* <li>It may be the request generated in response to an HTTP redirect.
* In this case the request URL may be different than the initial
* request URL.
* </ul>
*/
public Request request() {
return request;
}

public int code() {
return code;
}

public String header(String name) {
return header(name, null);
}

public String header(String name, String defaultValue) {
String result = headers.get(name);
return result != null ? result : defaultValue;
}

public List<String> headers(String name) {
return headers.values(name);
}

public Set<String> headerNames() {
return headers.names();
}

public int headerCount() {
return headers.length();
}

public String headerName(int index) {
return headers.getFieldName(index);
}

RawHeaders rawHeaders() {
return new RawHeaders(headers);
}

public String headerValue(int index) {
return headers.getValue(index);
}

public Body body() {
return body;
}

/**
* Returns the response for the HTTP redirect that triggered this response, or
* null if this response wasn't triggered by an automatic redirect. The body
* of the returned response should not be read because it has already been
* consumed by the redirecting client.
*/
public Response redirectedBy() {
return redirectedBy;
}

public abstract static class Body {
/** Multiple calls to {@link #charStream()} must return the same instance. */
private Reader reader;

/**
* Returns true if further data from this response body should be read at
* this time. For asynchronous transports like SPDY and HTTP/2.0, this will
* return false once all locally-available body bytes have been read.
*
* <p>Clients with many concurrent downloads can use this method to reduce
* the number of idle threads blocking on reads. See {@link
* Receiver#onResponse} for details.
*/
// <h3>Body.ready() vs. InputStream.available()</h3>
// TODO: Can we fix response bodies to implement InputStream.available well?
// The deflater implementation is broken by default but we could do better.
public abstract boolean ready() throws IOException;

public abstract MediaType contentType();

/**
* Returns the number of bytes in that will returned by {@link #bytes}, or
* {@link #byteStream}, or -1 if unknown.
*/
public abstract long contentLength();

public abstract InputStream byteStream() throws IOException;

public final byte[] bytes() throws IOException {
long contentLength = contentLength();
if (contentLength > Integer.MAX_VALUE) {
throw new IOException("Cannot buffer entire body for content length: " + contentLength);
}

if (contentLength != -1) {
byte[] content = new byte[(int) contentLength];
InputStream in = byteStream();
Util.readFully(in, content);
if (in.read() != -1) throw new IOException("Content-Length and stream length disagree");
return content;

} else {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Util.copy(byteStream(), out);
return out.toByteArray();
}
}

/**
* Returns the response as a character stream decoded with the charset
* of the Content-Type header. If that header is either absent or lacks a
* charset, this will attempt to decode the response body as UTF-8.
*/
public final Reader charStream() throws IOException {
if (reader == null) {
reader = new InputStreamReader(byteStream(), charset());
}
return reader;
}

/**
* Returns the response as a string decoded with the charset of the
* Content-Type header. If that header is either absent or lacks a charset,
* this will attempt to decode the response body as UTF-8.
*/
public final String string() throws IOException {
return new String(bytes(), charset().name());
}

private Charset charset() {
MediaType contentType = contentType();
return contentType != null ? contentType.charset(UTF_8) : UTF_8;
}
}

public interface Receiver {
/**
* Called when the request could not be executed due to a connectivity
* problem or timeout. Because networks can fail during an exchange, it is
* possible that the remote server accepted the request before the failure.
*/
void onFailure(Failure failure);

/**
* Called when the HTTP response was successfully returned by the remote
* server. The receiver may proceed to read the response body with the
* response's {@link #body} method.
*
* <p>Note that transport-layer success (receiving a HTTP response code,
* headers and body) does not necessarily indicate application-layer
* success: {@code response} may still indicate an unhappy HTTP response
* code like 404 or 500.
*
* <h3>Non-blocking responses</h3>
*
* <p>Receivers do not need to block while waiting for the response body to
* download. Instead, they can get called back as data arrives. Use {@link
* Body#ready} to check if bytes should be read immediately. While there is
* data ready, read it. If there isn't, return false: receivers will be
* called back with {@code onResponse()} as additional data is downloaded.
*
* <p>Return true to indicate that the receiver has finished handling the
* response body. If the response body has unread data, it will be
* discarded.
*
* <p>When the response body has been fully consumed the returned value is
* undefined.
*
* <p>The current implementation of {@link Body#ready} always returns true
* when the underlying transport is HTTP/1. This results in blocking on that
* transport. For effective non-blocking your server must support SPDY or
* HTTP/2.
*/
boolean onResponse(Response response) throws IOException;
}

public static class Builder {
private final Request request;
private final int code;
private RawHeaders headers = new RawHeaders();
private Body body;
private Response redirectedBy;

public Builder(Request request, int code) {
if (request == null) throw new IllegalArgumentException("request == null");
if (code <= 0) throw new IllegalArgumentException("code <= 0");
this.request = request;
this.code = code;
}

/**
* Sets the header named {@code name} to {@code value}. If this request
* already has any headers with that name, they are all replaced.
*/
public Builder header(String name, String value) {
headers.set(name, value);
return this;
}

/**
* Adds a header with {@code name} and {@code value}. Prefer this method for
* multiply-valued headers like "Set-Cookie".
*/
public Builder addHeader(String name, String value) {
headers.add(name, value);
return this;
}

Builder rawHeaders(RawHeaders rawHeaders) {
headers = new RawHeaders(rawHeaders);
return this;
}

public Builder body(Body body) {
this.body = body;
return this;
}

public Builder redirectedBy(Response redirectedBy) {
this.redirectedBy = redirectedBy;
return this;
}

public Response build() {
if (request == null) throw new IllegalStateException("Response has no request.");
if (code == -1) throw new IllegalStateException("Response has no code.");
return new Response(this);
}
}
}
@@ -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;
}
}
@@ -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.
*
* <strong>Warning:</strong> 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. */
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;
}
}
@@ -0,0 +1,57 @@
/*
* 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.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.net.ssl.SSLHandshakeException;

/**
* A blacklist of failed routes to avoid when creating a new connection to a
* target address. This is used so that OkHttp can learn from its mistakes: if
* there was a failure attempting to connect to a specific IP address, proxy
* server or TLS mode, that failure is remembered and alternate routes are
* preferred.
*/
public final class RouteDatabase {
private final Set<Route> failedRoutes = new LinkedHashSet<Route>();

/** Records a failure connecting to {@code failedRoute}. */
public synchronized void failed(Route failedRoute, IOException failure) {
failedRoutes.add(failedRoute);

if (!(failure instanceof SSLHandshakeException)) {
// If the problem was not related to SSL then it will also fail with
// a different TLS mode therefore we can be proactive about it.
failedRoutes.add(failedRoute.flipTlsMode());
}
}

/** Records success connecting to {@code failedRoute}. */
public synchronized void connected(Route route) {
failedRoutes.remove(route);
}

/** Returns true if {@code route} has failed recently and should be avoided. */
public synchronized boolean shouldPostpone(Route route) {
return failedRoutes.contains(route);
}

public synchronized int failedRoutesCount() {
return failedRoutes.size();
}
}
@@ -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.
*
* <p>See <a href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817, Section
* 5.2</a>.
*/
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;
}
}
@@ -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.
*
* <p>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;
}
}
@@ -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;

/**
* <a href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a> 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);
}
}
}