com.neovisionaries.ws.client.ReadingThread Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nv-websocket-client Show documentation
Show all versions of nv-websocket-client Show documentation
WebSocket client implementation in Java.
/*
* 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.WebSocketOpcode.BINARY;
import static com.neovisionaries.ws.client.WebSocketOpcode.CLOSE;
import static com.neovisionaries.ws.client.WebSocketOpcode.CONTINUATION;
import static com.neovisionaries.ws.client.WebSocketOpcode.PING;
import static com.neovisionaries.ws.client.WebSocketOpcode.PONG;
import static com.neovisionaries.ws.client.WebSocketOpcode.TEXT;
import static com.neovisionaries.ws.client.WebSocketState.CLOSED;
import static com.neovisionaries.ws.client.WebSocketState.CLOSING;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import com.neovisionaries.ws.client.StateManager.CloseInitiator;
class ReadingThread extends WebSocketThread
{
private boolean mStopRequested;
private WebSocketFrame mCloseFrame;
private List mContinuation = new ArrayList();
private final PerMessageCompressionExtension mPMCE;
private Object mCloseLock = new Object();
private Timer mCloseTimer;
private CloseTask mCloseTask;
private long mCloseDelay;
private boolean mNotWaitForCloseFrame;
public ReadingThread(WebSocket websocket)
{
super("ReadingThread", websocket, ThreadType.READING_THREAD);
mPMCE = websocket.getPerMessageCompressionExtension();
}
@Override
public void runMain()
{
try
{
main();
}
catch (Throwable t)
{
// An uncaught throwable was detected in the reading thread.
WebSocketException cause = new WebSocketException(
WebSocketError.UNEXPECTED_ERROR_IN_READING_THREAD,
"An uncaught throwable was detected in the reading thread: " + t.getMessage(), t);
// Notify the listeners.
ListenerManager manager = mWebSocket.getListenerManager();
manager.callOnError(cause);
manager.callOnUnexpectedError(cause);
}
// Notify this reading thread finished.
notifyFinished();
}
private void main()
{
mWebSocket.onReadingThreadStarted();
while (true)
{
synchronized (this)
{
if (mStopRequested)
{
break;
}
}
// Receive a frame from the server.
WebSocketFrame frame = readFrame();
if (frame == null)
{
// Something unexpected happened.
break;
}
// Handle the frame.
boolean keepReading = handleFrame(frame);
if (keepReading == false)
{
break;
}
}
// Wait for a close frame if one has not been received yet.
waitForCloseFrame();
// Cancel a task which calls Socket.close() if running.
cancelClose();
}
void requestStop(long closeDelay)
{
synchronized (this)
{
if (mStopRequested)
{
return;
}
mStopRequested = true;
}
// interrupt() may not interrupt a blocking socket read(), so calling
// interrupt() here may not work. interrupt() in Java is different
// from signal-based interruption in C which unblocks a read() system
// call. Anyway, let's mark this thread as interrupted.
interrupt();
// To surely unblock a read() call, Socket.close() needs to be called.
// Or, shutdownInterrupt() may work, but it is not explicitly stated
// in the JavaDoc. In either case, interruption should not be executed
// now because a close frame from the server should be waited for.
//
// So, let's schedule a task with some delay which calls Socket.close().
// However, in normal cases, a close frame will arrive from the server
// before the task calls Socket.close().
mCloseDelay = closeDelay;
scheduleClose();
}
/**
* Call {@link WebSocketListener#onFrame(WebSocket, WebSocketFrame) onFrame}
* method of the listeners.
*/
private void callOnFrame(WebSocketFrame frame)
{
mWebSocket.getListenerManager().callOnFrame(frame);
}
/**
* Call {@link WebSocketListener#onContinuationFrame(WebSocket, WebSocketFrame)
* onContinuationFrame} method of the listeners.
*/
private void callOnContinuationFrame(WebSocketFrame frame)
{
mWebSocket.getListenerManager().callOnContinuationFrame(frame);
}
/**
* Call {@link WebSocketListener#onTextFrame(WebSocket, WebSocketFrame)
* onTextFrame} method of the listeners.
*/
private void callOnTextFrame(WebSocketFrame frame)
{
mWebSocket.getListenerManager().callOnTextFrame(frame);
}
/**
* Call {@link WebSocketListener#onBinaryFrame(WebSocket, WebSocketFrame)
* onBinaryFrame} method of the listeners.
*/
private void callOnBinaryFrame(WebSocketFrame frame)
{
mWebSocket.getListenerManager().callOnBinaryFrame(frame);
}
/**
* Call {@link WebSocketListener#onCloseFrame(WebSocket, WebSocketFrame)
* onCloseFrame} method of the listeners.
*/
private void callOnCloseFrame(WebSocketFrame frame)
{
mWebSocket.getListenerManager().callOnCloseFrame(frame);
}
/**
* Call {@link WebSocketListener#onPingFrame(WebSocket, WebSocketFrame)
* onPingFrame} method of the listeners.
*/
private void callOnPingFrame(WebSocketFrame frame)
{
mWebSocket.getListenerManager().callOnPingFrame(frame);
}
/**
* Call {@link WebSocketListener#onPongFrame(WebSocket, WebSocketFrame)
* onPongFrame} method of the listeners.
*/
private void callOnPongFrame(WebSocketFrame frame)
{
mWebSocket.getListenerManager().callOnPongFrame(frame);
}
/**
* Call {@link WebSocketListener#onTextMessage(WebSocket, String)
* onTextMessage} method of the listeners.
*/
private void callOnTextMessage(byte[] data)
{
if (mWebSocket.isDirectTextMessage())
{
mWebSocket.getListenerManager().callOnTextMessage(data);
return;
}
try
{
// Interpret the byte array as a string.
// OutOfMemoryError may happen when the size of data is too big.
String message = Misc.toStringUTF8(data);
// Call onTextMessage() method of the listeners.
callOnTextMessage(message);
}
catch (Throwable t)
{
// Failed to convert payload data into a string.
WebSocketException wse = new WebSocketException(
WebSocketError.TEXT_MESSAGE_CONSTRUCTION_ERROR,
"Failed to convert payload data into a string: " + t.getMessage(), t);
// Notify the listeners that text message construction failed.
callOnError(wse);
callOnTextMessageError(wse, data);
}
}
/**
* Call {@link WebSocketListener#onTextMessage(WebSocket, String)
* onTextMessage} method of the listeners.
*/
private void callOnTextMessage(String message)
{
mWebSocket.getListenerManager().callOnTextMessage(message);
}
/**
* Call {@link WebSocketListener#onBinaryMessage(WebSocket, String)
* onBinaryMessage} method of the listeners.
*/
private void callOnBinaryMessage(byte[] message)
{
mWebSocket.getListenerManager().callOnBinaryMessage(message);
}
/**
* Call {@link WebSocketListener#onError(WebSocket, WebSocketException)
* onError} method of the listeners.
*/
private void callOnError(WebSocketException cause)
{
mWebSocket.getListenerManager().callOnError(cause);
}
/**
* Call {@link WebSocketListener#onFrameError(WebSocket,
* WebSocketException, WebSocketFrame) onFrameError} method of the listeners.
*/
private void callOnFrameError(WebSocketException cause, WebSocketFrame frame)
{
mWebSocket.getListenerManager().callOnFrameError(cause, frame);
}
/**
* Call {@link WebSocketListener#onMessageError(WebSocket, WebSocketException, List)
* onMessageError} method of the listeners.
*/
private void callOnMessageError(WebSocketException cause, List frames)
{
mWebSocket.getListenerManager().callOnMessageError(cause, frames);
}
/**
* Call {@link WebSocketListener#onMessageDecompressionError(WebSocket, WebSocketException, byte[])
* onMessageDecompressionError} method of the listeners.
*/
private void callOnMessageDecompressionError(WebSocketException cause, byte[] compressed)
{
mWebSocket.getListenerManager().callOnMessageDecompressionError(cause, compressed);
}
/**
* Call {@link WebSocketListener#onTextMessageError(WebSocket, WebSocketException, byte[])
* onTextMessageError} method of the listeners.
*/
private void callOnTextMessageError(WebSocketException cause, byte[] data)
{
mWebSocket.getListenerManager().callOnTextMessageError(cause, data);
}
private WebSocketFrame readFrame()
{
WebSocketFrame frame = null;
WebSocketException wse = null;
try
{
// Receive a frame from the server.
frame = mWebSocket.getInput().readFrame();
// Verify the frame. If invalid, WebSocketException is thrown.
verifyFrame(frame);
// Return the verified frame.
return frame;
}
catch (InterruptedIOException e)
{
if (mStopRequested)
{
// Thread.interrupt() interrupted a blocking socket read operation.
// This thread has been interrupted intentionally.
return null;
}
else
{
// Interruption occurred while a frame was being read from the web socket.
wse = new WebSocketException(
WebSocketError.INTERRUPTED_IN_READING,
"Interruption occurred while a frame was being read from the web socket: " + e.getMessage(), e);
}
}
catch (IOException e)
{
if (mStopRequested && isInterrupted())
{
// Socket.close() interrupted a blocking socket read operation.
// This thread has been interrupted intentionally.
return null;
}
else
{
// An I/O error occurred while a frame was being read from the web socket.
wse = new WebSocketException(
WebSocketError.IO_ERROR_IN_READING,
"An I/O error occurred while a frame was being read from the web socket: " + e.getMessage(), e);
}
}
catch (WebSocketException e)
{
// A protocol error.
wse = e;
}
boolean error = true;
// If the input stream of the WebSocket connection has reached the end
// without receiving a close frame from the server.
if (wse instanceof NoMoreFrameException)
{
// Not wait for a close frame in waitForCloseFrame() which will be called later.
mNotWaitForCloseFrame = true;
// If the configuration of the WebSocket instance allows the behavior.
if (mWebSocket.isMissingCloseFrameAllowed())
{
error = false;
}
}
if (error)
{
// Notify the listeners that an error occurred while a frame was being read.
callOnError(wse);
callOnFrameError(wse, frame);
}
// Create a close frame.
WebSocketFrame closeFrame = createCloseFrame(wse);
// Send the close frame.
mWebSocket.sendFrame(closeFrame);
// No WebSocket frame is available.
return null;
}
private void verifyFrame(WebSocketFrame frame) throws WebSocketException
{
// Verify RSV1, RSV2 and RSV3.
verifyReservedBits(frame);
// The opcode of the frame must be known.
verifyFrameOpcode(frame);
// Frames from the server must not be masked.
verifyFrameMask(frame);
// Verify fragmentation conditions.
verifyFrameFragmentation(frame);
// Verify the size of the payload.
verifyFrameSize(frame);
}
private void verifyReservedBits(WebSocketFrame frame) throws WebSocketException
{
// If extended use of web socket frames is allowed.
if (mWebSocket.isExtended())
{
// Do not check RSV1/RSV2/RSV3 bits.
return;
}
// RSV1, RSV2, RSV3
//
// The specification requires that these bits "be 0 unless an extension
// is negotiated that defines meanings for non-zero values".
verifyReservedBit1(frame);
verifyReservedBit2(frame);
verifyReservedBit3(frame);
}
/**
* Verify the RSV1 bit of a frame.
*/
private void verifyReservedBit1(WebSocketFrame frame) throws WebSocketException
{
// If a per-message compression extension has been agreed.
if (mPMCE != null)
{
// Verify the RSV1 bit using the rule described in RFC 7692.
boolean verified = verifyReservedBit1ForPMCE(frame);
if (verified)
{
return;
}
}
if (frame.getRsv1() == false)
{
// No problem.
return;
}
// The RSV1 bit of a frame is set unexpectedly.
throw new WebSocketException(
WebSocketError.UNEXPECTED_RESERVED_BIT, "The RSV1 bit of a frame is set unexpectedly.");
}
/**
* Verify the RSV1 bit of a frame using the rule described in RFC 7692.
* See 6. Framing
* in RFC 7692 for details.
*/
private boolean verifyReservedBit1ForPMCE(WebSocketFrame frame) throws WebSocketException
{
if (frame.isTextFrame() || frame.isBinaryFrame())
{
// The RSV1 of the first frame of a message is called
// "Per-Message Compressed" bit. It can be either 0 or 1.
// In other words, any value is okay.
return true;
}
// Further checking is required.
return false;
}
/**
* Verify the RSV2 bit of a frame.
*/
private void verifyReservedBit2(WebSocketFrame frame) throws WebSocketException
{
if (frame.getRsv2() == false)
{
// No problem.
return;
}
// The RSV2 bit of a frame is set unexpectedly.
throw new WebSocketException(
WebSocketError.UNEXPECTED_RESERVED_BIT, "The RSV2 bit of a frame is set unexpectedly.");
}
/**
* Verify the RSV3 bit of a frame.
*/
private void verifyReservedBit3(WebSocketFrame frame) throws WebSocketException
{
if (frame.getRsv3() == false)
{
// No problem.
return;
}
// The RSV3 bit of a frame is set unexpectedly.
throw new WebSocketException(
WebSocketError.UNEXPECTED_RESERVED_BIT, "The RSV3 bit of a frame is set unexpectedly.");
}
/**
* Ensure that the opcode of the give frame is a known one.
*
*
* From RFC 6455, 5.2. Base Framing Protocol
*
* If an unknown opcode is received, the receiving endpoint MUST
* Fail the WebSocket Connection.
*
*
*/
private void verifyFrameOpcode(WebSocketFrame frame) throws WebSocketException
{
switch (frame.getOpcode())
{
case CONTINUATION:
case TEXT:
case BINARY:
case CLOSE:
case PING:
case PONG:
// Known opcode
return;
default:
break;
}
// If extended use of web socket frames is allowed.
if (mWebSocket.isExtended())
{
// Allow the unknown opcode.
return;
}
// A frame has an unknown opcode.
throw new WebSocketException(
WebSocketError.UNKNOWN_OPCODE,
"A frame has an unknown opcode: 0x" + Integer.toHexString(frame.getOpcode()));
}
/**
* Ensure that the given frame is not masked.
*
*
* From RFC 6455, 5.1. Overview:
*
* A server MUST NOT mask any frames that it sends to the client.
* A client MUST close a connection if it detects a masked frame.
*
*
*/
private void verifyFrameMask(WebSocketFrame frame) throws WebSocketException
{
// If the frame is masked.
if (frame.getMask())
{
// A frame from the server is masked.
throw new WebSocketException(
WebSocketError.FRAME_MASKED,
"A frame from the server is masked.");
}
}
private void verifyFrameFragmentation(WebSocketFrame frame) throws WebSocketException
{
// Control frames (see Section 5.5) MAY be injected in the
// middle of a fragmented message. Control frames themselves
// MUST NOT be fragmented.
if (frame.isControlFrame())
{
// If fragmented.
if (frame.getFin() == false)
{
// A control frame is fragmented.
throw new WebSocketException(
WebSocketError.FRAGMENTED_CONTROL_FRAME,
"A control frame is fragmented.");
}
// No more requirements on a control frame.
return;
}
// True if a continuation has already started.
boolean continuationExists = (mContinuation.size() != 0);
// If the frame is a continuation frame.
if (frame.isContinuationFrame())
{
// There must already exist a continuation sequence.
if (continuationExists == false)
{
// A continuation frame was detected although a continuation had not started.
throw new WebSocketException(
WebSocketError.UNEXPECTED_CONTINUATION_FRAME,
"A continuation frame was detected although a continuation had not started.");
}
// No more requirements on a continuation frame.
return;
}
// A data frame.
if (continuationExists)
{
// A non-control frame was detected although the existing continuation had not been closed.
throw new WebSocketException(
WebSocketError.CONTINUATION_NOT_CLOSED,
"A non-control frame was detected although the existing continuation had not been closed.");
}
}
private void verifyFrameSize(WebSocketFrame frame) throws WebSocketException
{
// If the frame is not a control frame.
if (frame.isControlFrame() == false)
{
// Nothing to check.
return;
}
// RFC 6455, 5.5. Control Frames.
//
// All control frames MUST have a payload length of 125 bytes or less
// and MUST NOT be fragmented.
//
byte[] payload = frame.getPayload();
if (payload == null)
{
// The frame does not have payload.
return;
}
if (125 < payload.length)
{
// The payload size of a control frame exceeds the maximum size (125 bytes).
throw new WebSocketException(
WebSocketError.TOO_LONG_CONTROL_FRAME_PAYLOAD,
"The payload size of a control frame exceeds the maximum size (125 bytes): " + payload.length);
}
}
private WebSocketFrame createCloseFrame(WebSocketException wse)
{
int closeCode;
switch (wse.getError())
{
// In WebSocketInputStream.readFrame()
case INSUFFICENT_DATA:
case INVALID_PAYLOAD_LENGTH:
case NO_MORE_FRAME:
closeCode = WebSocketCloseCode.UNCONFORMED;
break;
case TOO_LONG_PAYLOAD:
case INSUFFICIENT_MEMORY_FOR_PAYLOAD:
closeCode = WebSocketCloseCode.OVERSIZE;
break;
// In this.verifyFrame(WebSocketFrame)
case NON_ZERO_RESERVED_BITS:
case UNEXPECTED_RESERVED_BIT:
case UNKNOWN_OPCODE:
case FRAME_MASKED:
case FRAGMENTED_CONTROL_FRAME:
case UNEXPECTED_CONTINUATION_FRAME:
case CONTINUATION_NOT_CLOSED:
case TOO_LONG_CONTROL_FRAME_PAYLOAD:
closeCode = WebSocketCloseCode.UNCONFORMED;
break;
// In this.readFrame()
case INTERRUPTED_IN_READING:
case IO_ERROR_IN_READING:
closeCode = WebSocketCloseCode.VIOLATED;
break;
// Others (unexpected)
default:
closeCode = WebSocketCloseCode.VIOLATED;
break;
}
return WebSocketFrame.createCloseFrame(closeCode, wse.getMessage());
}
private boolean handleFrame(WebSocketFrame frame)
{
// Notify the listeners that a frame was received.
callOnFrame(frame);
// Dispatch based on the opcode.
switch (frame.getOpcode())
{
case CONTINUATION:
return handleContinuationFrame(frame);
case TEXT:
return handleTextFrame(frame);
case BINARY:
return handleBinaryFrame(frame);
case CLOSE:
return handleCloseFrame(frame);
case PING:
return handlePingFrame(frame);
case PONG:
return handlePongFrame(frame);
default:
// Ignore the frame whose opcode is unknown. Keep reading.
return true;
}
}
private boolean handleContinuationFrame(WebSocketFrame frame)
{
// Notify the listeners that a continuation frame was received.
callOnContinuationFrame(frame);
// Append the continuation frame to the existing continuation sequence.
mContinuation.add(frame);
// If the frame is not the last one for the continuation.
if (frame.getFin() == false)
{
// Keep reading.
return true;
}
// Concatenate payloads of the frames. Decompression is performed
// when necessary.
byte[] data = getMessage(mContinuation);
// If the concatenation failed.
if (data == null)
{
// Stop reading.
return false;
}
// If the continuation forms a text message.
if (mContinuation.get(0).isTextFrame())
{
// Notify the listeners that a text message was received.
callOnTextMessage(data);
}
else
{
// Notify the listeners that a binary message was received.
callOnBinaryMessage(data);
}
// Clear the continuation.
mContinuation.clear();
// Keep reading.
return true;
}
private byte[] getMessage(List frames)
{
// Concatenate payloads of the frames.
byte[] data = concatenatePayloads(mContinuation);
// If the concatenation failed.
if (data == null)
{
// Stop reading.
return null;
}
// If a per-message compression extension is enabled and
// the Per-Message Compressed bit of the first frame is set.
if (mPMCE != null && frames.get(0).getRsv1())
{
// Decompress the data.
data = decompress(data);
}
return data;
}
private byte[] concatenatePayloads(List frames)
{
Throwable cause;
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// For each web socket frame.
for (WebSocketFrame frame : frames)
{
// Get the payload of the frame.
byte[] payload = frame.getPayload();
// If the payload is null or empty.
if (payload == null || payload.length == 0)
{
continue;
}
// Append the payload.
baos.write(payload);
}
// Return the concatenated byte array.
return baos.toByteArray();
}
catch (IOException e)
{
cause = e;
}
catch (OutOfMemoryError e)
{
cause = e;
}
// Create a WebSocketException which has a cause.
WebSocketException wse = new WebSocketException(
WebSocketError.MESSAGE_CONSTRUCTION_ERROR,
"Failed to concatenate payloads of multiple frames to construct a message: " + cause.getMessage(), cause);
// Notify the listeners that message construction failed.
callOnError(wse);
callOnMessageError(wse, frames);
// Create a close frame with a close code of 1009 which
// indicates that the message is too big to process.
WebSocketFrame frame = WebSocketFrame
.createCloseFrame(WebSocketCloseCode.OVERSIZE, wse.getMessage());
// Send the close frame.
mWebSocket.sendFrame(frame);
// Failed to construct a message.
return null;
}
private byte[] getMessage(WebSocketFrame frame)
{
// The raw payload of the frame.
byte[] payload = frame.getPayload();
// If a per-message compression extension is enabled and
// the Per-Message Compressed bit of the frame is set.
if (mPMCE != null && frame.getRsv1())
{
// Decompress the payload.
payload = decompress(payload);
}
return payload;
}
private byte[] decompress(byte[] input)
{
WebSocketException wse;
try
{
// Decompress the message.
return mPMCE.decompress(input);
}
catch (WebSocketException e)
{
wse = e;
}
// Notify the listeners that decompression failed.
callOnError(wse);
callOnMessageDecompressionError(wse, input);
// Create a close frame with a close code of 1003 which
// indicates that the message cannot be accepted.
WebSocketFrame frame = WebSocketFrame
.createCloseFrame(WebSocketCloseCode.UNACCEPTABLE, wse.getMessage());
// Send the close frame.
mWebSocket.sendFrame(frame);
// Failed to construct a message.
return null;
}
private boolean handleTextFrame(WebSocketFrame frame)
{
// Notify the listeners that a text frame was received.
callOnTextFrame(frame);
// If the frame indicates the start of fragmentation.
if (frame.getFin() == false)
{
// Start a continuation sequence.
mContinuation.add(frame);
// Keep reading.
return true;
}
// Get the payload of the frame. Decompression is performed
// when necessary.
byte[] payload = getMessage(frame);
// Notify the listeners that a text message was received.
callOnTextMessage(payload);
// Keep reading.
return true;
}
private boolean handleBinaryFrame(WebSocketFrame frame)
{
// Notify the listeners that a binary frame was received.
callOnBinaryFrame(frame);
// If the frame indicates the start of fragmentation.
if (frame.getFin() == false)
{
// Start a continuation sequence.
mContinuation.add(frame);
// Keep reading.
return true;
}
// Get the payload of the frame. Decompression is performed
// when necessary.
byte[] payload = getMessage(frame);
// Notify the listeners that a binary message was received.
callOnBinaryMessage(payload);
// Keep reading.
return true;
}
private boolean handleCloseFrame(WebSocketFrame frame)
{
// Get the manager which manages the state of the web socket.
StateManager manager = mWebSocket.getStateManager();
// The close frame sent from the server.
mCloseFrame = frame;
boolean stateChanged = false;
synchronized (manager)
{
// The current state of the web socket.
WebSocketState state = manager.getState();
// If the current state is neither CLOSING nor CLOSED.
if (state != CLOSING && state != CLOSED)
{
// Change the state to CLOSING.
manager.changeToClosing(CloseInitiator.SERVER);
// This web socket has not sent a close frame yet,
// so schedule sending a close frame.
// RFC 6455, 5.5.1. Close
//
// When sending a Close frame in response, the endpoint
// typically echos the status code it received.
//
// Simply reuse the frame.
mWebSocket.sendFrame(frame);
stateChanged = true;
}
}
if (stateChanged)
{
// Notify the listeners of the state change.
mWebSocket.getListenerManager().callOnStateChanged(CLOSING);
}
// Notify the listeners that a close frame was received.
callOnCloseFrame(frame);
// Stop reading.
return false;
}
private boolean handlePingFrame(WebSocketFrame frame)
{
// Notify the listeners that a ping frame was received.
callOnPingFrame(frame);
// RFC 6455, 5.5.3. Pong
//
// A Pong frame sent in response to a Ping frame must
// have identical "Application data" as found in the
// message body of the Ping frame being replied to.
// Create a pong frame which has the same payload as
// the ping frame.
WebSocketFrame pong = WebSocketFrame
.createPongFrame(frame.getPayload());
// Send the pong frame to the server.
mWebSocket.sendFrame(pong);
// Keep reading.
return true;
}
private boolean handlePongFrame(WebSocketFrame frame)
{
// Notify the listeners that a pong frame was received.
callOnPongFrame(frame);
// Keep reading.
return true;
}
private void waitForCloseFrame()
{
if (mNotWaitForCloseFrame)
{
return;
}
// If a close frame has already been received.
if (mCloseFrame != null)
{
return;
}
WebSocketFrame frame = null;
// Schedule a task which calls Socket.close() to prevent
// the code below from looping forever.
scheduleClose();
while (true)
{
try
{
// Read a frame from the server.
frame = mWebSocket.getInput().readFrame();
}
catch (Throwable t)
{
// Give up receiving a close frame.
break;
}
// If it is a close frame.
if (frame.isCloseFrame())
{
// Received a close frame. Finished.
mCloseFrame = frame;
break;
}
if (isInterrupted())
{
break;
}
}
}
private void notifyFinished()
{
mWebSocket.onReadingThreadFinished(mCloseFrame);
}
private void scheduleClose()
{
synchronized (mCloseLock)
{
cancelCloseTask();
scheduleCloseTask();
}
}
private void scheduleCloseTask()
{
mCloseTask = new CloseTask();
mCloseTimer = new Timer("ReadingThreadCloseTimer");
mCloseTimer.schedule(mCloseTask, mCloseDelay);
}
private void cancelClose()
{
synchronized (mCloseLock)
{
cancelCloseTask();
}
}
private void cancelCloseTask()
{
if (mCloseTimer != null)
{
mCloseTimer.cancel();
mCloseTimer = null;
}
if (mCloseTask != null)
{
mCloseTask.cancel();
mCloseTask = null;
}
}
private class CloseTask extends TimerTask
{
@Override
public void run()
{
try
{
Socket socket = mWebSocket.getSocket();
socket.close();
}
catch (Throwable t)
{
// Ignore.
}
}
}
}