mirror of
https://github.com/square/okhttp.git
synced 2026-01-25 16:01:38 +03:00
Teach MockWebServer SPDY.
Kill MockSpdyServer in the process.
This commit is contained in:
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Square, Inc.
|
||||
* 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.mockspdyserver;
|
||||
|
||||
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||
import com.squareup.okhttp.mockwebserver.QueueDispatcher;
|
||||
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
||||
import com.squareup.okhttp.internal.Platform;
|
||||
import com.squareup.okhttp.internal.spdy.IncomingStreamHandler;
|
||||
import com.squareup.okhttp.internal.spdy.SpdyConnection;
|
||||
import com.squareup.okhttp.internal.spdy.SpdyStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/** A scriptable spdy/3 + HTTP server. */
|
||||
public final class MockSpdyServer {
|
||||
private static final byte[] NPN_PROTOCOLS = new byte[] { 6, 's', 'p', 'd', 'y', '/', '3', };
|
||||
private static final Logger logger = Logger.getLogger(MockSpdyServer.class.getName());
|
||||
private SSLSocketFactory sslSocketFactory;
|
||||
private QueueDispatcher dispatcher = new QueueDispatcher();
|
||||
private ServerSocket serverSocket;
|
||||
private final Set<Socket> openClientSockets =
|
||||
Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>());
|
||||
private int port = -1;
|
||||
private final BlockingQueue<RecordedRequest> requestQueue =
|
||||
new LinkedBlockingQueue<RecordedRequest>();
|
||||
|
||||
public MockSpdyServer(SSLSocketFactory sslSocketFactory) {
|
||||
this.sslSocketFactory = sslSocketFactory;
|
||||
}
|
||||
|
||||
public String getHostName() {
|
||||
try {
|
||||
return InetAddress.getLocalHost().getHostName();
|
||||
} catch (UnknownHostException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
if (port == -1) {
|
||||
throw new IllegalStateException("Cannot retrieve port before calling play()");
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
public URL getUrl(String path) {
|
||||
try {
|
||||
return new URL("https://" + getHostName() + ":" + getPort() + path);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cookie domain for this server. This returns the server's
|
||||
* non-loopback host name if it is known. Otherwise this returns ".local"
|
||||
* for this server's loopback name.
|
||||
*/
|
||||
public String getCookieDomain() {
|
||||
String hostName = getHostName();
|
||||
return hostName.contains(".") ? hostName : ".local";
|
||||
}
|
||||
|
||||
/**
|
||||
* Awaits the next HTTP request, removes it, and returns it. Callers should
|
||||
* use this to verify the request sent was as intended.
|
||||
*/
|
||||
public RecordedRequest takeRequest() throws InterruptedException {
|
||||
return requestQueue.take();
|
||||
}
|
||||
|
||||
public void play() throws IOException {
|
||||
serverSocket = new ServerSocket(0);
|
||||
serverSocket.setReuseAddress(true);
|
||||
port = serverSocket.getLocalPort();
|
||||
|
||||
Thread acceptThread = new Thread("MockSpdyServer-accept-" + port) {
|
||||
@Override public void run() {
|
||||
int sequenceNumber = 0;
|
||||
try {
|
||||
acceptConnections(sequenceNumber);
|
||||
} catch (Throwable e) {
|
||||
logger.log(Level.WARNING, "MockWebServer connection failed", e);
|
||||
}
|
||||
|
||||
// This gnarly block of code will release all sockets and
|
||||
// all thread, even if any close fails.
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (Throwable e) {
|
||||
logger.log(Level.WARNING, "MockWebServer server socket close failed", e);
|
||||
}
|
||||
for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext(); ) {
|
||||
try {
|
||||
s.next().close();
|
||||
s.remove();
|
||||
} catch (Throwable e) {
|
||||
logger.log(Level.WARNING, "MockWebServer socket close failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
acceptThread.start();
|
||||
}
|
||||
|
||||
public void enqueue(MockResponse response) {
|
||||
dispatcher.enqueueResponse(response);
|
||||
}
|
||||
|
||||
private void acceptConnections(int sequenceNumber) throws Exception {
|
||||
while (true) {
|
||||
Socket socket;
|
||||
try {
|
||||
socket = serverSocket.accept();
|
||||
} catch (SocketException e) {
|
||||
return;
|
||||
}
|
||||
openClientSockets.add(socket);
|
||||
new SocketHandler(sequenceNumber++, socket).serve();
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() throws IOException {
|
||||
if (serverSocket != null) {
|
||||
serverSocket.close(); // should cause acceptConnections() to break out
|
||||
}
|
||||
}
|
||||
|
||||
private class SocketHandler implements IncomingStreamHandler {
|
||||
private final int sequenceNumber;
|
||||
private Socket socket;
|
||||
|
||||
private SocketHandler(int sequenceNumber, Socket socket) throws IOException {
|
||||
this.socket = socket;
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
}
|
||||
|
||||
public void serve() throws IOException {
|
||||
if (sslSocketFactory != null) {
|
||||
socket = doSsl(socket);
|
||||
}
|
||||
new SpdyConnection.Builder(false, socket).handler(this).build();
|
||||
}
|
||||
|
||||
private Socket doSsl(Socket socket) throws IOException {
|
||||
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket,
|
||||
socket.getInetAddress().getHostAddress(), socket.getPort(), true);
|
||||
sslSocket.setUseClientMode(false);
|
||||
Platform.get().setNpnProtocols(sslSocket, NPN_PROTOCOLS);
|
||||
return sslSocket;
|
||||
}
|
||||
|
||||
@Override public void receive(final SpdyStream stream) throws IOException {
|
||||
RecordedRequest request = readRequest(stream);
|
||||
requestQueue.add(request);
|
||||
MockResponse response;
|
||||
try {
|
||||
response = dispatcher.dispatch(request);
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
writeResponse(stream, response);
|
||||
logger.info("Received request: " + request + " and responded: " + response);
|
||||
}
|
||||
|
||||
private RecordedRequest readRequest(SpdyStream stream) throws IOException {
|
||||
List<String> spdyHeaders = stream.getRequestHeaders();
|
||||
List<String> httpHeaders = new ArrayList<String>();
|
||||
String method = "<:method omitted>";
|
||||
String path = "<:path omitted>";
|
||||
String version = "<:version omitted>";
|
||||
for (Iterator<String> i = spdyHeaders.iterator(); i.hasNext(); ) {
|
||||
String name = i.next();
|
||||
String value = i.next();
|
||||
if (":method".equals(name)) {
|
||||
method = value;
|
||||
} else if (":path".equals(name)) {
|
||||
path = value;
|
||||
} else if (":version".equals(name)) {
|
||||
version = value;
|
||||
} else {
|
||||
httpHeaders.add(name + ": " + value);
|
||||
}
|
||||
}
|
||||
|
||||
InputStream bodyIn = stream.getInputStream();
|
||||
ByteArrayOutputStream bodyOut = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[8192];
|
||||
int count;
|
||||
while ((count = bodyIn.read(buffer)) != -1) {
|
||||
bodyOut.write(buffer, 0, count);
|
||||
}
|
||||
bodyIn.close();
|
||||
String requestLine = method + ' ' + path + ' ' + version;
|
||||
List<Integer> chunkSizes = Collections.emptyList(); // No chunked encoding for SPDY.
|
||||
return new RecordedRequest(requestLine, httpHeaders, chunkSizes, bodyOut.size(),
|
||||
bodyOut.toByteArray(), sequenceNumber, socket);
|
||||
}
|
||||
|
||||
private void writeResponse(SpdyStream stream, MockResponse response) throws IOException {
|
||||
List<String> spdyHeaders = new ArrayList<String>();
|
||||
String[] statusParts = response.getStatus().split(" ", 2);
|
||||
if (statusParts.length != 2) {
|
||||
throw new AssertionError("Unexpected status: " + response.getStatus());
|
||||
}
|
||||
spdyHeaders.add(":status");
|
||||
spdyHeaders.add(statusParts[1]);
|
||||
spdyHeaders.add(":version");
|
||||
spdyHeaders.add(statusParts[0]);
|
||||
for (String header : response.getHeaders()) {
|
||||
String[] headerParts = header.split(":", 2);
|
||||
if (headerParts.length != 2) {
|
||||
throw new AssertionError("Unexpected header: " + header);
|
||||
}
|
||||
spdyHeaders.add(headerParts[0].toLowerCase(Locale.US).trim());
|
||||
spdyHeaders.add(headerParts[1].trim());
|
||||
}
|
||||
byte[] body = response.getBody();
|
||||
stream.reply(spdyHeaders, body.length > 0);
|
||||
if (body.length > 0) {
|
||||
stream.getOutputStream().write(body);
|
||||
stream.getOutputStream().close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
package com.squareup.okhttp.mockwebserver;
|
||||
|
||||
import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
|
||||
import static com.squareup.okhttp.mockwebserver.SocketPolicy.FAIL_HANDSHAKE;
|
||||
import com.squareup.okhttp.internal.Platform;
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.spdy.IncomingStreamHandler;
|
||||
import com.squareup.okhttp.internal.spdy.SpdyConnection;
|
||||
import com.squareup.okhttp.internal.spdy.SpdyStream;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -37,6 +40,8 @@ import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -55,11 +60,24 @@ import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
|
||||
import static com.squareup.okhttp.mockwebserver.SocketPolicy.FAIL_HANDSHAKE;
|
||||
|
||||
/**
|
||||
* A scriptable web server. Callers supply canned responses and the server
|
||||
* replays them upon request in sequence.
|
||||
*/
|
||||
public final class MockWebServer {
|
||||
private static final byte[] NPN_PROTOCOLS = {
|
||||
6, 's', 'p', 'd', 'y', '/', '3',
|
||||
8, 'h', 't', 't', 'p', '/', '1', '.', '1'
|
||||
};
|
||||
private static final byte[] SPDY3 = new byte[] {
|
||||
's', 'p', 'd', 'y', '/', '3'
|
||||
};
|
||||
private static final byte[] HTTP_11 = new byte[] {
|
||||
'h', 't', 't', 'p', '/', '1', '.', '1'
|
||||
};
|
||||
|
||||
static final String ASCII = "US-ASCII";
|
||||
|
||||
@@ -68,6 +86,8 @@ public final class MockWebServer {
|
||||
= new LinkedBlockingQueue<RecordedRequest>();
|
||||
/** All map values are Boolean.TRUE. (Collections.newSetFromMap isn't available in Froyo) */
|
||||
private final Map<Socket, Boolean> openClientSockets = new ConcurrentHashMap<Socket, Boolean>();
|
||||
private final Map<SpdyConnection, Boolean> openSpdyConnections
|
||||
= new ConcurrentHashMap<SpdyConnection, Boolean>();
|
||||
private final AtomicInteger requestCount = new AtomicInteger();
|
||||
private int bodyLimit = Integer.MAX_VALUE;
|
||||
private ServerSocket serverSocket;
|
||||
@@ -77,6 +97,7 @@ public final class MockWebServer {
|
||||
private Dispatcher dispatcher = new QueueDispatcher();
|
||||
|
||||
private int port = -1;
|
||||
private boolean npnEnabled = true;
|
||||
|
||||
public int getPort() {
|
||||
if (port == -1) {
|
||||
@@ -130,6 +151,15 @@ public final class MockWebServer {
|
||||
this.bodyLimit = maxBodyLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether NPN is used on incoming HTTPS connections to negotiate a
|
||||
* transport like HTTP/1.1 or SPDY/3. Call this method to disable NPN and
|
||||
* SPDY.
|
||||
*/
|
||||
public void setNpnEnabled(boolean npnEnabled) {
|
||||
this.npnEnabled = npnEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve requests with HTTPS rather than otherwise.
|
||||
*
|
||||
@@ -219,6 +249,15 @@ public final class MockWebServer {
|
||||
logger.log(Level.WARNING, "MockWebServer socket close failed", e);
|
||||
}
|
||||
}
|
||||
for (Iterator<SpdyConnection> s = openSpdyConnections.keySet().iterator();
|
||||
s.hasNext(); ) {
|
||||
try {
|
||||
s.next().close();
|
||||
s.remove();
|
||||
} catch (Throwable e) {
|
||||
logger.log(Level.WARNING, "MockWebServer SPDY connection close failed", e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
executor.shutdown();
|
||||
} catch (Throwable e) {
|
||||
@@ -267,6 +306,7 @@ public final class MockWebServer {
|
||||
}
|
||||
|
||||
public void processConnection() throws Exception {
|
||||
Transport transport = Transport.HTTP_11;
|
||||
Socket socket;
|
||||
if (sslSocketFactory != null) {
|
||||
if (tunnelProxy) {
|
||||
@@ -275,18 +315,47 @@ public final class MockWebServer {
|
||||
final SocketPolicy socketPolicy = dispatcher.peekSocketPolicy();
|
||||
if (socketPolicy == FAIL_HANDSHAKE) {
|
||||
dispatchBookkeepingRequest(sequenceNumber, raw);
|
||||
processHandshakeFailure(raw, sequenceNumber++);
|
||||
processHandshakeFailure(raw);
|
||||
return;
|
||||
}
|
||||
socket = sslSocketFactory.createSocket(
|
||||
raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
|
||||
((SSLSocket) socket).setUseClientMode(false);
|
||||
raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
|
||||
SSLSocket sslSocket = (SSLSocket) socket;
|
||||
sslSocket.setUseClientMode(false);
|
||||
openClientSockets.put(socket, true);
|
||||
|
||||
if (npnEnabled) {
|
||||
Platform.get().setNpnProtocols(sslSocket, NPN_PROTOCOLS);
|
||||
}
|
||||
|
||||
sslSocket.startHandshake();
|
||||
|
||||
if (npnEnabled) {
|
||||
byte[] selectedProtocol = Platform.get().getNpnSelectedProtocol(sslSocket);
|
||||
if (selectedProtocol == null || Arrays.equals(selectedProtocol, HTTP_11)) {
|
||||
transport = Transport.HTTP_11;
|
||||
} else if (Arrays.equals(selectedProtocol, SPDY3)) {
|
||||
transport = Transport.SPDY_3;
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected transport: "
|
||||
+ new String(selectedProtocol, Util.US_ASCII));
|
||||
}
|
||||
}
|
||||
openClientSockets.remove(raw);
|
||||
} else {
|
||||
socket = raw;
|
||||
}
|
||||
|
||||
if (transport == Transport.SPDY_3) {
|
||||
SpdySocketHandler spdySocketHandler = new SpdySocketHandler(socket);
|
||||
SpdyConnection spdyConnection = new SpdyConnection.Builder(false, socket)
|
||||
.handler(spdySocketHandler)
|
||||
.build();
|
||||
openSpdyConnections.put(spdyConnection, Boolean.TRUE);
|
||||
openClientSockets.remove(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
InputStream in = new BufferedInputStream(socket.getInputStream());
|
||||
OutputStream out = new BufferedOutputStream(socket.getOutputStream());
|
||||
|
||||
@@ -348,7 +417,7 @@ public final class MockWebServer {
|
||||
}));
|
||||
}
|
||||
|
||||
private void processHandshakeFailure(Socket raw, int sequenceNumber) throws Exception {
|
||||
private void processHandshakeFailure(Socket raw) throws Exception {
|
||||
X509TrustManager untrusted = new X509TrustManager() {
|
||||
@Override public void checkClientTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
@@ -586,4 +655,90 @@ public final class MockWebServer {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Processes HTTP requests layered over SPDY/3. */
|
||||
private class SpdySocketHandler implements IncomingStreamHandler {
|
||||
private final Socket socket;
|
||||
|
||||
private SpdySocketHandler(Socket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override public void receive(final SpdyStream stream) throws IOException {
|
||||
RecordedRequest request = readRequest(stream);
|
||||
requestQueue.add(request);
|
||||
MockResponse response;
|
||||
try {
|
||||
response = dispatcher.dispatch(request);
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
writeResponse(stream, response);
|
||||
logger.info("Received request: " + request + " and responded: " + response);
|
||||
}
|
||||
|
||||
private RecordedRequest readRequest(SpdyStream stream) throws IOException {
|
||||
List<String> spdyHeaders = stream.getRequestHeaders();
|
||||
List<String> httpHeaders = new ArrayList<String>();
|
||||
String method = "<:method omitted>";
|
||||
String path = "<:path omitted>";
|
||||
String version = "<:version omitted>";
|
||||
for (Iterator<String> i = spdyHeaders.iterator(); i.hasNext(); ) {
|
||||
String name = i.next();
|
||||
String value = i.next();
|
||||
if (":method".equals(name)) {
|
||||
method = value;
|
||||
} else if (":path".equals(name)) {
|
||||
path = value;
|
||||
} else if (":version".equals(name)) {
|
||||
version = value;
|
||||
} else {
|
||||
httpHeaders.add(name + ": " + value);
|
||||
}
|
||||
}
|
||||
|
||||
InputStream bodyIn = stream.getInputStream();
|
||||
ByteArrayOutputStream bodyOut = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[8192];
|
||||
int count;
|
||||
while ((count = bodyIn.read(buffer)) != -1) {
|
||||
bodyOut.write(buffer, 0, count);
|
||||
}
|
||||
bodyIn.close();
|
||||
String requestLine = method + ' ' + path + ' ' + version;
|
||||
List<Integer> chunkSizes = Collections.emptyList(); // No chunked encoding for SPDY.
|
||||
return new RecordedRequest(requestLine, httpHeaders, chunkSizes, bodyOut.size(),
|
||||
bodyOut.toByteArray(), 0, socket);
|
||||
}
|
||||
|
||||
private void writeResponse(SpdyStream stream, MockResponse response) throws IOException {
|
||||
List<String> spdyHeaders = new ArrayList<String>();
|
||||
String[] statusParts = response.getStatus().split(" ", 2);
|
||||
if (statusParts.length != 2) {
|
||||
throw new AssertionError("Unexpected status: " + response.getStatus());
|
||||
}
|
||||
spdyHeaders.add(":status");
|
||||
spdyHeaders.add(statusParts[1]);
|
||||
spdyHeaders.add(":version");
|
||||
spdyHeaders.add(statusParts[0]);
|
||||
for (String header : response.getHeaders()) {
|
||||
String[] headerParts = header.split(":", 2);
|
||||
if (headerParts.length != 2) {
|
||||
throw new AssertionError("Unexpected header: " + header);
|
||||
}
|
||||
spdyHeaders.add(headerParts[0].toLowerCase(Locale.US).trim());
|
||||
spdyHeaders.add(headerParts[1].trim());
|
||||
}
|
||||
byte[] body = response.getBody();
|
||||
stream.reply(spdyHeaders, body.length > 0);
|
||||
if (body.length > 0) {
|
||||
stream.getOutputStream().write(body);
|
||||
stream.getOutputStream().close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Transport {
|
||||
HTTP_11, SPDY_3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,11 +79,11 @@ abstract class AbstractHttpInputStream extends InputStream {
|
||||
* Closes the cache entry and makes the socket available for reuse. This
|
||||
* should be invoked when the end of the body has been reached.
|
||||
*/
|
||||
protected final void endOfInput(boolean streamCancelled) throws IOException {
|
||||
protected final void endOfInput() throws IOException {
|
||||
if (cacheRequest != null) {
|
||||
cacheBody.close();
|
||||
}
|
||||
httpEngine.release(streamCancelled);
|
||||
httpEngine.release(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -426,7 +426,7 @@ public class HttpEngine {
|
||||
* closed. Also call {@link #automaticallyReleaseConnectionToPool} unless
|
||||
* the connection will be used to follow a redirect.
|
||||
*/
|
||||
public final void release(boolean streamCancelled) {
|
||||
public final void release(boolean streamCanceled) {
|
||||
// If the response body comes from the cache, close it.
|
||||
if (responseBodyIn == cachedResponseBody) {
|
||||
Util.closeQuietly(responseBodyIn);
|
||||
@@ -435,8 +435,8 @@ public class HttpEngine {
|
||||
if (!connectionReleased && connection != null) {
|
||||
connectionReleased = true;
|
||||
|
||||
if (transport == null || !transport.makeReusable(streamCancelled, requestBodyOut,
|
||||
responseTransferIn)) {
|
||||
if (transport == null
|
||||
|| !transport.makeReusable(streamCanceled, requestBodyOut, responseTransferIn)) {
|
||||
Util.closeQuietly(connection);
|
||||
connection = null;
|
||||
} else if (automaticallyReleaseConnectionToPool) {
|
||||
|
||||
@@ -141,9 +141,9 @@ public final class HttpTransport implements Transport {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public boolean makeReusable(boolean streamCancelled, OutputStream requestBodyOut,
|
||||
public boolean makeReusable(boolean streamCanceled, OutputStream requestBodyOut,
|
||||
InputStream responseBodyIn) {
|
||||
if (streamCancelled) {
|
||||
if (streamCanceled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -177,6 +177,10 @@ public final class HttpTransport implements Transport {
|
||||
* 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.
|
||||
*
|
||||
* <p>A stream may be discarded to encourage response caching (a response
|
||||
* cannot be cached unless it is consumed completely) or to enable connection
|
||||
* reuse.
|
||||
*/
|
||||
private static boolean discardStream(HttpEngine httpEngine, InputStream responseBodyIn) {
|
||||
Connection connection = httpEngine.connection;
|
||||
@@ -373,7 +377,7 @@ public final class HttpTransport implements Transport {
|
||||
super(is, httpEngine, cacheRequest);
|
||||
bytesRemaining = length;
|
||||
if (bytesRemaining == 0) {
|
||||
endOfInput(false);
|
||||
endOfInput();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +395,7 @@ public final class HttpTransport implements Transport {
|
||||
bytesRemaining -= read;
|
||||
cacheWrite(buffer, offset, read);
|
||||
if (bytesRemaining == 0) {
|
||||
endOfInput(false);
|
||||
endOfInput();
|
||||
}
|
||||
return read;
|
||||
}
|
||||
@@ -468,7 +472,7 @@ public final class HttpTransport implements Transport {
|
||||
RawHeaders rawResponseHeaders = httpEngine.responseHeaders.getHeaders();
|
||||
RawHeaders.readHeaders(transport.socketIn, rawResponseHeaders);
|
||||
httpEngine.receiveHeaders(rawResponseHeaders);
|
||||
endOfInput(false);
|
||||
endOfInput();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,15 +80,15 @@ public final class SpdyTransport implements Transport {
|
||||
return new UnknownLengthHttpInputStream(stream.getInputStream(), cacheRequest, httpEngine);
|
||||
}
|
||||
|
||||
@Override public boolean makeReusable(boolean streamCancelled, OutputStream requestBodyOut,
|
||||
@Override public boolean makeReusable(boolean streamCanceled, OutputStream requestBodyOut,
|
||||
InputStream responseBodyIn) {
|
||||
if (streamCancelled) {
|
||||
if (streamCanceled) {
|
||||
if (stream != null) {
|
||||
stream.closeLater(SpdyStream.RST_CANCEL);
|
||||
return true;
|
||||
} else {
|
||||
// If stream is null, it either means that writeRequestHeaders wasn't called
|
||||
// or that SpdyConnection#newStream threw an IOEXception. In both cases there's
|
||||
// or that SpdyConnection#newStream threw an IOException. In both cases there's
|
||||
// nothing to do here and this stream can't be reused.
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,6 @@ interface Transport {
|
||||
InputStream getTransferStream(CacheRequest cacheRequest) throws IOException;
|
||||
|
||||
/** Returns true if the underlying connection can be recycled. */
|
||||
boolean makeReusable(boolean streamReusable, OutputStream requestBodyOut,
|
||||
boolean makeReusable(boolean streamCanceled, OutputStream requestBodyOut,
|
||||
InputStream responseBodyIn);
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ import static com.squareup.okhttp.internal.Util.checkOffsetAndCount;
|
||||
final class UnknownLengthHttpInputStream extends AbstractHttpInputStream {
|
||||
private boolean inputExhausted;
|
||||
|
||||
UnknownLengthHttpInputStream(InputStream is, CacheRequest cacheRequest, HttpEngine httpEngine)
|
||||
UnknownLengthHttpInputStream(InputStream in, CacheRequest cacheRequest, HttpEngine httpEngine)
|
||||
throws IOException {
|
||||
super(is, httpEngine, cacheRequest);
|
||||
super(in, httpEngine, cacheRequest);
|
||||
}
|
||||
|
||||
@Override public int read(byte[] buffer, int offset, int count) throws IOException {
|
||||
@@ -39,7 +39,7 @@ final class UnknownLengthHttpInputStream extends AbstractHttpInputStream {
|
||||
int read = in.read(buffer, offset, count);
|
||||
if (read == -1) {
|
||||
inputExhausted = true;
|
||||
endOfInput(false);
|
||||
endOfInput();
|
||||
return -1;
|
||||
}
|
||||
cacheWrite(buffer, offset, read);
|
||||
|
||||
@@ -15,12 +15,11 @@
|
||||
*/
|
||||
package com.squareup.okhttp;
|
||||
|
||||
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||
import com.squareup.okhttp.internal.RecordingHostnameVerifier;
|
||||
import com.squareup.okhttp.internal.SslContextBuilder;
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.http.HttpAuthenticator;
|
||||
import com.squareup.okhttp.internal.mockspdyserver.MockSpdyServer;
|
||||
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -53,7 +52,7 @@ public final class ConnectionPoolTest {
|
||||
}
|
||||
}
|
||||
|
||||
private final MockSpdyServer spdyServer = new MockSpdyServer(sslContext.getSocketFactory());
|
||||
private final MockWebServer spdyServer = new MockWebServer();
|
||||
private InetSocketAddress spdySocketAddress;
|
||||
private Address spdyAddress;
|
||||
|
||||
@@ -70,6 +69,8 @@ public final class ConnectionPoolTest {
|
||||
private Connection spdyB;
|
||||
|
||||
@Before public void setUp() throws Exception {
|
||||
spdyServer.useHttps(sslContext.getSocketFactory(), false);
|
||||
|
||||
httpServer.play();
|
||||
httpAddress = new Address(httpServer.getHostName(), httpServer.getPort(), null, null,
|
||||
HttpAuthenticator.SYSTEM_DEFAULT, null, Arrays.asList("spdy/3", "http/1.1"));
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
package com.squareup.okhttp.internal.http;
|
||||
|
||||
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
||||
import com.squareup.okhttp.HttpResponseCache;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.internal.RecordingAuthenticator;
|
||||
import com.squareup.okhttp.internal.SslContextBuilder;
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.mockspdyserver.MockSpdyServer;
|
||||
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -72,12 +72,13 @@ public final class HttpOverSpdyTest {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
private final MockSpdyServer server = new MockSpdyServer(sslContext.getSocketFactory());
|
||||
private final MockWebServer server = new MockWebServer();
|
||||
private final String hostName = server.getHostName();
|
||||
private final OkHttpClient client = new OkHttpClient();
|
||||
private HttpResponseCache cache;
|
||||
|
||||
@Before public void setUp() throws Exception {
|
||||
server.useHttps(sslContext.getSocketFactory(), false);
|
||||
client.setSslSocketFactory(sslContext.getSocketFactory());
|
||||
client.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
|
||||
String systemTmpDir = System.getProperty("java.io.tmpdir");
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
package com.squareup.okhttp.internal.http;
|
||||
|
||||
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
||||
import com.squareup.okhttp.HttpResponseCache;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.ResponseSource;
|
||||
import com.squareup.okhttp.internal.SslContextBuilder;
|
||||
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
@@ -110,6 +110,7 @@ public final class HttpResponseCacheTest {
|
||||
cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE);
|
||||
ResponseCache.setDefault(cache);
|
||||
CookieHandler.setDefault(cookieManager);
|
||||
server.setNpnEnabled(false);
|
||||
}
|
||||
|
||||
@After public void tearDown() throws Exception {
|
||||
@@ -437,15 +438,14 @@ public final class HttpResponseCacheTest {
|
||||
server.enqueue(new MockResponse().setBody("DEF"));
|
||||
server.play();
|
||||
|
||||
client.setSslSocketFactory(sslContext.getSocketFactory());
|
||||
client.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
|
||||
|
||||
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
|
||||
|
||||
@@ -109,6 +109,7 @@ public final class URLConnectionTest {
|
||||
|
||||
@Before public void setUp() throws Exception {
|
||||
hostName = server.getHostName();
|
||||
server.setNpnEnabled(false);
|
||||
}
|
||||
|
||||
@After public void tearDown() throws Exception {
|
||||
@@ -1619,6 +1620,7 @@ public final class URLConnectionTest {
|
||||
if (https) {
|
||||
server.useHttps(sslContext.getSocketFactory(), false);
|
||||
server2.useHttps(sslContext.getSocketFactory(), false);
|
||||
server2.setNpnEnabled(false);
|
||||
client.setSslSocketFactory(sslContext.getSocketFactory());
|
||||
client.setHostnameVerifier(new RecordingHostnameVerifier());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user