Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
jvmTest.okhttp3.internal.ws.WebSocketHttpTest Maven / Gradle / Ivy
/*
* Copyright (C) 2014 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 okhttp3.internal.ws;
import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.time.Duration;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import mockwebserver3.Dispatcher;
import mockwebserver3.MockResponse;
import mockwebserver3.MockWebServer;
import mockwebserver3.RecordedRequest;
import mockwebserver3.SocketPolicy;
import okhttp3.OkHttpClient;
import okhttp3.OkHttpClientTestRule;
import okhttp3.Protocol;
import okhttp3.RecordingEventListener;
import okhttp3.RecordingHostnameVerifier;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.TestLogHandler;
import okhttp3.TestUtil;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okhttp3.internal.UnreadableResponseBody;
import okhttp3.internal.concurrent.TaskRunner;
import okhttp3.testing.Flaky;
import okhttp3.testing.PlatformRule;
import okhttp3.tls.HandshakeCertificates;
import okio.Buffer;
import okio.ByteString;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import static java.util.Arrays.asList;
import static okhttp3.TestUtil.repeat;
import static okhttp3.tls.internal.TlsUtil.localhost;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;
import static org.junit.jupiter.api.Assertions.fail;
@Flaky
@Tag("Slow")
public final class WebSocketHttpTest {
// Flaky https://github.com/square/okhttp/issues/4515
// Flaky https://github.com/square/okhttp/issues/4953
@RegisterExtension OkHttpClientTestRule clientTestRule = configureClientTestRule();
@RegisterExtension PlatformRule platform = new PlatformRule();
@RegisterExtension TestLogHandler testLogHandler = new TestLogHandler(OkHttpClient.class);
private MockWebServer webServer;
private final HandshakeCertificates handshakeCertificates = localhost();
private final WebSocketRecorder clientListener = new WebSocketRecorder("client");
private final WebSocketRecorder serverListener = new WebSocketRecorder("server");
private final Random random = new Random(0);
private OkHttpClient client = clientTestRule.newClientBuilder()
.writeTimeout(Duration.ofMillis(500))
.readTimeout(Duration.ofMillis(500))
.addInterceptor(chain -> {
Response response = chain.proceed(chain.request());
// Ensure application interceptors never see a null body.
assertThat(response.body()).isNotNull();
return response;
})
.build();
private OkHttpClientTestRule configureClientTestRule() {
OkHttpClientTestRule clientTestRule = new OkHttpClientTestRule();
clientTestRule.setRecordTaskRunner(true);
return clientTestRule;
}
@BeforeEach public void setUp(MockWebServer webServer) {
this.webServer = webServer;
platform.assumeNotOpenJSSE();
platform.assumeNotBouncyCastle();
}
@AfterEach public void tearDown() throws InterruptedException {
clientListener.assertExhausted();
}
@Test public void textMessage() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
webSocket.send("Hello, WebSockets!");
serverListener.assertTextMessage("Hello, WebSockets!");
closeWebSockets(webSocket, server);
}
@Test public void binaryMessage() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
webSocket.send(ByteString.encodeUtf8("Hello!"));
serverListener.assertBinaryMessage(ByteString.of(new byte[] {'H', 'e', 'l', 'l', 'o', '!'}));
closeWebSockets(webSocket, server);
}
@Test public void nullStringThrows() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
try {
webSocket.send((String) null);
fail();
} catch (NullPointerException expected) {
}
closeWebSockets(webSocket, server);
}
@Test public void nullByteStringThrows() {
TestUtil.assumeNotWindows();
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
try {
webSocket.send((ByteString) null);
fail();
} catch (NullPointerException expected) {
}
closeWebSockets(webSocket, server);
}
@Test public void serverMessage() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
server.send("Hello, WebSockets!");
clientListener.assertTextMessage("Hello, WebSockets!");
closeWebSockets(webSocket, server);
}
@Test public void throwingOnOpenFailsImmediately() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
final RuntimeException e = new RuntimeException();
clientListener.setNextEventDelegate(new WebSocketListener() {
@Override public void onOpen(WebSocket webSocket, Response response) {
throw e;
}
});
newWebSocket();
serverListener.assertOpen();
serverListener.assertFailure(EOFException.class);
serverListener.assertExhausted();
clientListener.assertFailure(e);
}
@Disabled("AsyncCall currently lets runtime exceptions propagate.")
@Test public void throwingOnFailLogs() throws Exception {
webServer.enqueue(new MockResponse().setResponseCode(200).setBody("Body"));
final RuntimeException e = new RuntimeException("boom");
clientListener.setNextEventDelegate(new WebSocketListener() {
@Override public void onFailure(WebSocket webSocket, Throwable t, Response response) {
throw e;
}
});
newWebSocket();
assertThat(testLogHandler.take()).isEqualTo("INFO: [WS client] onFailure");
}
@Test public void throwingOnMessageClosesImmediatelyAndFails() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
final RuntimeException e = new RuntimeException();
clientListener.setNextEventDelegate(new WebSocketListener() {
@Override public void onMessage(WebSocket webSocket, String text) {
throw e;
}
});
server.send("Hello, WebSockets!");
clientListener.assertFailure(e);
serverListener.assertFailure(EOFException.class);
serverListener.assertExhausted();
}
@Test public void throwingOnClosingClosesImmediatelyAndFails() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
final RuntimeException e = new RuntimeException();
clientListener.setNextEventDelegate(new WebSocketListener() {
@Override public void onClosing(WebSocket webSocket, int code, String reason) {
throw e;
}
});
server.close(1000, "bye");
clientListener.assertFailure(e);
serverListener.assertFailure();
serverListener.assertExhausted();
}
@Test public void unplannedCloseHandledByCloseWithoutFailure() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
clientListener.setNextEventDelegate(new WebSocketListener() {
@Override public void onClosing(WebSocket webSocket, int code, String reason) {
webSocket.close(1000, null);
}
});
server.close(1001, "bye");
clientListener.assertClosed(1001, "bye");
clientListener.assertExhausted();
serverListener.assertClosing(1000, "");
serverListener.assertClosed(1000, "");
serverListener.assertExhausted();
}
@Test public void unplannedCloseHandledWithoutFailure() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
newWebSocket();
WebSocket webSocket = clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
closeWebSockets(webSocket, server);
}
@Test public void non101RetainsBody() throws IOException {
webServer.enqueue(new MockResponse().setResponseCode(200).setBody("Body"));
newWebSocket();
clientListener.assertFailure(200, "Body", ProtocolException.class,
"Expected HTTP 101 response but was '200 OK'");
}
@Test public void notFound() throws IOException {
webServer.enqueue(new MockResponse().setStatus("HTTP/1.1 404 Not Found"));
newWebSocket();
clientListener.assertFailure(404, null, ProtocolException.class,
"Expected HTTP 101 response but was '404 Not Found'");
}
@Test public void clientTimeoutClosesBody() {
webServer.enqueue(new MockResponse().setResponseCode(408));
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
webSocket.send("abc");
serverListener.assertTextMessage("abc");
server.send("def");
clientListener.assertTextMessage("def");
closeWebSockets(webSocket, server);
}
@Test public void missingConnectionHeader() throws IOException {
webServer.enqueue(new MockResponse()
.setResponseCode(101)
.setHeader("Upgrade", "websocket")
.setHeader("Sec-WebSocket-Accept", "ujmZX4KXZqjwy6vi1aQFH5p4Ygk="));
newWebSocket();
clientListener.assertFailure(101, null, ProtocolException.class,
"Expected 'Connection' header value 'Upgrade' but was 'null'");
}
@Test public void wrongConnectionHeader() throws IOException {
webServer.enqueue(new MockResponse()
.setResponseCode(101)
.setHeader("Upgrade", "websocket")
.setHeader("Connection", "Downgrade")
.setHeader("Sec-WebSocket-Accept", "ujmZX4KXZqjwy6vi1aQFH5p4Ygk="));
newWebSocket();
clientListener.assertFailure(101, null, ProtocolException.class,
"Expected 'Connection' header value 'Upgrade' but was 'Downgrade'");
}
@Test public void missingUpgradeHeader() throws IOException {
webServer.enqueue(new MockResponse()
.setResponseCode(101)
.setHeader("Connection", "Upgrade")
.setHeader("Sec-WebSocket-Accept", "ujmZX4KXZqjwy6vi1aQFH5p4Ygk="));
newWebSocket();
clientListener.assertFailure(101, null, ProtocolException.class,
"Expected 'Upgrade' header value 'websocket' but was 'null'");
}
@Test public void wrongUpgradeHeader() throws IOException {
webServer.enqueue(new MockResponse()
.setResponseCode(101)
.setHeader("Connection", "Upgrade")
.setHeader("Upgrade", "Pepsi")
.setHeader("Sec-WebSocket-Accept", "ujmZX4KXZqjwy6vi1aQFH5p4Ygk="));
newWebSocket();
clientListener.assertFailure(101, null, ProtocolException.class,
"Expected 'Upgrade' header value 'websocket' but was 'Pepsi'");
}
@Test public void missingMagicHeader() throws IOException {
webServer.enqueue(new MockResponse()
.setResponseCode(101)
.setHeader("Connection", "Upgrade")
.setHeader("Upgrade", "websocket"));
newWebSocket();
clientListener.assertFailure(101, null, ProtocolException.class,
"Expected 'Sec-WebSocket-Accept' header value 'ujmZX4KXZqjwy6vi1aQFH5p4Ygk=' but was 'null'");
}
@Test public void wrongMagicHeader() throws IOException {
webServer.enqueue(new MockResponse()
.setResponseCode(101)
.setHeader("Connection", "Upgrade")
.setHeader("Upgrade", "websocket")
.setHeader("Sec-WebSocket-Accept", "magic"));
newWebSocket();
clientListener.assertFailure(101, null, ProtocolException.class,
"Expected 'Sec-WebSocket-Accept' header value 'ujmZX4KXZqjwy6vi1aQFH5p4Ygk=' but was 'magic'");
}
@Test public void clientIncludesForbiddenHeader() throws IOException {
newWebSocket(new Request.Builder()
.url(webServer.url("/"))
.header("Sec-WebSocket-Extensions", "permessage-deflate")
.build());
clientListener.assertFailure(ProtocolException.class,
"Request header not permitted: 'Sec-WebSocket-Extensions'");
}
@SuppressWarnings("KotlinInternalInJava")
@Test public void webSocketAndApplicationInterceptors() {
final AtomicInteger interceptedCount = new AtomicInteger();
client = client.newBuilder()
.addInterceptor(chain -> {
assertThat(chain.request().body()).isNull();
Response response = chain.proceed(chain.request());
assertThat(response.header("Connection")).isEqualTo("Upgrade");
assertThat(response.body()).isInstanceOf(UnreadableResponseBody.class);
interceptedCount.incrementAndGet();
return response;
})
.build();
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
assertThat(interceptedCount.get()).isEqualTo(1);
closeWebSockets(webSocket, serverListener.assertOpen());
}
@Test public void webSocketAndNetworkInterceptors() {
client = client.newBuilder()
.addNetworkInterceptor(chain -> {
throw new AssertionError(); // Network interceptors don't execute.
})
.build();
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
closeWebSockets(webSocket, server);
}
@Test public void overflowOutgoingQueue() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
// Send messages until the client's outgoing buffer overflows!
ByteString message = ByteString.of(new byte[1024 * 1024]);
long messageCount = 0;
while (true) {
boolean success = webSocket.send(message);
if (!success) break;
messageCount++;
long queueSize = webSocket.queueSize();
assertThat(queueSize).isBetween(0L, messageCount * message.size());
// Expect to fail before enqueueing 32 MiB.
assertThat(messageCount).isLessThan(32L);
}
// Confirm all sent messages were received, followed by a client-initiated close.
WebSocket server = serverListener.assertOpen();
for (int i = 0; i < messageCount; i++) {
serverListener.assertBinaryMessage(message);
}
serverListener.assertClosing(1001, "");
// When the server acknowledges the close the connection shuts down gracefully.
server.close(1000, null);
clientListener.assertClosing(1000, "");
clientListener.assertClosed(1000, "");
serverListener.assertClosed(1001, "");
}
@Test public void closeReasonMaximumLength() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
String clientReason = repeat('C', 123);
String serverReason = repeat('S', 123);
WebSocket webSocket = newWebSocket();
WebSocket server = serverListener.assertOpen();
clientListener.assertOpen();
webSocket.close(1000, clientReason);
serverListener.assertClosing(1000, clientReason);
server.close(1000, serverReason);
clientListener.assertClosing(1000, serverReason);
clientListener.assertClosed(1000, serverReason);
serverListener.assertClosed(1000, clientReason);
}
@Test public void closeReasonTooLong() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
WebSocket server = serverListener.assertOpen();
clientListener.assertOpen();
String reason = repeat('X', 124);
try {
webSocket.close(1000, reason);
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected.getMessage()).isEqualTo(("reason.size() > 123: " + reason));
}
webSocket.close(1000, null);
serverListener.assertClosing(1000, "");
server.close(1000, null);
clientListener.assertClosing(1000, "");
clientListener.assertClosed(1000, "");
serverListener.assertClosed(1000, "");
}
@Test public void wsScheme() {
TestUtil.assumeNotWindows();
websocketScheme("ws");
}
@Test public void wsUppercaseScheme() {
websocketScheme("WS");
}
@Test public void wssScheme() {
webServer.useHttps(handshakeCertificates.sslSocketFactory());
client = client.newBuilder()
.sslSocketFactory(
handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
.hostnameVerifier(new RecordingHostnameVerifier())
.build();
websocketScheme("wss");
}
@Test public void httpsScheme() {
webServer.useHttps(handshakeCertificates.sslSocketFactory());
client = client.newBuilder()
.sslSocketFactory(
handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
.hostnameVerifier(new RecordingHostnameVerifier())
.build();
websocketScheme("https");
}
@Test public void readTimeoutAppliesToHttpRequest() {
webServer.enqueue(new MockResponse()
.setSocketPolicy(SocketPolicy.NO_RESPONSE));
WebSocket webSocket = newWebSocket();
clientListener.assertFailure(SocketTimeoutException.class, "timeout", "Read timed out");
assertThat(webSocket.close(1000, null)).isFalse();
}
/**
* There's no read timeout when reading the first byte of a new frame. But as soon as we start
* reading a frame we enable the read timeout. In this test we have the server returning the first
* byte of a frame but no more frames.
*/
@Test public void readTimeoutAppliesWithinFrames() {
webServer.setDispatcher(new Dispatcher() {
@Override public MockResponse dispatch(RecordedRequest request) {
return upgradeResponse(request)
.setBody(new Buffer().write(ByteString.decodeHex("81"))) // Truncated frame.
.removeHeader("Content-Length")
.setSocketPolicy(SocketPolicy.KEEP_OPEN);
}
});
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
clientListener.assertFailure(SocketTimeoutException.class, "timeout", "Read timed out");
assertThat(webSocket.close(1000, null)).isFalse();
}
@Test public void readTimeoutDoesNotApplyAcrossFrames() throws Exception {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
// Sleep longer than the HTTP client's read timeout.
Thread.sleep(client.readTimeoutMillis() + 500);
server.send("abc");
clientListener.assertTextMessage("abc");
closeWebSockets(webSocket, server);
}
@Test public void clientPingsServerOnInterval() throws Exception {
client = client.newBuilder()
.pingInterval(Duration.ofMillis(500))
.build();
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
RealWebSocket webSocket = newWebSocket();
clientListener.assertOpen();
RealWebSocket server = (RealWebSocket) serverListener.assertOpen();
long startNanos = System.nanoTime();
while (webSocket.receivedPongCount() < 3) {
Thread.sleep(50);
}
long elapsedUntilPong3 = System.nanoTime() - startNanos;
assertThat(TimeUnit.NANOSECONDS.toMillis(elapsedUntilPong3))
.isCloseTo(1500L, offset(250L));
// The client pinged the server 3 times, and it has ponged back 3 times.
assertThat(webSocket.sentPingCount()).isEqualTo(3);
assertThat(server.receivedPingCount()).isEqualTo(3);
assertThat(webSocket.receivedPongCount()).isEqualTo(3);
// The server has never pinged the client.
assertThat(server.receivedPongCount()).isEqualTo(0);
assertThat(webSocket.receivedPingCount()).isEqualTo(0);
closeWebSockets(webSocket, server);
}
@Test public void clientDoesNotPingServerByDefault() throws Exception {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
RealWebSocket webSocket = newWebSocket();
clientListener.assertOpen();
RealWebSocket server = (RealWebSocket) serverListener.assertOpen();
Thread.sleep(1000);
// No pings and no pongs.
assertThat(webSocket.sentPingCount()).isEqualTo(0);
assertThat(webSocket.receivedPingCount()).isEqualTo(0);
assertThat(webSocket.receivedPongCount()).isEqualTo(0);
assertThat(server.sentPingCount()).isEqualTo(0);
assertThat(server.receivedPingCount()).isEqualTo(0);
assertThat(server.receivedPongCount()).isEqualTo(0);
closeWebSockets(webSocket, server);
}
/**
* Configure the websocket to send pings every 500 ms. Artificially prevent the server from
* responding to pings. The client should give up when attempting to send its 2nd ping, at about
* 1000 ms.
*/
@Test public void unacknowledgedPingFailsConnection() {
TestUtil.assumeNotWindows();
client = client.newBuilder()
.pingInterval(Duration.ofMillis(500))
.build();
// Stall in onOpen to prevent pongs from being sent.
final CountDownLatch latch = new CountDownLatch(1);
webServer.enqueue(new MockResponse().withWebSocketUpgrade(new WebSocketListener() {
@Override public void onOpen(WebSocket webSocket, Response response) {
try {
latch.await(); // The server can't respond to pings!
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
}));
long openAtNanos = System.nanoTime();
newWebSocket();
clientListener.assertOpen();
clientListener.assertFailure(SocketTimeoutException.class,
"sent ping but didn't receive pong within 500ms (after 0 successful ping/pongs)");
latch.countDown();
long elapsedUntilFailure = System.nanoTime() - openAtNanos;
assertThat(TimeUnit.NANOSECONDS.toMillis(elapsedUntilFailure))
.isCloseTo(1000L, offset(250L));
}
/** https://github.com/square/okhttp/issues/2788 */
@Test public void clientCancelsIfCloseIsNotAcknowledged() {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
RealWebSocket webSocket = newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
// Initiate a close on the client, which will schedule a hard cancel in 500 ms.
long closeAtNanos = System.nanoTime();
webSocket.close(1000, "goodbye", 500L);
serverListener.assertClosing(1000, "goodbye");
// Confirm that the hard cancel occurred after 500 ms.
clientListener.assertFailure();
long elapsedUntilFailure = System.nanoTime() - closeAtNanos;
assertThat(TimeUnit.NANOSECONDS.toMillis(elapsedUntilFailure))
.isCloseTo(500L, offset(250L));
// Close the server and confirm it saw what we expected.
server.close(1000, null);
serverListener.assertClosed(1000, "goodbye");
}
@Test public void webSocketsDontTriggerEventListener() {
RecordingEventListener listener = new RecordingEventListener();
client = client.newBuilder()
.eventListenerFactory(clientTestRule.wrap(listener))
.build();
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
webSocket.send("Web Sockets and Events?!");
serverListener.assertTextMessage("Web Sockets and Events?!");
webSocket.close(1000, "");
serverListener.assertClosing(1000, "");
server.close(1000, "");
clientListener.assertClosing(1000, "");
clientListener.assertClosed(1000, "");
serverListener.assertClosed(1000, "");
assertThat(listener.recordedEventTypes()).isEmpty();
}
@Test public void callTimeoutAppliesToSetup() throws Exception {
webServer.enqueue(new MockResponse()
.setHeadersDelay(500, TimeUnit.MILLISECONDS));
client = client.newBuilder()
.readTimeout(Duration.ZERO)
.writeTimeout(Duration.ZERO)
.callTimeout(Duration.ofMillis(100))
.build();
newWebSocket();
clientListener.assertFailure(InterruptedIOException.class, "timeout");
}
@Test public void callTimeoutDoesNotApplyOnceConnected() throws Exception {
client = client.newBuilder()
.callTimeout(Duration.ofMillis(100))
.build();
webServer.enqueue(new MockResponse()
.withWebSocketUpgrade(serverListener));
WebSocket webSocket = newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
Thread.sleep(500);
server.send("Hello, WebSockets!");
clientListener.assertTextMessage("Hello, WebSockets!");
closeWebSockets(webSocket, server);
}
/**
* We had a bug where web socket connections were leaked if the HTTP connection upgrade was not
* successful. This test confirms that connections are released back to the connection pool!
* https://github.com/square/okhttp/issues/4258
*/
@Test public void webSocketConnectionIsReleased() throws Exception {
// This test assumes HTTP/1.1 pooling semantics.
client = client.newBuilder()
.protocols(asList(Protocol.HTTP_1_1))
.build();
webServer.enqueue(new MockResponse()
.setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
.setBody("not found!"));
webServer.enqueue(new MockResponse());
newWebSocket();
clientListener.assertFailure();
Request regularRequest = new Request.Builder()
.url(webServer.url("/"))
.build();
Response response = client.newCall(regularRequest).execute();
response.close();
assertThat(webServer.takeRequest().getSequenceNumber()).isEqualTo(0);
assertThat(webServer.takeRequest().getSequenceNumber()).isEqualTo(1);
}
/** https://github.com/square/okhttp/issues/5705 */
@Test public void closeWithoutSuccessfulConnect() {
Request request = new Request.Builder()
.url(webServer.url("/"))
.build();
WebSocket webSocket = client.newWebSocket(request, clientListener);
webSocket.send("hello");
webSocket.close(1000, null);
}
@Test public void compressedMessages() throws Exception {
successfulExtensions("permessage-deflate");
}
@Test public void compressedMessagesNoClientContextTakeover() throws Exception {
successfulExtensions("permessage-deflate; client_no_context_takeover");
}
@Test public void compressedMessagesNoServerContextTakeover() throws Exception {
successfulExtensions("permessage-deflate; server_no_context_takeover");
}
@Test public void unexpectedExtensionParameter() throws Exception {
extensionNegotiationFailure("permessage-deflate; unknown_parameter=15");
}
@Test public void clientMaxWindowBitsIncluded() throws Exception {
extensionNegotiationFailure("permessage-deflate; client_max_window_bits=15");
}
@Test public void serverMaxWindowBitsTooLow() throws Exception {
extensionNegotiationFailure("permessage-deflate; server_max_window_bits=7");
}
@Test public void serverMaxWindowBitsTooHigh() throws Exception {
extensionNegotiationFailure("permessage-deflate; server_max_window_bits=16");
}
@Test public void serverMaxWindowBitsJustRight() throws Exception {
successfulExtensions("permessage-deflate; server_max_window_bits=15");
}
private void successfulExtensions(String extensionsHeader) throws Exception {
webServer.enqueue(new MockResponse()
.addHeader("Sec-WebSocket-Extensions", extensionsHeader)
.withWebSocketUpgrade(serverListener));
WebSocket client = newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
// Server to client message big enough to be compressed.
String message1 = TestUtil.repeat('a', (int) RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE);
server.send(message1);
clientListener.assertTextMessage(message1);
// Client to server message big enough to be compressed.
String message2 = TestUtil.repeat('b', (int) RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE);
client.send(message2);
serverListener.assertTextMessage(message2);
// Empty server to client message.
String message3 = "";
server.send(message3);
clientListener.assertTextMessage(message3);
// Empty client to server message.
String message4 = "";
client.send(message4);
serverListener.assertTextMessage(message4);
// Server to client message that shares context with message1.
String message5 = message1 + message1;
server.send(message5);
clientListener.assertTextMessage(message5);
// Client to server message that shares context with message2.
String message6 = message2 + message2;
client.send(message6);
serverListener.assertTextMessage(message6);
closeWebSockets(client, server);
RecordedRequest upgradeRequest = webServer.takeRequest();
assertThat(upgradeRequest.getHeader("Sec-WebSocket-Extensions"))
.isEqualTo("permessage-deflate");
}
private void extensionNegotiationFailure(String extensionsHeader) throws Exception {
webServer.enqueue(new MockResponse()
.addHeader("Sec-WebSocket-Extensions", extensionsHeader)
.withWebSocketUpgrade(serverListener));
newWebSocket();
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
String clientReason = "unexpected Sec-WebSocket-Extensions in response header";
serverListener.assertClosing(1010, clientReason);
server.close(1010, "");
clientListener.assertClosing(1010, "");
clientListener.assertClosed(1010, "");
serverListener.assertClosed(1010, clientReason);
clientListener.assertExhausted();
serverListener.assertExhausted();
}
private MockResponse upgradeResponse(RecordedRequest request) {
String key = request.getHeader("Sec-WebSocket-Key");
return new MockResponse()
.setStatus("HTTP/1.1 101 Switching Protocols")
.setHeader("Connection", "Upgrade")
.setHeader("Upgrade", "websocket")
.setHeader("Sec-WebSocket-Accept", WebSocketProtocol.INSTANCE.acceptHeader(key));
}
private void websocketScheme(String scheme) {
webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
Request request = new Request.Builder()
.url(scheme + "://" + webServer.getHostName() + ":" + webServer.getPort() + "/")
.build();
RealWebSocket webSocket = newWebSocket(request);
clientListener.assertOpen();
WebSocket server = serverListener.assertOpen();
webSocket.send("abc");
serverListener.assertTextMessage("abc");
closeWebSockets(webSocket, server);
}
private RealWebSocket newWebSocket() {
return newWebSocket(new Request.Builder().get().url(webServer.url("/")).build());
}
private RealWebSocket newWebSocket(Request request) {
RealWebSocket webSocket = new RealWebSocket(TaskRunner.INSTANCE, request, clientListener,
random, client.pingIntervalMillis(), null, 0L);
webSocket.connect(client);
return webSocket;
}
private void closeWebSockets(WebSocket webSocket, WebSocket server) {
server.close(1001, "");
clientListener.assertClosing(1001, "");
webSocket.close(1000, "");
serverListener.assertClosing(1000, "");
clientListener.assertClosed(1001, "");
serverListener.assertClosed(1000, "");
clientListener.assertExhausted();
serverListener.assertExhausted();
}
}