/* * Copyright (C) 2015-2017 Neo Visionaries 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.neovisionaries.ws.client; import static com.neovisionaries.ws.client.WebSocketState.CLOSED; import static com.neovisionaries.ws.client.WebSocketState.CLOSING; import static com.neovisionaries.ws.client.WebSocketState.CONNECTING; import static com.neovisionaries.ws.client.WebSocketState.CREATED; import static com.neovisionaries.ws.client.WebSocketState.OPEN; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.net.Socket; import java.net.URI; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import com.neovisionaries.ws.client.StateManager.CloseInitiator; /** * WebSocket. * * <h3>Create WebSocketFactory</h3> * * <p> * {@link WebSocketFactory} is a factory class that creates * {@link WebSocket} instances. The first step is to create a * {@code WebSocketFactory} instance. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Create a WebSocketFactory instance.</span> * WebSocketFactory factory = new {@link WebSocketFactory#WebSocketFactory() * WebSocketFactory()};</pre> * </blockquote> * * <p> * By default, {@code WebSocketFactory} uses {@link * javax.net.SocketFactory SocketFactory}{@code .}{@link * javax.net.SocketFactory#getDefault() getDefault()} for * non-secure WebSocket connections ({@code ws:}) and {@link * javax.net.ssl.SSLSocketFactory SSLSocketFactory}{@code * .}{@link javax.net.ssl.SSLSocketFactory#getDefault() * getDefault()} for secure WebSocket connections ({@code * wss:}). You can change this default behavior by using * {@code WebSocketFactory.}{@link * WebSocketFactory#setSocketFactory(javax.net.SocketFactory) * setSocketFactory} method, {@code WebSocketFactory.}{@link * WebSocketFactory#setSSLSocketFactory(javax.net.ssl.SSLSocketFactory) * setSSLSocketFactory} method and {@code WebSocketFactory.}{@link * WebSocketFactory#setSSLContext(javax.net.ssl.SSLContext) * setSSLContext} method. Note that you don't have to call a {@code * setSSL*} method at all if you use the default SSL configuration. * Also note that calling {@code setSSLSocketFactory} method has no * meaning if you have called {@code setSSLContext} method. See the * description of {@code WebSocketFactory.}{@link * WebSocketFactory#createSocket(URI) createSocket(URI)} method for * details. * </p> * * <p> * The following is an example to set a custom SSL context to a * {@code WebSocketFactory} instance. (Again, you don't have to call a * {@code setSSL*} method if you use the default SSL configuration.) * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Create a custom SSL context.</span> * SSLContext context = <a href="https://gist.github.com/TakahikoKawasaki/d07de2218b4b81bf65ac" * >NaiveSSLContext</a>.getInstance(<span style="color:darkred;">"TLS"</span>); * * <span style="color: green;">// Set the custom SSL context.</span> * factory.{@link WebSocketFactory#setSSLContext(javax.net.ssl.SSLContext) * setSSLContext}(context);</pre> * </blockquote> * * <p> * <a href="https://gist.github.com/TakahikoKawasaki/d07de2218b4b81bf65ac" * >NaiveSSLContext</a> used in the above example is a factory class to * create an {@link javax.net.ssl.SSLContext SSLContext} which naively * accepts all certificates without verification. It's enough for testing * purposes. When you see an error message * "unable to find valid certificate path to requested target" while * testing, try {@code NaiveSSLContext}. * </p> * * <h3>HTTP Proxy</h3> * * <p> * If a WebSocket endpoint needs to be accessed via an HTTP proxy, * information about the proxy server has to be set to a {@code * WebSocketFactory} instance before creating a {@code WebSocket} * instance. Proxy settings are represented by {@link ProxySettings} * class. A {@code WebSocketFactory} instance has an associated * {@code ProxySettings} instance and it can be obtained by calling * {@code WebSocketFactory.}{@link WebSocketFactory#getProxySettings() * getProxySettings()} method. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Get the associated ProxySettings instance.</span> * {@link ProxySettings} settings = factory.{@link * WebSocketFactory#getProxySettings() getProxySettings()};</pre> * </blockquote> * * <p> * {@code ProxySettings} class has methods to set information about * a proxy server such as {@link ProxySettings#setHost(String) setHost} * method and {@link ProxySettings#setPort(int) setPort} method. The * following is an example to set a secure (<code>https</code>) proxy * server. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Set a proxy server.</span> * settings.{@link ProxySettings#setServer(String) * setServer}(<span style="color:darkred;">"https://proxy.example.com"</span>);</pre> * </blockquote> * * <p> * If credentials are required for authentication at a proxy server, * {@link ProxySettings#setId(String) setId} method and {@link * ProxySettings#setPassword(String) setPassword} method, or * {@link ProxySettings#setCredentials(String, String) setCredentials} * method can be used to set the credentials. Note that, however, * the current implementation supports only Basic Authentication. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Set credentials for authentication at a proxy server.</span> * settings.{@link ProxySettings#setCredentials(String, String) * setCredentials}(id, password); * </pre> * </blockquote> * * <h3>Create WebSocket</h3> * * <p> * {@link WebSocket} class represents a WebSocket. Its instances are * created by calling one of {@code createSocket} methods of a {@link * WebSocketFactory} instance. Below is the simplest example to create * a {@code WebSocket} instance. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Create a WebSocket. The scheme part can be one of the following: * // 'ws', 'wss', 'http' and 'https' (case-insensitive). The user info * // part, if any, is interpreted as expected. If a raw socket failed * // to be created, an IOException is thrown.</span> * WebSocket ws = new {@link WebSocketFactory#WebSocketFactory() * WebSocketFactory()} * .{@link WebSocketFactory#createSocket(String) * createWebSocket}(<span style="color: darkred;">"ws://localhost/endpoint"</span>);</pre> * </blockquote> * * <p> * There are two ways to set a timeout value for socket connection. The * first way is to call {@link WebSocketFactory#setConnectionTimeout(int) * setConnectionTimeout(int timeout)} method of {@code WebSocketFactory}. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Create a WebSocket factory and set 5000 milliseconds as a timeout * // value for socket connection.</span> * WebSocketFactory factory = new WebSocketFactory().{@link * WebSocketFactory#setConnectionTimeout(int) setConnectionTimeout}(5000); * * <span style="color: green;">// Create a WebSocket. The timeout value set above is used.</span> * WebSocket ws = factory.{@link WebSocketFactory#createSocket(String) * createWebSocket}(<span style="color: darkred;">"ws://localhost/endpoint"</span>);</pre> * </blockquote> * * <p> * The other way is to give a timeout value to a {@code createSocket} method. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Create a WebSocket factory. The timeout value remains 0.</span> * WebSocketFactory factory = new WebSocketFactory(); * * <span style="color: green;">// Create a WebSocket with a socket connection timeout value.</span> * WebSocket ws = factory.{@link WebSocketFactory#createSocket(String, int) * createWebSocket}(<span style="color: darkred;">"ws://localhost/endpoint"</span>, 5000);</pre> * </blockquote> * * <p> * The timeout value is passed to {@link Socket#connect(java.net.SocketAddress, int) * connect}{@code (}{@link java.net.SocketAddress SocketAddress}{@code , int)} * method of {@link java.net.Socket}. * </p> * * <h3>Register Listener</h3> * * <p> * After creating a {@code WebSocket} instance, you should call {@link * #addListener(WebSocketListener)} method to register a {@link * WebSocketListener} that receives WebSocket events. {@link * WebSocketAdapter} is an empty implementation of {@link * WebSocketListener} interface. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Register a listener to receive WebSocket events.</span> * ws.{@link #addListener(WebSocketListener) addListener}(new {@link * WebSocketAdapter#WebSocketAdapter() WebSocketAdapter()} { * <span style="color: gray;">{@code @}Override</span> * public void {@link WebSocketListener#onTextMessage(WebSocket, String) * onTextMessage}(WebSocket websocket, String message) throws Exception { * <span style="color: green;">// Received a text message.</span> * ...... * } * });</pre> * </blockquote> * * <p> * The table below is the list of callback methods defined in {@code WebSocketListener} * interface. * </p> * * <blockquote> * <table border="1" cellpadding="5" style="border-collapse: collapse;"> * <caption>{@code WebSocketListener} methods</caption> * <thead> * <tr> * <th>Method</th> * <th>Description</th> * </tr> * </thead> * <tbody> * <tr> * <td>{@link WebSocketListener#handleCallbackError(WebSocket, Throwable) handleCallbackError}</td> * <td>Called when an <code>on<i>Xxx</i>()</code> method threw a {@code Throwable}.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onBinaryFrame(WebSocket, WebSocketFrame) onBinaryFrame}</td> * <td>Called when a binary frame was received.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onBinaryMessage(WebSocket, byte[]) onBinaryMessage}</td> * <td>Called when a binary message was received.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onCloseFrame(WebSocket, WebSocketFrame) onCloseFrame}</td> * <td>Called when a close frame was received.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onConnected(WebSocket, Map) onConnected}</td> * <td>Called after the opening handshake succeeded.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onConnectError(WebSocket, WebSocketException) onConnectError}</td> * <td>Called when {@link #connectAsynchronously()} failed.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onContinuationFrame(WebSocket, WebSocketFrame) onContinuationFrame}</td> * <td>Called when a continuation frame was received.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onDisconnected(WebSocket, WebSocketFrame, WebSocketFrame, boolean) onDisconnected}</td> * <td>Called after a WebSocket connection was closed.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onError(WebSocket, WebSocketException) onError}</td> * <td>Called when an error occurred.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onFrame(WebSocket, WebSocketFrame) onFrame}</td> * <td>Called when a frame was received.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onFrameError(WebSocket, WebSocketException, WebSocketFrame) onFrameError}</td> * <td>Called when a frame failed to be read.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onFrameSent(WebSocket, WebSocketFrame) onFrameSent}</td> * <td>Called when a frame was sent.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onFrameUnsent(WebSocket, WebSocketFrame) onFrameUnsent}</td> * <td>Called when a frame was not sent.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onMessageDecompressionError(WebSocket, WebSocketException, byte[]) onMessageDecompressionError}</td> * <td>Called when a message failed to be decompressed.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onMessageError(WebSocket, WebSocketException, List) onMessageError}</td> * <td>Called when a message failed to be constructed.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onPingFrame(WebSocket, WebSocketFrame) onPingFrame}</td> * <td>Called when a ping frame was received.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onPongFrame(WebSocket, WebSocketFrame) onPongFrame}</td> * <td>Called when a pong frame was received.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onSendError(WebSocket, WebSocketException, WebSocketFrame) onSendError}</td> * <td>Called when an error occurred on sending a frame.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onSendingFrame(WebSocket, WebSocketFrame) onSendingFrame}</td> * <td>Called before a frame is sent.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onSendingHandshake(WebSocket, String, List) onSendingHandshake}</td> * <td>Called before an opening handshake is sent.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onStateChanged(WebSocket, WebSocketState) onStateChanged}</td> * <td>Called when the state of WebSocket changed.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onTextFrame(WebSocket, WebSocketFrame) onTextFrame}</td> * <td>Called when a text frame was received.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onTextMessage(WebSocket, String) onTextMessage}</td> * <td>Called when a text message was received.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onTextMessageError(WebSocket, WebSocketException, byte[]) onTextMessageError}</td> * <td>Called when a text message failed to be constructed.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onThreadCreated(WebSocket, ThreadType, Thread) onThreadCreated}</td> * <td>Called after a thread was created.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onThreadStarted(WebSocket, ThreadType, Thread) onThreadStarted}</td> * <td>Called at the beginning of a thread's {@code run()} method. * </tr> * <tr> * <td>{@link WebSocketListener#onThreadStopping(WebSocket, ThreadType, Thread) onThreadStopping}</td> * <td>Called at the end of a thread's {@code run()} method. * </tr> * <tr> * <td>{@link WebSocketListener#onUnexpectedError(WebSocket, WebSocketException) onUnexpectedError}</td> * <td>Called when an uncaught throwable was detected.</td> * </tr> * </tbody> * </table> * </blockquote> * * <h3>Configure WebSocket</h3> * * <p> * Before starting a WebSocket <a href="https://tools.ietf.org/html/rfc6455#section-4" * >opening handshake</a> with the server, you can configure the * {@code WebSocket} instance by using the following methods. * </p> * * <blockquote> * <table border="1" cellpadding="5" style="border-collapse: collapse;"> * <caption>Methods for Configuration</caption> * <thead> * <tr> * <th>METHOD</th> * <th>DESCRIPTION</th> * </tr> * </thead> * <tbody> * <tr> * <td>{@link #addProtocol(String) addProtocol}</td> * <td>Adds an element to {@code Sec-WebSocket-Protocol}</td> * </tr> * <tr> * <td>{@link #addExtension(WebSocketExtension) addExtension}</td> * <td>Adds an element to {@code Sec-WebSocket-Extensions}</td> * </tr> * <tr> * <td>{@link #addHeader(String, String) addHeader}</td> * <td>Adds an arbitrary HTTP header.</td> * </tr> * <tr> * <td>{@link #setUserInfo(String, String) setUserInfo}</td> * <td>Adds {@code Authorization} header for Basic Authentication.</td> * </tr> * <tr> * <td>{@link #getSocket() getSocket}</td> * <td>Gets the underlying {@link Socket} instance to configure it.</td> * </tr> * <tr> * <td>{@link #setExtended(boolean) setExtended}</td> * <td>Disables validity checks on RSV1/RSV2/RSV3 and opcode.</td> * </tr> * <tr> * <td>{@link #setFrameQueueSize(int) setFrameQueueSize}</td> * <td>Set the size of the frame queue for <a href="#congestion_control">congestion control</a>.</td> * </tr> * <tr> * <td>{@link #setMaxPayloadSize(int) setMaxPayloadSize}</td> * <td>Set the <a href="#maximum_payload_size">maximum payload size</a>.</td> * </tr> * <tr> * <td>{@link #setMissingCloseFrameAllowed(boolean) setMissingCloseFrameAllowed}</td> * <td>Set whether to allow the server to close the connection without sending a close frame.</td> * </tr> * </tbody> * </table> * </blockquote> * * <h3>Connect To Server</h3> * * <p> * By calling {@link #connect()} method, connection to the server is * established and a WebSocket opening handshake is performed * synchronously. If an error occurred during the handshake, * a {@link WebSocketException} would be thrown. Instead, when the * handshake succeeds, the {@code connect()} implementation creates * threads and starts them to read and write WebSocket frames * asynchronously. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> try * { * <span style="color: green;">// Connect to the server and perform an opening handshake.</span> * <span style="color: green;">// This method blocks until the opening handshake is finished.</span> * ws.{@link #connect()}; * } * catch ({@link OpeningHandshakeException} e) * { * <span style="color: green;">// A violation against the WebSocket protocol was detected</span> * <span style="color: green;">// during the opening handshake.</span> * } * catch ({@link HostnameUnverifiedException} e) * { * <span style="color: green;">// The certificate of the peer does not match the expected hostname.</span> * } * catch ({@link WebSocketException} e) * { * <span style="color: green;">// Failed to establish a WebSocket connection.</span> * }</pre> * </blockquote> * * <p> * In some cases, {@code connect()} method throws {@link OpeningHandshakeException} * which is a subclass of {@code WebSocketException} (since version 1.19). * {@code OpeningHandshakeException} provides additional methods such as * {@link OpeningHandshakeException#getStatusLine() getStatusLine()}, * {@link OpeningHandshakeException#getHeaders() getHeaders()} and * {@link OpeningHandshakeException#getBody() getBody()} to access the * response from a server. The following snippet is an example to print * information that the exception holds. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> catch ({@link OpeningHandshakeException} e) * { * <span style="color: green;">// Status line.</span> * {@link StatusLine} sl = e.{@link OpeningHandshakeException#getStatusLine() getStatusLine()}; * System.out.println(<span style="color:darkred;">"=== Status Line ==="</span>); * System.out.format(<span style="color:darkred;">"HTTP Version = %s\n"</span>, sl.{@link StatusLine#getHttpVersion() getHttpVersion()}); * System.out.format(<span style="color:darkred;">"Status Code = %d\n"</span>, sl.{@link StatusLine#getStatusCode() getStatusCode()}); * System.out.format(<span style="color:darkred;">"Reason Phrase = %s\n"</span>, sl.{@link StatusLine#getReasonPhrase() getReasonPhrase()}); * * <span style="color: green;">// HTTP headers.</span> * Map<String, List<String>> headers = e.{@link OpeningHandshakeException#getHeaders() getHeaders()}; * System.out.println(<span style="color:darkred;">"=== HTTP Headers ==="</span>); * for (Map.Entry<String, List<String>> entry : headers.entrySet()) * { * <span style="color: green;">// Header name.</span> * String name = entry.getKey(); * * <span style="color: green;">// Values of the header.</span> * List<String> values = entry.getValue(); * * if (values == null || values.size() == 0) * { * <span style="color: green;">// Print the name only.</span> * System.out.println(name); * continue; * } * * for (String value : values) * { * <span style="color: green;">// Print the name and the value.</span> * System.out.format(<span style="color:darkred;">"%s: %s\n"</span>, name, value); * } * } * }</pre> * </blockquote> * * <p> * Also, {@code connect()} method throws {@link HostnameUnverifiedException} * which is a subclass of {@code WebSocketException} (since version 2.1) when * the certificate of the peer does not match the expected hostname. * </p> * * <h3>Connect To Server Asynchronously</h3> * * <p> * The simplest way to call {@code connect()} method asynchronously is to * use {@link #connectAsynchronously()} method. The implementation of the * method creates a thread and calls {@code connect()} method in the thread. * When the {@code connect()} call failed, {@link * WebSocketListener#onConnectError(WebSocket, WebSocketException) * onConnectError()} of {@code WebSocketListener} would be called. Note that * {@code onConnectError()} is called only when {@code connectAsynchronously()} * was used and the {@code connect()} call executed in the background thread * failed. Neither direct synchronous {@code connect()} nor * {@link WebSocket#connect(java.util.concurrent.ExecutorService) * connect(ExecutorService)} (described below) will trigger the callback method. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Connect to the server asynchronously.</span> * ws.{@link #connectAsynchronously()}; * </pre> * </blockquote> * * <p> * Another way to call {@code connect()} method asynchronously is to use * {@link #connect(ExecutorService)} method. The method performs a WebSocket * opening handshake asynchronously using the given {@link ExecutorService}. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Prepare an ExecutorService.</span> * {@link ExecutorService} es = {@link java.util.concurrent.Executors Executors}.{@link * java.util.concurrent.Executors#newSingleThreadExecutor() newSingleThreadExecutor()}; * * <span style="color: green;">// Connect to the server asynchronously.</span> * {@link Future}{@code <WebSocket>} future = ws.{@link #connect(ExecutorService) connect}(es); * * try * { * <span style="color: green;">// Wait for the opening handshake to complete.</span> * future.get(); * } * catch ({@link java.util.concurrent.ExecutionException ExecutionException} e) * { * if (e.getCause() instanceof {@link WebSocketException}) * { * ...... * } * }</pre> * </blockquote> * * <p> * The implementation of {@code connect(ExecutorService)} method creates * a {@link java.util.concurrent.Callable Callable}{@code <WebSocket>} * instance by calling {@link #connectable()} method and passes the * instance to {@link ExecutorService#submit(Callable) submit(Callable)} * method of the given {@code ExecutorService}. What the implementation * of {@link Callable#call() call()} method of the {@code Callable} * instance does is just to call the synchronous {@code connect()}. * </p> * * <h3>Send Frames</h3> * * <p> * WebSocket frames can be sent by {@link #sendFrame(WebSocketFrame)} * method. Other <code>send<i>Xxx</i></code> methods such as {@link * #sendText(String)} are aliases of {@code sendFrame} method. All of * the <code>send<i>Xxx</i></code> methods work asynchronously. * However, under some conditions, <code>send<i>Xxx</i></code> methods * may block. See <a href="#congestion_control">Congestion Control</a> * for details. * </p> * * <p> * Below * are some examples of <code>send<i>Xxx</i></code> methods. Note that * in normal cases, you don't have to call {@link #sendClose()} method * and {@link #sendPong()} (or their variants) explicitly because they * are called automatically when appropriate. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Send a text frame.</span> * ws.{@link #sendText(String) sendText}(<span style="color: darkred;">"Hello."</span>); * * <span style="color: green;">// Send a binary frame.</span> * byte[] binary = ......; * ws.{@link #sendBinary(byte[]) sendBinary}(binary); * * <span style="color: green;">// Send a ping frame.</span> * ws.{@link #sendPing(String) sendPing}(<span style="color: darkred;">"Are you there?"</span>);</pre> * </blockquote> * * <p> * If you want to send fragmented frames, you have to know the details * of the specification (<a href="https://tools.ietf.org/html/rfc6455#section-5.4" * >5.4. Fragmentation</a>). Below is an example to send a text message * ({@code "How are you?"}) which consists of 3 fragmented frames. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// The first frame must be either a text frame or a binary frame. * // And its FIN bit must be cleared.</span> * WebSocketFrame firstFrame = WebSocketFrame * .{@link WebSocketFrame#createTextFrame(String) * createTextFrame}(<span style="color: darkred;">"How "</span>) * .{@link WebSocketFrame#setFin(boolean) setFin}(false); * * <span style="color: green;">// Subsequent frames must be continuation frames. The FIN bit of * // all continuation frames except the last one must be cleared. * // Note that the FIN bit of frames returned from * // WebSocketFrame.createContinuationFrame methods is cleared, so * // the example below does not clear the FIN bit explicitly.</span> * WebSocketFrame secondFrame = WebSocketFrame * .{@link WebSocketFrame#createContinuationFrame(String) * createContinuationFrame}(<span style="color: darkred;">"are "</span>); * * <span style="color: green;">// The last frame must be a continuation frame with the FIN bit set. * // Note that the FIN bit of frames returned from * // WebSocketFrame.createContinuationFrame methods is cleared, so * // the FIN bit of the last frame must be set explicitly.</span> * WebSocketFrame lastFrame = WebSocketFrame * .{@link WebSocketFrame#createContinuationFrame(String) * createContinuationFrame}(<span style="color: darkred;">"you?"</span>) * .{@link WebSocketFrame#setFin(boolean) setFin}(true); * * <span style="color: green;">// Send a text message which consists of 3 frames.</span> * ws.{@link #sendFrame(WebSocketFrame) sendFrame}(firstFrame) * .{@link #sendFrame(WebSocketFrame) sendFrame}(secondFrame) * .{@link #sendFrame(WebSocketFrame) sendFrame}(lastFrame);</pre> * </blockquote> * * <p> * Alternatively, the same as above can be done like this. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Send a text message which consists of 3 frames.</span> * ws.{@link #sendText(String, boolean) sendText}(<span style="color: darkred;">"How "</span>, false) * .{@link #sendContinuation(String) sendContinuation}(<span style="color: darkred;">"are "</span>) * .{@link #sendContinuation(String, boolean) sendContinuation}(<span style="color: darkred;">"you?"</span>, true);</pre> * </blockquote> * * <h3>Send Ping/Pong Frames Periodically</h3> * * <p> * You can send ping frames periodically by calling {@link #setPingInterval(long) * setPingInterval} method with an interval in milliseconds between ping frames. * This method can be called both before and after {@link #connect()} method. * Passing zero stops the periodical sending. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Send a ping per 60 seconds.</span> * ws.{@link #setPingInterval(long) setPingInterval}(60 * 1000); * * <span style="color: green;">// Stop the periodical sending.</span> * ws.{@link #setPingInterval(long) setPingInterval}(0);</pre> * </blockquote> * * <p> * Likewise, you can send pong frames periodically by calling {@link * #setPongInterval(long) setPongInterval} method. "<i>A Pong frame MAY be sent * <b>unsolicited</b>."</i> (<a href="https://tools.ietf.org/html/rfc6455#section-5.5.3" * >RFC 6455, 5.5.3. Pong</a>) * </p> * * <p> * You can customize payload of ping/pong frames that are sent automatically by using * {@link #setPingPayloadGenerator(PayloadGenerator)} and * {@link #setPongPayloadGenerator(PayloadGenerator)} methods. Both methods take an * instance of {@link PayloadGenerator} interface. The following is an example to * use the string representation of the current date as payload of ping frames. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> ws.{@link #setPingPayloadGenerator(PayloadGenerator) * setPingPayloadGenerator}(new {@link PayloadGenerator} () { * <span style="color: gray;">{@code @}Override</span> * public byte[] generate() { * <span style="color: green;">// The string representation of the current date.</span> * return new Date().toString().getBytes(); * } * });</pre> * </blockquote> * * <p> * Note that the maximum payload length of control frames (e.g. ping frames) is 125. * Therefore, the length of a byte array returned from {@link PayloadGenerator#generate() * generate()} method must not exceed 125. * </p> * * <h3>Auto Flush</h3> * * <p> * By default, a frame is automatically flushed to the server immediately after * {@link #sendFrame(WebSocketFrame) sendFrame} method is executed. This automatic * flush can be disabled by calling {@link #setAutoFlush(boolean) setAutoFlush}{@code * (false)}. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Disable auto-flush.</span> * ws.{@link #setAutoFlush(boolean) setAutoFlush}(false);</pre> * </blockquote> * * <p> * To flush frames manually, call {@link #flush()} method. Note that this method * works asynchronously. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Flush frames to the server manually.</span> * ws.{@link #flush()};</pre> * </blockquote> * * <h3 id="congestion_control">Congestion Control</h3> * * <p> * <code>send<i>Xxx</i></code> methods queue a {@link WebSocketFrame} instance to the * internal queue. By default, no upper limit is imposed on the queue size, so * <code>send<i>Xxx</i></code> methods do not block. However, this behavior may cause * a problem if your WebSocket client application sends too many WebSocket frames in * a short time for the WebSocket server to process. In such a case, you may want * <code>send<i>Xxx</i></code> methods to block when many frames are queued. * </p> * * <p> * You can set an upper limit on the internal queue by calling {@link #setFrameQueueSize(int)} * method. As a result, if the number of frames in the queue has reached the upper limit * when a <code>send<i>Xxx</i></code> method is called, the method blocks until the * queue gets spaces. The code snippet below is an example to set 5 as the upper limit * of the internal frame queue. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Set 5 as the frame queue size.</span> * ws.{@link #setFrameQueueSize(int) setFrameQueueSize}(5);</pre> * </blockquote> * * <p> * Note that under some conditions, even if the queue is full, <code>send<i>Xxx</i></code> * methods do not block. For example, in the case where the thread to send frames * ({@code WritingThread}) is going to stop or has already stopped. In addition, * method calls to send a <a href="https://tools.ietf.org/html/rfc6455#section-5.5" * >control frame</a> (e.g. {@link #sendClose()} and {@link #sendPing()}) do not block. * </p> * * <h3 id="maximum_payload_size">Maximum Payload Size</h3> * * <p> * You can set an upper limit on the payload size of WebSocket frames by calling * {@link #setMaxPayloadSize(int)} method with a positive value. Text, binary and * continuation frames whose payload size is bigger than the maximum payload size * you have set will be split into multiple frames. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Set 1024 as the maximum payload size.</span> * ws.{@link #setMaxPayloadSize(int) setMaxPayloadSize}(1024);</pre> * </blockquote> * * <p> * Control frames (close, ping and pong frames) are never split as per the specification. * </p> * * <p> * If permessage-deflate extension is enabled and if the payload size of a WebSocket * frame after compression does not exceed the maximum payload size, the WebSocket * frame is not split even if the payload size before compression execeeds the * maximum payload size. * </p> * * <h3 id="compression">Compression</h3> * * <p> * The <strong>permessage-deflate</strong> extension (<a href= * "http://tools.ietf.org/html/rfc7692">RFC 7692</a>) has been supported * since the version 1.17. To enable the extension, call {@link #addExtension(String) * addExtension} method with {@code "permessage-deflate"}. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"><span style="color: green;"> // Enable "permessage-deflate" extension (RFC 7692).</span> * ws.{@link #addExtension(String) addExtension}({@link WebSocketExtension#PERMESSAGE_DEFLATE});</pre> * </blockquote> * * <h3>Missing Close Frame</h3> * * <p> * Some server implementations close a WebSocket connection without sending a * <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close frame</a> to * a client in some cases. Strictly speaking, this is a violation against the * specification (<a href= * "https://tools.ietf.org/html/rfc6455#section-5.5.1">RFC 6455</a>). However, this * library has allowed the behavior by default since the version 1.29. Even if the * end of the input stream of a WebSocket connection were reached without a close * frame being received, it would trigger neither {@link * WebSocketListener#onError(WebSocket, WebSocketException) onError()} method nor * {@link WebSocketListener#onFrameError(WebSocket, WebSocketException, WebSocketFrame) * onFrameError()} method of {@link WebSocketListener}. If you want to make a * {@code WebSocket} instance report an error in the case, pass {@code false} to * {@link #setMissingCloseFrameAllowed(boolean)} method. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"><span style="color: green;" * > // Make this library report an error when the end of the input stream * // of the WebSocket connection is reached before a close frame is read.</span> * ws.{@link #setMissingCloseFrameAllowed(boolean) setMissingCloseFrameAllowed}(false);</pre> * </blockquote> * * <h3>Disconnect WebSocket</h3> * * <p> * Before a WebSocket is closed, a closing handshake is performed. A closing handshake * is started (1) when the server sends a close frame to the client or (2) when the * client sends a close frame to the server. You can start a closing handshake by calling * {@link #disconnect()} method (or by sending a close frame manually). * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Close the WebSocket connection.</span> * ws.{@link #disconnect()};</pre> * </blockquote> * * <p> * {@code disconnect()} method has some variants. If you want to change the close code * and the reason phrase of the close frame that this client will send to the server, * use a variant method such as {@link #disconnect(int, String)}. {@code disconnect()} * method itself is an alias of {@code disconnect(}{@link WebSocketCloseCode}{@code * .NORMAL, null)}. * </p> * * <h3>Reconnection</h3> * * <p> * {@code connect()} method can be called at most only once regardless of whether the * method succeeded or failed. If you want to re-connect to the WebSocket endpoint, * you have to create a new {@code WebSocket} instance again by calling one of {@code * createSocket} methods of a {@code WebSocketFactory}. You may find {@link #recreate()} * method useful if you want to create a new {@code WebSocket} instance that has the * same settings as the original instance. Note that, however, settings you made on * the raw socket of the original {@code WebSocket} instance are not copied. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: green;">// Create a new WebSocket instance and connect to the same endpoint.</span> * ws = ws.{@link #recreate()}.{@link #connect()};</pre> * </blockquote> * * <p> * There is a variant of {@code recreate()} method that takes a timeout value for * socket connection. If you want to use a timeout value that is different from the * one used when the existing {@code WebSocket} instance was created, use {@link * #recreate(int) recreate(int timeout)} method. * </p> * * <p> * Note that you should not trigger reconnection in {@link * WebSocketListener#onError(WebSocket, WebSocketException) onError()} method * because {@code onError()} may be called multiple times due to one error. Instead, * {@link WebSocketListener#onDisconnected(WebSocket, WebSocketFrame, WebSocketFrame, * boolean) onDisconnected()} is the right place to trigger reconnection. * </p> * * <p> * Also note that the reason I use an expression of <i>"to trigger reconnection"</i> * instead of <i>"to call <code>recreate().connect()</code>"</i> is that I myself * won't do it <i>synchronously</i> in <code>WebSocketListener</code> callback * methods but will just schedule reconnection or will just go to the top of a kind * of <i>application loop</i> that repeats to establish a WebSocket connection until * it succeeds. * </p> * * <h3>Error Handling</h3> * * <p> * {@code WebSocketListener} has some {@code onXxxError()} methods such as {@link * WebSocketListener#onFrameError(WebSocket, WebSocketException, WebSocketFrame) * onFrameError()} and {@link * WebSocketListener#onSendError(WebSocket, WebSocketException, WebSocketFrame) * onSendError()}. Among such methods, {@link * WebSocketListener#onError(WebSocket, WebSocketException) onError()} is a special * one. It is always called before any other {@code onXxxError()} is called. For * example, in the implementation of {@code run()} method of {@code ReadingThread}, * {@code Throwable} is caught and {@code onError()} and {@link * WebSocketListener#onUnexpectedError(WebSocket, WebSocketException) * onUnexpectedError()} are called in this order. The following is the implementation. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: gray;">{@code @}Override</span> * public void run() * { * try * { * main(); * } * catch (Throwable t) * { * <span style="color: green;">// An uncaught throwable was detected in the reading thread.</span> * {@link WebSocketException} cause = new WebSocketException( * {@link WebSocketError}.{@link WebSocketError#UNEXPECTED_ERROR_IN_READING_THREAD UNEXPECTED_ERROR_IN_READING_THREAD}, * <span style="color: darkred;">"An uncaught throwable was detected in the reading thread"</span>, t); * * <span style="color: green;">// Notify the listeners.</span> * ListenerManager manager = mWebSocket.getListenerManager(); * manager.callOnError(cause); * manager.callOnUnexpectedError(cause); * } * }</pre> * </blockquote> * * <p> * So, you can handle all error cases in {@code onError()} method. However, note * that {@code onError()} may be called multiple times for one error cause, so don't * try to trigger reconnection in {@code onError()}. Instead, {@link * WebSocketListener#onDisconnected(WebSocket, WebSocketFrame, WebSocketFrame, boolean) * onDiconnected()} is the right place to trigger reconnection. * </p> * * <p> * All {@code onXxxError()} methods receive a {@link WebSocketException} instance * as the second argument (the first argument is a {@code WebSocket} instance). The * exception class provides {@link WebSocketException#getError() getError()} method * which returns a {@link WebSocketError} enum entry. Entries in {@code WebSocketError} * enum are possible causes of errors that may occur in the implementation of this * library. The error causes are so granular that they can make it easy for you to * find the root cause when an error occurs. * </p> * * <p> * {@code Throwable}s thrown by implementations of {@code onXXX()} callback methods * are passed to {@link WebSocketListener#handleCallbackError(WebSocket, Throwable) * handleCallbackError()} of {@code WebSocketListener}. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: gray;">{@code @}Override</span> * public void {@link WebSocketListener#handleCallbackError(WebSocket, Throwable) * handleCallbackError}(WebSocket websocket, Throwable cause) throws Exception { * <span style="color: green;">// Throwables thrown by onXxx() callback methods come here.</span> * }</pre> * </blockquote> * * <h3>Thread Callbacks</h3> * * <p> * Some threads are created internally in the implementation of {@code WebSocket}. * Known threads are as follows. * </p> * * <blockquote> * <table border="1" cellpadding="5" style="border-collapse: collapse;"> * <caption>Internal Threads</caption> * <thead> * <tr> * <th>THREAD TYPE</th> * <th>DESCRIPTION</th> * </tr> * </thead> * <tbody> * <tr> * <td>{@link ThreadType#READING_THREAD READING_THREAD}</td> * <td>A thread which reads WebSocket frames from the server.</td> * </tr> * <tr> * <td>{@link ThreadType#WRITING_THREAD WRITING_THREAD}</td> * <td>A thread which sends WebSocket frames to the server.</td> * </tr> * <tr> * <td>{@link ThreadType#CONNECT_THREAD CONNECT_THREAD}</td> * <td>A thread which calls {@link WebSocket#connect()} asynchronously.</td> * </tr> * <tr> * <td>{@link ThreadType#FINISH_THREAD FINISH_THREAD}</td> * <td>A thread which does finalization of a {@code WebSocket} instance.</td> * </tr> * </tbody> * </table> * </blockquote> * * <p> * The following callback methods of {@link WebSocketListener} are called according * to the life cycle of the threads. * </p> * * <blockquote> * <table border="1" cellpadding="5" style="border-collapse: collapse;"> * <caption>Thread Callbacks</caption> * <thead> * <tr> * <th>METHOD</th> * <th>DESCRIPTION</th> * </tr> * </thead> * <tbody> * <tr> * <td>{@link WebSocketListener#onThreadCreated(WebSocket, ThreadType, Thread) onThreadCreated()}</td> * <td>Called after a thread was created.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onThreadStarted(WebSocket, ThreadType, Thread) onThreadStarted()}</td> * <td>Called at the beginning of the thread's {@code run()} method.</td> * </tr> * <tr> * <td>{@link WebSocketListener#onThreadStopping(WebSocket, ThreadType, Thread) onThreadStopping()}</td> * <td>Called at the end of the thread's {@code run()} method.</td> * </tr> * </tbody> * </table> * </blockquote> * * <p> * For example, if you want to change the name of the reading thread, * implement {@link WebSocketListener#onThreadCreated(WebSocket, ThreadType, Thread) * onThreadCreated()} method like below. * </p> * * <blockquote> * <pre style="border-left: solid 5px lightgray;"> <span style="color: gray;">{@code @}Override</span> * public void {@link WebSocketListener#onThreadCreated(WebSocket, ThreadType, Thread) * onThreadCreated}(WebSocket websocket, {@link ThreadType} type, Thread thread) * { * if (type == ThreadType.READING_THREAD) * { * thread.setName(<span style="color: darkred;">"READING_THREAD"</span>); * } * }</pre> * </blockquote> * * @see <a href="https://tools.ietf.org/html/rfc6455">RFC 6455 (The WebSocket Protocol)</a> * @see <a href="https://tools.ietf.org/html/rfc7692">RFC 7692 (Compression Extensions for WebSocket)</a> * @see <a href="https://github.com/TakahikoKawasaki/nv-websocket-client">[GitHub] nv-websocket-client</a> * * @author Takahiko Kawasaki */ public class WebSocket { private static final long DEFAULT_CLOSE_DELAY = 10 * 1000L; private final WebSocketFactory mWebSocketFactory; private final SocketConnector mSocketConnector; private final StateManager mStateManager; private HandshakeBuilder mHandshakeBuilder; private final ListenerManager mListenerManager; private final PingSender mPingSender; private final PongSender mPongSender; private final Object mThreadsLock = new Object(); private WebSocketInputStream mInput; private WebSocketOutputStream mOutput; private ReadingThread mReadingThread; private WritingThread mWritingThread; private Map<String, List<String>> mServerHeaders; private List<WebSocketExtension> mAgreedExtensions; private String mAgreedProtocol; private boolean mExtended; private boolean mAutoFlush = true; private boolean mMissingCloseFrameAllowed = true; private int mFrameQueueSize; private int mMaxPayloadSize; private boolean mOnConnectedCalled; private Object mOnConnectedCalledLock = new Object(); private boolean mReadingThreadStarted; private boolean mWritingThreadStarted; private boolean mReadingThreadFinished; private boolean mWritingThreadFinished; private WebSocketFrame mServerCloseFrame; private WebSocketFrame mClientCloseFrame; private PerMessageCompressionExtension mPerMessageCompressionExtension; WebSocket(WebSocketFactory factory, boolean secure, String userInfo, String host, String path, SocketConnector connector) { mWebSocketFactory = factory; mSocketConnector = connector; mStateManager = new StateManager(); mHandshakeBuilder = new HandshakeBuilder(secure, userInfo, host, path); mListenerManager = new ListenerManager(this); mPingSender = new PingSender(this, new CounterPayloadGenerator()); mPongSender = new PongSender(this, new CounterPayloadGenerator()); } /** * Create a new {@code WebSocket} instance that has the same settings * as this instance. Note that, however, settings you made on the raw * socket are not copied. * * <p> * The {@link WebSocketFactory} instance that you used to create this * {@code WebSocket} instance is used again. * </p> * * <p> * This method calls {@link #recreate(int)} with the timeout value that * was used when this instance was created. If you want to create a * socket connection with a different timeout value, use {@link * #recreate(int)} method instead. * </p> * * @return * A new {@code WebSocket} instance. * * @throws IOException * {@link WebSocketFactory#createSocket(URI)} threw an exception. * * @since 1.6 */ public WebSocket recreate() throws IOException { return recreate(mSocketConnector.getConnectionTimeout()); } /** * Create a new {@code WebSocket} instance that has the same settings * as this instance. Note that, however, settings you made on the raw * socket are not copied. * * <p> * The {@link WebSocketFactory} instance that you used to create this * {@code WebSocket} instance is used again. * </p> * * @return * A new {@code WebSocket} instance. * * @param timeout * The timeout value in milliseconds for socket timeout. * A timeout of zero is interpreted as an infinite timeout. * * @throws IllegalArgumentException * The given timeout value is negative. * * @throws IOException * {@link WebSocketFactory#createSocket(URI)} threw an exception. * * @since 1.10 */ public WebSocket recreate(int timeout) throws IOException { if (timeout < 0) { throw new IllegalArgumentException("The given timeout value is negative."); } WebSocket instance = mWebSocketFactory.createSocket(getURI(), timeout); // Copy the settings. instance.mHandshakeBuilder = new HandshakeBuilder(mHandshakeBuilder); instance.setPingInterval(getPingInterval()); instance.setPongInterval(getPongInterval()); instance.setPingPayloadGenerator(getPingPayloadGenerator()); instance.setPongPayloadGenerator(getPongPayloadGenerator()); instance.mExtended = mExtended; instance.mAutoFlush = mAutoFlush; instance.mMissingCloseFrameAllowed = mMissingCloseFrameAllowed; instance.mFrameQueueSize = mFrameQueueSize; // Copy listeners. List<WebSocketListener> listeners = mListenerManager.getListeners(); synchronized (listeners) { instance.addListeners(listeners); } return instance; } @Override protected void finalize() throws Throwable { if (isInState(CREATED)) { // The raw socket needs to be closed. finish(); } super.finalize(); } /** * Get the current state of this WebSocket. * * <p> * The initial state is {@link WebSocketState#CREATED CREATED}. * When {@link #connect()} is called, the state is changed to * {@link WebSocketState#CONNECTING CONNECTING}, and then to * {@link WebSocketState#OPEN OPEN} after a successful opening * handshake. The state is changed to {@link * WebSocketState#CLOSING CLOSING} when a closing handshake * is started, and then to {@link WebSocketState#CLOSED CLOSED} * when the closing handshake finished. * </p> * * <p> * See the description of {@link WebSocketState} for details. * </p> * * @return * The current state. * * @see WebSocketState */ public WebSocketState getState() { synchronized (mStateManager) { return mStateManager.getState(); } } /** * Check if the current state of this WebSocket is {@link * WebSocketState#OPEN OPEN}. * * @return * {@code true} if the current state is OPEN. * * @since 1.1 */ public boolean isOpen() { return isInState(OPEN); } /** * Check if the current state is equal to the specified state. */ private boolean isInState(WebSocketState state) { synchronized (mStateManager) { return (mStateManager.getState() == state); } } /** * Add a value for {@code Sec-WebSocket-Protocol}. * * @param protocol * A protocol name. * * @return * {@code this} object. * * @throws IllegalArgumentException * The protocol name is invalid. A protocol name must be * a non-empty string with characters in the range U+0021 * to U+007E not including separator characters. */ public WebSocket addProtocol(String protocol) { mHandshakeBuilder.addProtocol(protocol); return this; } /** * Remove a protocol from {@code Sec-WebSocket-Protocol}. * * @param protocol * A protocol name. {@code null} is silently ignored. * * @return * {@code this} object. * * @since 1.14 */ public WebSocket removeProtocol(String protocol) { mHandshakeBuilder.removeProtocol(protocol); return this; } /** * Remove all protocols from {@code Sec-WebSocket-Protocol}. * * @return * {@code this} object. * * @since 1.14 */ public WebSocket clearProtocols() { mHandshakeBuilder.clearProtocols(); return this; } /** * Add a value for {@code Sec-WebSocket-Extension}. * * @param extension * An extension. {@code null} is silently ignored. * * @return * {@code this} object. */ public WebSocket addExtension(WebSocketExtension extension) { mHandshakeBuilder.addExtension(extension); return this; } /** * Add a value for {@code Sec-WebSocket-Extension}. The input string * should comply with the format described in <a href= * "https://tools.ietf.org/html/rfc6455#section-9.1">9.1. Negotiating * Extensions</a> in <a href="https://tools.ietf.org/html/rfc6455" * >RFC 6455</a>. * * @param extension * A string that represents a WebSocket extension. If it does * not comply with RFC 6455, no value is added to {@code * Sec-WebSocket-Extension}. * * @return * {@code this} object. * * @since 1.14 */ public WebSocket addExtension(String extension) { mHandshakeBuilder.addExtension(extension); return this; } /** * Remove an extension from {@code Sec-WebSocket-Extension}. * * @param extension * An extension to remove. {@code null} is silently ignored. * * @return * {@code this} object. * * @since 1.14 */ public WebSocket removeExtension(WebSocketExtension extension) { mHandshakeBuilder.removeExtension(extension); return this; } /** * Remove extensions from {@code Sec-WebSocket-Extension} by * an extension name. * * @param name * An extension name. {@code null} is silently ignored. * * @return * {@code this} object. * * @since 1.14 */ public WebSocket removeExtensions(String name) { mHandshakeBuilder.removeExtensions(name); return this; } /** * Remove all extensions from {@code Sec-WebSocket-Extension}. * * @return * {@code this} object. * * @since 1.14 */ public WebSocket clearExtensions() { mHandshakeBuilder.clearExtensions(); return this; } /** * Add a pair of extra HTTP header. * * @param name * An HTTP header name. When {@code null} or an empty * string is given, no header is added. * * @param value * The value of the HTTP header. * * @return * {@code this} object. */ public WebSocket addHeader(String name, String value) { mHandshakeBuilder.addHeader(name, value); return this; } /** * Remove pairs of extra HTTP headers. * * @param name * An HTTP header name. {@code null} is silently ignored. * * @return * {@code this} object. * * @since 1.14 */ public WebSocket removeHeaders(String name) { mHandshakeBuilder.removeHeaders(name); return this; } /** * Clear all extra HTTP headers. * * @return * {@code this} object. * * @since 1.14 */ public WebSocket clearHeaders() { mHandshakeBuilder.clearHeaders(); return this; } /** * Set the credentials to connect to the WebSocket endpoint. * * @param userInfo * The credentials for Basic Authentication. The format * should be <code><i>id</i>:<i>password</i></code>. * * @return * {@code this} object. */ public WebSocket setUserInfo(String userInfo) { mHandshakeBuilder.setUserInfo(userInfo); return this; } /** * Set the credentials to connect to the WebSocket endpoint. * * @param id * The ID. * * @param password * The password. * * @return * {@code this} object. */ public WebSocket setUserInfo(String id, String password) { mHandshakeBuilder.setUserInfo(id, password); return this; } /** * Clear the credentials to connect to the WebSocket endpoint. * * @return * {@code this} object. * * @since 1.14 */ public WebSocket clearUserInfo() { mHandshakeBuilder.clearUserInfo(); return this; } /** * Check if extended use of WebSocket frames are allowed. * * <p> * When extended use is allowed, values of RSV1/RSV2/RSV3 bits * and opcode of frames are not checked. On the other hand, * if not allowed (default), non-zero values for RSV1/RSV2/RSV3 * bits and unknown opcodes cause an error. In such a case, * {@link WebSocketListener#onFrameError(WebSocket, * WebSocketException, WebSocketFrame) onFrameError} method of * listeners are called and the WebSocket is eventually closed. * </p> * * @return * {@code true} if extended use of WebSocket frames * are allowed. */ public boolean isExtended() { return mExtended; } /** * Allow or disallow extended use of WebSocket frames. * * @param extended * {@code true} to allow extended use of WebSocket frames. * * @return * {@code this} object. */ public WebSocket setExtended(boolean extended) { mExtended = extended; return this; } /** * Check if flush is performed automatically after {@link * #sendFrame(WebSocketFrame)} is done. The default value is * {@code true}. * * @return * {@code true} if flush is performed automatically. * * @since 1.5 */ public boolean isAutoFlush() { return mAutoFlush; } /** * Enable or disable auto-flush of sent frames. * * @param auto * {@code true} to enable auto-flush. {@code false} to * disable it. * * @return * {@code this} object. * * @since 1.5 */ public WebSocket setAutoFlush(boolean auto) { mAutoFlush = auto; return this; } /** * Check if this instance allows the server to close the WebSocket * connection without sending a <a href= * "https://tools.ietf.org/html/rfc6455#section-5.5.1">close frame</a> * to this client. The default value is {@code true}. * * @return * {@code true} if the configuration allows for the server to * close the WebSocket connection without sending a close frame * to this client. {@code false} if the configuration requires * that an error be reported via * {@link WebSocketListener#onError(WebSocket, WebSocketException) * onError()} method and {@link WebSocketListener#onFrameError(WebSocket, * WebSocketException, WebSocketFrame) onFrameError()} method of * {@link WebSocketListener}. * * @since 1.29 */ public boolean isMissingCloseFrameAllowed() { return mMissingCloseFrameAllowed; } /** * Set whether to allow the server to close the WebSocket connection * without sending a <a href= * "https://tools.ietf.org/html/rfc6455#section-5.5.1">close frame</a> * to this client. * * @param allowed * {@code true} to allow the server to close the WebSocket * connection without sending a close frame to this client. * {@code false} to make this instance report an error when the * end of the input stream of the WebSocket connection is reached * before a close frame is read. * * @return * {@code this} object. * * @since 1.29 */ public WebSocket setMissingCloseFrameAllowed(boolean allowed) { mMissingCloseFrameAllowed = allowed; return this; } /** * Flush frames to the server. Flush is performed asynchronously. * * @return * {@code this} object. * * @since 1.5 */ public WebSocket flush() { synchronized (mStateManager) { WebSocketState state = mStateManager.getState(); if (state != OPEN && state != CLOSING) { return this; } } // Get the reference to the instance of WritingThread. WritingThread wt = mWritingThread; // If and only if an instance of WritingThread is available. if (wt != null) { // Request flush. wt.queueFlush(); } return this; } /** * Get the size of the frame queue. The default value is 0 and it means * there is no limit on the queue size. * * @return * The size of the frame queue. * * @since 1.15 */ public int getFrameQueueSize() { return mFrameQueueSize; } /** * Set the size of the frame queue. The default value is 0 and it means * there is no limit on the queue size. * * <p> * <code>send<i>Xxx</i></code> methods queue a {@link WebSocketFrame} * instance to the internal queue. If the number of frames in the queue * has reached the upper limit (which has been set by this method) when * a <code>send<i>Xxx</i></code> method is called, the method blocks * until the queue gets spaces. * </p> * * <p> * Under some conditions, even if the queue is full, <code>send<i>Xxx</i></code> * methods do not block. For example, in the case where the thread to send * frames ({@code WritingThread}) is going to stop or has already stopped. * In addition, method calls to send a <a href= * "https://tools.ietf.org/html/rfc6455#section-5.5">control frame</a> (e.g. * {@link #sendClose()} and {@link #sendPing()}) do not block. * </p> * * @param size * The queue size. 0 means no limit. Negative numbers are not allowed. * * @return * {@code this} object. * * @throws IllegalArgumentException * {@code size} is negative. * * @since 1.15 */ public WebSocket setFrameQueueSize(int size) throws IllegalArgumentException { if (size < 0) { throw new IllegalArgumentException("size must not be negative."); } mFrameQueueSize = size; return this; } /** * Get the maximum payload size. The default value is 0 which means that * the maximum payload size is not set and as a result frames are not split. * * @return * The maximum payload size. 0 means that the maximum payload size * is not set. * * @since 1.27 */ public int getMaxPayloadSize() { return mMaxPayloadSize; } /** * Set the maximum payload size. * * <p> * Text, binary and continuation frames whose payload size is bigger than * the maximum payload size will be split into multiple frames. Note that * control frames (close, ping and pong frames) are not split as per the * specification even if their payload size exceeds the maximum payload size. * </p> * * @param size * The maximum payload size. 0 to unset the maximum payload size. * * @return * {@code this} object. * * @throws IllegalArgumentException * {@code size} is negative. * * @since 1.27 */ public WebSocket setMaxPayloadSize(int size) throws IllegalArgumentException { if (size < 0) { throw new IllegalArgumentException("size must not be negative."); } mMaxPayloadSize = size; return this; } /** * Get the interval of periodical * <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">ping</a> * frames. * * @return * The interval in milliseconds. * * @since 1.2 */ public long getPingInterval() { return mPingSender.getInterval(); } /** * Set the interval of periodical * <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">ping</a> * frames. * * <p> * Setting a positive number starts sending ping frames periodically. * Setting zero stops the periodical sending. This method can be called * both before and after {@link #connect()} method. * </p> * * @param interval * The interval in milliseconds. A negative value is * regarded as zero. * * @return * {@code this} object. * * @since 1.2 */ public WebSocket setPingInterval(long interval) { mPingSender.setInterval(interval); return this; } /** * Get the interval of periodical * <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pong</a> * frames. * * @return * The interval in milliseconds. * * @since 1.2 */ public long getPongInterval() { return mPongSender.getInterval(); } /** * Set the interval of periodical * <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pong</a> * frames. * * <p> * Setting a positive number starts sending pong frames periodically. * Setting zero stops the periodical sending. This method can be called * both before and after {@link #connect()} method. * </p> * * <blockquote> * <dl> * <dt> * <span style="font-weight: normal;">An excerpt from <a href= * "https://tools.ietf.org/html/rfc6455#section-5.5.3" * >RFC 6455, 5.5.3. Pong</a></span> * </dt> * <dd> * <p><i> * A Pong frame MAY be sent <b>unsolicited</b>. This serves as a * unidirectional heartbeat. A response to an unsolicited Pong * frame is not expected. * </i></p> * </dd> * </dl> * </blockquote> * * @param interval * The interval in milliseconds. A negative value is * regarded as zero. * * @return * {@code this} object. * * @since 1.2 */ public WebSocket setPongInterval(long interval) { mPongSender.setInterval(interval); return this; } /** * Get the generator of payload of ping frames that are sent automatically. * * @return * The generator of payload ping frames that are sent automatically. * * @since 1.20 */ public PayloadGenerator getPingPayloadGenerator() { return mPingSender.getPayloadGenerator(); } /** * Set the generator of payload of ping frames that are sent automatically. * * @param generator * The generator of payload ping frames that are sent automatically. * * @since 1.20 */ public WebSocket setPingPayloadGenerator(PayloadGenerator generator) { mPingSender.setPayloadGenerator(generator); return this; } /** * Get the generator of payload of pong frames that are sent automatically. * * @return * The generator of payload pong frames that are sent automatically. * * @since 1.20 */ public PayloadGenerator getPongPayloadGenerator() { return mPongSender.getPayloadGenerator(); } /** * Set the generator of payload of pong frames that are sent automatically. * * @param generator * The generator of payload ppng frames that are sent automatically. * * @since 1.20 */ public WebSocket setPongPayloadGenerator(PayloadGenerator generator) { mPongSender.setPayloadGenerator(generator); return this; } /** * Add a listener to receive events on this WebSocket. * * @param listener * A listener to add. * * @return * {@code this} object. */ public WebSocket addListener(WebSocketListener listener) { mListenerManager.addListener(listener); return this; } /** * Add listeners. * * @param listeners * Listeners to add. {@code null} is silently ignored. * {@code null} elements in the list are ignored, too. * * @return * {@code this} object. * * @since 1.14 */ public WebSocket addListeners(List<WebSocketListener> listeners) { mListenerManager.addListeners(listeners); return this; } /** * Remove a listener from this WebSocket. * * @param listener * A listener to remove. {@code null} won't cause an error. * * @return * {@code this} object. * * @since 1.13 */ public WebSocket removeListener(WebSocketListener listener) { mListenerManager.removeListener(listener); return this; } /** * Remove listeners. * * @param listeners * Listeners to remove. {@code null} is silently ignored. * {@code null} elements in the list are ignored, too. * * @return * {@code this} object. * * @since 1.14 */ public WebSocket removeListeners(List<WebSocketListener> listeners) { mListenerManager.removeListeners(listeners); return this; } /** * Remove all the listeners from this WebSocket. * * @return * {@code this} object. * * @since 1.13 */ public WebSocket clearListeners() { mListenerManager.clearListeners(); return this; } /** * Get the raw socket which this WebSocket uses internally. * * @return * The underlying {@link Socket} instance. */ public Socket getSocket() { return mSocketConnector.getSocket(); } /** * Get the URI of the WebSocket endpoint. The scheme part is either * {@code "ws"} or {@code "wss"}. The authority part is always empty. * * @return * The URI of the WebSocket endpoint. * * @since 1.1 */ public URI getURI() { return mHandshakeBuilder.getURI(); } /** * Connect to the server, send an opening handshake to the server, * receive the response and then start threads to communicate with * the server. * * <p> * As necessary, {@link #addProtocol(String)}, {@link #addExtension(WebSocketExtension)} * {@link #addHeader(String, String)} should be called before you call this * method. It is because the parameters set by these methods are used in the * opening handshake. * </p> * * <p> * Also, as necessary, {@link #getSocket()} should be used to set up socket * parameters before you call this method. For example, you can set the * socket timeout like the following. * </p> * * <pre> * WebSocket websocket = ......; * websocket.{@link #getSocket() getSocket()}.{@link Socket#setSoTimeout(int) * setSoTimeout}(5000); * </pre> * * <p> * If the WebSocket endpoint requires Basic Authentication, you can set * credentials by {@link #setUserInfo(String) setUserInfo(userInfo)} or * {@link #setUserInfo(String, String) setUserInfo(id, password)} before * you call this method. * Note that if the URI passed to {@link WebSocketFactory}{@code * .createSocket} method contains the user-info part, you don't have to * call {@code setUserInfo} method. * </p> * * <p> * Note that this method can be called at most only once regardless of * whether this method succeeded or failed. If you want to re-connect to * the WebSocket endpoint, you have to create a new {@code WebSocket} * instance again by calling one of {@code createSocket} methods of a * {@link WebSocketFactory}. You may find {@link #recreate()} method * useful if you want to create a new {@code WebSocket} instance that * has the same settings as this instance. (But settings you made on * the raw socket are not copied.) * </p> * * @return * {@code this} object. * * @throws WebSocketException * <ul> * <li>The current state of the WebSocket is not {@link * WebSocketState#CREATED CREATED} * <li>Connecting the server failed. * <li>The opening handshake failed. * </ul> */ public WebSocket connect() throws WebSocketException { // Change the state to CONNECTING. If the state before // the change is not CREATED, an exception is thrown. changeStateOnConnect(); // HTTP headers from the server. Map<String, List<String>> headers; try { // Connect to the server. mSocketConnector.connect(); // Perform WebSocket handshake. headers = shakeHands(); } catch (WebSocketException e) { // Close the socket. mSocketConnector.closeSilently(); // Change the state to CLOSED. mStateManager.setState(CLOSED); // Notify the listener of the state change. mListenerManager.callOnStateChanged(CLOSED); // The handshake failed. throw e; } // HTTP headers in the response from the server. mServerHeaders = headers; // Extensions. mPerMessageCompressionExtension = findAgreedPerMessageCompressionExtension(); // Change the state to OPEN. mStateManager.setState(OPEN); // Notify the listener of the state change. mListenerManager.callOnStateChanged(OPEN); // Start threads that communicate with the server. startThreads(); return this; } /** * Execute {@link #connect()} asynchronously using the given {@link * ExecutorService}. This method is just an alias of the following. * * <blockquote> * <code>executorService.{@link ExecutorService#submit(Callable) submit}({@link #connectable()})</code> * </blockquote> * * @param executorService * An {@link ExecutorService} to execute a task created by * {@link #connectable()}. * * @return * The value returned from {@link ExecutorService#submit(Callable)}. * * @throws NullPointerException * If the given {@link ExecutorService} is {@code null}. * * @throws RejectedExecutionException * If the given {@link ExecutorService} rejected the task * created by {@link #connectable()}. * * @see #connectAsynchronously() * * @since 1.7 */ public Future<WebSocket> connect(ExecutorService executorService) { return executorService.submit(connectable()); } /** * Get a new {@link Callable}{@code <}{@link WebSocket}{@code >} instance * whose {@link Callable#call() call()} method calls {@link #connect()} * method of this {@code WebSocket} instance. * * @return * A new {@link Callable}{@code <}{@link WebSocket}{@code >} instance * for asynchronous {@link #connect()}. * * @see #connect(ExecutorService) * * @since 1.7 */ public Callable<WebSocket> connectable() { return new Connectable(this); } /** * Execute {@link #connect()} asynchronously by creating a new thread and * calling {@code connect()} in the thread. If {@code connect()} failed, * {@link WebSocketListener#onConnectError(WebSocket, WebSocketException) * onConnectError()} method of {@link WebSocketListener} is called. * * @return * {@code this} object. * * @since 1.8 */ public WebSocket connectAsynchronously() { Thread thread = new ConnectThread(this); // Get the reference (just in case) ListenerManager lm = mListenerManager; if (lm != null) { lm.callOnThreadCreated(ThreadType.CONNECT_THREAD, thread); } thread.start(); return this; } /** * Disconnect the WebSocket. * * <p> * This method is an alias of {@link #disconnect(int, String) * disconnect}{@code (}{@link WebSocketCloseCode#NORMAL}{@code , null)}. * </p> * * @return * {@code this} object. */ public WebSocket disconnect() { return disconnect(WebSocketCloseCode.NORMAL, null); } /** * Disconnect the WebSocket. * * <p> * This method is an alias of {@link #disconnect(int, String) * disconnect}{@code (closeCode, null)}. * </p> * * @param closeCode * The close code embedded in a <a href= * "https://tools.ietf.org/html/rfc6455#section-5.5.1">close frame</a> * which this WebSocket client will send to the server. * * @return * {@code this} object. * * @since 1.5 */ public WebSocket disconnect(int closeCode) { return disconnect(closeCode, null); } /** * Disconnect the WebSocket. * * <p> * This method is an alias of {@link #disconnect(int, String) * disconnect}{@code (}{@link WebSocketCloseCode#NORMAL}{@code , reason)}. * </p> * * @param reason * The reason embedded in a <a href= * "https://tools.ietf.org/html/rfc6455#section-5.5.1">close frame</a> * which this WebSocket client will send to the server. Note that * the length of the bytes which represents the given reason must * not exceed 125. In other words, {@code (reason.}{@link * String#getBytes(String) getBytes}{@code ("UTF-8").length <= 125)} * must be true. * * @return * {@code this} object. * * @since 1.5 */ public WebSocket disconnect(String reason) { return disconnect(WebSocketCloseCode.NORMAL, reason); } /** * Disconnect the WebSocket. * * <p> * This method is an alias of {@link #disconnect(int, String, long) * disconnect}{@code (closeCode, reason, 10000L)}. * </p> * * @param closeCode * The close code embedded in a <a href= * "https://tools.ietf.org/html/rfc6455#section-5.5.1">close frame</a> * which this WebSocket client will send to the server. * * @param reason * The reason embedded in a <a href= * "https://tools.ietf.org/html/rfc6455#section-5.5.1">close frame</a> * which this WebSocket client will send to the server. Note that * the length of the bytes which represents the given reason must * not exceed 125. In other words, {@code (reason.}{@link * String#getBytes(String) getBytes}{@code ("UTF-8").length <= 125)} * must be true. * * @return * {@code this} object. * * @see WebSocketCloseCode * * @see <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">RFC 6455, 5.5.1. Close</a> * * @since 1.5 */ public WebSocket disconnect(int closeCode, String reason) { return disconnect(closeCode, reason, DEFAULT_CLOSE_DELAY); } /** * Disconnect the WebSocket. * * @param closeCode * The close code embedded in a <a href= * "https://tools.ietf.org/html/rfc6455#section-5.5.1">close frame</a> * which this WebSocket client will send to the server. * * @param reason * The reason embedded in a <a href= * "https://tools.ietf.org/html/rfc6455#section-5.5.1">close frame</a> * which this WebSocket client will send to the server. Note that * the length of the bytes which represents the given reason must * not exceed 125. In other words, {@code (reason.}{@link * String#getBytes(String) getBytes}{@code ("UTF-8").length <= 125)} * must be true. * * @param closeDelay * Delay in milliseconds before calling {@link Socket#close()} forcibly. * This safeguard is needed for the case where the server fails to send * back a close frame. The default value is 10000 (= 10 seconds). When * a negative value is given, the default value is used. * * If a very short time (e.g. 0) is given, it is likely to happen either * (1) that this client will fail to send a close frame to the server * (in this case, you will probably see an error message "Flushing frames * to the server failed: Socket closed") or (2) that the WebSocket * connection will be closed before this client receives a close frame * from the server (in this case, the second argument of {@link * WebSocketListener#onDisconnected(WebSocket, WebSocketFrame, * WebSocketFrame, boolean) WebSocketListener.onDisconnected} will be * {@code null}). * * @return * {@code this} object. * * @see WebSocketCloseCode * * @see <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">RFC 6455, 5.5.1. Close</a> * * @since 1.26 */ public WebSocket disconnect(int closeCode, String reason, long closeDelay) { synchronized (mStateManager) { switch (mStateManager.getState()) { case CREATED: finishAsynchronously(); return this; case OPEN: break; default: // - CONNECTING // It won't happen unless the programmer dare call // open() and disconnect() in parallel. // // - CLOSING // A closing handshake has already been started. // // - CLOSED // The connection has already been closed. return this; } // Change the state to CLOSING. mStateManager.changeToClosing(CloseInitiator.CLIENT); // Create a close frame. WebSocketFrame frame = WebSocketFrame.createCloseFrame(closeCode, reason); // Send the close frame to the server. sendFrame(frame); } // Notify the listeners of the state change. mListenerManager.callOnStateChanged(CLOSING); // If a negative value is given. if (closeDelay < 0) { // Use the default value. closeDelay = DEFAULT_CLOSE_DELAY; } // Request the threads to stop. stopThreads(closeDelay); return this; } /** * Get the agreed extensions. * * <p> * This method works correctly only after {@link #connect()} succeeds * (= after the opening handshake succeeds). * </p> * * @return * The agreed extensions. */ public List<WebSocketExtension> getAgreedExtensions() { return mAgreedExtensions; } /** * Get the agreed protocol. * * <p> * This method works correctly only after {@link #connect()} succeeds * (= after the opening handshake succeeds). * </p> * * @return * The agreed protocol. */ public String getAgreedProtocol() { return mAgreedProtocol; } /** * Send a WebSocket frame to the server. * * <p> * This method just queues the given frame. Actual transmission * is performed asynchronously. * </p> * * <p> * When the current state of this WebSocket is not {@link * WebSocketState#OPEN OPEN}, this method does not accept * the frame. * </p> * * <p> * Sending a <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1" * >close frame</a> changes the state to {@link WebSocketState#CLOSING * CLOSING} (if the current state is neither {@link WebSocketState#CLOSING * CLOSING} nor {@link WebSocketState#CLOSED CLOSED}). * </p> * * <p> * Note that the validity of the give frame is not checked. * For example, even if the payload length of a given frame * is greater than 125 and the opcode indicates that the * frame is a control frame, this method accepts the given * frame. * </p> * * @param frame * A WebSocket frame to be sent to the server. * If {@code null} is given, nothing is done. * * @return * {@code this} object. */ public WebSocket sendFrame(WebSocketFrame frame) { if (frame == null) { return this; } synchronized (mStateManager) { WebSocketState state = mStateManager.getState(); if (state != OPEN && state != CLOSING) { return this; } } // The current state is either OPEN or CLOSING. Or, CLOSED. // Get the reference to the writing thread. WritingThread wt = mWritingThread; // Some applications call sendFrame() without waiting for the // notification of WebSocketListener.onConnected() (Issue #23), // and/or even after the connection is closed. That is, there // are chances that sendFrame() is called when mWritingThread // is null. So, it should be checked whether an instance of // WritingThread is available or not before calling queueFrame(). if (wt == null) { // An instance of WritingThread is not available. return this; } // Split the frame into multiple frames if necessary. List<WebSocketFrame> frames = splitIfNecessary(frame); // Queue the frame or the frames. Even if the current state is // CLOSED, queueing won't be a big issue. // If the frame was not split. if (frames == null) { // Queue the frame. wt.queueFrame(frame); } else { for (WebSocketFrame f : frames) { // Queue the frame. wt.queueFrame(f); } } return this; } private List<WebSocketFrame> splitIfNecessary(WebSocketFrame frame) { return WebSocketFrame.splitIfNecessary(frame, mMaxPayloadSize, mPerMessageCompressionExtension); } /** * Send a continuation frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createContinuationFrame() * createContinuationFrame()}{@code )}. * </p> * * <p> * Note that the FIN bit of a frame sent by this method is {@code false}. * If you want to set the FIN bit, use {@link #sendContinuation(boolean) * sendContinuation(boolean fin)} with {@code fin=true}. * </p> * * @return * {@code this} object. */ public WebSocket sendContinuation() { return sendFrame(WebSocketFrame.createContinuationFrame()); } /** * Send a continuation frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createContinuationFrame() * createContinuationFrame()}{@code .}{@link * WebSocketFrame#setFin(boolean) setFin}{@code (fin))}. * </p> * * @param fin * The FIN bit value. * * @return * {@code this} object. */ public WebSocket sendContinuation(boolean fin) { return sendFrame(WebSocketFrame.createContinuationFrame().setFin(fin)); } /** * Send a continuation frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createContinuationFrame(String) * createContinuationFrame}{@code (payload))}. * </p> * * <p> * Note that the FIN bit of a frame sent by this method is {@code false}. * If you want to set the FIN bit, use {@link #sendContinuation(String, * boolean) sendContinuation(String payload, boolean fin)} with {@code * fin=true}. * </p> * * @param payload * The payload of a continuation frame. * * @return * {@code this} object. */ public WebSocket sendContinuation(String payload) { return sendFrame(WebSocketFrame.createContinuationFrame(payload)); } /** * Send a continuation frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createContinuationFrame(String) * createContinuationFrame}{@code (payload).}{@link * WebSocketFrame#setFin(boolean) setFin}{@code (fin))}. * </p> * * @param payload * The payload of a continuation frame. * * @param fin * The FIN bit value. * * @return * {@code this} object. */ public WebSocket sendContinuation(String payload, boolean fin) { return sendFrame(WebSocketFrame.createContinuationFrame(payload).setFin(fin)); } /** * Send a continuation frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createContinuationFrame(byte[]) * createContinuationFrame}{@code (payload))}. * </p> * * <p> * Note that the FIN bit of a frame sent by this method is {@code false}. * If you want to set the FIN bit, use {@link #sendContinuation(byte[], * boolean) sendContinuation(byte[] payload, boolean fin)} with {@code * fin=true}. * </p> * * @param payload * The payload of a continuation frame. * * @return * {@code this} object. */ public WebSocket sendContinuation(byte[] payload) { return sendFrame(WebSocketFrame.createContinuationFrame(payload)); } /** * Send a continuation frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createContinuationFrame(byte[]) * createContinuationFrame}{@code (payload).}{@link * WebSocketFrame#setFin(boolean) setFin}{@code (fin))}. * </p> * * @param payload * The payload of a continuation frame. * * @param fin * The FIN bit value. * * @return * {@code this} object. */ public WebSocket sendContinuation(byte[] payload, boolean fin) { return sendFrame(WebSocketFrame.createContinuationFrame(payload).setFin(fin)); } /** * Send a text message to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createTextFrame(String) * createTextFrame}{@code (message))}. * </p> * * <p> * If you want to send a text frame that is to be followed by * continuation frames, use {@link #sendText(String, boolean) * setText(String payload, boolean fin)} with {@code fin=false}. * </p> * * @param message * A text message to be sent to the server. * * @return * {@code this} object. */ public WebSocket sendText(String message) { return sendFrame(WebSocketFrame.createTextFrame(message)); } /** * Send a text frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createTextFrame(String) * createTextFrame}{@code (payload).}{@link * WebSocketFrame#setFin(boolean) setFin}{@code (fin))}. * </p> * * @param payload * The payload of a text frame. * * @param fin * The FIN bit value. * * @return * {@code this} object. */ public WebSocket sendText(String payload, boolean fin) { return sendFrame(WebSocketFrame.createTextFrame(payload).setFin(fin)); } /** * Send a binary message to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createBinaryFrame(byte[]) * createBinaryFrame}{@code (message))}. * </p> * * <p> * If you want to send a binary frame that is to be followed by * continuation frames, use {@link #sendBinary(byte[], boolean) * setBinary(byte[] payload, boolean fin)} with {@code fin=false}. * </p> * * @param message * A binary message to be sent to the server. * * @return * {@code this} object. */ public WebSocket sendBinary(byte[] message) { return sendFrame(WebSocketFrame.createBinaryFrame(message)); } /** * Send a binary frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createBinaryFrame(byte[]) * createBinaryFrame}{@code (payload).}{@link * WebSocketFrame#setFin(boolean) setFin}{@code (fin))}. * </p> * * @param payload * The payload of a binary frame. * * @param fin * The FIN bit value. * * @return * {@code this} object. */ public WebSocket sendBinary(byte[] payload, boolean fin) { return sendFrame(WebSocketFrame.createBinaryFrame(payload).setFin(fin)); } /** * Send a close frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createCloseFrame() createCloseFrame()}). * </p> * * @return * {@code this} object. */ public WebSocket sendClose() { return sendFrame(WebSocketFrame.createCloseFrame()); } /** * Send a close frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createCloseFrame(int) * createCloseFrame}{@code (closeCode))}. * </p> * * @param closeCode * The close code. * * @return * {@code this} object. * * @see WebSocketCloseCode */ public WebSocket sendClose(int closeCode) { return sendFrame(WebSocketFrame.createCloseFrame(closeCode)); } /** * Send a close frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createCloseFrame(int, String) * createCloseFrame}{@code (closeCode, reason))}. * </p> * * @param closeCode * The close code. * * @param reason * The close reason. * Note that a control frame's payload length must be 125 bytes or less * (RFC 6455, <a href="https://tools.ietf.org/html/rfc6455#section-5.5" * >5.5. Control Frames</a>). * * @return * {@code this} object. * * @see WebSocketCloseCode */ public WebSocket sendClose(int closeCode, String reason) { return sendFrame(WebSocketFrame.createCloseFrame(closeCode, reason)); } /** * Send a ping frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createPingFrame() createPingFrame()}). * </p> * * @return * {@code this} object. */ public WebSocket sendPing() { return sendFrame(WebSocketFrame.createPingFrame()); } /** * Send a ping frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createPingFrame(byte[]) * createPingFrame}{@code (payload))}. * </p> * * @param payload * The payload for a ping frame. * Note that a control frame's payload length must be 125 bytes or less * (RFC 6455, <a href="https://tools.ietf.org/html/rfc6455#section-5.5" * >5.5. Control Frames</a>). * * @return * {@code this} object. */ public WebSocket sendPing(byte[] payload) { return sendFrame(WebSocketFrame.createPingFrame(payload)); } /** * Send a ping frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createPingFrame(String) * createPingFrame}{@code (payload))}. * </p> * * @param payload * The payload for a ping frame. * Note that a control frame's payload length must be 125 bytes or less * (RFC 6455, <a href="https://tools.ietf.org/html/rfc6455#section-5.5" * >5.5. Control Frames</a>). * * @return * {@code this} object. */ public WebSocket sendPing(String payload) { return sendFrame(WebSocketFrame.createPingFrame(payload)); } /** * Send a pong frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createPongFrame() createPongFrame()}). * </p> * * @return * {@code this} object. */ public WebSocket sendPong() { return sendFrame(WebSocketFrame.createPongFrame()); } /** * Send a pong frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createPongFrame(byte[]) * createPongFrame}{@code (payload))}. * </p> * * @param payload * The payload for a pong frame. * Note that a control frame's payload length must be 125 bytes or less * (RFC 6455, <a href="https://tools.ietf.org/html/rfc6455#section-5.5" * >5.5. Control Frames</a>). * * @return * {@code this} object. */ public WebSocket sendPong(byte[] payload) { return sendFrame(WebSocketFrame.createPongFrame(payload)); } /** * Send a pong frame to the server. * * <p> * This method is an alias of {@link #sendFrame(WebSocketFrame) * sendFrame}{@code (WebSocketFrame.}{@link * WebSocketFrame#createPongFrame(String) * createPongFrame}{@code (payload))}. * </p> * * @param payload * The payload for a pong frame. * Note that a control frame's payload length must be 125 bytes or less * (RFC 6455, <a href="https://tools.ietf.org/html/rfc6455#section-5.5" * >5.5. Control Frames</a>). * * @return * {@code this} object. */ public WebSocket sendPong(String payload) { return sendFrame(WebSocketFrame.createPongFrame(payload)); } private void changeStateOnConnect() throws WebSocketException { synchronized (mStateManager) { // If the current state is not CREATED. if (mStateManager.getState() != CREATED) { throw new WebSocketException( WebSocketError.NOT_IN_CREATED_STATE, "The current state of the WebSocket is not CREATED."); } // Change the state to CONNECTING. mStateManager.setState(CONNECTING); } // Notify the listeners of the state change. mListenerManager.callOnStateChanged(CONNECTING); } /** * Perform the opening handshake. */ private Map<String, List<String>> shakeHands() throws WebSocketException { // The raw socket created by WebSocketFactory. Socket socket = mSocketConnector.getSocket(); // Get the input stream of the socket. WebSocketInputStream input = openInputStream(socket); // Get the output stream of the socket. WebSocketOutputStream output = openOutputStream(socket); // Generate a value for Sec-WebSocket-Key. String key = generateWebSocketKey(); // Send an opening handshake to the server. writeHandshake(output, key); // Read the response from the server. Map<String, List<String>> headers = readHandshake(input, key); // Keep the input stream and the output stream to pass them // to the reading thread and the writing thread later. mInput = input; mOutput = output; // The handshake succeeded. return headers; } /** * Open the input stream of the WebSocket connection. * The stream is used by the reading thread. */ private WebSocketInputStream openInputStream(Socket socket) throws WebSocketException { try { // Get the input stream of the raw socket through which // this client receives data from the server. return new WebSocketInputStream( new BufferedInputStream(socket.getInputStream())); } catch (IOException e) { // Failed to get the input stream of the raw socket. throw new WebSocketException( WebSocketError.SOCKET_INPUT_STREAM_FAILURE, "Failed to get the input stream of the raw socket: " + e.getMessage(), e); } } /** * Open the output stream of the WebSocket connection. * The stream is used by the writing thread. */ private WebSocketOutputStream openOutputStream(Socket socket) throws WebSocketException { try { // Get the output stream of the socket through which // this client sends data to the server. return new WebSocketOutputStream( new BufferedOutputStream(socket.getOutputStream())); } catch (IOException e) { // Failed to get the output stream from the raw socket. throw new WebSocketException( WebSocketError.SOCKET_OUTPUT_STREAM_FAILURE, "Failed to get the output stream from the raw socket: " + e.getMessage(), e); } } /** * Generate a value for Sec-WebSocket-Key. * * <blockquote> * <p><i> * The request MUST include a header field with the name Sec-WebSocket-Key. * The value of this header field MUST be a nonce consisting of a randomly * selected 16-byte value that has been base64-encoded (see Section 4 of * RFC 4648). The nonce MUST be selected randomly for each connection. * </i></p> * </blockquote> * * @return * A randomly generated WebSocket key. */ private static String generateWebSocketKey() { // "16-byte value" byte[] data = new byte[16]; // "randomly selected" Misc.nextBytes(data); // "base64-encoded" return Base64.encode(data); } /** * Send an opening handshake request to the WebSocket server. */ private void writeHandshake(WebSocketOutputStream output, String key) throws WebSocketException { // Generate an opening handshake sent to the server from this client. mHandshakeBuilder.setKey(key); String requestLine = mHandshakeBuilder.buildRequestLine(); List<String[]> headers = mHandshakeBuilder.buildHeaders(); String handshake = HandshakeBuilder.build(requestLine, headers); // Call onSendingHandshake() method of listeners. mListenerManager.callOnSendingHandshake(requestLine, headers); try { // Send the opening handshake to the server. output.write(handshake); output.flush(); } catch (IOException e) { // Failed to send an opening handshake request to the server. throw new WebSocketException( WebSocketError.OPENING_HAHDSHAKE_REQUEST_FAILURE, "Failed to send an opening handshake request to the server: " + e.getMessage(), e); } } /** * Receive an opening handshake response from the WebSocket server. */ private Map<String, List<String>> readHandshake(WebSocketInputStream input, String key) throws WebSocketException { return new HandshakeReader(this).readHandshake(input, key); } /** * Start both the reading thread and the writing thread. * * <p> * The reading thread will call {@link #onReadingThreadStarted()} * as its first step. Likewise, the writing thread will call * {@link #onWritingThreadStarted()} as its first step. After * both the threads have started, {@link #onThreadsStarted()} is * called. * </p> */ private void startThreads() { ReadingThread readingThread = new ReadingThread(this); WritingThread writingThread = new WritingThread(this); synchronized (mThreadsLock) { mReadingThread = readingThread; mWritingThread = writingThread; } // Execute onThreadCreated of the listeners. readingThread.callOnThreadCreated(); writingThread.callOnThreadCreated(); readingThread.start(); writingThread.start(); } /** * Stop both the reading thread and the writing thread. * * <p> * The reading thread will call {@link #onReadingThreadFinished(WebSocketFrame)} * as its last step. Likewise, the writing thread will call {@link * #onWritingThreadFinished(WebSocketFrame)} as its last step. * After both the threads have stopped, {@link #onThreadsFinished()} * is called. * </p> */ private void stopThreads(long closeDelay) { ReadingThread readingThread; WritingThread writingThread; synchronized (mThreadsLock) { readingThread = mReadingThread; writingThread = mWritingThread; mReadingThread = null; mWritingThread = null; } if (readingThread != null) { readingThread.requestStop(closeDelay); } if (writingThread != null) { writingThread.requestStop(); } } /** * Get the input stream of the WebSocket connection. */ WebSocketInputStream getInput() { return mInput; } /** * Get the output stream of the WebSocket connection. */ WebSocketOutputStream getOutput() { return mOutput; } /** * Get the manager that manages the state of this {@code WebSocket} instance. */ StateManager getStateManager() { return mStateManager; } /** * Get the manager that manages registered listeners. */ ListenerManager getListenerManager() { return mListenerManager; } /** * Get the handshake builder. {@link HandshakeReader} uses this method. */ HandshakeBuilder getHandshakeBuilder() { return mHandshakeBuilder; } /** * Set the agreed extensions. {@link HandshakeReader} uses this method. */ void setAgreedExtensions(List<WebSocketExtension> extensions) { mAgreedExtensions = extensions; } /** * Set the agreed protocol. {@link HandshakeReader} uses this method. */ void setAgreedProtocol(String protocol) { mAgreedProtocol = protocol; } /** * Called by the reading thread as its first step. */ void onReadingThreadStarted() { boolean bothStarted = false; synchronized (mThreadsLock) { mReadingThreadStarted = true; if (mWritingThreadStarted) { // Both the reading thread and the writing thread have started. bothStarted = true; } } // Call onConnected() method of listeners if not called yet. callOnConnectedIfNotYet(); // If both the reading thread and the writing thread have started. if (bothStarted) { onThreadsStarted(); } } /** * Called by the writing thread as its first step. */ void onWritingThreadStarted() { boolean bothStarted = false; synchronized (mThreadsLock) { mWritingThreadStarted = true; if (mReadingThreadStarted) { // Both the reading thread and the writing thread have started. bothStarted = true; } } // Call onConnected() method of listeners if not called yet. callOnConnectedIfNotYet(); // If both the reading thread and the writing thread have started. if (bothStarted) { onThreadsStarted(); } } /** * Call {@link WebSocketListener#onConnected(WebSocket, Map)} method * of the registered listeners if it has not been called yet. Either * the reading thread or the writing thread calls this method. */ private void callOnConnectedIfNotYet() { synchronized (mOnConnectedCalledLock) { // If onConnected() has already been called. if (mOnConnectedCalled) { // Do not call onConnected() twice. return; } mOnConnectedCalled = true; } // Notify the listeners that the handshake succeeded. mListenerManager.callOnConnected(mServerHeaders); } /** * Called when both the reading thread and the writing thread have started. * This method is called in the context of either the reading thread or * the writing thread. */ private void onThreadsStarted() { // Start sending ping frames periodically. // If the interval is zero, this call does nothing. mPingSender.start(); // Likewise, start the pong sender. mPongSender.start(); } /** * Called by the reading thread as its last step. */ void onReadingThreadFinished(WebSocketFrame closeFrame) { synchronized (mThreadsLock) { mReadingThreadFinished = true; mServerCloseFrame = closeFrame; if (mWritingThreadFinished == false) { // Wait for the writing thread to finish. return; } } // Both the reading thread and the writing thread have finished. onThreadsFinished(); } /** * Called by the writing thread as its last step. */ void onWritingThreadFinished(WebSocketFrame closeFrame) { synchronized (mThreadsLock) { mWritingThreadFinished = true; mClientCloseFrame = closeFrame; if (mReadingThreadFinished == false) { // Wait for the reading thread to finish. return; } } // Both the reading thread and the writing thread have finished. onThreadsFinished(); } /** * Called when both the reading thread and the writing thread have finished. * This method is called in the context of either the reading thread or * the writing thread. */ private void onThreadsFinished() { finish(); } void finish() { // Stop the ping sender and the pong sender. mPingSender.stop(); mPongSender.stop(); try { // Close the raw socket. mSocketConnector.getSocket().close(); } catch (Throwable t) { // Ignore any error raised by close(). } synchronized (mStateManager) { // Change the state to CLOSED. mStateManager.setState(CLOSED); } // Notify the listeners of the state change. mListenerManager.callOnStateChanged(CLOSED); // Notify the listeners that the WebSocket was disconnected. mListenerManager.callOnDisconnected( mServerCloseFrame, mClientCloseFrame, mStateManager.getClosedByServer()); } /** * Call {@link #finish()} from within a separate thread. */ private void finishAsynchronously() { WebSocketThread thread = new FinishThread(this); // Execute onThreadCreated() of the listeners. thread.callOnThreadCreated(); thread.start(); } /** * Find a per-message compression extension from among the agreed extensions. */ private PerMessageCompressionExtension findAgreedPerMessageCompressionExtension() { if (mAgreedExtensions == null) { return null; } for (WebSocketExtension extension : mAgreedExtensions) { if (extension instanceof PerMessageCompressionExtension) { return (PerMessageCompressionExtension)extension; } } return null; } /** * Get the PerMessageCompressionExtension in the agreed extensions. * This method returns null if a per-message compression extension * is not found in the agreed extensions. */ PerMessageCompressionExtension getPerMessageCompressionExtension() { return mPerMessageCompressionExtension; } }