diff options
author | jwilson <jwilson@squareup.com> | 2012-12-26 11:37:43 -0700 |
---|---|---|
committer | jwilson <jwilson@squareup.com> | 2013-01-03 13:05:16 -0700 |
commit | 2231db3e6bb54447a9b14cf004a6cb03c373651c (patch) | |
tree | 43f315e962b8a70d081fea0644c3770dbb5104fd | |
parent | abe10a6415358d66bb0d1ac3145c8909a327a54d (diff) | |
download | android_external_okhttp-2231db3e6bb54447a9b14cf004a6cb03c373651c.tar.gz android_external_okhttp-2231db3e6bb54447a9b14cf004a6cb03c373651c.tar.bz2 android_external_okhttp-2231db3e6bb54447a9b14cf004a6cb03c373651c.zip |
Upgrade to the latest OkHttp.
Note the internal package name has changed from libcore.net.http
to com.squareup.okhttp.
Change-Id: Ib0eb4bdd69f24f2a255975460bd001e59657ddcc
86 files changed, 5088 insertions, 6462 deletions
@@ -15,18 +15,25 @@ # LOCAL_PATH := $(call my-dir) +okhttp_src_files := $(call all-java-files-under,src/main/java) +okhttp_src_files := $(filter-out %/Platform.java, $(okhttp_src_files)) +okhttp_src_files += $(call all-java-files-under, android/main/java) + include $(CLEAR_VARS) LOCAL_MODULE := okhttp LOCAL_MODULE_TAGS := optional - -# Include all files under our snapshot except for Libcore.java -# because it contains OpenJDK dependencies. This file is replaced -# by an android specific version, see below. -LOCAL_SRC_FILES := $(call all-java-files-under, src/main/java) -LOCAL_SRC_FILES := $(filter-out %/Libcore.java, $(LOCAL_SRC_FILES)) - -LOCAL_SRC_FILES += $(call all-java-files-under, android/main/java) -LOCAL_SDK_VERSION := 16 - -LOCAL_JARJAR_RULES := ${LOCAL_PATH}/jarjar-rules.txt +LOCAL_SRC_FILES := $(okhttp_src_files) +LOCAL_JAVACFLAGS := -encoding UTF-8 +LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt include $(BUILD_JAVA_LIBRARY) + +ifeq ($(WITH_HOST_DALVIK),true) + include $(CLEAR_VARS) + LOCAL_MODULE := okhttp-hostdex + LOCAL_MODULE_TAGS := optional + LOCAL_SRC_FILES := $(okhttp_src_files) + LOCAL_JAVACFLAGS := -encoding UTF-8 + LOCAL_BUILD_HOST_DEX := true + LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt + include $(BUILD_HOST_JAVA_LIBRARY) +endif diff --git a/android/main/java/com/squareup/okhttp/HttpHandler.java b/android/main/java/com/squareup/okhttp/HttpHandler.java new file mode 100644 index 0000000..c960160 --- /dev/null +++ b/android/main/java/com/squareup/okhttp/HttpHandler.java @@ -0,0 +1,41 @@ +/* + * 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 java.io.IOException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +public final class HttpHandler extends URLStreamHandler { + @Override protected URLConnection openConnection(URL url) throws IOException { + return new OkHttpClient().open(url); + } + + @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException { + if (url == null || proxy == null) { + throw new IllegalArgumentException("url == null || proxy == null"); + } + return new OkHttpClient().setProxy(proxy).open(url); + } + + @Override protected int getDefaultPort() { + return 80; + } +} diff --git a/src/main/java/libcore/net/http/HttpsHandler.java b/android/main/java/com/squareup/okhttp/HttpsHandler.java index ed9ba72..1dc3826 100644 --- a/src/main/java/libcore/net/http/HttpsHandler.java +++ b/android/main/java/com/squareup/okhttp/HttpsHandler.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp; import java.io.IOException; import java.net.Proxy; @@ -24,16 +24,15 @@ import java.net.URLConnection; import java.net.URLStreamHandler; public final class HttpsHandler extends URLStreamHandler { - @Override protected URLConnection openConnection(URL url) throws IOException { - return new HttpsURLConnectionImpl(url, getDefaultPort()); + return new OkHttpClient().open(url); } @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException { if (url == null || proxy == null) { throw new IllegalArgumentException("url == null || proxy == null"); } - return new HttpsURLConnectionImpl(url, getDefaultPort(), proxy); + return new OkHttpClient().setProxy(proxy).open(url); } @Override protected int getDefaultPort() { diff --git a/android/main/java/com/squareup/okhttp/internal/Platform.java b/android/main/java/com/squareup/okhttp/internal/Platform.java new file mode 100644 index 0000000..7479de0 --- /dev/null +++ b/android/main/java/com/squareup/okhttp/internal/Platform.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2012 Square, Inc. + * 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.internal; + +import dalvik.system.SocketTagger; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import javax.net.ssl.SSLSocket; +import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; + +/** + * Access to proprietary Android APIs. Doesn't use reflection. + */ +public final class Platform { + private static final Platform PLATFORM = new Platform(); + + public static Platform get() { + return PLATFORM; + } + + public void logW(String warning) { + System.logW(warning); + } + + public void tagSocket(Socket socket) throws SocketException { + SocketTagger.get().tag(socket); + } + + public void untagSocket(Socket socket) throws SocketException { + SocketTagger.get().untag(socket); + } + + public URI toUriLenient(URL url) throws URISyntaxException { + return url.toURILenient(); + } + + public void enableTlsExtensions(SSLSocket socket, String uriHost) { + if (socket instanceof OpenSSLSocketImpl) { + OpenSSLSocketImpl openSSLSocket = (OpenSSLSocketImpl) socket; + openSSLSocket.setUseSessionTickets(true); + openSSLSocket.setHostname(uriHost); + } + } + + public void supportTlsIntolerantServer(SSLSocket socket) { + socket.setEnabledProtocols(new String[]{"SSLv3"}); + } + + /** + * Returns the negotiated protocol, or null if no protocol was negotiated. + */ + public byte[] getNpnSelectedProtocol(SSLSocket socket) { + return socket instanceof OpenSSLSocketImpl + ? ((OpenSSLSocketImpl) socket).getNpnSelectedProtocol() + : null; + } + + /** + * Sets client-supported protocols on a socket to send to a server. The + * protocols are only sent if the socket implementation supports NPN. + */ + public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) { + if (socket instanceof OpenSSLSocketImpl) { + ((OpenSSLSocketImpl) socket).setNpnProtocols(npnProtocols); + } + } + + /** + * Returns a deflater output stream that supports SYNC_FLUSH for SPDY name + * value blocks. This throws an {@link UnsupportedOperationException} on + * Java 6 and earlier where there is no built-in API to do SYNC_FLUSH. + */ + public OutputStream newDeflaterOutputStream( + OutputStream out, Deflater deflater, boolean syncFlush) { + return new DeflaterOutputStream(out, deflater, syncFlush); + } +} diff --git a/android/main/java/libcore/util/Libcore.java b/android/main/java/libcore/util/Libcore.java deleted file mode 100644 index c1cf069..0000000 --- a/android/main/java/libcore/util/Libcore.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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 libcore.util; - -import javax.net.ssl.SSLSocket; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.Socket; -import java.net.SocketException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.ByteOrder; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; - -/** - * APIs for interacting with Android's core library. The main purpose of this - * class is to access hidden methods on - * - * org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl - * - * via reflection. - */ -public final class Libcore { - - private Libcore() { - } - - private static final Class<?> openSslSocketClass; - private static final Method setUseSessionTickets; - private static final Method setHostname; - private static final Method setNpnProtocols; - private static final Method getNpnSelectedProtocol; - private static final Constructor<DeflaterOutputStream> deflaterOutputStreamConstructor; - - static { - try { - openSslSocketClass = Class.forName( - "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl"); - setUseSessionTickets = openSslSocketClass.getMethod( - "setUseSessionTickets", boolean.class); - setHostname = openSslSocketClass.getMethod("setHostname", String.class); - setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class); - getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol"); - deflaterOutputStreamConstructor = DeflaterOutputStream.class.getConstructor( - new Class[] { OutputStream.class, Deflater.class, boolean.class }); - } catch (ClassNotFoundException cnfe) { - throw new RuntimeException(cnfe); - } catch (NoSuchMethodException nsme) { - throw new RuntimeException(nsme); - } - } - - public static DeflaterOutputStream newDeflaterOutputStream( - OutputStream os, Deflater deflater, boolean syncFlush) { - try { - return deflaterOutputStreamConstructor.newInstance(os, deflater, syncFlush); - } catch (InstantiationException e) { - throw new RuntimeException("Unknown DeflaterOutputStream implementation."); - } catch (IllegalAccessException e) { - throw new RuntimeException("Unknown DeflaterOutputStream implementation."); - } catch (InvocationTargetException e) { - throw new RuntimeException("Unknown DeflaterOutputStream implementation."); - } - } - - public static void makeTlsTolerant(SSLSocket socket, String socketHost, boolean tlsTolerant) { - if (!tlsTolerant) { - socket.setEnabledProtocols(new String[] {"SSLv3"}); - return; - } - - if (openSslSocketClass.isInstance(socket)) { - try { - setUseSessionTickets.invoke(socket, true); - setHostname.invoke(socket, socketHost); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - } else { - throw new RuntimeException("Unknown socket implementation."); - } - } - - /** - * Returns the negotiated protocol, or null if no protocol was negotiated. - */ - public static byte[] getNpnSelectedProtocol(SSLSocket socket) { - if (openSslSocketClass.isInstance(socket)) { - try { - return (byte[]) getNpnSelectedProtocol.invoke(socket); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - } else { - throw new RuntimeException("Unknown socket implementation."); - } - } - - public static void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) { - if (openSslSocketClass.isInstance(socket)) { - try { - setNpnProtocols.invoke(socket, new Object[] {npnProtocols}); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - } else { - throw new RuntimeException("Unknown socket implementation."); - } - } - - public static void deleteIfExists(File file) throws IOException { - // okhttp-changed: was Libcore.os.remove() in a try/catch block - file.delete(); - } - - public static void logW(String warning) { - // okhttp-changed: was System.logw() - System.out.println(warning); - } - - public static int getEffectivePort(URI uri) { - return getEffectivePort(uri.getScheme(), uri.getPort()); - } - - public static int getEffectivePort(URL url) { - return getEffectivePort(url.getProtocol(), url.getPort()); - } - - private static int getEffectivePort(String scheme, int specifiedPort) { - return specifiedPort != -1 - ? specifiedPort - : getDefaultPort(scheme); - } - - public static int getDefaultPort(String scheme) { - if ("http".equalsIgnoreCase(scheme)) { - return 80; - } else if ("https".equalsIgnoreCase(scheme)) { - return 443; - } else { - return -1; - } - } - - public static void checkOffsetAndCount(int arrayLength, int offset, int count) { - if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) { - throw new ArrayIndexOutOfBoundsException(); - } - } - - public static void tagSocket(Socket socket) { - } - - public static void untagSocket(Socket socket) throws SocketException { - } - - public static URI toUriLenient(URL url) throws URISyntaxException { - return url.toURI(); // this isn't as good as the built-in toUriLenient - } - - public static void pokeInt(byte[] dst, int offset, int value, ByteOrder order) { - if (order == ByteOrder.BIG_ENDIAN) { - dst[offset++] = (byte) ((value >> 24) & 0xff); - dst[offset++] = (byte) ((value >> 16) & 0xff); - dst[offset++] = (byte) ((value >> 8) & 0xff); - dst[offset ] = (byte) ((value >> 0) & 0xff); - } else { - dst[offset++] = (byte) ((value >> 0) & 0xff); - dst[offset++] = (byte) ((value >> 8) & 0xff); - dst[offset++] = (byte) ((value >> 16) & 0xff); - dst[offset ] = (byte) ((value >> 24) & 0xff); - } - } -} diff --git a/jarjar-rules.txt b/jarjar-rules.txt index 83c5233..1629f1c 100644 --- a/jarjar-rules.txt +++ b/jarjar-rules.txt @@ -1 +1 @@ -rule libcore.** libcoreunbundled.@1 +rule com.squareup.** com.android.@1 diff --git a/src/main/java/com/squareup/okhttp/Address.java b/src/main/java/com/squareup/okhttp/Address.java new file mode 100644 index 0000000..430eff5 --- /dev/null +++ b/src/main/java/com/squareup/okhttp/Address.java @@ -0,0 +1,112 @@ +/* + * 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 static com.squareup.okhttp.internal.Util.equal; +import java.net.Proxy; +import java.net.UnknownHostException; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; + +/** + * 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; + + public Address(String uriHost, int uriPort, SSLSocketFactory sslSocketFactory, + HostnameVerifier hostnameVerifier, Proxy proxy) throws UnknownHostException { + if (uriHost == null) throw new NullPointerException("uriHost == null"); + if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort); + this.proxy = proxy; + this.uriHost = uriHost; + this.uriPort = uriPort; + this.sslSocketFactory = sslSocketFactory; + this.hostnameVerifier = hostnameVerifier; + } + + /** + * 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 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); + } + 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 + (proxy != null ? proxy.hashCode() : 0); + return result; + } +} diff --git a/src/main/java/com/squareup/okhttp/Connection.java b/src/main/java/com/squareup/okhttp/Connection.java new file mode 100644 index 0000000..61f74d1 --- /dev/null +++ b/src/main/java/com/squareup/okhttp/Connection.java @@ -0,0 +1,304 @@ +/* + * 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.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import java.net.URL; +import java.util.Arrays; +import javax.net.ssl.SSLSocket; + +/** + * 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', '/', '2', + 8, 'h', 't', 't', 'p', '/', '1', '.', '1', + }; + private static final byte[] SPDY2 = new byte[] { + 's', 'p', 'd', 'y', '/', '2', + }; + private static final byte[] HTTP_11 = new byte[] { + 'h', 't', 't', 'p', '/', '1', '.', '1', + }; + + private final Address address; + private final Proxy proxy; + private final InetSocketAddress inetSocketAddress; + private final boolean modernTls; + + private Socket socket; + private InputStream in; + private OutputStream out; + private boolean recycled = false; + private SpdyConnection spdyConnection; + private int httpMinorVersion = 1; // Assume HTTP/1.1 + + public Connection(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; + } + + public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest) + throws IOException { + socket = (proxy.type() != Proxy.Type.HTTP) + ? new Socket(proxy) + : new Socket(); + socket.connect(inetSocketAddress, connectTimeout); + socket.setSoTimeout(readTimeout); + in = socket.getInputStream(); + out = socket.getOutputStream(); + + if (address.sslSocketFactory != null) { + upgradeToTls(tunnelRequest); + } + + // Buffer the socket stream to permit efficient parsing of HTTP headers and chunk sizes. + if (!isSpdy()) { + int bufferSize = 128; + in = new BufferedInputStream(in, bufferSize); + } + } + + /** + * 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 = address.sslSocketFactory.createSocket( + socket, address.uriHost, address.uriPort, true /* autoClose */); + SSLSocket sslSocket = (SSLSocket) socket; + if (modernTls) { + platform.enableTlsExtensions(sslSocket, address.uriHost); + } else { + platform.supportTlsIntolerantServer(sslSocket); + } + + if (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 (!address.hostnameVerifier.verify(address.uriHost, sslSocket.getSession())) { + throw new IOException("Hostname '" + address.uriHost + "' was not verified"); + } + + out = sslSocket.getOutputStream(); + in = sslSocket.getInputStream(); + + byte[] selectedProtocol; + if (modernTls + && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) { + if (Arrays.equals(selectedProtocol, SPDY2)) { + sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream. + spdyConnection = new SpdyConnection.Builder(true, in, out).build(); + } else if (!Arrays.equals(selectedProtocol, HTTP_11)) { + throw new IOException("Unexpected NPN transport " + + new String(selectedProtocol, "ISO-8859-1")); + } + } + } + + @Override public void close() throws IOException { + socket.close(); + } + + /** + * Returns the proxy that this connection is using. + * + * <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; + } + + public Address getAddress() { + return address; + } + + public InetSocketAddress getSocketAddress() { + return inetSocketAddress; + } + + public boolean isModernTls() { + return modernTls; + } + + /** + * 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 has been used to satisfy an earlier + * HTTP request/response pair. + * + * <p>The HTTP client treats recycled and non-recycled connections + * differently. I/O failures on recycled connections are often temporary: + * the remote peer may have closed the socket because it was idle for an + * extended period of time. When fresh connections suffer similar failures + * the problem is fatal and the request is not retried. + */ + public boolean isRecycled() { + return recycled; + } + + public void setRecycled() { + this.recycled = true; + } + + /** + * Returns true if this connection is eligible to be reused for another + * request/response pair. + */ + protected boolean isEligibleForRecycling() { + return !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown(); + } + + /** + * 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; + } + + /** + * 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 address.sslSocketFactory != null && proxy != null && 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, 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()); + } + } + } +} diff --git a/src/main/java/com/squareup/okhttp/ConnectionPool.java b/src/main/java/com/squareup/okhttp/ConnectionPool.java new file mode 100644 index 0000000..afb0e58 --- /dev/null +++ b/src/main/java/com/squareup/okhttp/ConnectionPool.java @@ -0,0 +1,169 @@ +/* + * 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.HashMap; +import java.util.List; + +/** + * Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP + * requests that share the same {@link Address} may share a {@link 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 connections to each + * address. Default is 5. + * </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 final class ConnectionPool { + private static final ConnectionPool systemDefault; + static { + String keepAlive = System.getProperty("http.keepAlive"); + String maxConnections = System.getProperty("http.maxConnections"); + if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) { + systemDefault = new ConnectionPool(0); + } else if (maxConnections != null) { + systemDefault = new ConnectionPool(Integer.parseInt(maxConnections)); + } else { + systemDefault = new ConnectionPool(5); + } + } + + /** The maximum number of idle connections for each address. */ + private final int maxConnections; + private final HashMap<Address, List<Connection>> connectionPool + = new HashMap<Address, List<Connection>>(); + + public ConnectionPool(int maxConnections) { + this.maxConnections = maxConnections; + } + + public static ConnectionPool getDefault() { + return systemDefault; + } + + /** + * Returns a recycled connection to {@code address}, or null if no such + * connection exists. + */ + public Connection get(Address address) { + // First try to reuse an existing HTTP connection. + synchronized (connectionPool) { + List<Connection> connections = connectionPool.get(address); + while (connections != null) { + Connection connection = connections.get(connections.size() - 1); + if (!connection.isSpdy()) { + connections.remove(connections.size() - 1); + } + if (connections.isEmpty()) { + connectionPool.remove(address); + connections = null; + } + if (!connection.isEligibleForRecycling()) { + Util.closeQuietly(connection); + continue; + } + try { + Platform.get().tagSocket(connection.getSocket()); + } catch (SocketException e) { + // When unable to tag, skip recycling and close + Platform.get().logW("Unable to tagSocket(): " + e); + Util.closeQuietly(connection); + continue; + } + return connection; + } + } + return null; + } + + /** + * 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; + } + + 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; + } + + if (maxConnections > 0 && connection.isEligibleForRecycling()) { + Address address = connection.getAddress(); + synchronized (connectionPool) { + List<Connection> connections = connectionPool.get(address); + if (connections == null) { + connections = new ArrayList<Connection>(); + connectionPool.put(address, connections); + } + if (connections.size() < maxConnections) { + connection.setRecycled(); + connections.add(connection); + return; // keep the connection open + } + } + } + + // don't close streams while holding a lock! + Util.closeQuietly(connection); + } + + /** + * Shares the SPDY connection with the pool. Callers to this method may + * continue to use {@code connection}. + */ + public void share(Connection connection) { + if (!connection.isSpdy()) { + throw new IllegalArgumentException(); + } + if (maxConnections <= 0 || !connection.isEligibleForRecycling()) { + return; + } + Address address = connection.getAddress(); + synchronized (connectionPool) { + List<Connection> connections = connectionPool.get(address); + if (connections == null) { + connections = new ArrayList<Connection>(1); + connections.add(connection); + connectionPool.put(address, connections); + } + } + } +} diff --git a/src/main/java/com/squareup/okhttp/OkHttpClient.java b/src/main/java/com/squareup/okhttp/OkHttpClient.java new file mode 100644 index 0000000..2205487 --- /dev/null +++ b/src/main/java/com/squareup/okhttp/OkHttpClient.java @@ -0,0 +1,156 @@ +/* + * 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 java.net.CookieHandler; +import java.net.HttpURLConnection; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.ResponseCache; +import java.net.URL; +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 ProxySelector proxySelector; + private CookieHandler cookieHandler; + private ResponseCache responseCache; + private SSLSocketFactory sslSocketFactory; + private HostnameVerifier hostnameVerifier; + private ConnectionPool connectionPool; + + /** + * 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; + } + + /** + * 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. + * + * <p>If unset, the {@link ProxySelector#getDefault() system-wide default} + * proxy selector will be used. + */ + public OkHttpClient setProxySelector(ProxySelector proxySelector) { + this.proxySelector = proxySelector; + return this; + } + + /** + * Sets the cookie handler to be used to read outgoing cookies and write + * incoming cookies. + * + * <p>If unset, the {@link CookieHandler#getDefault() system-wide default} + * cookie handler will be used. + */ + public OkHttpClient setCookieHandler(CookieHandler cookieHandler) { + this.cookieHandler = cookieHandler; + return this; + } + + /** + * Sets the response cache to be used to read and write cached responses. + * + * <p>If unset, the {@link ResponseCache#getDefault() system-wide default} + * response cache will be used. + */ + public OkHttpClient setResponseCache(ResponseCache responseCache) { + this.responseCache = responseCache; + return this; + } + + /** + * Sets the socket factory used to secure HTTPS connections. + * + * <p>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; + } + + /** + * Sets the verifier used to confirm that response certificates apply to + * requested hostnames for HTTPS connections. + * + * <p>If unset, the {@link HttpsURLConnection#getDefaultHostnameVerifier() + * system-wide default} hostname verifier will be used. + */ + public OkHttpClient setHostnameVerifier(HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + return this; + } + + /** + * Sets the connection pool used to recycle HTTP and HTTPS connections. + * + * <p>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 HttpURLConnection open(URL url) { + ProxySelector proxySelector = this.proxySelector != null + ? this.proxySelector + : ProxySelector.getDefault(); + CookieHandler cookieHandler = this.cookieHandler != null + ? this.cookieHandler + : CookieHandler.getDefault(); + ResponseCache responseCache = this.responseCache != null + ? this.responseCache + : ResponseCache.getDefault(); + ConnectionPool connectionPool = this.connectionPool != null + ? this.connectionPool + : ConnectionPool.getDefault(); + + String protocol = url.getProtocol(); + if (protocol.equals("http")) { + return new HttpURLConnectionImpl( + url, 80, proxy, proxySelector, cookieHandler, responseCache, connectionPool); + } else if (protocol.equals("https")) { + HttpsURLConnectionImpl result = new HttpsURLConnectionImpl( + url, 443, proxy, proxySelector, cookieHandler, responseCache, connectionPool); + result.setSSLSocketFactory(this.sslSocketFactory != null + ? this.sslSocketFactory + : HttpsURLConnection.getDefaultSSLSocketFactory()); + result.setHostnameVerifier(this.hostnameVerifier != null + ? this.hostnameVerifier + : HttpsURLConnection.getDefaultHostnameVerifier()); + return result; + } else { + throw new IllegalArgumentException("Unexpected protocol: " + protocol); + } + } +} diff --git a/src/main/java/com/squareup/okhttp/OkHttpConnection.java b/src/main/java/com/squareup/okhttp/OkHttpConnection.java deleted file mode 100644 index 5df657a..0000000 --- a/src/main/java/com/squareup/okhttp/OkHttpConnection.java +++ /dev/null @@ -1,829 +0,0 @@ -/* - * 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 java.io.IOException; -import java.io.InputStream; -import java.net.ProtocolException; -import java.net.Proxy; -import java.net.SocketPermission; -import java.net.URL; -import java.net.URLConnection; -import java.util.Arrays; -import libcore.net.http.HttpEngine; - -/** - * An {@link java.net.URLConnection} for HTTP (<a - * href="http://tools.ietf.org/html/rfc2616">RFC 2616</a>) used to send and - * receive data over the web. Data may be of any type and length. This class may - * be used to send and receive streaming data whose length is not known in - * advance. - * - * <p>Uses of this class follow a pattern: - * <ol> - * <li>Obtain a new {@code HttpURLConnection} by calling {@link - * java.net.URL#openConnection() URL.openConnection()} and casting the result to - * {@code HttpURLConnection}. - * <li>Prepare the request. The primary property of a request is its URI. - * Request headers may also include metadata such as credentials, preferred - * content types, and session cookies. - * <li>Optionally upload a request body. Instances must be configured with - * {@link #setDoOutput(boolean) setDoOutput(true)} if they include a - * request body. Transmit data by writing to the stream returned by {@link - * #getOutputStream()}. - * <li>Read the response. Response headers typically include metadata such as - * the response body's content type and length, modified dates and session - * cookies. The response body may be read from the stream returned by {@link - * #getInputStream()}. If the response has no body, that method returns an - * empty stream. - * <li>Disconnect. Once the response body has been read, the {@code - * HttpURLConnection} should be closed by calling {@link #disconnect()}. - * Disconnecting releases the resources held by a connection so they may - * be closed or reused. - * </ol> - * - * <p>For example, to retrieve the webpage at {@code http://www.android.com/}: - * <pre> {@code - * URL url = new URL("http://www.android.com/"); - * HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - * try { - * InputStream in = new BufferedInputStream(urlConnection.getInputStream()); - * readStream(in); - * } finally { - * urlConnection.disconnect(); - * } - * }</pre> - * - * <h3>Secure Communication with HTTPS</h3> - * Calling {@link java.net.URL#openConnection()} on a URL with the "https" - * scheme will return an {@code HttpsURLConnection}, which allows for - * overriding the default {@link javax.net.ssl.HostnameVerifier - * HostnameVerifier} and {@link javax.net.ssl.SSLSocketFactory - * SSLSocketFactory}. An application-supplied {@code SSLSocketFactory} - * created from an {@link javax.net.ssl.SSLContext SSLContext} can - * provide a custom {@link javax.net.ssl.X509TrustManager - * X509TrustManager} for verifying certificate chains and a custom - * {@link javax.net.ssl.X509KeyManager X509KeyManager} for supplying - * client certificates. See {@link OkHttpsConnection HttpsURLConnection} for - * more details. - * - * <h3>Response Handling</h3> - * {@code HttpURLConnection} will follow up to five HTTP redirects. It will - * follow redirects from one origin server to another. This implementation - * doesn't follow redirects from HTTPS to HTTP or vice versa. - * - * <p>If the HTTP response indicates that an error occurred, {@link - * #getInputStream()} will throw an {@link java.io.IOException}. Use {@link - * #getErrorStream()} to read the error response. The headers can be read in - * the normal way using {@link #getHeaderFields()}, - * - * <h3>Posting Content</h3> - * To upload data to a web server, configure the connection for output using - * {@link #setDoOutput(boolean) setDoOutput(true)}. - * - * <p>For best performance, you should call either {@link - * #setFixedLengthStreamingMode(int)} when the body length is known in advance, - * or {@link #setChunkedStreamingMode(int)} when it is not. Otherwise {@code - * HttpURLConnection} will be forced to buffer the complete request body in - * memory before it is transmitted, wasting (and possibly exhausting) heap and - * increasing latency. - * - * <p>For example, to perform an upload: <pre> {@code - * HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - * try { - * urlConnection.setDoOutput(true); - * urlConnection.setChunkedStreamingMode(0); - * - * OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream()); - * writeStream(out); - * - * InputStream in = new BufferedInputStream(urlConnection.getInputStream()); - * readStream(in); - * } finally { - * urlConnection.disconnect(); - * } - * }</pre> - * - * <h3>Performance</h3> - * The input and output streams returned by this class are <strong>not - * buffered</strong>. Most callers should wrap the returned streams with {@link - * java.io.BufferedInputStream BufferedInputStream} or {@link - * java.io.BufferedOutputStream BufferedOutputStream}. Callers that do only bulk - * reads or writes may omit buffering. - * - * <p>When transferring large amounts of data to or from a server, use streams - * to limit how much data is in memory at once. Unless you need the entire - * body to be in memory at once, process it as a stream (rather than storing - * the complete body as a single byte array or string). - * - * <p>To reduce latency, this class may reuse the same underlying {@code Socket} - * for multiple request/response pairs. As a result, HTTP connections may be - * held open longer than necessary. Calls to {@link #disconnect()} may return - * the socket to a pool of connected sockets. This behavior can be disabled by - * setting the {@code http.keepAlive} system property to {@code false} before - * issuing any HTTP requests. The {@code http.maxConnections} property may be - * used to control how many idle connections to each server will be held. - * - * <p>By default, this implementation of {@code HttpURLConnection} requests that - * servers use gzip compression. Since {@link #getContentLength()} returns the - * number of bytes transmitted, you cannot use that method to predict how many - * bytes can be read from {@link #getInputStream()}. Instead, read that stream - * until it is exhausted: when {@link java.io.InputStream#read} returns -1. Gzip - * compression can be disabled by setting the acceptable encodings in the - * request header: <pre> {@code - * urlConnection.setRequestProperty("Accept-Encoding", "identity"); - * }</pre> - * - * <h3>Handling Network Sign-On</h3> - * Some Wi-Fi networks block Internet access until the user clicks through a - * sign-on page. Such sign-on pages are typically presented by using HTTP - * redirects. You can use {@link #getURL()} to test if your connection has been - * unexpectedly redirected. This check is not valid until <strong>after</strong> - * the response headers have been received, which you can trigger by calling - * {@link #getHeaderFields()} or {@link #getInputStream()}. For example, to - * check that a response was not redirected to an unexpected host: - * <pre> {@code - * HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - * try { - * InputStream in = new BufferedInputStream(urlConnection.getInputStream()); - * if (!url.getHost().equals(urlConnection.getURL().getHost())) { - * // we were redirected! Kick the user out to the browser to sign on? - * } - * ... - * } finally { - * urlConnection.disconnect(); - * } - * }</pre> - * - * <h3>HTTP Authentication</h3> - * {@code HttpURLConnection} supports <a - * href="http://www.ietf.org/rfc/rfc2617">HTTP basic authentication</a>. Use - * {@link java.net.Authenticator} to set the VM-wide authentication handler: - * <pre> {@code - * Authenticator.setDefault(new Authenticator() { - * protected PasswordAuthentication getPasswordAuthentication() { - * return new PasswordAuthentication(username, password.toCharArray()); - * } - * }); - * }</pre> - * Unless paired with HTTPS, this is <strong>not</strong> a secure mechanism for - * user authentication. In particular, the username, password, request and - * response are all transmitted over the network without encryption. - * - * <h3>Sessions with Cookies</h3> - * To establish and maintain a potentially long-lived session between client - * and server, {@code HttpURLConnection} includes an extensible cookie manager. - * Enable VM-wide cookie management using {@link java.net.CookieHandler} and {@link - * java.net.CookieManager}: <pre> {@code - * CookieManager cookieManager = new CookieManager(); - * CookieHandler.setDefault(cookieManager); - * }</pre> - * By default, {@code CookieManager} accepts cookies from the <a - * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec1.html">origin - * server</a> only. Two other policies are included: {@link - * java.net.CookiePolicy#ACCEPT_ALL} and {@link java.net.CookiePolicy#ACCEPT_NONE}. Implement - * {@link java.net.CookiePolicy} to define a custom policy. - * - * <p>The default {@code CookieManager} keeps all accepted cookies in memory. It - * will forget these cookies when the VM exits. Implement {@link java.net.CookieStore} to - * define a custom cookie store. - * - * <p>In addition to the cookies set by HTTP responses, you may set cookies - * programmatically. To be included in HTTP request headers, cookies must have - * the domain and path properties set. - * - * <p>By default, new instances of {@code HttpCookie} work only with servers - * that support <a href="http://www.ietf.org/rfc/rfc2965.txt">RFC 2965</a> - * cookies. Many web servers support only the older specification, <a - * href="http://www.ietf.org/rfc/rfc2109.txt">RFC 2109</a>. For compatibility - * with the most web servers, set the cookie version to 0. - * - * <p>For example, to receive {@code www.twitter.com} in French: <pre> {@code - * HttpCookie cookie = new HttpCookie("lang", "fr"); - * cookie.setDomain("twitter.com"); - * cookie.setPath("/"); - * cookie.setVersion(0); - * cookieManager.getCookieStore().add(new URI("http://twitter.com/"), cookie); - * }</pre> - * - * <h3>HTTP Methods</h3> - * <p>{@code HttpURLConnection} uses the {@code GET} method by default. It will - * use {@code POST} if {@link #setDoOutput setDoOutput(true)} has been called. - * Other HTTP methods ({@code OPTIONS}, {@code HEAD}, {@code PUT}, {@code - * DELETE} and {@code TRACE}) can be used with {@link #setRequestMethod}. - * - * <h3>Proxies</h3> - * By default, this class will connect directly to the <a - * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec1.html">origin - * server</a>. It can also connect via an {@link java.net.Proxy.Type#HTTP HTTP} or {@link - * java.net.Proxy.Type#SOCKS SOCKS} proxy. To use a proxy, use {@link - * java.net.URL#openConnection(java.net.Proxy) URL.openConnection(Proxy)} when creating the - * connection. - * - * <h3>IPv6 Support</h3> - * <p>This class includes transparent support for IPv6. For hosts with both IPv4 - * and IPv6 addresses, it will attempt to connect to each of a host's addresses - * until a connection is established. - * - * <h3>Response Caching</h3> - * Android 4.0 (Ice Cream Sandwich) includes a response cache. See {@code - * android.net.http.HttpResponseCache} for instructions on enabling HTTP caching - * in your application. - * - * <h3>Avoiding Bugs In Earlier Releases</h3> - * Prior to Android 2.2 (Froyo), this class had some frustrating bugs. In - * particular, calling {@code close()} on a readable {@code InputStream} could - * <a href="http://code.google.com/p/android/issues/detail?id=2939">poison the - * connection pool</a>. Work around this by disabling connection pooling: - * <pre> {@code - * private void disableConnectionReuseIfNecessary() { - * // Work around pre-Froyo bugs in HTTP connection reuse. - * if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) { - * System.setProperty("http.keepAlive", "false"); - * } - * }}</pre> - * - * <p>Each instance of {@code HttpURLConnection} may be used for one - * request/response pair. Instances of this class are not thread safe. - */ -public abstract class OkHttpConnection extends URLConnection { - - /** - * The subset of HTTP methods that the user may select via {@link - * #setRequestMethod(String)}. - */ - private static final String[] PERMITTED_USER_METHODS = { - HttpEngine.OPTIONS, - HttpEngine.GET, - HttpEngine.HEAD, - HttpEngine.POST, - HttpEngine.PUT, - HttpEngine.DELETE, - HttpEngine.TRACE - // Note: we don't allow users to specify "CONNECT" - }; - - /** - * The HTTP request method of this {@code HttpURLConnection}. The default - * value is {@code "GET"}. - */ - protected String method = HttpEngine.GET; - - /** - * The status code of the response obtained from the HTTP request. The - * default value is {@code -1}. - * <p> - * <li>1xx: Informational</li> - * <li>2xx: Success</li> - * <li>3xx: Relocation/Redirection</li> - * <li>4xx: Client Error</li> - * <li>5xx: Server Error</li> - */ - protected int responseCode = -1; - - /** - * The HTTP response message which corresponds to the response code. - */ - protected String responseMessage; - - /** - * Flag to define whether the protocol will automatically follow redirects - * or not. The default value is {@code true}. - */ - protected boolean instanceFollowRedirects = followRedirects; - - private static boolean followRedirects = true; - - /** - * If the HTTP chunked encoding is enabled this parameter defines the - * chunk-length. Default value is {@code -1} that means the chunked encoding - * mode is disabled. - */ - protected int chunkLength = -1; - - /** - * If using HTTP fixed-length streaming mode this parameter defines the - * fixed length of content. Default value is {@code -1} that means the - * fixed-length streaming mode is disabled. - */ - protected int fixedContentLength = -1; - - // 2XX: generally "OK" - // 3XX: relocation/redirect - // 4XX: client error - // 5XX: server error - /** - * Numeric status code, 202: Accepted. - */ - public static final int HTTP_ACCEPTED = 202; - - /** - * Numeric status code, 502: Bad Gateway. - */ - public static final int HTTP_BAD_GATEWAY = 502; - - /** - * Numeric status code, 405: Bad Method. - */ - public static final int HTTP_BAD_METHOD = 405; - - /** - * Numeric status code, 400: Bad Request. - */ - public static final int HTTP_BAD_REQUEST = 400; - - /** - * Numeric status code, 408: Client Timeout. - */ - public static final int HTTP_CLIENT_TIMEOUT = 408; - - /** - * Numeric status code, 409: Conflict. - */ - public static final int HTTP_CONFLICT = 409; - - /** - * Numeric status code, 201: Created. - */ - public static final int HTTP_CREATED = 201; - - /** - * Numeric status code, 413: Entity too large. - */ - public static final int HTTP_ENTITY_TOO_LARGE = 413; - - /** - * Numeric status code, 403: Forbidden. - */ - public static final int HTTP_FORBIDDEN = 403; - - /** - * Numeric status code, 504: Gateway timeout. - */ - public static final int HTTP_GATEWAY_TIMEOUT = 504; - - /** - * Numeric status code, 410: Gone. - */ - public static final int HTTP_GONE = 410; - - /** - * Numeric status code, 500: Internal error. - */ - public static final int HTTP_INTERNAL_ERROR = 500; - - /** - * Numeric status code, 411: Length required. - */ - public static final int HTTP_LENGTH_REQUIRED = 411; - - /** - * Numeric status code, 301 Moved permanently. - */ - public static final int HTTP_MOVED_PERM = 301; - - /** - * Numeric status code, 302: Moved temporarily. - */ - public static final int HTTP_MOVED_TEMP = 302; - - /** - * Numeric status code, 300: Multiple choices. - */ - public static final int HTTP_MULT_CHOICE = 300; - - /** - * Numeric status code, 204: No content. - */ - public static final int HTTP_NO_CONTENT = 204; - - /** - * Numeric status code, 406: Not acceptable. - */ - public static final int HTTP_NOT_ACCEPTABLE = 406; - - /** - * Numeric status code, 203: Not authoritative. - */ - public static final int HTTP_NOT_AUTHORITATIVE = 203; - - /** - * Numeric status code, 404: Not found. - */ - public static final int HTTP_NOT_FOUND = 404; - - /** - * Numeric status code, 501: Not implemented. - */ - public static final int HTTP_NOT_IMPLEMENTED = 501; - - /** - * Numeric status code, 304: Not modified. - */ - public static final int HTTP_NOT_MODIFIED = 304; - - /** - * Numeric status code, 200: OK. - */ - public static final int HTTP_OK = 200; - - /** - * Numeric status code, 206: Partial. - */ - public static final int HTTP_PARTIAL = 206; - - /** - * Numeric status code, 402: Payment required. - */ - public static final int HTTP_PAYMENT_REQUIRED = 402; - - /** - * Numeric status code, 412: Precondition failed. - */ - public static final int HTTP_PRECON_FAILED = 412; - - /** - * Numeric status code, 407: Proxy authentication required. - */ - public static final int HTTP_PROXY_AUTH = 407; - - /** - * Numeric status code, 414: Request too long. - */ - public static final int HTTP_REQ_TOO_LONG = 414; - - /** - * Numeric status code, 205: Reset. - */ - public static final int HTTP_RESET = 205; - - /** - * Numeric status code, 303: See other. - */ - public static final int HTTP_SEE_OTHER = 303; - - /** - * Numeric status code, 500: Internal error. - * - * @deprecated Use {@link #HTTP_INTERNAL_ERROR} - */ - @Deprecated - public static final int HTTP_SERVER_ERROR = 500; - - /** - * Numeric status code, 305: Use proxy. - * - * <p>Like Firefox and Chrome, this class doesn't honor this response code. - * Other implementations respond to this status code by retrying the request - * using the HTTP proxy named by the response's Location header field. - */ - public static final int HTTP_USE_PROXY = 305; - - /** - * Numeric status code, 401: Unauthorized. - */ - public static final int HTTP_UNAUTHORIZED = 401; - - /** - * Numeric status code, 415: Unsupported type. - */ - public static final int HTTP_UNSUPPORTED_TYPE = 415; - - /** - * Numeric status code, 503: Unavailable. - */ - public static final int HTTP_UNAVAILABLE = 503; - - /** - * Numeric status code, 505: Version not supported. - */ - public static final int HTTP_VERSION = 505; - - /** - * Returns a new OkHttpConnection or OkHttpsConnection to {@code url}. - */ - public static OkHttpConnection open(URL url) { - String protocol = url.getProtocol(); - if (protocol.equals("http")) { - return new libcore.net.http.HttpURLConnectionImpl(url, 80); - } else if (protocol.equals("https")) { - return new libcore.net.http.HttpsURLConnectionImpl(url, 443); - } else { - throw new IllegalArgumentException(); - } - } - - /** - * Returns a new OkHttpConnection or OkHttpsConnection to {@code url} that - * connects via {@code proxy}. - */ - public static OkHttpConnection open(URL url, Proxy proxy) { - String protocol = url.getProtocol(); - if (protocol.equals("http")) { - return new libcore.net.http.HttpURLConnectionImpl(url, 80, proxy); - } else if (protocol.equals("https")) { - return new libcore.net.http.HttpsURLConnectionImpl(url, 443, proxy); - } else { - throw new IllegalArgumentException(); - } - } - - /** - * Constructs a new {@code HttpURLConnection} instance pointing to the - * resource specified by the {@code url}. - * - * @param url - * the URL of this connection. - * @see java.net.URL - * @see java.net.URLConnection - */ - protected OkHttpConnection(URL url) { - super(url); - } - - /** - * Releases this connection so that its resources may be either reused or - * closed. - * - * <p>Unlike other Java implementations, this will not necessarily close - * socket connections that can be reused. You can disable all connection - * reuse by setting the {@code http.keepAlive} system property to {@code - * false} before issuing any HTTP requests. - */ - public abstract void disconnect(); - - /** - * Returns an input stream from the server in the case of an error such as - * the requested file has not been found on the remote server. This stream - * can be used to read the data the server will send back. - * - * @return the error input stream returned by the server. - */ - public InputStream getErrorStream() { - return null; - } - - /** - * Returns the value of {@code followRedirects} which indicates if this - * connection follows a different URL redirected by the server. It is - * enabled by default. - * - * @return the value of the flag. - * @see #setFollowRedirects - */ - public static boolean getFollowRedirects() { - return followRedirects; - } - - /** - * Returns the permission object (in this case {@code SocketPermission}) - * with the host and the port number as the target name and {@code - * "resolve, connect"} as the action list. If the port number of this URL - * instance is lower than {@code 0} the port will be set to {@code 80}. - * - * @return the permission object required for this connection. - * @throws java.io.IOException - * if an IO exception occurs during the creation of the - * permission object. - */ - @Override - public java.security.Permission getPermission() throws IOException { - int port = url.getPort(); - if (port < 0) { - port = 80; - } - return new SocketPermission(url.getHost() + ":" + port, - "connect, resolve"); - } - - /** - * Returns the request method which will be used to make the request to the - * remote HTTP server. All possible methods of this HTTP implementation is - * listed in the class definition. - * - * @return the request method string. - * @see #method - * @see #setRequestMethod - */ - public String getRequestMethod() { - return method; - } - - /** - * Returns the response code returned by the remote HTTP server. - * - * @return the response code, -1 if no valid response code. - * @throws java.io.IOException - * if there is an IO error during the retrieval. - * @see #getResponseMessage - */ - public int getResponseCode() throws IOException { - // Call getInputStream() first since getHeaderField() doesn't return - // exceptions - getInputStream(); - String response = getHeaderField(0); - if (response == null) { - return -1; - } - response = response.trim(); - int mark = response.indexOf(" ") + 1; - if (mark == 0) { - return -1; - } - int last = mark + 3; - if (last > response.length()) { - last = response.length(); - } - responseCode = Integer.parseInt(response.substring(mark, last)); - if (last + 1 <= response.length()) { - responseMessage = response.substring(last + 1); - } - return responseCode; - } - - /** - * Returns the response message returned by the remote HTTP server. - * - * @return the response message. {@code null} if no such response exists. - * @throws java.io.IOException - * if there is an error during the retrieval. - * @see #getResponseCode() - */ - public String getResponseMessage() throws IOException { - if (responseMessage != null) { - return responseMessage; - } - getResponseCode(); - return responseMessage; - } - - /** - * Sets the flag of whether this connection will follow redirects returned - * by the remote server. - * - * @param auto - * the value to enable or disable this option. - */ - public static void setFollowRedirects(boolean auto) { - followRedirects = auto; - } - - /** - * Sets the request command which will be sent to the remote HTTP server. - * This method can only be called before the connection is made. - * - * @param method - * the string representing the method to be used. - * @throws java.net.ProtocolException - * if this is called after connected, or the method is not - * supported by this HTTP implementation. - * @see #getRequestMethod() - * @see #method - */ - public void setRequestMethod(String method) throws ProtocolException { - if (connected) { - throw new ProtocolException("Connection already established"); - } - for (String permittedUserMethod : PERMITTED_USER_METHODS) { - if (permittedUserMethod.equals(method)) { - // if there is a supported method that matches the desired - // method, then set the current method and return - this.method = permittedUserMethod; - return; - } - } - // if none matches, then throw ProtocolException - throw new ProtocolException("Unknown method '" + method + "'; must be one of " - + Arrays.toString(PERMITTED_USER_METHODS)); - } - - /** - * Returns whether this connection uses a proxy server or not. - * - * @return {@code true} if this connection passes a proxy server, false - * otherwise. - */ - public abstract boolean usingProxy(); - - /** - * Returns the encoding used to transmit the response body over the network. - * This is null or "identity" if the content was not encoded, or "gzip" if - * the body was gzip compressed. Most callers will be more interested in the - * {@link #getContentType() content type}, which may also include the - * content's character encoding. - */ - @Override public String getContentEncoding() { - return super.getContentEncoding(); // overridden for Javadoc only - } - - /** - * Returns whether this connection follows redirects. - * - * @return {@code true} if this connection follows redirects, false - * otherwise. - */ - public boolean getInstanceFollowRedirects() { - return instanceFollowRedirects; - } - - /** - * Sets whether this connection follows redirects. - * - * @param followRedirects - * {@code true} if this connection will follows redirects, false - * otherwise. - */ - public void setInstanceFollowRedirects(boolean followRedirects) { - instanceFollowRedirects = followRedirects; - } - - /** - * Returns the date value in milliseconds since {@code 01.01.1970, 00:00h} - * corresponding to the header field {@code field}. The {@code defaultValue} - * will be returned if no such field can be found in the response header. - * - * @param field - * the header field name. - * @param defaultValue - * the default value to use if the specified header field wont be - * found. - * @return the header field represented in milliseconds since January 1, - * 1970 GMT. - */ - @Override - public long getHeaderFieldDate(String field, long defaultValue) { - return super.getHeaderFieldDate(field, defaultValue); - } - - /** - * If the length of a HTTP request body is known ahead, sets fixed length to - * enable streaming without buffering. Sets after connection will cause an - * exception. - * - * @see #setChunkedStreamingMode - * @param contentLength - * the fixed length of the HTTP request body. - * @throws IllegalStateException - * if already connected or another mode already set. - * @throws IllegalArgumentException - * if {@code contentLength} is less than zero. - */ - public void setFixedLengthStreamingMode(int contentLength) { - if (super.connected) { - throw new IllegalStateException("Already connected"); - } - if (chunkLength > 0) { - throw new IllegalStateException("Already in chunked mode"); - } - if (contentLength < 0) { - throw new IllegalArgumentException("contentLength < 0"); - } - this.fixedContentLength = contentLength; - } - - /** - * Stream a request body whose length is not known in advance. Old HTTP/1.0 - * only servers may not support this mode. - * - * <p>When HTTP chunked encoding is used, the stream is divided into - * chunks, each prefixed with a header containing the chunk's size. Setting - * a large chunk length requires a large internal buffer, potentially - * wasting memory. Setting a small chunk length increases the number of - * bytes that must be transmitted because of the header on every chunk. - * Most caller should use {@code 0} to get the system default. - * - * @see #setFixedLengthStreamingMode - * @param chunkLength the length to use, or {@code 0} for the default chunk - * length. - * @throws IllegalStateException if already connected or another mode - * already set. - */ - public void setChunkedStreamingMode(int chunkLength) { - if (super.connected) { - throw new IllegalStateException("Already connected"); - } - if (fixedContentLength >= 0) { - throw new IllegalStateException("Already in fixed-length mode"); - } - if (chunkLength <= 0) { - this.chunkLength = HttpEngine.DEFAULT_CHUNK_LENGTH; - } else { - this.chunkLength = chunkLength; - } - } -} diff --git a/src/main/java/com/squareup/okhttp/OkHttpsConnection.java b/src/main/java/com/squareup/okhttp/OkHttpsConnection.java deleted file mode 100644 index d1c144f..0000000 --- a/src/main/java/com/squareup/okhttp/OkHttpsConnection.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * 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 java.net.URL; -import java.security.Principal; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSocketFactory; - -/** - * An {@link java.net.HttpURLConnection} for HTTPS (<a - * href="http://tools.ietf.org/html/rfc2818">RFC 2818</a>). A - * connected {@code HttpsURLConnection} allows access to the - * negotiated cipher suite, the server certificate chain, and the - * client certificate chain if any. - * - * <h3>Providing an application specific X509TrustManager</h3> - * - * If an application wants to trust Certificate Authority (CA) - * certificates that are not part of the system, it should specify its - * own {@code X509TrustManager} via a {@code SSLSocketFactory} set on - * the {@code HttpsURLConnection}. The {@code X509TrustManager} can be - * created based on a {@code KeyStore} using a {@code - * TrustManagerFactory} to supply trusted CA certificates. Note that - * self-signed certificates are effectively their own CA and can be - * trusted by including them in a {@code KeyStore}. - * - * <p>For example, to trust a set of certificates specified by a {@code KeyStore}: - * <pre> {@code - * KeyStore keyStore = ...; - * TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); - * tmf.init(keyStore); - * - * SSLContext context = SSLContext.getInstance("TLS"); - * context.init(null, tmf.getTrustManagers(), null); - * - * URL url = new URL("https://www.example.com/"); - * HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); - * urlConnection.setSSLSocketFactory(context.getSocketFactory()); - * InputStream in = urlConnection.getInputStream(); - * }</pre> - * - * <p>It is possible to implement {@code X509TrustManager} directly - * instead of using one created by a {@code - * TrustManagerFactory}. While this is straightforward in the insecure - * case of allowing all certificate chains to pass verification, - * writing a proper implementation will usually want to take advantage - * of {@link java.security.cert.CertPathValidator - * CertPathValidator}. In general, it might be better to write a - * custom {@code KeyStore} implementation to pass to the {@code - * TrustManagerFactory} than to try and write a custom {@code - * X509TrustManager}. - * - * <h3>Providing an application specific X509KeyManager</h3> - * - * A custom {@code X509KeyManager} can be used to supply a client - * certificate and its associated private key to authenticate a - * connection to the server. The {@code X509KeyManager} can be created - * based on a {@code KeyStore} using a {@code KeyManagerFactory}. - * - * <p>For example, to supply client certificates from a {@code KeyStore}: - * <pre> {@code - * KeyStore keyStore = ...; - * KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509"); - * kmf.init(keyStore); - * - * SSLContext context = SSLContext.getInstance("TLS"); - * context.init(kmf.getKeyManagers(), null, null); - * - * URL url = new URL("https://www.example.com/"); - * HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); - * urlConnection.setSSLSocketFactory(context.getSocketFactory()); - * InputStream in = urlConnection.getInputStream(); - * }</pre> - * - * <p>A {@code X509KeyManager} can also be implemented directly. This - * can allow an application to return a certificate and private key - * from a non-{@code KeyStore} source or to specify its own logic for - * selecting a specific credential to use when many may be present in - * a single {@code KeyStore}. - * - * <h3>TLS Intolerance Support</h3> - * - * This class attempts to create secure connections using common TLS - * extensions and SSL deflate compression. Should that fail, the - * connection will be retried with SSLv3 only. - */ -public abstract class OkHttpsConnection extends OkHttpConnection { - - private static HostnameVerifier defaultHostnameVerifier - = HttpsURLConnection.getDefaultHostnameVerifier(); - - private static SSLSocketFactory defaultSSLSocketFactory = (SSLSocketFactory) SSLSocketFactory - .getDefault(); - - /** - * Sets the default hostname verifier to be used by new instances. - * - * @param v - * the new default hostname verifier - * @throws IllegalArgumentException - * if the specified verifier is {@code null}. - */ - public static void setDefaultHostnameVerifier(HostnameVerifier v) { - if (v == null) { - throw new IllegalArgumentException("HostnameVerifier is null"); - } - defaultHostnameVerifier = v; - } - - /** - * Returns the default hostname verifier. - * - * @return the default hostname verifier. - */ - public static HostnameVerifier getDefaultHostnameVerifier() { - return defaultHostnameVerifier; - } - - /** - * Sets the default SSL socket factory to be used by new instances. - * - * @param sf - * the new default SSL socket factory. - * @throws IllegalArgumentException - * if the specified socket factory is {@code null}. - */ - public static void setDefaultSSLSocketFactory(SSLSocketFactory sf) { - if (sf == null) { - throw new IllegalArgumentException("SSLSocketFactory is null"); - } - defaultSSLSocketFactory = sf; - } - - /** - * Returns the default SSL socket factory for new instances. - * - * @return the default SSL socket factory for new instances. - */ - public static SSLSocketFactory getDefaultSSLSocketFactory() { - return defaultSSLSocketFactory; - } - - /** - * The host name verifier used by this connection. It is initialized from - * the default hostname verifier - * {@link #setDefaultHostnameVerifier(javax.net.ssl.HostnameVerifier)} or - * {@link #getDefaultHostnameVerifier()}. - */ - protected HostnameVerifier hostnameVerifier; - - private SSLSocketFactory sslSocketFactory; - - /** - * Creates a new {@code HttpsURLConnection} with the specified {@code URL}. - * - * @param url - * the {@code URL} to connect to. - */ - protected OkHttpsConnection(URL url) { - super(url); - hostnameVerifier = defaultHostnameVerifier; - sslSocketFactory = defaultSSLSocketFactory; - } - - /** - * Returns the name of the cipher suite negotiated during the SSL handshake. - * - * @return the name of the cipher suite negotiated during the SSL handshake. - * @throws IllegalStateException - * if no connection has been established yet. - */ - public abstract String getCipherSuite(); - - /** - * Returns the list of local certificates used during the handshake. These - * certificates were sent to the peer. - * - * @return Returns the list of certificates used during the handshake with - * the local identity certificate followed by CAs, or {@code null} - * if no certificates were used during the handshake. - * @throws IllegalStateException - * if no connection has been established yet. - */ - public abstract Certificate[] getLocalCertificates(); - - /** - * Return the list of certificates identifying the peer during the - * handshake. - * - * @return the list of certificates identifying the peer with the peer's - * identity certificate followed by CAs. - * @throws javax.net.ssl.SSLPeerUnverifiedException - * if the identity of the peer has not been verified.. - * @throws IllegalStateException - * if no connection has been established yet. - */ - public abstract Certificate[] getServerCertificates() throws SSLPeerUnverifiedException; - - /** - * Returns the {@code Principal} identifying the peer. - * - * @return the {@code Principal} identifying the peer. - * @throws javax.net.ssl.SSLPeerUnverifiedException - * if the identity of the peer has not been verified. - * @throws IllegalStateException - * if no connection has been established yet. - */ - public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { - Certificate[] certs = getServerCertificates(); - if (certs == null || certs.length == 0 || (!(certs[0] instanceof X509Certificate))) { - throw new SSLPeerUnverifiedException("No server's end-entity certificate"); - } - return ((X509Certificate) certs[0]).getSubjectX500Principal(); - } - - /** - * Returns the {@code Principal} used to identify the local host during the handshake. - * - * @return the {@code Principal} used to identify the local host during the handshake, or - * {@code null} if none was used. - * @throws IllegalStateException - * if no connection has been established yet. - */ - public Principal getLocalPrincipal() { - Certificate[] certs = getLocalCertificates(); - if (certs == null || certs.length == 0 || (!(certs[0] instanceof X509Certificate))) { - return null; - } - return ((X509Certificate) certs[0]).getSubjectX500Principal(); - } - - /** - * Sets the hostname verifier for this instance. - * - * @param v - * the hostname verifier for this instance. - * @throws IllegalArgumentException - * if the specified verifier is {@code null}. - */ - public void setHostnameVerifier(HostnameVerifier v) { - if (v == null) { - throw new IllegalArgumentException("HostnameVerifier is null"); - } - hostnameVerifier = v; - } - - /** - * Returns the hostname verifier used by this instance. - */ - public HostnameVerifier getHostnameVerifier() { - return hostnameVerifier; - } - - /** - * Sets the SSL socket factory for this instance. - * - * @param sf - * the SSL socket factory to be used by this instance. - * @throws IllegalArgumentException - * if the specified socket factory is {@code null}. - */ - public void setSSLSocketFactory(SSLSocketFactory sf) { - if (sf == null) { - throw new IllegalArgumentException("SSLSocketFactory is null"); - } - sslSocketFactory = sf; - } - - /** - * Returns the SSL socket factory used by this instance. - */ - public SSLSocketFactory getSSLSocketFactory() { - return sslSocketFactory; - } - -} diff --git a/src/main/java/libcore/util/ExtendedResponseCache.java b/src/main/java/com/squareup/okhttp/OkResponseCache.java index 2d89569..c0f7c56 100644 --- a/src/main/java/libcore/util/ExtendedResponseCache.java +++ b/src/main/java/com/squareup/okhttp/OkResponseCache.java @@ -13,43 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.squareup.okhttp; -package libcore.util; - -import com.squareup.okhttp.OkHttpConnection; 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. - * - * @hide + * responses. Implementations of {@link java.net.ResponseCache} should implement + * this interface to receive additional support from the HTTP engine. */ -public interface ExtendedResponseCache { - - /* - * This hidden interface is defined in a non-hidden package (java.net) so - * its @hide tag will be parsed by Doclava. This hides this interface from - * implementing classes' documentation. - */ +public interface OkResponseCache { /** * Track an HTTP response being satisfied by {@code source}. - * @hide */ void trackResponse(ResponseSource source); /** * Track an conditional GET that was satisfied by this cache. - * @hide */ void trackConditionalCacheHit(); /** * Updates stored HTTP headers using a hit on a conditional GET. - * @hide */ - void update(CacheResponse conditionalCacheHit, OkHttpConnection httpConnection) throws IOException; + void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection) + throws IOException; } diff --git a/src/main/java/libcore/util/ResponseSource.java b/src/main/java/com/squareup/okhttp/ResponseSource.java index 8e7bfae..83388d6 100644 --- a/src/main/java/libcore/util/ResponseSource.java +++ b/src/main/java/com/squareup/okhttp/ResponseSource.java @@ -13,29 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package libcore.util; +package com.squareup.okhttp; /** - * Where the HTTP client should look for a response. - * - * @hide + * The source of an HTTP response. */ public enum ResponseSource { /** - * Return the response from the cache immediately. + * The response was returned from the local cache. */ CACHE, /** - * Make a conditional request to the host, returning the cache response if - * the cache is valid and the network response otherwise. + * 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, /** - * Return the response from the network. + * The response was returned from the network. */ NETWORK; diff --git a/src/main/java/com/squareup/okhttp/TunnelRequest.java b/src/main/java/com/squareup/okhttp/TunnelRequest.java new file mode 100644 index 0000000..39a820c --- /dev/null +++ b/src/main/java/com/squareup/okhttp/TunnelRequest.java @@ -0,0 +1,74 @@ +/* + * 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 static com.squareup.okhttp.internal.Util.getDefaultPort; +import com.squareup.okhttp.internal.http.RawHeaders; + +/** + * 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; + } +} diff --git a/src/main/java/libcore/io/Base64.java b/src/main/java/com/squareup/okhttp/internal/Base64.java index 96d3b9d..458e536 100644 --- a/src/main/java/libcore/io/Base64.java +++ b/src/main/java/com/squareup/okhttp/internal/Base64.java @@ -19,10 +19,10 @@ * @author Alexander Y. Kleymenov */ -package libcore.io; +package com.squareup.okhttp.internal; +import static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY; import java.io.UnsupportedEncodingException; -import libcore.util.EmptyArray; /** * <a href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a> encoder/decoder. @@ -41,7 +41,7 @@ public final class Base64 { int length = len / 4 * 3; // return an empty array on empty or short input without padding if (length == 0) { - return EmptyArray.BYTE; + return EMPTY_BYTE_ARRAY; } // temporary array byte[] out = new byte[length]; diff --git a/src/main/java/libcore/io/DiskLruCache.java b/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java index 5b78838..96f6d96 100644 --- a/src/main/java/libcore/io/DiskLruCache.java +++ b/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package libcore.io; +package com.squareup.okhttp.internal; +import static com.squareup.okhttp.internal.Util.UTF_8; import java.io.BufferedWriter; import java.io.Closeable; import java.io.EOFException; @@ -41,8 +42,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import libcore.util.Charsets; -import libcore.util.Libcore; /** * A cache that uses a bounded amount of space on a filesystem. Each cache @@ -213,7 +212,7 @@ public final class DiskLruCache implements Closeable { cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true)); return cache; } catch (IOException journalIsCorrupt) { - Libcore.logW("DiskLruCache " + directory + " is corrupt: " + Platform.get().logW("DiskLruCache " + directory + " is corrupt: " + journalIsCorrupt.getMessage() + ", removing"); cache.delete(); } @@ -228,7 +227,7 @@ public final class DiskLruCache implements Closeable { private void readJournal() throws IOException { StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), - Charsets.US_ASCII); + Util.US_ASCII); try { String magic = reader.readLine(); String version = reader.readLine(); @@ -252,7 +251,7 @@ public final class DiskLruCache implements Closeable { } } } finally { - IoUtils.closeQuietly(reader); + Util.closeQuietly(reader); } } @@ -344,7 +343,7 @@ public final class DiskLruCache implements Closeable { } private static void deleteIfExists(File file) throws IOException { - Libcore.deleteIfExists(file); + file.delete(); } /** @@ -460,7 +459,7 @@ public final class DiskLruCache implements Closeable { } if (!entry.getDirtyFile(i).exists()) { editor.abort(); - Libcore.logW( + Platform.get().logW( "DiskLruCache: Newly created entry doesn't have file for index " + i); return; } @@ -598,7 +597,7 @@ public final class DiskLruCache implements Closeable { */ public void delete() throws IOException { close(); - IoUtils.deleteContents(directory); + Util.deleteContents(directory); } private void validateKey(String key) { @@ -609,7 +608,7 @@ public final class DiskLruCache implements Closeable { } private static String inputStreamToString(InputStream in) throws IOException { - return Streams.readFully(new InputStreamReader(in, Charsets.UTF_8)); + return Util.readFully(new InputStreamReader(in, UTF_8)); } /** @@ -651,7 +650,7 @@ public final class DiskLruCache implements Closeable { @Override public void close() { for (InputStream in : ins) { - IoUtils.closeQuietly(in); + Util.closeQuietly(in); } } } @@ -719,10 +718,10 @@ public final class DiskLruCache implements Closeable { public void set(int index, String value) throws IOException { Writer writer = null; try { - writer = new OutputStreamWriter(newOutputStream(index), Charsets.UTF_8); + writer = new OutputStreamWriter(newOutputStream(index), UTF_8); writer.write(value); } finally { - IoUtils.closeQuietly(writer); + Util.closeQuietly(writer); } } diff --git a/src/main/java/libcore/util/MutableBoolean.java b/src/main/java/com/squareup/okhttp/internal/Dns.java index 359a8f9..37fa609 100644 --- a/src/main/java/libcore/util/MutableBoolean.java +++ b/src/main/java/com/squareup/okhttp/internal/Dns.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * 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. @@ -13,13 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.squareup.okhttp.internal; -package libcore.util; +import java.net.InetAddress; +import java.net.UnknownHostException; -public final class MutableBoolean { - public boolean value; +/** + * Domain name service. Prefer this over {@link InetAddress#getAllByName} to + * make code more testable. + */ +public interface Dns { + Dns DEFAULT = new Dns() { + @Override public InetAddress[] getAllByName(String host) throws UnknownHostException { + return InetAddress.getAllByName(host); + } + }; - public MutableBoolean(boolean value) { - this.value = value; - } + InetAddress[] getAllByName(String host) throws UnknownHostException; } diff --git a/src/main/java/com/squareup/okhttp/internal/Platform.java b/src/main/java/com/squareup/okhttp/internal/Platform.java new file mode 100644 index 0000000..ab71c62 --- /dev/null +++ b/src/main/java/com/squareup/okhttp/internal/Platform.java @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2012 Square, Inc. + * 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.internal; + +import com.squareup.okhttp.OkHttpClient; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.net.Socket; +import java.net.SocketException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import javax.net.ssl.SSLSocket; + +/** + * Access to Platform-specific features necessary for SPDY and advanced TLS. + * + * <h3>SPDY</h3> + * SPDY requires a TLS extension called NPN (Next Protocol Negotiation) that's + * available in Android 4.1+ and OpenJDK 7+ (with the npn-boot extension). It + * also requires a recent version of {@code DeflaterOutputStream} that is + * public API in Java 7 and callable via reflection in Android 4.1+. + */ +public class Platform { + private static final Platform PLATFORM = findPlatform(); + + private Constructor<DeflaterOutputStream> deflaterConstructor; + + public static Platform get() { + return PLATFORM; + } + + public void logW(String warning) { + System.out.println(warning); + } + + public void tagSocket(Socket socket) throws SocketException { + } + + public void untagSocket(Socket socket) throws SocketException { + } + + public URI toUriLenient(URL url) throws URISyntaxException { + return url.toURI(); // this isn't as good as the built-in toUriLenient + } + + /** + * Attempt a TLS connection with useful extensions enabled. This mode + * supports more features, but is less likely to be compatible with older + * HTTPS servers. + */ + public void enableTlsExtensions(SSLSocket socket, String uriHost) { + } + + /** + * Attempt a secure connection with basic functionality to maximize + * compatibility. Currently this uses SSL 3.0. + */ + public void supportTlsIntolerantServer(SSLSocket socket) { + socket.setEnabledProtocols(new String[]{"SSLv3"}); + } + + /** + * Returns the negotiated protocol, or null if no protocol was negotiated. + */ + public byte[] getNpnSelectedProtocol(SSLSocket socket) { + return null; + } + + /** + * Sets client-supported protocols on a socket to send to a server. The + * protocols are only sent if the socket implementation supports NPN. + */ + public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) { + } + + /** + * Returns a deflater output stream that supports SYNC_FLUSH for SPDY name + * value blocks. This throws an {@link UnsupportedOperationException} on + * Java 6 and earlier where there is no built-in API to do SYNC_FLUSH. + */ + public OutputStream newDeflaterOutputStream( + OutputStream out, Deflater deflater, boolean syncFlush) { + try { + Constructor<DeflaterOutputStream> constructor = deflaterConstructor; + if (constructor == null) { + constructor = deflaterConstructor = DeflaterOutputStream.class.getConstructor( + OutputStream.class, Deflater.class, boolean.class); + } + return constructor.newInstance(out, deflater, syncFlush); + } catch (NoSuchMethodException e) { + throw new UnsupportedOperationException("Cannot SPDY; no SYNC_FLUSH available"); + } catch (InvocationTargetException e) { + throw e.getCause() instanceof RuntimeException + ? (RuntimeException) e.getCause() + : new RuntimeException(e.getCause()); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new AssertionError(); + } + } + + /** + * Attempt to match the host runtime to a capable Platform implementation. + */ + private static Platform findPlatform() { + // Attempt to find Android 2.3+ APIs. + Class<?> openSslSocketClass; + Method setUseSessionTickets; + Method setHostname; + try { + openSslSocketClass = Class.forName( + "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl"); + setUseSessionTickets = openSslSocketClass.getMethod( + "setUseSessionTickets", boolean.class); + setHostname = openSslSocketClass.getMethod("setHostname", String.class); + + // Attempt to find Android 4.1+ APIs. + try { + Method setNpnProtocols = openSslSocketClass.getMethod( + "setNpnProtocols", byte[].class); + Method getNpnSelectedProtocol = openSslSocketClass.getMethod( + "getNpnSelectedProtocol"); + return new Android41(openSslSocketClass, setUseSessionTickets, setHostname, + setNpnProtocols, getNpnSelectedProtocol); + } catch (NoSuchMethodException ignored) { + return new Android23(openSslSocketClass, setUseSessionTickets, setHostname); + } + } catch (ClassNotFoundException ignored) { + // This isn't an Android runtime. + } catch (NoSuchMethodException ignored) { + // This isn't Android 2.3 or better. + } + + // Attempt to find the Jetty's NPN extension for OpenJDK. + try { + String npnClassName = "org.eclipse.jetty.npn.NextProtoNego"; + Class<?> nextProtoNegoClass = Class.forName(npnClassName); + Class<?> providerClass = Class.forName(npnClassName + "$Provider"); + Class<?> clientProviderClass = Class.forName(npnClassName + "$ClientProvider"); + Method putMethod = nextProtoNegoClass.getMethod("put", SSLSocket.class, providerClass); + Method getMethod = nextProtoNegoClass.getMethod("get", SSLSocket.class); + return new JdkWithJettyNpnPlatform(putMethod, getMethod, clientProviderClass); + } catch (ClassNotFoundException ignored) { + return new Platform(); // NPN isn't on the classpath. + } catch (NoSuchMethodException ignored) { + return new Platform(); // The NPN version isn't what we expect. + } + } + + /** + * Android version 2.3 and newer support TLS session tickets and server name + * indication (SNI). + */ + private static class Android23 extends Platform { + protected final Class<?> openSslSocketClass; + private final Method setUseSessionTickets; + private final Method setHostname; + + private Android23(Class<?> openSslSocketClass, Method setUseSessionTickets, + Method setHostname) { + this.openSslSocketClass = openSslSocketClass; + this.setUseSessionTickets = setUseSessionTickets; + this.setHostname = setHostname; + } + + @Override public void enableTlsExtensions(SSLSocket socket, String uriHost) { + super.enableTlsExtensions(socket, uriHost); + if (openSslSocketClass.isInstance(socket)) { + // This is Android: use reflection on OpenSslSocketImpl. + try { + setUseSessionTickets.invoke(socket, true); + setHostname.invoke(socket, uriHost); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + } + } + + /** + * Android version 4.1 and newer support NPN. + */ + private static class Android41 extends Android23 { + private final Method setNpnProtocols; + private final Method getNpnSelectedProtocol; + + private Android41(Class<?> openSslSocketClass, Method setUseSessionTickets, + Method setHostname, Method setNpnProtocols, Method getNpnSelectedProtocol) { + super(openSslSocketClass, setUseSessionTickets, setHostname); + this.setNpnProtocols = setNpnProtocols; + this.getNpnSelectedProtocol = getNpnSelectedProtocol; + } + + @Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) { + if (!openSslSocketClass.isInstance(socket)) { + return; + } + try { + setNpnProtocols.invoke(socket, new Object[] {npnProtocols}); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + @Override public byte[] getNpnSelectedProtocol(SSLSocket socket) { + if (!openSslSocketClass.isInstance(socket)) { + return null; + } + try { + return (byte[]) getNpnSelectedProtocol.invoke(socket); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + } + + /** + * OpenJDK 7 plus {@code org.mortbay.jetty.npn/npn-boot} on the boot class + * path. + */ + private static class JdkWithJettyNpnPlatform extends Platform { + private final Method getMethod; + private final Method putMethod; + private final Class<?> clientProviderClass; + + public JdkWithJettyNpnPlatform( + Method putMethod, Method getMethod, Class<?> clientProviderClass) { + this.putMethod = putMethod; + this.getMethod = getMethod; + this.clientProviderClass = clientProviderClass; + } + + @Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) { + try { + List<String> strings = new ArrayList<String>(); + for (int i = 0; i < npnProtocols.length;) { + int length = npnProtocols[i++]; + strings.add(new String(npnProtocols, i, length, "US-ASCII")); + i += length; + } + Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(), + new Class[] {clientProviderClass}, new JettyNpnProvider(strings)); + putMethod.invoke(null, socket, provider); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new AssertionError(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + + @Override public byte[] getNpnSelectedProtocol(SSLSocket socket) { + try { + JettyNpnProvider provider = (JettyNpnProvider) Proxy.getInvocationHandler( + getMethod.invoke(null, socket)); + if (!provider.unsupported && provider.selected == null) { + Logger logger = Logger.getLogger(OkHttpClient.class.getName()); + logger.log(Level.INFO, "NPN callback dropped so SPDY is disabled. " + + "Is npn-boot on the boot class path?"); + return null; + } + return provider.unsupported + ? null + : provider.selected.getBytes("US-ASCII"); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(); + } catch (InvocationTargetException e) { + throw new AssertionError(); + } catch (IllegalAccessException e) { + throw new AssertionError(); + } + } + } + + /** + * Handle the methods of NextProtoNego's ClientProvider and ServerProvider + * without a compile-time dependency on those interfaces. + */ + private static class JettyNpnProvider implements InvocationHandler { + private final List<String> clientProtocols; + private boolean unsupported; + private String selected; + + public JettyNpnProvider(List<String> clientProtocols) { + this.clientProtocols = clientProtocols; + } + + @Override public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + String methodName = method.getName(); + Class<?> returnType = method.getReturnType(); + if (methodName.equals("supports") && boolean.class == returnType) { + return true; + } else if (methodName.equals("unsupported") && void.class == returnType) { + this.unsupported = true; + return null; + } else if (methodName.equals("selectProtocol") && String.class == returnType + && args.length == 1 && (args[0] == null || args[0] instanceof List)) { + // TODO: use OpenSSL's algorithm which uses both lists + List<?> serverProtocols = (List) args[0]; + System.out.println("CLIENT PROTOCOLS: " + clientProtocols + " SERVER PROTOCOLS: " + serverProtocols); + this.selected = clientProtocols.get(0); + return selected; + } else { + return method.invoke(this, args); + } + } + } +} diff --git a/src/main/java/libcore/io/StrictLineReader.java b/src/main/java/com/squareup/okhttp/internal/StrictLineReader.java index dabb516..9011b2c 100644 --- a/src/main/java/libcore/io/StrictLineReader.java +++ b/src/main/java/com/squareup/okhttp/internal/StrictLineReader.java @@ -14,15 +14,17 @@ * limitations under the License. */ -package libcore.io; +package com.squareup.okhttp.internal; +import static com.squareup.okhttp.internal.Util.ISO_8859_1; +import static com.squareup.okhttp.internal.Util.US_ASCII; +import static com.squareup.okhttp.internal.Util.UTF_8; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; -import libcore.util.Charsets; /** * Buffers input from an {@link InputStream} for reading lines. @@ -78,7 +80,7 @@ public class StrictLineReader implements Closeable { * @throws IllegalArgumentException for negative or zero {@code capacity}. */ public StrictLineReader(InputStream in, int capacity) { - this(in, capacity, Charsets.US_ASCII); + this(in, capacity, US_ASCII); } /** @@ -112,8 +114,7 @@ public class StrictLineReader implements Closeable { if (capacity < 0) { throw new IllegalArgumentException("capacity <= 0"); } - if (!(charset.equals(Charsets.US_ASCII) || charset.equals(Charsets.UTF_8) - || charset.equals(Charsets.ISO_8859_1))) { + if (!(charset.equals(US_ASCII) || charset.equals(UTF_8) || charset.equals(ISO_8859_1))) { throw new IllegalArgumentException("Unsupported encoding"); } @@ -213,16 +214,6 @@ public class StrictLineReader implements Closeable { } /** - * Check whether there was an unterminated line at end of input after the line reader reported - * end-of-input with EOFException. The value is meaningless in any other situation. - * - * @return true if there was an unterminated line at end of input. - */ - public boolean hasUnterminatedLine() { - return end == -1; - } - - /** * Reads new input data into the buffer. Call only with pos == end or end == -1, * depending on the desired outcome if the function throws. * diff --git a/src/main/java/libcore/io/Streams.java b/src/main/java/com/squareup/okhttp/internal/Util.java index 1ad2356..f47362c 100644 --- a/src/main/java/libcore/io/Streams.java +++ b/src/main/java/com/squareup/okhttp/internal/Util.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * 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. @@ -14,22 +14,146 @@ * limitations under the License. */ -package libcore.io; +package com.squareup.okhttp.internal; -import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.EOFException; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.StringWriter; +import java.net.URI; +import java.net.URL; +import java.nio.ByteOrder; +import java.nio.charset.Charset; import java.util.concurrent.atomic.AtomicReference; -import libcore.util.Libcore; -public final class Streams { +/** + * Junk drawer of utility methods. + */ +public final class Util { + public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + /** A cheap and type-safe constant for the ISO-8859-1 Charset. */ + public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + /** A cheap and type-safe constant for the US-ASCII Charset. */ + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + + /** A cheap and type-safe constant for the UTF-8 Charset. */ + public static final Charset UTF_8 = Charset.forName("UTF-8"); private static AtomicReference<byte[]> skipBuffer = new AtomicReference<byte[]>(); - private Streams() { + private Util() { + } + + public static int getEffectivePort(URI uri) { + return getEffectivePort(uri.getScheme(), uri.getPort()); + } + + public static int getEffectivePort(URL url) { + return getEffectivePort(url.getProtocol(), url.getPort()); + } + + private static int getEffectivePort(String scheme, int specifiedPort) { + return specifiedPort != -1 + ? specifiedPort + : getDefaultPort(scheme); + } + + public static int getDefaultPort(String scheme) { + if ("http".equalsIgnoreCase(scheme)) { + return 80; + } else if ("https".equalsIgnoreCase(scheme)) { + return 443; + } else { + return -1; + } + } + + public static void checkOffsetAndCount(int arrayLength, int offset, int count) { + if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) { + throw new ArrayIndexOutOfBoundsException(); + } + } + + public static void pokeInt(byte[] dst, int offset, int value, ByteOrder order) { + if (order == ByteOrder.BIG_ENDIAN) { + dst[offset++] = (byte) ((value >> 24) & 0xff); + dst[offset++] = (byte) ((value >> 16) & 0xff); + dst[offset++] = (byte) ((value >> 8) & 0xff); + dst[offset ] = (byte) ((value >> 0) & 0xff); + } else { + dst[offset++] = (byte) ((value >> 0) & 0xff); + dst[offset++] = (byte) ((value >> 8) & 0xff); + dst[offset++] = (byte) ((value >> 16) & 0xff); + dst[offset ] = (byte) ((value >> 24) & 0xff); + } + } + + /** + * Returns true if two possibly-null objects are equal. + */ + public static boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** + * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null. + */ + public static void closeQuietly(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } + + /** + * Closes {@code a} and {@code b}. If either close fails, this completes + * the other close and rethrows the first encountered exception. + */ + public static void closeAll(Closeable a, Closeable b) throws IOException { + Throwable thrown = null; + try { + a.close(); + } catch (Throwable e) { + thrown = e; + } + try { + b.close(); + } catch (Throwable e) { + if (thrown == null) thrown = e; + } + if (thrown == null) return; + if (thrown instanceof IOException) throw (IOException) thrown; + if (thrown instanceof RuntimeException) throw (RuntimeException) thrown; + if (thrown instanceof Error) throw (Error) thrown; + throw new AssertionError(thrown); + } + + /** + * Recursively delete everything in {@code dir}. + */ + // TODO: this should specify paths as Strings rather than as Files + public static void deleteContents(File dir) throws IOException { + File[] files = dir.listFiles(); + if (files == null) { + throw new IllegalArgumentException("not a directory: " + dir); + } + for (File file : files) { + if (file.isDirectory()) { + deleteContents(file); + } + if (!file.delete()) { + throw new IOException("failed to delete file: " + file); + } + } } /** @@ -77,7 +201,7 @@ public final class Streams { if (dst == null) { throw new NullPointerException("dst == null"); } - Libcore.checkOffsetAndCount(dst.length, offset, byteCount); + checkOffsetAndCount(dst.length, offset, byteCount); while (byteCount > 0) { int bytesRead = in.read(dst, offset, byteCount); if (bytesRead < 0) { @@ -89,30 +213,6 @@ public final class Streams { } /** - * Returns a byte[] containing the remainder of 'in', closing it when done. - */ - public static byte[] readFully(InputStream in) throws IOException { - try { - return readFullyNoClose(in); - } finally { - in.close(); - } - } - - /** - * Returns a byte[] containing the remainder of 'in'. - */ - public static byte[] readFullyNoClose(InputStream in) throws IOException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int count; - while ((count = in.read(buffer)) != -1) { - bytes.write(buffer, 0, count); - } - return bytes.toByteArray(); - } - - /** * Returns the remainder of 'reader' as a string, closing it when done. */ public static String readFully(Reader reader) throws IOException { @@ -196,7 +296,6 @@ public final class Streams { */ public static String readAsciiLine(InputStream in) throws IOException { // TODO: support UTF-8 here instead - StringBuilder result = new StringBuilder(80); while (true) { int c = in.read(); diff --git a/src/main/java/libcore/net/http/AbstractHttpInputStream.java b/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java index 70f76b7..d915552 100644 --- a/src/main/java/libcore/net/http/AbstractHttpInputStream.java +++ b/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; +import com.squareup.okhttp.internal.Util; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.CacheRequest; -import libcore.io.Streams; /** * An input stream for the body of an HTTP response. @@ -60,7 +60,7 @@ abstract class AbstractHttpInputStream extends InputStream { * need to override the latter. */ @Override public final int read() throws IOException { - return Streams.readSingleByte(this); + return Util.readSingleByte(this); } protected final void checkNotClosed() throws IOException { diff --git a/src/main/java/libcore/net/http/AbstractHttpOutputStream.java b/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java index 145bc50..5d835cc 100644 --- a/src/main/java/libcore/net/http/AbstractHttpOutputStream.java +++ b/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpOutputStream.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; import java.io.IOException; import java.io.OutputStream; diff --git a/src/main/java/libcore/net/http/HeaderParser.java b/src/main/java/com/squareup/okhttp/internal/http/HeaderParser.java index b35db78..0dd096e 100644 --- a/src/main/java/libcore/net/http/HeaderParser.java +++ b/src/main/java/com/squareup/okhttp/internal/http/HeaderParser.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; final class HeaderParser { diff --git a/src/main/java/libcore/net/http/HttpAuthenticator.java b/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java index 882d16f..70104a5 100644 --- a/src/main/java/libcore/net/http/HttpAuthenticator.java +++ b/src/main/java/com/squareup/okhttp/internal/http/HttpAuthenticator.java @@ -14,12 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; -import static com.squareup.okhttp.OkHttpConnection.HTTP_PROXY_AUTH; -import static com.squareup.okhttp.OkHttpConnection.HTTP_UNAUTHORIZED; +import com.squareup.okhttp.internal.Base64; import java.io.IOException; import java.net.Authenticator; +import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.PasswordAuthentication; @@ -27,7 +28,6 @@ import java.net.Proxy; import java.net.URL; import java.util.ArrayList; import java.util.List; -import libcore.io.Base64; /** * Handles HTTP authentication headers from origin and proxy servers. @@ -42,7 +42,7 @@ public final class HttpAuthenticator { * @return true if credentials have been added to successorRequestHeaders * and another request should be attempted. */ - public static boolean processAuthHeader(int responseCode, RawHeaders responeHeaders, + public static boolean processAuthHeader(int responseCode, RawHeaders responseHeaders, RawHeaders successorRequestHeaders, Proxy proxy, URL url) throws IOException { if (responseCode != HTTP_PROXY_AUTH && responseCode != HTTP_UNAUTHORIZED) { throw new IllegalArgumentException(); @@ -52,7 +52,7 @@ public final class HttpAuthenticator { String challengeHeader = responseCode == HTTP_PROXY_AUTH ? "Proxy-Authenticate" : "WWW-Authenticate"; - String credentials = getCredentials(responeHeaders, challengeHeader, proxy, url); + String credentials = getCredentials(responseHeaders, challengeHeader, proxy, url); if (credentials == null) { return false; // Could not find credentials so end the request cycle. } diff --git a/src/main/java/libcore/net/http/HttpDate.java b/src/main/java/com/squareup/okhttp/internal/http/HttpDate.java index 41ae5ef..41f03fa 100644 --- a/src/main/java/libcore/net/http/HttpDate.java +++ b/src/main/java/com/squareup/okhttp/internal/http/HttpDate.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; import java.text.DateFormat; import java.text.ParseException; @@ -26,7 +26,7 @@ import java.util.TimeZone; /** * Best-effort parser for HTTP dates. */ -public final class HttpDate { +final class HttpDate { /** * Most websites serve cookies in the blessed format. Eagerly create the parser to ensure such diff --git a/src/main/java/libcore/net/http/HttpEngine.java b/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java index af3b831..22483ac 100644 --- a/src/main/java/libcore/net/http/HttpEngine.java +++ b/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java @@ -15,9 +15,19 @@ * limitations under the License. */ -package libcore.net.http; - -import com.squareup.okhttp.OkHttpConnection; +package com.squareup.okhttp.internal.http; + +import com.squareup.okhttp.Address; +import com.squareup.okhttp.Connection; +import com.squareup.okhttp.OkResponseCache; +import com.squareup.okhttp.ResponseSource; +import com.squareup.okhttp.TunnelRequest; +import com.squareup.okhttp.internal.Platform; +import com.squareup.okhttp.internal.Util; +import static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY; +import static com.squareup.okhttp.internal.Util.getDefaultPort; +import static com.squareup.okhttp.internal.Util.getEffectivePort; +import com.squareup.okhttp.internal.Dns; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -25,11 +35,12 @@ import java.io.OutputStream; import java.net.CacheRequest; import java.net.CacheResponse; import java.net.CookieHandler; +import java.net.HttpURLConnection; import java.net.Proxy; -import java.net.ResponseCache; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.net.UnknownHostException; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -38,11 +49,6 @@ import java.util.Map; import java.util.zip.GZIPInputStream; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSocketFactory; -import libcore.io.IoUtils; -import libcore.util.EmptyArray; -import libcore.util.ExtendedResponseCache; -import libcore.util.Libcore; -import libcore.util.ResponseSource; /** * Handles a single HTTP request/response pair. Each HTTP engine follows this @@ -75,19 +81,9 @@ public class HttpEngine { return result; } @Override public InputStream getBody() throws IOException { - return new ByteArrayInputStream(EmptyArray.BYTE); + return new ByteArrayInputStream(EMPTY_BYTE_ARRAY); } }; - public static final int DEFAULT_CHUNK_LENGTH = 1024; - - public static final String OPTIONS = "OPTIONS"; - public static final String GET = "GET"; - public static final String HEAD = "HEAD"; - public static final String POST = "POST"; - public static final String PUT = "PUT"; - public static final String DELETE = "DELETE"; - public static final String TRACE = "TRACE"; - public static final int HTTP_CONTINUE = 100; protected final HttpURLConnectionImpl policy; @@ -96,7 +92,8 @@ public class HttpEngine { private ResponseSource responseSource; - protected HttpConnection connection; + protected Connection connection; + protected RouteSelector routeSelector; private OutputStream requestBodyOut; private Transport transport; @@ -104,7 +101,6 @@ public class HttpEngine { private InputStream responseTransferIn; private InputStream responseBodyIn; - private final ResponseCache responseCache = ResponseCache.getDefault(); private CacheResponse cacheResponse; private CacheRequest cacheRequest; @@ -151,14 +147,14 @@ public class HttpEngine { * release it when it is unneeded. */ public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders, - HttpConnection connection, RetryableOutputStream requestBodyOut) throws IOException { + Connection connection, RetryableOutputStream requestBodyOut) throws IOException { this.policy = policy; this.method = method; this.connection = connection; this.requestBodyOut = requestBodyOut; try { - uri = Libcore.toUriLenient(policy.getURL()); + uri = Platform.get().toUriLenient(policy.getURL()); } catch (URISyntaxException e) { throw new IOException(e); } @@ -182,8 +178,8 @@ public class HttpEngine { prepareRawRequestHeaders(); initResponseSource(); - if (responseCache instanceof ExtendedResponseCache) { - ((ExtendedResponseCache) responseCache).trackResponse(responseSource); + if (policy.responseCache instanceof OkResponseCache) { + ((OkResponseCache) policy.responseCache).trackResponse(responseSource); } /* @@ -194,7 +190,7 @@ public class HttpEngine { */ if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) { if (responseSource == ResponseSource.CONDITIONAL_CACHE) { - IoUtils.closeQuietly(cachedResponseBody); + Util.closeQuietly(cachedResponseBody); } this.responseSource = ResponseSource.CACHE; this.cacheResponse = GATEWAY_TIMEOUT_RESPONSE; @@ -206,7 +202,7 @@ public class HttpEngine { if (responseSource.requiresConnection()) { sendSocketRequest(); } else if (connection != null) { - HttpConnectionPool.INSTANCE.recycle(connection); + policy.connectionPool.recycle(connection); connection = null; } } @@ -217,11 +213,11 @@ public class HttpEngine { */ private void initResponseSource() throws IOException { responseSource = ResponseSource.NETWORK; - if (!policy.getUseCaches() || responseCache == null) { + if (!policy.getUseCaches() || policy.responseCache == null) { return; } - CacheResponse candidate = responseCache.get(uri, method, + CacheResponse candidate = policy.responseCache.get(uri, method, requestHeaders.getHeaders().toMultimap(false)); if (candidate == null) { return; @@ -232,7 +228,7 @@ public class HttpEngine { if (!acceptCacheResponseType(candidate) || responseHeadersMap == null || cachedResponseBody == null) { - IoUtils.closeQuietly(cachedResponseBody); + Util.closeQuietly(cachedResponseBody); return; } @@ -246,7 +242,7 @@ public class HttpEngine { } else if (responseSource == ResponseSource.CONDITIONAL_CACHE) { this.cacheResponse = candidate; } else if (responseSource == ResponseSource.NETWORK) { - IoUtils.closeQuietly(cachedResponseBody); + Util.closeQuietly(cachedResponseBody); } else { throw new AssertionError(); } @@ -261,7 +257,7 @@ public class HttpEngine { throw new IllegalStateException(); } - transport = connection.newTransport(this); + transport = (Transport) connection.newTransport(this); if (hasRequestBody() && requestBodyOut == null) { // Create a request body if we don't have one already. We'll already @@ -273,14 +269,30 @@ public class HttpEngine { /** * Connect to the origin server either directly or via a proxy. */ - protected void connect() throws IOException { + protected final void connect() throws IOException { if (connection != null) { return; } - connection = HttpConnection.connect(uri, getSslSocketFactory(), getHostnameVerifier(), - policy.getProxy(), policy.getConnectTimeout(), policy.getReadTimeout(), - getTunnelConfig()); - Proxy proxy = connection.getAddress().getProxy(); + if (routeSelector == null) { + String uriHost = uri.getHost(); + if (uriHost == null) { + throw new UnknownHostException(uri.toString()); + } + Address address = new Address(uriHost, getEffectivePort(uri), + getSslSocketFactory(), getHostnameVerifier(), policy.getProxy()); + routeSelector = new RouteSelector( + address, uri, policy.proxySelector, policy.connectionPool, Dns.DEFAULT); + } + connection = routeSelector.next(); + if (!connection.isRecycled()) { + connection.connect(policy.getConnectTimeout(), policy.getReadTimeout(), + getTunnelConfig()); + if (connection.isSpdy()) { + policy.connectionPool.share(connection); + } + } + connected(connection); + Proxy proxy = connection.getProxy(); if (proxy != null) { policy.setProxy(proxy); // Add the authority to the request line when we're using a proxy. @@ -289,6 +301,13 @@ public class HttpEngine { } /** + * Called after a socket connection has been created or retrieved from the + * pool. Subclasses use this hook to get a reference to the TLS data. + */ + protected void connected(Connection connection) { + } + + /** * @param body the response body, or null if it doesn't exist or isn't * available. */ @@ -303,7 +322,7 @@ public class HttpEngine { } boolean hasRequestBody() { - return method == POST || method == PUT; + return method.equals("POST") || method.equals("PUT"); } /** @@ -349,7 +368,7 @@ public class HttpEngine { return cacheResponse; } - public final HttpConnection getConnection() { + public final Connection getConnection() { return connection; } @@ -368,7 +387,7 @@ public class HttpEngine { private void maybeCache() throws IOException { // Are we caching at all? - if (!policy.getUseCaches() || responseCache == null) { + if (!policy.getUseCaches() || policy.responseCache == null) { return; } @@ -378,10 +397,10 @@ public class HttpEngine { } // Offer this request to the cache. - cacheRequest = responseCache.put(uri, getHttpConnectionToCache()); + cacheRequest = policy.responseCache.put(uri, getHttpConnectionToCache()); } - protected OkHttpConnection getHttpConnectionToCache() { + protected HttpURLConnection getHttpConnectionToCache() { return policy; } @@ -394,7 +413,7 @@ public class HttpEngine { public final void automaticallyReleaseConnectionToPool() { automaticallyReleaseConnectionToPool = true; if (connection != null && connectionReleased) { - HttpConnectionPool.INSTANCE.recycle(connection); + policy.connectionPool.recycle(connection); connection = null; } } @@ -407,17 +426,17 @@ public class HttpEngine { public final void release(boolean reusable) { // If the response body comes from the cache, close it. if (responseBodyIn == cachedResponseBody) { - IoUtils.closeQuietly(responseBodyIn); + Util.closeQuietly(responseBodyIn); } if (!connectionReleased && connection != null) { connectionReleased = true; if (!reusable || !transport.makeReusable(requestBodyOut, responseTransferIn)) { - connection.closeSocketAndStreams(); + Util.closeQuietly(connection); connection = null; } else if (automaticallyReleaseConnectionToPool) { - HttpConnectionPool.INSTANCE.recycle(connection); + policy.connectionPool.recycle(connection); connection = null; } } @@ -445,7 +464,7 @@ public class HttpEngine { int responseCode = responseHeaders.getHeaders().getResponseCode(); // HEAD requests never yield a body regardless of the response headers. - if (method == HEAD) { + if (method.equals("HEAD")) { return false; } @@ -484,15 +503,13 @@ public class HttpEngine { requestHeaders.setHost(getOriginAddress(policy.getURL())); } - // TODO: this shouldn't be set for SPDY (it's ignored) - if ((connection == null || connection.httpMinorVersion != 0) + if ((connection == null || connection.getHttpMinorVersion() != 0) && requestHeaders.getConnection() == null) { requestHeaders.setConnection("Keep-Alive"); } if (requestHeaders.getAcceptEncoding() == null) { transparentGzip = true; - // TODO: this shouldn't be set for SPDY (it isn't necessary) requestHeaders.setAcceptEncoding("gzip"); } @@ -505,7 +522,7 @@ public class HttpEngine { requestHeaders.setIfModifiedSince(new Date(ifModifiedSince)); } - CookieHandler cookieHandler = CookieHandler.getDefault(); + CookieHandler cookieHandler = policy.cookieHandler; if (cookieHandler != null) { requestHeaders.addCookies( cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap(false))); @@ -518,7 +535,7 @@ public class HttpEngine { * it needs to be set even if the transport is SPDY. */ String getRequestLine() { - String protocol = (connection == null || connection.httpMinorVersion != 0) + String protocol = (connection == null || connection.getHttpMinorVersion() != 0) ? "HTTP/1.1" : "HTTP/1.0"; return method + " " + requestString() + " " + protocol; @@ -577,7 +594,7 @@ public class HttpEngine { return null; } - public static final String getDefaultUserAgent() { + public static String getDefaultUserAgent() { String agent = System.getProperty("http.agent"); return agent != null ? agent : ("Java" + System.getProperty("java.version")); } @@ -585,7 +602,7 @@ public class HttpEngine { public static String getOriginAddress(URL url) { int port = url.getPort(); String result = url.getHost(); - if (port > 0 && port != Libcore.getDefaultPort(url.getProtocol())) { + if (port > 0 && port != getDefaultPort(url.getProtocol())) { result = result + ":" + port; } return result; @@ -633,14 +650,15 @@ public class HttpEngine { release(true); ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders); setResponse(combinedHeaders, cachedResponseBody); - if (responseCache instanceof ExtendedResponseCache) { - ExtendedResponseCache httpResponseCache = (ExtendedResponseCache) responseCache; + if (policy.responseCache instanceof OkResponseCache) { + OkResponseCache httpResponseCache + = (OkResponseCache) policy.responseCache; httpResponseCache.trackConditionalCacheHit(); httpResponseCache.update(cacheResponse, getHttpConnectionToCache()); } return; } else { - IoUtils.closeQuietly(cachedResponseBody); + Util.closeQuietly(cachedResponseBody); } } @@ -651,7 +669,7 @@ public class HttpEngine { initContentStream(transport.getTransferStream(cacheRequest)); } - protected HttpConnection.TunnelConfig getTunnelConfig() { + protected TunnelRequest getTunnelConfig() { return null; } } diff --git a/src/main/java/libcore/net/http/HttpResponseCache.java b/src/main/java/com/squareup/okhttp/internal/http/HttpResponseCache.java index 653a920..4bdd8fc 100644 --- a/src/main/java/libcore/net/http/HttpResponseCache.java +++ b/src/main/java/com/squareup/okhttp/internal/http/HttpResponseCache.java @@ -14,11 +14,16 @@ * limitations under the License. */ -package libcore.net.http; - -import com.squareup.okhttp.OkHttpConnection; -import com.squareup.okhttp.OkHttpsConnection; - +package com.squareup.okhttp.internal.http; + +import com.squareup.okhttp.OkResponseCache; +import com.squareup.okhttp.ResponseSource; +import com.squareup.okhttp.internal.Util; +import static com.squareup.okhttp.internal.Util.US_ASCII; +import static com.squareup.okhttp.internal.Util.UTF_8; +import com.squareup.okhttp.internal.Base64; +import com.squareup.okhttp.internal.DiskLruCache; +import com.squareup.okhttp.internal.StrictLineReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.File; @@ -32,6 +37,7 @@ 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; @@ -47,22 +53,19 @@ 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 libcore.io.Base64; -import libcore.io.DiskLruCache; -import libcore.io.IoUtils; -import libcore.io.StrictLineReader; -import libcore.util.Charsets; -import libcore.util.ExtendedResponseCache; -import libcore.util.IntegralToString; -import libcore.util.ResponseSource; /** * Cache responses in a directory on the file system. Most clients should use * {@code android.net.HttpResponseCache}, the stable, documented front end for * this. */ -public final class HttpResponseCache extends ResponseCache implements ExtendedResponseCache { +public final class HttpResponseCache extends ResponseCache implements OkResponseCache { + 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; @@ -86,7 +89,7 @@ public final class HttpResponseCache extends ResponseCache implements ExtendedRe try { MessageDigest messageDigest = MessageDigest.getInstance("MD5"); byte[] md5bytes = messageDigest.digest(uri.toString().getBytes("UTF-8")); - return IntegralToString.bytesToHexString(md5bytes, false); + return bytesToHexString(md5bytes); } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); } catch (UnsupportedEncodingException e) { @@ -94,6 +97,17 @@ public final class HttpResponseCache extends ResponseCache implements ExtendedRe } } + 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<String, List<String>> requestHeaders) { String key = uriToKey(uri); @@ -121,24 +135,24 @@ public final class HttpResponseCache extends ResponseCache implements ExtendedRe } @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { - if (!(urlConnection instanceof OkHttpConnection)) { + if (!(urlConnection instanceof HttpURLConnection)) { return null; } - OkHttpConnection httpConnection = (OkHttpConnection) urlConnection; + HttpURLConnection httpConnection = (HttpURLConnection) urlConnection; String requestMethod = httpConnection.getRequestMethod(); String key = uriToKey(uri); - if (requestMethod.equals(HttpEngine.POST) - || requestMethod.equals(HttpEngine.PUT) - || requestMethod.equals(HttpEngine.DELETE)) { + 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(HttpEngine.GET)) { + } 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 @@ -181,8 +195,8 @@ public final class HttpResponseCache extends ResponseCache implements ExtendedRe * not updated. If the stored response has changed since {@code * conditionalCacheHit} was returned, this does nothing. */ - @Override public void update(CacheResponse conditionalCacheHit, OkHttpConnection httpConnection) - throws IOException { + @Override public void update(CacheResponse conditionalCacheHit, + HttpURLConnection httpConnection) throws IOException { HttpEngine httpEngine = getHttpEngine(httpConnection); URI uri = httpEngine.getUri(); ResponseHeaders response = httpEngine.getResponseHeaders(); @@ -305,7 +319,7 @@ public final class HttpResponseCache extends ResponseCache implements ExtendedRe done = true; writeAbortCount++; } - IoUtils.closeQuietly(cacheOut); + Util.closeQuietly(cacheOut); try { editor.abort(); } catch (IOException ignored) { @@ -374,7 +388,7 @@ public final class HttpResponseCache extends ResponseCache implements ExtendedRe */ public Entry(InputStream in) throws IOException { try { - StrictLineReader reader = new StrictLineReader(in, Charsets.US_ASCII); + StrictLineReader reader = new StrictLineReader(in, US_ASCII); uri = reader.readLine(); requestMethod = reader.readLine(); varyHeaders = new RawHeaders(); @@ -408,7 +422,7 @@ public final class HttpResponseCache extends ResponseCache implements ExtendedRe } } - public Entry(URI uri, RawHeaders varyHeaders, OkHttpConnection httpConnection) + public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection) throws IOException { this.uri = uri.toString(); this.varyHeaders = varyHeaders; @@ -416,8 +430,7 @@ public final class HttpResponseCache extends ResponseCache implements ExtendedRe this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields(), true); if (isHttps()) { - OkHttpsConnection httpsConnection - = (OkHttpsConnection) httpConnection; + HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection; cipherSuite = httpsConnection.getCipherSuite(); Certificate[] peerCertificatesNonFinal = null; try { @@ -435,7 +448,7 @@ public final class HttpResponseCache extends ResponseCache implements ExtendedRe public void writeTo(DiskLruCache.Editor editor) throws IOException { OutputStream out = editor.newOutputStream(ENTRY_METADATA); - Writer writer = new BufferedWriter(new OutputStreamWriter(out, Charsets.UTF_8)); + Writer writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8)); writer.write(uri + '\n'); writer.write(requestMethod + '\n'); diff --git a/src/main/java/libcore/net/http/HttpTransport.java b/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java index 83440d8..1c73423 100644 --- a/src/main/java/libcore/net/http/HttpTransport.java +++ b/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java @@ -14,8 +14,11 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; +import com.squareup.okhttp.Connection; +import com.squareup.okhttp.internal.Util; +import static com.squareup.okhttp.internal.Util.checkOffsetAndCount; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -23,10 +26,10 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.CacheRequest; import java.net.CookieHandler; -import libcore.io.Streams; -import libcore.util.Libcore; +import java.net.ProtocolException; +import java.net.Socket; -final class HttpTransport implements Transport { +public final class HttpTransport implements Transport { /** * The maximum number of bytes to buffer when sending headers and a request * body. When the headers and body can be sent in a single write, the @@ -35,6 +38,15 @@ final class HttpTransport implements Transport { */ private static final int MAX_REQUEST_BUFFER_LENGTH = 32768; + /** + * The timeout to use while discarding a stream of input data. Since this is + * used for connection reuse, this timeout should be significantly less than + * the time it takes to establish a new connection. + */ + private static final int DISCARD_STREAM_TIMEOUT_MILLIS = 30; + + public static final int DEFAULT_CHUNK_LENGTH = 1024; + private final HttpEngine httpEngine; private final InputStream socketIn; private final OutputStream socketOut; @@ -59,7 +71,7 @@ final class HttpTransport implements Transport { boolean chunked = httpEngine.requestHeaders.isChunked(); if (!chunked && httpEngine.policy.getChunkLength() > 0 - && httpEngine.connection.httpMinorVersion != 0) { + && httpEngine.connection.getHttpMinorVersion() != 0) { httpEngine.requestHeaders.setChunked(); chunked = true; } @@ -68,7 +80,7 @@ final class HttpTransport implements Transport { if (chunked) { int chunkLength = httpEngine.policy.getChunkLength(); if (chunkLength == -1) { - chunkLength = HttpEngine.DEFAULT_CHUNK_LENGTH; + chunkLength = DEFAULT_CHUNK_LENGTH; } writeRequestHeaders(); return new ChunkedOutputStream(requestOut, chunkLength); @@ -135,13 +147,13 @@ final class HttpTransport implements Transport { @Override public ResponseHeaders readResponseHeaders() throws IOException { RawHeaders headers = RawHeaders.fromBytes(socketIn); - httpEngine.connection.httpMinorVersion = headers.getHttpMinorVersion(); + httpEngine.connection.setHttpMinorVersion(headers.getHttpMinorVersion()); receiveHeaders(headers); return new ResponseHeaders(httpEngine.uri, headers); } private void receiveHeaders(RawHeaders headers) throws IOException { - CookieHandler cookieHandler = CookieHandler.getDefault(); + CookieHandler cookieHandler = httpEngine.policy.cookieHandler; if (cookieHandler != null) { cookieHandler.put(httpEngine.uri, headers.toMultimap(true)); } @@ -168,17 +180,36 @@ final class HttpTransport implements Transport { } if (responseBodyIn != null) { - // Discard the response body before the connection can be reused. - try { - Streams.skipAll(responseBodyIn); - } catch (IOException e) { - return false; - } + return discardStream(httpEngine, responseBodyIn); } return true; } + /** + * Discards the response body so that the connection can be reused. This + * needs to be done judiciously, since it delays the current request in + * order to speed up a potential future request that may never occur. + */ + private static boolean discardStream(HttpEngine httpEngine, InputStream responseBodyIn) { + Connection connection = httpEngine.connection; + if (connection == null) return false; + Socket socket = connection.getSocket(); + if (socket == null) return false; + try { + int socketTimeout = socket.getSoTimeout(); + socket.setSoTimeout(DISCARD_STREAM_TIMEOUT_MILLIS); + try { + Util.skipAll(responseBodyIn); + return true; + } finally { + socket.setSoTimeout(socketTimeout); + } + } catch (IOException e) { + return false; + } + } + @Override public InputStream getTransferStream(CacheRequest cacheRequest) throws IOException { if (!httpEngine.hasResponseBody()) { return new FixedLengthInputStream(socketIn, cacheRequest, httpEngine, 0); @@ -194,9 +225,9 @@ final class HttpTransport implements Transport { } /* - * Wrap the input stream from the HttpConnection (rather than - * just returning "socketIn" directly here), so that we can control - * its use after the reference escapes. + * Wrap the input stream from the connection (rather than just returning + * "socketIn" directly here), so that we can control its use after the + * reference escapes. */ return new UnknownLengthHttpInputStream(socketIn, cacheRequest, httpEngine); } @@ -215,9 +246,9 @@ final class HttpTransport implements Transport { @Override public void write(byte[] buffer, int offset, int count) throws IOException { checkNotClosed(); - Libcore.checkOffsetAndCount(buffer.length, offset, count); + checkOffsetAndCount(buffer.length, offset, count); if (count > bytesRemaining) { - throw new IOException("expected " + bytesRemaining + throw new ProtocolException("expected " + bytesRemaining + " bytes but received " + count); } socketOut.write(buffer, offset, count); @@ -237,7 +268,7 @@ final class HttpTransport implements Transport { } closed = true; if (bytesRemaining > 0) { - throw new IOException("unexpected end of stream"); + throw new ProtocolException("unexpected end of stream"); } } } @@ -283,7 +314,7 @@ final class HttpTransport implements Transport { @Override public synchronized void write(byte[] buffer, int offset, int count) throws IOException { checkNotClosed(); - Libcore.checkOffsetAndCount(buffer.length, offset, count); + checkOffsetAndCount(buffer.length, offset, count); while (count > 0) { int numBytesWritten; @@ -368,7 +399,7 @@ final class HttpTransport implements Transport { } @Override public int read(byte[] buffer, int offset, int count) throws IOException { - Libcore.checkOffsetAndCount(buffer.length, offset, count); + checkOffsetAndCount(buffer.length, offset, count); checkNotClosed(); if (bytesRemaining == 0) { return -1; @@ -376,7 +407,7 @@ final class HttpTransport implements Transport { int read = in.read(buffer, offset, Math.min(count, bytesRemaining)); if (read == -1) { unexpectedEndOfInput(); // the server didn't supply the promised content length - throw new IOException("unexpected end of stream"); + throw new ProtocolException("unexpected end of stream"); } bytesRemaining -= read; cacheWrite(buffer, offset, read); @@ -395,10 +426,10 @@ final class HttpTransport implements Transport { if (closed) { return; } - closed = true; - if (bytesRemaining != 0) { + if (bytesRemaining != 0 && !discardStream(httpEngine, this)) { unexpectedEndOfInput(); } + closed = true; } } @@ -406,7 +437,6 @@ final class HttpTransport implements Transport { * An HTTP body with alternating chunk sizes and chunk bodies. */ private static class ChunkedInputStream extends AbstractHttpInputStream { - private static final int MIN_LAST_CHUNK_LENGTH = "\r\n0\r\n\r\n".length(); private static final int NO_CHUNK_YET = -1; private final HttpTransport transport; private int bytesRemainingInChunk = NO_CHUNK_YET; @@ -419,7 +449,7 @@ final class HttpTransport implements Transport { } @Override public int read(byte[] buffer, int offset, int count) throws IOException { - Libcore.checkOffsetAndCount(buffer.length, offset, count); + checkOffsetAndCount(buffer.length, offset, count); checkNotClosed(); if (!hasMoreChunks) { @@ -438,27 +468,15 @@ final class HttpTransport implements Transport { } bytesRemainingInChunk -= read; cacheWrite(buffer, offset, read); - - /* - * If we're at the end of a chunk and the next chunk size is readable, - * read it! Reading the last chunk causes the underlying connection to - * be recycled and we want to do that as early as possible. Otherwise - * self-delimiting streams like gzip will never be recycled. - * http://code.google.com/p/android/issues/detail?id=7059 - */ - if (bytesRemainingInChunk == 0 && in.available() >= MIN_LAST_CHUNK_LENGTH) { - readChunkSize(); - } - return read; } private void readChunkSize() throws IOException { // read the suffix of the previous chunk if (bytesRemainingInChunk != NO_CHUNK_YET) { - Streams.readAsciiLine(in); + Util.readAsciiLine(in); } - String chunkSizeString = Streams.readAsciiLine(in); + String chunkSizeString = Util.readAsciiLine(in); int index = chunkSizeString.indexOf(";"); if (index != -1) { chunkSizeString = chunkSizeString.substring(0, index); @@ -466,7 +484,7 @@ final class HttpTransport implements Transport { try { bytesRemainingInChunk = Integer.parseInt(chunkSizeString.trim(), 16); } catch (NumberFormatException e) { - throw new IOException("Expected a hex chunk size, but was " + chunkSizeString); + throw new ProtocolException("Expected a hex chunk size but was " + chunkSizeString); } if (bytesRemainingInChunk == 0) { hasMoreChunks = false; @@ -489,11 +507,10 @@ final class HttpTransport implements Transport { if (closed) { return; } - - closed = true; - if (hasMoreChunks) { + if (hasMoreChunks && !discardStream(httpEngine, this)) { unexpectedEndOfInput(); } + closed = true; } } @@ -509,7 +526,7 @@ final class HttpTransport implements Transport { } @Override public int read(byte[] buffer, int offset, int count) throws IOException { - Libcore.checkOffsetAndCount(buffer.length, offset, count); + checkOffsetAndCount(buffer.length, offset, count); checkNotClosed(); if (in == null || inputExhausted) { return -1; diff --git a/src/main/java/libcore/net/http/HttpURLConnectionImpl.java b/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java index 0806680..aec7b4e 100644 --- a/src/main/java/libcore/net/http/HttpURLConnectionImpl.java +++ b/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java @@ -15,24 +15,31 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; -import com.squareup.okhttp.OkHttpConnection; +import com.squareup.okhttp.Connection; +import com.squareup.okhttp.ConnectionPool; +import com.squareup.okhttp.internal.Util; +import static com.squareup.okhttp.internal.Util.getEffectivePort; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.CookieHandler; import java.net.HttpRetryException; +import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.ProtocolException; import java.net.Proxy; +import java.net.ProxySelector; +import java.net.ResponseCache; import java.net.SocketPermission; import java.net.URL; import java.security.Permission; +import java.security.cert.CertificateException; import java.util.List; import java.util.Map; -import libcore.io.IoUtils; -import libcore.util.Libcore; +import javax.net.ssl.SSLHandshakeException; /** * This implementation uses HttpEngine to send requests and receive responses. @@ -48,7 +55,7 @@ import libcore.util.Libcore; * connection} field on this class for null/non-null to determine of an instance * is currently connected to a server. */ -public class HttpURLConnectionImpl extends OkHttpConnection { +public class HttpURLConnectionImpl extends HttpURLConnection { /** * HTTP 1.1 doesn't specify how many redirects to follow, but HTTP/1.0 * recommended 5. http://www.w3.org/Protocols/HTTP/1.0/spec.html#Code3xx @@ -58,6 +65,10 @@ public class HttpURLConnectionImpl extends OkHttpConnection { private final int defaultPort; private Proxy proxy; + final ProxySelector proxySelector; + final CookieHandler cookieHandler; + final ResponseCache responseCache; + final ConnectionPool connectionPool; private final RawHeaders rawRequestHeaders = new RawHeaders(); @@ -66,24 +77,24 @@ public class HttpURLConnectionImpl extends OkHttpConnection { protected IOException httpEngineFailure; protected HttpEngine httpEngine; - public HttpURLConnectionImpl(URL url, int port) { + public HttpURLConnectionImpl(URL url, int defaultPort, Proxy proxy, ProxySelector proxySelector, + CookieHandler cookieHandler, ResponseCache responseCache, + ConnectionPool connectionPool) { super(url); - defaultPort = port; - } - - public HttpURLConnectionImpl(URL url, int port, Proxy proxy) { - this(url, port); + this.defaultPort = defaultPort; this.proxy = proxy; + this.proxySelector = proxySelector; + this.cookieHandler = cookieHandler; + this.responseCache = responseCache; + this.connectionPool = connectionPool; } @Override public final void connect() throws IOException { initHttpEngine(); - try { - httpEngine.sendRequest(); - } catch (IOException e) { - httpEngineFailure = e; - throw e; - } + boolean success; + do { + success = execute(false); + } while (!success); } @Override public final void disconnect() { @@ -95,7 +106,7 @@ public class HttpURLConnectionImpl extends OkHttpConnection { // However the response body can be a GZIPInputStream that // still has unread data. if (httpEngine.hasResponse()) { - IoUtils.closeQuietly(httpEngine.getResponseBody()); + Util.closeQuietly(httpEngine.getResponseBody()); } httpEngine.release(false); } @@ -189,7 +200,8 @@ public class HttpURLConnectionImpl extends OkHttpConnection { InputStream result = response.getResponseBody(); if (result == null) { - throw new IOException("No response body exists; responseCode=" + getResponseCode()); + throw new ProtocolException("No response body exists; responseCode=" + + getResponseCode()); } return result; } @@ -242,10 +254,10 @@ public class HttpURLConnectionImpl extends OkHttpConnection { connected = true; try { if (doOutput) { - if (method == HttpEngine.GET) { + if (method.equals("GET")) { // they are requesting a stream to write to. This implies a POST method - method = HttpEngine.POST; - } else if (method != HttpEngine.POST && method != HttpEngine.PUT) { + method = "POST"; + } else if (!method.equals("POST") && !method.equals("PUT")) { // If the request method is neither POST nor PUT, then you're not writing throw new ProtocolException(method + " does not support writing"); } @@ -262,7 +274,7 @@ public class HttpURLConnectionImpl extends OkHttpConnection { * overridden by HttpsURLConnectionImpl. */ protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders, - HttpConnection connection, RetryableOutputStream requestBody) throws IOException { + Connection connection, RetryableOutputStream requestBody) throws IOException { return new HttpEngine(this, method, requestHeaders, connection, requestBody); } @@ -279,24 +291,8 @@ public class HttpURLConnectionImpl extends OkHttpConnection { } while (true) { - try { - httpEngine.sendRequest(); - httpEngine.readResponse(); - } catch (IOException e) { - /* - * If the connection was recycled, its staleness may have caused - * the failure. Silently retry with a different connection. - */ - OutputStream requestBody = httpEngine.getRequestBody(); - if (httpEngine.hasRecycledConnection() - && (requestBody == null || requestBody instanceof RetryableOutputStream)) { - httpEngine.release(false); - httpEngine = newHttpEngine(method, rawRequestHeaders, null, - (RetryableOutputStream) requestBody); - continue; - } - httpEngineFailure = e; - throw e; + if (!execute(true)) { + continue; } Retry retry = processResponseHeaders(); @@ -319,7 +315,7 @@ public class HttpURLConnectionImpl extends OkHttpConnection { int responseCode = getResponseCode(); if (responseCode == HTTP_MULT_CHOICE || responseCode == HTTP_MOVED_PERM || responseCode == HTTP_MOVED_TEMP || responseCode == HTTP_SEE_OTHER) { - retryMethod = HttpEngine.GET; + retryMethod = "GET"; requestBody = null; } @@ -339,6 +335,50 @@ public class HttpURLConnectionImpl extends OkHttpConnection { } } + /** + * Sends a request and optionally reads a response. Returns true if the + * request was successfully executed, and false if the request can be + * retried. Throws an exception if the request failed permanently. + */ + private boolean execute(boolean readResponse) throws IOException { + try { + httpEngine.sendRequest(); + if (readResponse) { + httpEngine.readResponse(); + } + return true; + } catch (IOException e) { + RouteSelector routeSelector = httpEngine.routeSelector; + if (routeSelector == null) { + throw e; // Without a route selector, we can't retry. + } else if (httpEngine.connection != null) { + routeSelector.connectFailed(httpEngine.connection, e); + } + + // The connection failure isn't fatal if there's another route to attempt. + OutputStream requestBody = httpEngine.getRequestBody(); + if (routeSelector.hasNext() && isRecoverable(e) + && (requestBody == null || requestBody instanceof RetryableOutputStream)) { + httpEngine.release(false); + httpEngine = newHttpEngine(method, rawRequestHeaders, null, + (RetryableOutputStream) requestBody); + httpEngine.routeSelector = routeSelector; // Keep the same routeSelector. + return false; + } + httpEngineFailure = e; + throw e; + } + } + + private boolean isRecoverable(IOException e) { + // If the problem was a CertificateException from the X509TrustManager, + // do not retry, we didn't have an abrupt server initiated exception. + boolean sslFailure = e instanceof SSLHandshakeException + && e.getCause() instanceof CertificateException; + boolean protocolFailure = e instanceof ProtocolException; + return !sslFailure && !protocolFailure; + } + HttpEngine getHttpEngine() { return httpEngine; } @@ -358,7 +398,7 @@ public class HttpURLConnectionImpl extends OkHttpConnection { switch (getResponseCode()) { case HTTP_PROXY_AUTH: if (!usingProxy()) { - throw new IOException( + throw new ProtocolException( "Received HTTP_PROXY_AUTH (407) code while not using proxy"); } // fall-through @@ -387,7 +427,7 @@ public class HttpURLConnectionImpl extends OkHttpConnection { return Retry.NONE; // the scheme changed; don't retry. } if (previousUrl.getHost().equals(url.getHost()) - && Libcore.getEffectivePort(previousUrl) == Libcore.getEffectivePort(url)) { + && getEffectivePort(previousUrl) == getEffectivePort(url)) { return Retry.SAME_CONNECTION; } else { return Retry.DIFFERENT_CONNECTION; diff --git a/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java b/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java index 2aaeaf4..b0fc73b 100644 --- a/src/main/java/libcore/net/http/HttpsURLConnectionImpl.java +++ b/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java @@ -14,16 +14,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; -import com.squareup.okhttp.OkHttpConnection; -import com.squareup.okhttp.OkHttpsConnection; +import com.squareup.okhttp.Connection; +import com.squareup.okhttp.ConnectionPool; +import com.squareup.okhttp.TunnelRequest; +import static com.squareup.okhttp.internal.Util.getEffectivePort; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.CacheResponse; +import java.net.CookieHandler; +import java.net.HttpURLConnection; import java.net.ProtocolException; import java.net.Proxy; +import java.net.ProxySelector; +import java.net.ResponseCache; import java.net.SecureCacheResponse; import java.net.URL; import java.security.Permission; @@ -32,23 +38,22 @@ import java.security.cert.Certificate; import java.util.List; import java.util.Map; import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; -public final class HttpsURLConnectionImpl extends OkHttpsConnection { +public final class HttpsURLConnectionImpl extends HttpsURLConnection { /** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl. */ private final HttpUrlConnectionDelegate delegate; - public HttpsURLConnectionImpl(URL url, int port) { + public HttpsURLConnectionImpl(URL url, int defaultPort, Proxy proxy, + ProxySelector proxySelector, CookieHandler cookieHandler, ResponseCache responseCache, + ConnectionPool connectionPool) { super(url); - delegate = new HttpUrlConnectionDelegate(url, port); - } - - public HttpsURLConnectionImpl(URL url, int port, Proxy proxy) { - super(url); - delegate = new HttpUrlConnectionDelegate(url, port, proxy); + delegate = new HttpUrlConnectionDelegate(url, defaultPort, proxy, proxySelector, + cookieHandler, responseCache, connectionPool); } private void checkConnected() { @@ -256,8 +261,8 @@ public final class HttpsURLConnectionImpl extends OkHttpsConnection { } @Override - public String getHeaderFieldKey(int posn) { - return delegate.getHeaderFieldKey(posn); + public String getHeaderFieldKey(int position) { + return delegate.getHeaderFieldKey(position); } @Override @@ -371,16 +376,15 @@ public final class HttpsURLConnectionImpl extends OkHttpsConnection { } private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl { - private HttpUrlConnectionDelegate(URL url, int port) { - super(url, port); - } - - private HttpUrlConnectionDelegate(URL url, int port, Proxy proxy) { - super(url, port, proxy); + private HttpUrlConnectionDelegate(URL url, int defaultPort, Proxy proxy, + ProxySelector proxySelector, CookieHandler cookieHandler, + ResponseCache responseCache, ConnectionPool connectionPool) { + super(url, defaultPort, proxy, proxySelector, cookieHandler, responseCache, + connectionPool); } @Override protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders, - HttpConnection connection, RetryableOutputStream requestBody) throws IOException { + Connection connection, RetryableOutputStream requestBody) throws IOException { return new HttpsEngine(this, method, requestHeaders, connection, requestBody, HttpsURLConnectionImpl.this); } @@ -397,14 +401,9 @@ public final class HttpsURLConnectionImpl extends OkHttpsConnection { } private static final class HttpsEngine extends HttpEngine { - /** - * Local stash of HttpsEngine.connection.sslSocket for answering - * queries such as getCipherSuite even after - * httpsEngine.Connection has been recycled. It's presence is also - * used to tell if the HttpsURLConnection is considered connected, - * as opposed to the connected field of URLConnection or the a - * non-null connect in HttpURLConnectionImpl + * Stash of HttpsEngine.connection.socket to implement requests like + * {@link #getCipherSuite} even after the connection has been recycled. */ private SSLSocket sslSocket; @@ -415,13 +414,17 @@ public final class HttpsURLConnectionImpl extends OkHttpsConnection { * @param enclosing the HttpsURLConnection with HTTPS features */ private HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders, - HttpConnection connection, RetryableOutputStream requestBody, + Connection connection, RetryableOutputStream requestBody, HttpsURLConnectionImpl enclosing) throws IOException { super(policy, method, requestHeaders, connection, requestBody); this.sslSocket = connection != null ? (SSLSocket) connection.getSocket() : null; this.enclosing = enclosing; } + @Override protected void connected(Connection connection) { + this.sslSocket = (SSLSocket) connection.getSocket(); + } + @Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) { return cacheResponse instanceof SecureCacheResponse; } @@ -439,24 +442,19 @@ public final class HttpsURLConnectionImpl extends OkHttpsConnection { return enclosing.getHostnameVerifier(); } - @Override protected OkHttpConnection getHttpConnectionToCache() { + @Override protected HttpURLConnection getHttpConnectionToCache() { return enclosing; } - @Override protected HttpConnection.TunnelConfig getTunnelConfig() { - String host = requestHeaders.getHost(); - if (host == null) { - host = HttpEngine.getOriginAddress(policy.getURL()); - } - + @Override protected TunnelRequest getTunnelConfig() { String userAgent = requestHeaders.getUserAgent(); if (userAgent == null) { - userAgent = HttpEngine.getDefaultUserAgent(); + userAgent = getDefaultUserAgent(); } - String proxyAuthorization = requestHeaders.getProxyAuthorization(); - return new HttpConnection.TunnelConfig( - policy.getURL(), host, userAgent, proxyAuthorization); + URL url = policy.getURL(); + return new TunnelRequest(url.getHost(), getEffectivePort(url), userAgent, + requestHeaders.getProxyAuthorization()); } } } diff --git a/src/main/java/libcore/net/http/RawHeaders.java b/src/main/java/com/squareup/okhttp/internal/http/RawHeaders.java index ba315d6..edb1436 100644 --- a/src/main/java/libcore/net/http/RawHeaders.java +++ b/src/main/java/com/squareup/okhttp/internal/http/RawHeaders.java @@ -15,11 +15,14 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; +import com.squareup.okhttp.internal.Platform; +import com.squareup.okhttp.internal.Util; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; +import java.net.ProtocolException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -30,8 +33,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; -import libcore.io.Streams; -import libcore.util.Libcore; /** * The HTTP status and unparsed header fields of a single HTTP message. Values @@ -101,17 +102,17 @@ public final class RawHeaders { if (!statusLine.startsWith("HTTP/1.") || statusLine.charAt(8) != ' ' || statusLine.charAt(12) != ' ') { - throw new IOException("Unexpected status line: " + statusLine); + throw new ProtocolException("Unexpected status line: " + statusLine); } int httpMinorVersion = statusLine.charAt(7) - '0'; if (httpMinorVersion < 0 || httpMinorVersion > 9) { - throw new IOException("Unexpected status line: " + statusLine); + throw new ProtocolException("Unexpected status line: " + statusLine); } int responseCode; try { responseCode = Integer.parseInt(statusLine.substring(9, 12)); } catch (NumberFormatException e) { - throw new IOException("Unexpected status line: " + statusLine); + throw new ProtocolException("Unexpected status line: " + statusLine); } this.responseMessage = statusLine.substring(13); this.responseCode = responseCode; @@ -131,7 +132,7 @@ public final class RawHeaders { } } if (status == null || version == null) { - throw new IOException("Expected 'status' and 'version' headers not present"); + throw new ProtocolException("Expected 'status' and 'version' headers not present"); } setStatusLine(version + " " + status); } @@ -202,7 +203,8 @@ public final class RawHeaders { * "Accept\r\n". For platform compatibility and HTTP compliance, we * print a warning and ignore null values. */ - Libcore.logW("Ignoring HTTP header field '" + fieldName + "' because its value is null"); + Platform.get().logW("Ignoring HTTP header field '" + + fieldName + "' because its value is null"); return; } namesAndValues.add(fieldName); @@ -309,7 +311,7 @@ public final class RawHeaders { RawHeaders headers; do { headers = new RawHeaders(); - headers.setStatusLine(Streams.readAsciiLine(in)); + headers.setStatusLine(Util.readAsciiLine(in)); readHeaders(in, headers); } while (headers.getResponseCode() == HttpEngine.HTTP_CONTINUE); return headers; @@ -321,7 +323,7 @@ public final class RawHeaders { public static void readHeaders(InputStream in, RawHeaders out) throws IOException { // parse the result headers until the first blank line String line; - while ((line = Streams.readAsciiLine(in)).length() != 0) { + while ((line = Util.readAsciiLine(in)).length() != 0) { out.addLine(line); } } @@ -391,6 +393,11 @@ public final class RawHeaders { throw new IllegalArgumentException("Unexpected header: " + name + ": " + value); } + // Drop headers that are ignored when layering HTTP over SPDY. + if (name.equals("connection") || name.equals("accept-encoding")) { + continue; + } + // If we haven't seen this name before, add the pair to the end of the list... if (names.add(name)) { result.add(name); diff --git a/src/main/java/libcore/net/http/RequestHeaders.java b/src/main/java/com/squareup/okhttp/internal/http/RequestHeaders.java index a84437e..2c12b95 100644 --- a/src/main/java/libcore/net/http/RequestHeaders.java +++ b/src/main/java/com/squareup/okhttp/internal/http/RequestHeaders.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; import java.net.URI; import java.util.Date; @@ -24,7 +24,7 @@ import java.util.Map; /** * Parsed HTTP request headers. */ -public final class RequestHeaders { +final class RequestHeaders { private final URI uri; private final RawHeaders headers; diff --git a/src/main/java/libcore/net/http/ResponseHeaders.java b/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java index 0d89fe5..d333d8b 100644 --- a/src/main/java/libcore/net/http/ResponseHeaders.java +++ b/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java @@ -14,8 +14,10 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; +import com.squareup.okhttp.ResponseSource; +import static com.squareup.okhttp.internal.Util.equal; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; @@ -26,13 +28,11 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.TimeUnit; -import libcore.util.Objects; -import libcore.util.ResponseSource; /** * Parsed HTTP response headers. */ -public final class ResponseHeaders { +final class ResponseHeaders { /** HTTP header name for the local time when the request was sent. */ private static final String SENT_MILLIS = "X-Android-Sent-Millis"; @@ -368,7 +368,7 @@ public final class ResponseHeaders { public boolean varyMatches(Map<String, List<String>> cachedRequest, Map<String, List<String>> newRequest) { for (String field : varyFields) { - if (!Objects.equal(cachedRequest.get(field), newRequest.get(field))) { + if (!equal(cachedRequest.get(field), newRequest.get(field))) { return false; } } diff --git a/src/main/java/libcore/net/http/RetryableOutputStream.java b/src/main/java/com/squareup/okhttp/internal/http/RetryableOutputStream.java index c8110be..a5c5842 100644 --- a/src/main/java/libcore/net/http/RetryableOutputStream.java +++ b/src/main/java/com/squareup/okhttp/internal/http/RetryableOutputStream.java @@ -14,12 +14,13 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; +import static com.squareup.okhttp.internal.Util.checkOffsetAndCount; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; -import libcore.util.Libcore; +import java.net.ProtocolException; /** * An HTTP request body that's completely buffered in memory. This allows @@ -40,13 +41,13 @@ final class RetryableOutputStream extends AbstractHttpOutputStream { this.content = new ByteArrayOutputStream(); } - @Override public synchronized void close() { + @Override public synchronized void close() throws IOException { if (closed) { return; } closed = true; if (content.size() < limit) { - throw new IllegalStateException("content-length promised " + throw new ProtocolException("content-length promised " + limit + " bytes, but received " + content.size()); } } @@ -54,14 +55,14 @@ final class RetryableOutputStream extends AbstractHttpOutputStream { @Override public synchronized void write(byte[] buffer, int offset, int count) throws IOException { checkNotClosed(); - Libcore.checkOffsetAndCount(buffer.length, offset, count); + checkOffsetAndCount(buffer.length, offset, count); if (limit != -1 && content.size() > limit - count) { - throw new IOException("exceeded content-length limit of " + limit + " bytes"); + throw new ProtocolException("exceeded content-length limit of " + limit + " bytes"); } content.write(buffer, offset, count); } - public synchronized int contentLength() { + public synchronized int contentLength() throws IOException { close(); return content.size(); } diff --git a/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java b/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java new file mode 100644 index 0000000..ac4bb6c --- /dev/null +++ b/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java @@ -0,0 +1,237 @@ +/* + * 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.internal.http; + +import com.squareup.okhttp.Address; +import com.squareup.okhttp.Connection; +import com.squareup.okhttp.ConnectionPool; +import com.squareup.okhttp.internal.Dns; +import static com.squareup.okhttp.internal.Util.getEffectivePort; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.net.UnknownHostException; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Selects routes to connect to an origin server. Each connection requires a + * choice of proxy server, IP address, and TLS mode. Connections may also be + * recycled. + */ +final class RouteSelector { + /** Uses {@link com.squareup.okhttp.internal.Platform#enableTlsExtensions}. */ + private static final int TLS_MODE_MODERN = 1; + /** Uses {@link com.squareup.okhttp.internal.Platform#supportTlsIntolerantServer}. */ + private static final int TLS_MODE_COMPATIBLE = 0; + /** No TLS mode. */ + private static final int TLS_MODE_NULL = -1; + + private final Address address; + private final URI uri; + private final ProxySelector proxySelector; + private final ConnectionPool pool; + private final Dns dns; + + /* The most recently attempted route. */ + private Proxy lastProxy; + private InetSocketAddress lastInetSocketAddress; + + /* State for negotiating the next proxy to use. */ + private boolean hasNextProxy; + private Proxy userSpecifiedProxy; + private Iterator<Proxy> proxySelectorProxies; + + /* State for negotiating the next InetSocketAddress to use. */ + private InetAddress[] socketAddresses; + private int nextSocketAddressIndex; + private String socketHost; + private int socketPort; + + /* State for negotiating the next TLS configuration */ + private int nextTlsMode = TLS_MODE_NULL; + + public RouteSelector(Address address, URI uri, ProxySelector proxySelector, + ConnectionPool pool, Dns dns) { + this.address = address; + this.uri = uri; + this.proxySelector = proxySelector; + this.pool = pool; + this.dns = dns; + + resetNextProxy(uri, address.getProxy()); + } + + /** + * Returns true if there's another route to attempt. Every address has at + * least one route. + */ + public boolean hasNext() { + return hasNextTlsMode() || hasNextInetSocketAddress() || hasNextProxy(); + } + + /** + * Returns the next route address to attempt. + * + * @throws NoSuchElementException if there are no more routes to attempt. + */ + public Connection next() throws IOException { + // Always prefer pooled connections over new connections. + Connection pooled = pool.get(address); + if (pooled != null) { + return pooled; + } + + // Compute the next route to attempt. + if (!hasNextTlsMode()) { + if (!hasNextInetSocketAddress()) { + if (!hasNextProxy()) { + throw new NoSuchElementException(); + } + lastProxy = nextProxy(); + resetNextInetSocketAddress(lastProxy); + } + lastInetSocketAddress = nextInetSocketAddress(); + resetNextTlsMode(); + } + boolean modernTls = nextTlsMode() == TLS_MODE_MODERN; + + return new Connection(address, lastProxy, lastInetSocketAddress, modernTls); + } + + /** + * Clients should invoke this method when they encounter a connectivity + * failure on a connection returned by this route selector. + */ + public void connectFailed(Connection connection, IOException failure) { + if (connection.getProxy().type() != Proxy.Type.DIRECT && proxySelector != null) { + // Tell the proxy selector when we fail to connect on a fresh connection. + proxySelector.connectFailed(uri, connection.getProxy().address(), failure); + } + } + + /** Resets {@link #nextProxy} to the first option. */ + private void resetNextProxy(URI uri, Proxy proxy) { + this.hasNextProxy = true; // This includes NO_PROXY! + if (proxy != null) { + this.userSpecifiedProxy = proxy; + } else { + List<Proxy> proxyList = proxySelector.select(uri); + if (proxyList != null) { + this.proxySelectorProxies = proxyList.iterator(); + } + } + } + + /** Returns true if there's another proxy to try. */ + private boolean hasNextProxy() { + return hasNextProxy; + } + + /** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */ + private Proxy nextProxy() { + // If the user specifies a proxy, try that and only that. + if (userSpecifiedProxy != null) { + hasNextProxy = false; + return userSpecifiedProxy; + } + + // Try each of the ProxySelector choices until one connection succeeds. If none succeed + // then we'll try a direct connection below. + if (proxySelectorProxies != null) { + while (proxySelectorProxies.hasNext()) { + Proxy candidate = proxySelectorProxies.next(); + if (candidate.type() != Proxy.Type.DIRECT) { + return candidate; + } + } + } + + // Finally try a direct connection. + hasNextProxy = false; + return Proxy.NO_PROXY; + } + + /** Resets {@link #nextInetSocketAddress} to the first option. */ + private void resetNextInetSocketAddress(Proxy proxy) throws UnknownHostException { + socketAddresses = null; // Clear the addresses. Necessary if getAllByName() below throws! + + if (proxy.type() == Proxy.Type.DIRECT) { + socketHost = uri.getHost(); + socketPort = getEffectivePort(uri); + } else { + SocketAddress proxyAddress = proxy.address(); + if (!(proxyAddress instanceof InetSocketAddress)) { + throw new IllegalArgumentException("Proxy.address() is not an " + + "InetSocketAddress: " + proxyAddress.getClass()); + } + InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress; + socketHost = proxySocketAddress.getHostName(); + socketPort = proxySocketAddress.getPort(); + } + + // Try each address for best behavior in mixed IPv4/IPv6 environments. + socketAddresses = dns.getAllByName(socketHost); + nextSocketAddressIndex = 0; + } + + /** Returns true if there's another socket address to try. */ + private boolean hasNextInetSocketAddress() { + return socketAddresses != null; + } + + /** Returns the next socket address to try. */ + private InetSocketAddress nextInetSocketAddress() throws UnknownHostException { + InetSocketAddress result = new InetSocketAddress( + socketAddresses[nextSocketAddressIndex++], socketPort); + if (nextSocketAddressIndex == socketAddresses.length) { + socketAddresses = null; // So that hasNextInetSocketAddress() returns false. + nextSocketAddressIndex = 0; + } + + return result; + } + + /** Resets {@link #nextTlsMode} to the first option. */ + private void resetNextTlsMode() { + nextTlsMode = (address.getSslSocketFactory() != null) + ? TLS_MODE_MODERN + : TLS_MODE_COMPATIBLE; + } + + /** Returns true if there's another TLS mode to try. */ + private boolean hasNextTlsMode() { + return nextTlsMode != TLS_MODE_NULL; + } + + /** Returns the next TLS mode to try. */ + private int nextTlsMode() { + if (nextTlsMode == TLS_MODE_MODERN) { + nextTlsMode = TLS_MODE_COMPATIBLE; + return TLS_MODE_MODERN; + } else if (nextTlsMode == TLS_MODE_COMPATIBLE) { + nextTlsMode = TLS_MODE_NULL; // So that hasNextTlsMode() returns false. + return TLS_MODE_COMPATIBLE; + } else { + throw new AssertionError(); + } + } +} diff --git a/src/main/java/libcore/net/http/SpdyTransport.java b/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java index 16ad452..28a47ab 100644 --- a/src/main/java/libcore/net/http/SpdyTransport.java +++ b/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java @@ -14,18 +14,17 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; +import com.squareup.okhttp.internal.spdy.SpdyConnection; +import com.squareup.okhttp.internal.spdy.SpdyStream; import java.io.IOException; import java.io.InputStream; -import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.CacheRequest; import java.util.List; -import libcore.net.spdy.SpdyConnection; -import libcore.net.spdy.SpdyStream; -final class SpdyTransport implements Transport { +public final class SpdyTransport implements Transport { private final HttpEngine httpEngine; private final SpdyConnection spdyConnection; private SpdyStream stream; @@ -33,7 +32,7 @@ final class SpdyTransport implements Transport { // TODO: set sentMillis // TODO: set cookie stuff - SpdyTransport(HttpEngine httpEngine, SpdyConnection spdyConnection) { + public SpdyTransport(HttpEngine httpEngine, SpdyConnection spdyConnection) { this.httpEngine = httpEngine; this.spdyConnection = spdyConnection; } @@ -49,13 +48,14 @@ final class SpdyTransport implements Transport { return; } RawHeaders requestHeaders = httpEngine.requestHeaders.getHeaders(); - String version = httpEngine.connection.httpMinorVersion == 1 ? "HTTP/1.1" : "HTTP/1.0"; + String version = httpEngine.connection.getHttpMinorVersion() == 1 ? "HTTP/1.1" : "HTTP/1.0"; requestHeaders.addSpdyRequestHeaders(httpEngine.method, httpEngine.uri.getScheme(), HttpEngine.requestPath(httpEngine.policy.getURL()), version); boolean hasRequestBody = httpEngine.hasRequestBody(); boolean hasResponseBody = true; stream = spdyConnection.newStream(requestHeaders.toNameValueBlock(), hasRequestBody, hasResponseBody); + stream.setReadTimeout(httpEngine.policy.getReadTimeout()); } @Override public void writeRequestBody(RetryableOutputStream requestBody) throws IOException { @@ -68,16 +68,10 @@ final class SpdyTransport implements Transport { @Override public ResponseHeaders readResponseHeaders() throws IOException { // TODO: fix the SPDY implementation so this throws a (buffered) IOException - try { - List<String> nameValueBlock = stream.getResponseHeaders(); - RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock); - rawHeaders.computeResponseStatusLineFromSpdyHeaders(); - return new ResponseHeaders(httpEngine.uri, rawHeaders); - } catch (InterruptedException e) { - InterruptedIOException rethrow = new InterruptedIOException(); - rethrow.initCause(e); - throw rethrow; - } + List<String> nameValueBlock = stream.getResponseHeaders(); + RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock); + rawHeaders.computeResponseStatusLineFromSpdyHeaders(); + return new ResponseHeaders(httpEngine.uri, rawHeaders); } @Override public InputStream getTransferStream(CacheRequest cacheRequest) throws IOException { diff --git a/src/main/java/libcore/net/http/Transport.java b/src/main/java/com/squareup/okhttp/internal/http/Transport.java index 3d4c8dd..f1212d5 100644 --- a/src/main/java/libcore/net/http/Transport.java +++ b/src/main/java/com/squareup/okhttp/internal/http/Transport.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; import java.io.IOException; import java.io.InputStream; diff --git a/src/main/java/libcore/net/spdy/IncomingStreamHandler.java b/src/main/java/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java index 69cc8e1..fc554f4 100644 --- a/src/main/java/libcore/net/spdy/IncomingStreamHandler.java +++ b/src/main/java/com/squareup/okhttp/internal/spdy/IncomingStreamHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package libcore.net.spdy; +package com.squareup.okhttp.internal.spdy; import java.io.IOException; @@ -30,9 +30,9 @@ public interface IncomingStreamHandler { /** * Handle a new stream from this connection's peer. Implementations should - * respond by either {@link SpdyStream#reply(java.util.List) replying to the - * stream} or {@link SpdyStream#close(int) closing it}. This response does - * not need to be synchronous. + * respond by either {@link SpdyStream#reply replying to the stream} or + * {@link SpdyStream#close closing it}. This response does not need to be + * synchronous. */ void receive(SpdyStream stream) throws IOException; } diff --git a/src/main/java/libcore/net/spdy/Ping.java b/src/main/java/com/squareup/okhttp/internal/spdy/Ping.java index 8eb5ebe..1fc3979 100644 --- a/src/main/java/libcore/net/spdy/Ping.java +++ b/src/main/java/com/squareup/okhttp/internal/spdy/Ping.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package libcore.net.spdy; +package com.squareup.okhttp.internal.spdy; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -40,9 +40,16 @@ public final class Ping { latch.countDown(); } + void cancel() { + if (received != -1 || sent == -1) throw new IllegalStateException(); + received = sent - 1; + latch.countDown(); + } + /** * Returns the round trip time for this ping in nanoseconds, waiting for the - * response to arrive if necessary. + * response to arrive if necessary. Returns -1 if the response was + * cancelled. */ public long roundTripTime() throws InterruptedException { latch.await(); @@ -51,13 +58,14 @@ public final class Ping { /** * Returns the round trip time for this ping in nanoseconds, or -1 if the - * timeout elapsed before the round trip completed. + * response was cancelled, or -2 if the timeout elapsed before the round + * trip completed. */ public long roundTripTime(long timeout, TimeUnit unit) throws InterruptedException { if (latch.await(timeout, unit)) { return received - sent; } else { - return -1; + return -2; } } } diff --git a/src/main/java/libcore/net/spdy/Settings.java b/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java index 0e3e40c..f4136e6 100644 --- a/src/main/java/libcore/net/spdy/Settings.java +++ b/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package libcore.net.spdy; +package com.squareup.okhttp.internal.spdy; final class Settings { /** Peer request to clear durable settings. */ diff --git a/src/main/java/libcore/net/spdy/SpdyConnection.java b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java index 0aa81fe..a67e3e8 100644 --- a/src/main/java/libcore/net/spdy/SpdyConnection.java +++ b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java @@ -14,14 +14,17 @@ * limitations under the License. */ -package libcore.net.spdy; +package com.squareup.okhttp.internal.spdy; +import com.squareup.okhttp.internal.Util; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.ProtocolException; import java.net.Socket; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; @@ -29,9 +32,6 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import libcore.io.Streams; - -import static libcore.net.spdy.Threads.newThreadFactory; /** * A socket connection to a remote peer. A connection hosts streams which can @@ -90,7 +90,9 @@ public final class SpdyConnection implements Closeable { private final ExecutorService callbackExecutor; private final Map<Integer, SpdyStream> streams = new HashMap<Integer, SpdyStream>(); + private int lastGoodStreamId; private int nextStreamId; + private boolean shutdown; /** Lazily-created map of in-flight pings awaiting a response. Guarded by this. */ private Map<Integer, Ping> pings; @@ -109,15 +111,23 @@ public final class SpdyConnection implements Closeable { String prefix = builder.client ? "Spdy Client " : "Spdy Server "; readExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, - new SynchronousQueue<Runnable>(), newThreadFactory(prefix + "Reader", false)); + new SynchronousQueue<Runnable>(), Threads.newThreadFactory(prefix + "Reader", false)); writeExecutor = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS, - new LinkedBlockingQueue<Runnable>(), newThreadFactory(prefix + "Writer", false)); + new LinkedBlockingQueue<Runnable>(), Threads.newThreadFactory(prefix + "Writer", false)); callbackExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, - new SynchronousQueue<Runnable>(), newThreadFactory(prefix + "Callbacks", false)); + new SynchronousQueue<Runnable>(), Threads.newThreadFactory(prefix + "Callbacks", false)); readExecutor.execute(new Reader()); } + /** + * Returns the number of {@link SpdyStream#isOpen() open streams} on this + * connection. + */ + public synchronized int openStreamCount() { + return streams.size(); + } + private synchronized SpdyStream getStream(int id) { return streams.get(id); } @@ -144,10 +154,15 @@ public final class SpdyConnection implements Closeable { synchronized (spdyWriter) { synchronized (this) { + if (shutdown) { + throw new IOException("shutdown"); + } streamId = nextStreamId; nextStreamId += 2; stream = new SpdyStream(streamId, this, requestHeaders, flags); - streams.put(streamId, stream); + if (stream.isOpen()) { + streams.put(streamId, stream); + } } spdyWriter.synStream(flags, streamId, associatedStreamId, priority, requestHeaders); @@ -157,9 +172,7 @@ public final class SpdyConnection implements Closeable { } void writeSynReply(int streamId, int flags, List<String> alternating) throws IOException { - synchronized (spdyWriter) { - spdyWriter.synReply(flags, streamId, alternating); - } + spdyWriter.synReply(flags, streamId, alternating); } /** Writes a complete data frame. */ @@ -181,9 +194,7 @@ public final class SpdyConnection implements Closeable { } void writeSynReset(int streamId, int statusCode) throws IOException { - synchronized (spdyWriter) { - spdyWriter.synReset(streamId, statusCode); - } + spdyWriter.synReset(streamId, statusCode); } /** @@ -194,6 +205,9 @@ public final class SpdyConnection implements Closeable { Ping ping = new Ping(); int pingId; synchronized (this) { + if (shutdown) { + throw new IOException("shutdown"); + } pingId = nextPingId; nextPingId += 2; if (pings == null) pings = new HashMap<Integer, Ping>(); @@ -230,9 +244,7 @@ public final class SpdyConnection implements Closeable { * Sends a noop frame to the peer. */ public void noop() throws IOException { - synchronized (spdyWriter) { - spdyWriter.noop(); - } + spdyWriter.noop(); } public void flush() throws IOException { @@ -241,17 +253,66 @@ public final class SpdyConnection implements Closeable { } } - @Override public void close() throws IOException { - close(null); + /** + * Degrades this connection such that new streams can neither be created + * locally, nor accepted from the remote peer. Existing streams are not + * impacted. This is intended to permit an endpoint to gracefully stop + * accepting new requests without harming previously established streams. + */ + public void shutdown() throws IOException { + synchronized (spdyWriter) { + int lastGoodStreamId; + synchronized (this) { + if (shutdown) { + return; + } + shutdown = true; + lastGoodStreamId = this.lastGoodStreamId; + } + spdyWriter.goAway(0, lastGoodStreamId); + } } - private synchronized void close(Throwable reason) throws IOException { - // TODO: forward 'reason' to forced closed streams? - // TODO: graceful close; send RST frames - // TODO: close all streams to release waiting readers + /** + * Closes this connection. This cancels all open streams and unanswered + * pings. It closes the underlying input and output streams and shuts down + * internal executor services. + */ + @Override public void close() throws IOException { + shutdown(); + + SpdyStream[] streamsToClose = null; + Ping[] pingsToCancel = null; + synchronized (this) { + if (!streams.isEmpty()) { + streamsToClose = streams.values().toArray(new SpdyStream[streams.size()]); + streams.clear(); + } + if (pings != null) { + pingsToCancel = pings.values().toArray(new Ping[pings.size()]); + pings = null; + } + } + + if (streamsToClose != null) { + for (SpdyStream stream : streamsToClose) { + try { + stream.close(SpdyStream.RST_CANCEL); + } catch (Throwable ignored) { + } + } + } + + if (pingsToCancel != null) { + for (Ping ping : pingsToCancel) { + ping.cancel(); + } + } + writeExecutor.shutdown(); - readExecutor.shutdown(); callbackExecutor.shutdown(); + readExecutor.shutdown(); + Util.closeAll(spdyReader, spdyWriter); } public static class Builder { @@ -290,28 +351,32 @@ public final class SpdyConnection implements Closeable { private class Reader implements Runnable, SpdyReader.Handler { @Override public void run() { - Throwable failure = null; try { while (spdyReader.nextFrame(this)) { } - } catch (Throwable e) { - failure = e; - } - - try { - close(failure); - } catch (IOException ignored) { + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + Util.closeQuietly(SpdyConnection.this); } } @Override public void data(int flags, int streamId, InputStream in, int length) throws IOException { SpdyStream dataStream = getStream(streamId); - if (dataStream != null) { - dataStream.receiveData(in, flags, length); - } else { + if (dataStream == null) { writeSynResetLater(streamId, SpdyStream.RST_INVALID_STREAM); - Streams.skipByReading(in, length); + Util.skipByReading(in, length); + return; + } + try { + dataStream.receiveData(in, length); + if ((flags & SpdyConnection.FLAG_FIN) != 0) { + dataStream.receiveFin(); + } + } catch (ProtocolException e) { + Util.skipByReading(in, length); + dataStream.closeLater(SpdyStream.RST_FLOW_CONTROL_ERROR); } } @@ -321,17 +386,15 @@ public final class SpdyConnection implements Closeable { nameValueBlock, flags); final SpdyStream previous; synchronized (SpdyConnection.this) { + if (shutdown) { + return; + } + lastGoodStreamId = streamId; previous = streams.put(streamId, synStream); } if (previous != null) { - writeExecutor.execute(new Runnable() { - @Override public void run() { - try { - previous.close(SpdyStream.RST_PROTOCOL_ERROR); - } catch (IOException ignored) { - } - } - }); + previous.closeLater(SpdyStream.RST_PROTOCOL_ERROR); + removeStream(streamId); return; } callbackExecutor.execute(new Runnable() { @@ -348,11 +411,29 @@ public final class SpdyConnection implements Closeable { @Override public void synReply(int flags, int streamId, List<String> nameValueBlock) throws IOException { SpdyStream replyStream = getStream(streamId); - if (replyStream != null) { - // TODO: honor incoming FLAG_FIN. - replyStream.receiveReply(nameValueBlock); - } else { + if (replyStream == null) { writeSynResetLater(streamId, SpdyStream.RST_INVALID_STREAM); + return; + } + try { + replyStream.receiveReply(nameValueBlock); + if ((flags & SpdyConnection.FLAG_FIN) != 0) { + replyStream.receiveFin(); + } + } catch (ProtocolException e) { + replyStream.closeLater(SpdyStream.RST_PROTOCOL_ERROR); + } + } + + @Override public void headers(int flags, int streamId, List<String> nameValueBlock) + throws IOException { + SpdyStream replyStream = getStream(streamId); + if (replyStream != null) { + try { + replyStream.receiveHeaders(nameValueBlock); + } catch (ProtocolException e) { + replyStream.closeLater(SpdyStream.RST_PROTOCOL_ERROR); + } } } @@ -388,5 +469,22 @@ public final class SpdyConnection implements Closeable { } } } + + @Override public void goAway(int flags, int lastGoodStreamId) { + synchronized (SpdyConnection.this) { + shutdown = true; + + // Fail all streams created after the last good stream ID. + for (Iterator<Map.Entry<Integer, SpdyStream>> i = streams.entrySet().iterator(); + i.hasNext();) { + Map.Entry<Integer, SpdyStream> entry = i.next(); + int streamId = entry.getKey(); + if (streamId > lastGoodStreamId && entry.getValue().isLocallyInitiated()) { + entry.getValue().receiveRstStream(SpdyStream.RST_REFUSED_STREAM); + i.remove(); + } + } + } + } } } diff --git a/src/main/java/libcore/net/spdy/SpdyReader.java b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyReader.java index 5240cd0..98fc975 100644 --- a/src/main/java/libcore/net/spdy/SpdyReader.java +++ b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyReader.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package libcore.net.spdy; +package com.squareup.okhttp.internal.spdy; +import com.squareup.okhttp.internal.Util; +import java.io.Closeable; import java.io.DataInputStream; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -27,12 +28,11 @@ import java.util.logging.Logger; import java.util.zip.DataFormatException; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; -import libcore.io.Streams; /** * Read version 2 SPDY frames. */ -final class SpdyReader { +final class SpdyReader implements Closeable { private static final String DICTIONARY_STRING = "" + "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" + "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" @@ -73,8 +73,8 @@ final class SpdyReader { int w1; try { w1 = in.readInt(); - } catch (EOFException e) { - return false; + } catch (IOException e) { + return false; // This might be a normal socket close. } int w2 = in.readInt(); @@ -113,9 +113,12 @@ final class SpdyReader { return true; case SpdyConnection.TYPE_GOAWAY: + readGoAway(handler, flags, length); + return true; + case SpdyConnection.TYPE_HEADERS: - Streams.skipByReading(in, length); - throw new UnsupportedOperationException("TODO"); + readHeaders(handler, flags, length); + return true; default: throw new IOException("Unexpected frame"); @@ -154,11 +157,19 @@ final class SpdyReader { handler.rstStream(flags, streamId, statusCode); } + private void readHeaders(Handler handler, int flags, int length) throws IOException { + int w1 = in.readInt(); + in.readShort(); // unused + int streamId = w1 & 0x7fffffff; + List<String> nameValueBlock = readNameValueBlock(length - 6); + handler.headers(flags, streamId, nameValueBlock); + } + private DataInputStream newNameValueBlockStream() { // Limit the inflater input stream to only those bytes in the Name/Value block. final InputStream throttleStream = new InputStream() { @Override public int read() throws IOException { - return Streams.readSingleByte(this); + return Util.readSingleByte(this); } @Override public int read(byte[] buffer, int offset, int byteCount) throws IOException { @@ -217,7 +228,7 @@ final class SpdyReader { private String readString() throws DataFormatException, IOException { int length = nameValueBlockIn.readShort(); byte[] bytes = new byte[length]; - Streams.readFully(nameValueBlockIn, bytes); + Util.readFully(nameValueBlockIn, bytes); return new String(bytes, 0, length, "UTF-8"); } @@ -227,6 +238,12 @@ final class SpdyReader { handler.ping(flags, id); } + private void readGoAway(Handler handler, int flags, int length) throws IOException { + if (length != 4) throw ioException("TYPE_GOAWAY length: %d != 4", length); + int lastGoodStreamId = in.readInt() & 0x7fffffff; + handler.goAway(flags, lastGoodStreamId); + } + private void readSettings(Handler handler, int flags, int length) throws IOException { int numberOfEntries = in.readInt(); if (length != 4 + 8 * numberOfEntries) { @@ -250,16 +267,20 @@ final class SpdyReader { throw new IOException(String.format(message, args)); } + @Override public void close() throws IOException { + Util.closeAll(in, nameValueBlockIn); + } + public interface Handler { void data(int flags, int streamId, InputStream in, int length) throws IOException; void synStream(int flags, int streamId, int associatedStreamId, int priority, List<String> nameValueBlock); void synReply(int flags, int streamId, List<String> nameValueBlock) throws IOException; + void headers(int flags, int streamId, List<String> nameValueBlock) throws IOException; void rstStream(int flags, int streamId, int statusCode); void settings(int flags, Settings settings); void noop(); void ping(int flags, int streamId); - // TODO: goaway - // TODO: headers + void goAway(int flags, int lastGoodStreamId); } } diff --git a/src/main/java/libcore/net/spdy/SpdyStream.java b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java index 2e336de..5c3d971 100644 --- a/src/main/java/libcore/net/spdy/SpdyStream.java +++ b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java @@ -14,17 +14,20 @@ * limitations under the License. */ -package libcore.net.spdy; +package com.squareup.okhttp.internal.spdy; +import com.squareup.okhttp.internal.Util; +import static com.squareup.okhttp.internal.Util.checkOffsetAndCount; +import static com.squareup.okhttp.internal.Util.pokeInt; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; -import java.util.List; -import libcore.io.Streams; -import libcore.util.Libcore; - +import java.net.ProtocolException; +import java.net.SocketTimeoutException; import static java.nio.ByteOrder.BIG_ENDIAN; +import java.util.ArrayList; +import java.util.List; /** * A logical bidirectional stream. @@ -38,6 +41,17 @@ public final class SpdyStream { private static final int DATA_FRAME_HEADER_LENGTH = 8; + private static final String[] STATUS_CODE_NAMES = { + null, + "PROTOCOL_ERROR", + "INVALID_STREAM", + "REFUSED_STREAM", + "UNSUPPORTED_VERSION", + "CANCEL", + "INTERNAL_ERROR", + "FLOW_CONTROL_ERROR", + }; + public static final int RST_PROTOCOL_ERROR = 1; public static final int RST_INVALID_STREAM = 2; public static final int RST_REFUSED_STREAM = 3; @@ -48,6 +62,7 @@ public final class SpdyStream { private final int id; private final SpdyConnection connection; + private long readTimeoutMillis = 0; /** Headers sent by the stream initiator. Immutable and non null. */ private final List<String> requestHeaders; @@ -82,6 +97,25 @@ public final class SpdyStream { } /** + * Returns true if this stream is open. A stream is open until either: + * <ul> + * <li>A {@code SYN_RESET} frame abnormally terminates the stream. + * <li>Both input and output streams have transmitted all data. + * </ul> + * Note that the input stream may continue to yield data even after a stream + * reports itself as not open. This is because input data is buffered. + */ + public synchronized boolean isOpen() { + if (rstStatusCode != -1) { + return false; + } + if ((in.finished || in.closed) && (out.finished || out.closed)) { + return false; + } + return true; + } + + /** * Returns true if this stream was created by this peer. */ public boolean isLocallyInitiated() { @@ -101,13 +135,20 @@ public final class SpdyStream { * Returns the stream's response headers, blocking if necessary if they * have not been received yet. */ - public synchronized List<String> getResponseHeaders() throws InterruptedException { - while (responseHeaders == null && rstStatusCode == -1) { - wait(); + public synchronized List<String> getResponseHeaders() throws IOException { + try { + while (responseHeaders == null && rstStatusCode == -1) { + wait(); + } + if (responseHeaders != null) { + return responseHeaders; + } + throw new IOException("stream was reset: " + rstStatusString()); + } catch (InterruptedException e) { + InterruptedIOException rethrow = new InterruptedIOException(); + rethrow.initCause(e); + throw rethrow; } - // TODO: throw InterruptedIOException? - // TODO: throw if responseHeaders == null - return responseHeaders; } /** @@ -129,6 +170,7 @@ public final class SpdyStream { * to the remote peer. Corresponds to {@code FLAG_FIN}. */ public void reply(List<String> responseHeaders, boolean out) throws IOException { + assert (!Thread.holdsLock(SpdyStream.this)); int flags = 0; synchronized (this) { if (responseHeaders == null) { @@ -150,6 +192,18 @@ public final class SpdyStream { } /** + * Sets the maximum time to wait on input stream reads before failing with a + * {@code SocketTimeoutException}, or {@code 0} to wait indefinitely. + */ + public void setReadTimeout(long readTimeoutMillis) { + this.readTimeoutMillis = readTimeoutMillis; + } + + public long getReadTimeoutMillis() { + return readTimeoutMillis; + } + + /** * Returns an input stream that can be used to read data from the peer. */ public InputStream getInputStream() { @@ -175,47 +229,93 @@ public final class SpdyStream { * Abnormally terminate this stream. */ public void close(int rstStatusCode) throws IOException { + if (!closeInternal(rstStatusCode)) { + return; // Already closed. + } + connection.writeSynReset(id, rstStatusCode); + } + + void closeLater(int rstStatusCode) { + if (!closeInternal(rstStatusCode)) { + return; // Already closed. + } + connection.writeSynResetLater(id, rstStatusCode); + } + + /** + * Returns true if this stream was closed. + */ + private boolean closeInternal(int rstStatusCode) { + assert (!Thread.holdsLock(this)); synchronized (this) { - // TODO: no-op if inFinished == true and outFinished == true ? if (this.rstStatusCode != -1) { - return; // Already closed. + return false; + } + if (in.finished && out.finished) { + return false; } this.rstStatusCode = rstStatusCode; - in.finished = true; - out.finished = true; notifyAll(); } - connection.writeSynReset(id, rstStatusCode); connection.removeStream(id); + return true; } - synchronized void receiveReply(List<String> strings) throws IOException { - if (!isLocallyInitiated() || responseHeaders != null) { - throw new IOException(); // TODO: send RST + void receiveReply(List<String> strings) throws IOException { + assert (!Thread.holdsLock(SpdyStream.this)); + synchronized (this) { + if (!isLocallyInitiated() || responseHeaders != null) { + throw new ProtocolException(); + } + responseHeaders = strings; + notifyAll(); + } + } + + void receiveHeaders(List<String> headers) throws IOException { + assert (!Thread.holdsLock(SpdyStream.this)); + synchronized (this) { + if (responseHeaders == null) { + throw new ProtocolException(); + } + List<String> newHeaders = new ArrayList<String>(); + newHeaders.addAll(responseHeaders); + newHeaders.addAll(headers); + this.responseHeaders = newHeaders; } - responseHeaders = strings; - notifyAll(); } - // TODO: locking here broken. Writing threads are blocked by potentially slow reads. - synchronized void receiveData(InputStream in, int flags, int length) throws IOException { + void receiveData(InputStream in, int length) throws IOException { + assert (!Thread.holdsLock(SpdyStream.this)); this.in.receive(in, length); - if ((flags & SpdyConnection.FLAG_FIN) != 0) { + } + + void receiveFin() { + assert (!Thread.holdsLock(SpdyStream.this)); + boolean open; + synchronized (this) { this.in.finished = true; - streamStateChanged(); + open = isOpen(); notifyAll(); } + if (!open) { + connection.removeStream(id); + } } synchronized void receiveRstStream(int statusCode) { if (rstStatusCode == -1) { rstStatusCode = statusCode; - in.finished = true; - out.finished = true; notifyAll(); } } + private String rstStatusString() { + return rstStatusCode > 0 && rstStatusCode < STATUS_CODE_NAMES.length + ? STATUS_CODE_NAMES[rstStatusCode] + : Integer.toString(rstStatusCode); + } + /** * An input stream that reads the incoming data frames of a stream. Although * this class uses synchronization to safely receive incoming data frames, @@ -248,8 +348,8 @@ public final class SpdyStream { private boolean closed; /** - * True if either side has shut down this stream. We will receive no - * more bytes beyond those already in the buffer. + * True if either side has cleanly shut down this stream. We will + * receive no more bytes beyond those already in the buffer. */ private boolean finished; @@ -267,21 +367,14 @@ public final class SpdyStream { } @Override public int read() throws IOException { - return Streams.readSingleByte(this); + return Util.readSingleByte(this); } @Override public int read(byte[] b, int offset, int count) throws IOException { synchronized (SpdyStream.this) { + checkOffsetAndCount(b.length, offset, count); + waitUntilReadable(); checkNotClosed(); - Libcore.checkOffsetAndCount(b.length, offset, count); - - while (pos == -1 && !finished) { - try { - SpdyStream.this.wait(); - } catch (InterruptedException e) { - throw new InterruptedIOException(); - } - } if (pos == -1) { return -1; @@ -319,62 +412,122 @@ public final class SpdyStream { } } - void receive(InputStream in, int byteCount) throws IOException { - if (finished) { - return; // ignore this; probably a benign race + /** + * Returns once the input stream is either readable or finished. Throws + * a {@link SocketTimeoutException} if the read timeout elapses before + * that happens. + */ + private void waitUntilReadable() throws IOException { + long start = 0; + long remaining = 0; + if (readTimeoutMillis != 0) { + start = (System.nanoTime() / 1000000); + remaining = readTimeoutMillis; } + try { + while (pos == -1 && !finished && !closed && rstStatusCode == -1) { + if (readTimeoutMillis == 0) { + SpdyStream.this.wait(); + } else if (remaining > 0) { + SpdyStream.this.wait(remaining); + remaining = start + readTimeoutMillis - (System.nanoTime() / 1000000); + } else { + throw new SocketTimeoutException(); + } + } + } catch (InterruptedException e) { + throw new InterruptedIOException(); + } + } + + void receive(InputStream in, int byteCount) throws IOException { + assert (!Thread.holdsLock(SpdyStream.this)); + if (byteCount == 0) { return; } - if (byteCount > buffer.length - available()) { - throw new IOException(); // TODO: RST the stream + int pos; + int limit; + int firstNewByte; + boolean finished; + synchronized (SpdyStream.this) { + finished = this.finished; + pos = this.pos; + firstNewByte = this.limit; + limit = this.limit; + if (byteCount > buffer.length - available()) { + throw new ProtocolException(); + } + } + + // Discard data received after the stream is finished. It's probably a benign race. + if (finished) { + Util.skipByReading(in, byteCount); + return; } - // fill [limit..buffer.length) + // Fill the buffer without holding any locks. First fill [limit..buffer.length) if that + // won't overwrite unread data. Then fill [limit..pos). We can't hold a lock, otherwise + // writes will be blocked until reads complete. if (pos < limit) { int firstCopyCount = Math.min(byteCount, buffer.length - limit); - Streams.readFully(in, buffer, limit, firstCopyCount); + Util.readFully(in, buffer, limit, firstCopyCount); limit += firstCopyCount; byteCount -= firstCopyCount; if (limit == buffer.length) { limit = 0; } } - - // fill [limit..pos) if (byteCount > 0) { - Streams.readFully(in, buffer, limit, byteCount); + Util.readFully(in, buffer, limit, byteCount); limit += byteCount; } - if (pos == -1) { - pos = 0; - SpdyStream.this.notifyAll(); + synchronized (SpdyStream.this) { + // Update the new limit, and mark the position as readable if necessary. + this.limit = limit; + if (this.pos == -1) { + this.pos = firstNewByte; + SpdyStream.this.notifyAll(); + } } } @Override public void close() throws IOException { synchronized (SpdyStream.this) { closed = true; - streamStateChanged(); + SpdyStream.this.notifyAll(); } + cancelStreamIfNecessary(); } private void checkNotClosed() throws IOException { if (closed) { throw new IOException("stream closed"); } + if (rstStatusCode != -1) { + throw new IOException("stream was reset: " + rstStatusString()); + } } } - private synchronized void streamStateChanged() throws IOException { - // If we closed the input stream before bytes ran out, we want to cancel - // it. But we can only cancel it once the output stream's bytes have all - // been sent, otherwise we'll terminate that innocent bystander. - if (in.closed && !in.finished && (out.finished || out.closed)) { - in.finished = true; + private void cancelStreamIfNecessary() throws IOException { + assert (!Thread.holdsLock(SpdyStream.this)); + boolean open; + boolean cancel; + synchronized (this) { + cancel = !in.finished && in.closed && (out.finished || out.closed); + open = isOpen(); + } + if (cancel) { + // RST this stream to prevent additional data from being sent. This + // is safe because the input stream is closed (we won't use any + // further bytes) and the output stream is either finished or closed + // (so RSTing both streams doesn't cause harm). SpdyStream.this.close(RST_CANCEL); + } else if (!open) { + connection.removeStream(id); } } @@ -390,17 +543,18 @@ public final class SpdyStream { private boolean closed; /** - * True if either side has shut down this stream. We shall send no more - * bytes. + * True if either side has cleanly shut down this stream. We shall send + * no more bytes. */ private boolean finished; @Override public void write(int b) throws IOException { - Streams.writeSingleByte(this, b); + Util.writeSingleByte(this, b); } @Override public void write(byte[] bytes, int offset, int count) throws IOException { - Libcore.checkOffsetAndCount(bytes.length, offset, count); + assert (!Thread.holdsLock(SpdyStream.this)); + checkOffsetAndCount(bytes.length, offset, count); checkNotClosed(); while (count > 0) { @@ -416,6 +570,7 @@ public final class SpdyStream { } @Override public void flush() throws IOException { + assert (!Thread.holdsLock(SpdyStream.this)); checkNotClosed(); if (pos > DATA_FRAME_HEADER_LENGTH) { writeFrame(false); @@ -424,6 +579,7 @@ public final class SpdyStream { } @Override public void close() throws IOException { + assert (!Thread.holdsLock(SpdyStream.this)); synchronized (SpdyStream.this) { if (closed) { return; @@ -432,17 +588,18 @@ public final class SpdyStream { } writeFrame(true); connection.flush(); - streamStateChanged(); + cancelStreamIfNecessary(); } private void writeFrame(boolean last) throws IOException { + assert (!Thread.holdsLock(SpdyStream.this)); int flags = 0; if (last) { flags |= SpdyConnection.FLAG_FIN; } int length = pos - DATA_FRAME_HEADER_LENGTH; - Libcore.pokeInt(buffer, 0, id & 0x7fffffff, BIG_ENDIAN); - Libcore.pokeInt(buffer, 4, (flags & 0xff) << 24 | length & 0xffffff, BIG_ENDIAN); + pokeInt(buffer, 0, id & 0x7fffffff, BIG_ENDIAN); + pokeInt(buffer, 4, (flags & 0xff) << 24 | length & 0xffffff, BIG_ENDIAN); connection.writeFrame(buffer, 0, pos); pos = DATA_FRAME_HEADER_LENGTH; } @@ -451,10 +608,10 @@ public final class SpdyStream { synchronized (SpdyStream.this) { if (closed) { throw new IOException("stream closed"); - } - if (finished) { - throw new IOException("output stream finished " - + "(RST status code=" + rstStatusCode + ")"); + } else if (finished) { + throw new IOException("stream finished"); + } else if (rstStatusCode != -1) { + throw new IOException("stream was reset: " + rstStatusString()); } } } diff --git a/src/main/java/libcore/net/spdy/SpdyWriter.java b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyWriter.java index f1fb620..8cd6ae0 100644 --- a/src/main/java/libcore/net/spdy/SpdyWriter.java +++ b/src/main/java/com/squareup/okhttp/internal/spdy/SpdyWriter.java @@ -14,22 +14,22 @@ * limitations under the License. */ -package libcore.net.spdy; +package com.squareup.okhttp.internal.spdy; +import com.squareup.okhttp.internal.Platform; +import com.squareup.okhttp.internal.Util; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.List; import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; - -import static libcore.util.Libcore.newDeflaterOutputStream; /** * Write version 2 SPDY frames. */ -final class SpdyWriter { +final class SpdyWriter implements Closeable { final DataOutputStream out; private final ByteArrayOutputStream nameValueBlockBuffer; private final DataOutputStream nameValueBlockOut; @@ -41,11 +41,11 @@ final class SpdyWriter { deflater.setDictionary(SpdyReader.DICTIONARY); nameValueBlockBuffer = new ByteArrayOutputStream(); nameValueBlockOut = new DataOutputStream( - newDeflaterOutputStream(nameValueBlockBuffer, deflater, true)); + Platform.get().newDeflaterOutputStream(nameValueBlockBuffer, deflater, true)); } - public void synStream(int flags, int streamId, int associatedStreamId, int priority, - List<String> nameValueBlock) throws IOException { + public synchronized void synStream(int flags, int streamId, int associatedStreamId, + int priority, List<String> nameValueBlock) throws IOException { writeNameValueBlockToBuffer(nameValueBlock); int length = 10 + nameValueBlockBuffer.size(); int type = SpdyConnection.TYPE_SYN_STREAM; @@ -60,7 +60,8 @@ final class SpdyWriter { out.flush(); } - public void synReply(int flags, int streamId, List<String> nameValueBlock) throws IOException { + public synchronized void synReply( + int flags, int streamId, List<String> nameValueBlock) throws IOException { writeNameValueBlockToBuffer(nameValueBlock); int type = SpdyConnection.TYPE_SYN_REPLY; int length = nameValueBlockBuffer.size() + 6; @@ -74,7 +75,22 @@ final class SpdyWriter { out.flush(); } - public void synReset(int streamId, int statusCode) throws IOException { + public synchronized void headers( + int flags, int streamId, List<String> nameValueBlock) throws IOException { + writeNameValueBlockToBuffer(nameValueBlock); + int type = SpdyConnection.TYPE_HEADERS; + int length = nameValueBlockBuffer.size() + 6; + int unused = 0; + + out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); + out.writeInt((flags & 0xff) << 24 | length & 0xffffff); + out.writeInt(streamId & 0x7fffffff); + out.writeShort(unused); + nameValueBlockBuffer.writeTo(out); + out.flush(); + } + + public synchronized void synReset(int streamId, int statusCode) throws IOException { int flags = 0; int type = SpdyConnection.TYPE_RST_STREAM; int length = 8; @@ -85,7 +101,7 @@ final class SpdyWriter { out.flush(); } - public void data(int flags, int streamId, byte[] data) throws IOException { + public synchronized void data(int flags, int streamId, byte[] data) throws IOException { int length = data.length; out.writeInt(streamId & 0x7fffffff); out.writeInt((flags & 0xff) << 24 | length & 0xffffff); @@ -104,7 +120,7 @@ final class SpdyWriter { nameValueBlockOut.flush(); } - public void settings(int flags, Settings settings) throws IOException { + public synchronized void settings(int flags, Settings settings) throws IOException { int type = SpdyConnection.TYPE_SETTINGS; int size = settings.size(); int length = 4 + size * 8; @@ -124,7 +140,7 @@ final class SpdyWriter { out.flush(); } - public void noop() throws IOException { + public synchronized void noop() throws IOException { int type = SpdyConnection.TYPE_NOOP; int length = 0; int flags = 0; @@ -133,7 +149,7 @@ final class SpdyWriter { out.flush(); } - public void ping(int flags, int id) throws IOException { + public synchronized void ping(int flags, int id) throws IOException { int type = SpdyConnection.TYPE_PING; int length = 4; out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); @@ -141,4 +157,17 @@ final class SpdyWriter { out.writeInt(id); out.flush(); } + + public synchronized void goAway(int flags, int lastGoodStreamId) throws IOException { + int type = SpdyConnection.TYPE_GOAWAY; + int length = 4; + out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); + out.writeInt((flags & 0xff) << 24 | length & 0xffffff); + out.writeInt(lastGoodStreamId); + out.flush(); + } + + @Override public void close() throws IOException { + Util.closeAll(out, nameValueBlockOut); + } } diff --git a/src/main/java/libcore/net/spdy/Threads.java b/src/main/java/com/squareup/okhttp/internal/spdy/Threads.java index 9e257f3..ef1d4f3 100644 --- a/src/main/java/libcore/net/spdy/Threads.java +++ b/src/main/java/com/squareup/okhttp/internal/spdy/Threads.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package libcore.net.spdy; +package com.squareup.okhttp.internal.spdy; import java.util.concurrent.ThreadFactory; diff --git a/src/main/java/libcore/io/AsynchronousCloseMonitor.java b/src/main/java/libcore/io/AsynchronousCloseMonitor.java deleted file mode 100644 index 62eec24..0000000 --- a/src/main/java/libcore/io/AsynchronousCloseMonitor.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 libcore.io; - -import java.io.FileDescriptor; - -public final class AsynchronousCloseMonitor { - private AsynchronousCloseMonitor() { - } - - public static native void signalBlockedThreads(FileDescriptor fd); -} diff --git a/src/main/java/libcore/io/BufferIterator.java b/src/main/java/libcore/io/BufferIterator.java deleted file mode 100644 index 7f3ad47..0000000 --- a/src/main/java/libcore/io/BufferIterator.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 libcore.io; - -/** - * Iterates over big- or little-endian bytes. See {@link MemoryMappedFile#bigEndianIterator} and - * {@link MemoryMappedFile#littleEndianIterator}. - * - * @hide don't make this public without adding bounds checking. - */ -public abstract class BufferIterator { - /** - * Seeks to the absolute position {@code offset}, measured in bytes from the start. - */ - public abstract void seek(int offset); - - /** - * Skips forwards or backwards {@code byteCount} bytes from the current position. - */ - public abstract void skip(int byteCount); - - /** - * Copies {@code byteCount} bytes from the current position into {@code dst}, starting at - * {@code dstOffset}, and advances the current position {@code byteCount} bytes. - */ - public abstract void readByteArray(byte[] dst, int dstOffset, int byteCount); - - /** - * Returns the byte at the current position, and advances the current position one byte. - */ - public abstract byte readByte(); - - /** - * Returns the 32-bit int at the current position, and advances the current position four bytes. - */ - public abstract int readInt(); - - /** - * Copies {@code intCount} 32-bit ints from the current position into {@code dst}, starting at - * {@code dstOffset}, and advances the current position {@code 4 * intCount} bytes. - */ - public abstract void readIntArray(int[] dst, int dstOffset, int intCount); - - /** - * Returns the 16-bit short at the current position, and advances the current position two bytes. - */ - public abstract short readShort(); -} diff --git a/src/main/java/libcore/io/IoUtils.java b/src/main/java/libcore/io/IoUtils.java deleted file mode 100644 index 307737d..0000000 --- a/src/main/java/libcore/io/IoUtils.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 libcore.io; - -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.net.Socket; - -public final class IoUtils { - private IoUtils() { - } - - /** - * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null. - */ - public static void closeQuietly(Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (RuntimeException rethrown) { - throw rethrown; - } catch (Exception ignored) { - } - } - } - - /** - * Closes 'socket', ignoring any exceptions. Does nothing if 'socket' is null. - */ - public static void closeQuietly(Socket socket) { - if (socket != null) { - try { - socket.close(); - } catch (Exception ignored) { - } - } - } - - /** - * Recursively delete everything in {@code dir}. - */ - // TODO: this should specify paths as Strings rather than as Files - public static void deleteContents(File dir) throws IOException { - File[] files = dir.listFiles(); - if (files == null) { - throw new IllegalArgumentException("not a directory: " + dir); - } - for (File file : files) { - if (file.isDirectory()) { - deleteContents(file); - } - if (!file.delete()) { - throw new IOException("failed to delete file: " + file); - } - } - } -} diff --git a/src/main/java/libcore/io/OsConstants.java b/src/main/java/libcore/io/OsConstants.java deleted file mode 100644 index 68a165c..0000000 --- a/src/main/java/libcore/io/OsConstants.java +++ /dev/null @@ -1,724 +0,0 @@ -/* - * 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 libcore.io; - -public final class OsConstants { - private OsConstants() { } - - public static boolean S_ISBLK(int mode) { return (mode & S_IFMT) == S_IFBLK; } - public static boolean S_ISCHR(int mode) { return (mode & S_IFMT) == S_IFCHR; } - public static boolean S_ISDIR(int mode) { return (mode & S_IFMT) == S_IFDIR; } - public static boolean S_ISFIFO(int mode) { return (mode & S_IFMT) == S_IFIFO; } - public static boolean S_ISREG(int mode) { return (mode & S_IFMT) == S_IFREG; } - public static boolean S_ISLNK(int mode) { return (mode & S_IFMT) == S_IFLNK; } - public static boolean S_ISSOCK(int mode) { return (mode & S_IFMT) == S_IFSOCK; } - - public static int WEXITSTATUS(int status) { return (status & 0xff00) >> 8; } - public static boolean WCOREDUMP(int status) { return (status & 0x80) != 0; } - public static int WTERMSIG(int status) { return status & 0x7f; } - public static int WSTOPSIG(int status) { return WEXITSTATUS(status); } - public static boolean WIFEXITED(int status) { return (WTERMSIG(status) == 0); } - public static boolean WIFSTOPPED(int status) { return (WTERMSIG(status) == 0x7f); } - public static boolean WIFSIGNALED(int status) { return (WTERMSIG(status + 1) >= 2); } - - public static final int AF_INET = placeholder(); - public static final int AF_INET6 = placeholder(); - public static final int AF_UNIX = placeholder(); - public static final int AF_UNSPEC = placeholder(); - public static final int AI_ADDRCONFIG = placeholder(); - public static final int AI_ALL = placeholder(); - public static final int AI_CANONNAME = placeholder(); - public static final int AI_NUMERICHOST = placeholder(); - public static final int AI_NUMERICSERV = placeholder(); - public static final int AI_PASSIVE = placeholder(); - public static final int AI_V4MAPPED = placeholder(); - public static final int E2BIG = placeholder(); - public static final int EACCES = placeholder(); - public static final int EADDRINUSE = placeholder(); - public static final int EADDRNOTAVAIL = placeholder(); - public static final int EAFNOSUPPORT = placeholder(); - public static final int EAGAIN = placeholder(); - public static final int EAI_AGAIN = placeholder(); - public static final int EAI_BADFLAGS = placeholder(); - public static final int EAI_FAIL = placeholder(); - public static final int EAI_FAMILY = placeholder(); - public static final int EAI_MEMORY = placeholder(); - public static final int EAI_NODATA = placeholder(); - public static final int EAI_NONAME = placeholder(); - public static final int EAI_OVERFLOW = placeholder(); - public static final int EAI_SERVICE = placeholder(); - public static final int EAI_SOCKTYPE = placeholder(); - public static final int EAI_SYSTEM = placeholder(); - public static final int EALREADY = placeholder(); - public static final int EBADF = placeholder(); - public static final int EBADMSG = placeholder(); - public static final int EBUSY = placeholder(); - public static final int ECANCELED = placeholder(); - public static final int ECHILD = placeholder(); - public static final int ECONNABORTED = placeholder(); - public static final int ECONNREFUSED = placeholder(); - public static final int ECONNRESET = placeholder(); - public static final int EDEADLK = placeholder(); - public static final int EDESTADDRREQ = placeholder(); - public static final int EDOM = placeholder(); - public static final int EDQUOT = placeholder(); - public static final int EEXIST = placeholder(); - public static final int EFAULT = placeholder(); - public static final int EFBIG = placeholder(); - public static final int EHOSTUNREACH = placeholder(); - public static final int EIDRM = placeholder(); - public static final int EILSEQ = placeholder(); - public static final int EINPROGRESS = placeholder(); - public static final int EINTR = placeholder(); - public static final int EINVAL = placeholder(); - public static final int EIO = placeholder(); - public static final int EISCONN = placeholder(); - public static final int EISDIR = placeholder(); - public static final int ELOOP = placeholder(); - public static final int EMFILE = placeholder(); - public static final int EMLINK = placeholder(); - public static final int EMSGSIZE = placeholder(); - public static final int EMULTIHOP = placeholder(); - public static final int ENAMETOOLONG = placeholder(); - public static final int ENETDOWN = placeholder(); - public static final int ENETRESET = placeholder(); - public static final int ENETUNREACH = placeholder(); - public static final int ENFILE = placeholder(); - public static final int ENOBUFS = placeholder(); - public static final int ENODATA = placeholder(); - public static final int ENODEV = placeholder(); - public static final int ENOENT = placeholder(); - public static final int ENOEXEC = placeholder(); - public static final int ENOLCK = placeholder(); - public static final int ENOLINK = placeholder(); - public static final int ENOMEM = placeholder(); - public static final int ENOMSG = placeholder(); - public static final int ENOPROTOOPT = placeholder(); - public static final int ENOSPC = placeholder(); - public static final int ENOSR = placeholder(); - public static final int ENOSTR = placeholder(); - public static final int ENOSYS = placeholder(); - public static final int ENOTCONN = placeholder(); - public static final int ENOTDIR = placeholder(); - public static final int ENOTEMPTY = placeholder(); - public static final int ENOTSOCK = placeholder(); - public static final int ENOTSUP = placeholder(); - public static final int ENOTTY = placeholder(); - public static final int ENXIO = placeholder(); - public static final int EOPNOTSUPP = placeholder(); - public static final int EOVERFLOW = placeholder(); - public static final int EPERM = placeholder(); - public static final int EPIPE = placeholder(); - public static final int EPROTO = placeholder(); - public static final int EPROTONOSUPPORT = placeholder(); - public static final int EPROTOTYPE = placeholder(); - public static final int ERANGE = placeholder(); - public static final int EROFS = placeholder(); - public static final int ESPIPE = placeholder(); - public static final int ESRCH = placeholder(); - public static final int ESTALE = placeholder(); - public static final int ETIME = placeholder(); - public static final int ETIMEDOUT = placeholder(); - public static final int ETXTBSY = placeholder(); - public static final int EWOULDBLOCK = placeholder(); - public static final int EXDEV = placeholder(); - public static final int EXIT_FAILURE = placeholder(); - public static final int EXIT_SUCCESS = placeholder(); - public static final int FD_CLOEXEC = placeholder(); - public static final int FIONREAD = placeholder(); - public static final int F_DUPFD = placeholder(); - public static final int F_GETFD = placeholder(); - public static final int F_GETFL = placeholder(); - public static final int F_GETLK = placeholder(); - public static final int F_GETLK64 = placeholder(); - public static final int F_GETOWN = placeholder(); - public static final int F_OK = placeholder(); - public static final int F_RDLCK = placeholder(); - public static final int F_SETFD = placeholder(); - public static final int F_SETFL = placeholder(); - public static final int F_SETLK = placeholder(); - public static final int F_SETLK64 = placeholder(); - public static final int F_SETLKW = placeholder(); - public static final int F_SETLKW64 = placeholder(); - public static final int F_SETOWN = placeholder(); - public static final int F_UNLCK = placeholder(); - public static final int F_WRLCK = placeholder(); - public static final int IFF_ALLMULTI = placeholder(); - public static final int IFF_AUTOMEDIA = placeholder(); - public static final int IFF_BROADCAST = placeholder(); - public static final int IFF_DEBUG = placeholder(); - public static final int IFF_DYNAMIC = placeholder(); - public static final int IFF_LOOPBACK = placeholder(); - public static final int IFF_MASTER = placeholder(); - public static final int IFF_MULTICAST = placeholder(); - public static final int IFF_NOARP = placeholder(); - public static final int IFF_NOTRAILERS = placeholder(); - public static final int IFF_POINTOPOINT = placeholder(); - public static final int IFF_PORTSEL = placeholder(); - public static final int IFF_PROMISC = placeholder(); - public static final int IFF_RUNNING = placeholder(); - public static final int IFF_SLAVE = placeholder(); - public static final int IFF_UP = placeholder(); - public static final int IPPROTO_ICMP = placeholder(); - public static final int IPPROTO_IP = placeholder(); - public static final int IPPROTO_IPV6 = placeholder(); - public static final int IPPROTO_RAW = placeholder(); - public static final int IPPROTO_TCP = placeholder(); - public static final int IPPROTO_UDP = placeholder(); - public static final int IPV6_CHECKSUM = placeholder(); - public static final int IPV6_MULTICAST_HOPS = placeholder(); - public static final int IPV6_MULTICAST_IF = placeholder(); - public static final int IPV6_MULTICAST_LOOP = placeholder(); - public static final int IPV6_RECVDSTOPTS = placeholder(); - public static final int IPV6_RECVHOPLIMIT = placeholder(); - public static final int IPV6_RECVHOPOPTS = placeholder(); - public static final int IPV6_RECVPKTINFO = placeholder(); - public static final int IPV6_RECVRTHDR = placeholder(); - public static final int IPV6_RECVTCLASS = placeholder(); - public static final int IPV6_TCLASS = placeholder(); - public static final int IPV6_UNICAST_HOPS = placeholder(); - public static final int IPV6_V6ONLY = placeholder(); - public static final int IP_MULTICAST_IF = placeholder(); - public static final int IP_MULTICAST_LOOP = placeholder(); - public static final int IP_MULTICAST_TTL = placeholder(); - public static final int IP_TOS = placeholder(); - public static final int IP_TTL = placeholder(); - public static final int MAP_FIXED = placeholder(); - public static final int MAP_PRIVATE = placeholder(); - public static final int MAP_SHARED = placeholder(); - public static final int MCAST_JOIN_GROUP = placeholder(); - public static final int MCAST_LEAVE_GROUP = placeholder(); - public static final int MCL_CURRENT = placeholder(); - public static final int MCL_FUTURE = placeholder(); - public static final int MSG_CTRUNC = placeholder(); - public static final int MSG_DONTROUTE = placeholder(); - public static final int MSG_EOR = placeholder(); - public static final int MSG_OOB = placeholder(); - public static final int MSG_PEEK = placeholder(); - public static final int MSG_TRUNC = placeholder(); - public static final int MSG_WAITALL = placeholder(); - public static final int MS_ASYNC = placeholder(); - public static final int MS_INVALIDATE = placeholder(); - public static final int MS_SYNC = placeholder(); - public static final int NI_DGRAM = placeholder(); - public static final int NI_NAMEREQD = placeholder(); - public static final int NI_NOFQDN = placeholder(); - public static final int NI_NUMERICHOST = placeholder(); - public static final int NI_NUMERICSERV = placeholder(); - public static final int O_ACCMODE = placeholder(); - public static final int O_APPEND = placeholder(); - public static final int O_CREAT = placeholder(); - public static final int O_EXCL = placeholder(); - public static final int O_NOCTTY = placeholder(); - public static final int O_NONBLOCK = placeholder(); - public static final int O_RDONLY = placeholder(); - public static final int O_RDWR = placeholder(); - public static final int O_SYNC = placeholder(); - public static final int O_TRUNC = placeholder(); - public static final int O_WRONLY = placeholder(); - public static final int POLLERR = placeholder(); - public static final int POLLHUP = placeholder(); - public static final int POLLIN = placeholder(); - public static final int POLLNVAL = placeholder(); - public static final int POLLOUT = placeholder(); - public static final int POLLPRI = placeholder(); - public static final int POLLRDBAND = placeholder(); - public static final int POLLRDNORM = placeholder(); - public static final int POLLWRBAND = placeholder(); - public static final int POLLWRNORM = placeholder(); - public static final int PROT_EXEC = placeholder(); - public static final int PROT_NONE = placeholder(); - public static final int PROT_READ = placeholder(); - public static final int PROT_WRITE = placeholder(); - public static final int R_OK = placeholder(); - public static final int SEEK_CUR = placeholder(); - public static final int SEEK_END = placeholder(); - public static final int SEEK_SET = placeholder(); - public static final int SHUT_RD = placeholder(); - public static final int SHUT_RDWR = placeholder(); - public static final int SHUT_WR = placeholder(); - public static final int SIGABRT = placeholder(); - public static final int SIGALRM = placeholder(); - public static final int SIGBUS = placeholder(); - public static final int SIGCHLD = placeholder(); - public static final int SIGCONT = placeholder(); - public static final int SIGFPE = placeholder(); - public static final int SIGHUP = placeholder(); - public static final int SIGILL = placeholder(); - public static final int SIGINT = placeholder(); - public static final int SIGIO = placeholder(); - public static final int SIGKILL = placeholder(); - public static final int SIGPIPE = placeholder(); - public static final int SIGPROF = placeholder(); - public static final int SIGPWR = placeholder(); - public static final int SIGQUIT = placeholder(); - public static final int SIGRTMAX = placeholder(); - public static final int SIGRTMIN = placeholder(); - public static final int SIGSEGV = placeholder(); - public static final int SIGSTKFLT = placeholder(); - public static final int SIGSTOP = placeholder(); - public static final int SIGSYS = placeholder(); - public static final int SIGTERM = placeholder(); - public static final int SIGTRAP = placeholder(); - public static final int SIGTSTP = placeholder(); - public static final int SIGTTIN = placeholder(); - public static final int SIGTTOU = placeholder(); - public static final int SIGURG = placeholder(); - public static final int SIGUSR1 = placeholder(); - public static final int SIGUSR2 = placeholder(); - public static final int SIGVTALRM = placeholder(); - public static final int SIGWINCH = placeholder(); - public static final int SIGXCPU = placeholder(); - public static final int SIGXFSZ = placeholder(); - public static final int SIOCGIFADDR = placeholder(); - public static final int SIOCGIFBRDADDR = placeholder(); - public static final int SIOCGIFDSTADDR = placeholder(); - public static final int SIOCGIFNETMASK = placeholder(); - public static final int SOCK_DGRAM = placeholder(); - public static final int SOCK_RAW = placeholder(); - public static final int SOCK_SEQPACKET = placeholder(); - public static final int SOCK_STREAM = placeholder(); - public static final int SOL_SOCKET = placeholder(); - public static final int SO_BINDTODEVICE = placeholder(); - public static final int SO_BROADCAST = placeholder(); - public static final int SO_DEBUG = placeholder(); - public static final int SO_DONTROUTE = placeholder(); - public static final int SO_ERROR = placeholder(); - public static final int SO_KEEPALIVE = placeholder(); - public static final int SO_LINGER = placeholder(); - public static final int SO_OOBINLINE = placeholder(); - public static final int SO_RCVBUF = placeholder(); - public static final int SO_RCVLOWAT = placeholder(); - public static final int SO_RCVTIMEO = placeholder(); - public static final int SO_REUSEADDR = placeholder(); - public static final int SO_SNDBUF = placeholder(); - public static final int SO_SNDLOWAT = placeholder(); - public static final int SO_SNDTIMEO = placeholder(); - public static final int SO_TYPE = placeholder(); - public static final int STDERR_FILENO = placeholder(); - public static final int STDIN_FILENO = placeholder(); - public static final int STDOUT_FILENO = placeholder(); - public static final int S_IFBLK = placeholder(); - public static final int S_IFCHR = placeholder(); - public static final int S_IFDIR = placeholder(); - public static final int S_IFIFO = placeholder(); - public static final int S_IFLNK = placeholder(); - public static final int S_IFMT = placeholder(); - public static final int S_IFREG = placeholder(); - public static final int S_IFSOCK = placeholder(); - public static final int S_IRGRP = placeholder(); - public static final int S_IROTH = placeholder(); - public static final int S_IRUSR = placeholder(); - public static final int S_IRWXG = placeholder(); - public static final int S_IRWXO = placeholder(); - public static final int S_IRWXU = placeholder(); - public static final int S_ISGID = placeholder(); - public static final int S_ISUID = placeholder(); - public static final int S_ISVTX = placeholder(); - public static final int S_IWGRP = placeholder(); - public static final int S_IWOTH = placeholder(); - public static final int S_IWUSR = placeholder(); - public static final int S_IXGRP = placeholder(); - public static final int S_IXOTH = placeholder(); - public static final int S_IXUSR = placeholder(); - public static final int TCP_NODELAY = placeholder(); - public static final int WCONTINUED = placeholder(); - public static final int WEXITED = placeholder(); - public static final int WNOHANG = placeholder(); - public static final int WNOWAIT = placeholder(); - public static final int WSTOPPED = placeholder(); - public static final int WUNTRACED = placeholder(); - public static final int W_OK = placeholder(); - public static final int X_OK = placeholder(); - public static final int _SC_2_CHAR_TERM = placeholder(); - public static final int _SC_2_C_BIND = placeholder(); - public static final int _SC_2_C_DEV = placeholder(); - public static final int _SC_2_C_VERSION = placeholder(); - public static final int _SC_2_FORT_DEV = placeholder(); - public static final int _SC_2_FORT_RUN = placeholder(); - public static final int _SC_2_LOCALEDEF = placeholder(); - public static final int _SC_2_SW_DEV = placeholder(); - public static final int _SC_2_UPE = placeholder(); - public static final int _SC_2_VERSION = placeholder(); - public static final int _SC_AIO_LISTIO_MAX = placeholder(); - public static final int _SC_AIO_MAX = placeholder(); - public static final int _SC_AIO_PRIO_DELTA_MAX = placeholder(); - public static final int _SC_ARG_MAX = placeholder(); - public static final int _SC_ASYNCHRONOUS_IO = placeholder(); - public static final int _SC_ATEXIT_MAX = placeholder(); - public static final int _SC_AVPHYS_PAGES = placeholder(); - public static final int _SC_BC_BASE_MAX = placeholder(); - public static final int _SC_BC_DIM_MAX = placeholder(); - public static final int _SC_BC_SCALE_MAX = placeholder(); - public static final int _SC_BC_STRING_MAX = placeholder(); - public static final int _SC_CHILD_MAX = placeholder(); - public static final int _SC_CLK_TCK = placeholder(); - public static final int _SC_COLL_WEIGHTS_MAX = placeholder(); - public static final int _SC_DELAYTIMER_MAX = placeholder(); - public static final int _SC_EXPR_NEST_MAX = placeholder(); - public static final int _SC_FSYNC = placeholder(); - public static final int _SC_GETGR_R_SIZE_MAX = placeholder(); - public static final int _SC_GETPW_R_SIZE_MAX = placeholder(); - public static final int _SC_IOV_MAX = placeholder(); - public static final int _SC_JOB_CONTROL = placeholder(); - public static final int _SC_LINE_MAX = placeholder(); - public static final int _SC_LOGIN_NAME_MAX = placeholder(); - public static final int _SC_MAPPED_FILES = placeholder(); - public static final int _SC_MEMLOCK = placeholder(); - public static final int _SC_MEMLOCK_RANGE = placeholder(); - public static final int _SC_MEMORY_PROTECTION = placeholder(); - public static final int _SC_MESSAGE_PASSING = placeholder(); - public static final int _SC_MQ_OPEN_MAX = placeholder(); - public static final int _SC_MQ_PRIO_MAX = placeholder(); - public static final int _SC_NGROUPS_MAX = placeholder(); - public static final int _SC_NPROCESSORS_CONF = placeholder(); - public static final int _SC_NPROCESSORS_ONLN = placeholder(); - public static final int _SC_OPEN_MAX = placeholder(); - public static final int _SC_PAGESIZE = placeholder(); - public static final int _SC_PAGE_SIZE = placeholder(); - public static final int _SC_PASS_MAX = placeholder(); - public static final int _SC_PHYS_PAGES = placeholder(); - public static final int _SC_PRIORITIZED_IO = placeholder(); - public static final int _SC_PRIORITY_SCHEDULING = placeholder(); - public static final int _SC_REALTIME_SIGNALS = placeholder(); - public static final int _SC_RE_DUP_MAX = placeholder(); - public static final int _SC_RTSIG_MAX = placeholder(); - public static final int _SC_SAVED_IDS = placeholder(); - public static final int _SC_SEMAPHORES = placeholder(); - public static final int _SC_SEM_NSEMS_MAX = placeholder(); - public static final int _SC_SEM_VALUE_MAX = placeholder(); - public static final int _SC_SHARED_MEMORY_OBJECTS = placeholder(); - public static final int _SC_SIGQUEUE_MAX = placeholder(); - public static final int _SC_STREAM_MAX = placeholder(); - public static final int _SC_SYNCHRONIZED_IO = placeholder(); - public static final int _SC_THREADS = placeholder(); - public static final int _SC_THREAD_ATTR_STACKADDR = placeholder(); - public static final int _SC_THREAD_ATTR_STACKSIZE = placeholder(); - public static final int _SC_THREAD_DESTRUCTOR_ITERATIONS = placeholder(); - public static final int _SC_THREAD_KEYS_MAX = placeholder(); - public static final int _SC_THREAD_PRIORITY_SCHEDULING = placeholder(); - public static final int _SC_THREAD_PRIO_INHERIT = placeholder(); - public static final int _SC_THREAD_PRIO_PROTECT = placeholder(); - public static final int _SC_THREAD_SAFE_FUNCTIONS = placeholder(); - public static final int _SC_THREAD_STACK_MIN = placeholder(); - public static final int _SC_THREAD_THREADS_MAX = placeholder(); - public static final int _SC_TIMERS = placeholder(); - public static final int _SC_TIMER_MAX = placeholder(); - public static final int _SC_TTY_NAME_MAX = placeholder(); - public static final int _SC_TZNAME_MAX = placeholder(); - public static final int _SC_VERSION = placeholder(); - public static final int _SC_XBS5_ILP32_OFF32 = placeholder(); - public static final int _SC_XBS5_ILP32_OFFBIG = placeholder(); - public static final int _SC_XBS5_LP64_OFF64 = placeholder(); - public static final int _SC_XBS5_LPBIG_OFFBIG = placeholder(); - public static final int _SC_XOPEN_CRYPT = placeholder(); - public static final int _SC_XOPEN_ENH_I18N = placeholder(); - public static final int _SC_XOPEN_LEGACY = placeholder(); - public static final int _SC_XOPEN_REALTIME = placeholder(); - public static final int _SC_XOPEN_REALTIME_THREADS = placeholder(); - public static final int _SC_XOPEN_SHM = placeholder(); - public static final int _SC_XOPEN_UNIX = placeholder(); - public static final int _SC_XOPEN_VERSION = placeholder(); - public static final int _SC_XOPEN_XCU_VERSION = placeholder(); - - public static String gaiName(int error) { - if (error == EAI_AGAIN) { - return "EAI_AGAIN"; - } - if (error == EAI_BADFLAGS) { - return "EAI_BADFLAGS"; - } - if (error == EAI_FAIL) { - return "EAI_FAIL"; - } - if (error == EAI_FAMILY) { - return "EAI_FAMILY"; - } - if (error == EAI_MEMORY) { - return "EAI_MEMORY"; - } - if (error == EAI_NODATA) { - return "EAI_NODATA"; - } - if (error == EAI_NONAME) { - return "EAI_NONAME"; - } - if (error == EAI_OVERFLOW) { - return "EAI_OVERFLOW"; - } - if (error == EAI_SERVICE) { - return "EAI_SERVICE"; - } - if (error == EAI_SOCKTYPE) { - return "EAI_SOCKTYPE"; - } - if (error == EAI_SYSTEM) { - return "EAI_SYSTEM"; - } - return null; - } - - public static String errnoName(int errno) { - if (errno == E2BIG) { - return "E2BIG"; - } - if (errno == EACCES) { - return "EACCES"; - } - if (errno == EADDRINUSE) { - return "EADDRINUSE"; - } - if (errno == EADDRNOTAVAIL) { - return "EADDRNOTAVAIL"; - } - if (errno == EAFNOSUPPORT) { - return "EAFNOSUPPORT"; - } - if (errno == EAGAIN) { - return "EAGAIN"; - } - if (errno == EALREADY) { - return "EALREADY"; - } - if (errno == EBADF) { - return "EBADF"; - } - if (errno == EBADMSG) { - return "EBADMSG"; - } - if (errno == EBUSY) { - return "EBUSY"; - } - if (errno == ECANCELED) { - return "ECANCELED"; - } - if (errno == ECHILD) { - return "ECHILD"; - } - if (errno == ECONNABORTED) { - return "ECONNABORTED"; - } - if (errno == ECONNREFUSED) { - return "ECONNREFUSED"; - } - if (errno == ECONNRESET) { - return "ECONNRESET"; - } - if (errno == EDEADLK) { - return "EDEADLK"; - } - if (errno == EDESTADDRREQ) { - return "EDESTADDRREQ"; - } - if (errno == EDOM) { - return "EDOM"; - } - if (errno == EDQUOT) { - return "EDQUOT"; - } - if (errno == EEXIST) { - return "EEXIST"; - } - if (errno == EFAULT) { - return "EFAULT"; - } - if (errno == EFBIG) { - return "EFBIG"; - } - if (errno == EHOSTUNREACH) { - return "EHOSTUNREACH"; - } - if (errno == EIDRM) { - return "EIDRM"; - } - if (errno == EILSEQ) { - return "EILSEQ"; - } - if (errno == EINPROGRESS) { - return "EINPROGRESS"; - } - if (errno == EINTR) { - return "EINTR"; - } - if (errno == EINVAL) { - return "EINVAL"; - } - if (errno == EIO) { - return "EIO"; - } - if (errno == EISCONN) { - return "EISCONN"; - } - if (errno == EISDIR) { - return "EISDIR"; - } - if (errno == ELOOP) { - return "ELOOP"; - } - if (errno == EMFILE) { - return "EMFILE"; - } - if (errno == EMLINK) { - return "EMLINK"; - } - if (errno == EMSGSIZE) { - return "EMSGSIZE"; - } - if (errno == EMULTIHOP) { - return "EMULTIHOP"; - } - if (errno == ENAMETOOLONG) { - return "ENAMETOOLONG"; - } - if (errno == ENETDOWN) { - return "ENETDOWN"; - } - if (errno == ENETRESET) { - return "ENETRESET"; - } - if (errno == ENETUNREACH) { - return "ENETUNREACH"; - } - if (errno == ENFILE) { - return "ENFILE"; - } - if (errno == ENOBUFS) { - return "ENOBUFS"; - } - if (errno == ENODATA) { - return "ENODATA"; - } - if (errno == ENODEV) { - return "ENODEV"; - } - if (errno == ENOENT) { - return "ENOENT"; - } - if (errno == ENOEXEC) { - return "ENOEXEC"; - } - if (errno == ENOLCK) { - return "ENOLCK"; - } - if (errno == ENOLINK) { - return "ENOLINK"; - } - if (errno == ENOMEM) { - return "ENOMEM"; - } - if (errno == ENOMSG) { - return "ENOMSG"; - } - if (errno == ENOPROTOOPT) { - return "ENOPROTOOPT"; - } - if (errno == ENOSPC) { - return "ENOSPC"; - } - if (errno == ENOSR) { - return "ENOSR"; - } - if (errno == ENOSTR) { - return "ENOSTR"; - } - if (errno == ENOSYS) { - return "ENOSYS"; - } - if (errno == ENOTCONN) { - return "ENOTCONN"; - } - if (errno == ENOTDIR) { - return "ENOTDIR"; - } - if (errno == ENOTEMPTY) { - return "ENOTEMPTY"; - } - if (errno == ENOTSOCK) { - return "ENOTSOCK"; - } - if (errno == ENOTSUP) { - return "ENOTSUP"; - } - if (errno == ENOTTY) { - return "ENOTTY"; - } - if (errno == ENXIO) { - return "ENXIO"; - } - if (errno == EOPNOTSUPP) { - return "EOPNOTSUPP"; - } - if (errno == EOVERFLOW) { - return "EOVERFLOW"; - } - if (errno == EPERM) { - return "EPERM"; - } - if (errno == EPIPE) { - return "EPIPE"; - } - if (errno == EPROTO) { - return "EPROTO"; - } - if (errno == EPROTONOSUPPORT) { - return "EPROTONOSUPPORT"; - } - if (errno == EPROTOTYPE) { - return "EPROTOTYPE"; - } - if (errno == ERANGE) { - return "ERANGE"; - } - if (errno == EROFS) { - return "EROFS"; - } - if (errno == ESPIPE) { - return "ESPIPE"; - } - if (errno == ESRCH) { - return "ESRCH"; - } - if (errno == ESTALE) { - return "ESTALE"; - } - if (errno == ETIME) { - return "ETIME"; - } - if (errno == ETIMEDOUT) { - return "ETIMEDOUT"; - } - if (errno == ETXTBSY) { - return "ETXTBSY"; - } - if (errno == EWOULDBLOCK) { - return "EWOULDBLOCK"; - } - if (errno == EXDEV) { - return "EXDEV"; - } - return null; - } - - private static native void initConstants(); - - // A hack to avoid these constants being inlined by javac... - private static int placeholder() { return 0; } - // ...because we want to initialize them at runtime. - static { - initConstants(); - } -} diff --git a/src/main/java/libcore/io/SizeOf.java b/src/main/java/libcore/io/SizeOf.java deleted file mode 100644 index 728fbfc..0000000 --- a/src/main/java/libcore/io/SizeOf.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 libcore.io; - -public final class SizeOf { - public static final int CHAR = 2; - public static final int DOUBLE = 8; - public static final int FLOAT = 4; - public static final int INT = 4; - public static final int LONG = 8; - public static final int SHORT = 2; - - private SizeOf() { - } -} diff --git a/src/main/java/libcore/net/MimeUtils.java b/src/main/java/libcore/net/MimeUtils.java deleted file mode 100644 index 76193ff..0000000 --- a/src/main/java/libcore/net/MimeUtils.java +++ /dev/null @@ -1,480 +0,0 @@ -/* - * 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 libcore.net; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -/** - * Utilities for dealing with MIME types. - * Used to implement java.net.URLConnection and android.webkit.MimeTypeMap. - */ -public final class MimeUtils { - private static final Map<String, String> MIME_TYPE_TO_EXTENSION_MAP = new HashMap<String, String>(); - - private static final Map<String, String> EXTENSION_TO_MIME_TYPE_MAP = new HashMap<String, String>(); - - static { - // The following table is based on /etc/mime.types data minus - // chemical/* MIME types and MIME types that don't map to any - // file extensions. We also exclude top-level domain names to - // deal with cases like: - // - // mail.google.com/a/google.com - // - // and "active" MIME types (due to potential security issues). - - add("application/andrew-inset", "ez"); - add("application/dsptype", "tsp"); - add("application/futuresplash", "spl"); - add("application/hta", "hta"); - add("application/mac-binhex40", "hqx"); - add("application/mac-compactpro", "cpt"); - add("application/mathematica", "nb"); - add("application/msaccess", "mdb"); - add("application/oda", "oda"); - add("application/ogg", "ogg"); - add("application/pdf", "pdf"); - add("application/pgp-keys", "key"); - add("application/pgp-signature", "pgp"); - add("application/pics-rules", "prf"); - add("application/rar", "rar"); - add("application/rdf+xml", "rdf"); - add("application/rss+xml", "rss"); - add("application/zip", "zip"); - add("application/vnd.android.package-archive", "apk"); - add("application/vnd.cinderella", "cdy"); - add("application/vnd.ms-pki.stl", "stl"); - add("application/vnd.oasis.opendocument.database", "odb"); - add("application/vnd.oasis.opendocument.formula", "odf"); - add("application/vnd.oasis.opendocument.graphics", "odg"); - add("application/vnd.oasis.opendocument.graphics-template", "otg"); - add("application/vnd.oasis.opendocument.image", "odi"); - add("application/vnd.oasis.opendocument.spreadsheet", "ods"); - add("application/vnd.oasis.opendocument.spreadsheet-template", "ots"); - add("application/vnd.oasis.opendocument.text", "odt"); - add("application/vnd.oasis.opendocument.text-master", "odm"); - add("application/vnd.oasis.opendocument.text-template", "ott"); - add("application/vnd.oasis.opendocument.text-web", "oth"); - add("application/vnd.google-earth.kml+xml", "kml"); - add("application/vnd.google-earth.kmz", "kmz"); - add("application/msword", "doc"); - add("application/msword", "dot"); - add("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx"); - add("application/vnd.openxmlformats-officedocument.wordprocessingml.template", "dotx"); - add("application/vnd.ms-excel", "xls"); - add("application/vnd.ms-excel", "xlt"); - add("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx"); - add("application/vnd.openxmlformats-officedocument.spreadsheetml.template", "xltx"); - add("application/vnd.ms-powerpoint", "ppt"); - add("application/vnd.ms-powerpoint", "pot"); - add("application/vnd.ms-powerpoint", "pps"); - add("application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx"); - add("application/vnd.openxmlformats-officedocument.presentationml.template", "potx"); - add("application/vnd.openxmlformats-officedocument.presentationml.slideshow", "ppsx"); - add("application/vnd.rim.cod", "cod"); - add("application/vnd.smaf", "mmf"); - add("application/vnd.stardivision.calc", "sdc"); - add("application/vnd.stardivision.draw", "sda"); - add("application/vnd.stardivision.impress", "sdd"); - add("application/vnd.stardivision.impress", "sdp"); - add("application/vnd.stardivision.math", "smf"); - add("application/vnd.stardivision.writer", "sdw"); - add("application/vnd.stardivision.writer", "vor"); - add("application/vnd.stardivision.writer-global", "sgl"); - add("application/vnd.sun.xml.calc", "sxc"); - add("application/vnd.sun.xml.calc.template", "stc"); - add("application/vnd.sun.xml.draw", "sxd"); - add("application/vnd.sun.xml.draw.template", "std"); - add("application/vnd.sun.xml.impress", "sxi"); - add("application/vnd.sun.xml.impress.template", "sti"); - add("application/vnd.sun.xml.math", "sxm"); - add("application/vnd.sun.xml.writer", "sxw"); - add("application/vnd.sun.xml.writer.global", "sxg"); - add("application/vnd.sun.xml.writer.template", "stw"); - add("application/vnd.visio", "vsd"); - add("application/x-abiword", "abw"); - add("application/x-apple-diskimage", "dmg"); - add("application/x-bcpio", "bcpio"); - add("application/x-bittorrent", "torrent"); - add("application/x-cdf", "cdf"); - add("application/x-cdlink", "vcd"); - add("application/x-chess-pgn", "pgn"); - add("application/x-cpio", "cpio"); - add("application/x-debian-package", "deb"); - add("application/x-debian-package", "udeb"); - add("application/x-director", "dcr"); - add("application/x-director", "dir"); - add("application/x-director", "dxr"); - add("application/x-dms", "dms"); - add("application/x-doom", "wad"); - add("application/x-dvi", "dvi"); - add("application/x-flac", "flac"); - add("application/x-font", "pfa"); - add("application/x-font", "pfb"); - add("application/x-font", "gsf"); - add("application/x-font", "pcf"); - add("application/x-font", "pcf.Z"); - add("application/x-freemind", "mm"); - add("application/x-futuresplash", "spl"); - add("application/x-gnumeric", "gnumeric"); - add("application/x-go-sgf", "sgf"); - add("application/x-graphing-calculator", "gcf"); - add("application/x-gtar", "gtar"); - add("application/x-gtar", "tgz"); - add("application/x-gtar", "taz"); - add("application/x-hdf", "hdf"); - add("application/x-ica", "ica"); - add("application/x-internet-signup", "ins"); - add("application/x-internet-signup", "isp"); - add("application/x-iphone", "iii"); - add("application/x-iso9660-image", "iso"); - add("application/x-jmol", "jmz"); - add("application/x-kchart", "chrt"); - add("application/x-killustrator", "kil"); - add("application/x-koan", "skp"); - add("application/x-koan", "skd"); - add("application/x-koan", "skt"); - add("application/x-koan", "skm"); - add("application/x-kpresenter", "kpr"); - add("application/x-kpresenter", "kpt"); - add("application/x-kspread", "ksp"); - add("application/x-kword", "kwd"); - add("application/x-kword", "kwt"); - add("application/x-latex", "latex"); - add("application/x-lha", "lha"); - add("application/x-lzh", "lzh"); - add("application/x-lzx", "lzx"); - add("application/x-maker", "frm"); - add("application/x-maker", "maker"); - add("application/x-maker", "frame"); - add("application/x-maker", "fb"); - add("application/x-maker", "book"); - add("application/x-maker", "fbdoc"); - add("application/x-mif", "mif"); - add("application/x-ms-wmd", "wmd"); - add("application/x-ms-wmz", "wmz"); - add("application/x-msi", "msi"); - add("application/x-ns-proxy-autoconfig", "pac"); - add("application/x-nwc", "nwc"); - add("application/x-object", "o"); - add("application/x-oz-application", "oza"); - add("application/x-pkcs12", "p12"); - add("application/x-pkcs12", "pfx"); - add("application/x-pkcs7-certreqresp", "p7r"); - add("application/x-pkcs7-crl", "crl"); - add("application/x-quicktimeplayer", "qtl"); - add("application/x-shar", "shar"); - add("application/x-shockwave-flash", "swf"); - add("application/x-stuffit", "sit"); - add("application/x-sv4cpio", "sv4cpio"); - add("application/x-sv4crc", "sv4crc"); - add("application/x-tar", "tar"); - add("application/x-texinfo", "texinfo"); - add("application/x-texinfo", "texi"); - add("application/x-troff", "t"); - add("application/x-troff", "roff"); - add("application/x-troff-man", "man"); - add("application/x-ustar", "ustar"); - add("application/x-wais-source", "src"); - add("application/x-wingz", "wz"); - add("application/x-webarchive", "webarchive"); - add("application/x-webarchive-xml", "webarchivexml"); - add("application/x-x509-ca-cert", "crt"); - add("application/x-x509-user-cert", "crt"); - add("application/x-xcf", "xcf"); - add("application/x-xfig", "fig"); - add("application/xhtml+xml", "xhtml"); - add("audio/3gpp", "3gpp"); - add("audio/amr", "amr"); - add("audio/basic", "snd"); - add("audio/midi", "mid"); - add("audio/midi", "midi"); - add("audio/midi", "kar"); - add("audio/midi", "xmf"); - add("audio/mobile-xmf", "mxmf"); - add("audio/mpeg", "mpga"); - add("audio/mpeg", "mpega"); - add("audio/mpeg", "mp2"); - add("audio/mpeg", "mp3"); - add("audio/mpeg", "m4a"); - add("audio/mpegurl", "m3u"); - add("audio/prs.sid", "sid"); - add("audio/x-aiff", "aif"); - add("audio/x-aiff", "aiff"); - add("audio/x-aiff", "aifc"); - add("audio/x-gsm", "gsm"); - add("audio/x-mpegurl", "m3u"); - add("audio/x-ms-wma", "wma"); - add("audio/x-ms-wax", "wax"); - add("audio/x-pn-realaudio", "ra"); - add("audio/x-pn-realaudio", "rm"); - add("audio/x-pn-realaudio", "ram"); - add("audio/x-realaudio", "ra"); - add("audio/x-scpls", "pls"); - add("audio/x-sd2", "sd2"); - add("audio/x-wav", "wav"); - add("image/bmp", "bmp"); - add("image/gif", "gif"); - add("image/ico", "cur"); - add("image/ico", "ico"); - add("image/ief", "ief"); - add("image/jpeg", "jpeg"); - add("image/jpeg", "jpg"); - add("image/jpeg", "jpe"); - add("image/pcx", "pcx"); - add("image/png", "png"); - add("image/svg+xml", "svg"); - add("image/svg+xml", "svgz"); - add("image/tiff", "tiff"); - add("image/tiff", "tif"); - add("image/vnd.djvu", "djvu"); - add("image/vnd.djvu", "djv"); - add("image/vnd.wap.wbmp", "wbmp"); - add("image/x-cmu-raster", "ras"); - add("image/x-coreldraw", "cdr"); - add("image/x-coreldrawpattern", "pat"); - add("image/x-coreldrawtemplate", "cdt"); - add("image/x-corelphotopaint", "cpt"); - add("image/x-icon", "ico"); - add("image/x-jg", "art"); - add("image/x-jng", "jng"); - add("image/x-ms-bmp", "bmp"); - add("image/x-photoshop", "psd"); - add("image/x-portable-anymap", "pnm"); - add("image/x-portable-bitmap", "pbm"); - add("image/x-portable-graymap", "pgm"); - add("image/x-portable-pixmap", "ppm"); - add("image/x-rgb", "rgb"); - add("image/x-xbitmap", "xbm"); - add("image/x-xpixmap", "xpm"); - add("image/x-xwindowdump", "xwd"); - add("model/iges", "igs"); - add("model/iges", "iges"); - add("model/mesh", "msh"); - add("model/mesh", "mesh"); - add("model/mesh", "silo"); - add("text/calendar", "ics"); - add("text/calendar", "icz"); - add("text/comma-separated-values", "csv"); - add("text/css", "css"); - add("text/html", "htm"); - add("text/html", "html"); - add("text/h323", "323"); - add("text/iuls", "uls"); - add("text/mathml", "mml"); - // add ".txt" first so it will be the default for ExtensionFromMimeType - add("text/plain", "txt"); - add("text/plain", "asc"); - add("text/plain", "text"); - add("text/plain", "diff"); - add("text/plain", "po"); // reserve "pot" for vnd.ms-powerpoint - add("text/richtext", "rtx"); - add("text/rtf", "rtf"); - add("text/texmacs", "ts"); - add("text/text", "phps"); - add("text/tab-separated-values", "tsv"); - add("text/xml", "xml"); - add("text/x-bibtex", "bib"); - add("text/x-boo", "boo"); - add("text/x-c++hdr", "h++"); - add("text/x-c++hdr", "hpp"); - add("text/x-c++hdr", "hxx"); - add("text/x-c++hdr", "hh"); - add("text/x-c++src", "c++"); - add("text/x-c++src", "cpp"); - add("text/x-c++src", "cxx"); - add("text/x-chdr", "h"); - add("text/x-component", "htc"); - add("text/x-csh", "csh"); - add("text/x-csrc", "c"); - add("text/x-dsrc", "d"); - add("text/x-haskell", "hs"); - add("text/x-java", "java"); - add("text/x-literate-haskell", "lhs"); - add("text/x-moc", "moc"); - add("text/x-pascal", "p"); - add("text/x-pascal", "pas"); - add("text/x-pcs-gcd", "gcd"); - add("text/x-setext", "etx"); - add("text/x-tcl", "tcl"); - add("text/x-tex", "tex"); - add("text/x-tex", "ltx"); - add("text/x-tex", "sty"); - add("text/x-tex", "cls"); - add("text/x-vcalendar", "vcs"); - add("text/x-vcard", "vcf"); - add("video/3gpp", "3gpp"); - add("video/3gpp", "3gp"); - add("video/3gpp", "3g2"); - add("video/dl", "dl"); - add("video/dv", "dif"); - add("video/dv", "dv"); - add("video/fli", "fli"); - add("video/m4v", "m4v"); - add("video/mpeg", "mpeg"); - add("video/mpeg", "mpg"); - add("video/mpeg", "mpe"); - add("video/mp4", "mp4"); - add("video/mpeg", "VOB"); - add("video/quicktime", "qt"); - add("video/quicktime", "mov"); - add("video/vnd.mpegurl", "mxu"); - add("video/x-la-asf", "lsf"); - add("video/x-la-asf", "lsx"); - add("video/x-mng", "mng"); - add("video/x-ms-asf", "asf"); - add("video/x-ms-asf", "asx"); - add("video/x-ms-wm", "wm"); - add("video/x-ms-wmv", "wmv"); - add("video/x-ms-wmx", "wmx"); - add("video/x-ms-wvx", "wvx"); - add("video/x-msvideo", "avi"); - add("video/x-sgi-movie", "movie"); - add("x-conference/x-cooltalk", "ice"); - add("x-epoc/x-sisx-app", "sisx"); - applyOverrides(); - } - - private static void add(String mimeType, String extension) { - // - // if we have an existing x --> y mapping, we do not want to - // override it with another mapping x --> ? - // this is mostly because of the way the mime-type map below - // is constructed (if a mime type maps to several extensions - // the first extension is considered the most popular and is - // added first; we do not want to overwrite it later). - // - if (!MIME_TYPE_TO_EXTENSION_MAP.containsKey(mimeType)) { - MIME_TYPE_TO_EXTENSION_MAP.put(mimeType, extension); - } - EXTENSION_TO_MIME_TYPE_MAP.put(extension, mimeType); - } - - private static InputStream getContentTypesPropertiesStream() { - // User override? - String userTable = System.getProperty("content.types.user.table"); - if (userTable != null) { - File f = new File(userTable); - if (f.exists()) { - try { - return new FileInputStream(f); - } catch (IOException ignored) { - } - } - } - - // Standard location? - File f = new File(System.getProperty("java.home"), "lib" + File.separator + "content-types.properties"); - if (f.exists()) { - try { - return new FileInputStream(f); - } catch (IOException ignored) { - } - } - - return null; - } - - /** - * This isn't what the RI does. The RI doesn't have hard-coded defaults, so supplying your - * own "content.types.user.table" means you don't get any of the built-ins, and the built-ins - * come from "$JAVA_HOME/lib/content-types.properties". - */ - private static void applyOverrides() { - // Get the appropriate InputStream to read overrides from, if any. - InputStream stream = getContentTypesPropertiesStream(); - if (stream == null) { - return; - } - - try { - try { - // Read the properties file... - Properties overrides = new Properties(); - overrides.load(stream); - // And translate its mapping to ours... - for (Map.Entry<Object, Object> entry : overrides.entrySet()) { - String extension = (String) entry.getKey(); - String mimeType = (String) entry.getValue(); - add(mimeType, extension); - } - } finally { - stream.close(); - } - } catch (IOException ignored) { - } - } - - private MimeUtils() { - } - - /** - * Returns true if the given MIME type has an entry in the map. - * @param mimeType A MIME type (i.e. text/plain) - * @return True iff there is a mimeType entry in the map. - */ - public static boolean hasMimeType(String mimeType) { - if (mimeType == null || mimeType.length() == 0) { - return false; - } - return MIME_TYPE_TO_EXTENSION_MAP.containsKey(mimeType); - } - - /** - * Returns the MIME type for the given extension. - * @param extension A file extension without the leading '.' - * @return The MIME type for the given extension or null iff there is none. - */ - public static String guessMimeTypeFromExtension(String extension) { - if (extension == null || extension.length() == 0) { - return null; - } - return EXTENSION_TO_MIME_TYPE_MAP.get(extension); - } - - /** - * Returns true if the given extension has a registered MIME type. - * @param extension A file extension without the leading '.' - * @return True iff there is an extension entry in the map. - */ - public static boolean hasExtension(String extension) { - if (extension == null || extension.length() == 0) { - return false; - } - return EXTENSION_TO_MIME_TYPE_MAP.containsKey(extension); - } - - /** - * Returns the registered extension for the given MIME type. Note that some - * MIME types map to multiple extensions. This call will return the most - * common extension for the given MIME type. - * @param mimeType A MIME type (i.e. text/plain) - * @return The extension for the given MIME type or null iff there is none. - */ - public static String guessExtensionFromMimeType(String mimeType) { - if (mimeType == null || mimeType.length() == 0) { - return null; - } - return MIME_TYPE_TO_EXTENSION_MAP.get(mimeType); - } -} diff --git a/src/main/java/libcore/net/http/Challenge.java b/src/main/java/libcore/net/http/Challenge.java deleted file mode 100644 index d373c0a..0000000 --- a/src/main/java/libcore/net/http/Challenge.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 libcore.net.http; - -/** - * An RFC 2617 challenge. - */ -final class Challenge { - final String scheme; - final String realm; - - Challenge(String scheme, String realm) { - this.scheme = scheme; - this.realm = 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(); - } -} diff --git a/src/main/java/libcore/net/http/HttpConnection.java b/src/main/java/libcore/net/http/HttpConnection.java deleted file mode 100644 index efdcf3d..0000000 --- a/src/main/java/libcore/net/http/HttpConnection.java +++ /dev/null @@ -1,473 +0,0 @@ -/* - * 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 libcore.net.http; - -import static com.squareup.okhttp.OkHttpConnection.HTTP_PROXY_AUTH; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import static java.net.HttpURLConnection.HTTP_OK; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.ProxySelector; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.URI; -import java.net.URL; -import java.net.UnknownHostException; -import java.security.cert.CertificateException; -import java.util.Arrays; -import java.util.List; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import libcore.io.IoUtils; -import libcore.net.spdy.SpdyConnection; -import libcore.util.Libcore; -import libcore.util.Objects; - -/** - * 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. Create an instance using - * the {@link Address} inner class. - * - * <p>Do not confuse this class with the misnamed {@code HttpURLConnection}, - * which isn't so much a connection as a single request/response pair. - */ -final class HttpConnection { - private static final byte[] NPN_PROTOCOLS = new byte[] { - 6, 's', 'p', 'd', 'y', '/', '2', - 8, 'h', 't', 't', 'p', '/', '1', '.', '1', - }; - private static final byte[] SPDY2 = new byte[] { - 's', 'p', 'd', 'y', '/', '2', - }; - private static final byte[] HTTP_11 = new byte[] { - 'h', 't', 't', 'p', '/', '1', '.', '1', - }; - - private final Address address; - private Socket socket; - private InputStream in; - private OutputStream out; - private boolean recycled = false; - private SpdyConnection spdyConnection; - - HttpConnection(Address address, Socket socket, InputStream in, OutputStream out) { - this.address = address; - this.socket = socket; - this.in = in; - this.out = out; - } - - /** - * The version this client will use. Either 0 for HTTP/1.0, or 1 for - * HTTP/1.1. Upon receiving a non-HTTP/1.1 response, this client - * automatically sets its version to HTTP/1.0. - */ - int httpMinorVersion = 1; // Assume HTTP/1.1 - - public static HttpConnection connect(URI uri, SSLSocketFactory sslSocketFactory, - HostnameVerifier hostnameVerifier, Proxy proxy, int connectTimeout, int readTimeout, - TunnelConfig tunnelConfig) throws IOException { - HttpConnection result = getConnection(uri, sslSocketFactory, hostnameVerifier, proxy, - connectTimeout, tunnelConfig); - result.socket.setSoTimeout(readTimeout); - return result; - } - - /** - * Selects a proxy and gets a connection with that proxy. - */ - private static HttpConnection getConnection(URI uri, SSLSocketFactory sslSocketFactory, - HostnameVerifier hostnameVerifier, Proxy proxy, int connectTimeout, - TunnelConfig tunnelConfig) throws IOException { - // Try an explicitly-specified proxy. - if (proxy != null) { - Address address = (proxy.type() == Proxy.Type.DIRECT) - ? new Address(uri, sslSocketFactory, hostnameVerifier) - : new Address(uri, sslSocketFactory, hostnameVerifier, proxy); - return getConnectionToAddress(address, connectTimeout, tunnelConfig); - } - - // Try each proxy provided by the ProxySelector until a connection succeeds. - ProxySelector selector = ProxySelector.getDefault(); - List<Proxy> proxyList = selector.select(uri); - if (proxyList != null) { - for (Proxy selectedProxy : proxyList) { - if (selectedProxy.type() == Proxy.Type.DIRECT) { - // the same as NO_PROXY - // TODO: if the selector recommends a direct connection, attempt that? - continue; - } - try { - return getConnectionToAddress(new Address(uri, sslSocketFactory, - hostnameVerifier, selectedProxy), connectTimeout, tunnelConfig); - } catch (IOException e) { - // failed to connect, tell it to the selector - selector.connectFailed(uri, selectedProxy.address(), e); - } - } - } - - // Try a direct connection. If this fails, this method will throw. - return getConnectionToAddress(new Address(uri, sslSocketFactory, hostnameVerifier), - connectTimeout, tunnelConfig); - } - - /** - * Selects a proxy and gets a connection with that proxy. - */ - private static HttpConnection getConnectionToAddress(Address address, int connectTimeout, - TunnelConfig tunnelConfig) throws IOException { - HttpConnection pooled = HttpConnectionPool.INSTANCE.get(address); - if (pooled != null) { - return pooled; - } - - Socket socket = connectSocket(address, connectTimeout); - HttpConnection result = new HttpConnection( - address, socket, socket.getInputStream(), socket.getOutputStream()); - - if (address.sslSocketFactory != null) { - // First try an SSL connection with compression and various TLS - // extensions enabled, if it fails (and its not unheard of that it - // will) fallback to a barebones connection. - try { - result.upgradeToTls(true, tunnelConfig); - } catch (IOException e) { - // If the problem was a CertificateException from the X509TrustManager, - // do not retry, we didn't have an abrupt server initiated exception. - if (e instanceof SSLHandshakeException - && e.getCause() instanceof CertificateException) { - throw e; - } - result.closeSocketAndStreams(); - - socket = connectSocket(address, connectTimeout); - result = new HttpConnection( - address, socket, socket.getInputStream(), socket.getOutputStream()); - result.upgradeToTls(false, tunnelConfig); - } - } - - /* - * Buffer the socket stream to permit efficient parsing of HTTP headers - * and chunk sizes. This also masks SSL InputStream's degenerate - * available() implementation. That way we can read the end of a chunked - * response without blocking and will recycle connections more reliably. - * http://code.google.com/p/android/issues/detail?id=38817 - */ - int bufferSize = 128; - result.in = new BufferedInputStream(result.in, bufferSize); - - return result; - } - - /** - * Try each of the host's addresses for best behavior in mixed IPv4/IPv6 - * environments. See http://b/2876927 - */ - private static Socket connectSocket(Address address, int connectTimeout) throws IOException { - Socket socket = null; - InetAddress[] addresses = InetAddress.getAllByName(address.socketHost); - for (int i = 0; i < addresses.length; i++) { - socket = (address.proxy != null && address.proxy.type() != Proxy.Type.HTTP) - ? new Socket(address.proxy) - : new Socket(); - try { - socket.connect( - new InetSocketAddress(addresses[i], address.socketPort), connectTimeout); - break; - } catch (IOException e) { - if (i == addresses.length - 1) { - throw e; - } - } - } - - if (socket == null) { - throw new IOException(); - } - - return socket; - } - - /** - * Create an {@code SSLSocket} and perform the TLS handshake and certificate - * validation. - * - * @param tlsTolerant If true, assume server can handle common TLS - * extensions and SSL deflate compression. If false, use an SSL3 only - * fallback mode without compression. - */ - private void upgradeToTls(boolean tlsTolerant, TunnelConfig tunnelConfig) throws IOException { - // Make an SSL Tunnel on the first message pair of each SSL + proxy connection. - if (address.requiresTunnel()) { - makeTunnel(tunnelConfig); - } - - // Create the wrapper over connected socket. - socket = address.sslSocketFactory.createSocket( - socket, address.uriHost, address.uriPort, true /* autoClose */); - SSLSocket sslSocket = (SSLSocket) socket; - Libcore.makeTlsTolerant(sslSocket, address.uriHost, tlsTolerant); - - if (tlsTolerant) { - Libcore.setNpnProtocols(sslSocket, NPN_PROTOCOLS); - } - - // Force handshake. This can throw! - sslSocket.startHandshake(); - - // Verify that the socket's certificates are acceptable for the target host. - if (!address.hostnameVerifier.verify(address.uriHost, sslSocket.getSession())) { - throw new IOException("Hostname '" + address.uriHost + "' was not verified"); - } - - out = sslSocket.getOutputStream(); - in = sslSocket.getInputStream(); - - byte[] selectedProtocol; - if (tlsTolerant - && (selectedProtocol = Libcore.getNpnSelectedProtocol(sslSocket)) != null) { - if (Arrays.equals(selectedProtocol, SPDY2)) { - spdyConnection = new SpdyConnection.Builder(true, in, out).build(); - HttpConnectionPool.INSTANCE.share(this); - } else if (!Arrays.equals(selectedProtocol, HTTP_11)) { - throw new IOException("Unexpected NPN transport " - + new String(selectedProtocol, "ISO-8859-1")); - } - } - } - - public void closeSocketAndStreams() { - IoUtils.closeQuietly(out); - IoUtils.closeQuietly(in); - IoUtils.closeQuietly(socket); - } - - public Socket getSocket() { - return socket; - } - - public Address getAddress() { - return address; - } - - /** - * Returns true if this connection has been used to satisfy an earlier - * HTTP request/response pair. - */ - public boolean isRecycled() { - return recycled; - } - - public void setRecycled() { - this.recycled = true; - } - - /** - * Returns true if this connection is eligible to be reused for another - * request/response pair. - */ - protected boolean isEligibleForRecycling() { - return !socket.isClosed() - && !socket.isInputShutdown() - && !socket.isOutputShutdown(); - } - - /** - * Returns the transport appropriate for this connection. - */ - public Transport newTransport(HttpEngine httpEngine) throws IOException { - if (spdyConnection != null) { - return new SpdyTransport(httpEngine, spdyConnection); - } else { - return 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 static final class TunnelConfig { - private final URL url; - private final String host; - private final String userAgent; - private final String proxyAuthorization; - - public TunnelConfig(URL url, String host, String userAgent, String proxyAuthorization) { - if (url == null || host == null || userAgent == null) throw new NullPointerException(); - this.url = url; - this.host = host; - this.userAgent = userAgent; - this.proxyAuthorization = proxyAuthorization; - } - - /** - * If we're establishing an HTTPS tunnel with CONNECT (RFC 2817 5.2), 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 " + url.getHost() + ":" - + Libcore.getEffectivePort(url) + " HTTP/1.1"); - - // Always set Host and User-Agent. - result.set("Host", host); - 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; - } - } - - /** - * 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(TunnelConfig tunnelConfig) throws IOException { - RawHeaders requestHeaders = tunnelConfig.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); - boolean credentialsFound = HttpAuthenticator.processAuthHeader(HTTP_PROXY_AUTH, - responseHeaders, requestHeaders, address.proxy, tunnelConfig.url); - if (credentialsFound) { - continue; - } else { - throw new IOException("Failed to authenticate with proxy"); - } - default: - throw new IOException("Unexpected response code for CONNECT: " - + responseHeaders.getResponseCode()); - } - } - } - - /** - * This address has two parts: the address we connect to directly and the - * origin address of the resource. These are the same unless a proxy is - * being used. It also includes the SSL socket factory so that a socket will - * not be reused if its SSL configuration is different. - */ - public static final class Address { - private final Proxy proxy; - private final String uriHost; - private final int uriPort; - private final String socketHost; - private final int socketPort; - private final SSLSocketFactory sslSocketFactory; - private final HostnameVerifier hostnameVerifier; - - public Address(URI uri, SSLSocketFactory sslSocketFactory, - HostnameVerifier hostnameVerifier) throws UnknownHostException { - this.proxy = null; - this.uriHost = uri.getHost(); - this.uriPort = Libcore.getEffectivePort(uri); - this.sslSocketFactory = sslSocketFactory; - this.hostnameVerifier = hostnameVerifier; - this.socketHost = uriHost; - this.socketPort = uriPort; - if (uriHost == null) { - throw new UnknownHostException(uri.toString()); - } - } - - public Address(URI uri, SSLSocketFactory sslSocketFactory, - HostnameVerifier hostnameVerifier, Proxy proxy) throws UnknownHostException { - this.proxy = proxy; - this.uriHost = uri.getHost(); - this.uriPort = Libcore.getEffectivePort(uri); - this.sslSocketFactory = sslSocketFactory; - this.hostnameVerifier = hostnameVerifier; - - SocketAddress proxyAddress = proxy.address(); - if (!(proxyAddress instanceof InetSocketAddress)) { - throw new IllegalArgumentException("Proxy.address() is not an InetSocketAddress: " - + proxyAddress.getClass()); - } - InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress; - this.socketHost = proxySocketAddress.getHostName(); - this.socketPort = proxySocketAddress.getPort(); - if (uriHost == null) { - throw new UnknownHostException(uri.toString()); - } - } - - public Proxy getProxy() { - return proxy; - } - - @Override public boolean equals(Object other) { - if (other instanceof Address) { - Address that = (Address) other; - return Objects.equal(this.proxy, that.proxy) - && this.uriHost.equals(that.uriHost) - && this.uriPort == that.uriPort - && Objects.equal(this.sslSocketFactory, that.sslSocketFactory) - && Objects.equal(this.hostnameVerifier, that.hostnameVerifier); - } - 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 + (proxy != null ? proxy.hashCode() : 0); - return result; - } - - /** - * 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 sslSocketFactory != null && proxy != null && proxy.type() == Proxy.Type.HTTP; - } - } -} diff --git a/src/main/java/libcore/net/http/HttpConnectionPool.java b/src/main/java/libcore/net/http/HttpConnectionPool.java deleted file mode 100644 index 0c2a188..0000000 --- a/src/main/java/libcore/net/http/HttpConnectionPool.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 libcore.net.http; - -import java.net.Socket; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import libcore.util.Libcore; - -/** - * A pool of HTTP and SPDY connections. This class exposes its tuning parameters - * as system properties: - * <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 connections to each host. - * Default is 5. - * </ul> - * - * <p>This class <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. - */ -final class HttpConnectionPool { - public static final HttpConnectionPool INSTANCE = new HttpConnectionPool(); - - private final int maxConnections; - private final HashMap<HttpConnection.Address, List<HttpConnection>> connectionPool - = new HashMap<HttpConnection.Address, List<HttpConnection>>(); - - private HttpConnectionPool() { - String keepAlive = System.getProperty("http.keepAlive"); - if (keepAlive != null && !Boolean.parseBoolean(keepAlive)) { - maxConnections = 0; - return; - } - - String maxConnectionsString = System.getProperty("http.maxConnections"); - this.maxConnections = maxConnectionsString != null - ? Integer.parseInt(maxConnectionsString) - : 5; - } - - public HttpConnection get(HttpConnection.Address address) { - // First try to reuse an existing HTTP connection. - synchronized (connectionPool) { - List<HttpConnection> connections = connectionPool.get(address); - while (connections != null) { - HttpConnection connection = connections.get(connections.size() - 1); - if (!connection.isSpdy()) { - connections.remove(connections.size() - 1); - } - if (connections.isEmpty()) { - connectionPool.remove(address); - connections = null; - } - if (connection.isEligibleForRecycling()) { - // Since Socket is recycled, re-tag before using - Socket socket = connection.getSocket(); - Libcore.tagSocket(socket); - return connection; - } - } - } - return null; - } - - /** - * Gives the HTTP/HTTPS connection to the pool. It is an error to use {@code - * connection} after calling this method. - */ - public void recycle(HttpConnection connection) { - if (connection.isSpdy()) { - throw new IllegalArgumentException(); // TODO: just 'return' here? - } - - Socket socket = connection.getSocket(); - try { - Libcore.untagSocket(socket); - } catch (SocketException e) { - // When unable to remove tagging, skip recycling and close - Libcore.logW("Unable to untagSocket(): " + e); - connection.closeSocketAndStreams(); - return; - } - - if (maxConnections > 0 && connection.isEligibleForRecycling()) { - HttpConnection.Address address = connection.getAddress(); - synchronized (connectionPool) { - List<HttpConnection> connections = connectionPool.get(address); - if (connections == null) { - connections = new ArrayList<HttpConnection>(); - connectionPool.put(address, connections); - } - if (connections.size() < maxConnections) { - connection.setRecycled(); - connections.add(connection); - return; // keep the connection open - } - } - } - - // don't close streams while holding a lock! - connection.closeSocketAndStreams(); - } - - /** - * Shares the SPDY connection with the pool. Callers to this method may - * continue to use {@code connection}. - */ - public void share(HttpConnection connection) { - if (!connection.isSpdy()) { - throw new IllegalArgumentException(); - } - if (maxConnections <= 0 || !connection.isEligibleForRecycling()) { - return; - } - HttpConnection.Address address = connection.getAddress(); - synchronized (connectionPool) { - List<HttpConnection> connections = connectionPool.get(address); - if (connections == null) { - connections = new ArrayList<HttpConnection>(1); - connections.add(connection); - connectionPool.put(address, connections); - } - } - } -} diff --git a/src/main/java/libcore/util/BasicLruCache.java b/src/main/java/libcore/util/BasicLruCache.java deleted file mode 100644 index b5f6fdf..0000000 --- a/src/main/java/libcore/util/BasicLruCache.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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 libcore.util; - -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * A minimal least-recently-used cache for libcore. Prefer {@code - * android.util.LruCache} where that is available. - */ -public class BasicLruCache<K, V> { - private final LinkedHashMap<K, V> map; - private final int maxSize; - - public BasicLruCache(int maxSize) { - if (maxSize <= 0) { - throw new IllegalArgumentException("maxSize <= 0"); - } - this.maxSize = maxSize; - this.map = new LinkedHashMap<K, V>(0, 0.75f, true); - } - - /** - * Returns the value for {@code key} if it exists in the cache or can be - * created by {@code #create}. If a value was returned, it is moved to the - * head of the queue. This returns null if a value is not cached and cannot - * be created. - */ - public synchronized final V get(K key) { - if (key == null) { - throw new NullPointerException(); - } - - V result = map.get(key); - if (result != null) { - return result; - } - - result = create(key); - - if (result != null) { - map.put(key, result); - trimToSize(maxSize); - } - return result; - } - - /** - * Caches {@code value} for {@code key}. The value is moved to the head of - * the queue. - * - * @return the previous value mapped by {@code key}. Although that entry is - * no longer cached, it has not been passed to {@link #entryEvicted}. - */ - public synchronized final V put(K key, V value) { - if (key == null || value == null) { - throw new NullPointerException(); - } - - V previous = map.put(key, value); - trimToSize(maxSize); - return previous; - } - - private void trimToSize(int maxSize) { - while (map.size() > maxSize) { - Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); - - K key = toEvict.getKey(); - V value = toEvict.getValue(); - map.remove(key); - - entryEvicted(key, value); - } - } - - /** - * Called for entries that have reached the tail of the least recently used - * queue and are be removed. The default implementation does nothing. - */ - protected void entryEvicted(K key, V value) { - } - - /** - * Called after a cache miss to compute a value for the corresponding key. - * Returns the computed value or null if no value can be computed. The - * default implementation returns null. - */ - protected V create(K key) { - return null; - } - - /** - * Returns a copy of the current contents of the cache, ordered from least - * recently accessed to most recently accessed. - */ - public synchronized final Map<K, V> snapshot() { - return new LinkedHashMap<K, V>(map); - } - - /** - * Clear the cache, calling {@link #entryEvicted} on each removed entry. - */ - public synchronized final void evictAll() { - trimToSize(0); - } -} diff --git a/src/main/java/libcore/util/Charsets.java b/src/main/java/libcore/util/Charsets.java deleted file mode 100644 index 95848ee..0000000 --- a/src/main/java/libcore/util/Charsets.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 libcore.util; - -import java.nio.charset.Charset; - -/** - * Provides convenient access to the most important built-in charsets. Saves a hash lookup and - * unnecessary handling of UnsupportedEncodingException at call sites, compared to using the - * charset's name. - * - * Also various special-case charset conversions (for performance). - * - * @hide internal use only - */ -public final class Charsets { - /** - * A cheap and type-safe constant for the ISO-8859-1 Charset. - */ - public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); - - /** - * A cheap and type-safe constant for the US-ASCII Charset. - */ - public static final Charset US_ASCII = Charset.forName("US-ASCII"); - - /** - * A cheap and type-safe constant for the UTF-8 Charset. - */ - public static final Charset UTF_8 = Charset.forName("UTF-8"); - - private Charsets() { - } -} diff --git a/src/main/java/libcore/util/CollectionUtils.java b/src/main/java/libcore/util/CollectionUtils.java deleted file mode 100644 index 45edf4f..0000000 --- a/src/main/java/libcore/util/CollectionUtils.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 libcore.util; - -import java.lang.ref.Reference; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; - -public final class CollectionUtils { - private CollectionUtils() { - } - - /** - * Returns an iterator over the values referenced by the elements of {@code - * iterable}. - * - * @param trim true to remove reference objects from the iterable after - * their referenced values have been cleared. - */ - public static <T> Iterable<T> dereferenceIterable( - final Iterable<? extends Reference<T>> iterable, final boolean trim) { - return new Iterable<T>() { - public Iterator<T> iterator() { - return new Iterator<T>() { - private final Iterator<? extends Reference<T>> delegate = iterable.iterator(); - private boolean removeIsOkay; - private T next; - - private void computeNext() { - removeIsOkay = false; - while (next == null && delegate.hasNext()) { - next = delegate.next().get(); - if (trim && next == null) { - delegate.remove(); - } - } - } - - @Override public boolean hasNext() { - computeNext(); - return next != null; - } - - @Override public T next() { - if (!hasNext()) { - throw new IllegalStateException(); - } - T result = next; - removeIsOkay = true; - next = null; - return result; - } - - public void remove() { - if (!removeIsOkay) { - throw new IllegalStateException(); - } - delegate.remove(); - } - }; - } - }; - } - - /** - * Sorts and removes duplicate elements from {@code list}. This method does - * not use {@link Object#equals}: only the comparator defines equality. - */ - public static <T> void removeDuplicates(List<T> list, Comparator<? super T> comparator) { - Collections.sort(list, comparator); - int j = 1; - for (int i = 1; i < list.size(); i++) { - if (comparator.compare(list.get(j - 1), list.get(i)) != 0) { - T object = list.get(i); - list.set(j++, object); - } - } - if (j < list.size()) { - list.subList(j, list.size()).clear(); - } - } -} diff --git a/src/main/java/libcore/util/DefaultFileNameMap.java b/src/main/java/libcore/util/DefaultFileNameMap.java deleted file mode 100644 index e817a72..0000000 --- a/src/main/java/libcore/util/DefaultFileNameMap.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 libcore.util; - -import java.net.FileNameMap; -import java.util.Locale; -import libcore.net.MimeUtils; - -/** - * Implements {@link java.net.FileNameMap} in terms of {@link libcore.net.MimeUtils}. - */ -class DefaultFileNameMap implements FileNameMap { - public String getContentTypeFor(String filename) { - if (filename.endsWith("/")) { - // a directory, return html - return MimeUtils.guessMimeTypeFromExtension("html"); - } - int lastCharInExtension = filename.lastIndexOf('#'); - if (lastCharInExtension < 0) { - lastCharInExtension = filename.length(); - } - int firstCharInExtension = filename.lastIndexOf('.') + 1; - String ext = ""; - if (firstCharInExtension > filename.lastIndexOf('/')) { - ext = filename.substring(firstCharInExtension, lastCharInExtension); - } - return MimeUtils.guessMimeTypeFromExtension(ext.toLowerCase(Locale.US)); - } -} diff --git a/src/main/java/libcore/util/EmptyArray.java b/src/main/java/libcore/util/EmptyArray.java deleted file mode 100644 index 0f919c5..0000000 --- a/src/main/java/libcore/util/EmptyArray.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 libcore.util; - -public final class EmptyArray { - private EmptyArray() { - } - - public static final boolean[] BOOLEAN = new boolean[0]; - public static final byte[] BYTE = new byte[0]; - public static final char[] CHAR = new char[0]; - public static final double[] DOUBLE = new double[0]; - public static final int[] INT = new int[0]; - - public static final Class<?>[] CLASS = new Class[0]; - public static final Object[] OBJECT = new Object[0]; - public static final String[] STRING = new String[0]; - public static final Throwable[] THROWABLE = new Throwable[0]; - public static final StackTraceElement[] STACK_TRACE_ELEMENT = new StackTraceElement[0]; -} diff --git a/src/main/java/libcore/util/IntegralToString.java b/src/main/java/libcore/util/IntegralToString.java deleted file mode 100644 index 1b66e51..0000000 --- a/src/main/java/libcore/util/IntegralToString.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 libcore.util; - -/** - * Converts integral types to strings. This class is public but hidden so that it can also be - * used by java.util.Formatter to speed up %d. This class is in java.lang so that it can take - * advantage of the package-private String constructor. - * - * The most important methods are appendInt/appendLong and intToString(int)/longToString(int). - * The former are used in the implementation of StringBuilder, StringBuffer, and Formatter, while - * the latter are used by Integer.toString and Long.toString. - * - * The append methods take AbstractStringBuilder rather than Appendable because the latter requires - * CharSequences, while we only have raw char[]s. Since much of the savings come from not creating - * any garbage, we can't afford temporary CharSequence instances. - * - * One day the performance advantage of the binary/hex/octal specializations will be small enough - * that we can lose the duplication, but until then this class offers the full set. - * - * @hide - */ -public final class IntegralToString { - /** - * The digits for every supported radix. - */ - private static final char[] DIGITS = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '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' - }; - - private static final char[] UPPER_CASE_DIGITS = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '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' - }; - - private IntegralToString() { - } - - public static String bytesToHexString(byte[] bytes, boolean upperCase) { - char[] digits = upperCase ? UPPER_CASE_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); - } -} diff --git a/src/main/java/libcore/util/Libcore.java b/src/main/java/libcore/util/Libcore.java deleted file mode 100644 index 7053187..0000000 --- a/src/main/java/libcore/util/Libcore.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * 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 libcore.util; - -import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.Socket; -import java.net.SocketException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.List; -import javax.net.ssl.SSLSocket; -import org.eclipse.jetty.npn.NextProtoNego; - -/** - * APIs for interacting with Android's core library. This mostly emulates the - * Android core library for interoperability with other runtimes. - */ -public final class Libcore { - - private Libcore() { - } - - private static boolean useAndroidTlsApis; - private static Class<?> openSslSocketClass; - private static Method setUseSessionTickets; - private static Method setHostname; - private static boolean android23TlsOptionsAvailable; - private static Method setNpnProtocols; - private static Method getNpnSelectedProtocol; - private static boolean android41TlsOptionsAvailable; - - static { - try { - openSslSocketClass = Class.forName( - "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl"); - useAndroidTlsApis = true; - setUseSessionTickets = openSslSocketClass.getMethod( - "setUseSessionTickets", boolean.class); - setHostname = openSslSocketClass.getMethod("setHostname", String.class); - android23TlsOptionsAvailable = true; - setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class); - getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol"); - android41TlsOptionsAvailable = true; - } catch (ClassNotFoundException ignored) { - // This isn't an Android runtime. - } catch (NoSuchMethodException ignored) { - // This Android runtime is missing some optional TLS options. - } - } - - public static void makeTlsTolerant(SSLSocket socket, String uriHost, boolean tlsTolerant) { - if (!tlsTolerant) { - socket.setEnabledProtocols(new String[] {"SSLv3"}); - return; - } - - if (android23TlsOptionsAvailable && openSslSocketClass.isInstance(socket)) { - // This is Android: use reflection on OpenSslSocketImpl. - try { - setUseSessionTickets.invoke(socket, true); - setHostname.invoke(socket, uriHost); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - } - } - - /** - * Returns the negotiated protocol, or null if no protocol was negotiated. - */ - public static byte[] getNpnSelectedProtocol(SSLSocket socket) { - if (useAndroidTlsApis) { - // This is Android: use reflection on OpenSslSocketImpl. - if (android41TlsOptionsAvailable && openSslSocketClass.isInstance(socket)) { - try { - return (byte[]) getNpnSelectedProtocol.invoke(socket); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - } - return null; - } else { - // This is OpenJDK: use JettyNpnProvider. - JettyNpnProvider provider = (JettyNpnProvider) NextProtoNego.get(socket); - if (!provider.unsupported && provider.selected == null) { - throw new IllegalStateException( - "No callback received. Is NPN configured properly?"); - } - try { - return provider.unsupported - ? null - : provider.selected.getBytes("US-ASCII"); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); - } - } - } - - public static void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) { - if (useAndroidTlsApis) { - // This is Android: use reflection on OpenSslSocketImpl. - if (android41TlsOptionsAvailable && openSslSocketClass.isInstance(socket)) { - try { - setNpnProtocols.invoke(socket, new Object[] {npnProtocols}); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - } - } else { - // This is OpenJDK: use JettyNpnProvider. - try { - List<String> strings = new ArrayList<String>(); - for (int i = 0; i < npnProtocols.length;) { - int length = npnProtocols[i++]; - strings.add(new String(npnProtocols, i, length, "US-ASCII")); - i += length; - } - JettyNpnProvider provider = new JettyNpnProvider(); - provider.protocols = strings; - NextProtoNego.put(socket, provider); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); - } - } - } - - private static class JettyNpnProvider - implements NextProtoNego.ClientProvider, NextProtoNego.ServerProvider { - List<String> protocols; - boolean unsupported; - String selected; - - @Override public boolean supports() { - return true; - } - @Override public List<String> protocols() { - return protocols; - } - @Override public void unsupported() { - this.unsupported = true; - } - @Override public void protocolSelected(String selected) { - this.selected = selected; - } - @Override public String selectProtocol(List<String> strings) { - // TODO: use OpenSSL's algorithm which uses 2 lists - System.out.println("CLIENT PROTOCOLS: " + protocols + " SERVER PROTOCOLS: " + strings); - String selected = protocols.get(0); - protocolSelected(selected); - return selected; - } - } - - public static void deleteIfExists(File file) throws IOException { - // okhttp-changed: was Libcore.os.remove() in a try/catch block - file.delete(); - } - - public static void logW(String warning) { - // okhttp-changed: was System.logw() - System.out.println(warning); - } - - public static int getEffectivePort(URI uri) { - return getEffectivePort(uri.getScheme(), uri.getPort()); - } - - public static int getEffectivePort(URL url) { - return getEffectivePort(url.getProtocol(), url.getPort()); - } - - private static int getEffectivePort(String scheme, int specifiedPort) { - return specifiedPort != -1 - ? specifiedPort - : getDefaultPort(scheme); - } - - public static int getDefaultPort(String scheme) { - if ("http".equalsIgnoreCase(scheme)) { - return 80; - } else if ("https".equalsIgnoreCase(scheme)) { - return 443; - } else { - return -1; - } - } - - public static void checkOffsetAndCount(int arrayLength, int offset, int count) { - if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) { - throw new ArrayIndexOutOfBoundsException(); - } - } - - public static void tagSocket(Socket socket) { - } - - public static void untagSocket(Socket socket) throws SocketException { - } - - public static URI toUriLenient(URL url) throws URISyntaxException { - return url.toURI(); // this isn't as good as the built-in toUriLenient - } - - public static void pokeInt(byte[] dst, int offset, int value, ByteOrder order) { - if (order == ByteOrder.BIG_ENDIAN) { - dst[offset++] = (byte) ((value >> 24) & 0xff); - dst[offset++] = (byte) ((value >> 16) & 0xff); - dst[offset++] = (byte) ((value >> 8) & 0xff); - dst[offset ] = (byte) ((value >> 0) & 0xff); - } else { - dst[offset++] = (byte) ((value >> 0) & 0xff); - dst[offset++] = (byte) ((value >> 8) & 0xff); - dst[offset++] = (byte) ((value >> 16) & 0xff); - dst[offset ] = (byte) ((value >> 24) & 0xff); - } - } -} diff --git a/src/main/java/libcore/util/MutableByte.java b/src/main/java/libcore/util/MutableByte.java deleted file mode 100644 index 13f780b..0000000 --- a/src/main/java/libcore/util/MutableByte.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 libcore.util; - -public final class MutableByte { - public byte value; - - public MutableByte(byte value) { - this.value = value; - } -} diff --git a/src/main/java/libcore/util/MutableChar.java b/src/main/java/libcore/util/MutableChar.java deleted file mode 100644 index 1cafc3c..0000000 --- a/src/main/java/libcore/util/MutableChar.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 libcore.util; - -public final class MutableChar { - public char value; - - public MutableChar(char value) { - this.value = value; - } -} diff --git a/src/main/java/libcore/util/MutableDouble.java b/src/main/java/libcore/util/MutableDouble.java deleted file mode 100644 index 4473ae6..0000000 --- a/src/main/java/libcore/util/MutableDouble.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 libcore.util; - -public final class MutableDouble { - public double value; - - public MutableDouble(double value) { - this.value = value; - } -} diff --git a/src/main/java/libcore/util/MutableFloat.java b/src/main/java/libcore/util/MutableFloat.java deleted file mode 100644 index f81fba5..0000000 --- a/src/main/java/libcore/util/MutableFloat.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 libcore.util; - -public final class MutableFloat { - public float value; - - public MutableFloat(float value) { - this.value = value; - } -} diff --git a/src/main/java/libcore/util/MutableInt.java b/src/main/java/libcore/util/MutableInt.java deleted file mode 100644 index c8feb3a..0000000 --- a/src/main/java/libcore/util/MutableInt.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 libcore.util; - -public final class MutableInt { - public int value; - - public MutableInt(int value) { - this.value = value; - } -} diff --git a/src/main/java/libcore/util/MutableLong.java b/src/main/java/libcore/util/MutableLong.java deleted file mode 100644 index ad9b78e..0000000 --- a/src/main/java/libcore/util/MutableLong.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 libcore.util; - -public final class MutableLong { - public long value; - - public MutableLong(long value) { - this.value = value; - } -} diff --git a/src/main/java/libcore/util/MutableShort.java b/src/main/java/libcore/util/MutableShort.java deleted file mode 100644 index 78b4c33..0000000 --- a/src/main/java/libcore/util/MutableShort.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 libcore.util; - -public final class MutableShort { - public short value; - - public MutableShort(short value) { - this.value = value; - } -} diff --git a/src/main/java/libcore/util/Objects.java b/src/main/java/libcore/util/Objects.java deleted file mode 100644 index 050888d..0000000 --- a/src/main/java/libcore/util/Objects.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 libcore.util; - -public final class Objects { - private Objects() { - } - - /** - * Returns true if two possibly-null objects are equal. - */ - public static boolean equal(Object a, Object b) { - return a == b || (a != null && a.equals(b)); - } - - public static int hashCode(Object o) { - return (o == null) ? 0 : o.hashCode(); - } -} diff --git a/src/main/java/libcore/util/SneakyThrow.java b/src/main/java/libcore/util/SneakyThrow.java deleted file mode 100644 index f5c077c..0000000 --- a/src/main/java/libcore/util/SneakyThrow.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 libcore.util; - -/** - * Exploits a weakness in the runtime to throw an arbitrary throwable without - * the traditional declaration. <strong>This is a dangerous API that should be - * used with great caution.</strong> Typically this is useful when rethrowing - * throwables that are of a known range of types. - * - * <p>The following code must enumerate several types to rethrow: - * <pre> - * public void close() throws IOException { - * Throwable thrown = null; - * ... - * - * if (thrown != null) { - * if (thrown instanceof IOException) { - * throw (IOException) thrown; - * } else if (thrown instanceof RuntimeException) { - * throw (RuntimeException) thrown; - * } else if (thrown instanceof Error) { - * throw (Error) thrown; - * } else { - * throw new AssertionError(); - * } - * } - * }</pre> - * With SneakyThrow, rethrowing is easier: - * <pre> - * public void close() throws IOException { - * Throwable thrown = null; - * ... - * - * if (thrown != null) { - * SneakyThrow.sneakyThrow(thrown); - * } - * }</pre> - */ -public final class SneakyThrow { - private SneakyThrow() { - } - - public static void sneakyThrow(Throwable t) { - SneakyThrow.<Error>sneakyThrow2(t); - } - - /** - * Exploits unsafety to throw an exception that the compiler wouldn't permit - * but that the runtime doesn't check. See Java Puzzlers #43. - */ - @SuppressWarnings("unchecked") - private static <T extends Throwable> void sneakyThrow2(Throwable t) throws T { - throw (T) t; - } -} diff --git a/src/test/java/libcore/io/DiskLruCacheTest.java b/src/test/java/com/squareup/okhttp/internal/DiskLruCacheTest.java index 87b3e8e..160ec78 100644 --- a/src/test/java/libcore/io/DiskLruCacheTest.java +++ b/src/test/java/com/squareup/okhttp/internal/DiskLruCacheTest.java @@ -14,8 +14,12 @@ * limitations under the License. */ -package libcore.io; +package com.squareup.okhttp.internal; +import com.squareup.okhttp.internal.DiskLruCache; +import static com.squareup.okhttp.internal.DiskLruCache.JOURNAL_FILE; +import static com.squareup.okhttp.internal.DiskLruCache.MAGIC; +import static com.squareup.okhttp.internal.DiskLruCache.VERSION_1; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; @@ -27,22 +31,24 @@ import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import junit.framework.TestCase; - -import static libcore.io.DiskLruCache.JOURNAL_FILE; -import static libcore.io.DiskLruCache.MAGIC; -import static libcore.io.DiskLruCache.VERSION_1; - -public final class DiskLruCacheTest extends TestCase { +import org.junit.After; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.Test; + +public final class DiskLruCacheTest { private final int appVersion = 100; private String javaTmpDir; private File cacheDir; private File journalFile; private DiskLruCache cache; -// private final MockOs mockOs = new MockOs(); - @Override public void setUp() throws Exception { - super.setUp(); + @Before public void setUp() throws Exception { javaTmpDir = System.getProperty("java.io.tmpdir"); cacheDir = new File(javaTmpDir, "DiskLruCacheTest"); cacheDir.mkdir(); @@ -51,21 +57,18 @@ public final class DiskLruCacheTest extends TestCase { file.delete(); } cache = DiskLruCache.open(cacheDir, appVersion, 2, Integer.MAX_VALUE); -// mockOs.install(); } - @Override protected void tearDown() throws Exception { -// mockOs.uninstall(); + @After public void tearDown() throws Exception { cache.close(); - super.tearDown(); } - public void testEmptyCache() throws Exception { + @Test public void emptyCache() throws Exception { cache.close(); assertJournalEquals(); } - public void testWriteAndReadEntry() throws Exception { + @Test public void writeAndReadEntry() throws Exception { DiskLruCache.Editor creator = cache.edit("k1"); creator.set(0, "ABC"); creator.set(1, "DE"); @@ -80,7 +83,7 @@ public final class DiskLruCacheTest extends TestCase { assertEquals("DE", snapshot.getString(1)); } - public void testReadAndWriteEntryAcrossCacheOpenAndClose() throws Exception { + @Test public void readAndWriteEntryAcrossCacheOpenAndClose() throws Exception { DiskLruCache.Editor creator = cache.edit("k1"); creator.set(0, "A"); creator.set(1, "B"); @@ -94,7 +97,7 @@ public final class DiskLruCacheTest extends TestCase { snapshot.close(); } - public void testJournalWithEditAndPublish() throws Exception { + @Test public void journalWithEditAndPublish() throws Exception { DiskLruCache.Editor creator = cache.edit("k1"); assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed creator.set(0, "AB"); @@ -104,7 +107,7 @@ public final class DiskLruCacheTest extends TestCase { assertJournalEquals("DIRTY k1", "CLEAN k1 2 1"); } - public void testRevertedNewFileIsRemoveInJournal() throws Exception { + @Test public void revertedNewFileIsRemoveInJournal() throws Exception { DiskLruCache.Editor creator = cache.edit("k1"); assertJournalEquals("DIRTY k1"); // DIRTY must always be flushed creator.set(0, "AB"); @@ -114,13 +117,13 @@ public final class DiskLruCacheTest extends TestCase { assertJournalEquals("DIRTY k1", "REMOVE k1"); } - public void testUnterminatedEditIsRevertedOnClose() throws Exception { + @Test public void unterminatedEditIsRevertedOnClose() throws Exception { cache.edit("k1"); cache.close(); assertJournalEquals("DIRTY k1", "REMOVE k1"); } - public void testJournalDoesNotIncludeReadOfYetUnpublishedValue() throws Exception { + @Test public void journalDoesNotIncludeReadOfYetUnpublishedValue() throws Exception { DiskLruCache.Editor creator = cache.edit("k1"); assertNull(cache.get("k1")); creator.set(0, "A"); @@ -130,7 +133,7 @@ public final class DiskLruCacheTest extends TestCase { assertJournalEquals("DIRTY k1", "CLEAN k1 1 2"); } - public void testJournalWithEditAndPublishAndRead() throws Exception { + @Test public void journalWithEditAndPublishAndRead() throws Exception { DiskLruCache.Editor k1Creator = cache.edit("k1"); k1Creator.set(0, "AB"); k1Creator.set(1, "C"); @@ -147,7 +150,7 @@ public final class DiskLruCacheTest extends TestCase { "READ k1"); } - public void testCannotOperateOnEditAfterPublish() throws Exception { + @Test public void cannotOperateOnEditAfterPublish() throws Exception { DiskLruCache.Editor editor = cache.edit("k1"); editor.set(0, "A"); editor.set(1, "B"); @@ -155,7 +158,7 @@ public final class DiskLruCacheTest extends TestCase { assertInoperable(editor); } - public void testCannotOperateOnEditAfterRevert() throws Exception { + @Test public void cannotOperateOnEditAfterRevert() throws Exception { DiskLruCache.Editor editor = cache.edit("k1"); editor.set(0, "A"); editor.set(1, "B"); @@ -163,7 +166,7 @@ public final class DiskLruCacheTest extends TestCase { assertInoperable(editor); } - public void testExplicitRemoveAppliedToDiskImmediately() throws Exception { + @Test public void explicitRemoveAppliedToDiskImmediately() throws Exception { DiskLruCache.Editor editor = cache.edit("k1"); editor.set(0, "ABC"); editor.set(1, "B"); @@ -178,7 +181,7 @@ public final class DiskLruCacheTest extends TestCase { * Each read sees a snapshot of the file at the time read was called. * This means that two reads of the same key can see different data. */ - public void testReadAndWriteOverlapsMaintainConsistency() throws Exception { + @Test public void readAndWriteOverlapsMaintainConsistency() throws Exception { DiskLruCache.Editor v1Creator = cache.edit("k1"); v1Creator.set(0, "AAaa"); v1Creator.set(1, "BBbb"); @@ -205,7 +208,7 @@ public final class DiskLruCacheTest extends TestCase { snapshot1.close(); } - public void testOpenWithDirtyKeyDeletesAllFilesForThatKey() throws Exception { + @Test public void openWithDirtyKeyDeletesAllFilesForThatKey() throws Exception { cache.close(); File cleanFile0 = getCleanFile("k1", 0); File cleanFile1 = getCleanFile("k1", 1); @@ -224,7 +227,7 @@ public final class DiskLruCacheTest extends TestCase { assertNull(cache.get("k1")); } - public void testOpenWithInvalidVersionClearsDirectory() throws Exception { + @Test public void openWithInvalidVersionClearsDirectory() throws Exception { cache.close(); generateSomeGarbageFiles(); createJournalWithHeader(MAGIC, "0", "100", "2", ""); @@ -232,7 +235,7 @@ public final class DiskLruCacheTest extends TestCase { assertGarbageFilesAllDeleted(); } - public void testOpenWithInvalidAppVersionClearsDirectory() throws Exception { + @Test public void openWithInvalidAppVersionClearsDirectory() throws Exception { cache.close(); generateSomeGarbageFiles(); createJournalWithHeader(MAGIC, "1", "101", "2", ""); @@ -240,7 +243,7 @@ public final class DiskLruCacheTest extends TestCase { assertGarbageFilesAllDeleted(); } - public void testOpenWithInvalidValueCountClearsDirectory() throws Exception { + @Test public void openWithInvalidValueCountClearsDirectory() throws Exception { cache.close(); generateSomeGarbageFiles(); createJournalWithHeader(MAGIC, "1", "100", "1", ""); @@ -248,7 +251,7 @@ public final class DiskLruCacheTest extends TestCase { assertGarbageFilesAllDeleted(); } - public void testOpenWithInvalidBlankLineClearsDirectory() throws Exception { + @Test public void openWithInvalidBlankLineClearsDirectory() throws Exception { cache.close(); generateSomeGarbageFiles(); createJournalWithHeader(MAGIC, "1", "100", "2", "x"); @@ -256,7 +259,7 @@ public final class DiskLruCacheTest extends TestCase { assertGarbageFilesAllDeleted(); } - public void testOpenWithInvalidJournalLineClearsDirectory() throws Exception { + @Test public void openWithInvalidJournalLineClearsDirectory() throws Exception { cache.close(); generateSomeGarbageFiles(); createJournal("CLEAN k1 1 1", "BOGUS"); @@ -265,7 +268,7 @@ public final class DiskLruCacheTest extends TestCase { assertNull(cache.get("k1")); } - public void testOpenWithInvalidFileSizeClearsDirectory() throws Exception { + @Test public void openWithInvalidFileSizeClearsDirectory() throws Exception { cache.close(); generateSomeGarbageFiles(); createJournal("CLEAN k1 0000x001 1"); @@ -274,7 +277,7 @@ public final class DiskLruCacheTest extends TestCase { assertNull(cache.get("k1")); } - public void testOpenWithTruncatedLineDiscardsThatLine() throws Exception { + @Test public void openWithTruncatedLineDiscardsThatLine() throws Exception { cache.close(); writeFile(getCleanFile("k1", 0), "A"); writeFile(getCleanFile("k1", 1), "B"); @@ -285,7 +288,7 @@ public final class DiskLruCacheTest extends TestCase { assertNull(cache.get("k1")); } - public void testOpenWithTooManyFileSizesClearsDirectory() throws Exception { + @Test public void openWithTooManyFileSizesClearsDirectory() throws Exception { cache.close(); generateSomeGarbageFiles(); createJournal("CLEAN k1 1 1 1"); @@ -294,7 +297,7 @@ public final class DiskLruCacheTest extends TestCase { assertNull(cache.get("k1")); } - public void testKeyWithSpaceNotPermitted() throws Exception { + @Test public void keyWithSpaceNotPermitted() throws Exception { try { cache.edit("my key"); fail(); @@ -302,7 +305,7 @@ public final class DiskLruCacheTest extends TestCase { } } - public void testKeyWithNewlineNotPermitted() throws Exception { + @Test public void keyWithNewlineNotPermitted() throws Exception { try { cache.edit("my\nkey"); fail(); @@ -310,7 +313,7 @@ public final class DiskLruCacheTest extends TestCase { } } - public void testKeyWithCarriageReturnNotPermitted() throws Exception { + @Test public void keyWithCarriageReturnNotPermitted() throws Exception { try { cache.edit("my\rkey"); fail(); @@ -318,7 +321,7 @@ public final class DiskLruCacheTest extends TestCase { } } - public void testNullKeyThrows() throws Exception { + @Test public void nullKeyThrows() throws Exception { try { cache.edit(null); fail(); @@ -326,7 +329,7 @@ public final class DiskLruCacheTest extends TestCase { } } - public void testCreateNewEntryWithTooFewValuesFails() throws Exception { + @Test public void createNewEntryWithTooFewValuesFails() throws Exception { DiskLruCache.Editor creator = cache.edit("k1"); creator.set(1, "A"); try { @@ -347,7 +350,7 @@ public final class DiskLruCacheTest extends TestCase { creator2.commit(); } - public void testCreateNewEntryWithMissingFileAborts() throws Exception { + @Test public void createNewEntryWithMissingFileAborts() throws Exception { DiskLruCache.Editor creator = cache.edit("k1"); creator.set(0, "A"); creator.set(1, "A"); @@ -369,7 +372,7 @@ public final class DiskLruCacheTest extends TestCase { creator2.commit(); } - public void testRevertWithTooFewValues() throws Exception { + @Test public void revertWithTooFewValues() throws Exception { DiskLruCache.Editor creator = cache.edit("k1"); creator.set(1, "A"); creator.abort(); @@ -380,7 +383,7 @@ public final class DiskLruCacheTest extends TestCase { assertNull(cache.get("k1")); } - public void testUpdateExistingEntryWithTooFewValuesReusesPreviousValues() throws Exception { + @Test public void updateExistingEntryWithTooFewValuesReusesPreviousValues() throws Exception { DiskLruCache.Editor creator = cache.edit("k1"); creator.set(0, "A"); creator.set(1, "B"); @@ -396,7 +399,7 @@ public final class DiskLruCacheTest extends TestCase { snapshot.close(); } - public void testEvictOnInsert() throws Exception { + @Test public void evictOnInsert() throws Exception { cache.close(); cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); @@ -432,7 +435,7 @@ public final class DiskLruCacheTest extends TestCase { assertValue("E", "eeee", "eeee"); } - public void testEvictOnUpdate() throws Exception { + @Test public void evictOnUpdate() throws Exception { cache.close(); cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); @@ -450,7 +453,7 @@ public final class DiskLruCacheTest extends TestCase { assertValue("C", "c", "cc"); } - public void testEvictionHonorsLruFromCurrentSession() throws Exception { + @Test public void evictionHonorsLruFromCurrentSession() throws Exception { cache.close(); cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); set("A", "a", "a"); @@ -474,7 +477,7 @@ public final class DiskLruCacheTest extends TestCase { assertValue("F", "f", "f"); } - public void testEvictionHonorsLruFromPreviousSession() throws Exception { + @Test public void evictionHonorsLruFromPreviousSession() throws Exception { set("A", "a", "a"); set("B", "b", "b"); set("C", "c", "c"); @@ -498,7 +501,7 @@ public final class DiskLruCacheTest extends TestCase { assertValue("G", "g", "g"); } - public void testCacheSingleEntryOfSizeGreaterThanMaxSize() throws Exception { + @Test public void cacheSingleEntryOfSizeGreaterThanMaxSize() throws Exception { cache.close(); cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); set("A", "aaaaa", "aaaaaa"); // size=11 @@ -506,7 +509,7 @@ public final class DiskLruCacheTest extends TestCase { assertAbsent("A"); } - public void testCacheSingleValueOfSizeGreaterThanMaxSize() throws Exception { + @Test public void cacheSingleValueOfSizeGreaterThanMaxSize() throws Exception { cache.close(); cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); set("A", "aaaaaaaaaaa", "a"); // size=12 @@ -514,7 +517,7 @@ public final class DiskLruCacheTest extends TestCase { assertAbsent("A"); } - public void testConstructorDoesNotAllowZeroCacheSize() throws Exception { + @Test public void constructorDoesNotAllowZeroCacheSize() throws Exception { try { DiskLruCache.open(cacheDir, appVersion, 2, 0); fail(); @@ -522,7 +525,7 @@ public final class DiskLruCacheTest extends TestCase { } } - public void testConstructorDoesNotAllowZeroValuesPerEntry() throws Exception { + @Test public void constructorDoesNotAllowZeroValuesPerEntry() throws Exception { try { DiskLruCache.open(cacheDir, appVersion, 0, 10); fail(); @@ -530,18 +533,18 @@ public final class DiskLruCacheTest extends TestCase { } } - public void testRemoveAbsentElement() throws Exception { + @Test public void removeAbsentElement() throws Exception { cache.remove("A"); } - public void testReadingTheSameStreamMultipleTimes() throws Exception { + @Test public void readingTheSameStreamMultipleTimes() throws Exception { set("A", "a", "b"); DiskLruCache.Snapshot snapshot = cache.get("A"); assertSame(snapshot.getInputStream(0), snapshot.getInputStream(0)); snapshot.close(); } - public void testRebuildJournalOnRepeatedReads() throws Exception { + @Test public void rebuildJournalOnRepeatedReads() throws Exception { set("A", "a", "a"); set("B", "b", "b"); long lastJournalLength = 0; @@ -558,7 +561,7 @@ public final class DiskLruCacheTest extends TestCase { } } - public void testRebuildJournalOnRepeatedEdits() throws Exception { + @Test public void rebuildJournalOnRepeatedEdits() throws Exception { long lastJournalLength = 0; while (true) { long journalLength = journalFile.length(); @@ -577,7 +580,7 @@ public final class DiskLruCacheTest extends TestCase { assertValue("B", "b", "b"); } - public void testOpenCreatesDirectoryIfNecessary() throws Exception { + @Test public void openCreatesDirectoryIfNecessary() throws Exception { cache.close(); File dir = new File(javaTmpDir, "testOpenCreatesDirectoryIfNecessary"); cache = DiskLruCache.open(dir, appVersion, 2, Integer.MAX_VALUE); @@ -587,42 +590,13 @@ public final class DiskLruCacheTest extends TestCase { assertTrue(new File(dir, "journal").exists()); } - public void testFileDeletedExternally() throws Exception { + @Test public void fileDeletedExternally() throws Exception { set("A", "a", "a"); getCleanFile("A", 1).delete(); assertNull(cache.get("A")); } -// public void testFileBecomesInaccessibleDuringReadResultsInIoException() throws Exception { -// set("A", "aaaaa", "a"); -// DiskLruCache.Snapshot snapshot = cache.get("A"); -// InputStream in = snapshot.getInputStream(0); -// assertEquals('a', in.read()); -// mockOs.enqueueFault("read"); -// try { -// in.read(); -// fail(); -// } catch (IOException expected) { -// } -// snapshot.close(); -// } - -// public void testFileBecomesInaccessibleDuringWriteIsSilentlyDiscarded() throws Exception { -// set("A", "a", "a"); -// DiskLruCache.Editor editor = cache.edit("A"); -// OutputStream out0 = editor.newOutputStream(0); -// out0.write('b'); -// out0.close(); -// OutputStream out1 = editor.newOutputStream(1); -// out1.write('c'); -// mockOs.enqueueFault("write"); -// out1.write('c'); // this doesn't throw... -// out1.close(); -// editor.commit(); // ... but this will abort -// assertAbsent("A"); -// } - - public void testEditSameVersion() throws Exception { + @Test public void editSameVersion() throws Exception { set("A", "a", "a"); DiskLruCache.Snapshot snapshot = cache.get("A"); DiskLruCache.Editor editor = snapshot.edit(); @@ -631,7 +605,7 @@ public final class DiskLruCacheTest extends TestCase { assertValue("A", "a", "a2"); } - public void testEditSnapshotAfterChangeAborted() throws Exception { + @Test public void editSnapshotAfterChangeAborted() throws Exception { set("A", "a", "a"); DiskLruCache.Snapshot snapshot = cache.get("A"); DiskLruCache.Editor toAbort = snapshot.edit(); @@ -643,7 +617,7 @@ public final class DiskLruCacheTest extends TestCase { assertValue("A", "a", "a2"); } - public void testEditSnapshotAfterChangeCommitted() throws Exception { + @Test public void editSnapshotAfterChangeCommitted() throws Exception { set("A", "a", "a"); DiskLruCache.Snapshot snapshot = cache.get("A"); DiskLruCache.Editor toAbort = snapshot.edit(); @@ -652,7 +626,7 @@ public final class DiskLruCacheTest extends TestCase { assertNull(snapshot.edit()); } - public void testEditSinceEvicted() throws Exception { + @Test public void editSinceEvicted() throws Exception { cache.close(); cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); set("A", "aa", "aaa"); // size 5 @@ -663,7 +637,7 @@ public final class DiskLruCacheTest extends TestCase { assertNull(snapshot.edit()); } - public void testEditSinceEvictedAndRecreated() throws Exception { + @Test public void editSinceEvictedAndRecreated() throws Exception { cache.close(); cache = DiskLruCache.open(cacheDir, appVersion, 2, 10); set("A", "aa", "aaa"); // size 5 diff --git a/src/test/java/libcore/net/ssl/SslContextBuilder.java b/src/test/java/com/squareup/okhttp/internal/SslContextBuilder.java index d88ca9c..c0a520c 100644 --- a/src/test/java/libcore/net/ssl/SslContextBuilder.java +++ b/src/test/java/com/squareup/okhttp/internal/SslContextBuilder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package libcore.net.ssl; +package com.squareup.okhttp.internal; import java.io.IOException; import java.io.InputStream; @@ -103,7 +103,7 @@ public final class SslContextBuilder { X509V3CertificateGenerator generator = new X509V3CertificateGenerator(); X500Principal issuer = new X500Principal("CN=" + hostName); X500Principal subject = new X500Principal("CN=" + hostName); - generator.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); + generator.setSerialNumber(BigInteger.ONE); generator.setIssuerDN(issuer); generator.setNotBefore(new Date(notBefore)); generator.setNotAfter(new Date(notAfter)); diff --git a/src/test/java/libcore/io/StrictLineReaderTest.java b/src/test/java/com/squareup/okhttp/internal/StrictLineReaderTest.java index 2b9e95e..5f85b52 100644 --- a/src/test/java/libcore/io/StrictLineReaderTest.java +++ b/src/test/java/com/squareup/okhttp/internal/StrictLineReaderTest.java @@ -14,49 +14,43 @@ * limitations under the License. */ -package libcore.io; +package com.squareup.okhttp.internal; +import static com.squareup.okhttp.internal.Util.US_ASCII; import java.io.ByteArrayInputStream; import java.io.EOFException; -import java.io.IOException; import java.io.InputStream; -import junit.framework.TestCase; -import libcore.util.Charsets; +import static org.junit.Assert.fail; +import org.junit.Test; -public class StrictLineReaderTest extends TestCase { - - public void testLineReaderConsistencyWithReadAsciiLine () { - try { - // Testing with LineReader buffer capacity 32 to check some corner cases. - StrictLineReader lineReader = new StrictLineReader(createTestInputStream(), 32, - Charsets.US_ASCII); - InputStream refStream = createTestInputStream(); - while (true) { +public final class StrictLineReaderTest { + @Test public void lineReaderConsistencyWithReadAsciiLine() throws Exception { + // Testing with LineReader buffer capacity 32 to check some corner cases. + StrictLineReader lineReader = new StrictLineReader(createTestInputStream(), 32, US_ASCII); + InputStream refStream = createTestInputStream(); + while (true) { + try { + String refLine = Util.readAsciiLine(refStream); try { - String refLine = Streams.readAsciiLine(refStream); - try { - String line = lineReader.readLine(); - if (!refLine.equals(line)) { - fail("line (\""+line+"\") differs from expected (\""+refLine+"\")."); - } - } catch (EOFException eof) { - fail("line reader threw EOFException too early."); - } - } catch (EOFException refEof) { - try { - lineReader.readLine(); - fail("line reader didn't throw the expected EOFException."); - } catch (EOFException eof) { - // OK - break; + String line = lineReader.readLine(); + if (!refLine.equals(line)) { + fail("line (\""+line+"\") differs from expected (\""+refLine+"\")."); } + } catch (EOFException eof) { + fail("line reader threw EOFException too early."); + } + } catch (EOFException refEof) { + try { + lineReader.readLine(); + fail("line reader didn't throw the expected EOFException."); + } catch (EOFException eof) { + // OK + break; } } - refStream.close(); - lineReader.close(); - } catch (IOException ioe) { - fail("Unexpected IOException " + ioe.toString()); } + refStream.close(); + lineReader.close(); } private InputStream createTestInputStream() { @@ -79,4 +73,3 @@ public class StrictLineReaderTest extends TestCase { ).getBytes()); } } - diff --git a/src/test/java/libcore/net/http/ExternalSpdyExample.java b/src/test/java/com/squareup/okhttp/internal/http/ExternalSpdyExample.java index dc8aa1e..f93c493 100644 --- a/src/test/java/libcore/net/http/ExternalSpdyExample.java +++ b/src/test/java/com/squareup/okhttp/internal/http/ExternalSpdyExample.java @@ -14,20 +14,20 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; -import com.squareup.okhttp.OkHttpConnection; -import com.squareup.okhttp.OkHttpsConnection; +import com.squareup.okhttp.OkHttpClient; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; public final class ExternalSpdyExample { public static void main(String[] args) throws Exception { URL url = new URL("https://www.google.ca/"); - OkHttpsConnection connection = (OkHttpsConnection) OkHttpConnection.open(url); + HttpsURLConnection connection = (HttpsURLConnection) new OkHttpClient().open(url); connection.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslSession) { diff --git a/src/test/java/libcore/net/http/HttpResponseCacheTest.java b/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java index 5c675fa..841647d 100644 --- a/src/test/java/libcore/net/http/HttpResponseCacheTest.java +++ b/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java @@ -14,12 +14,15 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.MockWebServer; import com.google.mockwebserver.RecordedRequest; -import com.squareup.okhttp.OkHttpConnection; +import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.internal.http.HttpResponseCache; +import com.squareup.okhttp.internal.SslContextBuilder; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; @@ -34,12 +37,17 @@ import java.net.CookieHandler; import java.net.CookieManager; import java.net.HttpCookie; import java.net.HttpURLConnection; +import java.net.InetAddress; import java.net.ResponseCache; import java.net.SecureCacheResponse; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; +import java.net.UnknownHostException; +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.security.cert.Certificate; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -56,48 +64,69 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.GZIPOutputStream; -import junit.framework.TestCase; - -import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import org.junit.After; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.Test; /** * Android's HttpResponseCacheTest. */ -public final class HttpResponseCacheTest extends TestCase { +public final class HttpResponseCacheTest { + private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = new HostnameVerifier() { + @Override public boolean verify(String s, SSLSession sslSession) { + return true; + } + }; + private final OkHttpClient client = new OkHttpClient(); private MockWebServer server = new MockWebServer(); private HttpResponseCache cache; -// private final MockOs mockOs = new MockOs(); private final CookieManager cookieManager = new CookieManager(); - @Override protected void setUp() throws Exception { - super.setUp(); + private static final SSLContext sslContext; + static { + try { + sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName()).build(); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + @Before public void setUp() throws Exception { String tmp = System.getProperty("java.io.tmpdir"); File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID()); cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE); ResponseCache.setDefault(cache); -// mockOs.install(); CookieHandler.setDefault(cookieManager); } - @Override protected void tearDown() throws Exception { -// mockOs.uninstall(); + @After public void tearDown() throws Exception { server.shutdown(); ResponseCache.setDefault(null); cache.getCache().delete(); CookieHandler.setDefault(null); - super.tearDown(); } - private static OkHttpConnection openConnection(URL url) { - return OkHttpConnection.open(url); + private HttpURLConnection openConnection(URL url) { + return client.open(url); } /** * Test that response caching is consistent with the RI and the spec. * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4 */ - public void testResponseCachingByResponseCode() throws Exception { + @Test public void responseCachingByResponseCode() throws Exception { // Test each documented HTTP/1.1 code, plus the first unused value in each range. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html @@ -137,12 +166,12 @@ public final class HttpResponseCacheTest extends TestCase { * Response code 407 should only come from proxy servers. Android's client * throws if it is sent by an origin server. */ - public void testOriginServerSends407() throws Exception { + @Test public void originServerSends407() throws Exception { server.enqueue(new MockResponse().setResponseCode(407)); server.play(); URL url = server.getUrl("/"); - OkHttpConnection conn = openConnection(url); + HttpURLConnection conn = openConnection(url); try { conn.getResponseCode(); fail(); @@ -150,7 +179,7 @@ public final class HttpResponseCacheTest extends TestCase { } } - public void test_responseCaching_410() throws Exception { + @Test public void responseCaching_410() throws Exception { // the HTTP spec permits caching 410s, but the RI doesn't. assertCached(true, 410); } @@ -172,7 +201,7 @@ public final class HttpResponseCacheTest extends TestCase { server.play(); URL url = server.getUrl("/"); - OkHttpConnection conn = openConnection(url); + HttpURLConnection conn = openConnection(url); assertEquals(responseCode, conn.getResponseCode()); // exhaust the content stream @@ -193,7 +222,7 @@ public final class HttpResponseCacheTest extends TestCase { * Test that we can interrogate the response when the cache is being * populated. http://code.google.com/p/android/issues/detail?id=7787 */ - public void testResponseCacheCallbackApis() throws Exception { + @Test public void responseCacheCallbackApis() throws Exception { final String body = "ABCDE"; final AtomicInteger cacheCount = new AtomicInteger(); @@ -209,7 +238,7 @@ public final class HttpResponseCacheTest extends TestCase { return null; } @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException { - OkHttpConnection httpConnection = (OkHttpConnection) conn; + HttpURLConnection httpConnection = (HttpURLConnection) conn; try { httpConnection.getRequestProperties(); fail(); @@ -238,21 +267,21 @@ public final class HttpResponseCacheTest extends TestCase { }); URL url = server.getUrl("/"); - OkHttpConnection connection = openConnection(url); + HttpURLConnection connection = openConnection(url); assertEquals(body, readAscii(connection)); assertEquals(1, cacheCount.get()); } - public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException { + @Test public void responseCachingAndInputStreamSkipWithFixedLength() throws IOException { testResponseCaching(TransferKind.FIXED_LENGTH); } - public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException { + @Test public void responseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException { testResponseCaching(TransferKind.CHUNKED); } - public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException { + @Test public void responseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException { testResponseCaching(TransferKind.END_OF_STREAM); } @@ -270,7 +299,7 @@ public final class HttpResponseCacheTest extends TestCase { server.play(); // Make sure that calling skip() doesn't omit bytes from the cache. - OkHttpConnection urlConnection = openConnection(server.getUrl("/")); + HttpURLConnection urlConnection = openConnection(server.getUrl("/")); InputStream in = urlConnection.getInputStream(); assertEquals("I love ", readAscii(urlConnection, "I love ".length())); reliableSkip(in, "puppies but hate ".length()); @@ -295,60 +324,63 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals(1, cache.getHitCount()); } -// public void testSecureResponseCaching() throws IOException { -// TestSSLContext testSSLContext = TestSSLContext.create(); -// server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); -// server.enqueue(new MockResponse() -// .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) -// .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) -// .setBody("ABC")); -// server.play(); -// -// HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); -// connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); -// assertEquals("ABC", readAscii(connection)); -// -// // OpenJDK 6 fails on this line, complaining that the connection isn't open yet -// String suite = connection.getCipherSuite(); -// List<Certificate> localCerts = toListOrNull(connection.getLocalCertificates()); -// List<Certificate> serverCerts = toListOrNull(connection.getServerCertificates()); -// Principal peerPrincipal = connection.getPeerPrincipal(); -// Principal localPrincipal = connection.getLocalPrincipal(); -// -// connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached! -// connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); -// assertEquals("ABC", readAscii(connection)); -// -// assertEquals(2, cache.getRequestCount()); -// assertEquals(1, cache.getNetworkCount()); -// assertEquals(1, cache.getHitCount()); -// -// assertEquals(suite, connection.getCipherSuite()); -// assertEquals(localCerts, toListOrNull(connection.getLocalCertificates())); -// assertEquals(serverCerts, toListOrNull(connection.getServerCertificates())); -// assertEquals(peerPrincipal, connection.getPeerPrincipal()); -// assertEquals(localPrincipal, connection.getLocalPrincipal()); -// } -// -// public void testCacheReturnsInsecureResponseForSecureRequest() throws IOException { -// TestSSLContext testSSLContext = TestSSLContext.create(); -// server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); -// server.enqueue(new MockResponse().setBody("ABC")); -// server.enqueue(new MockResponse().setBody("DEF")); -// server.play(); -// -// ResponseCache.setDefault(new InsecureResponseCache()); -// -// HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); -// connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); -// assertEquals("ABC", readAscii(connection)); -// -// connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // not cached! -// connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); -// assertEquals("DEF", readAscii(connection)); -// } - - public void testResponseCachingAndRedirects() throws Exception { + @Test public void secureResponseCaching() throws IOException { + server.useHttps(sslContext.getSocketFactory(), false); + server.enqueue(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) + .setBody("ABC")); + server.play(); + + HttpsURLConnection connection = (HttpsURLConnection) client.open(server.getUrl("/")); + connection.setSSLSocketFactory(sslContext.getSocketFactory()); + connection.setHostnameVerifier(NULL_HOSTNAME_VERIFIER); + assertEquals("ABC", readAscii(connection)); + + // OpenJDK 6 fails on this line, complaining that the connection isn't open yet + String suite = connection.getCipherSuite(); + List<Certificate> localCerts = toListOrNull(connection.getLocalCertificates()); + List<Certificate> serverCerts = toListOrNull(connection.getServerCertificates()); + Principal peerPrincipal = connection.getPeerPrincipal(); + Principal localPrincipal = connection.getLocalPrincipal(); + + connection = (HttpsURLConnection) client.open(server.getUrl("/")); // cached! + connection.setSSLSocketFactory(sslContext.getSocketFactory()); + connection.setHostnameVerifier(NULL_HOSTNAME_VERIFIER); + assertEquals("ABC", readAscii(connection)); + + assertEquals(2, cache.getRequestCount()); + assertEquals(1, cache.getNetworkCount()); + assertEquals(1, cache.getHitCount()); + + assertEquals(suite, connection.getCipherSuite()); + assertEquals(localCerts, toListOrNull(connection.getLocalCertificates())); + assertEquals(serverCerts, toListOrNull(connection.getServerCertificates())); + assertEquals(peerPrincipal, connection.getPeerPrincipal()); + assertEquals(localPrincipal, connection.getLocalPrincipal()); + } + + @Test public void cacheReturnsInsecureResponseForSecureRequest() throws IOException { + server.useHttps(sslContext.getSocketFactory(), false); + server.enqueue(new MockResponse().setBody("ABC")); + server.enqueue(new MockResponse().setBody("DEF")); + server.play(); + + ResponseCache.setDefault(new InsecureResponseCache()); + + HttpsURLConnection connection1 = (HttpsURLConnection) client.open(server.getUrl("/")); + connection1.setSSLSocketFactory(sslContext.getSocketFactory()); + connection1.setHostnameVerifier(NULL_HOSTNAME_VERIFIER); + assertEquals("ABC", readAscii(connection1)); + + // Not cached! + HttpsURLConnection connection2 = (HttpsURLConnection) client.open(server.getUrl("/")); + connection2.setSSLSocketFactory(sslContext.getSocketFactory()); + connection2.setHostnameVerifier(NULL_HOSTNAME_VERIFIER); + assertEquals("DEF", readAscii(connection2)); + } + + @Test public void responseCachingAndRedirects() throws Exception { server.enqueue(new MockResponse() .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) @@ -361,7 +393,7 @@ public final class HttpResponseCacheTest extends TestCase { server.enqueue(new MockResponse().setBody("DEF")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = openConnection(server.getUrl("/")); assertEquals("ABC", readAscii(connection)); connection = openConnection(server.getUrl("/")); // cached! @@ -372,7 +404,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals(2, cache.getHitCount()); } - public void testRedirectToCachedResult() throws Exception { + @Test public void redirectToCachedResult() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .setBody("ABC")); @@ -399,34 +431,36 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals(2, request3.getSequenceNumber()); } -// public void testSecureResponseCachingAndRedirects() throws IOException { -// TestSSLContext testSSLContext = TestSSLContext.create(); -// server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); -// server.enqueue(new MockResponse() -// .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) -// .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) -// .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM) -// .addHeader("Location: /foo")); -// server.enqueue(new MockResponse() -// .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) -// .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) -// .setBody("ABC")); -// server.enqueue(new MockResponse().setBody("DEF")); -// server.play(); -// -// HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); -// connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); -// assertEquals("ABC", readAscii(connection)); -// -// connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached! -// connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); -// assertEquals("ABC", readAscii(connection)); -// -// assertEquals(4, cache.getRequestCount()); // 2 direct + 2 redirect = 4 -// assertEquals(2, cache.getHitCount()); -// } - - public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException { + @Test public void secureResponseCachingAndRedirects() throws IOException { + server.useHttps(sslContext.getSocketFactory(), false); + server.enqueue(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) + .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM) + .addHeader("Location: /foo")); + server.enqueue(new MockResponse() + .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) + .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) + .setBody("ABC")); + server.enqueue(new MockResponse().setBody("DEF")); + server.play(); + + HttpsURLConnection connection1 = (HttpsURLConnection) client.open(server.getUrl("/")); + connection1.setSSLSocketFactory(sslContext.getSocketFactory()); + connection1.setHostnameVerifier(NULL_HOSTNAME_VERIFIER); + assertEquals("ABC", readAscii(connection1)); + + // Cached! + HttpsURLConnection connection2 = (HttpsURLConnection) client.open(server.getUrl("/")); + connection1.setSSLSocketFactory(sslContext.getSocketFactory()); + connection1.setHostnameVerifier(NULL_HOSTNAME_VERIFIER); + assertEquals("ABC", readAscii(connection2)); + + assertEquals(4, cache.getRequestCount()); // 2 direct + 2 redirect = 4 + assertEquals(2, cache.getHitCount()); + } + + @Test public void responseCacheRequestHeaders() throws IOException, URISyntaxException { server.enqueue(new MockResponse().setBody("ABC")); server.play(); @@ -451,15 +485,15 @@ public final class HttpResponseCacheTest extends TestCase { } - public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException { + @Test public void serverDisconnectsPrematurelyWithContentLengthHeader() throws IOException { testServerPrematureDisconnect(TransferKind.FIXED_LENGTH); } - public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException { + @Test public void serverDisconnectsPrematurelyWithChunkedEncoding() throws IOException { testServerPrematureDisconnect(TransferKind.CHUNKED); } - public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException { + @Test public void serverDisconnectsPrematurelyWithNoLengthHeaders() throws IOException { /* * Intentionally empty. This case doesn't make sense because there's no * such thing as a premature disconnect when the disconnect itself @@ -493,20 +527,21 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals(1, cache.getWriteSuccessCount()); } - public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException { + @Test public void clientPrematureDisconnectWithContentLengthHeader() throws IOException { testClientPrematureDisconnect(TransferKind.FIXED_LENGTH); } - public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException { + @Test public void clientPrematureDisconnectWithChunkedEncoding() throws IOException { testClientPrematureDisconnect(TransferKind.CHUNKED); } - public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException { + @Test public void clientPrematureDisconnectWithNoLengthHeaders() throws IOException { testClientPrematureDisconnect(TransferKind.END_OF_STREAM); } private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException { - MockResponse response = new MockResponse(); + // Setting a low transfer speed ensures that stream discarding will time out. + MockResponse response = new MockResponse().setBytesPerSecond(6); transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024); server.enqueue(response); server.enqueue(new MockResponse().setBody("Request #2")); @@ -530,7 +565,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals(1, cache.getWriteSuccessCount()); } - public void testDefaultExpirationDateFullyCachedForLessThan24Hours() throws Exception { + @Test public void defaultExpirationDateFullyCachedForLessThan24Hours() throws Exception { // last modified: 105 seconds ago // served: 5 seconds ago // default lifetime: (105 - 5) / 10 = 10 seconds @@ -548,7 +583,7 @@ public final class HttpResponseCacheTest extends TestCase { assertNull(connection.getHeaderField("Warning")); } - public void testDefaultExpirationDateConditionallyCached() throws Exception { + @Test public void defaultExpirationDateConditionallyCached() throws Exception { // last modified: 115 seconds ago // served: 15 seconds ago // default lifetime: (115 - 15) / 10 = 10 seconds @@ -561,7 +596,7 @@ public final class HttpResponseCacheTest extends TestCase { assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate)); } - public void testDefaultExpirationDateFullyCachedForMoreThan24Hours() throws Exception { + @Test public void defaultExpirationDateFullyCachedForMoreThan24Hours() throws Exception { // last modified: 105 days ago // served: 5 days ago // default lifetime: (105 - 5) / 10 = 10 days @@ -579,7 +614,7 @@ public final class HttpResponseCacheTest extends TestCase { connection.getHeaderField("Warning")); } - public void testNoDefaultExpirationForUrlsWithQueryString() throws Exception { + @Test public void noDefaultExpirationForUrlsWithQueryString() throws Exception { server.enqueue(new MockResponse() .addHeader("Last-Modified: " + formatDate(-105, TimeUnit.SECONDS)) .addHeader("Date: " + formatDate(-5, TimeUnit.SECONDS)) @@ -592,7 +627,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(openConnection(url))); } - public void testExpirationDateInThePastWithLastModifiedHeader() throws Exception { + @Test public void expirationDateInThePastWithLastModifiedHeader() throws Exception { String lastModifiedDate = formatDate(-2, TimeUnit.HOURS); RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() .addHeader("Last-Modified: " + lastModifiedDate) @@ -601,24 +636,24 @@ public final class HttpResponseCacheTest extends TestCase { assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate)); } - public void testExpirationDateInThePastWithNoLastModifiedHeader() throws Exception { + @Test public void expirationDateInThePastWithNoLastModifiedHeader() throws Exception { assertNotCached(new MockResponse() .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); } - public void testExpirationDateInTheFuture() throws Exception { + @Test public void expirationDateInTheFuture() throws Exception { assertFullyCached(new MockResponse() .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); } - public void testMaxAgePreferredWithMaxAgeAndExpires() throws Exception { + @Test public void maxAgePreferredWithMaxAgeAndExpires() throws Exception { assertFullyCached(new MockResponse() .addHeader("Date: " + formatDate(0, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Cache-Control: max-age=60")); } - public void testMaxAgeInThePastWithDateAndLastModifiedHeaders() throws Exception { + @Test public void maxAgeInThePastWithDateAndLastModifiedHeaders() throws Exception { String lastModifiedDate = formatDate(-2, TimeUnit.HOURS); RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() .addHeader("Date: " + formatDate(-120, TimeUnit.SECONDS)) @@ -628,7 +663,7 @@ public final class HttpResponseCacheTest extends TestCase { assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate)); } - public void testMaxAgeInThePastWithDateHeaderButNoLastModifiedHeader() throws Exception { + @Test public void maxAgeInThePastWithDateHeaderButNoLastModifiedHeader() throws Exception { /* * Chrome interprets max-age relative to the local clock. Both our cache * and Firefox both use the earlier of the local and server's clock. @@ -638,71 +673,71 @@ public final class HttpResponseCacheTest extends TestCase { .addHeader("Cache-Control: max-age=60")); } - public void testMaxAgeInTheFutureWithDateHeader() throws Exception { + @Test public void maxAgeInTheFutureWithDateHeader() throws Exception { assertFullyCached(new MockResponse() .addHeader("Date: " + formatDate(0, TimeUnit.HOURS)) .addHeader("Cache-Control: max-age=60")); } - public void testMaxAgeInTheFutureWithNoDateHeader() throws Exception { + @Test public void maxAgeInTheFutureWithNoDateHeader() throws Exception { assertFullyCached(new MockResponse() .addHeader("Cache-Control: max-age=60")); } - public void testMaxAgeWithLastModifiedButNoServedDate() throws Exception { + @Test public void maxAgeWithLastModifiedButNoServedDate() throws Exception { assertFullyCached(new MockResponse() .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS)) .addHeader("Cache-Control: max-age=60")); } - public void testMaxAgeInTheFutureWithDateAndLastModifiedHeaders() throws Exception { + @Test public void maxAgeInTheFutureWithDateAndLastModifiedHeaders() throws Exception { assertFullyCached(new MockResponse() .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS)) .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS)) .addHeader("Cache-Control: max-age=60")); } - public void testMaxAgePreferredOverLowerSharedMaxAge() throws Exception { + @Test public void maxAgePreferredOverLowerSharedMaxAge() throws Exception { assertFullyCached(new MockResponse() .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES)) .addHeader("Cache-Control: s-maxage=60") .addHeader("Cache-Control: max-age=180")); } - public void testMaxAgePreferredOverHigherMaxAge() throws Exception { + @Test public void maxAgePreferredOverHigherMaxAge() throws Exception { assertNotCached(new MockResponse() .addHeader("Date: " + formatDate(-2, TimeUnit.MINUTES)) .addHeader("Cache-Control: s-maxage=180") .addHeader("Cache-Control: max-age=60")); } - public void testRequestMethodOptionsIsNotCached() throws Exception { + @Test public void requestMethodOptionsIsNotCached() throws Exception { testRequestMethod("OPTIONS", false); } - public void testRequestMethodGetIsCached() throws Exception { + @Test public void requestMethodGetIsCached() throws Exception { testRequestMethod("GET", true); } - public void testRequestMethodHeadIsNotCached() throws Exception { + @Test public void requestMethodHeadIsNotCached() throws Exception { // We could support this but choose not to for implementation simplicity testRequestMethod("HEAD", false); } - public void testRequestMethodPostIsNotCached() throws Exception { + @Test public void requestMethodPostIsNotCached() throws Exception { // We could support this but choose not to for implementation simplicity testRequestMethod("POST", false); } - public void testRequestMethodPutIsNotCached() throws Exception { + @Test public void requestMethodPutIsNotCached() throws Exception { testRequestMethod("PUT", false); } - public void testRequestMethodDeleteIsNotCached() throws Exception { + @Test public void requestMethodDeleteIsNotCached() throws Exception { testRequestMethod("DELETE", false); } - public void testRequestMethodTraceIsNotCached() throws Exception { + @Test public void requestMethodTraceIsNotCached() throws Exception { testRequestMethod("TRACE", false); } @@ -720,7 +755,7 @@ public final class HttpResponseCacheTest extends TestCase { URL url = server.getUrl("/"); - OkHttpConnection request1 = (OkHttpConnection) openConnection(url); + HttpURLConnection request1 = openConnection(url); request1.setRequestMethod(requestMethod); addRequestBodyIfNecessary(requestMethod, request1); assertEquals("1", request1.getHeaderField("X-Response-ID")); @@ -733,15 +768,15 @@ public final class HttpResponseCacheTest extends TestCase { } } - public void testPostInvalidatesCache() throws Exception { + @Test public void postInvalidatesCache() throws Exception { testMethodInvalidates("POST"); } - public void testPutInvalidatesCache() throws Exception { + @Test public void putInvalidatesCache() throws Exception { testMethodInvalidates("PUT"); } - public void testDeleteMethodInvalidatesCache() throws Exception { + @Test public void deleteMethodInvalidatesCache() throws Exception { testMethodInvalidates("DELETE"); } @@ -761,7 +796,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("A", readAscii(openConnection(url))); - OkHttpConnection invalidate = openConnection(url); + HttpURLConnection invalidate = openConnection(url); invalidate.setRequestMethod(requestMethod); addRequestBodyIfNecessary(requestMethod, invalidate); assertEquals("B", readAscii(invalidate)); @@ -769,13 +804,13 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("C", readAscii(openConnection(url))); } - public void testEtag() throws Exception { + @Test public void etag() throws Exception { RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() .addHeader("ETag: v1")); assertTrue(conditionalRequest.getHeaders().contains("If-None-Match: v1")); } - public void testEtagAndExpirationDateInThePast() throws Exception { + @Test public void etagAndExpirationDateInThePast() throws Exception { String lastModifiedDate = formatDate(-2, TimeUnit.HOURS); RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() .addHeader("ETag: v1") @@ -786,18 +821,18 @@ public final class HttpResponseCacheTest extends TestCase { assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate)); } - public void testEtagAndExpirationDateInTheFuture() throws Exception { + @Test public void etagAndExpirationDateInTheFuture() throws Exception { assertFullyCached(new MockResponse() .addHeader("ETag: v1") .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); } - public void testCacheControlNoCache() throws Exception { + @Test public void cacheControlNoCache() throws Exception { assertNotCached(new MockResponse().addHeader("Cache-Control: no-cache")); } - public void testCacheControlNoCacheAndExpirationDateInTheFuture() throws Exception { + @Test public void cacheControlNoCacheAndExpirationDateInTheFuture() throws Exception { String lastModifiedDate = formatDate(-2, TimeUnit.HOURS); RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() .addHeader("Last-Modified: " + lastModifiedDate) @@ -807,11 +842,11 @@ public final class HttpResponseCacheTest extends TestCase { assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate)); } - public void testPragmaNoCache() throws Exception { + @Test public void pragmaNoCache() throws Exception { assertNotCached(new MockResponse().addHeader("Pragma: no-cache")); } - public void testPragmaNoCacheAndExpirationDateInTheFuture() throws Exception { + @Test public void pragmaNoCacheAndExpirationDateInTheFuture() throws Exception { String lastModifiedDate = formatDate(-2, TimeUnit.HOURS); RecordedRequest conditionalRequest = assertConditionallyCached(new MockResponse() .addHeader("Last-Modified: " + lastModifiedDate) @@ -821,18 +856,18 @@ public final class HttpResponseCacheTest extends TestCase { assertTrue(headers.contains("If-Modified-Since: " + lastModifiedDate)); } - public void testCacheControlNoStore() throws Exception { + @Test public void cacheControlNoStore() throws Exception { assertNotCached(new MockResponse().addHeader("Cache-Control: no-store")); } - public void testCacheControlNoStoreAndExpirationDateInTheFuture() throws Exception { + @Test public void cacheControlNoStoreAndExpirationDateInTheFuture() throws Exception { assertNotCached(new MockResponse() .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS)) .addHeader("Cache-Control: no-store")); } - public void testPartialRangeResponsesDoNotCorruptCache() throws Exception { + @Test public void partialRangeResponsesDoNotCorruptCache() throws Exception { /* * 1. request a range * 2. request a full document, expecting a cache miss @@ -853,7 +888,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("BB", readAscii(openConnection(url))); } - public void testServerReturnsDocumentOlderThanCache() throws Exception { + @Test public void serverReturnsDocumentOlderThanCache() throws Exception { server.enqueue(new MockResponse().setBody("A") .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); @@ -867,13 +902,13 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("A", readAscii(openConnection(url))); } - public void testNonIdentityEncodingAndConditionalCache() throws Exception { + @Test public void nonIdentityEncodingAndConditionalCache() throws Exception { assertNonIdentityEncodingCached(new MockResponse() .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(-1, TimeUnit.HOURS))); } - public void testNonIdentityEncodingAndFullCache() throws Exception { + @Test public void nonIdentityEncodingAndFullCache() throws Exception { assertNonIdentityEncodingCached(new MockResponse() .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))); @@ -890,13 +925,13 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("ABCABCABC", readAscii(openConnection(server.getUrl("/")))); } - public void testExpiresDateBeforeModifiedDate() throws Exception { + @Test public void expiresDateBeforeModifiedDate() throws Exception { assertConditionallyCached(new MockResponse() .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Expires: " + formatDate(-2, TimeUnit.HOURS))); } - public void testRequestMaxAge() throws IOException { + @Test public void requestMaxAge() throws IOException { server.enqueue(new MockResponse().setBody("A") .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.HOURS)) .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES)) @@ -911,7 +946,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(connection)); } - public void testRequestMinFresh() throws IOException { + @Test public void requestMinFresh() throws IOException { server.enqueue(new MockResponse().setBody("A") .addHeader("Cache-Control: max-age=60") .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES))); @@ -925,7 +960,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(connection)); } - public void testRequestMaxStale() throws IOException { + @Test public void requestMaxStale() throws IOException { server.enqueue(new MockResponse().setBody("A") .addHeader("Cache-Control: max-age=120") .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES))); @@ -941,7 +976,7 @@ public final class HttpResponseCacheTest extends TestCase { connection.getHeaderField("Warning")); } - public void testRequestMaxStaleNotHonoredWithMustRevalidate() throws IOException { + @Test public void requestMaxStaleNotHonoredWithMustRevalidate() throws IOException { server.enqueue(new MockResponse().setBody("A") .addHeader("Cache-Control: max-age=120, must-revalidate") .addHeader("Date: " + formatDate(-4, TimeUnit.MINUTES))); @@ -955,16 +990,16 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(connection)); } - public void testRequestOnlyIfCachedWithNoResponseCached() throws IOException { + @Test public void requestOnlyIfCachedWithNoResponseCached() throws IOException { // (no responses enqueued) server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = openConnection(server.getUrl("/")); connection.addRequestProperty("Cache-Control", "only-if-cached"); assertGatewayTimeout(connection); } - public void testRequestOnlyIfCachedWithFullResponseCached() throws IOException { + @Test public void requestOnlyIfCachedWithFullResponseCached() throws IOException { server.enqueue(new MockResponse().setBody("A") .addHeader("Cache-Control: max-age=30") .addHeader("Date: " + formatDate(0, TimeUnit.MINUTES))); @@ -976,29 +1011,29 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("A", readAscii(openConnection(server.getUrl("/")))); } - public void testRequestOnlyIfCachedWithConditionalResponseCached() throws IOException { + @Test public void requestOnlyIfCachedWithConditionalResponseCached() throws IOException { server.enqueue(new MockResponse().setBody("A") .addHeader("Cache-Control: max-age=30") .addHeader("Date: " + formatDate(-1, TimeUnit.MINUTES))); server.play(); assertEquals("A", readAscii(openConnection(server.getUrl("/")))); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = openConnection(server.getUrl("/")); connection.addRequestProperty("Cache-Control", "only-if-cached"); assertGatewayTimeout(connection); } - public void testRequestOnlyIfCachedWithUnhelpfulResponseCached() throws IOException { + @Test public void requestOnlyIfCachedWithUnhelpfulResponseCached() throws IOException { server.enqueue(new MockResponse().setBody("A")); server.play(); assertEquals("A", readAscii(openConnection(server.getUrl("/")))); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = openConnection(server.getUrl("/")); connection.addRequestProperty("Cache-Control", "only-if-cached"); assertGatewayTimeout(connection); } - public void testRequestCacheControlNoCache() throws Exception { + @Test public void requestCacheControlNoCache() throws Exception { server.enqueue(new MockResponse() .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS)) .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS)) @@ -1014,7 +1049,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(connection)); } - public void testRequestPragmaNoCache() throws Exception { + @Test public void requestPragmaNoCache() throws Exception { server.enqueue(new MockResponse() .addHeader("Last-Modified: " + formatDate(-120, TimeUnit.SECONDS)) .addHeader("Date: " + formatDate(0, TimeUnit.SECONDS)) @@ -1030,7 +1065,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(connection)); } - public void testClientSuppliedIfModifiedSinceWithCachedResult() throws Exception { + @Test public void clientSuppliedIfModifiedSinceWithCachedResult() throws Exception { MockResponse response = new MockResponse() .addHeader("ETag: v3") .addHeader("Cache-Control: max-age=0"); @@ -1042,7 +1077,7 @@ public final class HttpResponseCacheTest extends TestCase { assertFalse(headers.contains("If-None-Match: v3")); } - public void testClientSuppliedIfNoneMatchSinceWithCachedResult() throws Exception { + @Test public void clientSuppliedIfNoneMatchSinceWithCachedResult() throws Exception { String lastModifiedDate = formatDate(-3, TimeUnit.MINUTES); MockResponse response = new MockResponse() .addHeader("Last-Modified: " + lastModifiedDate) @@ -1064,7 +1099,7 @@ public final class HttpResponseCacheTest extends TestCase { URL url = server.getUrl("/"); assertEquals("A", readAscii(openConnection(url))); - OkHttpConnection connection = openConnection(url); + HttpURLConnection connection = openConnection(url); connection.addRequestProperty(conditionName, conditionValue); assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode()); assertEquals("", readAscii(connection)); @@ -1073,7 +1108,7 @@ public final class HttpResponseCacheTest extends TestCase { return server.takeRequest(); } - public void testSetIfModifiedSince() throws Exception { + @Test public void setIfModifiedSince() throws Exception { Date since = new Date(); server.enqueue(new MockResponse().setBody("A")); server.play(); @@ -1086,19 +1121,19 @@ public final class HttpResponseCacheTest extends TestCase { assertTrue(request.getHeaders().contains("If-Modified-Since: " + formatDate(since))); } - public void testClientSuppliedConditionWithoutCachedResult() throws Exception { + @Test public void clientSuppliedConditionWithoutCachedResult() throws Exception { server.enqueue(new MockResponse() .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = openConnection(server.getUrl("/")); String clientIfModifiedSince = formatDate(-24, TimeUnit.HOURS); connection.addRequestProperty("If-Modified-Since", clientIfModifiedSince); assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection.getResponseCode()); assertEquals("", readAscii(connection)); } - public void testAuthorizationRequestHeaderPreventsCaching() throws Exception { + @Test public void authorizationRequestHeaderPreventsCaching() throws Exception { server.enqueue(new MockResponse() .addHeader("Last-Modified: " + formatDate(-2, TimeUnit.MINUTES)) .addHeader("Cache-Control: max-age=60") @@ -1113,17 +1148,17 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(openConnection(url))); } - public void testAuthorizationResponseCachedWithSMaxAge() throws Exception { + @Test public void authorizationResponseCachedWithSMaxAge() throws Exception { assertAuthorizationRequestFullyCached(new MockResponse() .addHeader("Cache-Control: s-maxage=60")); } - public void testAuthorizationResponseCachedWithPublic() throws Exception { + @Test public void authorizationResponseCachedWithPublic() throws Exception { assertAuthorizationRequestFullyCached(new MockResponse() .addHeader("Cache-Control: public")); } - public void testAuthorizationResponseCachedWithMustRevalidate() throws Exception { + @Test public void authorizationResponseCachedWithMustRevalidate() throws Exception { assertAuthorizationRequestFullyCached(new MockResponse() .addHeader("Cache-Control: must-revalidate")); } @@ -1142,7 +1177,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("A", readAscii(openConnection(url))); } - public void testContentLocationDoesNotPopulateCache() throws Exception { + @Test public void contentLocationDoesNotPopulateCache() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Content-Location: /bar") @@ -1154,7 +1189,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(openConnection(server.getUrl("/bar")))); } - public void testUseCachesFalseDoesNotWriteToCache() throws Exception { + @Test public void useCachesFalseDoesNotWriteToCache() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .setBody("A").setBody("A")); @@ -1167,7 +1202,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(openConnection(server.getUrl("/")))); } - public void testUseCachesFalseDoesNotReadFromCache() throws Exception { + @Test public void useCachesFalseDoesNotReadFromCache() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .setBody("A").setBody("A")); @@ -1180,7 +1215,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(connection)); } - public void testDefaultUseCachesSetsInitialValueOnly() throws Exception { + @Test public void defaultUseCachesSetsInitialValueOnly() throws Exception { URL url = new URL("http://localhost/"); URLConnection c1 = openConnection(url); URLConnection c2 = openConnection(url); @@ -1196,7 +1231,7 @@ public final class HttpResponseCacheTest extends TestCase { } } - public void testConnectionIsReturnedToPoolAfterConditionalSuccess() throws Exception { + @Test public void connectionIsReturnedToPoolAfterConditionalSuccess() throws Exception { server.enqueue(new MockResponse() .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Cache-Control: max-age=0") @@ -1214,7 +1249,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals(2, server.takeRequest().getSequenceNumber()); } - public void testStatisticsConditionalCacheMiss() throws Exception { + @Test public void statisticsConditionalCacheMiss() throws Exception { server.enqueue(new MockResponse() .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Cache-Control: max-age=0") @@ -1234,7 +1269,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals(0, cache.getHitCount()); } - public void testStatisticsConditionalCacheHit() throws Exception { + @Test public void statisticsConditionalCacheHit() throws Exception { server.enqueue(new MockResponse() .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) .addHeader("Cache-Control: max-age=0") @@ -1254,7 +1289,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals(2, cache.getHitCount()); } - public void testStatisticsFullCacheHit() throws Exception { + @Test public void statisticsFullCacheHit() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .setBody("A")); @@ -1271,7 +1306,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals(2, cache.getHitCount()); } - public void testVaryMatchesChangedRequestHeaderField() throws Exception { + @Test public void varyMatchesChangedRequestHeaderField() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Vary: Accept-Language") @@ -1280,16 +1315,16 @@ public final class HttpResponseCacheTest extends TestCase { server.play(); URL url = server.getUrl("/"); - OkHttpConnection frConnection = openConnection(url); + HttpURLConnection frConnection = openConnection(url); frConnection.addRequestProperty("Accept-Language", "fr-CA"); assertEquals("A", readAscii(frConnection)); - OkHttpConnection enConnection = openConnection(url); + HttpURLConnection enConnection = openConnection(url); enConnection.addRequestProperty("Accept-Language", "en-US"); assertEquals("B", readAscii(enConnection)); } - public void testVaryMatchesUnchangedRequestHeaderField() throws Exception { + @Test public void varyMatchesUnchangedRequestHeaderField() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Vary: Accept-Language") @@ -1306,7 +1341,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("A", readAscii(connection2)); } - public void testVaryMatchesAbsentRequestHeaderField() throws Exception { + @Test public void varyMatchesAbsentRequestHeaderField() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Vary: Foo") @@ -1318,7 +1353,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("A", readAscii(openConnection(server.getUrl("/")))); } - public void testVaryMatchesAddedRequestHeaderField() throws Exception { + @Test public void varyMatchesAddedRequestHeaderField() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Vary: Foo") @@ -1332,7 +1367,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(fooConnection)); } - public void testVaryMatchesRemovedRequestHeaderField() throws Exception { + @Test public void varyMatchesRemovedRequestHeaderField() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Vary: Foo") @@ -1346,7 +1381,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(openConnection(server.getUrl("/")))); } - public void testVaryFieldsAreCaseInsensitive() throws Exception { + @Test public void varyFieldsAreCaseInsensitive() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Vary: ACCEPT-LANGUAGE") @@ -1363,7 +1398,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("A", readAscii(connection2)); } - public void testVaryMultipleFieldsWithMatch() throws Exception { + @Test public void varyMultipleFieldsWithMatch() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Vary: Accept-Language, Accept-Charset") @@ -1385,7 +1420,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("A", readAscii(connection2)); } - public void testVaryMultipleFieldsWithNoMatch() throws Exception { + @Test public void varyMultipleFieldsWithNoMatch() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Vary: Accept-Language, Accept-Charset") @@ -1407,7 +1442,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(enConnection)); } - public void testVaryMultipleFieldValuesWithMatch() throws Exception { + @Test public void varyMultipleFieldValuesWithMatch() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Vary: Accept-Language") @@ -1427,7 +1462,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("A", readAscii(connection2)); } - public void testVaryMultipleFieldValuesWithNoMatch() throws Exception { + @Test public void varyMultipleFieldValuesWithNoMatch() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Vary: Accept-Language") @@ -1447,7 +1482,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(connection2)); } - public void testVaryAsterisk() throws Exception { + @Test public void varyAsterisk() throws Exception { server.enqueue(new MockResponse() .addHeader("Cache-Control: max-age=60") .addHeader("Vary: *") @@ -1459,57 +1494,30 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(openConnection(server.getUrl("/")))); } -// public void testVaryAndHttps() throws Exception { -// TestSSLContext testSSLContext = TestSSLContext.create(); -// server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); -// server.enqueue(new MockResponse() -// .addHeader("Cache-Control: max-age=60") -// .addHeader("Vary: Accept-Language") -// .setBody("A")); -// server.enqueue(new MockResponse().setBody("B")); -// server.play(); -// -// URL url = server.getUrl("/"); -// HttpsURLConnection connection1 = (HttpsURLConnection) url.openConnection(); -// connection1.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); -// connection1.addRequestProperty("Accept-Language", "en-US"); -// assertEquals("A", readAscii(connection1)); -// -// HttpsURLConnection connection2 = (HttpsURLConnection) url.openConnection(); -// connection2.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); -// connection2.addRequestProperty("Accept-Language", "en-US"); -// assertEquals("A", readAscii(connection2)); -// } -// -// public void testDiskWriteFailureCacheDegradation() throws Exception { -// Deque<InvocationHandler> writeHandlers = mockOs.getHandlers("write"); -// int i = 0; -// boolean hasMoreScenarios = true; -// while (hasMoreScenarios) { -// mockOs.enqueueNormal("write", i++); -// mockOs.enqueueFault("write"); -// exercisePossiblyFaultyCache(false); -// hasMoreScenarios = writeHandlers.isEmpty(); -// writeHandlers.clear(); -// } -// System.out.println("Exercising the cache performs " + (i - 1) + " writes."); -// } -// -// public void testDiskReadFailureCacheDegradation() throws Exception { -// Deque<InvocationHandler> readHandlers = mockOs.getHandlers("read"); -// int i = 0; -// boolean hasMoreScenarios = true; -// while (hasMoreScenarios) { -// mockOs.enqueueNormal("read", i++); -// mockOs.enqueueFault("read"); -// exercisePossiblyFaultyCache(true); -// hasMoreScenarios = readHandlers.isEmpty(); -// readHandlers.clear(); -// } -// System.out.println("Exercising the cache performs " + (i - 1) + " reads."); -// } - - public void testCachePlusCookies() throws Exception { + @Test public void varyAndHttps() throws Exception { + server.useHttps(sslContext.getSocketFactory(), false); + server.enqueue(new MockResponse() + .addHeader("Cache-Control: max-age=60") + .addHeader("Vary: Accept-Language") + .setBody("A")); + server.enqueue(new MockResponse().setBody("B")); + server.play(); + + URL url = server.getUrl("/"); + HttpsURLConnection connection1 = (HttpsURLConnection) client.open(url); + connection1.setSSLSocketFactory(sslContext.getSocketFactory()); + connection1.setHostnameVerifier(NULL_HOSTNAME_VERIFIER); + connection1.addRequestProperty("Accept-Language", "en-US"); + assertEquals("A", readAscii(connection1)); + + HttpsURLConnection connection2 = (HttpsURLConnection) client.open(url); + connection2.setSSLSocketFactory(sslContext.getSocketFactory()); + connection2.setHostnameVerifier(NULL_HOSTNAME_VERIFIER); + connection2.addRequestProperty("Accept-Language", "en-US"); + assertEquals("A", readAscii(connection2)); + } + + @Test public void cachePlusCookies() throws Exception { server.enqueue(new MockResponse() .addHeader("Set-Cookie: a=FIRST; domain=" + server.getCookieDomain() + ";") .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) @@ -1527,7 +1535,7 @@ public final class HttpResponseCacheTest extends TestCase { assertCookies(url, "a=SECOND"); } - public void testGetHeadersReturnsNetworkEndToEndHeaders() throws Exception { + @Test public void getHeadersReturnsNetworkEndToEndHeaders() throws Exception { server.enqueue(new MockResponse() .addHeader("Allow: GET, HEAD") .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) @@ -1547,7 +1555,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("GET, HEAD, PUT", connection2.getHeaderField("Allow")); } - public void testGetHeadersReturnsCachedHopByHopHeaders() throws Exception { + @Test public void getHeadersReturnsCachedHopByHopHeaders() throws Exception { server.enqueue(new MockResponse() .addHeader("Transfer-Encoding: identity") .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) @@ -1567,7 +1575,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("identity", connection2.getHeaderField("Transfer-Encoding")); } - public void testGetHeadersDeletesCached100LevelWarnings() throws Exception { + @Test public void getHeadersDeletesCached100LevelWarnings() throws Exception { server.enqueue(new MockResponse() .addHeader("Warning: 199 test danger") .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) @@ -1586,7 +1594,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals(null, connection2.getHeaderField("Warning")); } - public void testGetHeadersRetainsCached200LevelWarnings() throws Exception { + @Test public void getHeadersRetainsCached200LevelWarnings() throws Exception { server.enqueue(new MockResponse() .addHeader("Warning: 299 test danger") .addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS)) @@ -1613,7 +1621,7 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals(Arrays.asList(expectedCookies), actualCookies); } - public void testCachePlusRange() throws Exception { + @Test public void cachePlusRange() throws Exception { assertNotCached(new MockResponse() .setResponseCode(HttpURLConnection.HTTP_PARTIAL) .addHeader("Date: " + formatDate(0, TimeUnit.HOURS)) @@ -1621,7 +1629,7 @@ public final class HttpResponseCacheTest extends TestCase { .addHeader("Cache-Control: max-age=60")); } - public void testConditionalHitUpdatesCache() throws Exception { + @Test public void conditionalHitUpdatesCache() throws Exception { server.enqueue(new MockResponse() .addHeader("Last-Modified: " + formatDate(0, TimeUnit.SECONDS)) .addHeader("Cache-Control: max-age=0") @@ -1634,18 +1642,18 @@ public final class HttpResponseCacheTest extends TestCase { server.play(); // cache miss; seed the cache - OkHttpConnection connection1 = openConnection(server.getUrl("/a")); + HttpURLConnection connection1 = openConnection(server.getUrl("/a")); assertEquals("A", readAscii(connection1)); assertEquals(null, connection1.getHeaderField("Allow")); // conditional cache hit; update the cache - OkHttpConnection connection2 = openConnection(server.getUrl("/a")); + HttpURLConnection connection2 = openConnection(server.getUrl("/a")); assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode()); assertEquals("A", readAscii(connection2)); assertEquals("GET, HEAD", connection2.getHeaderField("Allow")); // full cache hit - OkHttpConnection connection3 = openConnection(server.getUrl("/a")); + HttpURLConnection connection3 = openConnection(server.getUrl("/a")); assertEquals("A", readAscii(connection3)); assertEquals("GET, HEAD", connection3.getHeaderField("Allow")); @@ -1667,7 +1675,7 @@ public final class HttpResponseCacheTest extends TestCase { return rfc1123.format(date); } - private void addRequestBodyIfNecessary(String requestMethod, OkHttpConnection invalidate) + private void addRequestBodyIfNecessary(String requestMethod, HttpURLConnection invalidate) throws IOException { if (requestMethod.equals("POST") || requestMethod.equals("PUT")) { invalidate.setDoOutput(true); @@ -1687,31 +1695,6 @@ public final class HttpResponseCacheTest extends TestCase { assertEquals("B", readAscii(openConnection(url))); } - private void exercisePossiblyFaultyCache(boolean permitReadBodyFailures) throws Exception { - server.shutdown(); - server = new MockWebServer(); - server.enqueue(new MockResponse() - .addHeader("Cache-Control: max-age=60") - .setBody("A")); - server.enqueue(new MockResponse().setBody("B")); - server.play(); - - URL url = server.getUrl("/" + UUID.randomUUID()); - assertEquals("A", readAscii(openConnection(url))); - - URLConnection connection = openConnection(url); - InputStream in = connection.getInputStream(); - try { - int bodyChar = in.read(); - assertTrue(bodyChar == 'A' || bodyChar == 'B'); - assertEquals(-1, in.read()); - } catch (IOException e) { - if (!permitReadBodyFailures) { - throw e; - } - } - } - /** * @return the request with the conditional get headers. */ @@ -1727,21 +1710,21 @@ public final class HttpResponseCacheTest extends TestCase { server.play(); URL valid = server.getUrl("/valid"); - OkHttpConnection connection1 = openConnection(valid); + HttpURLConnection connection1 = openConnection(valid); assertEquals("A", readAscii(connection1)); assertEquals(HttpURLConnection.HTTP_OK, connection1.getResponseCode()); assertEquals("A-OK", connection1.getResponseMessage()); - OkHttpConnection connection2 = openConnection(valid); + HttpURLConnection connection2 = openConnection(valid); assertEquals("A", readAscii(connection2)); assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode()); assertEquals("A-OK", connection2.getResponseMessage()); URL invalid = server.getUrl("/invalid"); - OkHttpConnection connection3 = openConnection(invalid); + HttpURLConnection connection3 = openConnection(invalid); assertEquals("B", readAscii(connection3)); assertEquals(HttpURLConnection.HTTP_OK, connection3.getResponseCode()); assertEquals("B-OK", connection3.getResponseMessage()); - OkHttpConnection connection4 = openConnection(invalid); + HttpURLConnection connection4 = openConnection(invalid); assertEquals("C", readAscii(connection4)); assertEquals(HttpURLConnection.HTTP_OK, connection4.getResponseCode()); assertEquals("C-OK", connection4.getResponseMessage()); @@ -1780,7 +1763,7 @@ public final class HttpResponseCacheTest extends TestCase { * characters are returned and the stream is closed. */ private String readAscii(URLConnection connection, int count) throws IOException { - OkHttpConnection httpConnection = (OkHttpConnection) connection; + HttpURLConnection httpConnection = (HttpURLConnection) connection; InputStream in = httpConnection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST ? connection.getInputStream() : httpConnection.getErrorStream(); @@ -1806,7 +1789,7 @@ public final class HttpResponseCacheTest extends TestCase { } } - private void assertGatewayTimeout(OkHttpConnection connection) throws IOException { + private void assertGatewayTimeout(HttpURLConnection connection) throws IOException { try { connection.getInputStream(); fail(); diff --git a/src/test/java/libcore/net/http/RawHeadersTest.java b/src/test/java/com/squareup/okhttp/internal/http/RawHeadersTest.java index 34f0985..377227f 100644 --- a/src/test/java/libcore/net/http/RawHeadersTest.java +++ b/src/test/java/com/squareup/okhttp/internal/http/RawHeadersTest.java @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; +import com.squareup.okhttp.internal.http.RawHeaders; import java.util.Arrays; import java.util.List; -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; +import org.junit.Test; -public final class RawHeadersTest extends TestCase { - public void testParseNameValueBlock() { +public final class RawHeadersTest { + @Test public void parseNameValueBlock() { List<String> nameValueBlock = Arrays.asList( "cache-control", "no-cache, no-store", @@ -42,7 +44,7 @@ public final class RawHeadersTest extends TestCase { assertEquals("200 OK", rawHeaders.getValue(3)); } - public void testToNameValueBlock() { + @Test public void toNameValueBlock() { RawHeaders rawHeaders = new RawHeaders(); rawHeaders.add("cache-control", "no-cache, no-store"); rawHeaders.add("set-cookie", "Cookie1"); diff --git a/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java b/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java new file mode 100644 index 0000000..14642e9 --- /dev/null +++ b/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java @@ -0,0 +1,373 @@ +/* + * 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.internal.http; + +import com.squareup.okhttp.Address; +import com.squareup.okhttp.Connection; +import com.squareup.okhttp.ConnectionPool; +import com.squareup.okhttp.internal.Dns; +import com.squareup.okhttp.internal.SslContextBuilder; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import static java.net.Proxy.NO_PROXY; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Test; + +public final class RouteSelectorTest { + private static final int proxyAPort = 1001; + private static final String proxyAHost = "proxyA"; + private static final Proxy proxyA + = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyAHost, proxyAPort)); + private static final int proxyBPort = 1002; + private static final String proxyBHost = "proxyB"; + private static final Proxy proxyB + = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyBHost, proxyBPort)); + private static final URI uri; + private static final String uriHost = "hostA"; + private static final int uriPort = 80; + + private static final SSLContext sslContext; + private static final SSLSocketFactory socketFactory; + private static final HostnameVerifier hostnameVerifier; + private static final ConnectionPool pool; + static { + try { + uri = new URI("http://" + uriHost + ":" + uriPort + "/path"); + sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName()).build(); + socketFactory = sslContext.getSocketFactory(); + pool = ConnectionPool.getDefault(); + hostnameVerifier = HttpsURLConnectionImpl.getDefaultHostnameVerifier(); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + private final FakeDns dns = new FakeDns(); + private final FakeProxySelector proxySelector = new FakeProxySelector(); + + @Test public void singleRoute() throws Exception { + Address address = new Address(uriHost, uriPort, null, null, null); + RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns); + + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(255, 1); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[0], uriPort, false); + dns.assertRequests(uriHost); + + assertFalse(routeSelector.hasNext()); + try { + routeSelector.next(); + fail(); + } catch (NoSuchElementException expected) { + } + } + + @Test public void explicitProxyTriesThatProxiesAddressesOnly() throws Exception { + Address address = new Address(uriHost, uriPort, null, null, proxyA); + RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns); + + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(255, 2); + assertConnection(routeSelector.next(), + address, proxyA, dns.inetAddresses[0], proxyAPort, false); + assertConnection(routeSelector.next(), + address, proxyA, dns.inetAddresses[1], proxyAPort, false); + + assertFalse(routeSelector.hasNext()); + dns.assertRequests(proxyAHost); + proxySelector.assertRequests(); // No proxy selector requests! + } + + @Test public void explicitDirectProxy() throws Exception { + Address address = new Address(uriHost, uriPort, null, null, NO_PROXY); + RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns); + + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(255, 2); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[0], uriPort, false); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[1], uriPort, false); + + assertFalse(routeSelector.hasNext()); + dns.assertRequests(uri.getHost()); + proxySelector.assertRequests(); // No proxy selector requests! + } + + @Test public void proxySelectorReturnsNull() throws Exception { + Address address = new Address(uriHost, uriPort, null, null, null); + + proxySelector.proxies = null; + RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns); + proxySelector.assertRequests(uri); + + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(255, 1); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[0], uriPort, false); + dns.assertRequests(uriHost); + + assertFalse(routeSelector.hasNext()); + } + + @Test public void proxySelectorReturnsNoProxies() throws Exception { + Address address = new Address(uriHost, uriPort, null, null, null); + RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns); + + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(255, 2); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[0], uriPort, false); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[1], uriPort, false); + + assertFalse(routeSelector.hasNext()); + dns.assertRequests(uri.getHost()); + proxySelector.assertRequests(uri); + } + + @Test public void proxySelectorReturnsMultipleProxies() throws Exception { + Address address = new Address(uriHost, uriPort, null, null, null); + + proxySelector.proxies.add(proxyA); + proxySelector.proxies.add(proxyB); + RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns); + proxySelector.assertRequests(uri); + + // First try the IP addresses of the first proxy, in sequence. + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(255, 2); + assertConnection(routeSelector.next(), + address, proxyA, dns.inetAddresses[0], proxyAPort, false); + assertConnection(routeSelector.next(), + address, proxyA, dns.inetAddresses[1], proxyAPort, false); + dns.assertRequests(proxyAHost); + + // Next try the IP address of the second proxy. + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(254, 1); + assertConnection(routeSelector.next(), + address, proxyB, dns.inetAddresses[0], proxyBPort, false); + dns.assertRequests(proxyBHost); + + // Finally try the only IP address of the origin server. + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(253, 1); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[0], uriPort, false); + dns.assertRequests(uriHost); + + assertFalse(routeSelector.hasNext()); + } + + @Test public void proxySelectorDirectConnectionsAreSkipped() throws Exception { + Address address = new Address(uriHost, uriPort, null, null, null); + + proxySelector.proxies.add(NO_PROXY); + RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns); + proxySelector.assertRequests(uri); + + // Only the origin server will be attempted. + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(255, 1); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[0], uriPort, false); + dns.assertRequests(uriHost); + + assertFalse(routeSelector.hasNext()); + } + + @Test public void proxyDnsFailureContinuesToNextProxy() throws Exception { + Address address = new Address(uriHost, uriPort, null, null, null); + + proxySelector.proxies.add(proxyA); + proxySelector.proxies.add(proxyB); + proxySelector.proxies.add(proxyA); + RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns); + proxySelector.assertRequests(uri); + + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(255, 1); + assertConnection(routeSelector.next(), + address, proxyA, dns.inetAddresses[0], proxyAPort, false); + dns.assertRequests(proxyAHost); + + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = null; + try { + routeSelector.next(); + fail(); + } catch (UnknownHostException expected) { + } + dns.assertRequests(proxyBHost); + + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(255, 1); + assertConnection(routeSelector.next(), + address, proxyA, dns.inetAddresses[0], proxyAPort, false); + dns.assertRequests(proxyAHost); + + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(254, 1); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[0], uriPort, false); + dns.assertRequests(uriHost); + + assertFalse(routeSelector.hasNext()); + } + + @Test public void multipleTlsModes() throws Exception { + Address address = new Address( + uriHost, uriPort, socketFactory, hostnameVerifier, Proxy.NO_PROXY); + RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns); + + assertTrue(routeSelector.hasNext()); + dns.inetAddresses = makeFakeAddresses(255, 1); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[0], uriPort, true); + dns.assertRequests(uriHost); + + assertTrue(routeSelector.hasNext()); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[0], uriPort, false); + dns.assertRequests(); // No more DNS requests since the previous! + + assertFalse(routeSelector.hasNext()); + } + + @Test public void multipleProxiesMultipleInetAddressesMultipleTlsModes() throws Exception { + Address address = new Address( + uriHost, uriPort, socketFactory, hostnameVerifier, null); + proxySelector.proxies.add(proxyA); + proxySelector.proxies.add(proxyB); + RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns); + + // Proxy A + dns.inetAddresses = makeFakeAddresses(255, 2); + assertConnection(routeSelector.next(), + address, proxyA, dns.inetAddresses[0], proxyAPort, true); + dns.assertRequests(proxyAHost); + assertConnection(routeSelector.next(), + address, proxyA, dns.inetAddresses[0], proxyAPort, false); + assertConnection(routeSelector.next(), + address, proxyA, dns.inetAddresses[1], proxyAPort, true); + assertConnection(routeSelector.next(), + address, proxyA, dns.inetAddresses[1], proxyAPort, false); + + // Proxy B + dns.inetAddresses = makeFakeAddresses(254, 2); + assertConnection(routeSelector.next(), + address, proxyB, dns.inetAddresses[0], proxyBPort, true); + dns.assertRequests(proxyBHost); + assertConnection(routeSelector.next(), + address, proxyB, dns.inetAddresses[0], proxyBPort, false); + assertConnection(routeSelector.next(), + address, proxyB, dns.inetAddresses[1], proxyBPort, true); + assertConnection(routeSelector.next(), + address, proxyB, dns.inetAddresses[1], proxyBPort, false); + + // Origin + dns.inetAddresses = makeFakeAddresses(253, 2); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[0], uriPort, true); + dns.assertRequests(uriHost); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[0], uriPort, false); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[1], uriPort, true); + assertConnection(routeSelector.next(), + address, NO_PROXY, dns.inetAddresses[1], uriPort, false); + + assertFalse(routeSelector.hasNext()); + } + + private void assertConnection(Connection connection, Address address, + Proxy proxy, InetAddress socketAddress, int socketPort, boolean modernTls) { + assertEquals(address, connection.getAddress()); + assertEquals(proxy, connection.getProxy()); + assertEquals(socketAddress, connection.getSocketAddress().getAddress()); + assertEquals(socketPort, connection.getSocketAddress().getPort()); + assertEquals(modernTls, connection.isModernTls()); + } + + private static InetAddress[] makeFakeAddresses(int prefix, int count) { + try { + InetAddress[] result = new InetAddress[count]; + for (int i = 0; i < count; i++) { + result[i] = InetAddress.getByAddress( + new byte[] { (byte) prefix, (byte) 0, (byte) 0, (byte) i }); + } + return result; + } catch (UnknownHostException e) { + throw new AssertionError(); + } + } + + private static class FakeDns implements Dns { + List<String> requestedHosts = new ArrayList<String>(); + InetAddress[] inetAddresses; + + @Override public InetAddress[] getAllByName(String host) throws UnknownHostException { + requestedHosts.add(host); + if (inetAddresses == null) throw new UnknownHostException(); + return inetAddresses; + } + + public void assertRequests(String... expectedHosts) { + assertEquals(Arrays.asList(expectedHosts), requestedHosts); + requestedHosts.clear(); + } + } + + private static class FakeProxySelector extends ProxySelector { + List<URI> requestedUris = new ArrayList<URI>(); + List<Proxy> proxies = new ArrayList<Proxy>(); + List<String> failures = new ArrayList<String>(); + + @Override public List<Proxy> select(URI uri) { + requestedUris.add(uri); + return proxies; + } + + public void assertRequests(URI... expectedUris) { + assertEquals(Arrays.asList(expectedUris), requestedUris); + requestedUris.clear(); + } + + @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + InetSocketAddress socketAddress = (InetSocketAddress) sa; + failures.add(String.format("%s %s:%d %s", uri, socketAddress.getHostName(), + socketAddress.getPort(), ioe.getMessage())); + } + } +} diff --git a/src/test/java/libcore/net/http/URLConnectionTest.java b/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java index 53f521b..4d15189 100644 --- a/src/test/java/libcore/net/http/URLConnectionTest.java +++ b/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java @@ -14,14 +14,19 @@ * limitations under the License. */ -package libcore.net.http; +package com.squareup.okhttp.internal.http; import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.MockWebServer; import com.google.mockwebserver.RecordedRequest; import com.google.mockwebserver.SocketPolicy; -import com.squareup.okhttp.OkHttpConnection; -import com.squareup.okhttp.OkHttpsConnection; +import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END; +import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START; +import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END; +import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.internal.http.HttpResponseCache; +import com.squareup.okhttp.internal.SslContextBuilder; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -37,10 +42,11 @@ import java.net.InetAddress; import java.net.PasswordAuthentication; import java.net.ProtocolException; import java.net.Proxy; +import java.net.ProxySelector; import java.net.ResponseCache; +import java.net.SocketAddress; import java.net.SocketTimeoutException; import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.net.UnknownHostException; @@ -57,40 +63,44 @@ import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; -import junit.framework.TestCase; -import libcore.net.ssl.SslContextBuilder; - -import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END; -import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START; -import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END; -import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END; +import org.junit.After; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; /** * Android's URLConnectionTest. */ -public final class URLConnectionTest extends TestCase { +public final class URLConnectionTest { /** base64("username:password") */ private static final String BASE_64_CREDENTIALS = "dXNlcm5hbWU6cGFzc3dvcmQ="; private MockWebServer server = new MockWebServer(); + private MockWebServer server2 = new MockWebServer(); + + private final OkHttpClient client = new OkHttpClient(); private HttpResponseCache cache; private String hostName; private static final SSLContext sslContext; - static { try { - sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName()) - .build(); + sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName()).build(); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } catch (UnknownHostException e) { @@ -98,14 +108,11 @@ public final class URLConnectionTest extends TestCase { } } - @Override protected void setUp() throws Exception { - super.setUp(); + @Before public void setUp() throws Exception { hostName = server.getHostName(); } - @Override protected void tearDown() throws Exception { - ResponseCache.setDefault(null); - Authenticator.setDefault(null); + @After public void tearDown() throws Exception { System.clearProperty("proxyHost"); System.clearProperty("proxyPort"); System.clearProperty("http.proxyHost"); @@ -113,25 +120,17 @@ public final class URLConnectionTest extends TestCase { System.clearProperty("https.proxyHost"); System.clearProperty("https.proxyPort"); server.shutdown(); + server2.shutdown(); if (cache != null) { cache.getCache().delete(); } - super.tearDown(); - } - - private static OkHttpConnection openConnection(URL url) { - return OkHttpConnection.open(url); } - private static OkHttpConnection openConnection(URL url, Proxy proxy) { - return OkHttpConnection.open(url, proxy); - } - - public void testRequestHeaders() throws IOException, InterruptedException { + @Test public void requestHeaders() throws IOException, InterruptedException { server.enqueue(new MockResponse()); server.play(); - OkHttpConnection urlConnection = openConnection(server.getUrl("/")); + HttpURLConnection urlConnection = client.open(server.getUrl("/")); urlConnection.addRequestProperty("D", "e"); urlConnection.addRequestProperty("D", "f"); assertEquals("f", urlConnection.getRequestProperty("D")); @@ -190,15 +189,15 @@ public final class URLConnectionTest extends TestCase { } } - public void testGetRequestPropertyReturnsLastValue() throws Exception { + @Test public void getRequestPropertyReturnsLastValue() throws Exception { server.play(); - OkHttpConnection urlConnection = openConnection(server.getUrl("/")); + HttpURLConnection urlConnection = client.open(server.getUrl("/")); urlConnection.addRequestProperty("A", "value1"); urlConnection.addRequestProperty("A", "value2"); assertEquals("value2", urlConnection.getRequestProperty("A")); } - public void testResponseHeaders() throws IOException, InterruptedException { + @Test public void responseHeaders() throws IOException, InterruptedException { server.enqueue(new MockResponse() .setStatus("HTTP/1.0 200 Fantastic") .addHeader("A: c") @@ -207,7 +206,7 @@ public final class URLConnectionTest extends TestCase { .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8)); server.play(); - OkHttpConnection urlConnection = openConnection(server.getUrl("/")); + HttpURLConnection urlConnection = client.open(server.getUrl("/")); assertEquals(200, urlConnection.getResponseCode()); assertEquals("Fantastic", urlConnection.getResponseMessage()); assertEquals("HTTP/1.0 200 Fantastic", urlConnection.getHeaderField(null)); @@ -233,11 +232,11 @@ public final class URLConnectionTest extends TestCase { assertEquals("e", urlConnection.getHeaderField(2)); } - public void testServerSendsInvalidResponseHeaders() throws Exception { + @Test public void serverSendsInvalidResponseHeaders() throws Exception { server.enqueue(new MockResponse().setStatus("HTP/1.1 200 OK")); server.play(); - OkHttpConnection urlConnection = openConnection(server.getUrl("/")); + HttpURLConnection urlConnection = client.open(server.getUrl("/")); try { urlConnection.getResponseCode(); fail(); @@ -245,11 +244,11 @@ public final class URLConnectionTest extends TestCase { } } - public void testServerSendsInvalidCodeTooLarge() throws Exception { + @Test public void serverSendsInvalidCodeTooLarge() throws Exception { server.enqueue(new MockResponse().setStatus("HTTP/1.1 2147483648 OK")); server.play(); - OkHttpConnection urlConnection = openConnection(server.getUrl("/")); + HttpURLConnection urlConnection = client.open(server.getUrl("/")); try { urlConnection.getResponseCode(); fail(); @@ -257,11 +256,11 @@ public final class URLConnectionTest extends TestCase { } } - public void testServerSendsInvalidCodeNotANumber() throws Exception { + @Test public void serverSendsInvalidCodeNotANumber() throws Exception { server.enqueue(new MockResponse().setStatus("HTTP/1.1 00a OK")); server.play(); - OkHttpConnection urlConnection = openConnection(server.getUrl("/")); + HttpURLConnection urlConnection = client.open(server.getUrl("/")); try { urlConnection.getResponseCode(); fail(); @@ -269,11 +268,11 @@ public final class URLConnectionTest extends TestCase { } } - public void testServerSendsUnnecessaryWhitespace() throws Exception { + @Test public void serverSendsUnnecessaryWhitespace() throws Exception { server.enqueue(new MockResponse().setStatus(" HTTP/1.1 2147483648 OK")); server.play(); - OkHttpConnection urlConnection = openConnection(server.getUrl("/")); + HttpURLConnection urlConnection = client.open(server.getUrl("/")); try { urlConnection.getResponseCode(); fail(); @@ -281,42 +280,87 @@ public final class URLConnectionTest extends TestCase { } } - public void testGetErrorStreamOnSuccessfulRequest() throws Exception { + @Test public void connectRetriesUntilConnectedOrFailed() throws Exception { + server.play(); + URL url = server.getUrl("/foo"); + server.shutdown(); + + HttpURLConnection connection = client.open(url); + try { + connection.connect(); + fail(); + } catch (IOException expected) { + } + } + + @Test public void requestBodySurvivesRetriesWithFixedLength() throws Exception { + testRequestBodySurvivesRetries(TransferKind.FIXED_LENGTH); + } + + @Test public void requestBodySurvivesRetriesWithChunkedStreaming() throws Exception { + testRequestBodySurvivesRetries(TransferKind.CHUNKED); + } + + @Test public void requestBodySurvivesRetriesWithBufferedBody() throws Exception { + testRequestBodySurvivesRetries(TransferKind.END_OF_STREAM); + } + + private void testRequestBodySurvivesRetries(TransferKind transferKind) throws Exception { + server.enqueue(new MockResponse().setBody("abc")); + server.play(); + + // Use a misconfigured proxy to guarantee that the request is retried. + server2.play(); + FakeProxySelector proxySelector = new FakeProxySelector(); + proxySelector.proxies.add(server2.toProxyAddress()); + client.setProxySelector(proxySelector); + server2.shutdown(); + + HttpURLConnection connection = client.open(server.getUrl("/def")); + connection.setDoOutput(true); + transferKind.setForRequest(connection, 4); + connection.getOutputStream().write("body".getBytes("UTF-8")); + assertContent("abc", connection); + + assertEquals("body", server.takeRequest().getUtf8Body()); + } + + @Test public void getErrorStreamOnSuccessfulRequest() throws Exception { server.enqueue(new MockResponse().setBody("A")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); assertNull(connection.getErrorStream()); } - public void testGetErrorStreamOnUnsuccessfulRequest() throws Exception { + @Test public void getErrorStreamOnUnsuccessfulRequest() throws Exception { server.enqueue(new MockResponse().setResponseCode(404).setBody("A")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); assertEquals("A", readAscii(connection.getErrorStream(), Integer.MAX_VALUE)); } // Check that if we don't read to the end of a response, the next request on the // recycled connection doesn't get the unread tail of the first request's response. // http://code.google.com/p/android/issues/detail?id=2939 - public void test_2939() throws Exception { + @Test public void bug2939() throws Exception { MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8); server.enqueue(response); server.enqueue(response); server.play(); - assertContent("ABCDE", openConnection(server.getUrl("/")), 5); - assertContent("ABCDE", openConnection(server.getUrl("/")), 5); + assertContent("ABCDE", client.open(server.getUrl("/")), 5); + assertContent("ABCDE", client.open(server.getUrl("/")), 5); } // Check that we recognize a few basic mime types by extension. // http://code.google.com/p/android/issues/detail?id=10100 - public void test_10100() throws Exception { + @Test public void bug10100() throws Exception { assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg")); assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf")); } - public void testConnectionsArePooled() throws Exception { + @Test public void connectionsArePooled() throws Exception { MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"); server.enqueue(response); @@ -324,15 +368,15 @@ public final class URLConnectionTest extends TestCase { server.enqueue(response); server.play(); - assertContent("ABCDEFGHIJKLMNOPQR", openConnection(server.getUrl("/foo"))); + assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/foo"))); assertEquals(0, server.takeRequest().getSequenceNumber()); - assertContent("ABCDEFGHIJKLMNOPQR", openConnection(server.getUrl("/bar?baz=quux"))); + assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/bar?baz=quux"))); assertEquals(1, server.takeRequest().getSequenceNumber()); - assertContent("ABCDEFGHIJKLMNOPQR", openConnection(server.getUrl("/z"))); + assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/z"))); assertEquals(2, server.takeRequest().getSequenceNumber()); } - public void testChunkedConnectionsArePooled() throws Exception { + @Test public void chunkedConnectionsArePooled() throws Exception { MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5); server.enqueue(response); @@ -340,23 +384,23 @@ public final class URLConnectionTest extends TestCase { server.enqueue(response); server.play(); - assertContent("ABCDEFGHIJKLMNOPQR", openConnection(server.getUrl("/foo"))); + assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/foo"))); assertEquals(0, server.takeRequest().getSequenceNumber()); - assertContent("ABCDEFGHIJKLMNOPQR", openConnection(server.getUrl("/bar?baz=quux"))); + assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/bar?baz=quux"))); assertEquals(1, server.takeRequest().getSequenceNumber()); - assertContent("ABCDEFGHIJKLMNOPQR", openConnection(server.getUrl("/z"))); + assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/z"))); assertEquals(2, server.takeRequest().getSequenceNumber()); } - public void testServerClosesSocket() throws Exception { + @Test public void serverClosesSocket() throws Exception { testServerClosesOutput(DISCONNECT_AT_END); } - public void testServerShutdownInput() throws Exception { + @Test public void serverShutdownInput() throws Exception { testServerClosesOutput(SHUTDOWN_INPUT_AT_END); } - public void SUPPRESSED_testServerShutdownOutput() throws Exception { + @Test public void serverShutdownOutput() throws Exception { testServerClosesOutput(SHUTDOWN_OUTPUT_AT_END); } @@ -364,40 +408,52 @@ public final class URLConnectionTest extends TestCase { server.enqueue(new MockResponse() .setBody("This connection won't pool properly") .setSocketPolicy(socketPolicy)); - server.enqueue(new MockResponse() - .setBody("This comes after a busted connection")); + MockResponse responseAfter = new MockResponse() + .setBody("This comes after a busted connection"); + server.enqueue(responseAfter); + server.enqueue(responseAfter); // Enqueue 2x because the broken connection may be reused. server.play(); - assertContent("This connection won't pool properly", openConnection(server.getUrl("/a"))); + HttpURLConnection connection1 = client.open(server.getUrl("/a")); + connection1.setReadTimeout(100); + assertContent("This connection won't pool properly", connection1); assertEquals(0, server.takeRequest().getSequenceNumber()); - assertContent("This comes after a busted connection", openConnection(server.getUrl("/b"))); + HttpURLConnection connection2 = client.open(server.getUrl("/b")); + connection2.setReadTimeout(100); + assertContent("This comes after a busted connection", connection2); + + // Check that a fresh connection was created, either immediately or after attempting reuse. + RecordedRequest requestAfter = server.takeRequest(); + if (server.getRequestCount() == 3) { + requestAfter = server.takeRequest(); // The failure consumed a response. + } // sequence number 0 means the HTTP socket connection was not reused - assertEquals(0, server.takeRequest().getSequenceNumber()); + assertEquals(0, requestAfter.getSequenceNumber()); } enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS } - public void test_chunkedUpload_byteByByte() throws Exception { + @Test public void chunkedUpload_byteByByte() throws Exception { doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE); } - public void test_chunkedUpload_smallBuffers() throws Exception { + @Test public void chunkedUpload_smallBuffers() throws Exception { doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS); } - public void test_chunkedUpload_largeBuffers() throws Exception { + @Test public void chunkedUpload_largeBuffers() throws Exception { doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS); } - public void SUPPRESSED_test_fixedLengthUpload_byteByByte() throws Exception { + @Test public void fixedLengthUpload_byteByByte() throws Exception { doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE); } - public void test_fixedLengthUpload_smallBuffers() throws Exception { + @Test public void fixedLengthUpload_smallBuffers() throws Exception { doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS); } - public void test_fixedLengthUpload_largeBuffers() throws Exception { + @Test public void fixedLengthUpload_largeBuffers() throws Exception { doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS); } @@ -407,7 +463,7 @@ public final class URLConnectionTest extends TestCase { server.enqueue(new MockResponse()); server.play(); - OkHttpConnection conn = openConnection(server.getUrl("/")); + HttpURLConnection conn = client.open(server.getUrl("/")); conn.setDoOutput(true); conn.setRequestMethod("POST"); if (uploadKind == TransferKind.CHUNKED) { @@ -438,13 +494,13 @@ public final class URLConnectionTest extends TestCase { } } - public void testGetResponseCodeNoResponseBody() throws Exception { + @Test public void getResponseCodeNoResponseBody() throws Exception { server.enqueue(new MockResponse() .addHeader("abc: def")); server.play(); URL url = server.getUrl("/"); - OkHttpConnection conn = openConnection(url); + HttpURLConnection conn = client.open(url); conn.setDoInput(false); assertEquals("def", conn.getHeaderField("abc")); assertEquals(200, conn.getResponseCode()); @@ -455,14 +511,14 @@ public final class URLConnectionTest extends TestCase { } } - public void testConnectViaHttps() throws Exception { + @Test public void connectViaHttps() throws Exception { server.useHttps(sslContext.getSocketFactory(), false); server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); server.play(); - OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/foo")); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection.setHostnameVerifier(new RecordingHostnameVerifier()); + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(new RecordingHostnameVerifier()); + HttpURLConnection connection = client.open(server.getUrl("/foo")); assertContent("this response comes via HTTPS", connection); @@ -470,7 +526,7 @@ public final class URLConnectionTest extends TestCase { assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); } - public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException { + @Test public void connectViaHttpsReusingConnections() throws IOException, InterruptedException { server.useHttps(sslContext.getSocketFactory(), false); server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); server.enqueue(new MockResponse().setBody("another response via HTTPS")); @@ -480,21 +536,19 @@ public final class URLConnectionTest extends TestCase { SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory(); RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); - OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/")); - connection.setSSLSocketFactory(clientSocketFactory); - connection.setHostnameVerifier(hostnameVerifier); + client.setSSLSocketFactory(clientSocketFactory); + client.setHostnameVerifier(hostnameVerifier); + HttpURLConnection connection = client.open(server.getUrl("/")); assertContent("this response comes via HTTPS", connection); - connection = (OkHttpsConnection) openConnection(server.getUrl("/")); - connection.setSSLSocketFactory(clientSocketFactory); - connection.setHostnameVerifier(hostnameVerifier); + connection = client.open(server.getUrl("/")); assertContent("another response via HTTPS", connection); assertEquals(0, server.takeRequest().getSequenceNumber()); assertEquals(1, server.takeRequest().getSequenceNumber()); } - public void testConnectViaHttpsReusingConnectionsDifferentFactories() + @Test public void connectViaHttpsReusingConnectionsDifferentFactories() throws IOException, InterruptedException { server.useHttps(sslContext.getSocketFactory(), false); server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); @@ -502,28 +556,29 @@ public final class URLConnectionTest extends TestCase { server.play(); // install a custom SSL socket factory so the server can be authorized - OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/")); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection.setHostnameVerifier(new RecordingHostnameVerifier()); - assertContent("this response comes via HTTPS", connection); + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(new RecordingHostnameVerifier()); + HttpURLConnection connection1 = client.open(server.getUrl("/")); + assertContent("this response comes via HTTPS", connection1); - connection = (OkHttpsConnection) openConnection(server.getUrl("/")); + client.setSSLSocketFactory(null); + HttpURLConnection connection2 = client.open(server.getUrl("/")); try { - readAscii(connection.getInputStream(), Integer.MAX_VALUE); + readAscii(connection2.getInputStream(), Integer.MAX_VALUE); fail("without an SSL socket factory, the connection should fail"); } catch (SSLException expected) { } } - public void testConnectViaHttpsWithSSLFallback() throws IOException, InterruptedException { + @Test public void connectViaHttpsWithSSLFallback() throws IOException, InterruptedException { server.useHttps(sslContext.getSocketFactory(), false); server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE)); server.enqueue(new MockResponse().setBody("this response comes via SSL")); server.play(); - OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/foo")); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection.setHostnameVerifier(new RecordingHostnameVerifier()); + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(new RecordingHostnameVerifier()); + HttpURLConnection connection = client.open(server.getUrl("/foo")); assertContent("this response comes via SSL", connection); @@ -536,34 +591,30 @@ public final class URLConnectionTest extends TestCase { * * http://code.google.com/p/android/issues/detail?id=13178 */ -// public void testConnectViaHttpsToUntrustedServer() throws IOException, InterruptedException { -// TestSSLContext testSSLContext = TestSSLContext.create(TestKeyStore.getClientCA2(), -// TestKeyStore.getServer()); -// -// server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); -// server.enqueue(new MockResponse()); // unused -// server.play(); -// -// HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); -// connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); -// try { -// connection.getInputStream(); -// fail(); -// } catch (SSLHandshakeException expected) { -// assertTrue(expected.getCause() instanceof CertificateException); -// } -// assertEquals(0, server.getRequestCount()); -// } - - public void testConnectViaProxyUsingProxyArg() throws Exception { + @Test public void connectViaHttpsToUntrustedServer() throws IOException, InterruptedException { + server.useHttps(sslContext.getSocketFactory(), false); + server.enqueue(new MockResponse()); // unused + server.play(); + + HttpURLConnection connection = client.open(server.getUrl("/foo")); + try { + connection.getInputStream(); + fail(); + } catch (SSLHandshakeException expected) { + assertTrue(expected.getCause() instanceof CertificateException); + } + assertEquals(0, server.getRequestCount()); + } + + @Test public void connectViaProxyUsingProxyArg() throws Exception { testConnectViaProxy(ProxyConfig.CREATE_ARG); } - public void testConnectViaProxyUsingProxySystemProperty() throws Exception { + @Test public void connectViaProxyUsingProxySystemProperty() throws Exception { testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY); } - public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception { + @Test public void connectViaProxyUsingHttpProxySystemProperty() throws Exception { testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); } @@ -573,7 +624,7 @@ public final class URLConnectionTest extends TestCase { server.play(); URL url = new URL("http://android.com/foo"); - OkHttpConnection connection = proxyConfig.connect(server, url); + HttpURLConnection connection = proxyConfig.connect(server, client, url); assertContent("this response comes via a proxy", connection); RecordedRequest request = server.takeRequest(); @@ -581,17 +632,17 @@ public final class URLConnectionTest extends TestCase { assertContains(request.getHeaders(), "Host: android.com"); } - public void testContentDisagreesWithContentLengthHeader() throws IOException { + @Test public void contentDisagreesWithContentLengthHeader() throws IOException { server.enqueue(new MockResponse() .setBody("abc\r\nYOU SHOULD NOT SEE THIS") .clearHeaders() .addHeader("Content-Length: 3")); server.play(); - assertContent("abc", openConnection(server.getUrl("/"))); + assertContent("abc", client.open(server.getUrl("/"))); } - public void testContentDisagreesWithChunkedHeader() throws IOException { + @Test public void contentDisagreesWithChunkedHeader() throws IOException { MockResponse mockResponse = new MockResponse(); mockResponse.setChunkedBody("abc", 3); ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); @@ -604,14 +655,14 @@ public final class URLConnectionTest extends TestCase { server.enqueue(mockResponse); server.play(); - assertContent("abc", openConnection(server.getUrl("/"))); + assertContent("abc", client.open(server.getUrl("/"))); } - public void testConnectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception { + @Test public void connectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception { testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY); } - public void testConnectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception { + @Test public void connectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception { // https should not use http proxy testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); } @@ -622,9 +673,9 @@ public final class URLConnectionTest extends TestCase { server.play(); URL url = server.getUrl("/foo"); - OkHttpsConnection connection = (OkHttpsConnection) proxyConfig.connect(server, url); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection.setHostnameVerifier(new RecordingHostnameVerifier()); + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(new RecordingHostnameVerifier()); + HttpURLConnection connection = proxyConfig.connect(server, client, url); assertContent("this response comes via HTTPS", connection); @@ -632,7 +683,7 @@ public final class URLConnectionTest extends TestCase { assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); } - public void testConnectViaHttpProxyToHttpsUsingProxyArg() throws Exception { + @Test public void connectViaHttpProxyToHttpsUsingProxyArg() throws Exception { testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG); } @@ -640,11 +691,11 @@ public final class URLConnectionTest extends TestCase { * We weren't honoring all of the appropriate proxy system properties when * connecting via HTTPS. http://b/3097518 */ - public void testConnectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception { + @Test public void connectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception { testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY); } - public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception { + @Test public void connectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception { testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY); } @@ -663,9 +714,9 @@ public final class URLConnectionTest extends TestCase { server.play(); URL url = new URL("https://android.com/foo"); - OkHttpsConnection connection = (OkHttpsConnection) proxyConfig.connect(server, url); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection.setHostnameVerifier(hostnameVerifier); + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(hostnameVerifier); + HttpURLConnection connection = proxyConfig.connect(server, client, url); assertContent("this response comes via a secure proxy", connection); @@ -683,25 +734,31 @@ public final class URLConnectionTest extends TestCase { /** * Tolerate bad https proxy response when using HttpResponseCache. http://b/6754912 */ - public void testConnectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() throws Exception { - ProxyConfig proxyConfig = ProxyConfig.PROXY_SYSTEM_PROPERTY; - + @Test public void connectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() + throws Exception { initResponseCache(); server.useHttps(sslContext.getSocketFactory(), true); MockResponse response = new MockResponse() // Key to reproducing b/6754912 .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) .setBody("bogus proxy connect response content"); - server.enqueue(response); // For the first TLS tolerant connection - server.enqueue(response); // For the backwards-compatible SSLv3 retry + + // Enqueue a pair of responses for every IP address held by localhost, because the + // route selector will try each in sequence. + // TODO: use the fake Dns implementation instead of a loop + for (InetAddress inetAddress : InetAddress.getAllByName(server.getHostName())) { + server.enqueue(response); // For the first TLS tolerant connection + server.enqueue(response); // For the backwards-compatible SSLv3 retry + } server.play(); + client.setProxy(server.toProxyAddress()); URL url = new URL("https://android.com/foo"); - OkHttpsConnection connection = (OkHttpsConnection) proxyConfig.connect(server, url); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setSSLSocketFactory(sslContext.getSocketFactory()); + HttpURLConnection connection = client.open(url); try { - connection.connect(); + connection.getResponseCode(); fail(); } catch (IOException expected) { // Thrown when the connect causes SSLSocket.startHandshake() to throw @@ -719,13 +776,13 @@ public final class URLConnectionTest extends TestCase { String tmp = System.getProperty("java.io.tmpdir"); File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID()); cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE); - ResponseCache.setDefault(cache); + client.setResponseCache(cache); } /** * Test which headers are sent unencrypted to the HTTP proxy. */ - public void testProxyConnectIncludesProxyHeadersOnly() + @Test public void proxyConnectIncludesProxyHeadersOnly() throws IOException, InterruptedException { RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); @@ -735,15 +792,15 @@ public final class URLConnectionTest extends TestCase { .clearHeaders()); server.enqueue(new MockResponse().setBody("encrypted response from the origin server")); server.play(); + client.setProxy(server.toProxyAddress()); URL url = new URL("https://android.com/foo"); - OkHttpsConnection connection = (OkHttpsConnection) openConnection( - url, server.toProxyAddress()); + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(hostnameVerifier); + HttpURLConnection connection = client.open(url); connection.addRequestProperty("Private", "Secret"); connection.addRequestProperty("Proxy-Authorization", "bar"); connection.addRequestProperty("User-Agent", "baz"); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection.setHostnameVerifier(hostnameVerifier); assertContent("encrypted response from the origin server", connection); RecordedRequest connect = server.takeRequest(); @@ -758,7 +815,7 @@ public final class URLConnectionTest extends TestCase { assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); } - public void testProxyAuthenticateOnConnect() throws Exception { + @Test public void proxyAuthenticateOnConnect() throws Exception { Authenticator.setDefault(new RecordingAuthenticator()); server.useHttps(sslContext.getSocketFactory(), true); server.enqueue(new MockResponse() @@ -769,12 +826,12 @@ public final class URLConnectionTest extends TestCase { .clearHeaders()); server.enqueue(new MockResponse().setBody("A")); server.play(); + client.setProxy(server.toProxyAddress()); URL url = new URL("https://android.com/foo"); - OkHttpsConnection connection = (OkHttpsConnection) openConnection( - url, server.toProxyAddress()); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection.setHostnameVerifier(new RecordingHostnameVerifier()); + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(new RecordingHostnameVerifier()); + HttpURLConnection connection = client.open(url); assertContent("A", connection); RecordedRequest connect1 = server.takeRequest(); @@ -792,25 +849,25 @@ public final class URLConnectionTest extends TestCase { // Don't disconnect after building a tunnel with CONNECT // http://code.google.com/p/android/issues/detail?id=37221 - public void testProxyWithConnectionClose() throws IOException { + @Test public void proxyWithConnectionClose() throws IOException { server.useHttps(sslContext.getSocketFactory(), true); server.enqueue(new MockResponse() .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) .clearHeaders()); server.enqueue(new MockResponse().setBody("this response comes via a proxy")); server.play(); + client.setProxy(server.toProxyAddress()); URL url = new URL("https://android.com/foo"); - OkHttpsConnection connection = (OkHttpsConnection) openConnection( - url, server.toProxyAddress()); + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(new RecordingHostnameVerifier()); + HttpURLConnection connection = client.open(url); connection.setRequestProperty("Connection", "close"); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection.setHostnameVerifier(new RecordingHostnameVerifier()); assertContent("this response comes via a proxy", connection); } - public void testProxyWithConnectionReuse() throws IOException { + @Test public void proxyWithConnectionReuse() throws IOException { SSLSocketFactory socketFactory = sslContext.getSocketFactory(); RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); @@ -821,26 +878,20 @@ public final class URLConnectionTest extends TestCase { server.enqueue(new MockResponse().setBody("response 1")); server.enqueue(new MockResponse().setBody("response 2")); server.play(); + client.setProxy(server.toProxyAddress()); URL url = new URL("https://android.com/foo"); - OkHttpsConnection connection1 = (OkHttpsConnection) openConnection( - url, server.toProxyAddress()); - connection1.setSSLSocketFactory(socketFactory); - connection1.setHostnameVerifier(hostnameVerifier); - assertContent("response 1", connection1); - - OkHttpsConnection connection2 = (OkHttpsConnection) openConnection( - url, server.toProxyAddress()); - connection2.setSSLSocketFactory(socketFactory); - connection2.setHostnameVerifier(hostnameVerifier); - assertContent("response 2", connection2); + client.setSSLSocketFactory(socketFactory); + client.setHostnameVerifier(hostnameVerifier); + assertContent("response 1", client.open(url)); + assertContent("response 2", client.open(url)); } - public void testDisconnectedConnection() throws IOException { + @Test public void disconnectedConnection() throws IOException { server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); InputStream in = connection.getInputStream(); assertEquals('A', (char) in.read()); connection.disconnect(); @@ -851,62 +902,19 @@ public final class URLConnectionTest extends TestCase { } } - public void testDisconnectBeforeConnect() throws IOException { + @Test public void disconnectBeforeConnect() throws IOException { server.enqueue(new MockResponse().setBody("A")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); connection.disconnect(); assertContent("A", connection); assertEquals(200, connection.getResponseCode()); } -// public void testDisconnectAfterOnlyResponseCodeCausesNoCloseGuardWarning() throws IOException { -// CloseGuardGuard guard = new CloseGuardGuard(); -// try { -// server.enqueue(new MockResponse() -// .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) -// .addHeader("Content-Encoding: gzip")); -// server.play(); -// -// HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); -// assertEquals(200, connection.getResponseCode()); -// connection.disconnect(); -// connection = null; -// assertFalse(guard.wasCloseGuardCalled()); -// } finally { -// guard.close(); -// } -// } -// -// public static class CloseGuardGuard implements Closeable, CloseGuard.Reporter { -// private final CloseGuard.Reporter oldReporter = CloseGuard.getReporter(); -// -// private AtomicBoolean closeGuardCalled = new AtomicBoolean(); -// -// public CloseGuardGuard() { -// CloseGuard.setReporter(this); -// } -// -// @Override public void report(String message, Throwable allocationSite) { -// oldReporter.report(message, allocationSite); -// closeGuardCalled.set(true); -// } -// -// public boolean wasCloseGuardCalled() { -// // FinalizationTester.induceFinalization(); -// close(); -// return closeGuardCalled.get(); -// } -// -// @Override public void close() { -// CloseGuard.setReporter(oldReporter); -// } -// -// } - - public void testDefaultRequestProperty() throws Exception { + @SuppressWarnings("deprecation") + @Test public void defaultRequestProperty() throws Exception { URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A"); assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty")); } @@ -929,15 +937,15 @@ public final class URLConnectionTest extends TestCase { return result.toString(); } - public void testMarkAndResetWithContentLengthHeader() throws IOException { + @Test public void markAndResetWithContentLengthHeader() throws IOException { testMarkAndReset(TransferKind.FIXED_LENGTH); } - public void testMarkAndResetWithChunkedEncoding() throws IOException { + @Test public void markAndResetWithChunkedEncoding() throws IOException { testMarkAndReset(TransferKind.CHUNKED); } - public void testMarkAndResetWithNoLengthHeaders() throws IOException { + @Test public void markAndResetWithNoLengthHeaders() throws IOException { testMarkAndReset(TransferKind.END_OF_STREAM); } @@ -948,7 +956,7 @@ public final class URLConnectionTest extends TestCase { server.enqueue(response); server.play(); - InputStream in = openConnection(server.getUrl("/")).getInputStream(); + InputStream in = client.open(server.getUrl("/")).getInputStream(); assertFalse("This implementation claims to support mark().", in.markSupported()); in.mark(5); assertEquals("ABCDE", readAscii(in, 5)); @@ -958,7 +966,7 @@ public final class URLConnectionTest extends TestCase { } catch (IOException expected) { } assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE)); - assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", openConnection(server.getUrl("/"))); + assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", client.open(server.getUrl("/"))); } /** @@ -966,7 +974,7 @@ public final class URLConnectionTest extends TestCase { * code 401. This causes a new HTTP request to be issued for every call into * the URLConnection. */ - public void SUPPRESSED_testUnauthorizedResponseHandling() throws IOException { + @Test public void unauthorizedResponseHandling() throws IOException { MockResponse response = new MockResponse() .addHeader("WWW-Authenticate: challenge") .setResponseCode(401) // UNAUTHORIZED @@ -977,7 +985,7 @@ public final class URLConnectionTest extends TestCase { server.play(); URL url = server.getUrl("/"); - OkHttpConnection conn = openConnection(url); + HttpURLConnection conn = client.open(url); assertEquals(401, conn.getResponseCode()); assertEquals(401, conn.getResponseCode()); @@ -985,14 +993,14 @@ public final class URLConnectionTest extends TestCase { assertEquals(1, server.getRequestCount()); } - public void testNonHexChunkSize() throws IOException { + @Test public void nonHexChunkSize() throws IOException { server.enqueue(new MockResponse() .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n") .clearHeaders() .addHeader("Transfer-encoding: chunked")); server.play(); - URLConnection connection = openConnection(server.getUrl("/")); + URLConnection connection = client.open(server.getUrl("/")); try { readAscii(connection.getInputStream(), Integer.MAX_VALUE); fail(); @@ -1000,7 +1008,7 @@ public final class URLConnectionTest extends TestCase { } } - public void testMissingChunkBody() throws IOException { + @Test public void missingChunkBody() throws IOException { server.enqueue(new MockResponse() .setBody("5") .clearHeaders() @@ -1008,7 +1016,7 @@ public final class URLConnectionTest extends TestCase { .setSocketPolicy(DISCONNECT_AT_END)); server.play(); - URLConnection connection = openConnection(server.getUrl("/")); + URLConnection connection = client.open(server.getUrl("/")); try { readAscii(connection.getInputStream(), Integer.MAX_VALUE); fail(); @@ -1021,13 +1029,13 @@ public final class URLConnectionTest extends TestCase { * behavior in not required by the API, so a failure of this test does not * imply a bug in the implementation. */ - public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException { + @Test public void gzipEncodingEnabledByDefault() throws IOException, InterruptedException { server.enqueue(new MockResponse() .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) .addHeader("Content-Encoding: gzip")); server.play(); - URLConnection connection = openConnection(server.getUrl("/")); + URLConnection connection = client.open(server.getUrl("/")); assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); assertNull(connection.getContentEncoding()); @@ -1035,13 +1043,13 @@ public final class URLConnectionTest extends TestCase { assertContains(request.getHeaders(), "Accept-Encoding: gzip"); } - public void testClientConfiguredGzipContentEncoding() throws Exception { + @Test public void clientConfiguredGzipContentEncoding() throws Exception { server.enqueue(new MockResponse() .setBody(gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8"))) .addHeader("Content-Encoding: gzip")); server.play(); - URLConnection connection = openConnection(server.getUrl("/")); + URLConnection connection = client.open(server.getUrl("/")); connection.addRequestProperty("Accept-Encoding", "gzip"); InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE)); @@ -1050,29 +1058,29 @@ public final class URLConnectionTest extends TestCase { assertContains(request.getHeaders(), "Accept-Encoding: gzip"); } - public void testGzipAndConnectionReuseWithFixedLength() throws Exception { + @Test public void gzipAndConnectionReuseWithFixedLength() throws Exception { testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, false); } - public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception { + @Test public void gzipAndConnectionReuseWithChunkedEncoding() throws Exception { testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, false); } - public void testGzipAndConnectionReuseWithFixedLengthAndTls() throws Exception { + @Test public void gzipAndConnectionReuseWithFixedLengthAndTls() throws Exception { testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, true); } - public void testGzipAndConnectionReuseWithChunkedEncodingAndTls() throws Exception { + @Test public void gzipAndConnectionReuseWithChunkedEncodingAndTls() throws Exception { testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, true); } - public void testClientConfiguredCustomContentEncoding() throws Exception { + @Test public void clientConfiguredCustomContentEncoding() throws Exception { server.enqueue(new MockResponse() .setBody("ABCDE") .addHeader("Content-Encoding: custom")); server.play(); - URLConnection connection = openConnection(server.getUrl("/")); + URLConnection connection = client.open(server.getUrl("/")); connection.addRequestProperty("Accept-Encoding", "custom"); assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); @@ -1088,12 +1096,12 @@ public final class URLConnectionTest extends TestCase { */ private void testClientConfiguredGzipContentEncodingAndConnectionReuse( TransferKind transferKind, boolean tls) throws Exception { - SSLSocketFactory socketFactory = null; - RecordingHostnameVerifier hostnameVerifier = null; if (tls) { - socketFactory = sslContext.getSocketFactory(); - hostnameVerifier = new RecordingHostnameVerifier(); + SSLSocketFactory socketFactory = sslContext.getSocketFactory(); + RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); server.useHttps(socketFactory, false); + client.setSSLSocketFactory(socketFactory); + client.setHostnameVerifier(hostnameVerifier); } MockResponse responseOne = new MockResponse(); @@ -1105,36 +1113,61 @@ public final class URLConnectionTest extends TestCase { server.enqueue(responseTwo); server.play(); - URLConnection connection = openConnection(server.getUrl("/")); - if (tls) { - ((OkHttpsConnection) connection).setSSLSocketFactory(socketFactory); - ((OkHttpsConnection) connection).setHostnameVerifier(hostnameVerifier); - } - connection.addRequestProperty("Accept-Encoding", "gzip"); - InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); + HttpURLConnection connection1 = client.open(server.getUrl("/")); + connection1.addRequestProperty("Accept-Encoding", "gzip"); + InputStream gunzippedIn = new GZIPInputStream(connection1.getInputStream()); assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE)); assertEquals(0, server.takeRequest().getSequenceNumber()); - connection = openConnection(server.getUrl("/")); - if (tls) { - ((OkHttpsConnection) connection).setSSLSocketFactory(socketFactory); - ((OkHttpsConnection) connection).setHostnameVerifier(hostnameVerifier); - } - assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); + HttpURLConnection connection2 = client.open(server.getUrl("/")); + assertEquals("two (identity)", readAscii(connection2.getInputStream(), Integer.MAX_VALUE)); assertEquals(1, server.takeRequest().getSequenceNumber()); } + @Test public void earlyDisconnectDoesntHarmPoolingWithChunkedEncoding() throws Exception { + testEarlyDisconnectDoesntHarmPooling(TransferKind.CHUNKED); + } + + @Test public void earlyDisconnectDoesntHarmPoolingWithFixedLengthEncoding() throws Exception { + testEarlyDisconnectDoesntHarmPooling(TransferKind.FIXED_LENGTH); + } + + private void testEarlyDisconnectDoesntHarmPooling(TransferKind transferKind) throws Exception { + MockResponse response1 = new MockResponse(); + transferKind.setBody(response1, "ABCDEFGHIJK", 1024); + server.enqueue(response1); + + MockResponse response2 = new MockResponse(); + transferKind.setBody(response2, "LMNOPQRSTUV", 1024); + server.enqueue(response2); + + server.play(); + + URLConnection connection1 = client.open(server.getUrl("/")); + InputStream in1 = connection1.getInputStream(); + assertEquals("ABCDE", readAscii(in1, 5)); + in1.close(); + + HttpURLConnection connection2 = client.open(server.getUrl("/")); + InputStream in2 = connection2.getInputStream(); + assertEquals("LMNOP", readAscii(in2, 5)); + in2.close(); + + assertEquals(0, server.takeRequest().getSequenceNumber()); + assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection is pooled! + } + /** * Obnoxiously test that the chunk sizes transmitted exactly equal the * requested data+chunk header size. Although setChunkedStreamingMode() * isn't specific about whether the size applies to the data or the * complete chunk, the RI interprets it as a complete chunk. */ - public void testSetChunkedStreamingMode() throws IOException, InterruptedException { + @Test public void setChunkedStreamingMode() throws IOException, InterruptedException { server.enqueue(new MockResponse()); server.play(); - OkHttpConnection urlConnection = openConnection(server.getUrl("/")); + HttpURLConnection urlConnection = client.open(server.getUrl("/")); urlConnection.setChunkedStreamingMode(8); urlConnection.setDoOutput(true); OutputStream outputStream = urlConnection.getOutputStream(); @@ -1146,11 +1179,11 @@ public final class URLConnectionTest extends TestCase { assertEquals(Arrays.asList(3, 3, 3, 3, 3, 2), request.getChunkSizes()); } - public void testAuthenticateWithFixedLengthStreaming() throws Exception { + @Test public void authenticateWithFixedLengthStreaming() throws Exception { testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH); } - public void testAuthenticateWithChunkedStreaming() throws Exception { + @Test public void authenticateWithChunkedStreaming() throws Exception { testAuthenticateWithStreamingPost(StreamingMode.CHUNKED); } @@ -1163,7 +1196,7 @@ public final class URLConnectionTest extends TestCase { server.play(); Authenticator.setDefault(new RecordingAuthenticator()); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); connection.setDoOutput(true); byte[] requestBody = { 'A', 'B', 'C', 'D' }; if (streamingMode == StreamingMode.FIXED_LENGTH) { @@ -1186,12 +1219,12 @@ public final class URLConnectionTest extends TestCase { assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); } - public void testNonStandardAuthenticationScheme() throws Exception { + @Test public void nonStandardAuthenticationScheme() throws Exception { List<String> calls = authCallsForHeader("WWW-Authenticate: Foo"); assertEquals(Collections.<String>emptyList(), calls); } - public void testNonStandardAuthenticationSchemeWithRealm() throws Exception { + @Test public void nonStandardAuthenticationSchemeWithRealm() throws Exception { List<String> calls = authCallsForHeader("WWW-Authenticate: Foo realm=\"Bar\""); assertEquals(1, calls.size()); String call = calls.get(0); @@ -1201,7 +1234,7 @@ public final class URLConnectionTest extends TestCase { // Digest auth is currently unsupported. Test that digest requests should fail reasonably. // http://code.google.com/p/android/issues/detail?id=11140 - public void testDigestAuthentication() throws Exception { + @Test public void digestAuthentication() throws Exception { List<String> calls = authCallsForHeader("WWW-Authenticate: Digest " + "realm=\"testrealm@host.com\", qop=\"auth,auth-int\", " + "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", " @@ -1212,7 +1245,7 @@ public final class URLConnectionTest extends TestCase { assertTrue(call, call.contains("prompt=testrealm@host.com")); } - public void testAllAttributesSetInServerAuthenticationCallbacks() throws Exception { + @Test public void allAttributesSetInServerAuthenticationCallbacks() throws Exception { List<String> calls = authCallsForHeader("WWW-Authenticate: Basic realm=\"Bar\""); assertEquals(1, calls.size()); URL url = server.getUrl("/"); @@ -1227,7 +1260,7 @@ public final class URLConnectionTest extends TestCase { assertTrue(call, call.toLowerCase().contains("scheme=basic")); // lowercase for the RI. } - public void testAllAttributesSetInProxyAuthenticationCallbacks() throws Exception { + @Test public void allAttributesSetInProxyAuthenticationCallbacks() throws Exception { List<String> calls = authCallsForHeader("Proxy-Authenticate: Basic realm=\"Bar\""); assertEquals(1, calls.size()); URL url = server.getUrl("/"); @@ -1254,14 +1287,18 @@ public final class URLConnectionTest extends TestCase { server.enqueue(pleaseAuthenticate); server.play(); - OkHttpConnection connection = proxy - ? openConnection(new URL("http://android.com"), server.toProxyAddress()) - : openConnection(server.getUrl("/")); + HttpURLConnection connection; + if (proxy) { + client.setProxy(server.toProxyAddress()); + connection = client.open(new URL("http://android.com")); + } else { + connection = client.open(server.getUrl("/")); + } assertEquals(responseCode, connection.getResponseCode()); return authenticator.calls; } - public void testSetValidRequestMethod() throws Exception { + @Test public void setValidRequestMethod() throws Exception { server.play(); assertValidRequestMethod("GET"); assertValidRequestMethod("DELETE"); @@ -1273,23 +1310,23 @@ public final class URLConnectionTest extends TestCase { } private void assertValidRequestMethod(String requestMethod) throws Exception { - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); connection.setRequestMethod(requestMethod); assertEquals(requestMethod, connection.getRequestMethod()); } - public void testSetInvalidRequestMethodLowercase() throws Exception { + @Test public void setInvalidRequestMethodLowercase() throws Exception { server.play(); assertInvalidRequestMethod("get"); } - public void testSetInvalidRequestMethodConnect() throws Exception { + @Test public void setInvalidRequestMethodConnect() throws Exception { server.play(); assertInvalidRequestMethod("CONNECT"); } private void assertInvalidRequestMethod(String requestMethod) throws Exception { - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); try { connection.setRequestMethod(requestMethod); fail(); @@ -1297,9 +1334,9 @@ public final class URLConnectionTest extends TestCase { } } - public void testCannotSetNegativeFixedLengthStreamingMode() throws Exception { + @Test public void cannotSetNegativeFixedLengthStreamingMode() throws Exception { server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); try { connection.setFixedLengthStreamingMode(-2); fail(); @@ -1307,16 +1344,16 @@ public final class URLConnectionTest extends TestCase { } } - public void testCanSetNegativeChunkedStreamingMode() throws Exception { + @Test public void canSetNegativeChunkedStreamingMode() throws Exception { server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); connection.setChunkedStreamingMode(-2); } - public void testCannotSetFixedLengthStreamingModeAfterConnect() throws Exception { + @Test public void cannotSetFixedLengthStreamingModeAfterConnect() throws Exception { server.enqueue(new MockResponse().setBody("A")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); try { connection.setFixedLengthStreamingMode(1); @@ -1325,10 +1362,10 @@ public final class URLConnectionTest extends TestCase { } } - public void testCannotSetChunkedStreamingModeAfterConnect() throws Exception { + @Test public void cannotSetChunkedStreamingModeAfterConnect() throws Exception { server.enqueue(new MockResponse().setBody("A")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); try { connection.setChunkedStreamingMode(1); @@ -1337,9 +1374,9 @@ public final class URLConnectionTest extends TestCase { } } - public void testCannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception { + @Test public void cannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception { server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); connection.setChunkedStreamingMode(1); try { connection.setFixedLengthStreamingMode(1); @@ -1348,9 +1385,9 @@ public final class URLConnectionTest extends TestCase { } } - public void testCannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception { + @Test public void cannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception { server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); connection.setFixedLengthStreamingMode(1); try { connection.setChunkedStreamingMode(1); @@ -1359,11 +1396,11 @@ public final class URLConnectionTest extends TestCase { } } - public void testSecureFixedLengthStreaming() throws Exception { + @Test public void secureFixedLengthStreaming() throws Exception { testSecureStreamingPost(StreamingMode.FIXED_LENGTH); } - public void testSecureChunkedStreaming() throws Exception { + @Test public void secureChunkedStreaming() throws Exception { testSecureStreamingPost(StreamingMode.CHUNKED); } @@ -1376,9 +1413,9 @@ public final class URLConnectionTest extends TestCase { server.enqueue(new MockResponse().setBody("Success!")); server.play(); - OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/")); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection.setHostnameVerifier(new RecordingHostnameVerifier()); + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(new RecordingHostnameVerifier()); + HttpURLConnection connection = client.open(server.getUrl("/")); connection.setDoOutput(true); byte[] requestBody = { 'A', 'B', 'C', 'D' }; if (streamingMode == StreamingMode.FIXED_LENGTH) { @@ -1405,7 +1442,7 @@ public final class URLConnectionTest extends TestCase { FIXED_LENGTH, CHUNKED } - public void testAuthenticateWithPost() throws Exception { + @Test public void authenticateWithPost() throws Exception { MockResponse pleaseAuthenticate = new MockResponse() .setResponseCode(401) .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") @@ -1419,7 +1456,7 @@ public final class URLConnectionTest extends TestCase { server.play(); Authenticator.setDefault(new RecordingAuthenticator()); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); connection.setDoOutput(true); byte[] requestBody = { 'A', 'B', 'C', 'D' }; OutputStream outputStream = connection.getOutputStream(); @@ -1440,7 +1477,7 @@ public final class URLConnectionTest extends TestCase { } } - public void testAuthenticateWithGet() throws Exception { + @Test public void authenticateWithGet() throws Exception { MockResponse pleaseAuthenticate = new MockResponse() .setResponseCode(401) .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") @@ -1454,7 +1491,7 @@ public final class URLConnectionTest extends TestCase { server.play(); Authenticator.setDefault(new RecordingAuthenticator()); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); // no authorization header for the first request... @@ -1469,28 +1506,28 @@ public final class URLConnectionTest extends TestCase { } } - public void testRedirectedWithChunkedEncoding() throws Exception { + @Test public void redirectedWithChunkedEncoding() throws Exception { testRedirected(TransferKind.CHUNKED, true); } - public void testRedirectedWithContentLengthHeader() throws Exception { + @Test public void redirectedWithContentLengthHeader() throws Exception { testRedirected(TransferKind.FIXED_LENGTH, true); } - public void testRedirectedWithNoLengthHeaders() throws Exception { + @Test public void redirectedWithNoLengthHeaders() throws Exception { testRedirected(TransferKind.END_OF_STREAM, false); } private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception { MockResponse response = new MockResponse() - .setResponseCode(OkHttpConnection.HTTP_MOVED_TEMP) + .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) .addHeader("Location: /foo"); transferKind.setBody(response, "This page has moved!", 10); server.enqueue(response); server.enqueue(new MockResponse().setBody("This is the new location!")); server.play(); - URLConnection connection = openConnection(server.getUrl("/")); + URLConnection connection = client.open(server.getUrl("/")); assertEquals("This is the new location!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); @@ -1503,7 +1540,7 @@ public final class URLConnectionTest extends TestCase { } } - public void testRedirectedOnHttps() throws IOException, InterruptedException { + @Test public void redirectedOnHttps() throws IOException, InterruptedException { server.useHttps(sslContext.getSocketFactory(), false); server.enqueue(new MockResponse() .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) @@ -1512,9 +1549,9 @@ public final class URLConnectionTest extends TestCase { server.enqueue(new MockResponse().setBody("This is the new location!")); server.play(); - OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/")); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection.setHostnameVerifier(new RecordingHostnameVerifier()); + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(new RecordingHostnameVerifier()); + HttpURLConnection connection = client.open(server.getUrl("/")); assertEquals("This is the new location!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); @@ -1525,7 +1562,7 @@ public final class URLConnectionTest extends TestCase { assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); } - public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException { + @Test public void notRedirectedFromHttpsToHttp() throws IOException, InterruptedException { server.useHttps(sslContext.getSocketFactory(), false); server.enqueue(new MockResponse() .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) @@ -1533,45 +1570,45 @@ public final class URLConnectionTest extends TestCase { .setBody("This page has moved!")); server.play(); - OkHttpsConnection connection = (OkHttpsConnection) openConnection(server.getUrl("/")); - connection.setSSLSocketFactory(sslContext.getSocketFactory()); - connection.setHostnameVerifier(new RecordingHostnameVerifier()); + client.setSSLSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(new RecordingHostnameVerifier()); + HttpURLConnection connection = client.open(server.getUrl("/")); assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); } - public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException { + @Test public void notRedirectedFromHttpToHttps() throws IOException, InterruptedException { server.enqueue(new MockResponse() - .setResponseCode(OkHttpConnection.HTTP_MOVED_TEMP) + .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) .addHeader("Location: https://anyhost/foo") .setBody("This page has moved!")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); } - public void SUPPRESSED_testRedirectToAnotherOriginServer() throws Exception { + @Test public void redirectToAnotherOriginServer() throws Exception { MockWebServer server2 = new MockWebServer(); server2.enqueue(new MockResponse().setBody("This is the 2nd server!")); server2.play(); server.enqueue(new MockResponse() - .setResponseCode(OkHttpConnection.HTTP_MOVED_TEMP) + .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) .addHeader("Location: " + server2.getUrl("/").toString()) .setBody("This page has moved!")); server.enqueue(new MockResponse().setBody("This is the first server again!")); server.play(); - URLConnection connection = openConnection(server.getUrl("/")); + URLConnection connection = client.open(server.getUrl("/")); assertEquals("This is the 2nd server!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); assertEquals(server2.getUrl("/"), connection.getURL()); // make sure the first server was careful to recycle the connection assertEquals("This is the first server again!", - readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE)); + readAscii(client.open(server.getUrl("/")).getInputStream(), Integer.MAX_VALUE)); RecordedRequest first = server.takeRequest(); assertContains(first.getHeaders(), "Host: " + hostName + ":" + server.getPort()); @@ -1583,21 +1620,21 @@ public final class URLConnectionTest extends TestCase { server2.shutdown(); } - public void testResponse300MultipleChoiceWithPost() throws Exception { + @Test public void response300MultipleChoiceWithPost() throws Exception { // Chrome doesn't follow the redirect, but Firefox and the RI both do - testResponseRedirectedWithPost(OkHttpConnection.HTTP_MULT_CHOICE); + testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE); } - public void testResponse301MovedPermanentlyWithPost() throws Exception { - testResponseRedirectedWithPost(OkHttpConnection.HTTP_MOVED_PERM); + @Test public void response301MovedPermanentlyWithPost() throws Exception { + testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM); } - public void testResponse302MovedTemporarilyWithPost() throws Exception { - testResponseRedirectedWithPost(OkHttpConnection.HTTP_MOVED_TEMP); + @Test public void response302MovedTemporarilyWithPost() throws Exception { + testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP); } - public void testResponse303SeeOtherWithPost() throws Exception { - testResponseRedirectedWithPost(OkHttpConnection.HTTP_SEE_OTHER); + @Test public void response303SeeOtherWithPost() throws Exception { + testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER); } private void testResponseRedirectedWithPost(int redirectCode) throws Exception { @@ -1608,7 +1645,7 @@ public final class URLConnectionTest extends TestCase { server.enqueue(new MockResponse().setBody("Page 2")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/page1")); + HttpURLConnection connection = client.open(server.getUrl("/page1")); connection.setDoOutput(true); byte[] requestBody = { 'A', 'B', 'C', 'D' }; OutputStream outputStream = connection.getOutputStream(); @@ -1625,15 +1662,15 @@ public final class URLConnectionTest extends TestCase { assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine()); } - public void testResponse305UseProxy() throws Exception { + @Test public void response305UseProxy() throws Exception { server.play(); server.enqueue(new MockResponse() - .setResponseCode(OkHttpConnection.HTTP_USE_PROXY) + .setResponseCode(HttpURLConnection.HTTP_USE_PROXY) .addHeader("Location: " + server.getUrl("/")) .setBody("This page has moved!")); server.enqueue(new MockResponse().setBody("Proxy Response")); - OkHttpConnection connection = openConnection(server.getUrl("/foo")); + HttpURLConnection connection = client.open(server.getUrl("/foo")); // Fails on the RI, which gets "Proxy Response" assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); @@ -1643,61 +1680,31 @@ public final class URLConnectionTest extends TestCase { assertEquals(1, server.getRequestCount()); } -// public void testHttpsWithCustomTrustManager() throws Exception { -// RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); -// RecordingTrustManager trustManager = new RecordingTrustManager(); -// SSLContext sc = SSLContext.getInstance("TLS"); -// sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom()); -// -// HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); -// HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); -// SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); -// HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); -// try { -// TestSSLContext testSSLContext = TestSSLContext.create(); -// server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); -// server.enqueue(new MockResponse().setBody("ABC")); -// server.enqueue(new MockResponse().setBody("DEF")); -// server.enqueue(new MockResponse().setBody("GHI")); -// server.play(); -// -// URL url = server.getUrl("/"); -// assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE)); -// assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE)); -// assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE)); -// -// assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls); -// assertEquals(Arrays.asList("checkServerTrusted [" -// + "CN=" + hostName + " 1, " -// + "CN=Test Intermediate Certificate Authority 1, " -// + "CN=Test Root Certificate Authority 1" -// + "] RSA"), -// trustManager.calls); -// } finally { -// HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier); -// HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory); -// } -// } -// -// public void testConnectTimeouts() throws IOException { -// StuckServer ss = new StuckServer(); -// int serverPort = ss.getLocalPort(); -// URLConnection urlConnection = new URL("http://localhost:" + serverPort).openConnection(); -// int timeout = 1000; -// urlConnection.setConnectTimeout(timeout); -// long start = System.currentTimeMillis(); -// try { -// urlConnection.getInputStream(); -// fail(); -// } catch (SocketTimeoutException expected) { -// long actual = System.currentTimeMillis() - start; -// assertTrue(Math.abs(timeout - actual) < 500); -// } finally { -// ss.close(); -// } -// } - - public void testReadTimeouts() throws IOException { + @Test public void httpsWithCustomTrustManager() throws Exception { + RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); + RecordingTrustManager trustManager = new RecordingTrustManager(); + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom()); + + client.setHostnameVerifier(hostnameVerifier); + client.setSSLSocketFactory(sc.getSocketFactory()); + server.useHttps(sslContext.getSocketFactory(), false); + server.enqueue(new MockResponse().setBody("ABC")); + server.enqueue(new MockResponse().setBody("DEF")); + server.enqueue(new MockResponse().setBody("GHI")); + server.play(); + + URL url = server.getUrl("/"); + assertContent("ABC", client.open(url)); + assertContent("DEF", client.open(url)); + assertContent("GHI", client.open(url)); + + assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls); + assertEquals(Arrays.asList("checkServerTrusted [CN=" + hostName + " 1]"), + trustManager.calls); + } + + @Test public void readTimeouts() throws IOException { /* * This relies on the fact that MockWebServer doesn't close the * connection after a response has been sent. This causes the client to @@ -1711,7 +1718,7 @@ public final class URLConnectionTest extends TestCase { server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive server.play(); - URLConnection urlConnection = openConnection(server.getUrl("/")); + URLConnection urlConnection = client.open(server.getUrl("/")); urlConnection.setReadTimeout(1000); InputStream in = urlConnection.getInputStream(); assertEquals('A', in.read()); @@ -1724,11 +1731,11 @@ public final class URLConnectionTest extends TestCase { } } - public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException { + @Test public void setChunkedEncodingAsRequestProperty() throws IOException, InterruptedException { server.enqueue(new MockResponse()); server.play(); - OkHttpConnection urlConnection = openConnection(server.getUrl("/")); + HttpURLConnection urlConnection = client.open(server.getUrl("/")); urlConnection.setRequestProperty("Transfer-encoding", "chunked"); urlConnection.setDoOutput(true); urlConnection.getOutputStream().write("ABC".getBytes("UTF-8")); @@ -1738,16 +1745,16 @@ public final class URLConnectionTest extends TestCase { assertEquals("ABC", new String(request.getBody(), "UTF-8")); } - public void testConnectionCloseInRequest() throws IOException, InterruptedException { + @Test public void connectionCloseInRequest() throws IOException, InterruptedException { server.enqueue(new MockResponse()); // server doesn't honor the connection: close header! server.enqueue(new MockResponse()); server.play(); - OkHttpConnection a = openConnection(server.getUrl("/")); + HttpURLConnection a = client.open(server.getUrl("/")); a.setRequestProperty("Connection", "close"); assertEquals(200, a.getResponseCode()); - OkHttpConnection b = openConnection(server.getUrl("/")); + HttpURLConnection b = client.open(server.getUrl("/")); assertEquals(200, b.getResponseCode()); assertEquals(0, server.takeRequest().getSequenceNumber()); @@ -1755,15 +1762,15 @@ public final class URLConnectionTest extends TestCase { 0, server.takeRequest().getSequenceNumber()); } - public void testConnectionCloseInResponse() throws IOException, InterruptedException { + @Test public void connectionCloseInResponse() throws IOException, InterruptedException { server.enqueue(new MockResponse().addHeader("Connection: close")); server.enqueue(new MockResponse()); server.play(); - OkHttpConnection a = openConnection(server.getUrl("/")); + HttpURLConnection a = client.open(server.getUrl("/")); assertEquals(200, a.getResponseCode()); - OkHttpConnection b = openConnection(server.getUrl("/")); + HttpURLConnection b = client.open(server.getUrl("/")); assertEquals(200, b.getResponseCode()); assertEquals(0, server.takeRequest().getSequenceNumber()); @@ -1771,16 +1778,16 @@ public final class URLConnectionTest extends TestCase { 0, server.takeRequest().getSequenceNumber()); } - public void testConnectionCloseWithRedirect() throws IOException, InterruptedException { + @Test public void connectionCloseWithRedirect() throws IOException, InterruptedException { MockResponse response = new MockResponse() - .setResponseCode(OkHttpConnection.HTTP_MOVED_TEMP) + .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) .addHeader("Location: /foo") .addHeader("Connection: close"); server.enqueue(response); server.enqueue(new MockResponse().setBody("This is the new location!")); server.play(); - URLConnection connection = openConnection(server.getUrl("/")); + URLConnection connection = client.open(server.getUrl("/")); assertEquals("This is the new location!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); @@ -1789,37 +1796,37 @@ public final class URLConnectionTest extends TestCase { 0, server.takeRequest().getSequenceNumber()); } - public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException { + @Test public void responseCodeDisagreesWithHeaders() throws IOException, InterruptedException { server.enqueue(new MockResponse() - .setResponseCode(OkHttpConnection.HTTP_NO_CONTENT) + .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT) .setBody("This body is not allowed!")); server.play(); - URLConnection connection = openConnection(server.getUrl("/")); + URLConnection connection = client.open(server.getUrl("/")); assertEquals("This body is not allowed!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); } - public void testSingleByteReadIsSigned() throws IOException { + @Test public void singleByteReadIsSigned() throws IOException { server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 })); server.play(); - URLConnection connection = openConnection(server.getUrl("/")); + URLConnection connection = client.open(server.getUrl("/")); InputStream in = connection.getInputStream(); assertEquals(254, in.read()); assertEquals(255, in.read()); assertEquals(-1, in.read()); } - public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException { + @Test public void flushAfterStreamTransmittedWithChunkedEncoding() throws IOException { testFlushAfterStreamTransmitted(TransferKind.CHUNKED); } - public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException { + @Test public void flushAfterStreamTransmittedWithFixedLength() throws IOException { testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH); } - public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException { + @Test public void flushAfterStreamTransmittedWithNoLengthHeaders() throws IOException { testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM); } @@ -1832,7 +1839,7 @@ public final class URLConnectionTest extends TestCase { server.enqueue(new MockResponse().setBody("abc")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); connection.setDoOutput(true); byte[] upload = "def".getBytes("UTF-8"); @@ -1854,11 +1861,16 @@ public final class URLConnectionTest extends TestCase { } } - public void testGetHeadersThrows() throws IOException { - server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START)); + @Test public void getHeadersThrows() throws IOException { + // Enqueue a response for every IP address held by localhost, because the route selector + // will try each in sequence. + // TODO: use the fake Dns implementation instead of a loop + for (InetAddress inetAddress : InetAddress.getAllByName(server.getHostName())) { + server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START)); + } server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); try { connection.getInputStream(); fail(); @@ -1872,150 +1884,67 @@ public final class URLConnectionTest extends TestCase { } } - public void SUPPRESSED_testGetKeepAlive() throws Exception { + @Test public void dnsFailureThrowsIOException() throws IOException { + HttpURLConnection connection = client.open(new URL("http://host.unlikelytld")); + try { + connection.connect(); + fail(); + } catch (IOException expected) { + } + } + + @Test public void malformedUrlThrowsUnknownHostException() throws IOException { + HttpURLConnection connection = client.open(new URL("http:///foo.html")); + try { + connection.connect(); + fail(); + } catch (UnknownHostException expected) { + } + } + + @Test public void getKeepAlive() throws Exception { MockWebServer server = new MockWebServer(); server.enqueue(new MockResponse().setBody("ABC")); server.play(); // The request should work once and then fail - URLConnection connection = openConnection(server.getUrl("")); - InputStream input = connection.getInputStream(); + URLConnection connection1 = client.open(server.getUrl("")); + connection1.setReadTimeout(100); + InputStream input = connection1.getInputStream(); assertEquals("ABC", readAscii(input, Integer.MAX_VALUE)); input.close(); + server.shutdown(); try { - openConnection(server.getUrl("")).getInputStream(); + HttpURLConnection connection2 = client.open(server.getUrl("")); + connection2.setReadTimeout(100); + connection2.getInputStream(); fail(); } catch (ConnectException expected) { } } /** - * This test goes through the exhaustive set of interesting ASCII characters - * because most of those characters are interesting in some way according to - * RFC 2396 and RFC 2732. http://b/1158780 - */ - public void SUPPRESSED_testLenientUrlToUri() throws Exception { - // alphanum - testUrlToUriMapping("abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09"); - - // control characters - testUrlToUriMapping("\u0001", "%01", "%01", "%01", "%01"); - testUrlToUriMapping("\u001f", "%1F", "%1F", "%1F", "%1F"); - - // ascii characters - testUrlToUriMapping("%20", "%20", "%20", "%20", "%20"); - testUrlToUriMapping("%20", "%20", "%20", "%20", "%20"); - testUrlToUriMapping(" ", "%20", "%20", "%20", "%20"); - testUrlToUriMapping("!", "!", "!", "!", "!"); - testUrlToUriMapping("\"", "%22", "%22", "%22", "%22"); - testUrlToUriMapping("#", null, null, null, "%23"); - testUrlToUriMapping("$", "$", "$", "$", "$"); - testUrlToUriMapping("&", "&", "&", "&", "&"); - testUrlToUriMapping("'", "'", "'", "'", "'"); - testUrlToUriMapping("(", "(", "(", "(", "("); - testUrlToUriMapping(")", ")", ")", ")", ")"); - testUrlToUriMapping("*", "*", "*", "*", "*"); - testUrlToUriMapping("+", "+", "+", "+", "+"); - testUrlToUriMapping(",", ",", ",", ",", ","); - testUrlToUriMapping("-", "-", "-", "-", "-"); - testUrlToUriMapping(".", ".", ".", ".", "."); - testUrlToUriMapping("/", null, "/", "/", "/"); - testUrlToUriMapping(":", null, ":", ":", ":"); - testUrlToUriMapping(";", ";", ";", ";", ";"); - testUrlToUriMapping("<", "%3C", "%3C", "%3C", "%3C"); - testUrlToUriMapping("=", "=", "=", "=", "="); - testUrlToUriMapping(">", "%3E", "%3E", "%3E", "%3E"); - testUrlToUriMapping("?", null, null, "?", "?"); - testUrlToUriMapping("@", "@", "@", "@", "@"); - testUrlToUriMapping("[", null, "%5B", null, "%5B"); - testUrlToUriMapping("\\", "%5C", "%5C", "%5C", "%5C"); - testUrlToUriMapping("]", null, "%5D", null, "%5D"); - testUrlToUriMapping("^", "%5E", "%5E", "%5E", "%5E"); - testUrlToUriMapping("_", "_", "_", "_", "_"); - testUrlToUriMapping("`", "%60", "%60", "%60", "%60"); - testUrlToUriMapping("{", "%7B", "%7B", "%7B", "%7B"); - testUrlToUriMapping("|", "%7C", "%7C", "%7C", "%7C"); - testUrlToUriMapping("}", "%7D", "%7D", "%7D", "%7D"); - testUrlToUriMapping("~", "~", "~", "~", "~"); - testUrlToUriMapping("~", "~", "~", "~", "~"); - testUrlToUriMapping("\u007f", "%7F", "%7F", "%7F", "%7F"); - - // beyond ascii - testUrlToUriMapping("\u0080", "%C2%80", "%C2%80", "%C2%80", "%C2%80"); - testUrlToUriMapping("\u20ac", "\u20ac", "\u20ac", "\u20ac", "\u20ac"); - testUrlToUriMapping("\ud842\udf9f", - "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f"); - } - - public void SUPPRESSED_testLenientUrlToUriNul() throws Exception { - testUrlToUriMapping("\u0000", "%00", "%00", "%00", "%00"); // RI fails this - } - - private void testUrlToUriMapping(String string, String asAuthority, String asFile, - String asQuery, String asFragment) throws Exception { - if (asAuthority != null) { - assertEquals("http://host" + asAuthority + ".tld/", - backdoorUrlToUri(new URL("http://host" + string + ".tld/")).toString()); - } - if (asFile != null) { - assertEquals("http://host.tld/file" + asFile + "/", - backdoorUrlToUri(new URL("http://host.tld/file" + string + "/")).toString()); - } - if (asQuery != null) { - assertEquals("http://host.tld/file?q" + asQuery + "=x", - backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x")).toString()); - } - assertEquals("http://host.tld/file#" + asFragment + "-x", - backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString()); - } - - /** - * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI, - * HttpURLConnection recovers from URLs with unescaped but unsupported URI - * characters like '{' and '|' by escaping these characters. - */ - private URI backdoorUrlToUri(URL url) throws Exception { - final AtomicReference<URI> uriReference = new AtomicReference<URI>(); - - ResponseCache.setDefault(new ResponseCache() { - @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { - return null; - } - @Override public CacheResponse get(URI uri, String requestMethod, - Map<String, List<String>> requestHeaders) throws IOException { - uriReference.set(uri); - throw new UnsupportedOperationException(); - } - }); - - try { - OkHttpConnection connection = openConnection(url); - connection.getResponseCode(); - } catch (Exception expected) { - if (expected.getCause() instanceof URISyntaxException) { - expected.printStackTrace(); - } - } - - return uriReference.get(); - } - - /** * Don't explode if the cache returns a null body. http://b/3373699 */ - public void testResponseCacheReturnsNullOutputStream() throws Exception { + @Test public void responseCacheReturnsNullOutputStream() throws Exception { final AtomicBoolean aborted = new AtomicBoolean(); - ResponseCache.setDefault(new ResponseCache() { - @Override public CacheResponse get(URI uri, String requestMethod, + client.setResponseCache(new ResponseCache() { + @Override + public CacheResponse get(URI uri, String requestMethod, Map<String, List<String>> requestHeaders) throws IOException { return null; } - @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { + + @Override + public CacheRequest put(URI uri, URLConnection connection) throws IOException { return new CacheRequest() { - @Override public void abort() { + @Override + public void abort() { aborted.set(true); } - @Override public OutputStream getBody() throws IOException { + + @Override + public OutputStream getBody() throws IOException { return null; } }; @@ -2025,18 +1954,17 @@ public final class URLConnectionTest extends TestCase { server.enqueue(new MockResponse().setBody("abcdef")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); InputStream in = connection.getInputStream(); assertEquals("abc", readAscii(in, 3)); in.close(); assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here } - /** * http://code.google.com/p/android/issues/detail?id=14562 */ - public void testReadAfterLastByte() throws Exception { + @Test public void readAfterLastByte() throws Exception { server.enqueue(new MockResponse() .setBody("ABC") .clearHeaders() @@ -2044,29 +1972,29 @@ public final class URLConnectionTest extends TestCase { .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); InputStream in = connection.getInputStream(); assertEquals("ABC", readAscii(in, 3)); assertEquals(-1, in.read()); assertEquals(-1, in.read()); // throws IOException in Gingerbread } - public void testGetContent() throws Exception { + @Test public void getContent() throws Exception { server.enqueue(new MockResponse() .addHeader("Content-Type: text/plain") .setBody("A")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); InputStream in = (InputStream) connection.getContent(); assertEquals("A", readAscii(in, Integer.MAX_VALUE)); } - public void testGetContentOfType() throws Exception { + @Test public void getContentOfType() throws Exception { server.enqueue(new MockResponse() .addHeader("Content-Type: text/plain") .setBody("A")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); try { connection.getContent(null); fail(); @@ -2081,10 +2009,10 @@ public final class URLConnectionTest extends TestCase { connection.disconnect(); } - public void testGetOutputStreamOnGetFails() throws Exception { + @Test public void getOutputStreamOnGetFails() throws Exception { server.enqueue(new MockResponse()); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); try { connection.getOutputStream(); fail(); @@ -2092,10 +2020,10 @@ public final class URLConnectionTest extends TestCase { } } - public void testGetOutputAfterGetInputStreamFails() throws Exception { + @Test public void getOutputAfterGetInputStreamFails() throws Exception { server.enqueue(new MockResponse()); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); connection.setDoOutput(true); try { connection.getInputStream(); @@ -2105,10 +2033,10 @@ public final class URLConnectionTest extends TestCase { } } - public void testSetDoOutputOrDoInputAfterConnectFails() throws Exception { + @Test public void setDoOutputOrDoInputAfterConnectFails() throws Exception { server.enqueue(new MockResponse()); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); connection.connect(); try { connection.setDoOutput(true); @@ -2123,10 +2051,10 @@ public final class URLConnectionTest extends TestCase { connection.disconnect(); } - public void testClientSendsContentLength() throws Exception { + @Test public void clientSendsContentLength() throws Exception { server.enqueue(new MockResponse().setBody("A")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); connection.setDoOutput(true); OutputStream out = connection.getOutputStream(); out.write(new byte[] { 'A', 'B', 'C' }); @@ -2136,54 +2064,54 @@ public final class URLConnectionTest extends TestCase { assertContains(request.getHeaders(), "Content-Length: 3"); } - public void testGetContentLengthConnects() throws Exception { + @Test public void getContentLengthConnects() throws Exception { server.enqueue(new MockResponse().setBody("ABC")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); assertEquals(3, connection.getContentLength()); connection.disconnect(); } - public void testGetContentTypeConnects() throws Exception { + @Test public void getContentTypeConnects() throws Exception { server.enqueue(new MockResponse() .addHeader("Content-Type: text/plain") .setBody("ABC")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); assertEquals("text/plain", connection.getContentType()); connection.disconnect(); } - public void testGetContentEncodingConnects() throws Exception { + @Test public void getContentEncodingConnects() throws Exception { server.enqueue(new MockResponse() .addHeader("Content-Encoding: identity") .setBody("ABC")); server.play(); - OkHttpConnection connection = openConnection(server.getUrl("/")); + HttpURLConnection connection = client.open(server.getUrl("/")); assertEquals("identity", connection.getContentEncoding()); connection.disconnect(); } // http://b/4361656 - public void testUrlContainsQueryButNoPath() throws Exception { + @Test public void urlContainsQueryButNoPath() throws Exception { server.enqueue(new MockResponse().setBody("A")); server.play(); URL url = new URL("http", server.getHostName(), server.getPort(), "?query"); - assertEquals("A", readAscii(openConnection(url).getInputStream(), Integer.MAX_VALUE)); + assertEquals("A", readAscii(client.open(url).getInputStream(), Integer.MAX_VALUE)); RecordedRequest request = server.takeRequest(); assertEquals("GET /?query HTTP/1.1", request.getRequestLine()); } // http://code.google.com/p/android/issues/detail?id=20442 - public void testInputStreamAvailableWithChunkedEncoding() throws Exception { + @Test public void inputStreamAvailableWithChunkedEncoding() throws Exception { testInputStreamAvailable(TransferKind.CHUNKED); } - public void testInputStreamAvailableWithContentLengthHeader() throws Exception { + @Test public void inputStreamAvailableWithContentLengthHeader() throws Exception { testInputStreamAvailable(TransferKind.FIXED_LENGTH); } - public void testInputStreamAvailableWithNoLengthHeaders() throws Exception { + @Test public void inputStreamAvailableWithNoLengthHeaders() throws Exception { testInputStreamAvailable(TransferKind.END_OF_STREAM); } @@ -2193,7 +2121,7 @@ public final class URLConnectionTest extends TestCase { transferKind.setBody(response, body, 4); server.enqueue(response); server.play(); - URLConnection connection = openConnection(server.getUrl("/")); + URLConnection connection = client.open(server.getUrl("/")); InputStream in = connection.getInputStream(); for (int i = 0; i < body.length(); i++) { assertTrue(in.available() >= 0); @@ -2203,6 +2131,52 @@ public final class URLConnectionTest extends TestCase { assertEquals(-1, in.read()); } + @Test @Ignore public void testPooledConnectionsDetectHttp10() { + // TODO: write a test that shows pooled connections detect HTTP/1.0 (vs. HTTP/1.1) + fail("TODO"); + } + + @Test @Ignore public void postBodiesRetransmittedOnAuthProblems() { + fail("TODO"); + } + + @Test @Ignore public void cookiesAndTrailers() { + // Do cookie headers get processed too many times? + fail("TODO"); + } + + @Test @Ignore public void headerNamesContainingNullCharacter() { + // This is relevant for SPDY + fail("TODO"); + } + + @Test @Ignore public void headerValuesContainingNullCharacter() { + // This is relevant for SPDY + fail("TODO"); + } + + @Test @Ignore public void emptyHeaderName() { + // This is relevant for SPDY + fail("TODO"); + } + + @Test @Ignore public void emptyHeaderValue() { + // This is relevant for SPDY + fail("TODO"); + } + + @Test @Ignore public void deflateCompression() { + fail("TODO"); + } + + @Test @Ignore public void postBodiesRetransmittedOnIpAddressProblems() { + fail("TODO"); + } + + @Test @Ignore public void pooledConnectionProblemsNotReportedToProxySelector() { + fail("TODO"); + } + /** * Returns a gzipped copy of {@code bytes}. */ @@ -2222,7 +2196,7 @@ public final class URLConnectionTest extends TestCase { throws IOException { connection.connect(); assertEquals(expected, readAscii(connection.getInputStream(), limit)); - ((OkHttpConnection) connection).disconnect(); + ((HttpURLConnection) connection).disconnect(); } private void assertContent(String expected, URLConnection connection) throws IOException { @@ -2251,11 +2225,17 @@ public final class URLConnectionTest extends TestCase { throws IOException { response.setChunkedBody(content, chunkSize); } + @Override void setForRequest(HttpURLConnection connection, int contentLength) { + connection.setChunkedStreamingMode(5); + } }, FIXED_LENGTH() { @Override void setBody(MockResponse response, byte[] content, int chunkSize) { response.setBody(content); } + @Override void setForRequest(HttpURLConnection connection, int contentLength) { + connection.setChunkedStreamingMode(contentLength); + } }, END_OF_STREAM() { @Override void setBody(MockResponse response, byte[] content, int chunkSize) { @@ -2268,11 +2248,15 @@ public final class URLConnectionTest extends TestCase { } } } + @Override void setForRequest(HttpURLConnection connection, int contentLength) { + } }; abstract void setBody(MockResponse response, byte[] content, int chunkSize) throws IOException; + abstract void setForRequest(HttpURLConnection connection, int contentLength); + void setBody(MockResponse response, String content, int chunkSize) throws IOException { setBody(response, content.getBytes("UTF-8"), chunkSize); } @@ -2280,65 +2264,66 @@ public final class URLConnectionTest extends TestCase { enum ProxyConfig { NO_PROXY() { - @Override public OkHttpConnection connect(MockWebServer server, URL url) - throws IOException { - return openConnection(url, Proxy.NO_PROXY); + @Override public HttpURLConnection connect + (MockWebServer server, OkHttpClient client, URL url) throws IOException { + client.setProxy(Proxy.NO_PROXY); + return client.open(url); } }, CREATE_ARG() { - @Override public OkHttpConnection connect(MockWebServer server, URL url) - throws IOException { - return openConnection(url, server.toProxyAddress()); + @Override public HttpURLConnection connect( + MockWebServer server, OkHttpClient client, URL url) throws IOException { + client.setProxy(server.toProxyAddress()); + return client.open(url); } }, PROXY_SYSTEM_PROPERTY() { - @Override public OkHttpConnection connect(MockWebServer server, URL url) - throws IOException { + @Override public HttpURLConnection connect( + MockWebServer server, OkHttpClient client, URL url) throws IOException { System.setProperty("proxyHost", "localhost"); System.setProperty("proxyPort", Integer.toString(server.getPort())); - return openConnection(url); + return client.open(url); } }, HTTP_PROXY_SYSTEM_PROPERTY() { - @Override public OkHttpConnection connect(MockWebServer server, URL url) - throws IOException { + @Override public HttpURLConnection connect( + MockWebServer server, OkHttpClient client, URL url) throws IOException { System.setProperty("http.proxyHost", "localhost"); System.setProperty("http.proxyPort", Integer.toString(server.getPort())); - return openConnection(url); + return client.open(url); } }, HTTPS_PROXY_SYSTEM_PROPERTY() { - @Override public OkHttpConnection connect(MockWebServer server, URL url) - throws IOException { + @Override public HttpURLConnection connect( + MockWebServer server, OkHttpClient client, URL url) throws IOException { System.setProperty("https.proxyHost", "localhost"); System.setProperty("https.proxyPort", Integer.toString(server.getPort())); - return openConnection(url); + return client.open(url); } }; - public abstract OkHttpConnection connect(MockWebServer server, URL url) throws IOException; + public abstract HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) throws IOException; } private static class RecordingTrustManager implements X509TrustManager { private final List<String> calls = new ArrayList<String>(); public X509Certificate[] getAcceptedIssuers() { - calls.add("getAcceptedIssuers"); return new X509Certificate[] {}; } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType); + calls.add("checkClientTrusted " + certificatesToString(chain)); } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType); + calls.add("checkServerTrusted " + certificatesToString(chain)); } private String certificatesToString(X509Certificate[] certificates) { @@ -2383,4 +2368,18 @@ public final class URLConnectionTest extends TestCase { return authentication; } } + + private static class FakeProxySelector extends ProxySelector { + List<Proxy> proxies = new ArrayList<Proxy>(); + + @Override public List<Proxy> select(URI uri) { + // Don't handle 'socket' schemes, which the RI's Socket class may request (for SOCKS). + return uri.getScheme().equals("http") || uri.getScheme().equals("https") + ? proxies + : Collections.singletonList(Proxy.NO_PROXY); + } + + @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + } + } } diff --git a/src/test/java/com/squareup/okhttp/internal/http/URLEncodingTest.java b/src/test/java/com/squareup/okhttp/internal/http/URLEncodingTest.java new file mode 100644 index 0000000..0f8edca --- /dev/null +++ b/src/test/java/com/squareup/okhttp/internal/http/URLEncodingTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2009 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.http; + +import com.squareup.okhttp.OkHttpClient; +import java.io.IOException; +import java.net.CacheRequest; +import java.net.CacheResponse; +import java.net.HttpURLConnection; +import java.net.ResponseCache; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import static org.junit.Assert.assertEquals; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Exercises HttpURLConnection to convert URL to a URI. Unlike URL#toURI, + * HttpURLConnection recovers from URLs with unescaped but unsupported URI + * characters like '{' and '|' by escaping these characters. + */ +public final class URLEncodingTest { + /** + * This test goes through the exhaustive set of interesting ASCII characters + * because most of those characters are interesting in some way according to + * RFC 2396 and RFC 2732. http://b/1158780 + */ + @Test @Ignore public void lenientUrlToUri() throws Exception { + // alphanum + testUrlToUriMapping("abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09", "abzABZ09"); + + // control characters + testUrlToUriMapping("\u0001", "%01", "%01", "%01", "%01"); + testUrlToUriMapping("\u001f", "%1F", "%1F", "%1F", "%1F"); + + // ascii characters + testUrlToUriMapping("%20", "%20", "%20", "%20", "%20"); + testUrlToUriMapping("%20", "%20", "%20", "%20", "%20"); + testUrlToUriMapping(" ", "%20", "%20", "%20", "%20"); + testUrlToUriMapping("!", "!", "!", "!", "!"); + testUrlToUriMapping("\"", "%22", "%22", "%22", "%22"); + testUrlToUriMapping("#", null, null, null, "%23"); + testUrlToUriMapping("$", "$", "$", "$", "$"); + testUrlToUriMapping("&", "&", "&", "&", "&"); + testUrlToUriMapping("'", "'", "'", "'", "'"); + testUrlToUriMapping("(", "(", "(", "(", "("); + testUrlToUriMapping(")", ")", ")", ")", ")"); + testUrlToUriMapping("*", "*", "*", "*", "*"); + testUrlToUriMapping("+", "+", "+", "+", "+"); + testUrlToUriMapping(",", ",", ",", ",", ","); + testUrlToUriMapping("-", "-", "-", "-", "-"); + testUrlToUriMapping(".", ".", ".", ".", "."); + testUrlToUriMapping("/", null, "/", "/", "/"); + testUrlToUriMapping(":", null, ":", ":", ":"); + testUrlToUriMapping(";", ";", ";", ";", ";"); + testUrlToUriMapping("<", "%3C", "%3C", "%3C", "%3C"); + testUrlToUriMapping("=", "=", "=", "=", "="); + testUrlToUriMapping(">", "%3E", "%3E", "%3E", "%3E"); + testUrlToUriMapping("?", null, null, "?", "?"); + testUrlToUriMapping("@", "@", "@", "@", "@"); + testUrlToUriMapping("[", null, "%5B", null, "%5B"); + testUrlToUriMapping("\\", "%5C", "%5C", "%5C", "%5C"); + testUrlToUriMapping("]", null, "%5D", null, "%5D"); + testUrlToUriMapping("^", "%5E", "%5E", "%5E", "%5E"); + testUrlToUriMapping("_", "_", "_", "_", "_"); + testUrlToUriMapping("`", "%60", "%60", "%60", "%60"); + testUrlToUriMapping("{", "%7B", "%7B", "%7B", "%7B"); + testUrlToUriMapping("|", "%7C", "%7C", "%7C", "%7C"); + testUrlToUriMapping("}", "%7D", "%7D", "%7D", "%7D"); + testUrlToUriMapping("~", "~", "~", "~", "~"); + testUrlToUriMapping("~", "~", "~", "~", "~"); + testUrlToUriMapping("\u007f", "%7F", "%7F", "%7F", "%7F"); + + // beyond ascii + testUrlToUriMapping("\u0080", "%C2%80", "%C2%80", "%C2%80", "%C2%80"); + testUrlToUriMapping("\u20ac", "\u20ac", "\u20ac", "\u20ac", "\u20ac"); + testUrlToUriMapping("\ud842\udf9f", + "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f", "\ud842\udf9f"); + } + + @Test @Ignore public void lenientUrlToUriNul() throws Exception { + testUrlToUriMapping("\u0000", "%00", "%00", "%00", "%00"); // RI fails this + } + + private void testUrlToUriMapping(String string, String asAuthority, String asFile, + String asQuery, String asFragment) throws Exception { + if (asAuthority != null) { + assertEquals("http://host" + asAuthority + ".tld/", + backdoorUrlToUri(new URL("http://host" + string + ".tld/")).toString()); + } + if (asFile != null) { + assertEquals("http://host.tld/file" + asFile + "/", + backdoorUrlToUri(new URL("http://host.tld/file" + string + "/")).toString()); + } + if (asQuery != null) { + assertEquals("http://host.tld/file?q" + asQuery + "=x", + backdoorUrlToUri(new URL("http://host.tld/file?q" + string + "=x")).toString()); + } + assertEquals("http://host.tld/file#" + asFragment + "-x", + backdoorUrlToUri(new URL("http://host.tld/file#" + asFragment + "-x")).toString()); + } + + private URI backdoorUrlToUri(URL url) throws Exception { + final AtomicReference<URI> uriReference = new AtomicReference<URI>(); + + OkHttpClient client = new OkHttpClient(); + client.setResponseCache(new ResponseCache() { + @Override public CacheRequest put(URI uri, URLConnection connection) + throws IOException { + return null; + } + + @Override public CacheResponse get(URI uri, String requestMethod, + Map<String, List<String>> requestHeaders) throws IOException { + uriReference.set(uri); + throw new UnsupportedOperationException(); + } + }); + + try { + HttpURLConnection connection = client.open(url); + connection.getResponseCode(); + } catch (Exception expected) { + if (expected.getCause() instanceof URISyntaxException) { + expected.printStackTrace(); + } + } + + return uriReference.get(); + } +} diff --git a/src/test/java/libcore/net/spdy/MockSpdyPeer.java b/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java index 0eb8208..652786a 100644 --- a/src/test/java/libcore/net/spdy/MockSpdyPeer.java +++ b/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package libcore.net.spdy; +package com.squareup.okhttp.internal.spdy; +import com.squareup.okhttp.internal.Util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -29,13 +30,14 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; -import libcore.io.Streams; /** * Replays prerecorded outgoing frames and records incoming frames. */ public final class MockSpdyPeer { private int frameCount = 0; + private final ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + private final SpdyWriter spdyWriter = new SpdyWriter(bytesOut); private final List<OutFrame> outFrames = new ArrayList<OutFrame>(); private final BlockingQueue<InFrame> inFrames = new LinkedBlockingQueue<InFrame>(); private int port; @@ -47,9 +49,9 @@ public final class MockSpdyPeer { } public SpdyWriter sendFrame() { - OutFrame frame = new OutFrame(frameCount++); + OutFrame frame = new OutFrame(frameCount++, bytesOut.size()); outFrames.add(frame); - return new SpdyWriter(frame.out); + return spdyWriter; } public int getPort() { @@ -69,7 +71,7 @@ public final class MockSpdyPeer { try { readAndWriteFrames(serverSocket); } catch (IOException e) { - e.printStackTrace(); // TODO + throw new RuntimeException(e); } } }); @@ -79,8 +81,10 @@ public final class MockSpdyPeer { Socket socket = serverSocket.accept(); OutputStream out = socket.getOutputStream(); InputStream in = socket.getInputStream(); + SpdyReader reader = new SpdyReader(in); Iterator<OutFrame> outFramesIterator = outFrames.iterator(); + byte[] outBytes = bytesOut.toByteArray(); OutFrame nextOutFrame = null; for (int i = 0; i < frameCount; i++) { @@ -89,13 +93,20 @@ public final class MockSpdyPeer { } if (nextOutFrame != null && nextOutFrame.sequence == i) { + int start = nextOutFrame.start; + int end; + if (outFramesIterator.hasNext()) { + nextOutFrame = outFramesIterator.next(); + end = nextOutFrame.start; + } else { + end = outBytes.length; + } + // write a frame - nextOutFrame.out.writeTo(out); - nextOutFrame = null; + out.write(outBytes, start, end - start); } else { // read a frame - SpdyReader reader = new SpdyReader(in); InFrame inFrame = new InFrame(i, reader); reader.nextFrame(inFrame); inFrames.add(inFrame); @@ -109,10 +120,11 @@ public final class MockSpdyPeer { private static class OutFrame { private final int sequence; - private final ByteArrayOutputStream out = new ByteArrayOutputStream(); + private final int start; - private OutFrame(int sequence) { + private OutFrame(int sequence, int start) { this.sequence = sequence; + this.start = start; } } @@ -160,6 +172,14 @@ public final class MockSpdyPeer { this.nameValueBlock = nameValueBlock; } + @Override public void headers(int flags, int streamId, List<String> nameValueBlock) { + if (this.type != -1) throw new IllegalStateException(); + this.type = SpdyConnection.TYPE_HEADERS; + this.streamId = streamId; + this.flags = flags; + this.nameValueBlock = nameValueBlock; + } + @Override public void data(int flags, int streamId, InputStream in, int length) throws IOException { if (this.type != -1) throw new IllegalStateException(); @@ -167,7 +187,7 @@ public final class MockSpdyPeer { this.flags = flags; this.streamId = streamId; this.data = new byte[length]; - Streams.readFully(in, this.data); + Util.readFully(in, this.data); } @Override public void rstStream(int flags, int streamId, int statusCode) { @@ -189,5 +209,12 @@ public final class MockSpdyPeer { if (this.type != -1) throw new IllegalStateException(); this.type = SpdyConnection.TYPE_NOOP; } + + @Override public void goAway(int flags, int lastGoodStreamId) { + if (this.type != -1) throw new IllegalStateException(); + this.type = SpdyConnection.TYPE_GOAWAY; + this.flags = flags; + this.streamId = lastGoodStreamId; + } } }
\ No newline at end of file diff --git a/src/test/java/libcore/net/spdy/SettingsTest.java b/src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java index fe479cf..1c44493 100644 --- a/src/test/java/libcore/net/spdy/SettingsTest.java +++ b/src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java @@ -13,24 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package libcore.net.spdy; - -import junit.framework.TestCase; - -import static libcore.net.spdy.Settings.DOWNLOAD_BANDWIDTH; -import static libcore.net.spdy.Settings.DOWNLOAD_RETRANS_RATE; -import static libcore.net.spdy.Settings.MAX_CONCURRENT_STREAMS; -import static libcore.net.spdy.Settings.PERSISTED; -import static libcore.net.spdy.Settings.PERSIST_VALUE; -import static libcore.net.spdy.Settings.UPLOAD_BANDWIDTH; - -public final class SettingsTest extends TestCase { - public void testUnsetField() { +package com.squareup.okhttp.internal.spdy; + +import com.squareup.okhttp.internal.spdy.Settings; +import static com.squareup.okhttp.internal.spdy.Settings.DOWNLOAD_BANDWIDTH; +import static com.squareup.okhttp.internal.spdy.Settings.DOWNLOAD_RETRANS_RATE; +import static com.squareup.okhttp.internal.spdy.Settings.MAX_CONCURRENT_STREAMS; +import static com.squareup.okhttp.internal.spdy.Settings.PERSISTED; +import static com.squareup.okhttp.internal.spdy.Settings.PERSIST_VALUE; +import static com.squareup.okhttp.internal.spdy.Settings.UPLOAD_BANDWIDTH; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Test; + +public final class SettingsTest { + @Test public void unsetField() { Settings settings = new Settings(); assertEquals(-3, settings.getUploadBandwidth(-3)); } - public void testSetFields() { + @Test public void setFields() { Settings settings = new Settings(); assertEquals(-3, settings.getUploadBandwidth(-3)); @@ -62,7 +65,7 @@ public final class SettingsTest extends TestCase { assertEquals(108, settings.getInitialWindowSize(-3)); } - public void testIsPersisted() { + @Test public void isPersisted() { Settings settings = new Settings(); // Initially false. @@ -93,7 +96,7 @@ public final class SettingsTest extends TestCase { assertFalse(settings.isPersisted(Settings.ROUND_TRIP_TIME)); } - public void testPersistValue() { + @Test public void persistValue() { Settings settings = new Settings(); // Initially false. @@ -124,7 +127,7 @@ public final class SettingsTest extends TestCase { assertFalse(settings.persistValue(Settings.ROUND_TRIP_TIME)); } - public void testMerge() { + @Test public void merge() { Settings a = new Settings(); a.set(UPLOAD_BANDWIDTH, PERSIST_VALUE, 100); a.set(DOWNLOAD_BANDWIDTH, PERSIST_VALUE, 200); diff --git a/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java b/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java new file mode 100644 index 0000000..d51fed7 --- /dev/null +++ b/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java @@ -0,0 +1,919 @@ +/* + * 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.internal.spdy; + +import static com.squareup.okhttp.internal.Util.UTF_8; +import static com.squareup.okhttp.internal.spdy.Settings.PERSIST_VALUE; +import static com.squareup.okhttp.internal.spdy.SpdyConnection.FLAG_FIN; +import static com.squareup.okhttp.internal.spdy.SpdyConnection.FLAG_UNIDIRECTIONAL; +import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_DATA; +import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_GOAWAY; +import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_NOOP; +import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_PING; +import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_RST_STREAM; +import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_SYN_REPLY; +import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_SYN_STREAM; +import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_FLOW_CONTROL_ERROR; +import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_INVALID_STREAM; +import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_PROTOCOL_ERROR; +import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_REFUSED_STREAM; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Test; + +public final class SpdyConnectionTest { + private static final IncomingStreamHandler REJECT_INCOMING_STREAMS + = new IncomingStreamHandler() { + @Override public void receive(SpdyStream stream) throws IOException { + throw new AssertionError(); + } + }; + private final MockSpdyPeer peer = new MockSpdyPeer(); + + @Test public void clientCreatesStreamAndServerReplies() throws Exception { + // write the mocking script + peer.acceptFrame(); + peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); + peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "robot".getBytes("UTF-8")); + peer.acceptFrame(); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); + assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders()); + assertStreamData("robot", stream.getInputStream()); + writeAndClose(stream, "c3po"); + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + assertEquals(0, synStream.flags); + assertEquals(1, synStream.streamId); + assertEquals(0, synStream.associatedStreamId); + assertEquals(Arrays.asList("b", "banana"), synStream.nameValueBlock); + MockSpdyPeer.InFrame requestData = peer.takeFrame(); + assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data)); + } + + @Test public void headersOnlyStreamIsClosedImmediately() throws Exception { + peer.acceptFrame(); // SYN STREAM + peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana")); + peer.play(); + + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + connection.newStream(Arrays.asList("a", "android"), false, false); + assertEquals(0, connection.openStreamCount()); + } + + @Test public void clientCreatesStreamAndServerRepliesWithFin() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN STREAM + peer.acceptFrame(); // PING + peer.sendFrame().synReply(FLAG_FIN, 1, Arrays.asList("a", "android")); + peer.sendFrame().ping(0, 1); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + connection.newStream(Arrays.asList("b", "banana"), false, true); + assertEquals(1, connection.openStreamCount()); + connection.ping().roundTripTime(); // Ensure that the SYN_REPLY has been received. + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + } + + @Test public void serverCreatesStreamAndClientReplies() throws Exception { + // write the mocking script + peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android")); + peer.acceptFrame(); + peer.play(); + + // play it back + final AtomicInteger receiveCount = new AtomicInteger(); + IncomingStreamHandler handler = new IncomingStreamHandler() { + @Override public void receive(SpdyStream stream) throws IOException { + receiveCount.incrementAndGet(); + assertEquals(Arrays.asList("a", "android"), stream.getRequestHeaders()); + assertEquals(-1, stream.getRstStatusCode()); + stream.reply(Arrays.asList("b", "banana"), true); + + } + }; + new SpdyConnection.Builder(true, peer.openSocket()) + .handler(handler) + .build(); + + // verify the peer received what was expected + MockSpdyPeer.InFrame reply = peer.takeFrame(); + assertEquals(TYPE_SYN_REPLY, reply.type); + assertEquals(0, reply.flags); + assertEquals(2, reply.streamId); + assertEquals(Arrays.asList("b", "banana"), reply.nameValueBlock); + assertEquals(1, receiveCount.get()); + } + + @Test public void replyWithNoData() throws Exception { + // write the mocking script + peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android")); + peer.acceptFrame(); + peer.play(); + + // play it back + final AtomicInteger receiveCount = new AtomicInteger(); + IncomingStreamHandler handler = new IncomingStreamHandler() { + @Override public void receive(SpdyStream stream) throws IOException { + stream.reply(Arrays.asList("b", "banana"), false); + receiveCount.incrementAndGet(); + } + }; + new SpdyConnection.Builder(true, peer.openSocket()) + .handler(handler) + .build(); + + // verify the peer received what was expected + MockSpdyPeer.InFrame reply = peer.takeFrame(); + assertEquals(TYPE_SYN_REPLY, reply.type); + assertEquals(FLAG_FIN, reply.flags); + assertEquals(Arrays.asList("b", "banana"), reply.nameValueBlock); + assertEquals(1, receiveCount.get()); + } + + @Test public void noop() throws Exception { + // write the mocking script + peer.acceptFrame(); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + connection.noop(); + + // verify the peer received what was expected + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_NOOP, ping.type); + assertEquals(0, ping.flags); + } + + @Test public void serverPingsClient() throws Exception { + // write the mocking script + peer.sendFrame().ping(0, 2); + peer.acceptFrame(); + peer.play(); + + // play it back + new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + + // verify the peer received what was expected + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + assertEquals(0, ping.flags); + assertEquals(2, ping.streamId); + } + + @Test public void clientPingsServer() throws Exception { + // write the mocking script + peer.acceptFrame(); + peer.sendFrame().ping(0, 1); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + Ping ping = connection.ping(); + assertTrue(ping.roundTripTime() > 0); + assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1)); + + // verify the peer received what was expected + MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); + assertEquals(TYPE_PING, pingFrame.type); + assertEquals(0, pingFrame.flags); + assertEquals(1, pingFrame.streamId); + } + + @Test public void unexpectedPingIsNotReturned() throws Exception { + // write the mocking script + peer.sendFrame().ping(0, 2); + peer.acceptFrame(); + peer.sendFrame().ping(0, 3); // This ping will not be returned. + peer.sendFrame().ping(0, 4); + peer.acceptFrame(); + peer.play(); + + // play it back + new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + + // verify the peer received what was expected + MockSpdyPeer.InFrame ping2 = peer.takeFrame(); + assertEquals(2, ping2.streamId); + MockSpdyPeer.InFrame ping4 = peer.takeFrame(); + assertEquals(4, ping4.streamId); + } + + @Test public void serverSendsSettingsToClient() throws Exception { + // write the mocking script + Settings settings = new Settings(); + settings.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 10); + peer.sendFrame().settings(Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS, settings); + peer.sendFrame().ping(0, 2); + peer.acceptFrame(); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + + peer.takeFrame(); // Guarantees that the Settings frame has been processed. + synchronized (connection) { + assertEquals(10, connection.settings.getMaxConcurrentStreams(-1)); + } + } + + @Test public void multipleSettingsFramesAreMerged() throws Exception { + // write the mocking script + Settings settings1 = new Settings(); + settings1.set(Settings.UPLOAD_BANDWIDTH, PERSIST_VALUE, 100); + settings1.set(Settings.DOWNLOAD_BANDWIDTH, PERSIST_VALUE, 200); + settings1.set(Settings.DOWNLOAD_RETRANS_RATE, 0, 300); + peer.sendFrame().settings(0, settings1); + Settings settings2 = new Settings(); + settings2.set(Settings.DOWNLOAD_BANDWIDTH, 0, 400); + settings2.set(Settings.DOWNLOAD_RETRANS_RATE, PERSIST_VALUE, 500); + settings2.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 600); + peer.sendFrame().settings(0, settings2); + peer.sendFrame().ping(0, 2); + peer.acceptFrame(); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + + peer.takeFrame(); // Guarantees that the Settings frame has been processed. + synchronized (connection) { + assertEquals(100, connection.settings.getUploadBandwidth(-1)); + assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.UPLOAD_BANDWIDTH)); + assertEquals(400, connection.settings.getDownloadBandwidth(-1)); + assertEquals(0, connection.settings.flags(Settings.DOWNLOAD_BANDWIDTH)); + assertEquals(500, connection.settings.getDownloadRetransRate(-1)); + assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.DOWNLOAD_RETRANS_RATE)); + assertEquals(600, connection.settings.getMaxConcurrentStreams(-1)); + assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.MAX_CONCURRENT_STREAMS)); + } + } + + @Test public void bogusDataFrameDoesNotDisruptConnection() throws Exception { + // write the mocking script + peer.sendFrame().data(SpdyConnection.FLAG_FIN, 42, "bogus".getBytes("UTF-8")); + peer.acceptFrame(); // RST_STREAM + peer.sendFrame().ping(0, 2); + peer.acceptFrame(); // PING + peer.play(); + + // play it back + new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + + // verify the peer received what was expected + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(0, rstStream.flags); + assertEquals(42, rstStream.streamId); + assertEquals(RST_INVALID_STREAM, rstStream.statusCode); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(2, ping.streamId); + } + + @Test public void bogusReplyFrameDoesNotDisruptConnection() throws Exception { + // write the mocking script + peer.sendFrame().synReply(0, 42, Arrays.asList("a", "android")); + peer.acceptFrame(); // RST_STREAM + peer.sendFrame().ping(0, 2); + peer.acceptFrame(); // PING + peer.play(); + + // play it back + new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + + // verify the peer received what was expected + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(0, rstStream.flags); + assertEquals(42, rstStream.streamId); + assertEquals(RST_INVALID_STREAM, rstStream.statusCode); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(2, ping.streamId); + } + + @Test public void clientClosesClientOutputStream() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // TYPE_DATA + peer.acceptFrame(); // TYPE_DATA with FLAG_FIN + peer.sendFrame().ping(0, 2); + peer.acceptFrame(); // PING response + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, false); + OutputStream out = stream.getOutputStream(); + out.write("square".getBytes(UTF_8)); + out.flush(); + assertEquals(1, connection.openStreamCount()); + out.close(); + try { + out.write("round".getBytes(UTF_8)); + fail(); + } catch (Exception expected) { + assertEquals("stream closed", expected.getMessage()); + } + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + assertEquals(FLAG_UNIDIRECTIONAL, synStream.flags); + MockSpdyPeer.InFrame data = peer.takeFrame(); + assertEquals(TYPE_DATA, data.type); + assertEquals(0, data.flags); + assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); + MockSpdyPeer.InFrame fin = peer.takeFrame(); + assertEquals(TYPE_DATA, fin.type); + assertEquals(FLAG_FIN, fin.flags); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + assertEquals(2, ping.streamId); + } + + @Test public void serverClosesClientOutputStream() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReset(1, SpdyStream.RST_CANCEL); + peer.acceptFrame(); // PING + peer.sendFrame().ping(0, 1); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); + OutputStream out = stream.getOutputStream(); + connection.ping().roundTripTime(); // Ensure that the RST_CANCEL has been received. + try { + out.write("square".getBytes(UTF_8)); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: CANCEL", expected.getMessage()); + } + out.close(); + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + assertEquals(0, synStream.flags); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + assertEquals(1, ping.streamId); + } + + /** + * Test that the client sends a RST_STREAM if doing so won't disrupt the + * output stream. + */ + @Test public void clientClosesClientInputStream() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, true); + InputStream in = stream.getInputStream(); + OutputStream out = stream.getOutputStream(); + in.close(); + try { + in.read(); + fail(); + } catch (IOException expected) { + assertEquals("stream closed", expected.getMessage()); + } + try { + out.write('a'); + fail(); + } catch (IOException expected) { + assertEquals("stream finished", expected.getMessage()); + } + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + assertEquals(SpdyConnection.FLAG_FIN, synStream.flags); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(SpdyStream.RST_CANCEL, rstStream.statusCode); + } + + /** + * Test that the client doesn't send a RST_STREAM if doing so will disrupt + * the output stream. + */ + @Test public void clientClosesClientInputStreamIfOutputStreamIsClosed() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // DATA + peer.acceptFrame(); // DATA with FLAG_FIN + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); + InputStream in = stream.getInputStream(); + OutputStream out = stream.getOutputStream(); + in.close(); + try { + in.read(); + fail(); + } catch (IOException expected) { + assertEquals("stream closed", expected.getMessage()); + } + out.write("square".getBytes(UTF_8)); + out.flush(); + out.close(); + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + assertEquals(0, synStream.flags); + MockSpdyPeer.InFrame data = peer.takeFrame(); + assertEquals(TYPE_DATA, data.type); + assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); + MockSpdyPeer.InFrame fin = peer.takeFrame(); + assertEquals(TYPE_DATA, fin.type); + assertEquals(FLAG_FIN, fin.flags); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(SpdyStream.RST_CANCEL, rstStream.statusCode); + } + + @Test public void serverClosesClientInputStream() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().data(FLAG_FIN, 1, "square".getBytes(UTF_8)); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) + .handler(REJECT_INCOMING_STREAMS) + .build(); + SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, true); + InputStream in = stream.getInputStream(); + assertStreamData("square", in); + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + assertEquals(SpdyConnection.FLAG_FIN, synStream.flags); + } + + @Test public void remoteDoubleSynReply() throws Exception { + // write the mocking script + peer.acceptFrame(); + peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); + peer.acceptFrame(); // PING + peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana")); + peer.sendFrame().ping(0, 1); + peer.acceptFrame(); // RST STREAM + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + SpdyStream stream = connection.newStream(Arrays.asList("c", "cola"), true, true); + assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders()); + connection.ping().roundTripTime(); // Ensure that the 2nd SYN REPLY has been received. + try { + stream.getInputStream().read(); + fail(); + } catch (IOException e) { + assertEquals("stream was reset: PROTOCOL_ERROR", e.getMessage()); + } + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(1, rstStream.streamId); + assertEquals(0, rstStream.flags); + assertEquals(RST_PROTOCOL_ERROR, rstStream.statusCode); + } + + @Test public void remoteDoubleSynStream() throws Exception { + // write the mocking script + peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android")); + peer.acceptFrame(); + peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("b", "banana")); + peer.acceptFrame(); + peer.play(); + + // play it back + final AtomicInteger receiveCount = new AtomicInteger(); + IncomingStreamHandler handler = new IncomingStreamHandler() { + @Override public void receive(SpdyStream stream) throws IOException { + receiveCount.incrementAndGet(); + assertEquals(Arrays.asList("a", "android"), stream.getRequestHeaders()); + assertEquals(-1, stream.getRstStatusCode()); + stream.reply(Arrays.asList("c", "cola"), true); + } + }; + new SpdyConnection.Builder(true, peer.openSocket()) + .handler(handler) + .build(); + + // verify the peer received what was expected + MockSpdyPeer.InFrame reply = peer.takeFrame(); + assertEquals(TYPE_SYN_REPLY, reply.type); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(2, rstStream.streamId); + assertEquals(0, rstStream.flags); + assertEquals(RST_PROTOCOL_ERROR, rstStream.statusCode); + assertEquals(1, receiveCount.intValue()); + } + + @Test public void remoteSendsDataAfterInFinished() throws Exception { + // write the mocking script + peer.acceptFrame(); + peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); + peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "robot".getBytes("UTF-8")); + peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "c3po".getBytes("UTF-8")); // Ignored. + peer.sendFrame().ping(0, 2); // Ping just to make sure the stream was fastforwarded. + peer.acceptFrame(); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); + assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders()); + assertStreamData("robot", stream.getInputStream()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + assertEquals(2, ping.streamId); + assertEquals(0, ping.flags); + } + + @Test public void remoteSendsTooMuchData() throws Exception { + // write the mocking script + peer.acceptFrame(); + peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana")); + peer.sendFrame().data(0, 1, new byte[64 * 1024 + 1]); + peer.acceptFrame(); + peer.sendFrame().ping(0, 2); // Ping just to make sure the stream was fastforwarded. + peer.acceptFrame(); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); + assertEquals(Arrays.asList("b", "banana"), stream.getResponseHeaders()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(1, rstStream.streamId); + assertEquals(0, rstStream.flags); + assertEquals(RST_FLOW_CONTROL_ERROR, rstStream.statusCode); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + assertEquals(2, ping.streamId); + } + + @Test public void remoteSendsRefusedStreamBeforeReplyHeaders() throws Exception { + // write the mocking script + peer.acceptFrame(); + peer.sendFrame().synReset(1, RST_REFUSED_STREAM); + peer.sendFrame().ping(0, 2); + peer.acceptFrame(); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); + try { + stream.getResponseHeaders(); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); + } + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + assertEquals(2, ping.streamId); + assertEquals(0, ping.flags); + } + + @Test public void receiveGoAway() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN STREAM 1 + peer.acceptFrame(); // SYN STREAM 3 + peer.sendFrame().goAway(0, 1); + peer.acceptFrame(); // PING + peer.sendFrame().ping(0, 1); + peer.acceptFrame(); // DATA STREAM 1 + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + SpdyStream stream1 = connection.newStream(Arrays.asList("a", "android"), true, true); + SpdyStream stream2 = connection.newStream(Arrays.asList("b", "banana"), true, true); + connection.ping().roundTripTime(); // Ensure that the GO_AWAY has been received. + stream1.getOutputStream().write("abc".getBytes(UTF_8)); + try { + stream2.getOutputStream().write("abc".getBytes(UTF_8)); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); + } + stream1.getOutputStream().write("def".getBytes(UTF_8)); + stream1.getOutputStream().close(); + try { + connection.newStream(Arrays.asList("c", "cola"), true, true); + fail(); + } catch (IOException expected) { + assertEquals("shutdown", expected.getMessage()); + } + assertEquals(1, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream1.type); + MockSpdyPeer.InFrame synStream2 = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream2.type); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + MockSpdyPeer.InFrame data1 = peer.takeFrame(); + assertEquals(TYPE_DATA, data1.type); + assertEquals(1, data1.streamId); + assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data)); + } + + @Test public void sendGoAway() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN STREAM 1 + peer.acceptFrame(); // GOAWAY + peer.acceptFrame(); // PING + peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("b", "banana")); // Should be ignored! + peer.sendFrame().ping(0, 1); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + connection.newStream(Arrays.asList("a", "android"), true, true); + Ping ping = connection.ping(); + connection.shutdown(); + ping.roundTripTime(); // Ensure that the SYN STREAM has been received. + assertEquals(1, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream1.type); + MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); + assertEquals(TYPE_PING, pingFrame.type); + MockSpdyPeer.InFrame goaway = peer.takeFrame(); + assertEquals(TYPE_GOAWAY, goaway.type); + assertEquals(0, goaway.streamId); + } + + @Test public void noPingsAfterShutdown() throws Exception { + // write the mocking script + peer.acceptFrame(); // GOAWAY + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + connection.shutdown(); + try { + connection.ping(); + fail(); + } catch (IOException expected) { + assertEquals("shutdown", expected.getMessage()); + } + + // verify the peer received what was expected + MockSpdyPeer.InFrame goaway = peer.takeFrame(); + assertEquals(TYPE_GOAWAY, goaway.type); + } + + @Test public void close() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN STREAM + peer.acceptFrame(); // GOAWAY + peer.acceptFrame(); // RST STREAM + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); + assertEquals(1, connection.openStreamCount()); + connection.close(); + assertEquals(0, connection.openStreamCount()); + try { + connection.newStream(Arrays.asList("b", "banana"), true, true); + fail(); + } catch (IOException expected) { + assertEquals("shutdown", expected.getMessage()); + } + try { + stream.getOutputStream().write(0); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: CANCEL", expected.getMessage()); + } + try { + stream.getInputStream().read(); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: CANCEL", expected.getMessage()); + } + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + MockSpdyPeer.InFrame goaway = peer.takeFrame(); + assertEquals(TYPE_GOAWAY, goaway.type); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(1, rstStream.streamId); + } + + @Test public void closeCancelsPings() throws Exception { + // write the mocking script + peer.acceptFrame(); // PING + peer.acceptFrame(); // GOAWAY + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + Ping ping = connection.ping(); + connection.close(); + assertEquals(-1, ping.roundTripTime()); + } + + @Test public void readTimeoutExpires() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN STREAM + peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); + stream.setReadTimeout(1000); + InputStream in = stream.getInputStream(); + long startNanos = System.nanoTime(); + try { + in.read(); + fail(); + } catch (IOException expected) { + } + long elapsedNanos = System.nanoTime() - startNanos; + assertEquals(1000d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */); + assertEquals(1, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + } + + @Test public void headers() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN STREAM + peer.acceptFrame(); // PING + peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); + peer.sendFrame().headers(0, 1, Arrays.asList("c", "c3po")); + peer.sendFrame().ping(0, 1); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); + connection.ping().roundTripTime(); // Ensure that the HEADERS has been received. + assertEquals(Arrays.asList("a", "android", "c", "c3po"), stream.getResponseHeaders()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + } + + @Test public void headersBeforeReply() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN STREAM + peer.acceptFrame(); // PING + peer.sendFrame().headers(0, 1, Arrays.asList("c", "c3po")); + peer.acceptFrame(); // RST STREAM + peer.sendFrame().ping(0, 1); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); + connection.ping().roundTripTime(); // Ensure that the HEADERS has been received. + try { + stream.getResponseHeaders(); + fail(); + } catch (IOException e) { + assertEquals("stream was reset: PROTOCOL_ERROR", e.getMessage()); + } + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(RST_PROTOCOL_ERROR, rstStream.statusCode); + } + + private void writeAndClose(SpdyStream stream, String data) throws IOException { + OutputStream out = stream.getOutputStream(); + out.write(data.getBytes("UTF-8")); + out.close(); + } + + private void assertStreamData(String expected, InputStream inputStream) throws IOException { + ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + for (int count; (count = inputStream.read(buffer)) != -1; ) { + bytesOut.write(buffer, 0, count); + } + String actual = bytesOut.toString("UTF-8"); + assertEquals(expected, actual); + } +} diff --git a/src/main/java/libcore/net/spdy/SpdyServer.java b/src/test/java/com/squareup/okhttp/internal/spdy/SpdyServer.java index d2ad4ec..1b5574d 100644 --- a/src/main/java/libcore/net/spdy/SpdyServer.java +++ b/src/test/java/com/squareup/okhttp/internal/spdy/SpdyServer.java @@ -14,41 +14,70 @@ * limitations under the License. */ -package libcore.net.spdy; +package com.squareup.okhttp.internal.spdy; +import com.squareup.okhttp.internal.SslContextBuilder; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.Arrays; import java.util.List; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import org.eclipse.jetty.npn.NextProtoNego; /** - * A basic SPDY server that serves the contents of a local directory. This - * server will service a single SPDY connection. + * A basic SPDY server that serves the contents of a local directory. */ public final class SpdyServer implements IncomingStreamHandler { private final File baseDirectory; + private SSLSocketFactory sslSocketFactory; public SpdyServer(File baseDirectory) { this.baseDirectory = baseDirectory; } + public void useHttps(SSLSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + } + private void run() throws Exception { ServerSocket serverSocket = new ServerSocket(8888); serverSocket.setReuseAddress(true); - Socket socket = serverSocket.accept(); - SpdyConnection connection = new SpdyConnection.Builder(false, socket) - .handler(this) - .build(); + while (true) { + Socket socket = serverSocket.accept(); + if (sslSocketFactory != null) { + socket = doSsl(socket); + } + new SpdyConnection.Builder(false, socket).handler(this).build(); + } + } - // Chrome doesn't seem to like pings coming from the server: - // https://groups.google.com/forum/?fromgroups=#!topic/spdy-dev/NgTHYUQKWBY - // System.out.println("PING RTT TIME " + connection.ping().roundTripTime()); + private Socket doSsl(Socket socket) throws IOException { + SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, + socket.getInetAddress().getHostAddress(), socket.getPort(), true); + sslSocket.setUseClientMode(false); + NextProtoNego.put(sslSocket, new NextProtoNego.ServerProvider() { + @Override public void unsupported() { + System.out.println("UNSUPPORTED"); + } + @Override public List<String> protocols() { + return Arrays.asList("spdy/2"); + } + @Override public void protocolSelected(String protocol) { + System.out.println("PROTOCOL SELECTED: " + protocol); + } + }); + return sslSocket; } @Override public void receive(final SpdyStream stream) throws IOException { @@ -69,7 +98,9 @@ public final class SpdyServer implements IncomingStreamHandler { File file = new File(baseDirectory + path); - if (file.exists() && !file.isDirectory()) { + if (file.isDirectory()) { + serveDirectory(stream, file.list()); + } else if (file.exists()) { serveFile(stream, file); } else { send404(stream, path); @@ -89,6 +120,21 @@ public final class SpdyServer implements IncomingStreamHandler { out.close(); } + private void serveDirectory(SpdyStream stream, String[] files) throws IOException { + List<String> responseHeaders = Arrays.asList( + "status", "200", + "version", "HTTP/1.1", + "content-type", "text/html; charset=UTF-8" + ); + stream.reply(responseHeaders, true); + OutputStream out = stream.getOutputStream(); + Writer writer = new OutputStreamWriter(out, "UTF-8"); + for (String file : files) { + writer.write("<a href='" + file + "'>" + file + "</a><br>"); + } + writer.close(); + } + private void serveFile(SpdyStream stream, File file) throws IOException { InputStream in = new FileInputStream(file); byte[] buffer = new byte[8192]; @@ -115,6 +161,10 @@ public final class SpdyServer implements IncomingStreamHandler { return; } - new SpdyServer(new File(args[0])).run(); + SpdyServer server = new SpdyServer(new File(args[0])); + SSLContext sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName()) + .build(); + server.useHttps(sslContext.getSocketFactory()); + server.run(); } } diff --git a/src/test/java/libcore/net/http/NewURLConnectionTest.java b/src/test/java/libcore/net/http/NewURLConnectionTest.java deleted file mode 100644 index 8c6121e..0000000 --- a/src/test/java/libcore/net/http/NewURLConnectionTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 libcore.net.http; - -import junit.framework.TestCase; - -public final class NewURLConnectionTest extends TestCase { - - public void testUrlConnection() { - } - - // TODO: write a test that shows pooled connections detect HTTP/1.0 (vs. HTTP/1.1) - - // TODO: write a test that shows POST bodies are retained on AUTH problems (or prove it unnecessary) - - // TODO: cookies + trailers. Do cookie headers get processed too many times? - - // TODO: crash on header names or values containing the '\0' character - - // TODO: crash on empty names and empty values - - // TODO: deflate compression - - // TODO: read the outgoing status line and incoming status line? - -} diff --git a/src/test/java/libcore/net/spdy/SpdyConnectionTest.java b/src/test/java/libcore/net/spdy/SpdyConnectionTest.java deleted file mode 100644 index 4009599..0000000 --- a/src/test/java/libcore/net/spdy/SpdyConnectionTest.java +++ /dev/null @@ -1,519 +0,0 @@ -/* - * 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 libcore.net.spdy; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import junit.framework.TestCase; - -import static libcore.net.spdy.Settings.PERSIST_VALUE; -import static libcore.net.spdy.SpdyConnection.FLAG_FIN; -import static libcore.net.spdy.SpdyConnection.TYPE_DATA; -import static libcore.net.spdy.SpdyConnection.TYPE_NOOP; -import static libcore.net.spdy.SpdyConnection.TYPE_PING; -import static libcore.net.spdy.SpdyConnection.TYPE_RST_STREAM; -import static libcore.net.spdy.SpdyConnection.TYPE_SYN_REPLY; -import static libcore.net.spdy.SpdyConnection.TYPE_SYN_STREAM; -import static libcore.net.spdy.SpdyStream.RST_INVALID_STREAM; -import static libcore.util.Charsets.UTF_8; - -public final class SpdyConnectionTest extends TestCase { - private static final IncomingStreamHandler REJECT_INCOMING_STREAMS - = new IncomingStreamHandler() { - @Override public void receive(SpdyStream stream) throws IOException { - throw new AssertionError(); - } - }; - private final MockSpdyPeer peer = new MockSpdyPeer(); - - public void testClientCreatesStreamAndServerReplies() throws Exception { - // write the mocking script - peer.acceptFrame(); - peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); - peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "robot".getBytes("UTF-8")); - peer.acceptFrame(); - peer.play(); - - // play it back - SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); - SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); - assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders()); - assertStreamData("robot", stream.getInputStream()); - writeAndClose(stream, "c3po"); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_SYN_STREAM, synStream.type); - assertEquals(0, synStream.flags); - assertEquals(1, synStream.streamId); - assertEquals(0, synStream.associatedStreamId); - assertEquals(Arrays.asList("b", "banana"), synStream.nameValueBlock); - MockSpdyPeer.InFrame requestData = peer.takeFrame(); - assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data)); - } - - public void testServerCreatesStreamAndClientReplies() throws Exception { - // write the mocking script - peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android")); - peer.acceptFrame(); - peer.play(); - - // play it back - final AtomicInteger receiveCount = new AtomicInteger(); - IncomingStreamHandler handler = new IncomingStreamHandler() { - @Override public void receive(SpdyStream stream) throws IOException { - receiveCount.incrementAndGet(); - assertEquals(Arrays.asList("a", "android"), stream.getRequestHeaders()); - assertEquals(-1, stream.getRstStatusCode()); - stream.reply(Arrays.asList("b", "banana"), true); - - } - }; - new SpdyConnection.Builder(true, peer.openSocket()) - .handler(handler) - .build(); - - // verify the peer received what was expected - MockSpdyPeer.InFrame reply = peer.takeFrame(); - assertEquals(TYPE_SYN_REPLY, reply.type); - assertEquals(0, reply.flags); - assertEquals(2, reply.streamId); - assertEquals(Arrays.asList("b", "banana"), reply.nameValueBlock); - assertEquals(1, receiveCount.get()); - } - - public void testReplyWithNoData() throws Exception { - // write the mocking script - peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android")); - peer.acceptFrame(); - peer.play(); - - // play it back - final AtomicInteger receiveCount = new AtomicInteger(); - IncomingStreamHandler handler = new IncomingStreamHandler() { - @Override public void receive(SpdyStream stream) throws IOException { - stream.reply(Arrays.asList("b", "banana"), false); - receiveCount.incrementAndGet(); - } - }; - new SpdyConnection.Builder(true, peer.openSocket()) - .handler(handler) - .build(); - - // verify the peer received what was expected - MockSpdyPeer.InFrame reply = peer.takeFrame(); - assertEquals(TYPE_SYN_REPLY, reply.type); - assertEquals(FLAG_FIN, reply.flags); - assertEquals(Arrays.asList("b", "banana"), reply.nameValueBlock); - assertEquals(1, receiveCount.get()); - } - - public void testNoop() throws Exception { - // write the mocking script - peer.acceptFrame(); - peer.play(); - - // play it back - SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - connection.noop(); - - // verify the peer received what was expected - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_NOOP, ping.type); - assertEquals(0, ping.flags); - } - - public void testServerPingsClient() throws Exception { - // write the mocking script - peer.sendFrame().ping(0, 2); - peer.acceptFrame(); - peer.play(); - - // play it back - new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - - // verify the peer received what was expected - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - assertEquals(0, ping.flags); - assertEquals(2, ping.streamId); - } - - public void testClientPingsServer() throws Exception { - // write the mocking script - peer.acceptFrame(); - peer.sendFrame().ping(0, 1); - peer.play(); - - // play it back - SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - Ping ping = connection.ping(); - assertTrue(ping.roundTripTime() > 0); - assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1)); - - // verify the peer received what was expected - MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); - assertEquals(TYPE_PING, pingFrame.type); - assertEquals(0, pingFrame.flags); - assertEquals(1, pingFrame.streamId); - } - - public void testUnexpectedPingIsNotReturned() throws Exception { - // write the mocking script - peer.sendFrame().ping(0, 2); - peer.acceptFrame(); - peer.sendFrame().ping(0, 3); // This ping will not be returned. - peer.sendFrame().ping(0, 4); - peer.acceptFrame(); - peer.play(); - - // play it back - new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - - // verify the peer received what was expected - MockSpdyPeer.InFrame ping2 = peer.takeFrame(); - assertEquals(2, ping2.streamId); - MockSpdyPeer.InFrame ping4 = peer.takeFrame(); - assertEquals(4, ping4.streamId); - } - - public void testServerSendsSettingsToClient() throws Exception { - // write the mocking script - Settings settings = new Settings(); - settings.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 10); - peer.sendFrame().settings(Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS, settings); - peer.sendFrame().ping(0, 2); - peer.acceptFrame(); - peer.play(); - - // play it back - SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - - peer.takeFrame(); // Guarantees that the Settings frame has been processed. - synchronized (connection) { - assertEquals(10, connection.settings.getMaxConcurrentStreams(-1)); - } - } - - public void testMultipleSettingsFramesAreMerged() throws Exception { - // write the mocking script - Settings settings1 = new Settings(); - settings1.set(Settings.UPLOAD_BANDWIDTH, PERSIST_VALUE, 100); - settings1.set(Settings.DOWNLOAD_BANDWIDTH, PERSIST_VALUE, 200); - settings1.set(Settings.DOWNLOAD_RETRANS_RATE, 0, 300); - peer.sendFrame().settings(0, settings1); - Settings settings2 = new Settings(); - settings2.set(Settings.DOWNLOAD_BANDWIDTH, 0, 400); - settings2.set(Settings.DOWNLOAD_RETRANS_RATE, PERSIST_VALUE, 500); - settings2.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 600); - peer.sendFrame().settings(0, settings2); - peer.sendFrame().ping(0, 2); - peer.acceptFrame(); - peer.play(); - - // play it back - SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - - peer.takeFrame(); // Guarantees that the Settings frame has been processed. - synchronized (connection) { - assertEquals(100, connection.settings.getUploadBandwidth(-1)); - assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.UPLOAD_BANDWIDTH)); - assertEquals(400, connection.settings.getDownloadBandwidth(-1)); - assertEquals(0, connection.settings.flags(Settings.DOWNLOAD_BANDWIDTH)); - assertEquals(500, connection.settings.getDownloadRetransRate(-1)); - assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.DOWNLOAD_RETRANS_RATE)); - assertEquals(600, connection.settings.getMaxConcurrentStreams(-1)); - assertEquals(PERSIST_VALUE, connection.settings.flags(Settings.MAX_CONCURRENT_STREAMS)); - } - } - - public void testBogusDataFrameDoesNotDisruptConnection() throws Exception { - // write the mocking script - peer.sendFrame().data(SpdyConnection.FLAG_FIN, 42, "bogus".getBytes("UTF-8")); - peer.acceptFrame(); // RST_STREAM - peer.sendFrame().ping(0, 2); - peer.acceptFrame(); // PING - peer.play(); - - // play it back - new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - - // verify the peer received what was expected - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(0, rstStream.flags); - assertEquals(42, rstStream.streamId); - assertEquals(RST_INVALID_STREAM, rstStream.statusCode); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(2, ping.streamId); - } - - public void testBogusReplyFrameDoesNotDisruptConnection() throws Exception { - // write the mocking script - peer.sendFrame().synReply(0, 42, Arrays.asList("a", "android")); - peer.acceptFrame(); // RST_STREAM - peer.sendFrame().ping(0, 2); - peer.acceptFrame(); // PING - peer.play(); - - // play it back - new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - - // verify the peer received what was expected - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(0, rstStream.flags); - assertEquals(42, rstStream.streamId); - assertEquals(RST_INVALID_STREAM, rstStream.statusCode); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(2, ping.streamId); - } - - public void testClientClosesClientOutputStream() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.acceptFrame(); // TYPE_DATA - peer.acceptFrame(); // TYPE_DATA with FLAG_FIN - peer.sendFrame().ping(0, 2); - peer.acceptFrame(); // PING response - peer.play(); - - // play it back - SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); - OutputStream out = stream.getOutputStream(); - out.write("square".getBytes(UTF_8)); - out.flush(); - out.close(); - try { - out.write("round".getBytes(UTF_8)); - fail(); - } catch (Exception expected) { - } - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_SYN_STREAM, synStream.type); - assertEquals(0, synStream.flags); - MockSpdyPeer.InFrame data = peer.takeFrame(); - assertEquals(TYPE_DATA, data.type); - assertEquals(0, data.flags); - assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); - MockSpdyPeer.InFrame fin = peer.takeFrame(); - assertEquals(TYPE_DATA, fin.type); - assertEquals(FLAG_FIN, fin.flags); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - assertEquals(2, ping.streamId); - } - - public void testServerClosesClientOutputStream() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReset(1, SpdyStream.RST_CANCEL); - peer.acceptFrame(); // PING - peer.sendFrame().ping(0, 1); - peer.play(); - - // play it back - SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); - OutputStream out = stream.getOutputStream(); - connection.ping().roundTripTime(); // Ensure that the RST_CANCEL has been received. - try { - out.write("square".getBytes(UTF_8)); - fail(); - } catch (IOException expected) { - } - out.close(); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_SYN_STREAM, synStream.type); - assertEquals(0, synStream.flags); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - assertEquals(1, ping.streamId); - } - - /** - * Test that the client sends a RST_STREAM if doing so won't disrupt the - * output stream. - */ - public void testClientClosesClientInputStream() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, true); - InputStream in = stream.getInputStream(); - OutputStream out = stream.getOutputStream(); - in.close(); - try { - in.read(); - fail(); - } catch (IOException expected) { - } - try { - out.write('a'); - fail(); - } catch (IOException expected) { - } - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_SYN_STREAM, synStream.type); - assertEquals(SpdyConnection.FLAG_FIN, synStream.flags); - - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(SpdyStream.RST_CANCEL, rstStream.statusCode); - } - - /** - * Test that the client doesn't send a RST_STREAM if doing so will disrupt - * the output stream. - */ - public void testClientClosesClientInputStreamIfOutputStreamIsClosed() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.acceptFrame(); // DATA - peer.acceptFrame(); // DATA with FLAG_FIN - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), true, true); - InputStream in = stream.getInputStream(); - OutputStream out = stream.getOutputStream(); - in.close(); - try { - in.read(); - fail(); - } catch (IOException expected) { - } - out.write("square".getBytes(UTF_8)); - out.flush(); - out.close(); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_SYN_STREAM, synStream.type); - assertEquals(0, synStream.flags); - - MockSpdyPeer.InFrame data = peer.takeFrame(); - assertEquals(TYPE_DATA, data.type); - assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); - - MockSpdyPeer.InFrame fin = peer.takeFrame(); - assertEquals(TYPE_DATA, fin.type); - assertEquals(FLAG_FIN, fin.flags); - - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(SpdyStream.RST_CANCEL, rstStream.statusCode); - } - - public void testServerClosesClientInputStream() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().data(FLAG_FIN, 1, "square".getBytes(UTF_8)); - peer.play(); - - // play it back - SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()) - .handler(REJECT_INCOMING_STREAMS) - .build(); - SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, true); - InputStream in = stream.getInputStream(); - assertStreamData("square", in); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_SYN_STREAM, synStream.type); - assertEquals(SpdyConnection.FLAG_FIN, synStream.flags); - } - - public void testRemoteDoubleReply() { - // We should get a PROTOCOL ERROR - // TODO - } - - public void testRemoteSendsDataAfterInFinished() { - // We have a bug where we don't fastfoward the stream - // TODO - } - - public void testRemoteSendsTooMuchData() { - // We should send RST_FLOW_CONTROL_ERROR (and fastforward the stream) - // TODO - } - - public void testRemoteSendsRefusedStreamBeforeReplyHeaders() { - // Calling getResponseHeaders() should throw an IOException if the stream is refused. - // TODO - } - - private void writeAndClose(SpdyStream stream, String data) throws IOException { - OutputStream out = stream.getOutputStream(); - out.write(data.getBytes("UTF-8")); - out.close(); - } - - private void assertStreamData(String expected, InputStream inputStream) throws IOException { - ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - for (int count; (count = inputStream.read(buffer)) != -1; ) { - bytesOut.write(buffer, 0, count); - } - String actual = bytesOut.toString("UTF-8"); - assertEquals(expected, actual); - } -} |